/* * AdKats - Advanced In-Game Admin and Ban Enforcer for Procon Frostbite. * * Copyright 2014 A Different Kind, LLC * * AdKats was inspired by the gaming community A Different Kind (ADK). Visit http://www.ADKGamers.com/ for more information. * * The AdKats Frostbite Plugin is free software: You can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. AdKats is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. To view this license, visit http://www.gnu.org/licenses/. * * Code Credit: * Modded Levenshtein Distance algorithm and Tag Parsing from Micovery's InsaneLimits * Email System adapted from MorpheusX(AUT)'s "Notify Me!" * TeamSpeak Integration by Imisnew2 * Discord report posting by jbrunink * * Development by Daniel J. Gradinjan (ColColonCleaner) * * AdKats.cs * Version 7.6.1.3 * 15-Dec-2022 * * Automatic Update Information * 7.6.1.3 */ using System; using System.CodeDom.Compiler; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; using System.Net.Mail; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Reflection; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Windows.Forms; using Microsoft.CSharp; using MySql.Data.MySqlClient; using PRoCon.Core; using PRoCon.Core.Players; using PRoCon.Core.Players.Items; using PRoCon.Core.Plugin; using PRoCon.Core.Plugin.Commands; using PRoCon.Core.Maps; namespace PRoConEvents { public class AdKats : PRoConPluginAPI, IPRoConPluginInterface { //Current Plugin Version private const String PluginVersion = "7.6.1.3"; public enum GameVersionEnum { UNKNOWN, BF3, BF4, BFHL }; public enum RoundState { Loaded, Playing, Ended } public enum PopulationState { Unknown, Low, Medium, High, } public enum PlayerType { Player, Spectator, CommanderPC, CommanderMobile } public enum VersionStatus { OutdatedBuild, StableBuild, TestBuild, UnknownBuild, UnfetchedBuild } public enum AutoSurrenderAction { None, Surrender, Nuke, Vote } public enum VoipJoinDisplayType { Disabled, Say, Yell, Tell } private const string s = " | "; private const string t = "|"; private Utilities Util; //State private Boolean _LevelLoadShutdown; private const Boolean FullDebug = false; private volatile String _pluginChangelog; private volatile String _pluginDescription; private volatile String _pluginLinks; private volatile Boolean _pluginEnabled; private volatile Boolean _pluginRebootOnDisable; private volatile String _pluginRebootOnDisableSource; private volatile Boolean _threadsReady; private volatile String _latestPluginVersion; private Int64 _latestPluginVersionInt; private Int64 _currentPluginVersionInt; private volatile String _pluginVersionStatusString; private volatile VersionStatus _pluginVersionStatus = VersionStatus.UnfetchedBuild; private volatile Boolean _pluginUpdateServerInfoChecked; private volatile Boolean _pluginUpdatePatched; private volatile String _pluginPatchedVersion; private Int64 _pluginPatchedVersionInt; private volatile String _pluginUpdateProgress = "NotStarted"; private volatile String _pluginDescFetchProgress = "NotStarted"; private ARecord _pluginUpdateCaller; private volatile Boolean _useKeepAlive; private Int32 _startingTicketCount = -1; private RoundState _roundState = RoundState.Loaded; private DateTime _playingStartTime = DateTime.UtcNow; private Int32 _highestTicketCount; private Int32 _lowestTicketCount = 500000; private volatile Boolean _fetchedPluginInformation; private Boolean _firstUserListComplete; private Boolean _firstPlayerListStarted; private Boolean _firstPlayerListComplete; private String _vipKickedPlayerName; private Boolean _enforceSingleInstance = true; private GameVersionEnum GameVersion = GameVersionEnum.UNKNOWN; private Boolean _endingRound; private readonly AServer _serverInfo; private TimeSpan _previousRoundDuration = TimeSpan.Zero; private Int32 _soldierHealth = 100; private Int64 _settingImportID = -1; private Boolean _settingsFetched; private Boolean _settingsLocked; private String _settingsPassword; private Int32 _pingKicksThisRound; private Int32 _mapBenefitIndex; private Int32 _mapDetrimentIndex; private Int32 _pingKicksTotal; private Int32 _roundID; private Boolean _versionTrackingDisabled; private Boolean _automaticUpdatesDisabled; private String _currentFlagMessage; private Boolean _populationPopulating; private readonly Dictionary _populationPopulatingPlayers = new Dictionary(); private String _AdKatsLRTExtensionToken = String.Empty; private List _roundOverPlayers = null; private Int32 _MemoryUsageCurrent = 0; private Int32 _MemoryUsageWarn = 512; private Int32 _MemoryUsageRestartPlugin = 1024; private Int32 _MemoryUsageRestartProcon = 2048; //Debug private String _debugSoldierName = "ColColonCleaner"; private Boolean _toldCol; private Boolean _debugDisplayPlayerFetches; //Timing private readonly DateTime _proconStartTime = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _AdKatsStartTime = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _AdKatsRunningTime = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _commandStartTime = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _lastBanListCall = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _lastDbBanFetch = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _lastGUIDBanCountFetch = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _lastIPBanCountFetch = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _lastNameBanCountFetch = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _lastStatLoggerStatusUpdateTime = DateTime.UtcNow - TimeSpan.FromMinutes(60); private DateTime _lastSuccessfulBanList = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _populationTransitionTime = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _populationUpdateTime = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _lastDatabaseTimeout = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _lastDbActionFetch = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _lastDbSettingFetch = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _lastSettingPageUpdate = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _lastSettingPageUpdateRequest = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _lastUserFetch = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _LastPlayerMoveIssued = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _LastPluginDescFetch = DateTime.UtcNow - TimeSpan.FromSeconds(5); private DateTime _LastWeaponCodePost = DateTime.UtcNow - TimeSpan.FromHours(1); private DateTime _LastTicketRateDisplay = DateTime.UtcNow - TimeSpan.FromSeconds(30); private DateTime _lastAutoSurrenderTriggerTime = DateTime.UtcNow - TimeSpan.FromSeconds(10); private DateTime _LastBattlelogAction = DateTime.UtcNow - TimeSpan.FromSeconds(2); private DateTime _LastBattlelogIssue = DateTime.UtcNow - TimeSpan.FromSeconds(30); private DateTime _LastServerInfoTrigger = DateTime.UtcNow - TimeSpan.FromSeconds(30); private DateTime _LastServerInfoReceive = DateTime.UtcNow - TimeSpan.FromSeconds(30); private Object _battlelogLocker = new Object(); private TimeSpan _BattlelogWaitDuration = TimeSpan.FromSeconds(5); private DateTime _LastIPAPIAction = DateTime.UtcNow - TimeSpan.FromSeconds(5); private readonly TimeSpan _IPAPIWaitDuration = TimeSpan.FromSeconds(6); private Object _IPAPILocker = new Object(); private DateTime _LastGoogleAction = DateTime.UtcNow - TimeSpan.FromSeconds(0.3); private readonly TimeSpan _GoogleWaitDuration = TimeSpan.FromSeconds(0.3); private DateTime _lastGlitchedPlayerNotification = DateTime.UtcNow; private DateTime _lastInvalidPlayerNameNotification = DateTime.UtcNow; private DateTime _lastIPAPIError = DateTime.UtcNow; private DateTime _lastBattlelogFrequencyMessage = DateTime.UtcNow - TimeSpan.FromSeconds(5); private Queue _BattlelogActionTimes = new Queue(); private DateTime _LastPlayerListTrigger = DateTime.UtcNow - TimeSpan.FromSeconds(30); private Queue _PlayerListTriggerTimes = new Queue(); private DateTime _LastPlayerListReceive = DateTime.UtcNow - TimeSpan.FromSeconds(30); private Queue _PlayerListReceiveTimes = new Queue(); private DateTime _LastPlayerListAccept = DateTime.UtcNow - TimeSpan.FromSeconds(30); private Queue _PlayerListAcceptTimes = new Queue(); private DateTime _LastPlayerListProcessed = DateTime.UtcNow - TimeSpan.FromSeconds(30); private Queue _PlayerListProcessedTimes = new Queue(); private Boolean _DebugPlayerListing = false; private DateTime _LastDebugPlayerListingMessage = DateTime.UtcNow - TimeSpan.FromSeconds(30); private Queue _startupDurations = new Queue(); private DateTime _LastShortKeepAliveCheck = DateTime.UtcNow - TimeSpan.FromSeconds(30); private DateTime _LastLongKeepAliveCheck = DateTime.UtcNow - TimeSpan.FromSeconds(30); private DateTime _LastVeryLongKeepAliveCheck = DateTime.UtcNow - TimeSpan.FromSeconds(30); private DateTime _LastMemoryWarning = DateTime.UtcNow - TimeSpan.FromSeconds(30); //Server private PopulationState _populationStatus = PopulationState.Unknown; private readonly Dictionary _populationDurations = new Dictionary(); private Int32 _lowPopulationPlayerCount = 20; private Int32 _highPopulationPlayerCount = 40; private String _shortServerName = ""; private Boolean _automaticServerRestart = false; private Boolean _automaticServerRestartProcon = false; private Int32 _automaticServerRestartMinHours = 18; private Dictionary ReadableMaps = new Dictionary(); private Dictionary ReadableModes = new Dictionary(); //MySQL connection private String _mySqlSchemaName = ""; private String _mySqlHostname = ""; private String _mySqlPassword = ""; private String _mySqlPort = ""; private String _mySqlUsername = ""; private readonly MySqlConnectionStringBuilder _dbCommStringBuilder = new MySqlConnectionStringBuilder(); private Boolean _fetchActionsFromDb = true; private const Boolean UseConnectionPooling = true; private const Int32 MinConnectionPoolSize = 0; private const Int32 MaxConnectionPoolSize = 20; private const Boolean UseCompressedConnection = false; private const Int32 DatabaseTimeoutThreshold = 15; private const Int32 DatabaseSuccessThreshold = 5; private Boolean _databaseConnectionCriticalState; private Int32 _databaseSuccess; private Int32 _databaseTimeouts; private readonly List _DatabaseReaderDurations = new List(); private Double _DatabaseReadAverageDuration = 100; private readonly List _DatabaseNonQueryDurations = new List(); private Double _DatabaseWriteAverageDuration = 100; private volatile Boolean _dbSettingsChanged = true; private Boolean _dbTimingChecked; private Boolean _dbTimingValid; private TimeSpan _dbTimingOffset = TimeSpan.Zero; private Boolean _globalTimingChecked; private Boolean _globalTimingValid; private TimeSpan _globalTimingOffset = TimeSpan.Zero; private Boolean _timingValidOverride; private String _statLoggerVersion = "BF3"; //Action fetching private const Int32 DbActionFetchFrequency = 10; private const Int32 DbSettingFetchFrequency = 300; private const Int32 DbBanFetchFrequency = 60; //Event trigger dictionaries private readonly Dictionary _ActOnIsAliveDictionary = new Dictionary(); private readonly Dictionary _ActOnSpawnDictionary = new Dictionary(); private readonly Dictionary _LoadoutConfirmDictionary = new Dictionary(); private readonly Dictionary _ActionConfirmDic = new Dictionary(); private readonly Dictionary _RoundMutedPlayers = new Dictionary(); private readonly List _PlayerReports = new List(); private readonly HashSet _PlayersRequestingCommands = new HashSet(); //Threads private ThreadManager Threading; private Thread _Activator; private Thread _Finalizer; private Thread _DatabaseCommunicationThread; private Thread _MessageProcessingThread; private Thread _CommandParsingThread; private Thread _PlayerListingThread; private Thread _TeamSwapThread; private Thread _BanEnforcerThread; private Thread _RoundTimerThread; private Thread _KillProcessingThread; private Thread _AntiCheatThread; private Thread _DisconnectHandlingThread; private Thread _AccessFetchingThread; private Thread _ActionHandlingThread; private Thread _BattlelogCommThread; private Thread _IPAPICommThread; //Threading queues private readonly Queue _BanEnforcerCheckingQueue = new Queue(); private readonly Queue _BanEnforcerProcessingQueue = new Queue(); private readonly Queue _CBanProcessingQueue = new Queue(); private readonly Queue _CommandRemovalQueue = new Queue(); private readonly Queue _CommandUploadQueue = new Queue(); private readonly Queue _AntiCheatQueue = new Queue(); private readonly Queue _KillProcessingQueue = new Queue(); private readonly Queue> _PlayerListProcessingQueue = new Queue>(); private readonly Queue _PlayerRemovalProcessingQueue = new Queue(); private readonly Queue _RoleRemovalQueue = new Queue(); private readonly Queue _RoleUploadQueue = new Queue(); private readonly Queue _SettingUploadQueue = new Queue(); private readonly Queue _UnparsedCommandQueue = new Queue(); private readonly Queue _UnparsedMessageQueue = new Queue(); private readonly Queue _UnprocessedActionQueue = new Queue(); private readonly Queue _UnprocessedRecordQueue = new Queue(); private readonly Queue _UnprocessedStatisticQueue = new Queue(); private readonly Queue _UserRemovalQueue = new Queue(); private readonly Queue _UserUploadQueue = new Queue(); private readonly Queue _BattlelogFetchQueue = new Queue(); private readonly Queue _IPInfoFetchQueue = new Queue(); //Threading wait handles private EventWaitHandle _WeaponStatsWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private EventWaitHandle _AccessFetchWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private EventWaitHandle _TeamswapWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private EventWaitHandle _StatLoggerStatusWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private EventWaitHandle _ServerInfoWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private EventWaitHandle _RoundEndingWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private EventWaitHandle _PlayerListUpdateWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private EventWaitHandle _MessageParsingWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private EventWaitHandle _KillProcessingWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private EventWaitHandle _AntiCheatWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private EventWaitHandle _DbCommunicationWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private EventWaitHandle _CommandParsingWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private EventWaitHandle _BanEnforcerWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private EventWaitHandle _ActionHandlingWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private EventWaitHandle _PlayerProcessingWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private EventWaitHandle _PluginDescriptionWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private EventWaitHandle _BattlelogCommWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); private readonly EventWaitHandle _IPInfoWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); //Procon MatchCommand private readonly MatchCommand _PluginEnabledMatchCommand; private readonly MatchCommand _fetchAuthorizedSoldiersMatchCommand; private readonly MatchCommand _subscribeAsClientMatchCommand; private readonly MatchCommand _issueCommandMatchCommand; //Commands global private readonly Dictionary _CommandIDDictionary = new Dictionary(); private readonly Dictionary _CommandKeyDictionary = new Dictionary(); private readonly Dictionary _CommandNameDictionary = new Dictionary(); private readonly Dictionary _CommandTextDictionary = new Dictionary(); private readonly Dictionary _CommandDescriptionDictionary = new Dictionary(); private readonly Dictionary> _commandTimeoutDictionary = new Dictionary>(); private readonly Dictionary _commandUsageTimes = new Dictionary(); private Boolean _AllowAdminSayCommands = true; private Boolean _ReservedSquadLead = false; private Boolean _ReservedSelfMove = false; private Boolean _ReservedSelfKill = false; private Boolean _bypassCommandConfirmation = false; private List _ExternalPlayerCommands = new List(); private List _ExternalAdminCommands = new List(); private List _CommandTargetWhitelistCommands = new List(); private Int32 _RequiredReasonLength = 4; private Int32 _minimumAssistMinutes = 5; //Commands specific private String _ServerVoipAddress = "Enter teamspeak/discord/etc address here."; //Dynamic access public Func AAPerkFunc = ((plugin, aPlayer) => ((plugin._EnableAdminAssistantPerk && aPlayer.player_aa) || (aPlayer.player_reputation > _reputationThresholdGood))); public Func TeamSwapFunc = ((plugin, aPlayer) => ((plugin._EnableAdminAssistantPerk && aPlayer.player_aa) || plugin.GetMatchingVerboseASPlayersOfGroup("whitelist_teamswap", aPlayer).Any())); //Roles private readonly Dictionary _RoleIDDictionary = new Dictionary(); private readonly Dictionary _RoleKeyDictionary = new Dictionary(); private readonly Dictionary _RoleNameDictionary = new Dictionary(); private Boolean _PlayerRoleRefetch; private readonly Dictionary _RoleCommandCache = new Dictionary(); private DateTime _RoleCommandCacheUpdate = DateTime.UtcNow - TimeSpan.FromMinutes(5); private TimeSpan _RoleCommandCacheUpdateBufferDuration = TimeSpan.FromSeconds(5); private DateTime _RoleCommandCacheUpdateBufferStart = DateTime.UtcNow - TimeSpan.FromSeconds(5); //Users private const Int32 DbUserFetchFrequency = 300; private readonly Dictionary _userCache = new Dictionary(); private readonly Dictionary _specialPlayerGroupIDDictionary = new Dictionary(); private readonly Dictionary _specialPlayerGroupKeyDictionary = new Dictionary(); private readonly Dictionary _baseSpecialPlayerCache = new Dictionary(); private readonly Dictionary _verboseSpecialPlayerCache = new Dictionary(); //Games and teams private readonly Dictionary _gameIDDictionary = new Dictionary(); private readonly Dictionary _teamDictionary = new Dictionary(); private Boolean _acceptingTeamUpdates; private readonly Dictionary _unmatchedRoundDeathCounts = new Dictionary(); private readonly HashSet _unmatchedRoundDeaths = new HashSet(); private readonly Dictionary _roundAssists = new Dictionary(); //Players private readonly Dictionary _PlayerDictionary = new Dictionary(); private readonly List _MissingPlayers = new List(); private readonly List _RoundPrepSquads = new List(); private readonly Dictionary _PlayerLeftDictionary = new Dictionary(); private readonly Dictionary _FetchedPlayers = new Dictionary(); private readonly Dictionary> _RoundPlayerIDs = new Dictionary>(); //Punishment settings private readonly List _PunishmentSeverityIndex; private Boolean _CombineServerPunishments; private Boolean _AutomaticForgives; private Int32 _AutomaticForgiveLastPunishDays = 30; private Int32 _AutomaticForgiveLastForgiveDays = 14; private Boolean _IROActive = true; private Boolean _IROOverridesLowPop; private Int32 _IROOverridesLowPopInfractions = 5; private Int32 _IROTimeout = 10; private Boolean _OnlyKillOnLowPop = true; private String[] _PunishmentHierarchy = { "kill", "kick", "tban120", "kill", "kick", "tbanday", "kick", "tbanweek", "kick", "tban2weeks", "kick", "tbanmonth", "kick", "ban" }; //Teamswap private Int32 _TeamSwapTicketWindowHigh = 500000; private Int32 _TeamSwapTicketWindowLow; private Queue _Team1MoveQueue = new Queue(); private Queue _Team2MoveQueue = new Queue(); private Queue _TeamswapForceMoveQueue = new Queue(); private Queue _TeamswapOnDeathCheckingQueue = new Queue(); private readonly Dictionary _TeamswapOnDeathMoveDic = new Dictionary(); //AFK manager private Boolean _AFKManagerEnable; private Boolean _AFKAutoKickEnable; private Double _AFKTriggerDurationMinutes = 5; private Int32 _AFKTriggerMinimumPlayers = 20; private Boolean _AFKIgnoreUserList = true; private String[] _AFKIgnoreRoles = { }; private Boolean _AFKIgnoreChat; //Ping enforcer private Boolean _pingEnforcerEnable; private Int32 _pingEnforcerTriggerMinimumPlayers = 50; private Double _pingEnforcerLowTriggerMS = 300; private Int32[] _pingEnforcerLowTimeModifier = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; private Double _pingEnforcerMedTriggerMS = 300; private Int32[] _pingEnforcerMedTimeModifier = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; private Double _pingEnforcerHighTriggerMS = 300; private Int32[] _pingEnforcerHighTimeModifier = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; private Double _pingEnforcerFullTriggerMS = 300; private Int32[] _pingEnforcerFullTimeModifier = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; private Double _pingMovingAverageDurationSeconds = 180; private Boolean _pingEnforcerKickMissingPings = true; private Boolean _pingEnforcerIgnoreUserList = true; private String _pingEnforcerMessagePrefix = "Please fix your ping and join us again."; private String[] _pingEnforcerIgnoreRoles = { }; private Boolean _attemptManualPingWhenMissing = false; private Boolean _pingEnforcerDisplayProconChat = true; //Commander manager private Boolean _CMDRManagerEnable = false; private Int32 _CMDRMinimumPlayers = 40; //Ban enforcer private Boolean _UseBanAppend; private String _BanAppend = "Appeal at your_site.com"; private Boolean _UseBanEnforcer; private Boolean _UseBanEnforcerPreviousState; private Boolean _BanEnforcerBF4LenientKick = false; private List _BanEnforcerSearchResults = new List(); private Boolean _BansQueuing; private String _CBanAdminName = "BanEnforcer"; private Boolean _DefaultEnforceGUID = true; private Boolean _DefaultEnforceIP = false; private Boolean _DefaultEnforceName = false; private Int64 _GUIDBanCount = -1; private Int64 _IPBanCount = -1; private Int64 _NameBanCount = -1; private TimeSpan _MaxTempBanDuration = TimeSpan.FromDays(3650); //Reports public String[] _AutoReportHandleStrings = { }; private Boolean _InformReportedPlayers; private String[] _PlayerInformExclusionStrings = { }; private Int32 _MinimumReportHandleSeconds; //Email private Boolean _UseEmail; //PushBullet private Boolean _UsePushBullet; //Muting private Int32 _MutedPlayerChances = 5; private String _MutedPlayerKickMessage = "Talking excessively while muted."; private String _MutedPlayerKillMessage = "Do not talk while muted. You can speak again next round."; private String _MutedPlayerMuteMessage = "You have been muted by an admin, talking will cause punishment. You can speak again next round."; private Boolean _MutedPlayerIgnoreCommands = true; //Surrender private Boolean _surrenderVoteEnable; private Double _surrenderVoteMinimumPlayerPercentage = 30; private Int32 _surrenderVoteMinimumPlayerCount = 16; private Int32 _surrenderVoteMinimumTicketGap = 250; private Boolean _surrenderVoteTicketRateGapEnable; private Double _surrenderVoteMinimumTicketRateGap = 10; private Boolean _surrenderVoteTimeoutEnable; private Double _surrenderVoteTimeoutMinutes = 5; private Boolean _surrenderVoteActive; private Boolean _surrenderVoteSucceeded; private DateTime _surrenderVoteStartTime = DateTime.UtcNow; private readonly HashSet _surrenderVoteList = new HashSet(); private readonly HashSet _nosurrenderVoteList = new HashSet(); //Auto-Surrender private Boolean _surrenderAutoEnable; private Boolean _surrenderAutoSucceeded; private Boolean _surrenderAutoUseMetroValues; private Boolean _surrenderAutoUseLockerValues; private Int32 _surrenderAutoMinimumTicketGap = 100; private Int32 _surrenderAutoMinimumTicketCount = 100; private Int32 _surrenderAutoMaximumTicketCount = 999; private Double _surrenderAutoLosingRateMax = 999; private Double _surrenderAutoLosingRateMin = 999; private Double _surrenderAutoWinningRateMax = 999; private Double _surrenderAutoWinningRateMin = 999; private Int32 _surrenderAutoTriggerCountToSurrender = 10; private Boolean _surrenderAutoResetTriggerCountOnCancel = true; private Boolean _surrenderAutoResetTriggerCountOnFire = true; private Int32 _surrenderAutoTriggerCountCurrent; private Int32 _surrenderAutoTriggerCountPause; private Int32 _surrenderAutoMinimumPlayers = 10; private String _surrenderAutoMessage = "Auto-Resolving Round. %WinnerName% Wins!"; private Boolean _surrenderAutoNukeInstead; private Boolean _nukeAutoSlayActive = false; private Int32 _surrenderAutoNukeDurationHigh = 0; private Int32 _surrenderAutoNukeDurationMed = 0; private Int32 _surrenderAutoNukeDurationLow = 0; private Int32 _nukeAutoSlayActiveDuration = 0; private String _lastNukeSlayDurationMessage = null; private Int32 _surrenderAutoNukeDurationIncrease = 0; private Int32 _surrenderAutoNukeDurationIncreaseTicketDiff = 100; private Int32 _surrenderAutoNukeMinBetween = 60; private DateTime _lastNukeTime = DateTime.UtcNow - TimeSpan.FromMinutes(10); private ATeam _lastNukeTeam; private Boolean _surrenderAutoAnnounceNukePrep = true; private Boolean _surrenderAutoNukeLosingTeams = false; private Int32 _surrenderAutoNukeLosingMaxDiff = 200; private Boolean _surrenderAutoNukeResolveAfterMax = false; private Int32 _surrenderAutoMaxNukesEachRound = 4; private Dictionary _nukesThisRound = new Dictionary(); private Boolean _surrenderAutoTriggerVote; private String _surrenderAutoNukeMessage = "Nuking %WinnerName% for baserape!"; private Int32 _NukeCountdownDurationSeconds = 0; private Int32 _NukeWinningTeamUpTicketCount = 9999; private Boolean _NukeWinningTeamUpTicketHigh = true; //EmailHandler private EmailHandler _EmailHandler; private Boolean _EmailReportsOnlyWhenAdminless; //PushBullet private PushBulletHandler _PushBulletHandler; private Boolean _PushBulletReportsOnlyWhenAdminless; //Perks private String[] _PerkSpecialPlayerGroups = { "slot_reserved", "slot_spectator", "whitelist_report", "whitelist_spambot", "whitelist_adminassistant", "whitelist_ping", "whitelist_anticheat", "whitelist_multibalancer", "whitelist_populator", "whitelist_teamkill" }; private Boolean _UsePerkExpirationNotify = false; private Int32 _PerkExpirationNotifyDays = 7; //Orchestration private List _CurrentReservedSlotPlayers = new List(); private List _CurrentSpectatorListPlayers; private Boolean _FeedMultiBalancerWhitelist; private Boolean _FeedMultiBalancerWhitelist_Admins = true; private Boolean _FeedMultiBalancerDisperseList; private Boolean _FeedTeamKillTrackerWhitelist; private Boolean _FeedTeamKillTrackerWhitelist_Admins; private Boolean _FeedServerReservedSlots; private Boolean _FeedServerReservedSlots_VSM; private Boolean _FeedServerReservedSlots_Admins = true; private Boolean _FeedServerReservedSlots_Admins_VIPKickWhitelist = false; private Boolean _FeedServerSpectatorList; private Boolean _FeedServerSpectatorList_Admins; private Boolean _FeedStatLoggerSettings; private Boolean _PostStatLoggerChatManually; private Boolean _PostStatLoggerChatManually_PostServerChatSpam = true; private Boolean _PostStatLoggerChatManually_IgnoreCommands; private Boolean _PostMapBenefitStatistics; private Boolean _MULTIBalancerUnswitcherDisabled; public readonly String[] _subscriptionGroups = { "OnlineSoldiers" }; private readonly List _subscribedClients = new List(); private String[] _BannedTags = { }; private DateTime _AutoKickNewPlayerDate = DateTime.UtcNow + TimeSpan.FromDays(7300); //Team Power Monitor private Boolean _UseTeamPowerMonitorSeeders = false; private Boolean _UseTeamPowerDisplayBalance = false; private Boolean _UseTeamPowerMonitorScrambler = false; private Boolean _ScrambleRequiredTeamsRemoved = false; private Boolean _UseTeamPowerMonitorReassign = false; private Boolean _UseTeamPowerMonitorReassignLenient = false; private Double _TeamPowerMonitorReassignLenientPercent = 30; private Boolean _UseTeamPowerMonitorUnswitcher = false; private Boolean currentStartingTeam1 = true; private Boolean _PlayersAutoAssistedThisRound = false; private Double _TeamPowerActiveInfluence = 35; //Populators private Boolean _PopulatorMonitor; private Boolean _PopulatorUseSpecifiedPopulatorsOnly; private Boolean _PopulatorPopulatingThisServerOnly; private Int32 _PopulatorMinimumPopulationCountPastWeek = 5; private Int32 _PopulatorMinimumPopulationCountPast2Weeks = 10; private readonly Dictionary _populatorPlayers = new Dictionary(); private Boolean _PopulatorPerksEnable; private Boolean _PopulatorPerksReservedSlot; private Boolean _PopulatorPerksBalanceWhitelist; private Boolean _PopulatorPerksPingWhitelist; private Boolean _PopulatorPerksTeamKillTrackerWhitelist; //Teamspeak private readonly TeamSpeakClientViewer _TeamspeakManager; private Boolean _TeamspeakPlayerMonitorView; private Boolean _TeamspeakPlayerMonitorEnable; private readonly Dictionary _TeamspeakPlayers = new Dictionary(); private Boolean _TeamspeakPlayerPerksEnable; private Boolean _TeamspeakPlayerPerksVIPKickWhitelist; private Boolean _TeamspeakPlayerPerksBalanceWhitelist; private Boolean _TeamspeakPlayerPerksPingWhitelist; private Boolean _TeamspeakPlayerPerksTeamKillTrackerWhitelist; //Discord private readonly DiscordManager _DiscordManager; private Boolean _DiscordPlayerMonitorView; private Boolean _DiscordPlayerMonitorEnable; private readonly Dictionary _DiscordPlayers = new Dictionary(); private Boolean _DiscordPlayerRequireVoiceForAdmin; private Boolean _DiscordPlayerPerksEnable; private Boolean _DiscordPlayerPerksVIPKickWhitelist; private Boolean _DiscordPlayerPerksBalanceWhitelist; private Boolean _DiscordPlayerPerksPingWhitelist; private Boolean _DiscordPlayerPerksTeamKillTrackerWhitelist; private Boolean _UseDiscordForReports; private Boolean _DiscordReportsOnlyWhenAdminless; private Boolean _DiscordReportsLeftWithoutAction; //Challenge private AChallengeManager ChallengeManager; //AntiCheat private Boolean _useAntiCheatLIVESystem = true; private Boolean _AntiCheatLIVESystemActiveStats = false; private Boolean _UseHskChecker; private Boolean _UseKpmChecker; private Double _HskTriggerLevel = 60.0; private Double _KpmTriggerLevel = 5.0; private String _AntiCheatDPSBanMessage = "DPS Automatic Ban"; private String _AntiCheatHSKBanMessage = "HSK Automatic Ban"; private String _AntiCheatKPMBanMessage = "KPM Automatic Ban"; //External commands private readonly String _instanceKey = GetRandom32BitHashCode(); //Admin assistants public Boolean _EnableAdminAssistantPerk = false; public Boolean _EnableAdminAssistants = false; public Int32 _MinimumRequiredMonthlyReports = 10; public Boolean _UseAAReportAutoHandler = false; //Messaging private List _PreMessageList; private Boolean _RequirePreMessageUse; private Boolean _ShowAdminNameInAnnouncement; private Boolean _ShowNewPlayerAnnouncement = true; private Boolean _ShowPlayerNameChangeAnnouncement = true; private Boolean _ShowTargetedPlayerLeftNotification = true; private Int32 _YellDuration = 5; private Boolean _UseFirstSpawnMessage; private Boolean _useFirstSpawnRepMessage; private String _FirstSpawnMessage = "FIRST SPAWN MESSAGE"; private Boolean _DisplayTicketRatesInProconChat; private Boolean _InformReputablePlayersOfAdminJoins = false; private Boolean _InformAdminsOfAdminJoins = true; private Boolean _UseAllCapsLimiter = false; private Boolean _AllCapsLimiterSpecifiedPlayersOnly = false; private Int32 _AllCapsLimterPercentage = 80; private Int32 _AllCapsLimterMinimumCharacters = 15; private Int32 _AllCapsLimiterWarnThreshold = 3; private Int32 _AllCapsLimiterKillThreshold = 5; private Int32 _AllCapsLimiterKickThreshold = 6; //SpamBot private Boolean _spamBotEnabled; private List _spamBotSayList; private readonly Queue _spamBotSayQueue = new Queue(); private Int32 _spamBotSayDelaySeconds = 300; private DateTime _spamBotSayLastPost = DateTime.UtcNow - TimeSpan.FromSeconds(300); private List _spamBotYellList; private readonly Queue _spamBotYellQueue = new Queue(); private Int32 _spamBotYellDelaySeconds = 600; private DateTime _spamBotYellLastPost = DateTime.UtcNow - TimeSpan.FromSeconds(600); private List _spamBotTellList; private readonly Queue _spamBotTellQueue = new Queue(); private Int32 _spamBotTellDelaySeconds = 900; private DateTime _spamBotTellLastPost = DateTime.UtcNow - TimeSpan.FromSeconds(900); private Boolean _spamBotExcludeAdminsAndWhitelist; //Rules private Double _ServerRulesDelay = 0.5; private Double _ServerRulesInterval = 5; private String[] _ServerRulesList = { "No AdKats rules have been set." }; private Boolean _ServerRulesNumbers = true; private Boolean _ServerRulesYell; //Locking private Double _playerLockingManualDuration = 10; private Boolean _playerLockingAutomaticLock; private Double _playerLockingAutomaticDuration = 2.5; //Round monitor private Boolean _useRoundTimer; private Double _maxRoundTimeMinutes = 30; //Reputation private Dictionary _commandSourceReputationDictionary; private Dictionary _commandTargetReputationDictionary; private const Double _reputationThresholdGood = 75; private const Double _reputationThresholdBad = 0; //Assist private Queue _AssistAttemptQueue = new Queue(); DateTime _LastAutoAssist = DateTime.UtcNow - TimeSpan.FromSeconds(300); //Battlecries public enum BattlecryVolume { Say, Yell, Tell, Disabled } private BattlecryVolume _battlecryVolume = BattlecryVolume.Disabled; private Int32 _battlecryMaxLength = 100; private String[] _battlecryDeniedWords = { }; //Faction randomizer public enum FactionRandomizerRestriction { NoRestriction, NeverSameFaction, AlwaysSameFaction, AlwaysSwapUSvsRU, AlwaysSwapUSvsCN, AlwaysSwapRUvsCN, AlwaysBothUS, AlwaysBothRU, AlwaysBothCN, AlwaysUSvsX, AlwaysRUvsX, AlwaysCNvsX, NeverUSvsX, NeverRUvsX, NeverCNvsX } private Boolean _factionRandomizerEnable = false; private FactionRandomizerRestriction _factionRandomizerRestriction = FactionRandomizerRestriction.NoRestriction; private Boolean _factionRandomizerAllowRepeatSelection = true; private Int32 _factionRandomizerCurrentTeam1 = 0; private Int32 _factionRandomizerCurrentTeam2 = 1; //MapModes public List _AvailableMapModes = null; //Weapon stats protected AWeaponDictionary WeaponDictionary; private StatLibrary _StatLibrary; HashSet _AntiCheatCheckedPlayers = new HashSet(); HashSet _AntiCheatCheckedPlayersStats = new HashSet(); //Polling private APoll _ActivePoll = null; private TimeSpan _PollMaxDuration = TimeSpan.FromMinutes(4); private TimeSpan _PollPrintInterval = TimeSpan.FromSeconds(30); private Int32 _PollMaxVotes = 18; private String[] _AvailablePolls = new String[] { "event" }; //Experimental private Boolean _UseExperimentalTools; private Boolean _ShowQuerySettings; private Boolean _DebugKills; private readonly Ping _PingProcessor = new Ping(); private Boolean _UseGrenadeCookCatcher; private Dictionary _RoundCookers = new Dictionary(); private Boolean _UseWeaponLimiter; private String _WeaponLimiterExceptionString = "_Flechette|_Slug|_Dart|_SHG"; private String _WeaponLimiterString = "ROADKILL|Death|_LVG|_HE|_Frag|_XM25|_FLASH|_V40|_M34|_Flashbang|_SMK|_Smoke|_FGM148|_Grenade|_SLAM|_NLAW|_RPG7|_C4|_Claymore|_FIM92|_M67|_SMAW|_SRAW|_Sa18IGLA|_Tomahawk|_3GL|USAS|MGL|UCAV"; //Events private Boolean _EventWeeklyRepeat = false; private DayOfWeek _EventWeeklyDay = DayOfWeek.Saturday; private Boolean _EventPollAutomatic = false; private Boolean _eventPollYellWinningRule = true; private DateTime _EventDate = GetLocalEpochTime(); private Double _EventHour = 0; private Int32 _EventTestRoundNumber = 999999; private Double _EventAnnounceDayDifference = 7; private Int32 _CurrentEventRoundNumber = 999999; private List _EventRoundOptions = new List(); private Boolean _EventRoundPolled = false; private Int32 _EventPollMaxOptions = 4; private Int32 _EventRoundAutoPollsMax = 7; private TimeSpan _EventRoundAutoVoteDuration = TimeSpan.FromMinutes(2.5); private List _EventRoundPollOptions = new List(); private String _EventRoundOptionsEnum; private String _eventBaseServerName = "Event Base Server Name"; private String _eventCountdownServerName = "Event Countdown Server Name"; private String _eventConcreteCountdownServerName = "Event Concrete Countdown Server Name"; private String _eventActiveServerName = "Event Active Server Name"; private readonly HashSet _DetectedWeaponCodes = new HashSet(); // Proxy private Boolean _UseProxy = false; private String _ProxyURL = ""; //Settings display private Dictionary _SettingSections = new Dictionary(); private String _SettingSectionEnum; private String _CurrentSettingSection; private const String _AllSettingSections = "All Settings .*"; public readonly Logger Log; public AdKats() { Log = new Logger(this); Util = new Utilities(Log); Threading = new ThreadManager(Log); //Create the server reference _serverInfo = new AServer(this); //Set defaults for webclient ServicePointManager.Expect100Continue = false; //By default plugin is not enabled or ready _pluginEnabled = false; _threadsReady = false; //Assign the match commands _PluginEnabledMatchCommand = new MatchCommand("AdKats", "PluginEnabled", new List(), "AdKats_PluginEnabled", new List(), new ExecutionRequirements(ExecutionScope.None), "Useable by other plugins to check if AdKats is enabled or in process of starting up."); _issueCommandMatchCommand = new MatchCommand("AdKats", "IssueCommand", new List(), "AdKats_IssueCommand", new List(), new ExecutionRequirements(ExecutionScope.None), "Useable by other plugins to call AdKats commands."); _fetchAuthorizedSoldiersMatchCommand = new MatchCommand("AdKats", "FetchAuthorizedSoldiers", new List(), "AdKats_FetchAuthorizedSoldiers", new List(), new ExecutionRequirements(ExecutionScope.None), "Useable by other plugins to fetch authorized soldiers."); _subscribeAsClientMatchCommand = new MatchCommand("AdKats", "SubscribeAsClient", new List(), "AdKats_SubscribeAsClient", new List(), new ExecutionRequirements(ExecutionScope.None), "Useable by other plugins to subscribe to group events."); //Debug level is 0 by default Log.DebugLevel = 0; //Setting Sections AddSettingSection("*", _AllSettingSections); AddSettingSection("0", "Instance Settings"); AddSettingSection("1", "Server Settings"); AddSettingSection("2", "MySQL Settings"); AddSettingSection("3", "User Settings"); AddSettingSection("3-2", "Special Player Display"); AddSettingSection("3-3", "Verbose Special Player Display"); AddSettingSection("4", "Role Settings"); AddSettingSection("4-2", "Role Group Settings"); AddSettingSection("5", "Command Settings"); AddSettingSection("6", "Command List"); AddSettingSection("7", "Punishment Settings"); AddSettingSection("8", "Email Settings"); AddSettingSection("8-2", "PushBullet Settings"); AddSettingSection("8-3", "Discord WebHook Settings"); AddSettingSection("9", "TeamSwap Settings"); AddSettingSection("A10", "Admin Assistant Settings"); AddSettingSection("A11", "Player Mute Settings"); AddSettingSection("A12", "Messaging Settings"); AddSettingSection("A12-2", "SpamBot Settings"); AddSettingSection("A12-3", "Battlecry Settings - Thanks WDF"); AddSettingSection("A12-4", "All-Caps Chat Monitor"); AddSettingSection("A13", "Banning Settings"); AddSettingSection("A13-2", "Ban Enforcer Settings"); AddSettingSection("A13-3", "Mini Ban Management"); AddSettingSection("A14", "External Command Settings"); AddSettingSection("A15", "VOIP Settings"); AddSettingSection("A16", "Orchestration Settings"); AddSettingSection("A17", "Round Settings"); AddSettingSection("A17-2", "Round Faction Randomizer Settings - Thanks FPSG"); AddSettingSection("A18", "AntiCheat Settings"); AddSettingSection("A19", "Server Rules Settings"); AddSettingSection("B20", "AFK Settings"); AddSettingSection("B21", "Ping Enforcer Settings"); AddSettingSection("B22", "Commander Manager Settings"); AddSettingSection("B23", "Player Locking Settings"); AddSettingSection("B24", "Surrender Vote Settings"); AddSettingSection("B25", "Auto-Surrender Settings"); AddSettingSection("B25-2", "Auto-Nuke Settings"); AddSettingSection("B26", "Statistics Settings"); AddSettingSection("B27", "Populator Monitor Settings - Thanks CMWGaming"); AddSettingSection("B28", "Teamspeak Player Monitor Settings - Thanks CMWGaming"); AddSettingSection("B29", "Discord Player Monitor Settings"); AddSettingSection("C30", "Team Power Monitor"); AddSettingSection("C31", "Weapon Limiter Settings"); AddSettingSection("C32", "Challenge Settings"); AddSettingSection("D98", "Database Timing Mismatch"); AddSettingSection("D99", "Debugging"); AddSettingSection("X98", "Proxy Settings"); AddSettingSection("X99", "Experimental"); AddSettingSection("Y99", "Event Automation"); //Build setting section enum _SettingSectionEnum = String.Empty; Random random = new Random(Environment.TickCount); var sections = _SettingSections.Keys.ToList(); sections.Sort(); foreach (String sectionKey in sections) { if (String.IsNullOrEmpty(_SettingSectionEnum)) { _SettingSectionEnum += "enum.SettingSectionEnum_" + random.Next(100000, 999999) + "("; } else { _SettingSectionEnum += "|"; } _SettingSectionEnum += GetSettingSection(sectionKey); } _SettingSectionEnum += ")"; //Set default setting section _CurrentSettingSection = GetSettingSection("*"); //Build event round options enum _EventRoundOptionsEnum = String.Empty; random = new Random(Environment.TickCount); foreach (String map in AEventOption.MapNames.Values) { foreach (String mode in AEventOption.ModeNames.Values) { foreach (String rule in AEventOption.RuleNames.Values.Where(ruleValue => ruleValue != AEventOption.RuleNames[AEventOption.RuleCode.ENDEVENT])) { if (String.IsNullOrEmpty(_EventRoundOptionsEnum)) { _EventRoundOptionsEnum += "enum.EventRoundOptionsEnum_" + random.Next(100000, 999999) + "(Remove|"; } else { _EventRoundOptionsEnum += "|"; } _EventRoundOptionsEnum += map + "/" + mode + "/" + rule; } } } _EventRoundOptionsEnum += ")"; //Init the punishment severity index _PunishmentSeverityIndex = new List { "warn", "kill", "kick", "tban60", "tban120", "tbanday", "tban2days", "tban3days", "tbanweek", "tban2weeks", "tbanmonth", "ban" }; //Init the pre-message list _PreMessageList = new List { "Predefined message 1", "Predefined message 2", "Predefined message 3", "Predefined message 4", "Predefined message 5", }; //Init the spam message lists _spamBotSayList = new List { "AdminSay1", "AdminSay2", "AdminSay3" }; foreach (String line in _spamBotSayList) { _spamBotSayQueue.Enqueue(line); } _spamBotYellList = new List { "AdminYell1", "AdminYell2", "AdminYell3" }; foreach (String line in _spamBotYellList) { _spamBotYellQueue.Enqueue(line); } _spamBotTellList = new List { "AdminTell1", "AdminTell2", "AdminTell3" }; foreach (String line in _spamBotTellList) { _spamBotTellQueue.Enqueue(line); } //Fill the population durations foreach (PopulationState popState in Enum.GetValues(typeof(PopulationState)).Cast()) { _populationDurations[popState] = TimeSpan.Zero; } //Fetch the plugin description and changelog FetchPluginDocumentation(); //Fill command descriptions FillCommandDescDictionary(); //Prepare the keep-alive threads SetupStatusMonitor(); SetupFastStatusMonitor(); //Start up TeamSpeakClientViewer _TeamspeakManager = new TeamSpeakClientViewer(this); _DiscordManager = new DiscordManager(this); FillReadableMapModeDictionaries(); try { //Initialize the weapon name dictionary WeaponDictionary = new AWeaponDictionary(this); //Initialize the challenge manager ChallengeManager = new AChallengeManager(this); } catch (Exception e) { Log.HandleException(new AException("Error while enabling weapon dictionary or challenge manager.", e)); } } public String GetPluginName() { return "AdKats - Advanced In-Game Admin"; } public String GetPluginVersion() { return PluginVersion; } public String GetPluginAuthor() { return "ColColonCleaner"; } public String GetPluginWebsite() { return "github.com/AdKats/AdKats"; } public String GetPluginDescription() { String concat = @"

AdKats Advanced In-Game Admin Tools

"; try { if (!_fetchedPluginInformation) { //Wait up to 10 seconds for the description to fetch Log.Debug(() => "Waiting for plugin information...", 1); _PluginDescriptionWaitHandle.WaitOne(10000); } //Parse out the descriptions if (!String.IsNullOrEmpty(_pluginVersionStatusString)) { concat += _pluginVersionStatusString; } if (!String.IsNullOrEmpty(_pluginLinks)) { concat += _pluginLinks; } } catch (Exception e) { Log.HandleException(new AException("Error while fetching plugin information.", e)); } return concat; } private String AddSettingSection(String number, String desc) { _SettingSections[number] = desc; return GetSettingSection(number); } private String GetSettingSection(String number) { return number + ". " + _SettingSections[number]; } private Boolean IsActiveSettingSection(String number) { return _CurrentSettingSection == GetSettingSection("*") || _CurrentSettingSection == GetSettingSection(number); } public void BuildUnreadySettings(List lstReturn) { List buildList = new List(); try { if (!_settingsLocked) { if (_useKeepAlive) { buildList.Add(new CPluginVariable(GetSettingSection("0") + t + "Auto-Enable/Keep-Alive", typeof(Boolean), true)); } buildList.Add(new CPluginVariable("Complete these settings before enabling.", typeof(String), "Once enabled, more settings will appear.")); //SQL Settings buildList.Add(new CPluginVariable(GetSettingSection("2") + t + "MySQL Hostname", typeof(String), _mySqlHostname)); buildList.Add(new CPluginVariable(GetSettingSection("2") + t + "MySQL Port", typeof(String), _mySqlPort)); buildList.Add(new CPluginVariable(GetSettingSection("2") + t + "MySQL Database", typeof(String), _mySqlSchemaName)); buildList.Add(new CPluginVariable(GetSettingSection("2") + t + "MySQL Username", typeof(String), _mySqlUsername)); buildList.Add(new CPluginVariable(GetSettingSection("2") + t + "MySQL Password", typeof(String), _mySqlPassword)); } //Debugging Settings buildList.Add(new CPluginVariable(GetSettingSection("D99") + t + "Debug level", typeof(Int32), Log.DebugLevel)); //Database Timing if (_dbTimingChecked && !_dbTimingValid) { buildList.Add(new CPluginVariable(GetSettingSection("D98") + t + "Override Timing Confirmation", typeof(Boolean), _timingValidOverride)); } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building unready setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("0") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildReadyLockedSettings(List lstReturn) { List buildList = new List(); try { buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Server ID (Display)", typeof(int), _serverInfo.ServerID)); buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Server IP (Display)", typeof(String), _serverInfo.ServerIP)); buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Server Round (Display)", typeof(String), _roundID)); if (_UseBanEnforcer) { buildList.Add(new CPluginVariable(GetSettingSection("A13-3") + t + "NAME Ban Count", typeof(int), _NameBanCount)); buildList.Add(new CPluginVariable(GetSettingSection("A13-3") + t + "GUID Ban Count", typeof(int), _GUIDBanCount)); buildList.Add(new CPluginVariable(GetSettingSection("A13-3") + t + "IP Ban Count", typeof(int), _IPBanCount)); buildList.Add(new CPluginVariable(GetSettingSection("A13-3") + t + "Ban Search", typeof(String), "")); buildList.AddRange(_BanEnforcerSearchResults.Select(aBan => new CPluginVariable(GetSettingSection("A13-3") + t + "BAN" + aBan.ban_id + s + aBan.ban_record.target_player.player_name + s + aBan.ban_record.source_name + s + aBan.ban_record.record_message, "enum.commandActiveEnum(Active|Disabled|Expired)", aBan.ban_status))); } buildList.Add(new CPluginVariable(GetSettingSection("D99") + t + "Debug level", typeof(int), Log.DebugLevel)); buildList.Add(new CPluginVariable(GetSettingSection("D99") + t + "Debug Soldier Name", typeof(String), _debugSoldierName)); lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building ready locked setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("1") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildServerSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("1")) { //Server Settings buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Setting Import", typeof(String), _serverInfo.ServerID)); buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Server ID (Display)", typeof(Int32), _serverInfo.ServerID)); buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Server IP (Display)", typeof(String), _serverInfo.ServerIP)); buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Server Round (Display)", typeof(String), _roundID)); buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Server Game (Display)", typeof(String), GameVersion.ToString())); buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Short Server Name", typeof(String), _shortServerName)); buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Low Population Value", typeof(Int32), _lowPopulationPlayerCount)); buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "High Population Value", typeof(Int32), _highPopulationPlayerCount)); buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Automatic Server Restart When Empty", typeof(Boolean), _automaticServerRestart)); if (_automaticServerRestart) { buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Automatic Restart Minimum Uptime Hours", typeof(Int32), _automaticServerRestartMinHours)); buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Automatic Procon Reboot When Server Reboots", typeof(Boolean), _automaticServerRestartProcon)); } buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Procon Memory Usage MB (Display)", typeof(Int32), _MemoryUsageCurrent)); buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Procon Memory Usage MB Warning", typeof(Int32), _MemoryUsageWarn)); buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Procon Memory Usage MB AdKats Restart", typeof(Int32), _MemoryUsageRestartPlugin)); buildList.Add(new CPluginVariable(GetSettingSection("1") + t + "Procon Memory Usage MB Procon Restart", typeof(Int32), _MemoryUsageRestartProcon)); } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building server setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("1") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildSQLSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("2")) { //SQL Settings buildList.Add(new CPluginVariable(GetSettingSection("2") + t + "MySQL Hostname", typeof(String), _mySqlHostname)); buildList.Add(new CPluginVariable(GetSettingSection("2") + t + "MySQL Port", typeof(String), _mySqlPort)); buildList.Add(new CPluginVariable(GetSettingSection("2") + t + "MySQL Database", typeof(String), _mySqlSchemaName)); buildList.Add(new CPluginVariable(GetSettingSection("2") + t + "MySQL Username", typeof(String), _mySqlUsername)); buildList.Add(new CPluginVariable(GetSettingSection("2") + t + "MySQL Password", typeof(String), _mySqlPassword)); } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building sql setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("2") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildUserSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("3")) { //User Settings buildList.Add(new CPluginVariable(GetSettingSection("3") + t + "Add User", typeof(String), "")); if (_userCache.Count > 0) { //Sort access list by access level, then by id List tempAccess = _userCache.Values.ToList(); tempAccess.Sort((a1, a2) => (a1.user_role.role_powerLevel == a2.user_role.role_powerLevel) ? (String.CompareOrdinal(a1.user_name.ToLower(), a2.user_name.ToLower())) : ((a1.user_role.role_powerLevel > a2.user_role.role_powerLevel) ? (-1) : (1))); String roleEnum = String.Empty; if (_RoleKeyDictionary.Count > 0) { Random random = new Random(); foreach (ARole role in _RoleKeyDictionary.Values.ToList()) { if (String.IsNullOrEmpty(roleEnum)) { roleEnum += "enum.RoleEnum_" + random.Next(100000, 999999) + "("; } else { roleEnum += "" + t.ToString(); } roleEnum += role.role_name; } roleEnum += ")"; } foreach (AUser user in tempAccess) { String userPrefix = GetSettingSection("3") + t + "USR" + user.user_id + s + user.user_name + s; if (_UseEmail) { buildList.Add(new CPluginVariable(userPrefix + "User Email", typeof(String), user.user_email)); } buildList.Add(new CPluginVariable(userPrefix + "User Expiration", typeof(String), user.user_expiration.ToShortDateString())); buildList.Add(new CPluginVariable(userPrefix + "User Notes", typeof(String), user.user_notes)); //Do not display phone input until that operation is available for use //lstReturn.Add(new CPluginVariable(userPrefix + "User Phone", typeof(String), user.user_phone)); buildList.Add(new CPluginVariable(userPrefix + "User Role", roleEnum, user.user_role.role_name)); buildList.Add(new CPluginVariable(userPrefix + "Delete User?", typeof(String), "")); buildList.Add(new CPluginVariable(userPrefix + "Add Soldier?", typeof(String), "")); String soldierPrefix = userPrefix + "Soldiers" + s; buildList.AddRange(user.soldierDictionary.Values.Select(aPlayer => new CPluginVariable(soldierPrefix + aPlayer.player_id + s + (_gameIDDictionary.ContainsKey(aPlayer.game_id) ? (_gameIDDictionary[aPlayer.game_id].ToString()) : ("INVALID GAME ID [" + aPlayer.game_id + "]")) + s + aPlayer.player_name + s + "Delete Soldier?", typeof(String), ""))); } } else { if (_firstUserListComplete) { buildList.Add(new CPluginVariable(GetSettingSection("3") + t + "No Users in User List", typeof(String), "Add Users with 'Add User'.")); } else { buildList.Add(new CPluginVariable(GetSettingSection("3") + t + "Please Wait, Fetching User List.", typeof(String), "Please Wait, Fetching User List.")); } } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building user setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("3") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildSpecialPlayerSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("3-2")) { if (_firstUserListComplete) { //Special Player Settings Boolean anyList = false; foreach (ASpecialGroup asGroup in _specialPlayerGroupIDDictionary.Values.OrderBy(aGroup => aGroup.group_name)) { List groupList = new List(); foreach (ASpecialPlayer asPlayer in GetASPlayersOfGroup(asGroup.group_key).OrderBy(asPlayer => asPlayer.player_object != null ? (asPlayer.player_object.GetVerboseName()) : (asPlayer.player_identifier))) { String playerIdentifier = null; if (asPlayer.player_object != null && !String.IsNullOrEmpty(asPlayer.player_object.player_name)) { playerIdentifier = asPlayer.player_object.player_name; } else { playerIdentifier = asPlayer.player_identifier; } if (String.IsNullOrEmpty(playerIdentifier)) { continue; } TimeSpan duration = (asPlayer.player_expiration - UtcNow()).Duration(); if (duration.TotalDays > 3650) { playerIdentifier += " | Permanent"; } else { playerIdentifier += " | " + FormatTimeString(duration, 3); } if (!groupList.Contains(playerIdentifier)) { groupList.Add(playerIdentifier); } } if (groupList.Any()) { anyList = true; buildList.Add(new CPluginVariable(GetSettingSection("3-2") + t + "[" + groupList.Count + "] " + asGroup.group_name + " (Display)", typeof(String[]), groupList.ToArray())); } } if (!anyList) { buildList.Add(new CPluginVariable(GetSettingSection("3-2") + t + "All Groups Empty", typeof(String), "All Groups Empty")); } } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building special player setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("3-2") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildVerboseSpecialPlayerSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("3-3")) { if (_firstUserListComplete) { //Verbose Special Player Settings Boolean anyVerbostList = false; foreach (ASpecialGroup asGroup in _specialPlayerGroupIDDictionary.Values.OrderBy(aGroup => aGroup.group_name)) { List groupList = new List(); foreach (ASpecialPlayer asPlayer in GetVerboseASPlayersOfGroup(asGroup.group_key).OrderBy(asPlayer => asPlayer.player_object != null ? (asPlayer.player_object.GetVerboseName()) : (asPlayer.player_identifier))) { String playerIdentifier = null; if (asPlayer.player_object != null && !String.IsNullOrEmpty(asPlayer.player_object.player_name)) { playerIdentifier = asPlayer.player_object.player_name; } else { playerIdentifier = asPlayer.player_identifier; } if (String.IsNullOrEmpty(playerIdentifier)) { continue; } if (!groupList.Contains(playerIdentifier)) { groupList.Add(playerIdentifier); } } if (groupList.Any()) { anyVerbostList = true; buildList.Add(new CPluginVariable(GetSettingSection("3-3") + t + "[" + groupList.Count + "] Verbose " + asGroup.group_name + " (Display)", typeof(String[]), groupList.ToArray())); } } if (!anyVerbostList) { buildList.Add(new CPluginVariable(GetSettingSection("3-3") + t + "All Verbose Groups Empty", typeof(String), "All Verbose Groups Empty")); } } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building verbose special player setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("3-3") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildRoleSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("4")) { //Role Settings buildList.Add(new CPluginVariable(GetSettingSection("4") + t + "Add Role", typeof(String), "")); var useCache = NowDuration(_RoleCommandCacheUpdate).TotalMinutes < 5 && _RoleCommandCache != null && NowDuration(_RoleCommandCacheUpdateBufferStart) > _RoleCommandCacheUpdateBufferDuration; if (_RoleIDDictionary.Count > 0) { if (!useCache) { // We are not using the cache; clear the entries to we can rebuild it _RoleCommandCache.Clear(); _RoleCommandCacheUpdate = UtcNow(); } lock (_RoleIDDictionary) { foreach (ARole aRole in _RoleKeyDictionary.Values.ToList()) { lock (_CommandIDDictionary) { Random random = new Random(); String rolePrefix = GetSettingSection("4") + t + "RLE" + aRole.role_id + s + ((RoleIsAdmin(aRole)) ? ("[A]") : ("")) + aRole.role_name + s; foreach (var aCommand in _CommandNameDictionary.Values.Where(dCommand => dCommand.command_active == ACommand.CommandActive.Active && // Never allow the confirm/cancel commands to be edited, players need these to be universal across servers dCommand.command_key != "command_confirm" && dCommand.command_key != "command_cancel" && // Never allow the default guest role to have admin commands assigned to it (aRole.role_key != "guest_default" || !dCommand.command_playerInteraction))) { var allowed = aRole.RoleAllowedCommands.ContainsKey(aCommand.command_key); var key = aRole.role_id + "-" + aCommand.command_id; String display; if (useCache && _RoleCommandCache.ContainsKey(key)) { // Using the role command cache; fetch from the dictionary display = _RoleCommandCache[key]; } else { display = rolePrefix + "CDE" + aCommand.command_id + s + aCommand.command_name + ((aCommand.command_playerInteraction) ? (" [ADMIN]") : ("")) + ((aCommand.command_playerInteraction && allowed) ? (" <---") : ("")); // We've just generated a new display string, add it to the cache _RoleCommandCache[key] = display; } buildList.Add(new CPluginVariable(display, "enum.roleAllowCommandEnum(Allow|Deny)", allowed ? ("Allow") : ("Deny"))); } //Do not display the delete option for default guest if (aRole.role_key != "guest_default") { buildList.Add(new CPluginVariable(rolePrefix + "Delete Role? (All assignments will be removed)", typeof(String), "")); } } } } } else { buildList.Add(new CPluginVariable(GetSettingSection("4") + t + "Role List Empty", typeof(String), "No valid roles found in database.")); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building role setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("4") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildRoleGroupSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("4-2")) { //Role Group Settings if (_RoleIDDictionary.Count > 0) { lock (_RoleIDDictionary) { foreach (ARole aRole in _RoleKeyDictionary.Values.ToList()) { lock (_specialPlayerGroupKeyDictionary) { Random random = new Random(); String rolePrefix = GetSettingSection("4-2") + t + "RLE" + aRole.role_id + s + ((RoleIsAdmin(aRole)) ? ("[A]") : ("")) + aRole.role_name + s; buildList.AddRange(from aGroup in _specialPlayerGroupKeyDictionary.Values let allowed = aRole.RoleSetGroups.ContainsKey(aGroup.group_key) let required = (aGroup.group_key == "slot_reserved" && _FeedServerReservedSlots && _FeedServerReservedSlots_Admins && RoleIsAdmin(aRole)) || (aGroup.group_key == "slot_spectator" && _FeedServerSpectatorList && _FeedServerSpectatorList_Admins && RoleIsAdmin(aRole)) || (aGroup.group_key == "whitelist_multibalancer" && _FeedMultiBalancerWhitelist && _FeedMultiBalancerWhitelist_Admins && RoleIsAdmin(aRole)) || (aGroup.group_key == "whitelist_teamkill" && _FeedTeamKillTrackerWhitelist && _FeedTeamKillTrackerWhitelist_Admins && RoleIsAdmin(aRole)) || (aGroup.group_key == "whitelist_spambot" && _spamBotExcludeAdminsAndWhitelist && RoleIsAdmin(aRole)) let blocked = (aGroup.group_key == "whitelist_adminassistant" && RoleIsAdmin(aRole)) let enumString = blocked ? "enum.roleSetGroupEnum_blocked(Blocked Based On Other Settings)" : (required ? "enum.roleSetGroupEnum_required(Required Based On Other Settings)" : "enum.roleSetGroupEnum(Assign|Ignore)") let display = rolePrefix + "GPE" + aGroup.group_id + s + aGroup.group_name select new CPluginVariable(display, enumString, blocked ? "Blocked Based On Other Settings" : (required ? "Required Based On Other Settings" : (allowed ? ("Assign") : ("Ignore"))))); } } } } else { buildList.Add(new CPluginVariable(GetSettingSection("4-2") + t + "Role List Empty", typeof(String), "No valid roles found in database.")); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building role group setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("4-2") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildCommandSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("5")) { buildList.Add(new CPluginVariable(GetSettingSection("5") + t + "Minimum Required Reason Length", typeof(int), _RequiredReasonLength)); buildList.Add(new CPluginVariable(GetSettingSection("5") + t + "Minimum Report Handle Seconds", typeof(int), _MinimumReportHandleSeconds)); buildList.Add(new CPluginVariable(GetSettingSection("5") + t + "Minimum Minutes Into Round To Use Assist", typeof(int), _minimumAssistMinutes)); buildList.Add(new CPluginVariable(GetSettingSection("5") + t + "Maximum Temp-Ban Duration Minutes", typeof(Double), _MaxTempBanDuration.TotalMinutes)); buildList.Add(new CPluginVariable(GetSettingSection("5") + t + "Countdown Duration before a Nuke is fired", typeof(int), _NukeCountdownDurationSeconds)); buildList.Add(new CPluginVariable(GetSettingSection("5") + t + "Allow Commands from Admin Say", typeof(Boolean), _AllowAdminSayCommands)); buildList.Add(new CPluginVariable(GetSettingSection("5") + t + "Bypass all command confirmation -DO NOT USE-", typeof(Boolean), _bypassCommandConfirmation)); buildList.Add(new CPluginVariable(GetSettingSection("5") + t + "External plugin player commands", typeof(String[]), _ExternalPlayerCommands.ToArray())); buildList.Add(new CPluginVariable(GetSettingSection("5") + t + "External plugin admin commands", typeof(String[]), _ExternalAdminCommands.ToArray())); buildList.Add(new CPluginVariable(GetSettingSection("5") + t + "Command Target Whitelist Commands", typeof(String[]), _CommandTargetWhitelistCommands.ToArray())); buildList.Add(new CPluginVariable(GetSettingSection("5") + t + "Reserved slot grants access to squad lead command", typeof(Boolean), _ReservedSquadLead)); buildList.Add(new CPluginVariable(GetSettingSection("5") + t + "Reserved slot grants access to self-move command", typeof(Boolean), _ReservedSelfMove)); buildList.Add(new CPluginVariable(GetSettingSection("5") + t + "Reserved slot grants access to self-kill command", typeof(Boolean), _ReservedSelfKill)); } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building command setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("5") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildCommandListSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("6")) { //Command Settings if (_CommandNameDictionary.Count > 0) { lock (_CommandIDDictionary) { foreach (ACommand command in _CommandIDDictionary.Values.ToList()) { if (command.command_active != ACommand.CommandActive.Invisible) { String commandPrefix = GetSettingSection("6") + t + "CDE" + command.command_id + s + command.command_name + s; buildList.Add(new CPluginVariable(commandPrefix + "Active", "enum.commandActiveEnum(Active|Disabled)", command.command_active.ToString())); if (command.command_active != ACommand.CommandActive.Disabled) { if (command.command_logging != ACommand.CommandLogging.Mandatory && command.command_logging != ACommand.CommandLogging.Unable) { buildList.Add(new CPluginVariable(commandPrefix + "Logging", "enum.commandLoggingEnum(Log|Ignore)", command.command_logging.ToString())); } buildList.Add(new CPluginVariable(commandPrefix + "Text", typeof(String), command.command_text)); buildList.Add(new CPluginVariable(commandPrefix + "Access Method", CreateEnumString(typeof(ACommand.CommandAccess)), command.command_access.ToString())); } } } } } else { buildList.Add(new CPluginVariable(GetSettingSection("6") + t + "Command List Empty", typeof(String), "No valid commands found in database.")); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building command list setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("6") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildPunishmentSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("7")) { //Punishment Settings buildList.Add(new CPluginVariable(GetSettingSection("7") + t + "Punishment Hierarchy", typeof(String[]), _PunishmentHierarchy)); buildList.Add(new CPluginVariable(GetSettingSection("7") + t + "Combine Server Punishments", typeof(Boolean), _CombineServerPunishments)); buildList.Add(new CPluginVariable(GetSettingSection("7") + t + "Automatic Forgives", typeof(Boolean), _AutomaticForgives)); if (_AutomaticForgives) { buildList.Add(new CPluginVariable(GetSettingSection("7") + t + "Automatic Forgive Days Since Punished", typeof(Int32), _AutomaticForgiveLastPunishDays)); buildList.Add(new CPluginVariable(GetSettingSection("7") + t + "Automatic Forgive Days Since Forgiven", typeof(Int32), _AutomaticForgiveLastForgiveDays)); } buildList.Add(new CPluginVariable(GetSettingSection("7") + t + "Only Kill Players when Server in low population", typeof(Boolean), _OnlyKillOnLowPop)); buildList.Add(new CPluginVariable(GetSettingSection("7") + t + "Use IRO Punishment", typeof(Boolean), _IROActive)); if (_IROActive) { buildList.Add(new CPluginVariable(GetSettingSection("7") + t + "IRO Timeout Minutes", typeof(Int32), _IROTimeout)); buildList.Add(new CPluginVariable(GetSettingSection("7") + t + "IRO Punishment Overrides Low Pop", typeof(Boolean), _IROOverridesLowPop)); if (_IROOverridesLowPop) { buildList.Add(new CPluginVariable(GetSettingSection("7") + t + "IRO Punishment Infractions Required to Override", typeof(Int32), _IROOverridesLowPopInfractions)); } } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building punishment setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("7") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildEmailSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("8")) { //Email Settings buildList.Add(new CPluginVariable(GetSettingSection("8") + t + "Send Emails", typeof(Boolean), _UseEmail)); if (_UseEmail) { buildList.Add(new CPluginVariable(GetSettingSection("8") + t + "Use SSL?", typeof(Boolean), _EmailHandler.UseSSL)); buildList.Add(new CPluginVariable(GetSettingSection("8") + t + "SMTP-Server address", typeof(String), _EmailHandler.SMTPServer)); buildList.Add(new CPluginVariable(GetSettingSection("8") + t + "SMTP-Server port", typeof(int), _EmailHandler.SMTPPort)); buildList.Add(new CPluginVariable(GetSettingSection("8") + t + "Sender address", typeof(String), _EmailHandler.SenderEmail)); buildList.Add(new CPluginVariable(GetSettingSection("8") + t + "SMTP-Server username", typeof(String), _EmailHandler.SMTPUser)); buildList.Add(new CPluginVariable(GetSettingSection("8") + t + "SMTP-Server password", typeof(String), _EmailHandler.SMTPPassword)); buildList.Add(new CPluginVariable(GetSettingSection("8") + t + "Custom HTML Addition", typeof(String), _EmailHandler.CustomHTMLAddition)); buildList.Add(new CPluginVariable(GetSettingSection("8") + t + "Extra Recipient Email Addresses", typeof(String[]), _EmailHandler.RecipientEmails.ToArray())); buildList.Add(new CPluginVariable(GetSettingSection("8") + t + "Only Send Report Emails When Admins Offline", typeof(Boolean), _EmailReportsOnlyWhenAdminless)); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building email setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("8") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildPushbulletSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("8-2")) { //PushBullet Settings buildList.Add(new CPluginVariable(GetSettingSection("8-2") + t + "Send PushBullet Reports", typeof(Boolean), _UsePushBullet)); if (_UsePushBullet) { buildList.Add(new CPluginVariable(GetSettingSection("8-2") + t + "PushBullet Access Token", typeof(String), _PushBulletHandler.AccessToken)); buildList.Add(new CPluginVariable(GetSettingSection("8-2") + t + "PushBullet Note Target", "enum.pushBulletTargetEnum(Private|Channel)", _PushBulletHandler.DefaultTarget.ToString())); if (_PushBulletHandler.DefaultTarget == PushBulletHandler.Target.Channel) { buildList.Add(new CPluginVariable(GetSettingSection("8-2") + t + "PushBullet Channel Tag", typeof(String), _PushBulletHandler.DefaultChannelTag)); } buildList.Add(new CPluginVariable(GetSettingSection("8-2") + t + "Only Send PushBullet Reports When Admins Offline", typeof(Boolean), _PushBulletReportsOnlyWhenAdminless)); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building pushbullet setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("8-2") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildDiscordWebHookSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("8-3")) { //Discord Settings buildList.Add(new CPluginVariable(GetSettingSection("8-3") + t + "Send Reports to Discord WebHook", typeof(Boolean), _UseDiscordForReports)); if (_UseDiscordForReports) { buildList.Add(new CPluginVariable(GetSettingSection("8-3") + t + "Discord WebHook URL", typeof(String), _DiscordManager.URL)); buildList.Add(new CPluginVariable(GetSettingSection("8-3") + t + "Only Send Discord Reports When Admins Offline", typeof(Boolean), _DiscordReportsOnlyWhenAdminless)); buildList.Add(new CPluginVariable(GetSettingSection("8-3") + t + "Send update if reported players leave without action", typeof(Boolean), _DiscordReportsLeftWithoutAction)); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building Discord WebHook setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("8-3") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildTeamswapSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("9")) { //TeamSwap Settings buildList.Add(new CPluginVariable(GetSettingSection("9") + t + "Ticket Window High", typeof(int), _TeamSwapTicketWindowHigh)); buildList.Add(new CPluginVariable(GetSettingSection("9") + t + "Ticket Window Low", typeof(int), _TeamSwapTicketWindowLow)); } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building teamswap setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("9") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildAASettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("A10")) { //Admin Assistant Settings buildList.Add(new CPluginVariable(GetSettingSection("A10") + t + "Enable Admin Assistants", typeof(Boolean), _EnableAdminAssistants)); if (_EnableAdminAssistants) { buildList.Add(new CPluginVariable(GetSettingSection("A10") + t + "Minimum Confirmed Reports Per Month", typeof(int), _MinimumRequiredMonthlyReports)); buildList.Add(new CPluginVariable(GetSettingSection("A10") + t + "Enable Admin Assistant Perk", typeof(Boolean), _EnableAdminAssistantPerk)); buildList.Add(new CPluginVariable(GetSettingSection("A10") + t + "Use AA Report Auto Handler", typeof(Boolean), _UseAAReportAutoHandler)); if (_UseAAReportAutoHandler) { buildList.Add(new CPluginVariable(GetSettingSection("A10") + t + "Auto-Report-Handler Strings", typeof(String[]), _AutoReportHandleStrings)); } } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building AA setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("A10") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildMuteSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("A11")) { //Muting Settings buildList.Add(new CPluginVariable(GetSettingSection("A11") + t + "On-Player-Muted Message", typeof(String), _MutedPlayerMuteMessage)); buildList.Add(new CPluginVariable(GetSettingSection("A11") + t + "On-Player-Killed Message", typeof(String), _MutedPlayerKillMessage)); buildList.Add(new CPluginVariable(GetSettingSection("A11") + t + "On-Player-Kicked Message", typeof(String), _MutedPlayerKickMessage)); buildList.Add(new CPluginVariable(GetSettingSection("A11") + t + "# Chances to give player before kicking", typeof(int), _MutedPlayerChances)); buildList.Add(new CPluginVariable(GetSettingSection("A11") + t + "Ignore commands for mute enforcement", typeof(Boolean), _MutedPlayerIgnoreCommands)); } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building mute setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("A11") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildMessagingSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("A12")) { //Message Settings buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "Display Admin Name in Action Announcement", typeof(Boolean), _ShowAdminNameInAnnouncement)); buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "Display New Player Announcement", typeof(Boolean), _ShowNewPlayerAnnouncement)); buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "Display Player Name Change Announcement", typeof(Boolean), _ShowPlayerNameChangeAnnouncement)); buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "Display Targeted Player Left Notification", typeof(Boolean), _ShowTargetedPlayerLeftNotification)); buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "Display Ticket Rates in Procon Chat", typeof(Boolean), _DisplayTicketRatesInProconChat)); buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "Inform players of reports against them", typeof(Boolean), _InformReportedPlayers)); if (_InformReportedPlayers) { buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "Player Inform Exclusion Strings", typeof(String[]), _PlayerInformExclusionStrings)); } buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "Inform reputable players of admin joins", typeof(Boolean), _InformReputablePlayersOfAdminJoins)); buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "Inform admins of admin joins", typeof(Boolean), _InformAdminsOfAdminJoins)); buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "Yell display time seconds", typeof(Int32), _YellDuration)); buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "Pre-Message List", typeof(String[]), _PreMessageList.ToArray())); buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "Require Use of Pre-Messages", typeof(Boolean), _RequirePreMessageUse)); buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "Use first spawn message", typeof(Boolean), _UseFirstSpawnMessage)); if (_UseFirstSpawnMessage) { buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "First spawn message text", typeof(String), _FirstSpawnMessage)); buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "Use First Spawn Reputation and Infraction Message", typeof(Boolean), _useFirstSpawnRepMessage)); } buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "Use Perk Expiration Notification", typeof(Boolean), _UsePerkExpirationNotify)); if (_UsePerkExpirationNotify) { buildList.Add(new CPluginVariable(GetSettingSection("A12") + t + "Perk Expiration Notify Days Remaining", typeof(Int32), _PerkExpirationNotifyDays)); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building messaging setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("A12") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildSpambotSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("A12-2")) { buildList.Add(new CPluginVariable(GetSettingSection("A12-2") + t + "SpamBot Enable", typeof(Boolean), _spamBotEnabled)); buildList.Add(new CPluginVariable(GetSettingSection("A12-2") + t + "SpamBot Say List", typeof(String[]), _spamBotSayList.ToArray())); buildList.Add(new CPluginVariable(GetSettingSection("A12-2") + t + "SpamBot Say Delay Seconds", typeof(Int32), _spamBotSayDelaySeconds)); buildList.Add(new CPluginVariable(GetSettingSection("A12-2") + t + "SpamBot Yell List", typeof(String[]), _spamBotYellList.ToArray())); buildList.Add(new CPluginVariable(GetSettingSection("A12-2") + t + "SpamBot Yell Delay Seconds", typeof(Int32), _spamBotYellDelaySeconds)); buildList.Add(new CPluginVariable(GetSettingSection("A12-2") + t + "SpamBot Tell List", typeof(String[]), _spamBotTellList.ToArray())); buildList.Add(new CPluginVariable(GetSettingSection("A12-2") + t + "SpamBot Tell Delay Seconds", typeof(Int32), _spamBotTellDelaySeconds)); buildList.Add(new CPluginVariable(GetSettingSection("A12-2") + t + "Exclude Admins and Whitelist from Spam", typeof(Boolean), _spamBotExcludeAdminsAndWhitelist)); } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building spambot setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("A12-2") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildBattlecrySettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("A12-3")) { buildList.Add(new CPluginVariable(GetSettingSection("A12-3") + t + "Player Battlecry Volume", "enum.battlecryVolumeEnum(Disabled|Say|Yell|Tell)", _battlecryVolume.ToString())); buildList.Add(new CPluginVariable(GetSettingSection("A12-3") + t + "Player Battlecry Max Length", typeof(Int32), _battlecryMaxLength)); buildList.Add(new CPluginVariable(GetSettingSection("A12-3") + t + "Player Battlecry Denied Words", typeof(String[]), _battlecryDeniedWords)); } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building battlecry setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("A12-3") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildAllCapsSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("A12-4")) { buildList.Add(new CPluginVariable(GetSettingSection("A12-4") + t + "Use All Caps Limiter", typeof(Boolean), _UseAllCapsLimiter)); if (_UseAllCapsLimiter) { buildList.Add(new CPluginVariable(GetSettingSection("A12-4") + t + "All Caps Limiter Only Limit Specified Players", typeof(Boolean), _AllCapsLimiterSpecifiedPlayersOnly)); buildList.Add(new CPluginVariable(GetSettingSection("A12-4") + t + "All Caps Limiter Character Percentage", typeof(Int32), _AllCapsLimterPercentage)); buildList.Add(new CPluginVariable(GetSettingSection("A12-4") + t + "All Caps Limiter Minimum Characters", typeof(Int32), _AllCapsLimterMinimumCharacters)); buildList.Add(new CPluginVariable(GetSettingSection("A12-4") + t + "All Caps Limiter Warn Threshold", typeof(Int32), _AllCapsLimiterWarnThreshold)); buildList.Add(new CPluginVariable(GetSettingSection("A12-4") + t + "All Caps Limiter Kill Threshold", typeof(Int32), _AllCapsLimiterKillThreshold)); buildList.Add(new CPluginVariable(GetSettingSection("A12-4") + t + "All Caps Limiter Kick Threshold", typeof(Int32), _AllCapsLimiterKickThreshold)); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building all-caps setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("A12-4") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildBanSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("A13")) { //Ban Settings buildList.Add(new CPluginVariable(GetSettingSection("A13") + t + "Use Additional Ban Message", typeof(Boolean), _UseBanAppend)); if (_UseBanAppend) { buildList.Add(new CPluginVariable(GetSettingSection("A13") + t + "Additional Ban Message", typeof(String), _BanAppend)); } buildList.Add(new CPluginVariable(GetSettingSection("A13") + t + "Procon Ban Admin Name", typeof(String), _CBanAdminName)); } if (IsActiveSettingSection("A13-2")) { buildList.Add(new CPluginVariable(GetSettingSection("A13-2") + t + "Use Ban Enforcer", typeof(Boolean), _UseBanEnforcer)); if (_UseBanEnforcer) { buildList.Add(new CPluginVariable(GetSettingSection("A13-2") + t + "Ban Enforcer BF4 Lenient Kick", typeof(Boolean), _BanEnforcerBF4LenientKick)); buildList.Add(new CPluginVariable(GetSettingSection("A13-2") + t + "Enforce New Bans by NAME", typeof(Boolean), _DefaultEnforceName)); buildList.Add(new CPluginVariable(GetSettingSection("A13-2") + t + "Enforce New Bans by GUID", typeof(Boolean), _DefaultEnforceGUID)); buildList.Add(new CPluginVariable(GetSettingSection("A13-2") + t + "Enforce New Bans by IP", typeof(Boolean), _DefaultEnforceIP)); } } if (IsActiveSettingSection("A13-3")) { if (_UseBanEnforcer) { buildList.Add(new CPluginVariable(GetSettingSection("A13-3") + t + "NAME Ban Count", typeof(int), _NameBanCount)); buildList.Add(new CPluginVariable(GetSettingSection("A13-3") + t + "GUID Ban Count", typeof(int), _GUIDBanCount)); buildList.Add(new CPluginVariable(GetSettingSection("A13-3") + t + "IP Ban Count", typeof(int), _IPBanCount)); buildList.Add(new CPluginVariable(GetSettingSection("A13-3") + t + "Ban Search", typeof(String), "")); buildList.AddRange(_BanEnforcerSearchResults.Select(aBan => new CPluginVariable(GetSettingSection("A13-3") + t + "BAN" + aBan.ban_id + s + aBan.ban_record.target_player.player_name + s + aBan.ban_record.source_name + s + aBan.ban_record.record_message, "enum.commandActiveEnum(Active|Disabled|Expired)", aBan.ban_status))); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building ban setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("A13") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildExternalCommandSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("A14")) { //External Command Settings buildList.Add(new CPluginVariable(GetSettingSection("A14") + t + "AdkatsLRT Extension Token", typeof(String), _AdKatsLRTExtensionToken)); if (!_UseBanEnforcer) { buildList.Add(new CPluginVariable(GetSettingSection("A14") + t + "Fetch Actions from Database", typeof(Boolean), _fetchActionsFromDb)); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building external command setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("A14") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildVOIPSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("A15")) { //VOIP buildList.Add(new CPluginVariable(GetSettingSection("A15") + t + "Server VOIP Address", typeof(String), _ServerVoipAddress)); } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building VOIP setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("A15") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildOrchestrationSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("A16")) { //MULTIBalancer buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Feed MULTIBalancer Whitelist", typeof(Boolean), _FeedMultiBalancerWhitelist)); if (_FeedMultiBalancerWhitelist) { buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Automatic MULTIBalancer Whitelist for Admins", typeof(Boolean), _FeedMultiBalancerWhitelist_Admins)); } buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Feed MULTIBalancer Even Dispersion List", typeof(Boolean), _FeedMultiBalancerDisperseList)); //TeamKillTracker buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Feed TeamKillTracker Whitelist", typeof(Boolean), _FeedTeamKillTrackerWhitelist)); if (_FeedTeamKillTrackerWhitelist) { buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Automatic TeamKillTracker Whitelist for Admins", typeof(Boolean), _FeedTeamKillTrackerWhitelist_Admins)); } buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Feed Server Reserved Slots", typeof(Boolean), _FeedServerReservedSlots)); if (_FeedServerReservedSlots) { buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Automatic Reserved Slot for Admins", typeof(Boolean), _FeedServerReservedSlots_Admins)); buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Automatic VIP Kick Whitelist for Admins", typeof(Boolean), _FeedServerReservedSlots_Admins_VIPKickWhitelist)); } else { buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Send new reserved slots to VIP Slot Manager", typeof(Boolean), _FeedServerReservedSlots_VSM)); } buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Feed Server Spectator List", typeof(Boolean), _FeedServerSpectatorList)); if (_FeedServerSpectatorList) { buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Automatic Spectator Slot for Admins", typeof(Boolean), _FeedServerSpectatorList_Admins)); } buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Feed Stat Logger Settings", typeof(Boolean), _FeedStatLoggerSettings)); buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Post Stat Logger Chat Manually", typeof(Boolean), _PostStatLoggerChatManually)); if (_PostStatLoggerChatManually) { buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Post Server Chat Spam", typeof(Boolean), _PostStatLoggerChatManually_PostServerChatSpam)); buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Exclude Commands from Chat Logs", typeof(Boolean), _PostStatLoggerChatManually_IgnoreCommands)); } buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Banned Tags", typeof(String[]), _BannedTags)); buildList.Add(new CPluginVariable(GetSettingSection("A16") + t + "Auto-Kick Players Who First Joined After This Date", typeof(String), _AutoKickNewPlayerDate.ToShortDateString())); } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building orchestration setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("A16") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildRoundSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("A17")) { buildList.Add(new CPluginVariable(GetSettingSection("A17") + t + "Round Timer: Enable", typeof(Boolean), _useRoundTimer)); if (_useRoundTimer) { buildList.Add(new CPluginVariable(GetSettingSection("A17") + t + "Round Timer: Round Duration Minutes", typeof(Double), _maxRoundTimeMinutes)); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building round setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("A17") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildFactionSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("A17-2") && GameVersion == GameVersionEnum.BF4) { buildList.Add(new CPluginVariable(GetSettingSection("A17-2") + t + "Faction Randomizer: Enable", typeof(Boolean), _factionRandomizerEnable)); buildList.Add(new CPluginVariable(GetSettingSection("A17-2") + t + "Faction Randomizer: Restriction", "enum.factionRandomizerRestriction2Enum(NoRestriction|NeverSameFaction|AlwaysSameFaction|AlwaysSwapUSvsRU|AlwaysSwapUSvsCN|AlwaysSwapRUvsCN|AlwaysBothUS|AlwaysBothRU|AlwaysBothCN|AlwaysUSvsX|AlwaysRUvsX|AlwaysCNvsX|NeverUSvsX|NeverRUvsX|NeverCNvsX)", _factionRandomizerRestriction.ToString())); buildList.Add(new CPluginVariable(GetSettingSection("A17-2") + t + "Faction Randomizer: Allow Repeat Team Selections", typeof(Boolean), _factionRandomizerAllowRepeatSelection)); } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building faction setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("A17-2") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildAntiCheatSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("A18")) { buildList.Add(new CPluginVariable(GetSettingSection("A18") + t + "Use LIVE Anti Cheat System", typeof(Boolean), _useAntiCheatLIVESystem)); if (_useAntiCheatLIVESystem) { buildList.Add(new CPluginVariable(GetSettingSection("A18") + t + "LIVE System Includes Mass Murder and Aimbot Checks", typeof(Boolean), _AntiCheatLIVESystemActiveStats)); } buildList.Add(new CPluginVariable(GetSettingSection("A18") + t + "DPS Checker: Ban Message", typeof(String), _AntiCheatDPSBanMessage)); buildList.Add(new CPluginVariable(GetSettingSection("A18") + t + "HSK Checker: Enable", typeof(Boolean), _UseHskChecker)); if (_UseHskChecker) { buildList.Add(new CPluginVariable(GetSettingSection("A18") + t + "HSK Checker: Trigger Level", typeof(Double), _HskTriggerLevel)); buildList.Add(new CPluginVariable(GetSettingSection("A18") + t + "HSK Checker: Ban Message", typeof(String), _AntiCheatHSKBanMessage)); } buildList.Add(new CPluginVariable(GetSettingSection("A18") + t + "KPM Checker: Enable", typeof(Boolean), _UseKpmChecker)); if (_UseKpmChecker) { buildList.Add(new CPluginVariable(GetSettingSection("A18") + t + "KPM Checker: Trigger Level", typeof(Double), _KpmTriggerLevel)); buildList.Add(new CPluginVariable(GetSettingSection("A18") + t + "KPM Checker: Ban Message", typeof(String), _AntiCheatKPMBanMessage)); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building anticheat setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("A18") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildRuleSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("A19")) { //Server rules settings buildList.Add(new CPluginVariable(GetSettingSection("A19") + t + "Rule Print Delay", typeof(Double), _ServerRulesDelay)); buildList.Add(new CPluginVariable(GetSettingSection("A19") + t + "Rule Print Interval", typeof(Double), _ServerRulesInterval)); buildList.Add(new CPluginVariable(GetSettingSection("A19") + t + "Server Rule List", typeof(String[]), _ServerRulesList)); buildList.Add(new CPluginVariable(GetSettingSection("A19") + t + "Server Rule Numbers", typeof(Boolean), _ServerRulesNumbers)); buildList.Add(new CPluginVariable(GetSettingSection("A19") + t + "Yell Server Rules", typeof(Boolean), _ServerRulesYell)); } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building rule setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("A19") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildAFKSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("B20")) { //AFK manager settings buildList.Add(new CPluginVariable(GetSettingSection("B20") + t + "AFK System Enable", typeof(Boolean), _AFKManagerEnable)); if (_AFKManagerEnable) { buildList.Add(new CPluginVariable(GetSettingSection("B20") + t + "AFK Ignore Chat", typeof(Boolean), _AFKIgnoreChat)); buildList.Add(new CPluginVariable(GetSettingSection("B20") + t + "AFK Auto-Kick Enable", typeof(Boolean), _AFKAutoKickEnable)); buildList.Add(new CPluginVariable(GetSettingSection("B20") + t + "AFK Trigger Minutes", typeof(Double), _AFKTriggerDurationMinutes)); buildList.Add(new CPluginVariable(GetSettingSection("B20") + t + "AFK Minimum Players", typeof(Int32), _AFKTriggerMinimumPlayers)); buildList.Add(new CPluginVariable(GetSettingSection("B20") + t + "AFK Ignore User List", typeof(Boolean), _AFKIgnoreUserList)); if (!_AFKIgnoreUserList) { buildList.Add(new CPluginVariable(GetSettingSection("B20") + t + "AFK Ignore Roles", typeof(String[]), _AFKIgnoreRoles)); } } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building AFK setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("B20") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildPingSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("B21")) { //Ping enforcer settings buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Ping Enforcer Enable", typeof(Boolean), _pingEnforcerEnable)); if (_pingEnforcerEnable) { buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Current Ping Limit (Display)", typeof(String), GetPingLimitStatus())); buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Ping Moving Average Duration sec", typeof(Double), _pingMovingAverageDurationSeconds)); buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Ping Kick Low Population Trigger ms", typeof(Double), _pingEnforcerLowTriggerMS)); buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Ping Kick Low Population Time Modifier", typeof(String[]), _pingEnforcerLowTimeModifier.Select(x => x.ToString()).ToArray())); buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Ping Kick Medium Population Trigger ms", typeof(Double), _pingEnforcerMedTriggerMS)); buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Ping Kick Medium Population Time Modifier", typeof(String[]), _pingEnforcerMedTimeModifier.Select(x => x.ToString()).ToArray())); buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Ping Kick High Population Trigger ms", typeof(Double), _pingEnforcerHighTriggerMS)); buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Ping Kick High Population Time Modifier", typeof(String[]), _pingEnforcerHighTimeModifier.Select(x => x.ToString()).ToArray())); buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Ping Kick Full Population Trigger ms", typeof(Double), _pingEnforcerFullTriggerMS)); buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Ping Kick Full Population Time Modifier", typeof(String[]), _pingEnforcerFullTimeModifier.Select(x => x.ToString()).ToArray())); buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Ping Kick Minimum Players", typeof(Int32), _pingEnforcerTriggerMinimumPlayers)); buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Kick Missing Pings", typeof(Boolean), _pingEnforcerKickMissingPings)); if (_pingEnforcerKickMissingPings) { buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Attempt Manual Ping when Missing", typeof(Boolean), _attemptManualPingWhenMissing)); } buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Ping Kick Ignore User List", typeof(Boolean), _pingEnforcerIgnoreUserList)); if (!_pingEnforcerIgnoreUserList) { buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Ping Kick Ignore Roles", typeof(String[]), _pingEnforcerIgnoreRoles)); } buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Ping Kick Message Prefix", typeof(String), _pingEnforcerMessagePrefix)); buildList.Add(new CPluginVariable(GetSettingSection("B21") + t + "Display Ping Enforcer Messages In Procon Chat", typeof(Boolean), _pingEnforcerDisplayProconChat)); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building ping setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("B21") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildCommanderSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("B22")) { //Commander manager settings buildList.Add(new CPluginVariable(GetSettingSection("B22") + t + "Commander Manager Enable", typeof(Boolean), _CMDRManagerEnable)); if (_CMDRManagerEnable) { buildList.Add(new CPluginVariable(GetSettingSection("B22") + t + "Minimum Players to Allow Commanders", typeof(Int32), _CMDRMinimumPlayers)); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building commander setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("B22") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildPlayerLockingSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("B23")) { //Player locking settings buildList.Add(new CPluginVariable(GetSettingSection("B23") + t + "Player Lock Manual Duration Minutes", typeof(Double), _playerLockingManualDuration)); buildList.Add(new CPluginVariable(GetSettingSection("B23") + t + "Automatically Lock Players on Admin Action", typeof(Boolean), _playerLockingAutomaticLock)); if (_playerLockingAutomaticLock) { buildList.Add(new CPluginVariable(GetSettingSection("B23") + t + "Player Lock Automatic Duration Minutes", typeof(Double), _playerLockingAutomaticDuration)); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building player locking setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("B23") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildSurrenderSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("B24")) { //Surrender Vote settings buildList.Add(new CPluginVariable(GetSettingSection("B24") + t + "Surrender Vote Enable", typeof(Boolean), _surrenderVoteEnable)); if (_surrenderVoteEnable) { buildList.Add(new CPluginVariable(GetSettingSection("B24") + t + "Percentage Votes Needed for Surrender", typeof(Double), _surrenderVoteMinimumPlayerPercentage)); buildList.Add(new CPluginVariable(GetSettingSection("B24") + t + "Minimum Player Count to Enable Surrender", typeof(Int32), _surrenderVoteMinimumPlayerCount)); buildList.Add(new CPluginVariable(GetSettingSection("B24") + t + "Minimum Ticket Gap to Surrender", typeof(Int32), _surrenderVoteMinimumTicketGap)); buildList.Add(new CPluginVariable(GetSettingSection("B24") + t + "Enable Required Ticket Rate Gap to Surrender", typeof(Boolean), _surrenderVoteTicketRateGapEnable)); if (_surrenderVoteTicketRateGapEnable) { buildList.Add(new CPluginVariable(GetSettingSection("B24") + t + "Minimum Ticket Rate Gap to Surrender", typeof(Double), _surrenderVoteMinimumTicketRateGap)); } buildList.Add(new CPluginVariable(GetSettingSection("B24") + t + "Surrender Vote Timeout Enable", typeof(Boolean), _surrenderVoteTimeoutEnable)); if (_surrenderVoteTimeoutEnable) { buildList.Add(new CPluginVariable(GetSettingSection("B24") + t + "Surrender Vote Timeout Minutes", typeof(Double), _surrenderVoteTimeoutMinutes)); } } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building surrender setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("B24") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildAutoSurrenderSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("B25") || IsActiveSettingSection("B25-2")) { //Auto-Surrender Settings buildList.Add(new CPluginVariable(GetSettingSection("B25") + t + "Auto-Surrender Enable", typeof(Boolean), _surrenderAutoEnable)); if (_surrenderAutoEnable) { buildList.Add(new CPluginVariable(GetSettingSection("B25") + t + "Auto-Surrender Use Optimal Values for Metro Conquest", typeof(Boolean), _surrenderAutoUseMetroValues)); buildList.Add(new CPluginVariable(GetSettingSection("B25") + t + "Auto-Surrender Use Optimal Values for Locker Conquest", typeof(Boolean), _surrenderAutoUseLockerValues)); buildList.Add(new CPluginVariable(GetSettingSection("B25") + t + "Auto-Surrender Minimum Ticket Count", typeof(Int32), _surrenderAutoMinimumTicketCount)); buildList.Add(new CPluginVariable(GetSettingSection("B25") + t + "Auto-Surrender Maximum Ticket Count", typeof(Int32), _surrenderAutoMaximumTicketCount)); buildList.Add(new CPluginVariable(GetSettingSection("B25") + t + "Auto-Surrender Minimum Ticket Gap", typeof(Int32), _surrenderAutoMinimumTicketGap)); if (!_surrenderAutoUseMetroValues && !_surrenderAutoUseLockerValues) { buildList.Add(new CPluginVariable(GetSettingSection("B25") + t + "Auto-Surrender Losing Team Rate Window Max", typeof(Double), _surrenderAutoLosingRateMax)); buildList.Add(new CPluginVariable(GetSettingSection("B25") + t + "Auto-Surrender Losing Team Rate Window Min", typeof(Double), _surrenderAutoLosingRateMin)); buildList.Add(new CPluginVariable(GetSettingSection("B25") + t + "Auto-Surrender Winning Team Rate Window Max", typeof(Double), _surrenderAutoWinningRateMax)); buildList.Add(new CPluginVariable(GetSettingSection("B25") + t + "Auto-Surrender Winning Team Rate Window Min", typeof(Double), _surrenderAutoWinningRateMin)); buildList.Add(new CPluginVariable(GetSettingSection("B25") + t + "Auto-Surrender Trigger Count to Surrender", typeof(Int32), _surrenderAutoTriggerCountToSurrender)); } buildList.Add(new CPluginVariable(GetSettingSection("B25") + t + "Auto-Surrender Reset Trigger Count on Cancel", typeof(Boolean), _surrenderAutoResetTriggerCountOnCancel)); buildList.Add(new CPluginVariable(GetSettingSection("B25") + t + "Auto-Surrender Minimum Players", typeof(Int32), _surrenderAutoMinimumPlayers)); buildList.Add(new CPluginVariable(GetSettingSection("B25") + t + "Nuke Winning Team Instead of Surrendering Losing Team", typeof(Boolean), _surrenderAutoNukeInstead)); if (_surrenderAutoNukeInstead) { buildList.Add(new CPluginVariable(GetSettingSection("B25-2") + t + "Maximum Auto-Nukes Each Round", typeof(Int32), _surrenderAutoMaxNukesEachRound)); buildList.Add(new CPluginVariable(GetSettingSection("B25-2") + t + "Reset Auto-Nuke Trigger Count on Fire", typeof(Boolean), _surrenderAutoResetTriggerCountOnFire)); buildList.Add(new CPluginVariable(GetSettingSection("B25-2") + t + "Switch to surrender after max nukes", typeof(Boolean), _surrenderAutoNukeResolveAfterMax)); buildList.Add(new CPluginVariable(GetSettingSection("B25-2") + t + "Minimum Seconds Between Nukes", typeof(Int32), _surrenderAutoNukeMinBetween)); buildList.Add(new CPluginVariable(GetSettingSection("B25-2") + t + "Countdown Duration before a Nuke is fired", typeof(Int32), _NukeCountdownDurationSeconds)); buildList.Add(new CPluginVariable(GetSettingSection("B25-2") + t + "Fire Nuke Triggers if Winning Team up by X Tickets", typeof(Int32), _NukeWinningTeamUpTicketCount)); buildList.Add(new CPluginVariable(GetSettingSection("B25-2") + t + "Only fire ticket difference nukes in high population", typeof(Boolean), _NukeWinningTeamUpTicketHigh)); buildList.Add(new CPluginVariable(GetSettingSection("B25-2") + t + "Announce Nuke Preparation to Players", typeof(Boolean), _surrenderAutoAnnounceNukePrep)); buildList.Add(new CPluginVariable(GetSettingSection("B25-2") + t + "Allow Auto-Nuke to fire on losing teams", typeof(Boolean), _surrenderAutoNukeLosingTeams)); if (_surrenderAutoNukeLosingTeams) { buildList.Add(new CPluginVariable(GetSettingSection("B25-2") + t + "Maximum Nuke Ticket Difference for Losing Team", typeof(Int32), _surrenderAutoNukeLosingMaxDiff)); } buildList.Add(new CPluginVariable(GetSettingSection("B25-2") + t + "Auto-Nuke High Pop Duration Seconds", typeof(Int32), _surrenderAutoNukeDurationHigh)); buildList.Add(new CPluginVariable(GetSettingSection("B25-2") + t + "Auto-Nuke Medium Pop Duration Seconds", typeof(Int32), _surrenderAutoNukeDurationMed)); buildList.Add(new CPluginVariable(GetSettingSection("B25-2") + t + "Auto-Nuke Low Pop Duration Seconds", typeof(Int32), _surrenderAutoNukeDurationLow)); buildList.Add(new CPluginVariable(GetSettingSection("B25-2") + t + "Auto-Nuke Consecutive Duration Increase", typeof(Int32), _surrenderAutoNukeDurationIncrease)); buildList.Add(new CPluginVariable(GetSettingSection("B25-2") + t + "Auto-Nuke Duration Increase Minimum Ticket Difference", typeof(Int32), _surrenderAutoNukeDurationIncreaseTicketDiff)); } buildList.Add(new CPluginVariable(GetSettingSection("B25") + t + "Start Surrender Vote Instead of Surrendering Losing Team", typeof(Boolean), _surrenderAutoTriggerVote)); if (!_surrenderAutoTriggerVote) { if (!_surrenderAutoNukeInstead) { buildList.Add(new CPluginVariable(GetSettingSection("B25") + t + "Auto-Surrender Message", typeof(String), _surrenderAutoMessage)); } else { buildList.Add(new CPluginVariable(GetSettingSection("B25-2") + t + "Auto-Nuke Message", typeof(String), _surrenderAutoNukeMessage)); } } } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building autosurrender setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("B25") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildStatisticsSettings(List lstReturn) { List buildList = new List(); try { if (IsActiveSettingSection("B26")) { //Statistics Settings buildList.Add(new CPluginVariable(GetSettingSection("B26") + t + "Post Map Benefit/Detriment Statistics", typeof(Boolean), _PostMapBenefitStatistics)); } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building statistics setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("B26") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildPopulatorSettings(List lstReturn) { List buildList = new List(); var popMonitorSection = "B27"; try { if (IsActiveSettingSection(popMonitorSection)) { buildList.Add(new CPluginVariable(GetSettingSection(popMonitorSection) + t + "Monitor Populator Players - Thanks CMWGaming", typeof(Boolean), _PopulatorMonitor)); if (_PopulatorMonitor) { buildList.Add(new CPluginVariable(GetSettingSection(popMonitorSection) + t + "[" + _populatorPlayers.Count() + "] Populator Players (Display)", typeof(String[]), _populatorPlayers.Values.Select(aPlayer => aPlayer.player_name).ToArray())); buildList.Add(new CPluginVariable(GetSettingSection(popMonitorSection) + t + "Monitor Specified Populators Only", typeof(Boolean), _PopulatorUseSpecifiedPopulatorsOnly)); buildList.Add(new CPluginVariable(GetSettingSection(popMonitorSection) + t + "Monitor Populators of This Server Only", typeof(Boolean), _PopulatorPopulatingThisServerOnly)); buildList.Add(new CPluginVariable(GetSettingSection(popMonitorSection) + t + "Count to Consider Populator Past Week", typeof(Int32), _PopulatorMinimumPopulationCountPastWeek)); buildList.Add(new CPluginVariable(GetSettingSection(popMonitorSection) + t + "Count to Consider Populator Past 2 Weeks", typeof(Int32), _PopulatorMinimumPopulationCountPast2Weeks)); buildList.Add(new CPluginVariable(GetSettingSection(popMonitorSection) + t + "Enable Populator Perks", typeof(Boolean), _PopulatorPerksEnable)); if (_PopulatorPerksEnable) { buildList.Add(new CPluginVariable(GetSettingSection(popMonitorSection) + t + "Populator Perks - Reserved Slot", typeof(Boolean), _PopulatorPerksReservedSlot)); buildList.Add(new CPluginVariable(GetSettingSection(popMonitorSection) + t + "Populator Perks - Autobalance Whitelist", typeof(Boolean), _PopulatorPerksBalanceWhitelist)); buildList.Add(new CPluginVariable(GetSettingSection(popMonitorSection) + t + "Populator Perks - Ping Whitelist", typeof(Boolean), _PopulatorPerksPingWhitelist)); buildList.Add(new CPluginVariable(GetSettingSection(popMonitorSection) + t + "Populator Perks - TeamKillTracker Whitelist", typeof(Boolean), _PopulatorPerksTeamKillTrackerWhitelist)); } } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building populator setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection(popMonitorSection) + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildTeamspeakSettings(List lstReturn) { List buildList = new List(); var tsMonitorSection = "B28"; try { if (IsActiveSettingSection(tsMonitorSection)) { buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Monitor Teamspeak Players - Thanks CMWGaming", typeof(Boolean), _TeamspeakPlayerMonitorView)); if (_TeamspeakPlayerMonitorView) { buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "[" + _TeamspeakPlayers.Count() + "] Teamspeak Players (Display)", typeof(String[]), _TeamspeakPlayers.Values.Select(aPlayer => aPlayer.player_name + " (" + aPlayer.TSClientObject.TsName + ")").ToArray())); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Enable Teamspeak Player Monitor", typeof(Boolean), _TeamspeakPlayerMonitorEnable)); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Teamspeak Server IP", typeof(String), _TeamspeakManager.Ts3ServerIp)); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Teamspeak Server Port", typeof(Int32), _TeamspeakManager.Ts3ServerPort)); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Teamspeak Server Query Port", typeof(Int32), _TeamspeakManager.Ts3QueryPort)); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Teamspeak Server Query Username", typeof(String), _TeamspeakManager.Ts3QueryUsername)); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Teamspeak Server Query Password", typeof(String), _TeamspeakManager.Ts3QueryPassword)); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Teamspeak Server Query Nickname", typeof(String), _TeamspeakManager.Ts3QueryNickname)); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Teamspeak Main Channel Name", typeof(String), _TeamspeakManager.Ts3MainChannelName)); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Teamspeak Secondary Channel Names", typeof(String[]), _TeamspeakManager.Ts3SubChannelNames)); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Debug Display Teamspeak Clients", typeof(Boolean), _TeamspeakManager.DebugClients)); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "TeamSpeak Player Join Announcement", "enum.tsAnnounceEnum(Disabled|Say|Yell|Tell)", _TeamspeakManager.JoinDisplay.ToString())); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "TeamSpeak Player Join Message", typeof(String), _TeamspeakManager.JoinDisplayMessage)); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "TeamSpeak Player Update Seconds", typeof(Int32), _TeamspeakManager.UpdateIntervalSeconds)); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Enable Teamspeak Player Perks", typeof(Boolean), _TeamspeakPlayerPerksEnable)); if (_TeamspeakPlayerPerksEnable) { buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Teamspeak Player Perks - VIP Kick Whitelist", typeof(Boolean), _TeamspeakPlayerPerksVIPKickWhitelist)); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Teamspeak Player Perks - Autobalance Whitelist", typeof(Boolean), _TeamspeakPlayerPerksBalanceWhitelist)); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Teamspeak Player Perks - Ping Whitelist", typeof(Boolean), _TeamspeakPlayerPerksPingWhitelist)); buildList.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Teamspeak Player Perks - TeamKillTracker Whitelist", typeof(Boolean), _TeamspeakPlayerPerksTeamKillTrackerWhitelist)); } } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building teamspeak setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection(tsMonitorSection) + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildDiscordSettings(List lstReturn) { List buildList = new List(); var discordMonitorSection = "B29"; try { if (IsActiveSettingSection(discordMonitorSection)) { buildList.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "Monitor Discord Players", typeof(Boolean), _DiscordPlayerMonitorView)); if (_DiscordPlayerMonitorView && _DiscordManager != null) { buildList.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "[" + _DiscordPlayers.Count() + "] Discord Players (Display)", typeof(String[]), _DiscordPlayers.Values.Where(aPlayer => aPlayer != null && aPlayer.DiscordObject != null && aPlayer.DiscordObject.Channel != null) .OrderBy(aPlayer => aPlayer.DiscordObject.Channel.Name) .Select(aPlayer => aPlayer.player_name + " [" + aPlayer.DiscordObject.Name + "] (" + aPlayer.DiscordObject.Channel.Name + ") " + (String.IsNullOrEmpty(aPlayer.player_discord_id) ? "[Name]" : "[ID]")) .ToArray())); var discordMembers = _DiscordManager.GetMembers(false, true, true); buildList.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "[" + discordMembers.Count() + "] Discord Channel Members (Display)", typeof(String[]), discordMembers.OrderBy(aMember => aMember.Channel.Name).Select(aMember => aMember.Name + " (" + aMember.Channel.Name + ")").ToArray())); discordMembers = _DiscordManager.GetMembers(false, false, false); buildList.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "[" + discordMembers.Count() + "] Discord All Members (Display)", typeof(String[]), discordMembers.OrderBy(aMember => (aMember.Channel != null ? aMember.Channel.Name : "_NO VOICE_")).Select(aMember => aMember.Name + " (" + (aMember.Channel != null ? aMember.Channel.Name : "_NO VOICE_") + ")").ToArray())); buildList.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "Enable Discord Player Monitor", typeof(Boolean), _DiscordPlayerMonitorEnable)); buildList.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "Discord Server ID", typeof(String), _DiscordManager.ServerID)); buildList.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "Discord Channel Names", typeof(String[]), _DiscordManager.ChannelNames)); buildList.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "Require Voice in Discord to Issue Admin Commands", typeof(Boolean), _DiscordPlayerRequireVoiceForAdmin)); buildList.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "Discord Player Join Announcement", "enum.tsAnnounceEnum(Disabled|Say|Yell|Tell)", _DiscordManager.JoinDisplay.ToString())); buildList.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "Discord Player Join Message", typeof(String), _DiscordManager.JoinMessage)); buildList.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "Enable Discord Player Perks", typeof(Boolean), _DiscordPlayerPerksEnable)); if (_DiscordPlayerPerksEnable) { buildList.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "Discord Player Perks - VIP Kick Whitelist", typeof(Boolean), _DiscordPlayerPerksVIPKickWhitelist)); buildList.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "Discord Player Perks - Autobalance Whitelist", typeof(Boolean), _DiscordPlayerPerksBalanceWhitelist)); buildList.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "Discord Player Perks - Ping Whitelist", typeof(Boolean), _DiscordPlayerPerksPingWhitelist)); buildList.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "Discord Player Perks - TeamKillTracker Whitelist", typeof(Boolean), _DiscordPlayerPerksTeamKillTrackerWhitelist)); } buildList.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "Debug Display Discord Members", typeof(Boolean), _DiscordManager.DebugMembers)); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building discord setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection(discordMonitorSection) + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildTeamPowerSettings(List lstReturn) { List buildList = new List(); var teamPowerSection = "C30"; try { if (IsActiveSettingSection(teamPowerSection)) { buildList.Add(new CPluginVariable(GetSettingSection(teamPowerSection) + t + "Team Power Active Influence", typeof(Double), _TeamPowerActiveInfluence)); try { ATeam t1, t2; String teamPower = "Unknown"; if (_roundState != RoundState.Loaded && GetTeamByID(1, out t1) && GetTeamByID(2, out t2)) { Double t1Power = t1.GetTeamPower(); Double t2Power = t2.GetTeamPower(); Double percDiff = Math.Abs(t1Power - t2Power) / ((t1Power + t2Power) / 2.0) * 100.0; if (t1Power > t2Power) { teamPower = t1.GetTeamIDKey() + " up " + Math.Round(((t1Power - t2Power) / t2Power) * 100) + "% "; } else { teamPower = t2.GetTeamIDKey() + " up " + Math.Round(((t2Power - t1Power) / t1Power) * 100) + "% "; } teamPower += "(" + t1.TeamKey + ":" + t1.GetTeamPower() + ":" + t1.GetTeamPower(false) + " / " + t2.TeamKey + ":" + t2.GetTeamPower() + ":" + t2.GetTeamPower(false) + ")"; } buildList.Add(new CPluginVariable(GetSettingSection(teamPowerSection) + t + "Team Power (Display)", typeof(String), teamPower)); var onlinePlayers = _PlayerDictionary.Values.ToList() .Where(aPlayer => aPlayer.GetPower(true) > 1); var onlinePlayerListing = onlinePlayers .Select(aPlayer => ((aPlayer.RequiredTeam != null) ? ("(" + ((aPlayer.RequiredTeam.TeamID != aPlayer.fbpInfo.TeamID && _roundState == RoundState.Playing) ? (aPlayer.GetTeamKey() + " -> ") : ("")) + aPlayer.RequiredTeam.TeamKey + "+) ") : ("(" + aPlayer.GetTeamKey() + ") ")) + "(" + aPlayer.GetPower(true, true, true).ToString("00") + "|" + aPlayer.GetPower(false, true, true).ToString("00") + "|" + aPlayer.GetPower(true, true, false).ToString("00") + "|" + aPlayer.GetPower(true, false, true).ToString("00") + "|" + aPlayer.GetPower(true, false, false).ToString("00") + "|" + aPlayer.TopStats.TopCount + "|" + aPlayer.TopStats.RoundCount + ") " + aPlayer.GetVerboseName()) .OrderByDescending(item => item) .ToArray(); buildList.Add(new CPluginVariable(GetSettingSection(teamPowerSection) + t + "Player Power (Display)", typeof(String[]), onlinePlayerListing)); } catch (Exception e) { Log.HandleException(new AException("Error building team power displays.", e)); } //buildList.Add(new CPluginVariable(GetSettingSection(teamPowerSection) + t + "Enable Team Power Scrambler", typeof(Boolean), _UseTeamPowerMonitorScrambler)); buildList.Add(new CPluginVariable(GetSettingSection(teamPowerSection) + t + "Enable Team Power Join Reassignment", typeof(Boolean), _UseTeamPowerMonitorReassign)); if (_UseTeamPowerMonitorReassign) { buildList.Add(new CPluginVariable(GetSettingSection(teamPowerSection) + t + "Team Power Join Reassignment Leniency", typeof(Boolean), _UseTeamPowerMonitorReassignLenient)); if (_UseTeamPowerMonitorReassignLenient) { buildList.Add(new CPluginVariable(GetSettingSection(teamPowerSection) + t + "Team Power Join Reassignment Leniency Percent", typeof(Double), _TeamPowerMonitorReassignLenientPercent)); } } buildList.Add(new CPluginVariable(GetSettingSection(teamPowerSection) + t + "Enable Team Power Unswitcher", typeof(Boolean), _UseTeamPowerMonitorUnswitcher)); buildList.Add(new CPluginVariable(GetSettingSection(teamPowerSection) + t + "Enable Team Power Seeder Control", typeof(Boolean), _UseTeamPowerMonitorSeeders)); buildList.Add(new CPluginVariable(GetSettingSection(teamPowerSection) + t + "Display Team Power In Procon Chat", typeof(Boolean), _UseTeamPowerDisplayBalance)); } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building team power setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection(teamPowerSection) + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildWeaponLimiterSettings(List lstReturn) { List buildList = new List(); var weaponLimiterSection = "C31"; try { if (IsActiveSettingSection(weaponLimiterSection)) { buildList.Add(new CPluginVariable(GetSettingSection(weaponLimiterSection) + t + "Use NO EXPLOSIVES Limiter", typeof(Boolean), _UseWeaponLimiter)); if (_UseWeaponLimiter) { buildList.Add(new CPluginVariable(GetSettingSection(weaponLimiterSection) + t + "NO EXPLOSIVES Weapon String", typeof(String), _WeaponLimiterString)); buildList.Add(new CPluginVariable(GetSettingSection(weaponLimiterSection) + t + "NO EXPLOSIVES Exception String", typeof(String), _WeaponLimiterExceptionString)); buildList.Add(new CPluginVariable(GetSettingSection(weaponLimiterSection) + t + "Use Grenade Cook Catcher", typeof(Boolean), _UseGrenadeCookCatcher)); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building weapon limiter section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection(weaponLimiterSection) + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildChallengeSettings(List lstReturn) { List buildList = new List(); var challengeSettings = "C32"; try { if (IsActiveSettingSection(challengeSettings) && ChallengeManager != null) { buildList.Add(new CPluginVariable(GetSettingSection(challengeSettings) + t + "Use Challenge System", typeof(Boolean), ChallengeManager.Enabled)); buildList.Add(new CPluginVariable(GetSettingSection(challengeSettings) + t + "Challenge System Minimum Players", typeof(Int32), ChallengeManager.MinimumPlayers)); buildList.Add(new CPluginVariable(GetSettingSection(challengeSettings) + t + "Use Server-Wide Round Rules", typeof(Boolean), ChallengeManager.EnableServerRoundRules)); if (ChallengeManager.EnableServerRoundRules) { buildList.Add(new CPluginVariable(GetSettingSection(challengeSettings) + t + "Challenge System Auto-Assign Round rules", typeof(Boolean), ChallengeManager.AutoPlay)); if (ChallengeManager.AutoPlay) { buildList.Add(new CPluginVariable(GetSettingSection(challengeSettings) + t + "Use Different Round Rule For Each Player", typeof(Boolean), ChallengeManager.RandomPlayerRoundRules)); } } // DISPLAYS var displaySectionPrefix = GetSettingSection(challengeSettings) + " [1] Displays" + t; var roundRule = ChallengeManager.RoundRule; var roundRuleName = roundRule != null ? roundRule.Name : "No Round Rule"; var activeEntries = _PlayerDictionary.Values.ToList().Where(aPlayer => aPlayer.ActiveChallenge != null) .Select(aPlayer => aPlayer.ActiveChallenge) .OrderBy(entry => entry.Rule.Name) .ThenByDescending(entry => entry.Progress.CompletionPercentage) .ToList(); var roundRuleEntries = new List(); if (roundRule != null) { roundRuleEntries.AddRange(activeEntries.Where(entry => entry.Rule == roundRule)); } var activeEntriesArray = activeEntries.Select(entry => entry.ToString()).ToArray(); buildList.Add(new CPluginVariable(displaySectionPrefix + "[" + activeEntriesArray.Count() + "] Active Entries (Display)", typeof(String[]), activeEntriesArray)); buildList.Add(new CPluginVariable(displaySectionPrefix + "Current Server-Wide Round Rule (Display)", typeof(String), roundRuleName)); var activeRoundEntriesArray = roundRuleEntries.Select(entry => entry.ToString()).ToArray(); buildList.Add(new CPluginVariable(displaySectionPrefix + "[" + activeRoundEntriesArray.Count() + "] Active Round Rule Entries (Display)", typeof(String[]), activeRoundEntriesArray)); // ACTIONS var actionSectionPrefix = GetSettingSection(challengeSettings) + " [2] Actions" + t; buildList.Add(new CPluginVariable(actionSectionPrefix + "Run Round Challenge ID", typeof(Int32), 0)); // DEFINITIONS var defSectionPrefix = GetSettingSection(challengeSettings) + " [3] Definitions" + t; buildList.Add(new CPluginVariable(defSectionPrefix + "Add Definition?", typeof(String), "")); var definitions = ChallengeManager.GetDefinitions().OrderBy(dDef => dDef.Name); foreach (var def in definitions) { if (def.ID <= 0) { Log.Error("Unable to render challenge definition " + def.ID + ". It had an invalid ID of " + def.ID + "."); continue; } //CDH1 | 5 ARs | Change Name? //CDH1 | 5 ARs | Add Damage Type? //CDH1 | 5 ARs | Add Weapon? //CDH1 | 5 ARs | Delete Definition? //CDH1 | 5 ARs | CDD1 | Damage - Assault Rifle | Damage Type //CDH1 | 5 ARs | CDD1 | Damage - Assault Rifle | Weapon Count //CDH1 | 5 ARs | CDD1 | Damage - Assault Rifle | Kill Count //CDH1 | 5 ARs | CDD1 | Damage - Assault Rifle | Delete Detail? //CDH1 | 5 ARs | CDD2 | Weapon - AEK-971 | Weapon Name //CDH1 | 5 ARs | CDD2 | Weapon - AEK-971 | Kill Count //CDH1 | 5 ARs | CDD2 | Weapon - AEK-971 | Delete Detail? var defPrefix = defSectionPrefix + "CDH" + def.ID + s + def.Name + s; buildList.Add(new CPluginVariable(defPrefix + "Change Name?", typeof(String), def.Name)); buildList.Add(new CPluginVariable(defPrefix + "Add Damage Type?", AChallengeManager.CDefinition.DetailDamageEnumString, "None")); buildList.Add(new CPluginVariable(defPrefix + "Add Weapon?", WeaponDictionary.InfantryWeaponNameEnumString, "None")); buildList.Add(new CPluginVariable(defPrefix + "Delete Definition?", typeof(String), "")); foreach (var detail in def.GetDetails()) { if (detail.Type == AChallengeManager.CDefinition.CDefinitionDetail.DetailType.None) { Log.Error("Unable to render challenge definition detail " + def.ID + ":" + detail.DetailID + ". It had a type of None."); continue; } var detailPrefix = defPrefix + "CDD" + detail.DetailID + s + detail.ToString() + s; if (detail.Type == AChallengeManager.CDefinition.CDefinitionDetail.DetailType.Damage) { buildList.Add(new CPluginVariable(detailPrefix + "Damage Type", AChallengeManager.CDefinition.DetailDamageEnumString, detail.Damage.ToString())); buildList.Add(new CPluginVariable(detailPrefix + "Weapon Count", typeof(Int32), detail.WeaponCount)); } else if (detail.Type == AChallengeManager.CDefinition.CDefinitionDetail.DetailType.Weapon) { buildList.Add(new CPluginVariable(detailPrefix + "Weapon Name", WeaponDictionary.InfantryWeaponNameEnumString, WeaponDictionary.GetDamageTypeByWeaponCode(detail.Weapon) + "\\" + WeaponDictionary.GetShortWeaponNameByCode(detail.Weapon))); } buildList.Add(new CPluginVariable(detailPrefix + "Kill Count", typeof(Int32), detail.KillCount)); buildList.Add(new CPluginVariable(detailPrefix + "Delete Detail?", typeof(String), "")); } } // RULES var ruleSectionPrefix = GetSettingSection(challengeSettings) + " [4] Rules" + t; if (!definitions.Any()) { // Do not display options to add rules until definitions exist buildList.Add(new CPluginVariable(ruleSectionPrefix + "Add a challenge definition first.", typeof(String), "")); } else { buildList.Add(new CPluginVariable(ruleSectionPrefix + "Add Rule?", ChallengeManager.GetDefinitionEnum(true), "None")); var rules = ChallengeManager.GetRules().OrderBy(dRule => dRule.Definition.Name).ThenBy(dRule => dRule.Name); var defEnum = ChallengeManager.GetDefinitionEnum(false); foreach (var rule in rules) { if (rule.ID <= 0) { Log.Error("Unable to render challenge rule " + rule.Name + ". It had an invalid ID of " + rule.ID + "."); continue; } //CRH1 | 5 ARs 1 Round | Definition //CRH1 | 5 ARs 1 Round | Name //CRH1 | 5 ARs 1 Round | Enabled //CRH1 | 5 ARs 1 Round | Tier //CRH1 | 5 ARs 1 Round | Completion //CRH1 | 5 ARs 1 Round | Round Count //CRH1 | 5 ARs 1 Round | Delete Rule? //CRH2 | 5 ARs 30 Mins | Definition //CRH2 | 5 ARs 30 Mins | Name //CRH2 | 5 ARs 30 Mins | Enabled //CRH2 | 5 ARs 30 Mins | Tier //CRH2 | 5 ARs 30 Mins | Completion //CRH2 | 5 ARs 30 Mins | Duration Minutes //CRH2 | 5 ARs 30 Mins | Delete Rule? var rulePrefix = ruleSectionPrefix + "CRH" + rule.ID + s + rule.Name + s; buildList.Add(new CPluginVariable(rulePrefix + "Definition", defEnum, rule.Definition.Name)); buildList.Add(new CPluginVariable(rulePrefix + "Name", typeof(String), rule.Name)); buildList.Add(new CPluginVariable(rulePrefix + "Enabled", typeof(Boolean), rule.Enabled)); buildList.Add(new CPluginVariable(rulePrefix + "Tier", typeof(Int32), rule.Tier)); buildList.Add(new CPluginVariable(rulePrefix + "Completion", AChallengeManager.CRule.CompletionTypeEnumString, rule.Completion.ToString())); if (rule.Completion == AChallengeManager.CRule.CompletionType.None) { buildList.Add(new CPluginVariable(rulePrefix + "^^^SET COMPLETION TYPE^^^", typeof(String), "")); } else if (rule.Completion == AChallengeManager.CRule.CompletionType.Rounds) { buildList.Add(new CPluginVariable(rulePrefix + "Round Count", typeof(Int32), rule.RoundCount)); } else if (rule.Completion == AChallengeManager.CRule.CompletionType.Duration) { buildList.Add(new CPluginVariable(rulePrefix + "Duration Minutes", typeof(Int32), rule.DurationMinutes)); } else if (rule.Completion == AChallengeManager.CRule.CompletionType.Deaths) { buildList.Add(new CPluginVariable(rulePrefix + "Death Count", typeof(Int32), rule.DeathCount)); } buildList.Add(new CPluginVariable(rulePrefix + "Delete Rule?", typeof(String), "")); } } // REWARDS var rewardSectionPrefix = GetSettingSection(challengeSettings) + " [5] Rewards" + t; buildList.Add(new CPluginVariable(rewardSectionPrefix + "Challenge Command Lock Timeout Hours", typeof(Int32), ChallengeManager.CommandLockTimeoutHours)); buildList.Add(new CPluginVariable(rewardSectionPrefix + "Add Reward?", typeof(Int32), 0)); var rewards = ChallengeManager.GetRewards().OrderBy(dReward => dReward.Tier).ThenBy(dReward => dReward.Reward.ToString()); foreach (var reward in rewards) { if (reward.ID <= 0) { Log.Error("Unable to render challenge reward " + reward.ID + ". It had an invalid ID."); continue; } //CCR1 | Tier 1 - ReservedSlot | Tier Level //CCR1 | Tier 1 - ReservedSlot | Reward Type //CCR1 | Tier 1 - ReservedSlot | Enabled //CCR1 | Tier 1 - ReservedSlot | Duration Minutes //CCR1 | Tier 1 - ReservedSlot | Delete Reward? var rewardPrefix = rewardSectionPrefix + "CCR" + reward.ID + s + "Tier " + reward.Tier + " - " + reward.getDescriptionString(null) + s; buildList.Add(new CPluginVariable(rewardPrefix + "Tier Level", typeof(Int32), reward.Tier)); buildList.Add(new CPluginVariable(rewardPrefix + "Reward Type", AChallengeManager.CReward.RewardTypeEnumString, reward.Reward.ToString())); buildList.Add(new CPluginVariable(rewardPrefix + "Enabled", typeof(Boolean), reward.Enabled)); buildList.Add(new CPluginVariable(rewardPrefix + "Duration Minutes", typeof(Int32), reward.DurationMinutes)); buildList.Add(new CPluginVariable(rewardPrefix + "Delete Reward?", typeof(String), "")); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building challenge settings section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection(challengeSettings) + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildDebugSettings(List lstReturn) { List buildList = new List(); try { buildList.Add(new CPluginVariable(GetSettingSection("D99") + t + "Debug level", typeof(int), Log.DebugLevel)); if (IsActiveSettingSection("D99")) { //Debug settings buildList.Add(new CPluginVariable(GetSettingSection("D99") + t + "Debug Soldier Name", typeof(String), _debugSoldierName)); buildList.Add(new CPluginVariable(GetSettingSection("D99") + t + "Enforce Single Instance", typeof(Boolean), _enforceSingleInstance)); buildList.Add(new CPluginVariable(GetSettingSection("D99") + t + "Disable Automatic Updates", typeof(Boolean), _automaticUpdatesDisabled)); buildList.Add(new CPluginVariable(GetSettingSection("D99") + t + "Command Entry", typeof(String), "")); buildList.Add(new CPluginVariable(GetSettingSection("D99") + t + "Client Download URL Entry", typeof(String), "")); } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building debug setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection("D99") + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildProxySettings(List lstReturn) { List buildList = new List(); var proxySection = "X98"; try { if (IsActiveSettingSection(proxySection)) { buildList.Add(new CPluginVariable(GetSettingSection(proxySection) + t + "Use Proxy for Battlelog", typeof(Boolean), _UseProxy)); if (_UseProxy) { buildList.Add(new CPluginVariable(GetSettingSection(proxySection) + t + "Proxy URL", typeof(String), _ProxyURL)); } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building proxy setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection(proxySection) + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildExperimentalSettings(List lstReturn) { List buildList = new List(); var ex = "X99"; try { if (IsActiveSettingSection(ex)) { buildList.Add(new CPluginVariable(GetSettingSection(ex) + t + "Use Experimental Tools", typeof(Boolean), _UseExperimentalTools)); if (_UseExperimentalTools) { if (_ShowQuerySettings) { buildList.Add(new CPluginVariable(GetSettingSection(ex) + t + "Send Query", typeof(String), "")); buildList.Add(new CPluginVariable(GetSettingSection(ex) + t + "Send Non-Query", typeof(String), "")); } } } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building experimental setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection(ex) + t + "Failed to build setting section.", typeof(String), "")); } } public void BuildEventSettings(List lstReturn) { List buildList = new List(); var ex = "X99"; var ev = "Y99"; try { if ((IsActiveSettingSection(ex) || IsActiveSettingSection(ev)) && _UseExperimentalTools) { buildList.Add(new CPluginVariable(GetSettingSection(ev) + t + "Event Test Round Number", typeof(Int32), _EventTestRoundNumber)); buildList.Add(new CPluginVariable(GetSettingSection(ev) + t + "Automatically Poll Server For Event Options", typeof(Boolean), _EventPollAutomatic)); if (_EventPollAutomatic) { buildList.Add(new CPluginVariable(GetSettingSection(ev) + t + "Max Automatic Polls Per Event", typeof(Int32), _EventRoundAutoPollsMax)); } buildList.Add(new CPluginVariable(GetSettingSection(ev) + t + "Yell Current Winning Rule Option", typeof(Boolean), _eventPollYellWinningRule)); buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [1] Round Settings" + t + "Event Duration Rounds", typeof(Int32), _EventRoundOptions.Count())); for (int roundNumber = 0; roundNumber < _EventRoundOptions.Count(); roundNumber++) { buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [1] Round Settings" + t + "Event Round " + (roundNumber + 1) + " Options", _EventRoundOptionsEnum, _EventRoundOptions[roundNumber].getDisplay())); } buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [2] Schedule Settings" + t + "Weekly Events", typeof(Boolean), _EventWeeklyRepeat)); if (_EventWeeklyRepeat) { buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [2] Schedule Settings" + t + "Event Day", "enum.weekdays(Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)", _EventWeeklyDay.ToString())); } else { buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [2] Schedule Settings" + t + "Event Date", typeof(String), _EventDate.ToShortDateString())); } buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [2] Schedule Settings" + t + "Event Hour in 24 format", typeof(Double), _EventHour)); buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [2] Schedule Settings" + t + "Is it daylight savings?", typeof(String), DateTime.Now.IsDaylightSavingTime() ? "Yes, currently daylight savings." : "No, not daylight savings.")); buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [2] Schedule Settings" + t + "Event Announce Day Difference", typeof(Double), _EventAnnounceDayDifference)); if (_EventDate.ToShortDateString() != GetLocalEpochTime().ToShortDateString()) { var eventDate = _EventDate.AddHours(_EventHour); buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [3] Schedule Display" + t + "Processed Time Of Event (display)", typeof(String), eventDate.ToShortDateString() + " " + eventDate.ToShortTimeString() + " (" + FormatTimeString(eventDate - DateTime.Now, 3) + ")")); buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [3] Schedule Display" + t + "Current Round Number (display)", typeof(String), String.Format("{0:n0}", _roundID))); buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [3] Schedule Display" + t + "Estimated Event Round Number (display)", typeof(String), String.Format("{0:n0}", FetchEstimatedEventRoundNumber()))); buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [3] Schedule Display" + t + "Concrete Event Round Number (display)", typeof(String), _CurrentEventRoundNumber == 999999 ? "Undecided." : String.Format("{0:n0}", _CurrentEventRoundNumber))); } buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [4] Poll Settings" + t + "Poll Max Option Count", typeof(Int32), _EventPollMaxOptions)); buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [4] Poll Settings" + t + "Poll Mode Rule Combination Count", typeof(Int32), _EventRoundPollOptions.Count())); for (int optionNumber = 0; optionNumber < _EventRoundPollOptions.Count(); optionNumber++) { buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [4] Poll Settings" + t + "Event Poll Option " + (optionNumber + 1), _EventRoundOptionsEnum, _EventRoundPollOptions[optionNumber].getDisplay())); } buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [5] Name Settings" + t + "Event Base Server Name", typeof(String), _eventBaseServerName)); buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [5] Name Settings" + t + "Event Countdown Server Name", typeof(String), _eventCountdownServerName)); buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [5] Name Settings" + t + "Event Concrete Countdown Server Name", typeof(String), _eventConcreteCountdownServerName)); buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [5] Name Settings" + t + "Event Active Server Name", typeof(String), _eventActiveServerName)); buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [5] Name Display" + t + "Processed Base Server Name (display)", typeof(String), ProcessEventServerName(_eventBaseServerName, false, false))); buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [5] Name Display" + t + "Processed Countdown Server Name (display)", typeof(String), ProcessEventServerName(_eventCountdownServerName, false, false))); buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [5] Name Display" + t + "Processed Concrete Countdown Server Name (display)", typeof(String), ProcessEventServerName(_eventConcreteCountdownServerName, false, true))); buildList.Add(new CPluginVariable(GetSettingSection(ev) + " [5] Name Display" + t + "Processed Active Server Name (display)", typeof(String), ProcessEventServerName(_eventActiveServerName, true, true))); } lstReturn.AddRange(buildList); } catch (Exception e) { Log.HandleException(new AException("Error building event setting section.", e)); lstReturn.Add(new CPluginVariable(GetSettingSection(ev) + t + "Failed to build setting section.", typeof(String), "")); } } public List GetDisplayPluginVariables() { try { Log.Debug(() => "Updating Setting Page [" + ((String.IsNullOrEmpty(Thread.CurrentThread.Name)) ? ("Main") : (Thread.CurrentThread.Name)) + "]: " + (UtcNow() - _lastSettingPageUpdate).TotalSeconds + " seconds since last update.", 4); _lastSettingPageUpdate = UtcNow(); Stopwatch timer = new Stopwatch(); timer.Start(); List lstReturn = new List(); if (_settingsLocked) { lstReturn.Add(new CPluginVariable(GetSettingSection("0") + t + "Unlock Settings", typeof(String), "")); } else { lstReturn.Add(new CPluginVariable(String.IsNullOrEmpty(_settingsPassword) ? (GetSettingSection("0") + t + "Lock Settings - Create Password") : (GetSettingSection("0") + t + "Lock Settings"), typeof(String), "")); } //Only fetch the following settings when plugin disabled if (!_threadsReady) { BuildUnreadySettings(lstReturn); } else { if (_settingsLocked) { BuildReadyLockedSettings(lstReturn); timer.Stop(); return lstReturn; } lstReturn.Add(new CPluginVariable("* AdKats *|Current Setting Section", _SettingSectionEnum, _CurrentSettingSection)); //Auto-Enable Settings lstReturn.Add(new CPluginVariable(GetSettingSection("0") + t + "Auto-Enable/Keep-Alive", typeof(Boolean), _useKeepAlive)); BuildServerSettings(lstReturn); BuildSQLSettings(lstReturn); BuildUserSettings(lstReturn); BuildSpecialPlayerSettings(lstReturn); BuildVerboseSpecialPlayerSettings(lstReturn); BuildRoleSettings(lstReturn); BuildRoleGroupSettings(lstReturn); BuildCommandSettings(lstReturn); BuildCommandListSettings(lstReturn); BuildPunishmentSettings(lstReturn); BuildEmailSettings(lstReturn); BuildPushbulletSettings(lstReturn); BuildDiscordWebHookSettings(lstReturn); BuildTeamswapSettings(lstReturn); BuildAASettings(lstReturn); BuildMuteSettings(lstReturn); BuildMessagingSettings(lstReturn); BuildSpambotSettings(lstReturn); BuildBattlecrySettings(lstReturn); BuildAllCapsSettings(lstReturn); BuildBanSettings(lstReturn); BuildExternalCommandSettings(lstReturn); BuildVOIPSettings(lstReturn); BuildOrchestrationSettings(lstReturn); BuildRoundSettings(lstReturn); BuildFactionSettings(lstReturn); BuildAntiCheatSettings(lstReturn); BuildRuleSettings(lstReturn); BuildAFKSettings(lstReturn); BuildPingSettings(lstReturn); BuildCommanderSettings(lstReturn); BuildPlayerLockingSettings(lstReturn); BuildSurrenderSettings(lstReturn); BuildAutoSurrenderSettings(lstReturn); BuildStatisticsSettings(lstReturn); BuildPopulatorSettings(lstReturn); BuildTeamspeakSettings(lstReturn); BuildDiscordSettings(lstReturn); BuildTeamPowerSettings(lstReturn); BuildWeaponLimiterSettings(lstReturn); BuildChallengeSettings(lstReturn); BuildDebugSettings(lstReturn); BuildProxySettings(lstReturn); BuildExperimentalSettings(lstReturn); BuildEventSettings(lstReturn); } timer.Stop(); return lstReturn; } catch (Exception e) { Log.HandleException(new AException("Error while fetching display vars.", e)); return new List(); } } public List GetPluginVariables() { List lstReturn = new List(); try { lstReturn.Add(new CPluginVariable(GetSettingSection("0") + t + "Auto-Enable/Keep-Alive", typeof(Boolean), _useKeepAlive)); lstReturn.Add(new CPluginVariable(GetSettingSection("1") + t + "Settings Locked", typeof(Boolean), _settingsLocked, true)); lstReturn.Add(new CPluginVariable(GetSettingSection("1") + t + "Settings Password", typeof(String), _settingsPassword)); lstReturn.Add(new CPluginVariable(GetSettingSection("2") + t + "MySQL Hostname", typeof(String), _mySqlHostname)); lstReturn.Add(new CPluginVariable(GetSettingSection("2") + t + "MySQL Port", typeof(String), _mySqlPort)); lstReturn.Add(new CPluginVariable(GetSettingSection("2") + t + "MySQL Database", typeof(String), _mySqlSchemaName)); lstReturn.Add(new CPluginVariable(GetSettingSection("2") + t + "MySQL Username", typeof(String), _mySqlUsername)); lstReturn.Add(new CPluginVariable(GetSettingSection("2") + t + "MySQL Password", typeof(String), _mySqlPassword)); lstReturn.Add(new CPluginVariable(GetSettingSection("D98") + t + "Override Timing Confirmation", typeof(Boolean), _timingValidOverride)); lstReturn.Add(new CPluginVariable(GetSettingSection("D99") + t + "Enforce Single Instance", typeof(Boolean), _enforceSingleInstance)); lstReturn.Add(new CPluginVariable(GetSettingSection("D99") + t + "Debug level", typeof(Int32), Log.DebugLevel)); lstReturn.Add(new CPluginVariable(GetSettingSection("D99") + t + "Disable Automatic Updates", typeof(Boolean), _automaticUpdatesDisabled)); lstReturn.Add(new CPluginVariable("startup_durations", typeof(String[]), _startupDurations.Select(duration => ((int)duration.TotalSeconds).ToString()).ToArray())); } catch (Exception e) { Log.HandleException(new AException("Error while fetching save vars.", e)); } return lstReturn; } public void SetPluginVariable(String strVariable, String strValue) { if (strValue == null) { return; } try { if (strVariable == "UpdateSettings") { //Do nothing. Settings page will be updated after return. } else if (strVariable == "startup_durations") { var stringDurations = CPluginVariable.DecodeStringArray(strValue); _startupDurations.Clear(); foreach (String stringDuration in stringDurations) { _startupDurations.Enqueue(TimeSpan.FromSeconds(Int32.Parse(stringDuration))); } } else if (Regex.Match(strVariable, @"Auto-Enable/Keep-Alive").Success) { Boolean autoEnable = Boolean.Parse(strValue); if (autoEnable != _useKeepAlive) { if (autoEnable) { Enable(); } _useKeepAlive = autoEnable; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Enable/Keep-Alive", typeof(Boolean), _useKeepAlive)); } } else if (Regex.Match(strVariable, @"Current Setting Section").Success) { _CurrentSettingSection = strValue; } else if (Regex.Match(strVariable, @"Override Timing Confirmation").Success) { Boolean dbTimingValidOverride = Boolean.Parse(strValue); if (dbTimingValidOverride != _timingValidOverride) { _timingValidOverride = dbTimingValidOverride; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Override Timing Confirmation", typeof(Boolean), _timingValidOverride)); } } else if (Regex.Match(strVariable, @"Unlock Settings").Success) { if (String.IsNullOrEmpty(strValue) || strValue.Length < 5) { return; } if (strValue != _settingsPassword) { Log.Error("Password incorrect."); return; } _settingsLocked = false; Log.Success("Settings unlocked."); QueueSettingForUpload(new CPluginVariable(@"Settings Locked", typeof(Boolean), _settingsLocked)); } else if (Regex.Match(strVariable, @"Lock Settings - Create Password").Success) { if (String.IsNullOrEmpty(strValue) || strValue.Length < 5) { Log.Error("Password had invalid format/length, unable to submit."); return; } _settingsPassword = strValue; _settingsLocked = true; Log.Success("Password created. Settings Locked."); QueueSettingForUpload(new CPluginVariable(@"Settings Password", typeof(String), _settingsPassword)); QueueSettingForUpload(new CPluginVariable(@"Settings Locked", typeof(Boolean), _settingsLocked)); } else if (Regex.Match(strVariable, @"Lock Settings").Success) { if (String.IsNullOrEmpty(strValue) || strValue.Length < 5) { return; } if (strValue != _settingsPassword) { Log.Error("Password incorrect."); return; } _settingsLocked = true; Log.Success("Settings locked."); QueueSettingForUpload(new CPluginVariable(@"Settings Locked", typeof(Boolean), _settingsLocked)); } else if (Regex.Match(strVariable, @"Settings Password").Success) { if (String.IsNullOrEmpty(strValue) || strValue.Length < 5) { return; } _settingsPassword = strValue; } else if (Regex.Match(strVariable, @"Settings Locked").Success) { _settingsLocked = Boolean.Parse(strValue); } else if (Regex.Match(strVariable, @"Send Query").Success) { if (_databaseConnectionCriticalState) { return; } SendQuery(strValue, true); _ShowQuerySettings = false; } else if (Regex.Match(strVariable, @"Send Non-Query").Success) { if (_databaseConnectionCriticalState) { return; } SendNonQuery("Experimental Query", strValue, true); _ShowQuerySettings = false; } else if (Regex.Match(strVariable, @"Setting Import").Success) { Int32 tmp = -1; if (int.TryParse(strValue, out tmp)) { if (tmp != -1) { QueueSettingImport(tmp); } } else { Log.Error("Invalid Input for Setting Import"); } } else if (Regex.Match(strVariable, @"Command Entry").Success) { if (String.IsNullOrEmpty(strValue)) { return; } //Check if the message is a command if (strValue.StartsWith("@") || strValue.StartsWith("!") || strValue.StartsWith(".")) { strValue = strValue.Substring(1); } else if (strValue.StartsWith("/@") || strValue.StartsWith("/!") || strValue.StartsWith("/.")) { strValue = strValue.Substring(2); } else if (strValue.StartsWith("/")) { strValue = strValue.Substring(1); } else { Log.Error("Invalid command format."); return; } ARecord record = new ARecord { record_source = ARecord.Sources.Settings, record_access = ARecord.AccessMethod.HiddenExternal, source_name = "SettingsAdmin", record_time = UtcNow() }; CompleteRecordInformation(record, new AChatMessage() { Message = strValue }); } else if (Regex.Match(strVariable, @"Client Download URL Entry").Success) { if (String.IsNullOrEmpty(strValue)) { return; } using (GZipWebClient client = new GZipWebClient(compress: false)) { try { client.Encoding = Encoding.UTF8; String response = Util.ClientDownloadTimer(client, strValue); if (String.IsNullOrEmpty(response)) { Log.Warn("Request response was empty."); } else { Log.Success("Request response received [Length " + response.Length + "], displaying."); try { // Remove surrogate codepoint values as raw text var tester = new Regex(@"[\\][u][d][8-9a-f][0-9a-f][0-9a-f]", RegexOptions.IgnoreCase); if (tester.IsMatch(response)) { Log.Warn("Found invalid codepoint raw text values."); } else { Log.Success("No invalid codepoint raw text values."); } response = tester.Replace(response, String.Empty); Log.Success("[Length " + response.Length + "]."); var responseJSON = (Hashtable)JSON.JsonDecode(response); Log.Warn("Parsed as JSON."); } catch (Exception e) { Log.Warn("Unable to parse as JSON. " + e.Message); } response = response.Length <= 500 ? response : response.Substring(0, 500); Log.Info(response); } } catch (Exception e) { Log.HandleException(new AException("Error downloading/displaying response from " + strValue, e)); } } } else if (Regex.Match(strVariable, @"Debug level").Success) { Int32 tmp; if (int.TryParse(strValue, out tmp)) { if (tmp == -10) { Log.Info("8345: Clear all fetched players and left players."); Log.Info("3958: Print average database read/write durations."); Log.Info("5682: Toggle discord debug."); Log.Info("2563: Toggle player fetch debug."); Log.Info("7621: Toggle player listing debug."); } else if (tmp == 8345) { _FetchedPlayers.Clear(); _PlayerLeftDictionary.Clear(); } else if (tmp == 3958) { Log.Info("Avg Read: " + _DatabaseReadAverageDuration + " | Avg Write: " + _DatabaseWriteAverageDuration); } else if (tmp == 2232) { Environment.Exit(2232); } else if (tmp == 3840) { _DebugKills = !_DebugKills; } else if (tmp == 8142) { _ShowQuerySettings = true; } else if (tmp == 5682) { _DiscordManager.DebugService = !_DiscordManager.DebugService; Log.Info("Discord Debug Display: " + _DiscordManager.DebugService); } else if (tmp == 2563) { _debugDisplayPlayerFetches = !_debugDisplayPlayerFetches; Log.Info("Player Fetch Debug Display: " + _debugDisplayPlayerFetches); } else if (tmp == 7621) { _DebugPlayerListing = !_DebugPlayerListing; Log.Info("Player Listing Debug Display: " + _DebugPlayerListing); } else if (tmp != Log.DebugLevel) { if (tmp < 0) { tmp = 0; } Log.DebugLevel = tmp; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Debug level", typeof(int), Log.DebugLevel)); } } } else if (Regex.Match(strVariable, @"Debug Soldier Name").Success) { if (IsSoldierNameValid(strValue)) { if (strValue != _debugSoldierName) { _debugSoldierName = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Debug Soldier Name", typeof(String), _debugSoldierName)); } } } else if (Regex.Match(strVariable, @"Maximum Temp-Ban Duration Minutes").Success) { Double maxDuration; if (!Double.TryParse(strValue, out maxDuration)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (maxDuration <= 0) { Log.Error("Max duration cannot be negative."); return; } TimeSpan tempMaxDur = TimeSpan.FromMinutes(maxDuration); if (tempMaxDur.TotalDays > 3650) { Log.Error("Max duration cannot be longer than 10 years."); return; } _MaxTempBanDuration = tempMaxDur; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Maximum Temp-Ban Duration Minutes", typeof(Double), _MaxTempBanDuration.TotalMinutes)); } else if (Regex.Match(strVariable, @"Server VOIP Address").Success) { if (strValue != _ServerVoipAddress) { _ServerVoipAddress = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Server VOIP Address", typeof(String), _ServerVoipAddress)); } } else if (Regex.Match(strVariable, @"Rule Print Delay").Success) { Double delay; if (!Double.TryParse(strValue, out delay)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_ServerRulesDelay != delay) { if (delay < 0) { Log.Error("Delay cannot be negative."); delay = 0.1; } _ServerRulesDelay = delay; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Rule Print Delay", typeof(Double), _ServerRulesDelay)); } } else if (Regex.Match(strVariable, @"Rule Print Interval").Success) { Double interval; if (!Double.TryParse(strValue, out interval)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_ServerRulesInterval != interval) { if (interval <= 0) { Log.Error("Interval cannot be negative."); interval = 5.0; } _ServerRulesInterval = interval; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Rule Print Interval", typeof(Double), _ServerRulesInterval)); } } else if (Regex.Match(strVariable, @"Server Rule List").Success) { _ServerRulesList = CPluginVariable.DecodeStringArray(strValue); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Server Rule List", typeof(String), CPluginVariable.EncodeStringArray(_ServerRulesList))); } else if (Regex.Match(strVariable, @"Server Rule Numbers").Success) { Boolean ruleNumbers = Boolean.Parse(strValue); if (ruleNumbers != _ServerRulesNumbers) { _ServerRulesNumbers = ruleNumbers; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Server Rule Numbers", typeof(Boolean), _ServerRulesNumbers)); } } else if (Regex.Match(strVariable, @"Yell Server Rules").Success) { Boolean ruleYell = Boolean.Parse(strValue); if (ruleYell != _ServerRulesYell) { _ServerRulesYell = ruleYell; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Yell Server Rules", typeof(Boolean), _ServerRulesYell)); } } else if (Regex.Match(strVariable, @"Disable Automatic Updates").Success) { Boolean disableAutomaticUpdates = Boolean.Parse(strValue); if (disableAutomaticUpdates != _automaticUpdatesDisabled) { _automaticUpdatesDisabled = disableAutomaticUpdates; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Disable Automatic Updates", typeof(Boolean), _automaticUpdatesDisabled)); } } else if (Regex.Match(strVariable, @"Enforce Single Instance").Success) { Boolean enforceSingleInstance = Boolean.Parse(strValue); if (enforceSingleInstance != _enforceSingleInstance) { _enforceSingleInstance = enforceSingleInstance; if (!_enforceSingleInstance && _threadsReady) { var message = "Running multiple instances of AdKats on the same server is a very bad idea. If you are sure this won't happen, it's safe to disable this setting."; Log.Warn(message); Log.Warn(message); Log.Warn(message); } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Enforce Single Instance", typeof(Boolean), _enforceSingleInstance)); } } else if (Regex.Match(strVariable, @"AFK System Enable").Success) { if (_serverInfo.ServerType == "OFFICIAL" && Boolean.Parse(strValue) == true) { strValue = "False"; Log.Error("'" + strVariable + "' cannot be enabled on official servers."); return; } Boolean afkSystemEnable = Boolean.Parse(strValue); if (afkSystemEnable != _AFKManagerEnable) { _AFKManagerEnable = afkSystemEnable; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"AFK System Enable", typeof(Boolean), _AFKManagerEnable)); } } else if (Regex.Match(strVariable, @"AFK Ignore Chat").Success) { Boolean afkIgnoreChat = Boolean.Parse(strValue); if (afkIgnoreChat != _AFKIgnoreChat) { _AFKIgnoreChat = afkIgnoreChat; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"AFK Ignore Chat", typeof(Boolean), _AFKIgnoreChat)); } } else if (Regex.Match(strVariable, @"AFK Auto-Kick Enable").Success) { if (_serverInfo.ServerType == "OFFICIAL" && Boolean.Parse(strValue) == true) { strValue = "False"; Log.Error("'" + strVariable + "' cannot be enabled on official servers."); return; } Boolean afkAutoKickEnable = Boolean.Parse(strValue); if (afkAutoKickEnable != _AFKAutoKickEnable) { _AFKAutoKickEnable = afkAutoKickEnable; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"AFK Auto-Kick Enable", typeof(Boolean), _AFKAutoKickEnable)); } } else if (Regex.Match(strVariable, @"AFK Trigger Minutes").Success) { Double afkAutoKickDurationMinutes; if (!Double.TryParse(strValue, out afkAutoKickDurationMinutes)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_AFKTriggerDurationMinutes != afkAutoKickDurationMinutes) { if (afkAutoKickDurationMinutes < 0) { Log.Error("Duration cannot be negative."); return; } _AFKTriggerDurationMinutes = afkAutoKickDurationMinutes; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"AFK Trigger Minutes", typeof(Double), _AFKTriggerDurationMinutes)); } } else if (Regex.Match(strVariable, @"AFK Minimum Players").Success) { Int32 afkAutoKickMinimumPlayers = Int32.Parse(strValue); if (_AFKTriggerMinimumPlayers != afkAutoKickMinimumPlayers) { if (afkAutoKickMinimumPlayers < 0) { Log.Error("Minimum players cannot be negative."); return; } _AFKTriggerMinimumPlayers = afkAutoKickMinimumPlayers; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"AFK Minimum Players", typeof(Int32), _AFKTriggerMinimumPlayers)); } } else if (Regex.Match(strVariable, @"AFK Ignore User List").Success) { Boolean afkIgnoreUserList = Boolean.Parse(strValue); if (afkIgnoreUserList != _AFKIgnoreUserList) { _AFKIgnoreUserList = afkIgnoreUserList; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"AFK Ignore User List", typeof(Boolean), _AFKIgnoreUserList)); } } else if (Regex.Match(strVariable, @"AFK Ignore Roles").Success) { _AFKIgnoreRoles = CPluginVariable.DecodeStringArray(strValue); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"AFK Ignore Roles", typeof(String), CPluginVariable.EncodeStringArray(_AFKIgnoreRoles))); } else if (Regex.Match(strVariable, @"Ping Enforcer Enable").Success) { if (_serverInfo.ServerType == "OFFICIAL" && Boolean.Parse(strValue) == true) { strValue = "False"; Log.Error("'" + strVariable + "' cannot be enabled on official servers."); return; } Boolean PingSystemEnable = Boolean.Parse(strValue); if (PingSystemEnable != _pingEnforcerEnable) { _pingEnforcerEnable = PingSystemEnable; //Once setting has been changed, upload the change to database if (_pingEnforcerEnable) { //Disable latency manager ExecuteCommand("procon.protected.plugins.enable", "CLatencyManager", "False"); } QueueSettingForUpload(new CPluginVariable(@"Ping Enforcer Enable", typeof(Boolean), _pingEnforcerEnable)); } } else if (Regex.Match(strVariable, @"Ping Moving Average Duration sec").Success) { Double pingMovingAverageDurationSeconds; if (!Double.TryParse(strValue, out pingMovingAverageDurationSeconds)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_pingMovingAverageDurationSeconds != pingMovingAverageDurationSeconds) { if (pingMovingAverageDurationSeconds < 30) { Log.Error("Duration cannot be less than 30 seconds."); return; } _pingMovingAverageDurationSeconds = pingMovingAverageDurationSeconds; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ping Moving Average Duration sec", typeof(Double), _pingMovingAverageDurationSeconds)); } } else if (Regex.Match(strVariable, @"Ping Kick Low Population Trigger ms").Success) { Double pingEnforcerLowTriggerMS; if (!Double.TryParse(strValue, out pingEnforcerLowTriggerMS)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_pingEnforcerLowTriggerMS != pingEnforcerLowTriggerMS) { if (pingEnforcerLowTriggerMS < 10) { Log.Error("Trigger ms cannot be less than 10."); return; } _pingEnforcerLowTriggerMS = pingEnforcerLowTriggerMS; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ping Kick Low Population Trigger ms", typeof(Double), _pingEnforcerLowTriggerMS)); } } else if (Regex.Match(strVariable, @"Ping Kick Low Population Time Modifier").Success) { Int32 parser; var timeModifiers = CPluginVariable.DecodeStringArray(strValue) .Select((modifier, index) => ((Int32.TryParse(modifier.Trim(), out parser)) ? (parser) : (0))) .Take(24).ToList(); while (timeModifiers.Count() < 24) { Log.Error("Not all hours accounted for, adding 0 for low hour " + (timeModifiers.Count() - 1)); timeModifiers.Add(0); } _pingEnforcerLowTimeModifier = timeModifiers.ToArray(); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ping Kick Low Population Time Modifier", typeof(String), CPluginVariable.EncodeStringArray(_pingEnforcerLowTimeModifier.Select(x => x.ToString()).ToArray()))); } else if (Regex.Match(strVariable, @"Ping Kick Medium Population Trigger ms").Success) { Double pingEnforcerMedTriggerMS; if (!Double.TryParse(strValue, out pingEnforcerMedTriggerMS)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_pingEnforcerMedTriggerMS != pingEnforcerMedTriggerMS) { if (pingEnforcerMedTriggerMS < 10) { Log.Error("Trigger ms cannot be less than 10."); return; } _pingEnforcerMedTriggerMS = pingEnforcerMedTriggerMS; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ping Kick Medium Population Trigger ms", typeof(Double), _pingEnforcerMedTriggerMS)); } } else if (Regex.Match(strVariable, @"Ping Kick Medium Population Time Modifier").Success) { Int32 parser; var timeModifiers = CPluginVariable.DecodeStringArray(strValue) .Select((modifier, index) => ((Int32.TryParse(modifier.Trim(), out parser)) ? (parser) : (0))) .Take(24).ToList(); while (timeModifiers.Count() < 24) { Log.Error("Not all hours accounted for, adding 0 for medium hour " + (timeModifiers.Count() - 1)); timeModifiers.Add(0); } _pingEnforcerMedTimeModifier = timeModifiers.ToArray(); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ping Kick Medium Population Time Modifier", typeof(String), CPluginVariable.EncodeStringArray(_pingEnforcerMedTimeModifier.Select(x => x.ToString()).ToArray()))); } else if (Regex.Match(strVariable, @"Ping Kick High Population Trigger ms").Success) { Double pingEnforcerHighTriggerMS; if (!Double.TryParse(strValue, out pingEnforcerHighTriggerMS)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_pingEnforcerHighTriggerMS != pingEnforcerHighTriggerMS) { if (pingEnforcerHighTriggerMS < 10) { Log.Error("Trigger ms cannot be less than 10."); return; } _pingEnforcerHighTriggerMS = pingEnforcerHighTriggerMS; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ping Kick High Population Trigger ms", typeof(Double), _pingEnforcerHighTriggerMS)); } } else if (Regex.Match(strVariable, @"Ping Kick High Population Time Modifier").Success) { Int32 parser; var timeModifiers = CPluginVariable.DecodeStringArray(strValue) .Select((modifier, index) => ((Int32.TryParse(modifier.Trim(), out parser)) ? (parser) : (0))) .Take(24).ToList(); while (timeModifiers.Count() < 24) { Log.Error("Not all hours accounted for, adding 0 for high hour " + (timeModifiers.Count() - 1)); timeModifiers.Add(0); } _pingEnforcerHighTimeModifier = timeModifiers.ToArray(); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ping Kick High Population Time Modifier", typeof(String), CPluginVariable.EncodeStringArray(_pingEnforcerHighTimeModifier.Select(x => x.ToString()).ToArray()))); } else if (Regex.Match(strVariable, @"Ping Kick Full Population Trigger ms").Success) { Double pingEnforcerFullTriggerMS; if (!Double.TryParse(strValue, out pingEnforcerFullTriggerMS)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_pingEnforcerFullTriggerMS != pingEnforcerFullTriggerMS) { if (pingEnforcerFullTriggerMS < 10) { Log.Error("Trigger ms cannot be less than 10."); return; } _pingEnforcerFullTriggerMS = pingEnforcerFullTriggerMS; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ping Kick Full Population Trigger ms", typeof(Double), _pingEnforcerFullTriggerMS)); } } else if (Regex.Match(strVariable, @"Ping Kick Full Population Time Modifier").Success) { Int32 parser; var timeModifiers = CPluginVariable.DecodeStringArray(strValue) .Select((modifier, index) => ((Int32.TryParse(modifier.Trim(), out parser)) ? (parser) : (0))) .Take(24).ToList(); while (timeModifiers.Count() < 24) { Log.Error("Not all hours accounted for, adding 0 for full hour " + (timeModifiers.Count() - 1)); timeModifiers.Add(0); } _pingEnforcerFullTimeModifier = timeModifiers.ToArray(); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ping Kick Full Population Time Modifier", typeof(String), CPluginVariable.EncodeStringArray(_pingEnforcerFullTimeModifier.Select(x => x.ToString()).ToArray()))); } else if (Regex.Match(strVariable, @"Ping Kick Minimum Players").Success) { Int32 pingEnforcerTriggerMinimumPlayers = Int32.Parse(strValue); if (_pingEnforcerTriggerMinimumPlayers != pingEnforcerTriggerMinimumPlayers) { if (pingEnforcerTriggerMinimumPlayers < 0) { Log.Error("Minimum players cannot be negative."); return; } _pingEnforcerTriggerMinimumPlayers = pingEnforcerTriggerMinimumPlayers; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ping Kick Minimum Players", typeof(Int32), _pingEnforcerTriggerMinimumPlayers)); } } else if (Regex.Match(strVariable, @"Kick Missing Pings").Success) { Boolean pingEnforcerKickMissingPings = Boolean.Parse(strValue); if (pingEnforcerKickMissingPings != _pingEnforcerKickMissingPings) { _pingEnforcerKickMissingPings = pingEnforcerKickMissingPings; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Kick Missing Pings", typeof(Boolean), _pingEnforcerKickMissingPings)); } } else if (Regex.Match(strVariable, @"Attempt Manual Ping when Missing").Success) { Boolean attemptManualPingWhenMissing = Boolean.Parse(strValue); if (attemptManualPingWhenMissing != _attemptManualPingWhenMissing) { _attemptManualPingWhenMissing = attemptManualPingWhenMissing; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Attempt Manual Ping when Missing", typeof(Boolean), _attemptManualPingWhenMissing)); } } else if (Regex.Match(strVariable, @"Display Ping Enforcer Messages In Procon Chat").Success) { Boolean pingEnforcerDisplayProconChat = Boolean.Parse(strValue); if (pingEnforcerDisplayProconChat != _pingEnforcerDisplayProconChat) { _pingEnforcerDisplayProconChat = pingEnforcerDisplayProconChat; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Display Ping Enforcer Messages In Procon Chat", typeof(Boolean), _pingEnforcerDisplayProconChat)); } } else if (Regex.Match(strVariable, @"Ping Kick Ignore User List").Success) { Boolean pingEnforcerIgnoreUserList = Boolean.Parse(strValue); if (pingEnforcerIgnoreUserList != _pingEnforcerIgnoreUserList) { _pingEnforcerIgnoreUserList = pingEnforcerIgnoreUserList; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ping Kick Ignore User List", typeof(Boolean), _pingEnforcerIgnoreUserList)); } } else if (Regex.Match(strVariable, @"Ping Kick Ignore Roles").Success) { _pingEnforcerIgnoreRoles = CPluginVariable.DecodeStringArray(strValue); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ping Kick Ignore Roles", typeof(String), CPluginVariable.EncodeStringArray(_pingEnforcerIgnoreRoles))); } else if (Regex.Match(strVariable, @"Ping Kick Message Prefix").Success) { if (strValue != _pingEnforcerMessagePrefix) { _pingEnforcerMessagePrefix = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ping Kick Message Prefix", typeof(String), _pingEnforcerMessagePrefix)); } } else if (Regex.Match(strVariable, @"Commander Manager Enable").Success) { if (_serverInfo.ServerType == "OFFICIAL" && Boolean.Parse(strValue) == true) { strValue = "False"; Log.Error("'" + strVariable + "' cannot be enabled on official servers."); return; } Boolean CMDRManagerEnable = Boolean.Parse(strValue); if (CMDRManagerEnable != _CMDRManagerEnable) { if (GameVersion == GameVersionEnum.BF3 && CMDRManagerEnable) { Log.Error("Commander manager cannot be enabled in BF3"); _CMDRManagerEnable = false; } else { _CMDRManagerEnable = CMDRManagerEnable; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Commander Manager Enable", typeof(Boolean), _CMDRManagerEnable)); } } } else if (Regex.Match(strVariable, @"Minimum Players to Allow Commanders").Success) { Int32 CMDRMinimumPlayers = Int32.Parse(strValue); if (_CMDRMinimumPlayers != CMDRMinimumPlayers) { if (CMDRMinimumPlayers < 0) { Log.Error("Minimum players cannot be negative."); return; } _CMDRMinimumPlayers = CMDRMinimumPlayers; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Minimum Players to Allow Commanders", typeof(Int32), _CMDRMinimumPlayers)); } } else if (Regex.Match(strVariable, @"Surrender Vote Enable").Success) { if (_serverInfo.ServerType == "OFFICIAL" && Boolean.Parse(strValue) == true) { strValue = "False"; Log.Error("'" + strVariable + "' cannot be enabled on official servers."); return; } Boolean surrenderVoteEnable = Boolean.Parse(strValue); if (surrenderVoteEnable != _surrenderVoteEnable) { _surrenderVoteEnable = surrenderVoteEnable; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Surrender Vote Enable", typeof(Boolean), _surrenderVoteEnable)); } } else if (Regex.Match(strVariable, @"Percentage Votes Needed for Surrender").Success) { Double surrenderVoteMinimumPlayerPercentage; if (!Double.TryParse(strValue, out surrenderVoteMinimumPlayerPercentage)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_surrenderVoteMinimumPlayerPercentage != surrenderVoteMinimumPlayerPercentage) { if (surrenderVoteMinimumPlayerPercentage < 0) { Log.Error("Minimum player percentage cannot be negative."); return; } if (surrenderVoteMinimumPlayerPercentage > 100) { Log.Error("Minimum player percentage cannot be greater than 100."); return; } _surrenderVoteMinimumPlayerPercentage = surrenderVoteMinimumPlayerPercentage; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Percentage Votes Needed for Surrender", typeof(Double), _surrenderVoteMinimumPlayerPercentage)); } } else if (Regex.Match(strVariable, @"Minimum Player Count to Enable Surrender").Success) { Int32 surrenderVoteMinimumPlayerCount = Int32.Parse(strValue); if (_surrenderVoteMinimumPlayerCount != surrenderVoteMinimumPlayerCount) { if (surrenderVoteMinimumPlayerCount < 0) { Log.Error("Minimum player count cannot be negative."); return; } _surrenderVoteMinimumPlayerCount = surrenderVoteMinimumPlayerCount; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Minimum Player Count to Enable Surrender", typeof(Int32), _surrenderVoteMinimumPlayerCount)); } } else if (Regex.Match(strVariable, @"Minimum Ticket Gap to Surrender").Success) { Int32 surrenderVoteMinimumTicketGap = Int32.Parse(strValue); if (_surrenderVoteMinimumTicketGap != surrenderVoteMinimumTicketGap) { if (surrenderVoteMinimumTicketGap < 0) { Log.Error("Minimum ticket gap cannot be negative."); return; } _surrenderVoteMinimumTicketGap = surrenderVoteMinimumTicketGap; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Minimum Ticket Gap to Surrender", typeof(Int32), _surrenderVoteMinimumTicketGap)); } } else if (Regex.Match(strVariable, @"Enable Required Ticket Rate Gap to Surrender").Success) { Boolean surrenderVoteTicketRateGapEnable = Boolean.Parse(strValue); if (surrenderVoteTicketRateGapEnable != _surrenderVoteTicketRateGapEnable) { _surrenderVoteTicketRateGapEnable = surrenderVoteTicketRateGapEnable; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Enable Required Ticket Rate Gap to Surrender", typeof(Boolean), _surrenderVoteTicketRateGapEnable)); } } else if (Regex.Match(strVariable, @"Minimum Ticket Rate Gap to Surrender").Success) { Double surrenderVoteMinimumTicketRateGap; if (!Double.TryParse(strValue, out surrenderVoteMinimumTicketRateGap)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_surrenderVoteMinimumTicketRateGap != surrenderVoteMinimumTicketRateGap) { if (surrenderVoteMinimumTicketRateGap < 0) { Log.Error("Minimum ticket rate gap cannot be negative."); return; } _surrenderVoteMinimumTicketRateGap = surrenderVoteMinimumTicketRateGap; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Minimum Ticket Rate Gap to Surrender", typeof(Double), _surrenderVoteMinimumTicketRateGap)); } } else if (Regex.Match(strVariable, @"Surrender Vote Timeout Enable").Success) { Boolean surrenderVoteTimeoutEnable = Boolean.Parse(strValue); if (surrenderVoteTimeoutEnable != _surrenderVoteTimeoutEnable) { _surrenderVoteTimeoutEnable = surrenderVoteTimeoutEnable; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Surrender Vote Timeout Enable", typeof(Boolean), _surrenderVoteTimeoutEnable)); } } else if (Regex.Match(strVariable, @"Surrender Vote Timeout Minutes").Success) { Int32 surrenderVoteTimeoutMinutes = Int32.Parse(strValue); if (_surrenderVoteTimeoutMinutes != surrenderVoteTimeoutMinutes) { if (surrenderVoteTimeoutMinutes < 0) { Log.Error("Timeout cannot be negative."); return; } _surrenderVoteTimeoutMinutes = surrenderVoteTimeoutMinutes; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Surrender Vote Timeout Minutes", typeof(Int32), _surrenderVoteTimeoutMinutes)); } } else if (Regex.Match(strVariable, @"Auto-Surrender Enable").Success) { if (_serverInfo.ServerType == "OFFICIAL" && Boolean.Parse(strValue) == true) { strValue = "False"; Log.Error("'" + strVariable + "' cannot be enabled on official servers."); return; } Boolean surrenderAutoEnable = Boolean.Parse(strValue); if (surrenderAutoEnable != _surrenderAutoEnable) { _surrenderAutoEnable = surrenderAutoEnable; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Enable", typeof(Boolean), _surrenderAutoEnable)); } } else if (Regex.Match(strVariable, @"Auto-Surrender Use Optimal Values for Metro").Success) { Boolean surrenderAutoUseMetroValues = Boolean.Parse(strValue); if (surrenderAutoUseMetroValues != _surrenderAutoUseMetroValues) { _surrenderAutoUseMetroValues = surrenderAutoUseMetroValues; if (_surrenderAutoUseMetroValues) { _surrenderAutoUseLockerValues = false; } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Use Optimal Values for Metro Conquest", typeof(Boolean), _surrenderAutoUseMetroValues)); } } else if (Regex.Match(strVariable, @"Auto-Surrender Use Optimal Values for Locker").Success) { Boolean surrenderAutoUseLockerValues = Boolean.Parse(strValue); if (surrenderAutoUseLockerValues != _surrenderAutoUseLockerValues) { _surrenderAutoUseLockerValues = surrenderAutoUseLockerValues; if (_surrenderAutoUseLockerValues) { _surrenderAutoUseMetroValues = false; } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Use Optimal Values for Locker Conquest", typeof(Boolean), _surrenderAutoUseLockerValues)); } } else if (Regex.Match(strVariable, @"Auto-Surrender Reset Trigger Count on Cancel").Success) { Boolean surrenderAutoResetTriggerCountOnCancel = Boolean.Parse(strValue); if (surrenderAutoResetTriggerCountOnCancel != _surrenderAutoResetTriggerCountOnCancel) { _surrenderAutoResetTriggerCountOnCancel = surrenderAutoResetTriggerCountOnCancel; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Reset Trigger Count on Cancel", typeof(Boolean), _surrenderAutoResetTriggerCountOnCancel)); } } else if (Regex.Match(strVariable, @"Reset Auto-Nuke Trigger Count on Fire").Success) { Boolean surrenderAutoResetTriggerCountOnFire = Boolean.Parse(strValue); if (surrenderAutoResetTriggerCountOnFire != _surrenderAutoResetTriggerCountOnFire) { _surrenderAutoResetTriggerCountOnFire = surrenderAutoResetTriggerCountOnFire; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Reset Auto-Nuke Trigger Count on Fire", typeof(Boolean), _surrenderAutoResetTriggerCountOnFire)); } } else if (Regex.Match(strVariable, @"Nuke Winning Team Instead of Surrendering Losing Team").Success) { Boolean surrenderAutoNukeWinning = Boolean.Parse(strValue); if (surrenderAutoNukeWinning != _surrenderAutoNukeInstead) { _surrenderAutoNukeInstead = surrenderAutoNukeWinning; if (_surrenderAutoNukeInstead) { _surrenderAutoTriggerVote = false; } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Nuke Winning Team Instead of Surrendering Losing Team", typeof(Boolean), _surrenderAutoNukeInstead)); } } else if (Regex.Match(strVariable, @"Fire Nuke Triggers if Winning Team up by X Tickets").Success) { Int32 ticketCount = Int32.Parse(strValue); if (_NukeWinningTeamUpTicketCount != ticketCount) { _NukeWinningTeamUpTicketCount = ticketCount; if (_NukeWinningTeamUpTicketCount < 1) { _NukeWinningTeamUpTicketCount = 1; } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Fire Nuke Triggers if Winning Team up by X Tickets", typeof(Int32), _NukeWinningTeamUpTicketCount)); } } else if (Regex.Match(strVariable, @"Maximum Auto-Nukes Each Round").Success) { Int32 surrenderAutoMaxNukesEachRound = Int32.Parse(strValue); if (_surrenderAutoMaxNukesEachRound != surrenderAutoMaxNukesEachRound) { if (surrenderAutoMaxNukesEachRound < 0) { Log.Error("Maxumim nuke count each round cannot be negative."); return; } _surrenderAutoMaxNukesEachRound = surrenderAutoMaxNukesEachRound; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Maximum Auto-Nukes Each Round", typeof(Int32), _surrenderAutoMaxNukesEachRound)); } } else if (Regex.Match(strVariable, @"Minimum Seconds Between Nukes").Success) { Int32 surrenderAutoNukeMinBetween = Int32.Parse(strValue); if (_surrenderAutoNukeMinBetween != surrenderAutoNukeMinBetween) { if (surrenderAutoNukeMinBetween < 0) { Log.Error("Minimum seconds between nukes must be positive."); surrenderAutoNukeMinBetween = 1; } if (surrenderAutoNukeMinBetween > 300) { Log.Error("Minimum seconds between nukes cannot be longer than 300 seconds."); surrenderAutoNukeMinBetween = 300; } _surrenderAutoNukeMinBetween = surrenderAutoNukeMinBetween; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Minimum Seconds Between Nukes", typeof(Int32), _surrenderAutoNukeMinBetween)); } } else if (Regex.Match(strVariable, @"Switch to surrender after max nukes").Success) { Boolean surrenderAutoNukeResolveAfterMax = Boolean.Parse(strValue); if (surrenderAutoNukeResolveAfterMax != _surrenderAutoNukeResolveAfterMax) { _surrenderAutoNukeResolveAfterMax = surrenderAutoNukeResolveAfterMax; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Switch to surrender after max nukes", typeof(Boolean), _surrenderAutoNukeResolveAfterMax)); } } else if (Regex.Match(strVariable, @"Only fire ticket difference nukes in high population").Success) { Boolean NukeWinningTeamUpTicketHigh = Boolean.Parse(strValue); if (NukeWinningTeamUpTicketHigh != _NukeWinningTeamUpTicketHigh) { _NukeWinningTeamUpTicketHigh = NukeWinningTeamUpTicketHigh; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Only fire ticket difference nukes in high population", typeof(Boolean), _NukeWinningTeamUpTicketHigh)); } } else if (Regex.Match(strVariable, @"Announce Nuke Preparation to Players").Success) { Boolean surrenderAutoAnnounceNukePrep = Boolean.Parse(strValue); if (surrenderAutoAnnounceNukePrep != _surrenderAutoAnnounceNukePrep) { _surrenderAutoAnnounceNukePrep = surrenderAutoAnnounceNukePrep; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Announce Nuke Preparation to Players", typeof(Boolean), _surrenderAutoAnnounceNukePrep)); } } else if (Regex.Match(strVariable, @"Allow Auto-Nuke to fire on losing teams").Success) { Boolean surrenderAutoNukeLosingTeams = Boolean.Parse(strValue); if (surrenderAutoNukeLosingTeams != _surrenderAutoNukeLosingTeams) { _surrenderAutoNukeLosingTeams = surrenderAutoNukeLosingTeams; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Allow Auto-Nuke to fire on losing teams", typeof(Boolean), _surrenderAutoNukeLosingTeams)); } } else if (Regex.Match(strVariable, @"Start Surrender Vote Instead of Surrendering Losing Team").Success) { Boolean surrenderAutoTriggerVote = Boolean.Parse(strValue); if (surrenderAutoTriggerVote != _surrenderAutoTriggerVote) { _surrenderAutoTriggerVote = surrenderAutoTriggerVote; if (surrenderAutoTriggerVote) { _surrenderAutoNukeInstead = false; } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Start Surrender Vote Instead of Surrendering Losing Team", typeof(Boolean), _surrenderAutoTriggerVote)); } } else if (Regex.Match(strVariable, @"Auto-Surrender Minimum Ticket Gap").Success) { Int32 surrenderAutoMinimumTicketGap = Int32.Parse(strValue); if (_surrenderAutoMinimumTicketGap != surrenderAutoMinimumTicketGap) { if (_surrenderAutoMinimumTicketGap < 0) { Log.Error("Minimum ticket gap cannot be negative."); return; } _surrenderAutoMinimumTicketGap = surrenderAutoMinimumTicketGap; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Minimum Ticket Gap", typeof(Int32), _surrenderAutoMinimumTicketGap)); } } else if (Regex.Match(strVariable, @"Auto-Surrender Minimum Ticket Count").Success) { Int32 surrenderAutoMinimumTicketCount = Int32.Parse(strValue); if (_surrenderAutoMinimumTicketCount != surrenderAutoMinimumTicketCount) { if (surrenderAutoMinimumTicketCount < 0) { Log.Error("Minimum ticket count cannot be negative."); return; } _surrenderAutoMinimumTicketCount = surrenderAutoMinimumTicketCount; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Minimum Ticket Count", typeof(Int32), _surrenderAutoMinimumTicketCount)); } } else if (Regex.Match(strVariable, @"Auto-Surrender Maximum Ticket Count").Success) { Int32 surrenderAutoMaximumTicketCount = Int32.Parse(strValue); if (_surrenderAutoMaximumTicketCount != surrenderAutoMaximumTicketCount) { if (surrenderAutoMaximumTicketCount < 0) { Log.Error("Maximum ticket count cannot be negative."); return; } _surrenderAutoMaximumTicketCount = surrenderAutoMaximumTicketCount; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Maximum Ticket Count", typeof(Int32), _surrenderAutoMaximumTicketCount)); } } else if (Regex.Match(strVariable, @"Auto-Surrender Losing Team Rate Window Max").Success) { Double surrenderAutoLosingRateMax; if (!Double.TryParse(strValue, out surrenderAutoLosingRateMax)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_surrenderAutoLosingRateMax != surrenderAutoLosingRateMax) { _surrenderAutoLosingRateMax = surrenderAutoLosingRateMax; if (_surrenderAutoLosingRateMin > _surrenderAutoLosingRateMax) { Log.Info("Min ticket rate cannot be greater than max. Swapping values."); var pivot = _surrenderAutoLosingRateMin; _surrenderAutoLosingRateMin = _surrenderAutoLosingRateMax; _surrenderAutoLosingRateMax = pivot; } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Losing Team Rate Window Max", typeof(Double), _surrenderAutoLosingRateMax)); } } else if (Regex.Match(strVariable, @"Auto-Surrender Losing Team Rate Window Min").Success) { Double surrenderAutoLosingRateMin; if (!Double.TryParse(strValue, out surrenderAutoLosingRateMin)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_surrenderAutoLosingRateMin != surrenderAutoLosingRateMin) { _surrenderAutoLosingRateMin = surrenderAutoLosingRateMin; if (_surrenderAutoLosingRateMin > _surrenderAutoLosingRateMax) { Log.Info("Min ticket rate cannot be greater than max. Swapping values."); var pivot = _surrenderAutoLosingRateMin; _surrenderAutoLosingRateMin = _surrenderAutoLosingRateMax; _surrenderAutoLosingRateMax = pivot; } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Losing Team Rate Window Min", typeof(Double), _surrenderAutoLosingRateMin)); } } else if (Regex.Match(strVariable, @"Auto-Surrender Winning Team Rate Window Max").Success) { Double surrenderAutoWinningRateMax; if (!Double.TryParse(strValue, out surrenderAutoWinningRateMax)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_surrenderAutoWinningRateMax != surrenderAutoWinningRateMax) { _surrenderAutoWinningRateMax = surrenderAutoWinningRateMax; if (_surrenderAutoWinningRateMin > _surrenderAutoWinningRateMax) { Log.Info("Min ticket rate cannot be greater than max. Swapping values."); var pivot = _surrenderAutoWinningRateMin; _surrenderAutoWinningRateMin = _surrenderAutoWinningRateMax; _surrenderAutoWinningRateMax = pivot; } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Winning Team Rate Window Max", typeof(Double), _surrenderAutoWinningRateMax)); } } else if (Regex.Match(strVariable, @"Auto-Surrender Winning Team Rate Window Min").Success) { Double surrenderAutoWinningRateMin; if (!Double.TryParse(strValue, out surrenderAutoWinningRateMin)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_surrenderAutoWinningRateMin != surrenderAutoWinningRateMin) { _surrenderAutoWinningRateMin = surrenderAutoWinningRateMin; if (_surrenderAutoWinningRateMin > _surrenderAutoWinningRateMax) { Log.Info("Min ticket rate cannot be greater than max. Swapping values."); var pivot = _surrenderAutoWinningRateMin; _surrenderAutoWinningRateMin = _surrenderAutoWinningRateMax; _surrenderAutoWinningRateMax = pivot; } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Winning Team Rate Window Min", typeof(Double), _surrenderAutoWinningRateMin)); } } else if (Regex.Match(strVariable, @"Auto-Surrender Message").Success) { if (strValue != _surrenderAutoMessage) { _surrenderAutoMessage = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Message", typeof(String), _surrenderAutoMessage)); } } else if (Regex.Match(strVariable, @"Auto-Nuke Message").Success) { if (strValue != _surrenderAutoNukeMessage) { _surrenderAutoNukeMessage = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Nuke Message", typeof(String), _surrenderAutoNukeMessage)); } } else if (Regex.Match(strVariable, @"Auto-Surrender Trigger Count to Surrender").Success) { Int32 surrenderAutoTriggerCountToSurrender = Int32.Parse(strValue); if (_surrenderAutoTriggerCountToSurrender != surrenderAutoTriggerCountToSurrender) { _surrenderAutoTriggerCountToSurrender = surrenderAutoTriggerCountToSurrender; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Trigger Count to Surrender", typeof(Int32), _surrenderAutoTriggerCountToSurrender)); } } else if (Regex.Match(strVariable, @"Auto-Surrender Minimum Players").Success) { Int32 surrenderAutoMinimumPlayers = Int32.Parse(strValue); if (_surrenderAutoMinimumPlayers != surrenderAutoMinimumPlayers) { if (surrenderAutoMinimumPlayers < 0) { Log.Error("Minimum player count cannot be negative."); surrenderAutoMinimumPlayers = 0; } _surrenderAutoMinimumPlayers = surrenderAutoMinimumPlayers; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Minimum Players", typeof(Int32), _surrenderAutoMinimumPlayers)); } } else if (Regex.Match(strVariable, @"Maximum Nuke Ticket Difference for Losing Team").Success) { Int32 losingTeamTicketDiff = Int32.Parse(strValue); if (_surrenderAutoNukeLosingMaxDiff != losingTeamTicketDiff) { if (losingTeamTicketDiff < 0) { Log.Error("Max ticket difference must be non-negative."); losingTeamTicketDiff = 0; } _surrenderAutoNukeLosingMaxDiff = losingTeamTicketDiff; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Maximum Nuke Ticket Difference for Losing Team", typeof(Int32), _surrenderAutoNukeLosingMaxDiff)); } } else if (Regex.Match(strVariable, @"Auto-Nuke High Pop Duration Seconds").Success) { Int32 surrenderAutoNukeDuration = Int32.Parse(strValue); if (_surrenderAutoNukeDurationHigh != surrenderAutoNukeDuration) { if (surrenderAutoNukeDuration < 0) { Log.Error("Auto-nuke high population duration must be non-negative."); surrenderAutoNukeDuration = 0; } if (surrenderAutoNukeDuration > 60) { Log.Error("Auto-nuke high population duration cannot be longer than 60 seconds."); surrenderAutoNukeDuration = 60; } _surrenderAutoNukeDurationHigh = surrenderAutoNukeDuration; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Nuke High Pop Duration Seconds", typeof(Int32), _surrenderAutoNukeDurationHigh)); } } else if (Regex.Match(strVariable, @"Auto-Nuke Medium Pop Duration Seconds").Success) { Int32 surrenderAutoNukeDuration = Int32.Parse(strValue); if (_surrenderAutoNukeDurationMed != surrenderAutoNukeDuration) { if (surrenderAutoNukeDuration < 0) { Log.Error("Auto-nuke medium population duration must be non-negative."); surrenderAutoNukeDuration = 0; } if (surrenderAutoNukeDuration > 45) { Log.Error("Auto-nuke medium population duration cannot be longer than 45 seconds."); surrenderAutoNukeDuration = 45; } _surrenderAutoNukeDurationMed = surrenderAutoNukeDuration; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Nuke Medium Pop Duration Seconds", typeof(Int32), _surrenderAutoNukeDurationMed)); } } else if (Regex.Match(strVariable, @"Auto-Nuke Low Pop Duration Seconds").Success) { Int32 surrenderAutoNukeDuration = Int32.Parse(strValue); if (_surrenderAutoNukeDurationLow != surrenderAutoNukeDuration) { if (surrenderAutoNukeDuration < 0) { Log.Error("Auto-nuke low population duration must be non-negative."); surrenderAutoNukeDuration = 0; } if (surrenderAutoNukeDuration > 30) { Log.Error("Auto-nuke low population duration cannot be longer than 30 seconds."); surrenderAutoNukeDuration = 30; } _surrenderAutoNukeDurationLow = surrenderAutoNukeDuration; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Nuke Low Pop Duration Seconds", typeof(Int32), _surrenderAutoNukeDurationLow)); } } else if (Regex.Match(strVariable, @"Auto-Nuke Consecutive Duration Increase").Success) { Int32 surrenderAutoNukeDurationIncrease = Int32.Parse(strValue); if (_surrenderAutoNukeDurationIncrease != surrenderAutoNukeDurationIncrease) { if (surrenderAutoNukeDurationIncrease < 0) { Log.Error("Auto-nuke consecutive duration increase must be non-negative."); surrenderAutoNukeDurationIncrease = 0; } if (surrenderAutoNukeDurationIncrease > 30) { Log.Error("Auto-nuke consecutive duration cannot be longer than 30 seconds."); surrenderAutoNukeDurationIncrease = 30; } _surrenderAutoNukeDurationIncrease = surrenderAutoNukeDurationIncrease; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Nuke Consecutive Duration Increase", typeof(Int32), _surrenderAutoNukeDurationIncrease)); } } else if (Regex.Match(strVariable, @"Auto-Nuke Duration Increase Minimum Ticket Difference").Success) { Int32 surrenderAutoNukeDurationIncreaseTicketDiff = Int32.Parse(strValue); if (_surrenderAutoNukeDurationIncreaseTicketDiff != surrenderAutoNukeDurationIncreaseTicketDiff) { if (surrenderAutoNukeDurationIncreaseTicketDiff < 0) { Log.Error("Auto-Nuke Duration Increase Minimum Ticket Difference must be non-negative."); surrenderAutoNukeDurationIncreaseTicketDiff = 0; } _surrenderAutoNukeDurationIncreaseTicketDiff = surrenderAutoNukeDurationIncreaseTicketDiff; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Nuke Duration Increase Minimum Ticket Difference", typeof(Int32), _surrenderAutoNukeDurationIncreaseTicketDiff)); } } else if (Regex.Match(strVariable, @"Player Lock Manual Duration Minutes").Success) { Double playerLockingManualDuration; if (!Double.TryParse(strValue, out playerLockingManualDuration)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_playerLockingManualDuration != playerLockingManualDuration) { if (playerLockingManualDuration < 0) { Log.Error("Duration cannot be negative."); return; } _playerLockingManualDuration = playerLockingManualDuration; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Player Lock Manual Duration Minutes", typeof(Double), _playerLockingManualDuration)); } } else if (Regex.Match(strVariable, @"Automatically Lock Players on Admin Action").Success) { Boolean playerLockingAutomaticLock = Boolean.Parse(strValue); if (playerLockingAutomaticLock != _playerLockingAutomaticLock) { _playerLockingAutomaticLock = playerLockingAutomaticLock; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Automatically Lock Players on Admin Action", typeof(Boolean), _playerLockingAutomaticLock)); } } else if (Regex.Match(strVariable, @"Player Lock Automatic Duration Minutes").Success) { Double playerLockingAutomaticDuration; if (!Double.TryParse(strValue, out playerLockingAutomaticDuration)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_playerLockingAutomaticDuration != playerLockingAutomaticDuration) { if (playerLockingAutomaticDuration < 0) { Log.Error("Duration cannot be negative."); return; } _playerLockingAutomaticDuration = playerLockingAutomaticDuration; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Player Lock Automatic Duration Minutes", typeof(Double), _playerLockingAutomaticDuration)); } } else if (Regex.Match(strVariable, @"Feed MULTIBalancer Whitelist").Success) { if (_serverInfo.ServerType == "OFFICIAL" && Boolean.Parse(strValue) == true) { strValue = "False"; Log.Error("'" + strVariable + "' cannot be enabled on official servers."); return; } Boolean feedMTBWhite = Boolean.Parse(strValue); if (feedMTBWhite != _FeedMultiBalancerWhitelist) { _FeedMultiBalancerWhitelist = feedMTBWhite; if (_FeedMultiBalancerWhitelist) { SetExternalPluginSetting("MULTIbalancer", "2 - Exclusions|On Whitelist", "True"); } FetchAllAccess(true); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Feed MULTIBalancer Whitelist", typeof(Boolean), _FeedMultiBalancerWhitelist)); } } else if (Regex.Match(strVariable, @"Automatic MULTIBalancer Whitelist for Admins").Success) { Boolean feedMTBWhiteUser = Boolean.Parse(strValue); if (feedMTBWhiteUser != _FeedMultiBalancerWhitelist_Admins) { _FeedMultiBalancerWhitelist_Admins = feedMTBWhiteUser; FetchAllAccess(true); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Automatic MULTIBalancer Whitelist for Admins", typeof(Boolean), _FeedMultiBalancerWhitelist_Admins)); } } else if (Regex.Match(strVariable, @"Feed TeamKillTracker Whitelist").Success) { if (_serverInfo.ServerType == "OFFICIAL" && Boolean.Parse(strValue) == true) { strValue = "False"; Log.Error("'" + strVariable + "' cannot be enabled on official servers."); return; } Boolean FeedTeamKillTrackerWhitelist = Boolean.Parse(strValue); if (FeedTeamKillTrackerWhitelist != _FeedTeamKillTrackerWhitelist) { _FeedTeamKillTrackerWhitelist = FeedTeamKillTrackerWhitelist; if (_FeedTeamKillTrackerWhitelist) { SetExternalPluginSetting("TeamKillTracker", "Who should be protected?", "Whitelist"); } FetchAllAccess(true); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Feed TeamKillTracker Whitelist", typeof(Boolean), _FeedTeamKillTrackerWhitelist)); } } else if (Regex.Match(strVariable, @"Automatic TeamKillTracker Whitelist for Admins").Success) { Boolean FeedTeamKillTrackerWhitelist_Admins = Boolean.Parse(strValue); if (FeedTeamKillTrackerWhitelist_Admins != _FeedTeamKillTrackerWhitelist_Admins) { _FeedTeamKillTrackerWhitelist_Admins = FeedTeamKillTrackerWhitelist_Admins; FetchAllAccess(true); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Automatic TeamKillTracker Whitelist for Admins", typeof(Boolean), _FeedTeamKillTrackerWhitelist_Admins)); } } else if (Regex.Match(strVariable, @"Feed MULTIBalancer Even Dispersion List").Success) { if (_serverInfo.ServerType == "OFFICIAL" && Boolean.Parse(strValue) == true) { strValue = "False"; Log.Error("'" + strVariable + "' cannot be enabled on official servers."); return; } Boolean feedMTBBlack = Boolean.Parse(strValue); if (feedMTBBlack != _FeedMultiBalancerDisperseList) { _FeedMultiBalancerDisperseList = feedMTBBlack; if (_FeedMultiBalancerDisperseList) { SetExternalPluginSetting("MULTIbalancer", "8 - Settings for Conquest Large|Conquest Large: Enable Disperse Evenly List", "True"); SetExternalPluginSetting("MULTIbalancer", "8 - Settings for Conquest Small|Conquest Small: Enable Disperse Evenly List", "True"); SetExternalPluginSetting("MULTIbalancer", "8 - Settings for Defuse|Defuse: Enable Disperse Evenly List", "True"); SetExternalPluginSetting("MULTIbalancer", "8 - Settings for Domination|Domination: Enable Disperse Evenly List", "True"); SetExternalPluginSetting("MULTIbalancer", "8 - Settings for Obliteration|Obliteration: Enable Disperse Evenly List", "True"); SetExternalPluginSetting("MULTIbalancer", "8 - Settings for Rush|Rush: Enable Disperse Evenly List", "True"); SetExternalPluginSetting("MULTIbalancer", "8 - Settings for Squad Deathmatch|Squad Deathmatch: Enable Disperse Evenly List", "True"); SetExternalPluginSetting("MULTIbalancer", "8 - Settings for Superiority|Superiority: Enable Disperse Evenly List", "True"); SetExternalPluginSetting("MULTIbalancer", "8 - Settings for Team Deathmatch|Team Deathmatch: Enable Disperse Evenly List", "True"); SetExternalPluginSetting("MULTIbalancer", "8 - Settings for Unknown or New Mode|Unknown or New Mode: Enable Disperse Evenly List", "True"); } FetchAllAccess(true); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Feed MULTIBalancer Even Dispersion List", typeof(Boolean), _FeedMultiBalancerDisperseList)); } } else if (Regex.Match(strVariable, @"Feed Server Reserved Slots").Success) { Boolean FeedServerReservedSlots = Boolean.Parse(strValue); if (FeedServerReservedSlots != _FeedServerReservedSlots) { _FeedServerReservedSlots = FeedServerReservedSlots; FetchAllAccess(true); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Feed Server Reserved Slots", typeof(Boolean), _FeedServerReservedSlots)); } } else if (Regex.Match(strVariable, @"Automatic Reserved Slot for Admins").Success) { Boolean FeedServerReservedSlots_Admins = Boolean.Parse(strValue); if (FeedServerReservedSlots_Admins != _FeedServerReservedSlots_Admins) { _FeedServerReservedSlots_Admins = FeedServerReservedSlots_Admins; FetchAllAccess(true); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Automatic Reserved Slot for Admins", typeof(Boolean), _FeedServerReservedSlots_Admins)); } } else if (Regex.Match(strVariable, @"Automatic VIP Kick Whitelist for Admins").Success) { Boolean FeedServerReservedSlots_Admins_VIPKickWhitelist = Boolean.Parse(strValue); if (FeedServerReservedSlots_Admins_VIPKickWhitelist != _FeedServerReservedSlots_Admins_VIPKickWhitelist) { _FeedServerReservedSlots_Admins_VIPKickWhitelist = FeedServerReservedSlots_Admins_VIPKickWhitelist; FetchAllAccess(true); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Automatic VIP Kick Whitelist for Admins", typeof(Boolean), _FeedServerReservedSlots_Admins_VIPKickWhitelist)); } } else if (Regex.Match(strVariable, @"Send new reserved slots to VIP Slot Manager").Success) { Boolean FeedServerReservedSlots_VSM = Boolean.Parse(strValue); if (FeedServerReservedSlots_VSM != _FeedServerReservedSlots_VSM) { _FeedServerReservedSlots_VSM = FeedServerReservedSlots_VSM; QueueSettingForUpload(new CPluginVariable(@"Send new reserved slots to VIP Slot Manager", typeof(Boolean), _FeedServerReservedSlots_VSM)); } } else if (Regex.Match(strVariable, @"Feed Server Spectator List").Success) { Boolean feedSSL = Boolean.Parse(strValue); if (feedSSL != _FeedServerSpectatorList) { if (GameVersion == GameVersionEnum.BF3) { Log.Error("This feature cannot be enabled on BF3 servers."); return; } _FeedServerSpectatorList = feedSSL; FetchAllAccess(true); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Feed Server Spectator List", typeof(Boolean), _FeedServerSpectatorList)); } } else if (Regex.Match(strVariable, @"Automatic Spectator Slot for Admins").Success) { Boolean feedSSLUser = Boolean.Parse(strValue); if (feedSSLUser != _FeedServerSpectatorList_Admins) { _FeedServerSpectatorList_Admins = feedSSLUser; FetchAllAccess(true); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Automatic Spectator Slot for Admins", typeof(Boolean), _FeedServerSpectatorList_Admins)); } } else if (Regex.Match(strVariable, @"Feed Stat Logger Settings").Success) { Boolean feedSLS = Boolean.Parse(strValue); if (feedSLS != _FeedStatLoggerSettings) { _FeedStatLoggerSettings = feedSLS; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Feed Stat Logger Settings", typeof(Boolean), _FeedStatLoggerSettings)); } } else if (Regex.Match(strVariable, @"Post Stat Logger Chat Manually").Success) { Boolean PostStatLoggerChatManually = Boolean.Parse(strValue); if (PostStatLoggerChatManually != _PostStatLoggerChatManually) { _PostStatLoggerChatManually = PostStatLoggerChatManually; if (_PostStatLoggerChatManually) { SetExternalPluginSetting("CChatGUIDStatsLogger", "Enable Chatlogging?", "No"); SetExternalPluginSetting("CChatGUIDStatsLogger", "Instant Logging of Chat Messages?", "No"); SetExternalPluginSetting("CChatGUIDStatsLoggerBF3", "Enable Chatlogging?", "No"); SetExternalPluginSetting("CChatGUIDStatsLoggerBF3", "Instant Logging of Chat Messages?", "No"); } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Post Stat Logger Chat Manually", typeof(Boolean), _PostStatLoggerChatManually)); } } else if (Regex.Match(strVariable, @"Post Server Chat Spam").Success) { Boolean PostServerChatSpam = Boolean.Parse(strValue); if (PostServerChatSpam != _PostStatLoggerChatManually_PostServerChatSpam) { _PostStatLoggerChatManually_PostServerChatSpam = PostServerChatSpam; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Post Server Chat Spam", typeof(Boolean), _PostStatLoggerChatManually_PostServerChatSpam)); } } else if (Regex.Match(strVariable, @"Exclude Commands from Chat Logs").Success) { Boolean PostStatLoggerChatManually_IgnoreCommands = Boolean.Parse(strValue); if (PostStatLoggerChatManually_IgnoreCommands != _PostStatLoggerChatManually_IgnoreCommands) { _PostStatLoggerChatManually_IgnoreCommands = PostStatLoggerChatManually_IgnoreCommands; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Exclude Commands from Chat Logs", typeof(Boolean), _PostStatLoggerChatManually_IgnoreCommands)); } } else if (Regex.Match(strVariable, @"Post Map Benefit/Detriment Statistics").Success) { Boolean PostMapBenefitStatistics = Boolean.Parse(strValue); if (PostMapBenefitStatistics != _PostMapBenefitStatistics) { _PostMapBenefitStatistics = PostMapBenefitStatistics; if (_threadsReady) { if (_PostMapBenefitStatistics) { Log.Info("Statistics for map benefit/detriment to the server will now be logged."); } else { Log.Info("Statistics for map benefit/detriment to the server will no longer be logged."); } } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Post Map Benefit/Detriment Statistics", typeof(Boolean), _PostMapBenefitStatistics)); } } else if (Regex.Match(strVariable, @"Team Power Active Influence").Success) { //Initial parse Int32 TeamPowerActiveInfluence = Int32.Parse(strValue); //Check for changed value if (_TeamPowerActiveInfluence != TeamPowerActiveInfluence) { if (TeamPowerActiveInfluence < 1) { TeamPowerActiveInfluence = 1; } foreach (var aPlayer in GetFetchedPlayers()) { aPlayer.TopStats.TempTopPower = 0.0; } //Assignment _TeamPowerActiveInfluence = TeamPowerActiveInfluence; //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Team Power Active Influence", typeof(Double), _TeamPowerActiveInfluence)); } } else if (Regex.Match(strVariable, @"Display Team Power In Procon Chat").Success) { //Initial parse Boolean UseTeamPowerDisplayBalance = Boolean.Parse(strValue); //Check for changed value if (UseTeamPowerDisplayBalance != _UseTeamPowerDisplayBalance) { //Assignment _UseTeamPowerDisplayBalance = UseTeamPowerDisplayBalance; //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Display Team Power In Procon Chat", typeof(Boolean), _UseTeamPowerDisplayBalance)); } } else if (Regex.Match(strVariable, @"Enable Team Power Scrambler").Success) { //Initial parse Boolean UseTeamPowerMonitorScrambler = false; //Boolean.Parse(strValue); //Check for changed value if (UseTeamPowerMonitorScrambler != _UseTeamPowerMonitorScrambler) { //Assignment _UseTeamPowerMonitorScrambler = UseTeamPowerMonitorScrambler; //Notification if (_threadsReady) { if (_UseTeamPowerMonitorScrambler) { Log.Info("Team scrambling is now being controlled by the team power monitor."); } else { Log.Info("Team scrambling is no longer being controlled by the team power monitor."); } } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Enable Team Power Scrambler", typeof(Boolean), _UseTeamPowerMonitorScrambler)); } } else if (strVariable == "Enable Team Power Join Reassignment") { //Initial parse Boolean UseTeamPowerMonitorReassign = Boolean.Parse(strValue); //Check for changed value if (UseTeamPowerMonitorReassign != _UseTeamPowerMonitorReassign) { //Assignment _UseTeamPowerMonitorReassign = UseTeamPowerMonitorReassign; //Notification if (_threadsReady) { if (_UseTeamPowerMonitorReassign) { Log.Info("When players join the server and are over rank 15 they are now automatically placed on the weak team, unless that team would be up by 4 or more players."); } else { Log.Info("Team join reassignment is no longer being controlled by the team power monitor."); } } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Enable Team Power Join Reassignment", typeof(Boolean), _UseTeamPowerMonitorReassign)); } } else if (strVariable == "Team Power Join Reassignment Leniency") { //Initial parse Boolean UseTeamPowerMonitorReassignLenient = Boolean.Parse(strValue); //Check for changed value if (UseTeamPowerMonitorReassignLenient != _UseTeamPowerMonitorReassignLenient) { //Assignment _UseTeamPowerMonitorReassignLenient = UseTeamPowerMonitorReassignLenient; //Notification if (_threadsReady) { if (_UseTeamPowerMonitorReassignLenient) { Log.Info("If a reassignment would normally not happen, but a team is down by more than the configured percentage of power, it will assign players to the weak team anyway."); } else { Log.Info("Team join reassignment leniency is no longer enabled."); } } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Team Power Join Reassignment Leniency", typeof(Boolean), _UseTeamPowerMonitorReassignLenient)); } } else if (strVariable == "Team Power Join Reassignment Leniency Percent") { Double leniencyPercent; if (!Double.TryParse(strValue, out leniencyPercent)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_TeamPowerMonitorReassignLenientPercent != leniencyPercent) { if (leniencyPercent <= 15.0) { leniencyPercent = 15.0; } _TeamPowerMonitorReassignLenientPercent = leniencyPercent; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Team Power Join Reassignment Leniency Percent", typeof(Double), _TeamPowerMonitorReassignLenientPercent)); } } else if (Regex.Match(strVariable, @"Enable Team Power Unswitcher").Success) { //Initial parse Boolean UseTeamPowerMonitorUnswitcher = Boolean.Parse(strValue); //Check for changed value if (UseTeamPowerMonitorUnswitcher != _UseTeamPowerMonitorUnswitcher) { //Assignment _UseTeamPowerMonitorUnswitcher = UseTeamPowerMonitorUnswitcher; //Notification if (_threadsReady) { if (_UseTeamPowerMonitorUnswitcher) { Log.Info("Based on the 'unswitcher' in MULTIBalancer, this system works based on team power and wont let players move to the more powerful team."); } else { Log.Info("Manual player movement is no longer being controlled by the team power monitor."); } } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Enable Team Power Unswitcher", typeof(Boolean), _UseTeamPowerMonitorUnswitcher)); } } else if (Regex.Match(strVariable, @"Enable Team Power Seeder Control").Success) { //Initial parse Boolean UseTeamPowerMonitorSeeders = Boolean.Parse(strValue); //Check for changed value if (UseTeamPowerMonitorSeeders != _UseTeamPowerMonitorSeeders) { //Assignment _UseTeamPowerMonitorSeeders = UseTeamPowerMonitorSeeders; //Notification if (_threadsReady) { if (_UseTeamPowerMonitorSeeders) { Log.Info("Team seeders is now being controlled by the team power monitor."); } else { Log.Info("Team seeders is no longer being controlled by the team power monitor."); } } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Enable Team Power Seeder Control", typeof(Boolean), _UseTeamPowerMonitorSeeders)); } } else if (Regex.Match(strVariable, @"Monitor Populator Players").Success) { //Initial parse Boolean PopulatorMonitor = Boolean.Parse(strValue); //Check for changed value if (PopulatorMonitor != _PopulatorMonitor) { //Assignment _PopulatorMonitor = PopulatorMonitor; //Notification if (_threadsReady) { if (_PopulatorMonitor) { Log.Info("Populator players are now being monitored."); UpdatePopulatorPlayers(); } else { Log.Info("Populator players are no longer being monitored."); } FetchAllAccess(true); } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Monitor Populator Players", typeof(Boolean), _PopulatorMonitor)); } } else if (Regex.Match(strVariable, @"Monitor Specified Populators Only").Success) { //Initial parse Boolean PopulatorUseSpecifiedPopulatorsOnly = Boolean.Parse(strValue); //Check for changed value if (PopulatorUseSpecifiedPopulatorsOnly != _PopulatorUseSpecifiedPopulatorsOnly) { //Assignment _PopulatorUseSpecifiedPopulatorsOnly = PopulatorUseSpecifiedPopulatorsOnly; //Notification if (_threadsReady) { if (_PopulatorUseSpecifiedPopulatorsOnly) { Log.Info("Only players under whitelist_populator specialplayer group can be considered populators now."); } else { Log.Info("All players can be considered populators now."); } FetchAllAccess(true); } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Monitor Specified Populators Only", typeof(Boolean), _PopulatorUseSpecifiedPopulatorsOnly)); } } else if (Regex.Match(strVariable, @"Monitor Populators of This Server Only").Success) { //Initial parse Boolean PopulatorPopulatingThisServerOnly = Boolean.Parse(strValue); //Check for changed value if (PopulatorPopulatingThisServerOnly != _PopulatorPopulatingThisServerOnly) { //Assignment _PopulatorPopulatingThisServerOnly = PopulatorPopulatingThisServerOnly; //Notification if (_threadsReady) { if (_PopulatorPopulatingThisServerOnly) { Log.Info("Only populations of this server will be considered toward a player's populator status on this server."); } else { Log.Info("Populations of all servers will be considered toward a player's population status on this server."); } FetchAllAccess(true); } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Monitor Populators of This Server Only", typeof(Boolean), _PopulatorPopulatingThisServerOnly)); } } else if (Regex.Match(strVariable, @"Count to Consider Populator Past Week").Success) { //Initial parse Int32 PopulatorMinimumPopulationCountPastWeek = Int32.Parse(strValue); //Check for changed value if (_PopulatorMinimumPopulationCountPastWeek != PopulatorMinimumPopulationCountPastWeek) { //Rejection cases if (PopulatorMinimumPopulationCountPastWeek < 1) { Log.Error("'Count to Consider Populator Past Week' cannot be less than 1."); PopulatorMinimumPopulationCountPastWeek = 1; } //Assignment _PopulatorMinimumPopulationCountPastWeek = PopulatorMinimumPopulationCountPastWeek; //Notification if (_threadsReady) { Log.Info("Players are now considered populator if they contribute to " + _PopulatorMinimumPopulationCountPastWeek + " populations in the past week, or " + _PopulatorMinimumPopulationCountPast2Weeks + " populations in the past 2 weeks."); FetchAllAccess(true); } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Count to Consider Populator Past Week", typeof(Int32), _PopulatorMinimumPopulationCountPastWeek)); } } else if (Regex.Match(strVariable, @"Count to Consider Populator Past 2 Weeks").Success) { //Initial parse Int32 PopulatorMinimumPopulationCountPast2Weeks = Int32.Parse(strValue); //Check for changed value if (_PopulatorMinimumPopulationCountPast2Weeks != PopulatorMinimumPopulationCountPast2Weeks) { //Rejection cases if (PopulatorMinimumPopulationCountPast2Weeks < 1) { Log.Error("'Count to Consider Populator Past 2 Weeks' cannot be less than 1."); PopulatorMinimumPopulationCountPast2Weeks = 1; } //Assignment _PopulatorMinimumPopulationCountPast2Weeks = PopulatorMinimumPopulationCountPast2Weeks; //Notification if (_threadsReady) { Log.Info("Players are now considered populator if they contribute to " + _PopulatorMinimumPopulationCountPastWeek + " populations in the past week, or " + _PopulatorMinimumPopulationCountPast2Weeks + " populations in the past 2 weeks."); FetchAllAccess(true); } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Count to Consider Populator Past 2 Weeks", typeof(Int32), _PopulatorMinimumPopulationCountPast2Weeks)); } } else if (Regex.Match(strVariable, @"Enable Populator Perks").Success) { //Initial parse Boolean PopulatorPerksEnable = Boolean.Parse(strValue); //Check for changed value if (PopulatorPerksEnable != _PopulatorPerksEnable) { //Assignment _PopulatorPerksEnable = PopulatorPerksEnable; //Notification if (_threadsReady) { if (_PopulatorPerksEnable) { Log.Info("Populator perks are now enabled."); } else { Log.Info("Populator perks are now disabled."); } FetchAllAccess(true); } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Enable Populator Perks", typeof(Boolean), _PopulatorPerksEnable)); } } else if (Regex.Match(strVariable, @"Populator Perks - Reserved Slot").Success) { //Initial parse Boolean PopulatorPerksReservedSlot = Boolean.Parse(strValue); //Check for changed value if (PopulatorPerksReservedSlot != _PopulatorPerksReservedSlot) { //Rejection cases if (_threadsReady && !_FeedServerReservedSlots && PopulatorPerksReservedSlot) { Log.Error("'Populator Perks - Reserved Slot' cannot be enabled when 'Feed Server Reserved Slots' is disabled."); return; } //Assignment _PopulatorPerksReservedSlot = PopulatorPerksReservedSlot; //Notification if (_threadsReady) { if (_PopulatorPerksReservedSlot) { Log.Info("Populator perks now include reserved slot."); } else { Log.Info("Populator perks no longer include reserved slot."); } FetchAllAccess(true); } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Populator Perks - Reserved Slot", typeof(Boolean), _PopulatorPerksReservedSlot)); } } else if (Regex.Match(strVariable, @"Populator Perks - Autobalance Whitelist").Success) { //Initial parse Boolean PopulatorPerksBalanceWhitelist = Boolean.Parse(strValue); //Check for changed value if (PopulatorPerksBalanceWhitelist != _PopulatorPerksBalanceWhitelist) { //Rejection cases if (_threadsReady && !_FeedMultiBalancerWhitelist && PopulatorPerksBalanceWhitelist) { Log.Error("'Populator Perks - Autobalance Whitelist' cannot be enabled when 'Feed MULTIBalancer Whitelist' is disabled."); return; } //Assignment _PopulatorPerksBalanceWhitelist = PopulatorPerksBalanceWhitelist; //Notification if (_threadsReady) { if (_PopulatorPerksBalanceWhitelist) { Log.Info("Populator perks now include autobalance whitelist."); } else { Log.Info("Populator perks no longer include autobalance whitelist."); } FetchAllAccess(true); } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Populator Perks - Autobalance Whitelist", typeof(Boolean), _PopulatorPerksBalanceWhitelist)); } } else if (Regex.Match(strVariable, @"Populator Perks - Ping Whitelist").Success) { //Initial parse Boolean PopulatorPerksPingWhitelist = Boolean.Parse(strValue); //Check for changed value if (PopulatorPerksPingWhitelist != _PopulatorPerksPingWhitelist) { //Rejection cases if (_threadsReady && !_pingEnforcerEnable && PopulatorPerksPingWhitelist) { Log.Error("'Populator Perks - Ping Whitelist' cannot be enabled when Ping Enforcer is disabled."); return; } //Assignment _PopulatorPerksPingWhitelist = PopulatorPerksPingWhitelist; //Notification if (_threadsReady) { if (_PopulatorPerksPingWhitelist) { Log.Info("Populator perks now include ping whitelist."); } else { Log.Info("Populator perks no longer include ping whitelist."); } FetchAllAccess(true); } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Populator Perks - Ping Whitelist", typeof(Boolean), _PopulatorPerksPingWhitelist)); } } else if (Regex.Match(strVariable, @"Populator Perks - TeamKillTracker Whitelist").Success) { //Initial parse Boolean PopulatorPerksTeamKillTrackerWhitelist = Boolean.Parse(strValue); //Check for changed value if (PopulatorPerksTeamKillTrackerWhitelist != _PopulatorPerksTeamKillTrackerWhitelist) { //Rejection cases if (_threadsReady && !_FeedTeamKillTrackerWhitelist && PopulatorPerksTeamKillTrackerWhitelist) { Log.Error("'Populator Perks - TeamKillTracker Whitelist' cannot be enabled when 'Feed TeamKillTracker Whitelist' is disabled."); return; } //Assignment _PopulatorPerksTeamKillTrackerWhitelist = PopulatorPerksTeamKillTrackerWhitelist; //Notification if (_threadsReady) { if (_PopulatorPerksTeamKillTrackerWhitelist) { Log.Info("Populator perks now include TeamKillTracker whitelist."); } else { Log.Info("Populator perks no longer include TeamKillTracker whitelist."); } FetchAllAccess(true); } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Populator Perks - TeamKillTracker Whitelist", typeof(Boolean), _PopulatorPerksTeamKillTrackerWhitelist)); } } else if (Regex.Match(strVariable, @"Monitor Teamspeak Players").Success) { //Initial parse Boolean TeamspeakPlayerMonitorView = Boolean.Parse(strValue); //Check for changed value if (TeamspeakPlayerMonitorView != _TeamspeakPlayerMonitorView) { //Assignment _TeamspeakPlayerMonitorView = TeamspeakPlayerMonitorView; //No Notification //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Monitor Teamspeak Players", typeof(Boolean), _TeamspeakPlayerMonitorView)); } } else if (Regex.Match(strVariable, @"Enable Teamspeak Player Monitor").Success) { //Initial parse Boolean TeamspeakPlayerMonitorEnable = Boolean.Parse(strValue); //Check for changed value if (TeamspeakPlayerMonitorEnable != _TeamspeakPlayerMonitorEnable) { //Assignment _TeamspeakPlayerMonitorEnable = TeamspeakPlayerMonitorEnable; if (_threadsReady) { if (TeamspeakPlayerMonitorEnable) { _TeamspeakManager.Enable(); } else { _TeamspeakManager.Disable(); } } //No Notification //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Enable Teamspeak Player Monitor", typeof(Boolean), _TeamspeakPlayerMonitorEnable)); } } else if (Regex.Match(strVariable, @"Teamspeak Server IP").Success) { if (_TeamspeakManager.Ts3ServerIp != strValue) { _TeamspeakManager.Ts3ServerIp = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Teamspeak Server IP", typeof(String), _TeamspeakManager.Ts3ServerIp)); } } else if (Regex.Match(strVariable, @"Teamspeak Server Port").Success) { //Initial parse Int32 Ts3ServerPort = Int32.Parse(strValue); //Check for changed value if (_TeamspeakManager.Ts3ServerPort != Ts3ServerPort) { //Assignment _TeamspeakManager.Ts3ServerPort = (ushort)Ts3ServerPort; //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Teamspeak Server Port", typeof(Int32), _TeamspeakManager.Ts3ServerPort)); } } else if (Regex.Match(strVariable, @"Teamspeak Server Query Port").Success) { //Initial parse Int32 Ts3QueryPort = Int32.Parse(strValue); //Check for changed value if (_TeamspeakManager.Ts3QueryPort != Ts3QueryPort) { //Assignment _TeamspeakManager.Ts3QueryPort = (ushort)Ts3QueryPort; //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Teamspeak Server Query Port", typeof(Int32), _TeamspeakManager.Ts3QueryPort)); } } else if (Regex.Match(strVariable, @"Teamspeak Server Query Username").Success) { if (_TeamspeakManager.Ts3QueryUsername != strValue) { _TeamspeakManager.Ts3QueryUsername = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Teamspeak Server Query Username", typeof(String), _TeamspeakManager.Ts3QueryUsername)); } } else if (Regex.Match(strVariable, @"Teamspeak Server Query Password").Success) { if (_TeamspeakManager.Ts3QueryPassword != strValue) { _TeamspeakManager.Ts3QueryPassword = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Teamspeak Server Query Password", typeof(String), _TeamspeakManager.Ts3QueryPassword)); } } else if (Regex.Match(strVariable, @"Teamspeak Server Query Nickname").Success) { if (_TeamspeakManager.Ts3QueryNickname != strValue) { _TeamspeakManager.Ts3QueryNickname = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Teamspeak Server Query Nickname", typeof(String), _TeamspeakManager.Ts3QueryNickname)); } } else if (Regex.Match(strVariable, @"Teamspeak Main Channel Name").Success) { if (_TeamspeakManager.Ts3MainChannelName != strValue) { _TeamspeakManager.Ts3MainChannelName = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Teamspeak Main Channel Name", typeof(String), _TeamspeakManager.Ts3MainChannelName)); } } else if (Regex.Match(strVariable, @"Teamspeak Secondary Channel Names").Success) { _TeamspeakManager.Ts3SubChannelNames = CPluginVariable.DecodeStringArray(strValue); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Teamspeak Secondary Channel Names", typeof(String), CPluginVariable.EncodeStringArray(_TeamspeakManager.Ts3SubChannelNames))); } else if (Regex.Match(strVariable, @"Debug Display Teamspeak Clients").Success) { //Initial parse Boolean DbgClients = Boolean.Parse(strValue); //Check for changed value if (DbgClients != _TeamspeakManager.DebugClients) { //Assignment _TeamspeakManager.DebugClients = DbgClients; //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Debug Display Teamspeak Clients", typeof(Boolean), _TeamspeakManager.DebugClients)); } } else if (Regex.Match(strVariable, @"TeamSpeak Player Join Announcement").Success) { switch (strValue) { case "Disabled": _TeamspeakManager.JoinDisplay = VoipJoinDisplayType.Disabled; break; case "Say": _TeamspeakManager.JoinDisplay = VoipJoinDisplayType.Say; break; case "Yell": _TeamspeakManager.JoinDisplay = VoipJoinDisplayType.Yell; break; case "Tell": _TeamspeakManager.JoinDisplay = VoipJoinDisplayType.Tell; break; default: Log.Error("Unknown setting when setting teamspeak player announcement."); return; } QueueSettingForUpload(new CPluginVariable(@"TeamSpeak Player Join Announcement", typeof(String), _TeamspeakManager.JoinDisplay.ToString())); } else if (Regex.Match(strVariable, @"TeamSpeak Player Join Message").Success) { if (_TeamspeakManager.JoinDisplayMessage != strValue && (strValue.Contains("%player%") || strValue.Contains("%username%") || strValue.Contains("%playerusername%"))) { _TeamspeakManager.JoinDisplayMessage = strValue; QueueSettingForUpload(new CPluginVariable(@"TeamSpeak Player Join Message", typeof(String), _TeamspeakManager.JoinDisplayMessage)); } } else if (Regex.Match(strVariable, @"TeamSpeak Player Update Seconds").Success) { //Initial parse Int32 UpdateIntervalSeconds = Int32.Parse(strValue); //Check for changed value if (_TeamspeakManager.UpdateIntervalSeconds != UpdateIntervalSeconds) { if (UpdateIntervalSeconds < 5) { UpdateIntervalSeconds = 5; } //Assignment _TeamspeakManager.UpdateIntervalSeconds = UpdateIntervalSeconds; //Upload change to database QueueSettingForUpload(new CPluginVariable(@"TeamSpeak Player Update Seconds", typeof(Int32), _TeamspeakManager.UpdateIntervalSeconds)); } } else if (Regex.Match(strVariable, @"Enable Teamspeak Player Perks").Success) { //Initial parse Boolean TeamspeakPlayerPerksEnable = Boolean.Parse(strValue); //Check for changed value if (TeamspeakPlayerPerksEnable != _TeamspeakPlayerPerksEnable) { //Assignment _TeamspeakPlayerPerksEnable = TeamspeakPlayerPerksEnable; //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Enable Teamspeak Player Perks", typeof(Boolean), _TeamspeakPlayerPerksEnable)); } } else if (Regex.Match(strVariable, @"Teamspeak Player Perks - VIP Kick Whitelist").Success) { //Initial parse Boolean TeamspeakPlayerPerksVIPKickWhitelist = Boolean.Parse(strValue); //Check for changed value if (TeamspeakPlayerPerksVIPKickWhitelist != _TeamspeakPlayerPerksVIPKickWhitelist) { //Rejection cases if (_threadsReady && !_FeedServerReservedSlots && TeamspeakPlayerPerksVIPKickWhitelist) { Log.Error("'Teamspeak Player Perks - VIP Kick Whitelist' cannot be enabled when 'Feed Server Reserved Slots' is disabled."); return; } //Assignment _TeamspeakPlayerPerksVIPKickWhitelist = TeamspeakPlayerPerksVIPKickWhitelist; //Notification if (_threadsReady) { if (_TeamspeakPlayerPerksVIPKickWhitelist) { Log.Info("Teamspeak Player perks now include VIP Kick Whitelist."); } else { Log.Info("Teamspeak Player perks no longer include VIP Kick Whitelist."); } FetchAllAccess(true); } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Teamspeak Player Perks - VIP Kick Whitelist", typeof(Boolean), _TeamspeakPlayerPerksVIPKickWhitelist)); } } else if (Regex.Match(strVariable, @"Teamspeak Player Perks - Autobalance Whitelist").Success) { //Initial parse Boolean TeamspeakPlayerPerksBalanceWhitelist = Boolean.Parse(strValue); //Check for changed value if (TeamspeakPlayerPerksBalanceWhitelist != _TeamspeakPlayerPerksBalanceWhitelist) { //Rejection cases if (_threadsReady && !_FeedMultiBalancerWhitelist && TeamspeakPlayerPerksBalanceWhitelist) { Log.Error("'Teamspeak Player Perks - Autobalance Whitelist' cannot be enabled when 'Feed MULTIBalancer Whitelist' is disabled."); return; } //Assignment _TeamspeakPlayerPerksBalanceWhitelist = TeamspeakPlayerPerksBalanceWhitelist; //Notification if (_threadsReady) { if (_TeamspeakPlayerPerksBalanceWhitelist) { Log.Info("Teamspeak Player perks now include autobalance whitelist."); } else { Log.Info("Teamspeak Player perks no longer include autobalance whitelist."); } FetchAllAccess(true); } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Teamspeak Player Perks - Autobalance Whitelist", typeof(Boolean), _TeamspeakPlayerPerksBalanceWhitelist)); } } else if (Regex.Match(strVariable, @"Teamspeak Player Perks - Ping Whitelist").Success) { //Initial parse Boolean TeamspeakPlayerPerksPingWhitelist = Boolean.Parse(strValue); //Check for changed value if (TeamspeakPlayerPerksPingWhitelist != _TeamspeakPlayerPerksPingWhitelist) { //Rejection cases if (_threadsReady && !_pingEnforcerEnable && TeamspeakPlayerPerksPingWhitelist) { Log.Error("'Teamspeak Player Perks - Ping Whitelist' cannot be enabled when Ping Enforcer is disabled."); return; } //Assignment _TeamspeakPlayerPerksPingWhitelist = TeamspeakPlayerPerksPingWhitelist; //Notification if (_threadsReady) { if (_TeamspeakPlayerPerksPingWhitelist) { Log.Info("Teamspeak Player perks now include ping whitelist."); } else { Log.Info("Teamspeak Player perks no longer include ping whitelist."); } FetchAllAccess(true); } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Teamspeak Player Perks - Ping Whitelist", typeof(Boolean), _TeamspeakPlayerPerksPingWhitelist)); } } else if (Regex.Match(strVariable, @"Teamspeak Player Perks - TeamKillTracker Whitelist").Success) { //Initial parse Boolean TeamspeakPlayerPerksTeamKillTrackerWhitelist = Boolean.Parse(strValue); //Check for changed value if (TeamspeakPlayerPerksTeamKillTrackerWhitelist != _TeamspeakPlayerPerksTeamKillTrackerWhitelist) { //Rejection cases if (_threadsReady && !_FeedTeamKillTrackerWhitelist && TeamspeakPlayerPerksTeamKillTrackerWhitelist) { Log.Error("'Teamspeak Player Perks - TeamKillTracker Whitelist' cannot be enabled when 'Feed TeamKillTracker Whitelist' is disabled."); return; } //Assignment _TeamspeakPlayerPerksTeamKillTrackerWhitelist = TeamspeakPlayerPerksTeamKillTrackerWhitelist; //Notification if (_threadsReady) { if (_TeamspeakPlayerPerksTeamKillTrackerWhitelist) { Log.Info("Teamspeak Player perks now include TeamKillTracker whitelist."); } else { Log.Info("Teamspeak Player perks no longer include TeamKillTracker whitelist."); } FetchAllAccess(true); } //Upload change to database QueueSettingForUpload(new CPluginVariable(@"Teamspeak Player Perks - TeamKillTracker Whitelist", typeof(Boolean), _TeamspeakPlayerPerksTeamKillTrackerWhitelist)); } } else if (Regex.Match(strVariable, @"Monitor Discord Players").Success) { Boolean DiscordPlayerMonitorView = Boolean.Parse(strValue); if (DiscordPlayerMonitorView != _DiscordPlayerMonitorView) { _DiscordPlayerMonitorView = DiscordPlayerMonitorView; QueueSettingForUpload(new CPluginVariable(@"Monitor Discord Players", typeof(Boolean), _DiscordPlayerMonitorView)); } } else if (Regex.Match(strVariable, @"Enable Discord Player Monitor").Success) { Boolean DiscordPlayerMonitorEnable = Boolean.Parse(strValue); if (DiscordPlayerMonitorEnable != _DiscordPlayerMonitorEnable) { _DiscordPlayerMonitorEnable = DiscordPlayerMonitorEnable; if (_threadsReady) { if (DiscordPlayerMonitorEnable) { _DiscordManager.Enable(); } else { _DiscordManager.Disable(); } } QueueSettingForUpload(new CPluginVariable(@"Enable Discord Player Monitor", typeof(Boolean), _DiscordPlayerMonitorEnable)); } } else if (Regex.Match(strVariable, @"Discord Server ID").Success) { if (_DiscordManager.ServerID != strValue) { _DiscordManager.ServerID = strValue; QueueSettingForUpload(new CPluginVariable(@"Discord Server ID", typeof(String), _DiscordManager.ServerID)); } } else if (Regex.Match(strVariable, @"Discord Channel Names").Success) { _DiscordManager.ChannelNames = CPluginVariable.DecodeStringArray(strValue); QueueSettingForUpload(new CPluginVariable(@"Discord Channel Names", typeof(String), CPluginVariable.EncodeStringArray(_DiscordManager.ChannelNames))); } else if (Regex.Match(strVariable, @"Debug Display Discord Members").Success) { Boolean DebugMembers = Boolean.Parse(strValue); if (DebugMembers != _DiscordManager.DebugMembers) { _DiscordManager.DebugMembers = DebugMembers; QueueSettingForUpload(new CPluginVariable(@"Debug Display Discord Members", typeof(Boolean), _DiscordManager.DebugMembers)); } } else if (Regex.Match(strVariable, @"Discord Player Join Announcement").Success) { switch (strValue) { case "Disabled": _DiscordManager.JoinDisplay = VoipJoinDisplayType.Disabled; break; case "Say": _DiscordManager.JoinDisplay = VoipJoinDisplayType.Say; break; case "Yell": _DiscordManager.JoinDisplay = VoipJoinDisplayType.Yell; break; case "Tell": _DiscordManager.JoinDisplay = VoipJoinDisplayType.Tell; break; default: Log.Error("Unknown value when setting discord player announcement."); return; } QueueSettingForUpload(new CPluginVariable(@"Discord Player Join Announcement", typeof(String), _DiscordManager.JoinDisplay.ToString())); } else if (Regex.Match(strVariable, @"Discord Player Join Message").Success) { if (_DiscordManager.JoinMessage != strValue && (strValue.Contains("%player%") || strValue.Contains("%username%") || strValue.Contains("%playerusername%"))) { _DiscordManager.JoinMessage = strValue; QueueSettingForUpload(new CPluginVariable(@"Discord Player Join Message", typeof(String), _DiscordManager.JoinMessage)); } } else if (Regex.Match(strVariable, @"Enable Discord Player Perks").Success) { Boolean DiscordPlayerPerksEnable = Boolean.Parse(strValue); if (DiscordPlayerPerksEnable != _DiscordPlayerPerksEnable) { _DiscordPlayerPerksEnable = DiscordPlayerPerksEnable; QueueSettingForUpload(new CPluginVariable(@"Enable Discord Player Perks", typeof(Boolean), _DiscordPlayerPerksEnable)); } } else if (Regex.Match(strVariable, @"Require Voice in Discord to Issue Admin Commands").Success) { Boolean DiscordPlayerRequireVoiceForAdmin = Boolean.Parse(strValue); if (DiscordPlayerRequireVoiceForAdmin != _DiscordPlayerRequireVoiceForAdmin) { _DiscordPlayerRequireVoiceForAdmin = DiscordPlayerRequireVoiceForAdmin; QueueSettingForUpload(new CPluginVariable(@"Require Voice in Discord to Issue Admin Commands", typeof(Boolean), _DiscordPlayerRequireVoiceForAdmin)); } } else if (Regex.Match(strVariable, @"Discord Player Perks - VIP Kick Whitelist").Success) { Boolean DiscordPlayerPerksVIPKickWhitelist = Boolean.Parse(strValue); if (DiscordPlayerPerksVIPKickWhitelist != _DiscordPlayerPerksVIPKickWhitelist) { if (_threadsReady && !_FeedServerReservedSlots && DiscordPlayerPerksVIPKickWhitelist) { Log.Error("'Discord Player Perks - VIP Kick Whitelist' cannot be enabled when 'Feed Server Reserved Slots' is disabled."); return; } _DiscordPlayerPerksVIPKickWhitelist = DiscordPlayerPerksVIPKickWhitelist; if (_threadsReady) { if (_DiscordPlayerPerksVIPKickWhitelist) { Log.Info("Discord Player perks now include VIP Kick Whitelist."); } else { Log.Info("Discord Player perks no longer include VIP Kick Whitelist."); } FetchAllAccess(true); } QueueSettingForUpload(new CPluginVariable(@"Discord Player Perks - VIP Kick Whitelist", typeof(Boolean), _DiscordPlayerPerksVIPKickWhitelist)); } } else if (Regex.Match(strVariable, @"Discord Player Perks - Autobalance Whitelist").Success) { Boolean DiscordPlayerPerksBalanceWhitelist = Boolean.Parse(strValue); if (DiscordPlayerPerksBalanceWhitelist != _DiscordPlayerPerksBalanceWhitelist) { if (_threadsReady && !_FeedMultiBalancerWhitelist && DiscordPlayerPerksBalanceWhitelist) { Log.Error("'Discord Player Perks - Autobalance Whitelist' cannot be enabled when 'Feed MULTIBalancer Whitelist' is disabled."); return; } _DiscordPlayerPerksBalanceWhitelist = DiscordPlayerPerksBalanceWhitelist; if (_threadsReady) { if (_DiscordPlayerPerksBalanceWhitelist) { Log.Info("Discord Player perks now include autobalance whitelist."); } else { Log.Info("Discord Player perks no longer include autobalance whitelist."); } FetchAllAccess(true); } QueueSettingForUpload(new CPluginVariable(@"Discord Player Perks - Autobalance Whitelist", typeof(Boolean), _DiscordPlayerPerksBalanceWhitelist)); } } else if (Regex.Match(strVariable, @"Discord Player Perks - Ping Whitelist").Success) { Boolean DiscordPlayerPerksPingWhitelist = Boolean.Parse(strValue); if (DiscordPlayerPerksPingWhitelist != _DiscordPlayerPerksPingWhitelist) { if (_threadsReady && !_pingEnforcerEnable && DiscordPlayerPerksPingWhitelist) { Log.Error("'Discord Player Perks - Ping Whitelist' cannot be enabled when Ping Enforcer is disabled."); return; } _DiscordPlayerPerksPingWhitelist = DiscordPlayerPerksPingWhitelist; if (_threadsReady) { if (_DiscordPlayerPerksPingWhitelist) { Log.Info("Discord Player perks now include ping whitelist."); } else { Log.Info("Discord Player perks no longer include ping whitelist."); } FetchAllAccess(true); } QueueSettingForUpload(new CPluginVariable(@"Discord Player Perks - Ping Whitelist", typeof(Boolean), _DiscordPlayerPerksPingWhitelist)); } } else if (Regex.Match(strVariable, @"Discord Player Perks - TeamKillTracker Whitelist").Success) { Boolean DiscordPlayerPerksTeamKillTrackerWhitelist = Boolean.Parse(strValue); if (DiscordPlayerPerksTeamKillTrackerWhitelist != _DiscordPlayerPerksTeamKillTrackerWhitelist) { if (_threadsReady && !_FeedTeamKillTrackerWhitelist && DiscordPlayerPerksTeamKillTrackerWhitelist) { Log.Error("'Discord Player Perks - TeamKillTracker Whitelist' cannot be enabled when 'Feed TeamKillTracker Whitelist' is disabled."); return; } _DiscordPlayerPerksTeamKillTrackerWhitelist = DiscordPlayerPerksTeamKillTrackerWhitelist; if (_threadsReady) { if (_DiscordPlayerPerksTeamKillTrackerWhitelist) { Log.Info("Discord Player perks now include TeamKillTracker whitelist."); } else { Log.Info("Discord Player perks no longer include TeamKillTracker whitelist."); } FetchAllAccess(true); } QueueSettingForUpload(new CPluginVariable(@"Discord Player Perks - TeamKillTracker Whitelist", typeof(Boolean), _DiscordPlayerPerksTeamKillTrackerWhitelist)); } } else if (Regex.Match(strVariable, @"Use Experimental Tools").Success) { Boolean useEXP = Boolean.Parse(strValue); if (useEXP != _UseExperimentalTools) { _UseExperimentalTools = useEXP; if (_UseExperimentalTools) { if (_threadsReady) { Log.Warn("Using experimental tools. Take caution."); } } else { Log.Info("Experimental tools disabled."); } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Use Experimental Tools", typeof(Boolean), _UseExperimentalTools)); } } else if (Regex.Match(strVariable, @"Round Timer: Enable").Success) { if (_serverInfo.ServerType == "OFFICIAL" && Boolean.Parse(strValue) == true) { strValue = "False"; Log.Error("'" + strVariable + "' cannot be enabled on official servers."); return; } Boolean useTimer = Boolean.Parse(strValue); if (useTimer != _useRoundTimer) { _useRoundTimer = useTimer; if (_useRoundTimer) { if (_threadsReady) { Log.Info("Round Timer activated, will enable on next round."); } } else { Log.Info("Round Timer disabled."); } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Round Timer: Enable", typeof(Boolean), _useRoundTimer)); } } else if (Regex.Match(strVariable, @"Round Timer: Round Duration Minutes").Success) { Double duration; if (!Double.TryParse(strValue, out duration)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_maxRoundTimeMinutes != duration) { if (duration <= 0) { duration = 30.0; } _maxRoundTimeMinutes = duration; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Round Timer: Round Duration Minutes", typeof(Double), _maxRoundTimeMinutes)); } } else if (Regex.Match(strVariable, @"Faction Randomizer: Enable").Success) { if (_serverInfo.ServerType == "OFFICIAL" && Boolean.Parse(strValue) == true) { strValue = "False"; Log.Error("Faction Randomizer cannot be enabled on official servers."); return; } Boolean useRandomizer = Boolean.Parse(strValue); if (useRandomizer != _factionRandomizerEnable) { _factionRandomizerEnable = useRandomizer; if (_factionRandomizerEnable) { if (_threadsReady) { Log.Info("Faction randomizer enabled, will activate on next round."); } } else { Log.Info("Faction randomizer disabled."); } QueueSettingForUpload(new CPluginVariable(@"Faction Randomizer: Enable", typeof(Boolean), _factionRandomizerEnable)); } } else if (Regex.Match(strVariable, @"Faction Randomizer: Restriction").Success) { switch (strValue) { case "NoRestriction": _factionRandomizerRestriction = FactionRandomizerRestriction.NoRestriction; break; case "NeverSameFaction": _factionRandomizerRestriction = FactionRandomizerRestriction.NeverSameFaction; break; case "AlwaysSameFaction": _factionRandomizerRestriction = FactionRandomizerRestriction.AlwaysSameFaction; break; case "AlwaysSwapUSvsRU": _factionRandomizerRestriction = FactionRandomizerRestriction.AlwaysSwapUSvsRU; break; case "AlwaysSwapUSvsCN": _factionRandomizerRestriction = FactionRandomizerRestriction.AlwaysSwapUSvsCN; break; case "AlwaysSwapRUvsCN": _factionRandomizerRestriction = FactionRandomizerRestriction.AlwaysSwapRUvsCN; break; case "AlwaysBothUS": _factionRandomizerRestriction = FactionRandomizerRestriction.AlwaysBothUS; break; case "AlwaysBothRU": _factionRandomizerRestriction = FactionRandomizerRestriction.AlwaysBothRU; break; case "AlwaysBothCN": _factionRandomizerRestriction = FactionRandomizerRestriction.AlwaysBothCN; break; case "AlwaysUSvsX": _factionRandomizerRestriction = FactionRandomizerRestriction.AlwaysUSvsX; break; case "AlwaysRUvsX": _factionRandomizerRestriction = FactionRandomizerRestriction.AlwaysRUvsX; break; case "AlwaysCNvsX": _factionRandomizerRestriction = FactionRandomizerRestriction.AlwaysCNvsX; break; case "NeverUSvsX": _factionRandomizerRestriction = FactionRandomizerRestriction.NeverUSvsX; break; case "NeverRUvsX": _factionRandomizerRestriction = FactionRandomizerRestriction.NeverRUvsX; break; case "NeverCNvsX": _factionRandomizerRestriction = FactionRandomizerRestriction.NeverCNvsX; break; } QueueSettingForUpload(new CPluginVariable(@"Faction Randomizer: Restriction", typeof(String), _factionRandomizerRestriction.ToString())); } else if (Regex.Match(strVariable, @"Faction Randomizer: Allow Repeat Team Selections").Success) { Boolean allowRepeatSelections = Boolean.Parse(strValue); if (allowRepeatSelections != _factionRandomizerAllowRepeatSelection) { _factionRandomizerAllowRepeatSelection = allowRepeatSelections; QueueSettingForUpload(new CPluginVariable(@"Faction Randomizer: Allow Repeat Team Selections", typeof(Boolean), _factionRandomizerAllowRepeatSelection)); } } else if (Regex.Match(strVariable, @"Use Challenge System").Success) { Boolean enabled = Boolean.Parse(strValue); if (ChallengeManager != null && enabled != ChallengeManager.Enabled) { ChallengeManager.SetEnabled(enabled); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Use Challenge System", typeof(Boolean), ChallengeManager.Enabled)); } } else if (Regex.Match(strVariable, @"Run Round Challenge ID").Success) { Int32 RuleID = Int32.Parse(strValue); if (ChallengeManager != null) { if (!ChallengeManager.Enabled) { Log.Error("Unable to run challenge rule. Challenge system not enabled."); return; } Log.Info("Attempting to run challenge rule " + RuleID); ChallengeManager.RunRoundChallenge(RuleID); } } else if (Regex.Match(strVariable, @"Challenge System Auto-Assign Round rules").Success) { Boolean autoAssign = Boolean.Parse(strValue); if (ChallengeManager != null && autoAssign != ChallengeManager.AutoPlay) { ChallengeManager.AutoPlay = autoAssign; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Challenge System Auto-Assign Round rules", typeof(Boolean), ChallengeManager.AutoPlay)); } } else if (Regex.Match(strVariable, @"Challenge System Minimum Players").Success) { Int32 minPlayers = Int32.Parse(strValue); if (ChallengeManager != null && minPlayers != ChallengeManager.MinimumPlayers) { ChallengeManager.MinimumPlayers = minPlayers; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Challenge System Minimum Players", typeof(Int32), ChallengeManager.MinimumPlayers)); } } else if (Regex.Match(strVariable, @"Challenge Command Lock Timeout Hours").Success) { Int32 commandLockTimeout = Int32.Parse(strValue); if (ChallengeManager != null && commandLockTimeout != ChallengeManager.CommandLockTimeoutHours) { if (commandLockTimeout < 12) { commandLockTimeout = 12; } ChallengeManager.CommandLockTimeoutHours = commandLockTimeout; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Challenge Command Lock Timeout Hours", typeof(Int32), ChallengeManager.CommandLockTimeoutHours)); } } else if (Regex.Match(strVariable, @"Use Server-Wide Round Rules").Success) { Boolean useRoundRules = Boolean.Parse(strValue); if (ChallengeManager != null && useRoundRules != ChallengeManager.EnableServerRoundRules) { ChallengeManager.EnableServerRoundRules = useRoundRules; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Use Server-Wide Round Rules", typeof(Boolean), ChallengeManager.EnableServerRoundRules)); } } else if (Regex.Match(strVariable, @"Use Different Round Rule For Each Player").Success) { Boolean useRandomRules = Boolean.Parse(strValue); if (ChallengeManager != null && useRandomRules != ChallengeManager.RandomPlayerRoundRules) { ChallengeManager.RandomPlayerRoundRules = useRandomRules; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Use Different Round Rule For Each Player", typeof(Boolean), ChallengeManager.RandomPlayerRoundRules)); } } else if (Regex.Match(strVariable, @"Use NO EXPLOSIVES Limiter").Success) { if (_serverInfo.ServerType == "OFFICIAL" && Boolean.Parse(strValue) == true) { strValue = "False"; Log.Error("'" + strVariable + "' cannot be enabled on official servers."); return; } Boolean useLimiter = Boolean.Parse(strValue); if (useLimiter != _UseWeaponLimiter) { _UseWeaponLimiter = useLimiter; if (_threadsReady) { if (_UseWeaponLimiter) { Log.Info("NO EXPLOSIVES punish limit activated."); } else { Log.Info("NO EXPLOSIVES punish limit disabled."); } } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Use NO EXPLOSIVES Limiter", typeof(Boolean), _UseWeaponLimiter)); } } else if (Regex.Match(strVariable, @"NO EXPLOSIVES Weapon String").Success) { if (_WeaponLimiterString != strValue) { if (!String.IsNullOrEmpty(strValue)) { _WeaponLimiterString = strValue; } else { Log.Error("Weapon String cannot be empty."); } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"NO EXPLOSIVES Weapon String", typeof(String), _WeaponLimiterString)); } } else if (Regex.Match(strVariable, @"NO EXPLOSIVES Exception String").Success) { if (_WeaponLimiterExceptionString != strValue) { if (!String.IsNullOrEmpty(strValue)) { _WeaponLimiterExceptionString = strValue; } else { Log.Error("Weapon exception String cannot be empty."); } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"NO EXPLOSIVES Exception String", typeof(String), _WeaponLimiterExceptionString)); } } else if (Regex.Match(strVariable, @"Event Base Server Name").Success) { if (_eventBaseServerName != strValue) { if (!String.IsNullOrEmpty(strValue)) { _eventBaseServerName = strValue; } else { Log.Error("Server name selection cannot be empty."); } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Event Base Server Name", typeof(String), _eventBaseServerName)); } } else if (Regex.Match(strVariable, @"Event Countdown Server Name").Success) { if (_eventCountdownServerName != strValue) { if (!String.IsNullOrEmpty(strValue)) { _eventCountdownServerName = strValue; } else { Log.Error("Server name selection cannot be empty."); } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Event Countdown Server Name", typeof(String), _eventCountdownServerName)); } } else if (Regex.Match(strVariable, @"Event Concrete Countdown Server Name").Success) { if (_eventConcreteCountdownServerName != strValue) { if (!String.IsNullOrEmpty(strValue)) { _eventConcreteCountdownServerName = strValue; } else { Log.Error("Server name selection cannot be empty."); } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Event Concrete Countdown Server Name", typeof(String), _eventConcreteCountdownServerName)); } } else if (Regex.Match(strVariable, @"Event Active Server Name").Success) { if (_eventActiveServerName != strValue) { if (!String.IsNullOrEmpty(strValue)) { _eventActiveServerName = strValue; } else { Log.Error("Server name selection cannot be empty."); } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Event Active Server Name", typeof(String), _eventActiveServerName)); } } else if (Regex.Match(strVariable, @"Use Grenade Cook Catcher").Success) { Boolean useCookCatcher = Boolean.Parse(strValue); if (useCookCatcher != _UseGrenadeCookCatcher) { _UseGrenadeCookCatcher = useCookCatcher; if (_UseGrenadeCookCatcher) { if (_threadsReady) { Log.Info("Grenade Cook Catcher activated."); } } else { Log.Info("Grenade Cook Catcher disabled."); } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Use Grenade Cook Catcher", typeof(Boolean), _UseGrenadeCookCatcher)); } } else if (Regex.Match(strVariable, @"Automatically Poll Server For Event Options").Success) { Boolean eventPollAutomatic = Boolean.Parse(strValue); if (eventPollAutomatic != _EventPollAutomatic) { _EventPollAutomatic = eventPollAutomatic; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Automatically Poll Server For Event Options", typeof(Boolean), _EventPollAutomatic)); } } else if (Regex.Match(strVariable, @"Max Automatic Polls Per Event").Success) { Int32 EventRoundAutoPollsMax = Int32.Parse(strValue); if (EventRoundAutoPollsMax != _EventRoundAutoPollsMax) { if (EventRoundAutoPollsMax < 1) { EventRoundAutoPollsMax = 1; } if (EventRoundAutoPollsMax > 20) { EventRoundAutoPollsMax = 20; } _EventRoundAutoPollsMax = EventRoundAutoPollsMax; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Max Automatic Polls Per Event", typeof(Double), _EventRoundAutoPollsMax)); } } else if (Regex.Match(strVariable, @"Yell Current Winning Rule Option").Success) { Boolean PollPrintWinning = Boolean.Parse(strValue); if (PollPrintWinning != _eventPollYellWinningRule) { _eventPollYellWinningRule = PollPrintWinning; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Yell Current Winning Rule Option", typeof(Boolean), _eventPollYellWinningRule)); } } else if (strVariable == "Weekly Events") { Boolean EventWeeklyRepeat = Boolean.Parse(strValue); if (EventWeeklyRepeat != _EventWeeklyRepeat) { _EventWeeklyRepeat = EventWeeklyRepeat; // Do not recalculate the event round number from downloaded settings // Only recalculate it from user input if (_firstUserListComplete) { _CurrentEventRoundNumber = 999999; QueueSettingForUpload(new CPluginVariable(@"Event Current Round Number", typeof(Int32), _CurrentEventRoundNumber)); if (_EventWeeklyRepeat) { _EventDate = GetNextWeekday(DateTime.Now.Date, _EventWeeklyDay); if (GetEventRoundDateTime() < DateTime.Now) { // If the given event date is today, but is already in the past // reset it to the same day next week _EventDate = _EventDate.AddDays(7); } QueueSettingForUpload(new CPluginVariable(@"Event Date", typeof(String), _EventDate.ToShortDateString())); } } QueueSettingForUpload(new CPluginVariable(@"Weekly Events", typeof(Boolean), _EventWeeklyRepeat)); } } else if (Regex.Match(strVariable, @"Event Day").Success) { //Check for valid value DayOfWeek update; switch (strValue) { case "Sunday": update = DayOfWeek.Sunday; break; case "Monday": update = DayOfWeek.Monday; break; case "Tuesday": update = DayOfWeek.Tuesday; break; case "Wednesday": update = DayOfWeek.Wednesday; break; case "Thursday": update = DayOfWeek.Thursday; break; case "Friday": update = DayOfWeek.Friday; break; case "Saturday": update = DayOfWeek.Saturday; break; default: Log.Error("Day of week setting " + strValue + " was invalid."); return; } if (_EventWeeklyDay != update) { _EventWeeklyDay = update; // Do not recalculate the event round number from downloaded settings // Only recalculate it from user input if (_firstUserListComplete) { _CurrentEventRoundNumber = 999999; QueueSettingForUpload(new CPluginVariable(@"Event Current Round Number", typeof(Int32), _CurrentEventRoundNumber)); } if (_EventWeeklyRepeat) { _EventDate = GetNextWeekday(DateTime.Now.Date, _EventWeeklyDay); if (GetEventRoundDateTime() < DateTime.Now) { // If the given event date is today, but is already in the past // reset it to the same day next week _EventDate = _EventDate.AddDays(7); } QueueSettingForUpload(new CPluginVariable(@"Event Date", typeof(String), _EventDate.ToShortDateString())); } QueueSettingForUpload(new CPluginVariable(@"Event Day", typeof(String), _EventWeeklyDay.ToString())); } } else if (Regex.Match(strVariable, @"Event Date").Success) { DateTime eventDate = DateTime.Parse(strValue); if (eventDate.ToShortDateString() != _EventDate.ToShortDateString()) { _EventDate = eventDate; // Do not recalculate the event round number from downloaded settings // Only recalculate it from user input if (_firstUserListComplete) { _CurrentEventRoundNumber = 999999; QueueSettingForUpload(new CPluginVariable(@"Event Current Round Number", typeof(Int32), _CurrentEventRoundNumber)); } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Event Date", typeof(String), _EventDate.ToShortDateString())); } } else if (Regex.Match(strVariable, @"Auto-Kick Players Who First Joined After This Date").Success) { DateTime autoKickDate = DateTime.Parse(strValue); if (autoKickDate.ToShortDateString() != _AutoKickNewPlayerDate.ToShortDateString()) { _AutoKickNewPlayerDate = autoKickDate; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Kick Players Who First Joined After This Date", typeof(String), _AutoKickNewPlayerDate.ToShortDateString())); } } else if (Regex.Match(strVariable, @"Event Hour in 24 format").Success) { Double eventHour = Double.Parse(strValue); if (eventHour != _EventHour) { if (eventHour < 0) { eventHour = 0; } if (eventHour > 23.9) { eventHour = 23.9; } _EventHour = eventHour; // Do not recalculate the event round number from downloaded settings // Only recalculate it from user input if (_firstUserListComplete) { _CurrentEventRoundNumber = 999999; QueueSettingForUpload(new CPluginVariable(@"Event Current Round Number", typeof(Int32), _CurrentEventRoundNumber)); } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Event Hour in 24 format", typeof(Double), _EventHour)); } } else if (Regex.Match(strVariable, @"Poll Max Option Count").Success) { Int32 EventPollMaxOptions = Int32.Parse(strValue); if (EventPollMaxOptions != _EventPollMaxOptions) { if (EventPollMaxOptions < 1) { EventPollMaxOptions = 1; } if (EventPollMaxOptions > 5) { EventPollMaxOptions = 5; } _EventPollMaxOptions = EventPollMaxOptions; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Poll Max Option Count", typeof(Double), _EventPollMaxOptions)); } } else if (Regex.Match(strVariable, @"Event Test Round Number").Success) { Int32 roundNumber = Int32.Parse(strValue); if (roundNumber != _EventTestRoundNumber) { if (roundNumber < 1) { roundNumber = 1; } _EventTestRoundNumber = roundNumber; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Event Test Round Number", typeof(Int32), _EventTestRoundNumber)); } } else if (Regex.Match(strVariable, @"Event Current Round Number").Success) { Int32 roundNumber = Int32.Parse(strValue); if (roundNumber != _CurrentEventRoundNumber) { if (roundNumber < 1) { roundNumber = 1; } _CurrentEventRoundNumber = roundNumber; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Event Current Round Number", typeof(Int32), _CurrentEventRoundNumber)); } } else if (Regex.Match(strVariable, @"Event Announce Day Difference").Success) { Double dayDifference = Double.Parse(strValue); if (dayDifference != _EventAnnounceDayDifference) { _EventAnnounceDayDifference = dayDifference; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Event Announce Day Difference", typeof(Double), _EventAnnounceDayDifference)); } } else if (Regex.Match(strVariable, @"Event Duration Rounds").Success) { Int32 roundCount = Int32.Parse(strValue); if (roundCount != _EventRoundOptions.Count()) { if (roundCount < 0) { roundCount = 0; } // Rebuild the round selection list List optionList = new List(); for (int roundNumber = 0; roundNumber < roundCount; roundNumber++) { // If the round option already exists, save it if (roundNumber < _EventRoundOptions.Count()) { optionList.Add(_EventRoundOptions[roundNumber]); } else { AEventOption.MapCode chosenMap = AEventOption.MapCode.UNKNOWN; AEventOption.ModeCode chosenMode = AEventOption.ModeCode.UNKNOWN; AEventOption.RuleCode chosenRule = AEventOption.RuleCode.UNKNOWN; Boolean chosen = false; foreach (AEventOption.MapCode map in AEventOption.MapNames.Keys) { foreach (AEventOption.ModeCode mode in AEventOption.ModeNames.Keys) { foreach (AEventOption.RuleCode rule in AEventOption.RuleNames.Keys.Where(rule => rule != AEventOption.RuleCode.ENDEVENT)) { if (!optionList.Any(option => option.Map == map && option.Mode == mode && option.Rule == rule)) { chosenMap = map; chosenMode = mode; chosenRule = rule; chosen = true; break; } } if (chosen) { break; } } if (chosen) { break; } } if (chosen) { optionList.Add(new AEventOption() { Map = chosenMap, Mode = chosenMode, Rule = chosenRule }); } } } _EventRoundOptions = optionList; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Event Round Codes", typeof(String[]), _EventRoundOptions.Select(round => round.getCode()).ToArray())); } } else if (Regex.Match(strVariable, @"Event Round \d+ Options").Success) { var regex = new Regex("[0-9]+"); Int32 roundNumber = Int32.Parse(regex.Match(strVariable).Value) - 1; if (strValue == "Remove") { _EventRoundOptions.RemoveAt(roundNumber); } else { var newOption = AEventOption.FromDisplay(strValue); if (_EventRoundOptions.Any(option => option.Map == newOption.Map && option.Mode == newOption.Mode && option.Rule == newOption.Rule)) { Log.Error("Event round option " + newOption.getDisplay() + " already exists."); return; } _EventRoundOptions[roundNumber] = newOption; } QueueSettingForUpload(new CPluginVariable(@"Event Round Codes", typeof(String[]), _EventRoundOptions.Select(round => round.getCode()).ToArray())); } else if (Regex.Match(strVariable, @"Event Round Codes").Success) { if (strValue.Trim().Length > 0) { _EventRoundOptions = CPluginVariable.DecodeStringArray(strValue).Select(option => AEventOption.FromCode(option)).ToList(); } QueueSettingForUpload(new CPluginVariable(@"Event Round Codes", typeof(String[]), _EventRoundOptions.Select(round => round.getCode()).ToArray())); } else if (Regex.Match(strVariable, @"Poll Mode Rule Combination Count").Success) { Int32 optionCount = Int32.Parse(strValue); if (optionCount != _EventRoundPollOptions.Count()) { if (optionCount < 0) { optionCount = 0; } // Rebuild the option selection list List optionList = new List(); for (int optionNumber = 0; optionNumber < optionCount; optionNumber++) { // If the option already exists, save it if (optionNumber < _EventRoundPollOptions.Count()) { optionList.Add(_EventRoundPollOptions[optionNumber]); } else { AEventOption.MapCode chosenMap = AEventOption.MapCode.UNKNOWN; AEventOption.ModeCode chosenMode = AEventOption.ModeCode.UNKNOWN; AEventOption.RuleCode chosenRule = AEventOption.RuleCode.UNKNOWN; Boolean chosen = false; foreach (AEventOption.MapCode mapCode in AEventOption.MapNames.Keys) { foreach (AEventOption.ModeCode modeCode in AEventOption.ModeNames.Keys) { foreach (AEventOption.RuleCode ruleCode in AEventOption.RuleNames.Keys.Where(rule => rule != AEventOption.RuleCode.ENDEVENT)) { if (!optionList.Any(option => option.Map == mapCode && option.Mode == modeCode && option.Rule == ruleCode)) { chosenMap = mapCode; chosenMode = modeCode; chosenRule = ruleCode; chosen = true; break; } } if (chosen) { break; } } if (chosen) { break; } } if (chosen) { optionList.Add(new AEventOption() { Map = chosenMap, Mode = chosenMode, Rule = chosenRule }); } } } _EventRoundPollOptions = optionList; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Event Round Poll Codes", typeof(String[]), _EventRoundPollOptions.Select(option => option.getCode()).ToArray())); } } else if (Regex.Match(strVariable, @"Event Poll Option \d+").Success) { var regex = new Regex("[0-9]+"); Int32 optionNumber = Int32.Parse(regex.Match(strVariable).Value) - 1; if (strValue == "Remove") { _EventRoundPollOptions.RemoveAt(optionNumber); } else { var newOption = AEventOption.FromDisplay(strValue); if (_EventRoundPollOptions.Any(option => option.Map == newOption.Map && option.Mode == newOption.Mode && option.Rule == newOption.Rule)) { Log.Error("Event poll option " + newOption.getDisplay() + " already exists."); return; } _EventRoundPollOptions[optionNumber] = newOption; } QueueSettingForUpload(new CPluginVariable(@"Event Round Poll Codes", typeof(String[]), _EventRoundPollOptions.Select(option => option.getCode()).ToArray())); } else if (Regex.Match(strVariable, @"Event Round Poll Codes").Success) { if (strValue.Trim().Length > 0) { _EventRoundPollOptions = CPluginVariable.DecodeStringArray(strValue).Select(option => AEventOption.FromCode(option)).ToList(); } QueueSettingForUpload(new CPluginVariable(@"Event Round Poll Codes", typeof(String[]), _EventRoundPollOptions.Select(option => option.getCode()).ToArray())); } else if (Regex.Match(strVariable, @"Use LIVE Anti Cheat System").Success) { Boolean useLIVESystem = Boolean.Parse(strValue); if (useLIVESystem != _useAntiCheatLIVESystem) { _useAntiCheatLIVESystem = useLIVESystem; if (_threadsReady) { if (_useAntiCheatLIVESystem) { Log.Info("AntiCheat now using the LIVE System."); } else { Log.Info("AntiCheat LIVE system disabled. This should ONLY be disabled if you are seeing 'Issue connecting to Battlelog' warnings."); } } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Use LIVE Anti Cheat System", typeof(Boolean), _useAntiCheatLIVESystem)); } } else if (Regex.Match(strVariable, @"LIVE System Includes Mass Murder and Aimbot Checks").Success) { Boolean AntiCheatLIVESystemActiveStats = Boolean.Parse(strValue); if (AntiCheatLIVESystemActiveStats != _AntiCheatLIVESystemActiveStats) { _AntiCheatLIVESystemActiveStats = AntiCheatLIVESystemActiveStats; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"LIVE System Includes Mass Murder and Aimbot Checks", typeof(Boolean), _AntiCheatLIVESystemActiveStats)); } } else if (Regex.Match(strVariable, @"DPS Checker: Ban Message").Success) { if (_AntiCheatDPSBanMessage != strValue) { _AntiCheatDPSBanMessage = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"DPS Checker: Ban Message", typeof(String), _AntiCheatDPSBanMessage)); } } else if (Regex.Match(strVariable, @"HSK Checker: Enable").Success) { Boolean useAimbotChecker = Boolean.Parse(strValue); if (useAimbotChecker != _UseHskChecker) { _UseHskChecker = useAimbotChecker; if (_UseHskChecker) { if (_threadsReady) { Log.Info("Aimbot Checker activated."); } } else { Log.Info("Aimbot Checker disabled."); } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"HSK Checker: Enable", typeof(Boolean), _UseHskChecker)); } } else if (Regex.Match(strVariable, @"HSK Checker: Trigger Level").Success) { Double triggerLevel; if (!Double.TryParse(strValue, out triggerLevel)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_HskTriggerLevel != triggerLevel) { if (triggerLevel < 45) { triggerLevel = 45; } else if (triggerLevel > 100) { triggerLevel = 100; } _HskTriggerLevel = triggerLevel; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"HSK Checker: Trigger Level", typeof(Double), _HskTriggerLevel)); } } else if (Regex.Match(strVariable, @"HSK Checker: Ban Message").Success) { if (_AntiCheatHSKBanMessage != strValue) { _AntiCheatHSKBanMessage = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"HSK Checker: Ban Message", typeof(String), _AntiCheatHSKBanMessage)); } } else if (Regex.Match(strVariable, @"KPM Checker: Enable").Success) { Boolean useKPMChecker = Boolean.Parse(strValue); if (useKPMChecker != _UseKpmChecker) { _UseKpmChecker = useKPMChecker; if (_UseKpmChecker) { if (_threadsReady) { Log.Info("KPM Checker activated."); } } else { Log.Info("KPM Checker disabled."); } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"KPM Checker: Enable", typeof(Boolean), _UseKpmChecker)); } } else if (Regex.Match(strVariable, @"KPM Checker: Trigger Level").Success) { Double triggerLevel; if (!Double.TryParse(strValue, out triggerLevel)) { Log.HandleException(new AException("Error parsing double value for setting '" + strVariable + "'")); return; } if (_KpmTriggerLevel != triggerLevel) { if (triggerLevel < 4) { triggerLevel = 4; } else if (triggerLevel > 10) { triggerLevel = 10; } _KpmTriggerLevel = triggerLevel; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"KPM Checker: Trigger Level", typeof(Double), _KpmTriggerLevel)); } } else if (Regex.Match(strVariable, @"KPM Checker: Ban Message").Success) { if (_AntiCheatKPMBanMessage != strValue) { _AntiCheatKPMBanMessage = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"KPM Checker: Ban Message", typeof(String), _AntiCheatKPMBanMessage)); } } else if (Regex.Match(strVariable, @"Fetch Actions from Database").Success) { Boolean fetch = Boolean.Parse(strValue); if (fetch != _fetchActionsFromDb) { _fetchActionsFromDb = fetch; _DbCommunicationWaitHandle.Set(); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Fetch Actions from Database", typeof(Boolean), _fetchActionsFromDb)); } } else if (Regex.Match(strVariable, @"AdkatsLRT Extension Token").Success) { if (_AdKatsLRTExtensionToken != strValue) { _AdKatsLRTExtensionToken = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"AdkatsLRT Extension Token", typeof(String), _AdKatsLRTExtensionToken)); CheckForPluginUpdates(true); } } else if (Regex.Match(strVariable, @"Use Additional Ban Message").Success) { Boolean use = Boolean.Parse(strValue); if (_UseBanAppend != use) { _UseBanAppend = use; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Use Additional Ban Message", typeof(Boolean), _UseBanAppend)); } } else if (Regex.Match(strVariable, @"Additional Ban Message").Success) { if (strValue.Length > 30) { strValue = strValue.Substring(0, 30); Log.Error("Ban append cannot be more than 30 characters."); } if (_BanAppend != strValue) { _BanAppend = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Additional Ban Message", typeof(String), _BanAppend)); } } else if (Regex.Match(strVariable, @"Procon Ban Admin Name").Success) { if (strValue.Length > 16) { strValue = strValue.Substring(0, 16); Log.Error("Procon ban admin id cannot be more than 16 characters."); } if (_CBanAdminName != strValue) { _CBanAdminName = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Procon Ban Admin Name", typeof(String), _CBanAdminName)); } } else if (Regex.Match(strVariable, @"Use Ban Enforcer").Success) { if (_serverInfo.ServerType == "OFFICIAL" && Boolean.Parse(strValue) == true) { strValue = "False"; Log.Error("'" + strVariable + "' cannot be enabled on official servers."); return; } Boolean use = Boolean.Parse(strValue); if (_UseBanEnforcer != use) { _UseBanEnforcer = use; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Use Ban Enforcer", typeof(Boolean), _UseBanEnforcer)); if (_UseBanEnforcer) { _fetchActionsFromDb = true; _DbCommunicationWaitHandle.Set(); } } } else if (Regex.Match(strVariable, @"Ban Enforcer BF4 Lenient Kick").Success) { Boolean lenientKick = Boolean.Parse(strValue); if (_BanEnforcerBF4LenientKick != lenientKick) { _BanEnforcerBF4LenientKick = lenientKick; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ban Enforcer BF4 Lenient Kick", typeof(Boolean), _BanEnforcerBF4LenientKick)); } } else if (Regex.Match(strVariable, @"Enforce New Bans by NAME").Success) { Boolean enforceName = Boolean.Parse(strValue); if (_DefaultEnforceName != enforceName) { _DefaultEnforceName = enforceName; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Enforce New Bans by NAME", typeof(Boolean), _DefaultEnforceName)); } } else if (Regex.Match(strVariable, @"Enforce New Bans by GUID").Success) { Boolean enforceGUID = Boolean.Parse(strValue); if (_DefaultEnforceGUID != enforceGUID) { _DefaultEnforceGUID = enforceGUID; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Enforce New Bans by GUID", typeof(Boolean), _DefaultEnforceGUID)); } } else if (Regex.Match(strVariable, @"Enforce New Bans by IP").Success) { Boolean enforceIP = Boolean.Parse(strValue); if (_DefaultEnforceIP != enforceIP) { _DefaultEnforceIP = enforceIP; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Enforce New Bans by IP", typeof(Boolean), _DefaultEnforceIP)); } } else if (Regex.Match(strVariable, @"Ban Search").Success) { if (String.IsNullOrEmpty(strValue) || strValue.Length < 3) { Log.Error("Search query must be 3 or more characters."); return; } lock (_BanEnforcerSearchResults) { _BanEnforcerSearchResults = FetchMatchingBans(strValue, 5); if (_BanEnforcerSearchResults.Count == 0) { Log.Error("No players matching '" + strValue + "' have active bans."); } } } else if (Regex.Match(strVariable, @"Countdown Duration before a Nuke is fired").Success) { Int32 duration = Int32.Parse(strValue); if (_NukeCountdownDurationSeconds != duration) { _NukeCountdownDurationSeconds = duration; if (_NukeCountdownDurationSeconds < 0) { _NukeCountdownDurationSeconds = 0; } if (_NukeCountdownDurationSeconds > 30) { _NukeCountdownDurationSeconds = 30; } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Countdown Duration before a Nuke is fired", typeof(Int32), _NukeCountdownDurationSeconds)); } } else if (Regex.Match(strVariable, @"Minimum Required Reason Length").Success) { Int32 required = Int32.Parse(strValue); if (_RequiredReasonLength != required) { _RequiredReasonLength = required; if (_RequiredReasonLength < 1) { _RequiredReasonLength = 1; } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Minimum Required Reason Length", typeof(Int32), _RequiredReasonLength)); } } else if (Regex.Match(strVariable, @"Minimum Report Handle Seconds").Success) { Int32 minimumReportHandleSeconds = Int32.Parse(strValue); if (_MinimumReportHandleSeconds != minimumReportHandleSeconds) { _MinimumReportHandleSeconds = minimumReportHandleSeconds; if (_MinimumReportHandleSeconds < 0) { _MinimumReportHandleSeconds = 0; } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Minimum Report Handle Seconds", typeof(Int32), _MinimumReportHandleSeconds)); } } else if (Regex.Match(strVariable, @"Minimum Minutes Into Round To Use Assist").Success) { Int32 minimumAssistMinutes = Int32.Parse(strValue); if (_minimumAssistMinutes != minimumAssistMinutes) { _minimumAssistMinutes = minimumAssistMinutes; if (_minimumAssistMinutes < 0) { _minimumAssistMinutes = 0; } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Minimum Minutes Into Round To Use Assist", typeof(Int32), _minimumAssistMinutes)); } } else if (Regex.Match(strVariable, @"Allow Commands from Admin Say").Success) { Boolean allowSayCommands = Boolean.Parse(strValue); if (_AllowAdminSayCommands != allowSayCommands) { _AllowAdminSayCommands = allowSayCommands; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Allow Commands from Admin Say", typeof(Boolean), _AllowAdminSayCommands)); } } else if (Regex.Match(strVariable, @"Reserved slot grants access to squad lead command").Success) { Boolean reservedSquadLead = Boolean.Parse(strValue); if (_ReservedSquadLead != reservedSquadLead) { _ReservedSquadLead = reservedSquadLead; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Reserved slot grants access to squad lead command", typeof(Boolean), _ReservedSquadLead)); } } else if (Regex.Match(strVariable, @"Reserved slot grants access to self-move command").Success) { Boolean reservedSelfMove = Boolean.Parse(strValue); if (_ReservedSelfMove != reservedSelfMove) { _ReservedSelfMove = reservedSelfMove; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Reserved slot grants access to self-move command", typeof(Boolean), _ReservedSelfMove)); } } else if (Regex.Match(strVariable, @"Reserved slot grants access to self-kill command").Success) { Boolean reservedSelfKill = Boolean.Parse(strValue); if (_ReservedSelfKill != reservedSelfKill) { _ReservedSelfKill = reservedSelfKill; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Reserved slot grants access to self-kill command", typeof(Boolean), _ReservedSelfKill)); } } else if (Regex.Match(strVariable, @"Bypass all command confirmation -DO NOT USE-").Success) { Boolean bypassAllConfirmation = Boolean.Parse(strValue); if (_bypassCommandConfirmation != bypassAllConfirmation) { _bypassCommandConfirmation = bypassAllConfirmation; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Bypass all command confirmation -DO NOT USE-", typeof(Boolean), _bypassCommandConfirmation)); } } else if (Regex.Match(strVariable, @"External plugin player commands").Success) { _ExternalPlayerCommands = new List(CPluginVariable.DecodeStringArray(strValue)); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"External plugin player commands", typeof(String), CPluginVariable.EncodeStringArray(_ExternalPlayerCommands.ToArray()))); } else if (Regex.Match(strVariable, @"External plugin admin commands").Success) { _ExternalAdminCommands = new List(CPluginVariable.DecodeStringArray(strValue)); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"External plugin admin commands", typeof(String), CPluginVariable.EncodeStringArray(_ExternalAdminCommands.ToArray()))); } else if (Regex.Match(strVariable, @"Command Target Whitelist Commands").Success) { _CommandTargetWhitelistCommands = new List(CPluginVariable.DecodeStringArray(strValue)); if (_firstUserListComplete) { foreach (string commandText in _CommandTargetWhitelistCommands.ToList()) { if (!_CommandTextDictionary.ContainsKey(commandText)) { Log.Error("Command " + commandText + " not found, removing from command target whitelist commands."); _CommandTargetWhitelistCommands.Remove(commandText); } } } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Command Target Whitelist Commands", typeof(String), CPluginVariable.EncodeStringArray(_CommandTargetWhitelistCommands.ToArray()))); } else if (strVariable.StartsWith("USR")) { //USR1 | ColColonCleaner | User Email //USR1 | ColColonCleaner | User Phone //USR1 | ColColonCleaner | User Role //USR1 | ColColonCleaner | Delete User? //USR1 | ColColonCleaner | Add Soldier? //USR1 | ColColonCleaner | Soldiers | 293492 | ColColonCleaner | Delete Soldier? String[] commandSplit = CPluginVariable.DecodeStringArray(strVariable); String user_id_str = commandSplit[0].TrimStart("USR".ToCharArray()).Trim(); Int32 user_id = Int32.Parse(user_id_str); String section = commandSplit[2].Trim(); AUser aUser = null; if (_userCache.TryGetValue(user_id, out aUser)) { switch (section) { case "User Email": if (String.IsNullOrEmpty(strValue) || Regex.IsMatch(strValue, @"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$")) { aUser.user_email = strValue; //Reupload the user QueueUserForUpload(aUser); } else { Log.Error(strValue + " is an invalid email address."); return; } break; case "User Expiration": DateTime newExpiration; if (DateTime.TryParse(strValue, out newExpiration)) { aUser.user_expiration = newExpiration; //Reupload the user QueueUserForUpload(aUser); } else { Log.Error(strValue + " is an invalid date."); } break; case "User Notes": if (String.IsNullOrEmpty(strValue)) { Log.Error("User notes cannot be blank."); return; } aUser.user_notes = strValue; //Reupload the user QueueUserForUpload(aUser); break; case "User Phone": aUser.user_phone = strValue; //Reupload the user QueueUserForUpload(aUser); break; case "User Role": ARole aRole = null; if (_RoleNameDictionary.TryGetValue(strValue, out aRole)) { aUser.user_role = aRole; } else { Log.Error("Role " + strValue + " not found."); return; } //Reupload the user QueueUserForUpload(aUser); break; case "Delete User?": if (strValue.ToLower() == "delete") { QueueUserForRemoval(aUser); } break; case "Add Soldier?": Thread addSoldierThread = new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "AddSoldier"; Log.Debug(() => "Starting a user change thread.", 2); TryAddUserSoldier(aUser, strValue); QueueUserForUpload(aUser); Log.Debug(() => "Exiting a user change thread.", 2); Threading.StopWatchdog(); })); Threading.StartWatchdog(addSoldierThread); break; case "Soldiers": if (strVariable.Contains("Delete Soldier?") && strValue.ToLower() == "delete") { String player_id_str = commandSplit[3].Trim(); Int64 player_id = Int64.Parse(player_id_str); aUser.soldierDictionary.Remove(player_id); //Reupload the user QueueUserForUpload(aUser); } break; default: Log.Error("Section " + section + " not found."); break; } } } else if (strVariable.StartsWith("CDE")) { //Trim off all but the command ID and section //5. Command List|CDE1 | Kill Player | Active //5. Command List|CDE1 | Kill Player | Logging //5. Command List|CDE1 | Kill Player | Text String[] commandSplit = CPluginVariable.DecodeStringArray(strVariable); String command_id_str = commandSplit[0].TrimStart("CDE".ToCharArray()).Trim(); Int32 command_id = Int32.Parse(command_id_str); String section = commandSplit[2].Trim(); ACommand command = null; if (_CommandIDDictionary.TryGetValue(command_id, out command)) { if (section == "Active") { //Check for valid value if (strValue == "Active") { _RoleCommandCacheUpdateBufferStart = UtcNow(); command.command_active = ACommand.CommandActive.Active; } else if (strValue == "Disabled") { _RoleCommandCacheUpdateBufferStart = UtcNow(); command.command_active = ACommand.CommandActive.Disabled; } else if (strValue == "Invisible") { command.command_active = ACommand.CommandActive.Invisible; } else { Log.Error("Activity setting " + strValue + " was invalid."); return; } switch (command.command_key) { case "command_confirm": if (command.command_active != ACommand.CommandActive.Active) { Log.Warn("Confirm command must be active. Resetting."); command.command_active = ACommand.CommandActive.Active; } break; case "command_cancel": if (command.command_active != ACommand.CommandActive.Active) { Log.Warn("Cancel command must be active. Resetting."); command.command_active = ACommand.CommandActive.Active; } break; } } else if (section == "Logging") { //Check for valid value switch (strValue) { case "Log": command.command_logging = ACommand.CommandLogging.Log; break; case "Mandatory": command.command_logging = ACommand.CommandLogging.Mandatory; break; case "Ignore": command.command_logging = ACommand.CommandLogging.Ignore; break; case "Unable": command.command_logging = ACommand.CommandLogging.Unable; break; default: Log.Error("Logging setting " + strValue + " was invalid."); return; } } else if (section == "Text") { if (String.IsNullOrEmpty(strValue)) { Log.Error("Command text cannot be blank."); return; } //Make sure command text only contains alphanumeric chars, underscores, and dashes Regex rgx = new Regex("[^a-zA-Z0-9_-]"); strValue = rgx.Replace(strValue, "").ToLower(); //Check to make sure text is not a duplicate foreach (ACommand testCommand in _CommandNameDictionary.Values.ToList()) { if (testCommand.command_text == strValue) { Log.Error("Command text cannot be the same as another command."); return; } } switch (command.command_key) { case "command_confirm": if (strValue != "yes") { Log.Warn("Confirm command text must be 'yes'. Resetting."); strValue = "yes"; } break; case "command_cancel": if (strValue != "no") { Log.Warn("Cancel command text must be 'no'. Resetting."); strValue = "no"; } break; } //Assign the command text lock (_CommandIDDictionary) { _CommandTextDictionary.Remove(command.command_text); command.command_text = strValue; _CommandTextDictionary.Add(command.command_text, command); } } else if (section == "Access Method") { if (String.IsNullOrEmpty(strValue)) { Log.Error("Command access method cannot be blank."); return; } command.command_access = (ACommand.CommandAccess)Enum.Parse(typeof(ACommand.CommandAccess), strValue); switch (command.command_key) { case "command_confirm": if (command.command_access != ACommand.CommandAccess.Any) { Log.Warn("Confirm command access must be 'Any'. Resetting."); command.command_access = ACommand.CommandAccess.Any; } break; case "command_cancel": if (command.command_access != ACommand.CommandAccess.Any) { Log.Warn("Confirm command access must be 'Any'. Resetting."); command.command_access = ACommand.CommandAccess.Any; } break; } } else { Log.Error("Section " + section + " not understood."); return; } //Upload the command changes QueueCommandForUpload(command); } else { Log.Error("Command " + command_id + " not found in command dictionary."); } } else if (strVariable.StartsWith("RLE")) { //Trim off all but the role ID and section //RLE1 | Default Guest | CDE3 | Kill Player String[] commandSplit = CPluginVariable.DecodeStringArray(strVariable); String roleIDStr = commandSplit[0].TrimStart("RLE".ToCharArray()).Trim(); Int32 roleID = Int32.Parse(roleIDStr); //If second section is a command prefix, this is the allow/deny clause if (commandSplit[2].Trim().StartsWith("CDE")) { String commandIDStr = commandSplit[2].Trim().TrimStart("CDE".ToCharArray()); Int32 commandID = Int32.Parse(commandIDStr); //Fetch needed role ARole aRole = null; if (_RoleIDDictionary.TryGetValue(roleID, out aRole)) { //Fetch needed command ACommand aCommand = null; if (_CommandIDDictionary.TryGetValue(commandID, out aCommand)) { switch (strValue.ToLower()) { case "allow": lock (aRole.RoleAllowedCommands) { if (!aRole.RoleAllowedCommands.ContainsKey(aCommand.command_key)) { aRole.RoleAllowedCommands.Add(aCommand.command_key, aCommand); } } QueueRoleForUpload(aRole); break; case "deny": switch (aCommand.command_key) { case "command_confirm": Log.Error("Confirm command cannot be denied for any role. [M]"); return; case "command_cancel": Log.Error("Cancel command cannot be denied for any role. [M]"); return; } lock (aRole.RoleAllowedCommands) { aRole.RoleAllowedCommands.Remove(aCommand.command_key); } QueueRoleForUpload(aRole); break; default: Log.Error("Unknown setting when assigning command allowance."); return; } } else { Log.Error("Command " + commandID + " not found in command dictionary."); } } else { Log.Error("Role " + roleID + " not found in role dictionary."); } } else if (commandSplit[2].Trim().StartsWith("GPE")) { String groupIDStr = commandSplit[2].Trim().TrimStart("GPE".ToCharArray()); Int32 groupID = Int32.Parse(groupIDStr); //Fetch needed role ARole aRole = null; if (_RoleIDDictionary.TryGetValue(roleID, out aRole)) { //Fetch needed group ASpecialGroup aGroup = null; if (_specialPlayerGroupIDDictionary.TryGetValue(groupID, out aGroup)) { switch (strValue.ToLower()) { case "assign": lock (aRole.RoleSetGroups) { if (!aRole.RoleSetGroups.ContainsKey(aGroup.group_key)) { aRole.RoleSetGroups.Add(aGroup.group_key, aGroup); } } QueueRoleForUpload(aRole); break; case "ignore": lock (aRole.RoleSetGroups) { aRole.RoleSetGroups.Remove(aGroup.group_key); } QueueRoleForUpload(aRole); break; case "required based on other settings": case "blocked based on other settings": return; default: Log.Error("Unknown setting when changing role group assignment."); return; } } else { Log.Error("Group " + groupID + " not found in group dictionary."); } } else { Log.Error("Role " + roleID + " not found in role dictionary."); } } else if (commandSplit[2].Contains("Delete Role?") && strValue.ToLower() == "delete") { //Fetch needed role ARole aRole = null; if (_RoleIDDictionary.TryGetValue(roleID, out aRole)) { _RoleCommandCacheUpdateBufferStart = UtcNow(); QueueRoleForRemoval(aRole); } else { Log.Error("Unable to fetch role " + roleID + " for deletion."); } } } else if (strVariable.StartsWith("BAN")) { //Trim off all but the command ID and section //BAN1 | ColColonCleaner | Some Reason String[] commandSplit = CPluginVariable.DecodeStringArray(strVariable); String banIDStr = commandSplit[0].TrimStart("BAN".ToCharArray()).Trim(); Int32 banID = Int32.Parse(banIDStr); ABan aBan = null; foreach (ABan innerBan in _BanEnforcerSearchResults.ToList()) { if (innerBan.ban_id == banID) { aBan = innerBan; break; } } if (aBan != null) { switch (strValue) { case "Active": aBan.ban_status = strValue; break; case "Disabled": aBan.ban_status = strValue; break; default: Log.Error("Unknown setting when assigning ban status."); return; } if (aBan.ban_status == "Disabled") { if (aBan.ban_record.command_action.command_key == "player_ban_perm" || aBan.ban_record.command_action.command_key == "player_ban_perm_future") { aBan.ban_record.command_action = GetCommandByKey("player_ban_perm_old"); } else if (aBan.ban_record.command_action.command_key == "player_ban_temp") { aBan.ban_record.command_action = GetCommandByKey("player_ban_temp_old"); } UpdateRecord(aBan.ban_record); } else if (aBan.ban_status == "Active") { if (aBan.ban_record.command_action.command_key == "player_ban_perm_old") { aBan.ban_record.command_action = GetCommandByKey("player_ban_perm"); } else if (aBan.ban_record.command_action.command_key == "player_ban_temp_old") { aBan.ban_record.command_action = GetCommandByKey("player_ban_temp"); } UpdateRecord(aBan.ban_record); } UpdateBanStatus(aBan); Log.Success("Ban " + aBan.ban_id + " is now " + strValue); } else { Log.Error("Unable to update ban. This should not happen."); } } else if (Regex.Match(strVariable, @"Banned Tags").Success) { _BannedTags = CPluginVariable.DecodeStringArray(strValue).Where(entry => !String.IsNullOrEmpty(entry)).ToArray(); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Banned Tags", typeof(String), CPluginVariable.EncodeStringArray(_BannedTags))); } else if (Regex.Match(strVariable, @"Punishment Hierarchy").Success) { _PunishmentHierarchy = CPluginVariable.DecodeStringArray(strValue).Where(punishType => _PunishmentSeverityIndex.Contains(punishType)).ToArray(); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Punishment Hierarchy", typeof(String), CPluginVariable.EncodeStringArray(_PunishmentHierarchy))); } else if (Regex.Match(strVariable, @"Combine Server Punishments").Success) { Boolean combine = Boolean.Parse(strValue); if (_CombineServerPunishments != combine) { _CombineServerPunishments = combine; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Combine Server Punishments", typeof(Boolean), _CombineServerPunishments)); } } else if (Regex.Match(strVariable, @"Automatic Forgives").Success) { Boolean AutomaticForgives = Boolean.Parse(strValue); if (_AutomaticForgives != AutomaticForgives) { _AutomaticForgives = AutomaticForgives; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Automatic Forgives", typeof(Boolean), _AutomaticForgives)); } } else if (Regex.Match(strVariable, @"Automatic Forgive Days Since Punished").Success) { Int32 AutomaticForgiveLastPunishDays = Int32.Parse(strValue); if (AutomaticForgiveLastPunishDays != _AutomaticForgiveLastPunishDays) { if (AutomaticForgiveLastPunishDays < 7) { AutomaticForgiveLastPunishDays = 7; } _AutomaticForgiveLastPunishDays = AutomaticForgiveLastPunishDays; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Automatic Forgive Days Since Punished", typeof(Int32), _AutomaticForgiveLastPunishDays)); } } else if (Regex.Match(strVariable, @"Automatic Forgive Days Since Forgiven").Success) { Int32 AutomaticForgiveLastForgiveDays = Int32.Parse(strValue); if (AutomaticForgiveLastForgiveDays != _AutomaticForgiveLastForgiveDays) { if (AutomaticForgiveLastForgiveDays < 7) { AutomaticForgiveLastForgiveDays = 7; } _AutomaticForgiveLastForgiveDays = AutomaticForgiveLastForgiveDays; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Automatic Forgive Days Since Forgiven", typeof(Int32), _AutomaticForgiveLastForgiveDays)); } } else if (Regex.Match(strVariable, @"Only Kill Players when Server in low population").Success) { Boolean onlyKill = Boolean.Parse(strValue); if (onlyKill != _OnlyKillOnLowPop) { _OnlyKillOnLowPop = onlyKill; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Only Kill Players when Server in low population", typeof(Boolean), _OnlyKillOnLowPop)); } } else if (Regex.Match(strVariable, @"Short Server Name").Success) { var newName = MakeAlphanumeric(strValue); if (newName.Length > 30) { newName = newName.Substring(0, Math.Min(30, newName.Length - 1)); } _shortServerName = strValue; QueueSettingForUpload(new CPluginVariable(@"Short Server Name", typeof(String), _shortServerName)); } else if (Regex.Match(strVariable, @"Low Population Value").Success) { Int32 lowPopulationPlayerCount = Int32.Parse(strValue); if (lowPopulationPlayerCount != _lowPopulationPlayerCount) { if (lowPopulationPlayerCount < 0) { Log.Error("Low population value cannot be less than 0."); lowPopulationPlayerCount = 0; } if (lowPopulationPlayerCount > _highPopulationPlayerCount) { Log.Error("Low population value cannot be greater than high population value."); lowPopulationPlayerCount = _highPopulationPlayerCount; } _lowPopulationPlayerCount = lowPopulationPlayerCount; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Low Population Value", typeof(Int32), _lowPopulationPlayerCount)); } } else if (Regex.Match(strVariable, @"High Population Value").Success) { Int32 HighPopulationPlayerCount = Int32.Parse(strValue); if (HighPopulationPlayerCount != _highPopulationPlayerCount) { if (HighPopulationPlayerCount > 64) { Log.Error("High population value cannot be greater than 64."); HighPopulationPlayerCount = 64; } if (HighPopulationPlayerCount < _lowPopulationPlayerCount) { Log.Error("High population value cannot be less than low population value."); HighPopulationPlayerCount = _lowPopulationPlayerCount; } _highPopulationPlayerCount = HighPopulationPlayerCount; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"High Population Value", typeof(Int32), _highPopulationPlayerCount)); } } else if (Regex.Match(strVariable, @"Automatic Server Restart When Empty").Success) { Boolean automaticServerRestart = Boolean.Parse(strValue); if (automaticServerRestart != _automaticServerRestart) { _automaticServerRestart = automaticServerRestart; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Automatic Server Restart When Empty", typeof(Boolean), _automaticServerRestart)); } } else if (Regex.Match(strVariable, @"Automatic Restart Minimum Uptime Hours").Success) { Int32 automaticServerRestartMinHours = Int32.Parse(strValue); if (automaticServerRestartMinHours != _automaticServerRestartMinHours) { if (automaticServerRestartMinHours < 5) { Log.Error("Duration between automatic restarts cannot be less than 5 hours."); automaticServerRestartMinHours = 5; } _automaticServerRestartMinHours = automaticServerRestartMinHours; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Automatic Restart Minimum Uptime Hours", typeof(Int32), _automaticServerRestartMinHours)); } } else if (Regex.Match(strVariable, @"Automatic Procon Reboot When Server Reboots").Success) { Boolean automaticServerRestartProcon = Boolean.Parse(strValue); if (automaticServerRestartProcon != _automaticServerRestartProcon) { _automaticServerRestartProcon = automaticServerRestartProcon; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Automatic Procon Reboot When Server Reboots", typeof(Boolean), _automaticServerRestartProcon)); } } else if (Regex.Match(strVariable, @"Procon Memory Usage MB Warning").Success) { Int32 MemoryUsageWarn = Int32.Parse(strValue); if (MemoryUsageWarn != _MemoryUsageWarn) { if (MemoryUsageWarn < 256) { Log.Error("Memory warning level cannot be less than 256MB."); MemoryUsageWarn = 256; } _MemoryUsageWarn = MemoryUsageWarn; QueueSettingForUpload(new CPluginVariable(@"Procon Memory Usage MB Warning", typeof(Int32), _MemoryUsageWarn)); } } else if (Regex.Match(strVariable, @"Procon Memory Usage MB AdKats Restart").Success) { Int32 MemoryUsageRestartPlugin = Int32.Parse(strValue); if (MemoryUsageRestartPlugin != _MemoryUsageRestartPlugin) { if (MemoryUsageRestartPlugin < 512) { Log.Error("AdKats reboot for memory level cannot be less than 512MB."); MemoryUsageRestartPlugin = 512; } _MemoryUsageRestartPlugin = MemoryUsageRestartPlugin; QueueSettingForUpload(new CPluginVariable(@"Procon Memory Usage MB AdKats Restart", typeof(Int32), _MemoryUsageRestartPlugin)); } } else if (Regex.Match(strVariable, @"Procon Memory Usage MB Procon Restart").Success) { Int32 MemoryUsageRestartProcon = Int32.Parse(strValue); if (MemoryUsageRestartProcon != _MemoryUsageRestartProcon) { if (MemoryUsageRestartProcon < 1024) { Log.Error("Procon shutdown for memory level cannot be less than 1024MB."); MemoryUsageRestartProcon = 1024; } _MemoryUsageRestartProcon = MemoryUsageRestartProcon; QueueSettingForUpload(new CPluginVariable(@"Procon Memory Usage MB Procon Restart", typeof(Int32), _MemoryUsageRestartProcon)); } } else if (Regex.Match(strVariable, @"Use IRO Punishment").Success) { Boolean iro = Boolean.Parse(strValue); if (iro != _IROActive) { _IROActive = iro; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Use IRO Punishment", typeof(Boolean), _IROActive)); } } else if (Regex.Match(strVariable, @"IRO Punishment Overrides Low Pop").Success) { Boolean overrideIRO = Boolean.Parse(strValue); if (overrideIRO != _IROOverridesLowPop) { _IROOverridesLowPop = overrideIRO; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"IRO Punishment Overrides Low Pop", typeof(Boolean), _IROOverridesLowPop)); } } else if (Regex.Match(strVariable, @"IRO Punishment Infractions Required to Override").Success) { Int32 IROOverridesLowPopInfractions = Int32.Parse(strValue); if (IROOverridesLowPopInfractions != _IROOverridesLowPopInfractions) { _IROOverridesLowPopInfractions = IROOverridesLowPopInfractions; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"IRO Punishment Infractions Required to Override", typeof(Int32), _IROOverridesLowPopInfractions)); } } else if (Regex.Match(strVariable, @"IRO Timeout Minutes").Success) { Int32 timeout = Int32.Parse(strValue); if (timeout != _IROTimeout) { _IROTimeout = timeout; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"IRO Timeout Minutes", typeof(Int32), _IROTimeout)); } } else if (Regex.Match(strVariable, @"MySQL Hostname").Success) { _mySqlHostname = strValue; _dbSettingsChanged = true; _DbCommunicationWaitHandle.Set(); } else if (Regex.Match(strVariable, @"MySQL Port").Success) { Int32 tmp = 3306; if (!Int32.TryParse(strValue, out tmp)) { tmp = 3306; } if (tmp > 0 && tmp < 65536) { _mySqlPort = strValue; _dbSettingsChanged = true; _DbCommunicationWaitHandle.Set(); } else { Log.Error("Invalid value for MySQL Port: '" + strValue + "'. Must be number between 1 and 65535!"); } } else if (Regex.Match(strVariable, @"MySQL Database").Success) { _mySqlSchemaName = strValue; _dbSettingsChanged = true; _DbCommunicationWaitHandle.Set(); } else if (Regex.Match(strVariable, @"MySQL Username").Success) { _mySqlUsername = strValue; _dbSettingsChanged = true; _DbCommunicationWaitHandle.Set(); } else if (Regex.Match(strVariable, @"MySQL Password").Success) { _mySqlPassword = strValue; _dbSettingsChanged = true; _DbCommunicationWaitHandle.Set(); } else if (Regex.Match(strVariable, @"Send Emails").Success) { //Disabled _UseEmail = Boolean.Parse(strValue); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable("Send Emails", typeof(Boolean), _UseEmail)); } else if (Regex.Match(strVariable, @"Use SSL?").Success) { _EmailHandler.UseSSL = Boolean.Parse(strValue); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable("Use SSL?", typeof(Boolean), _EmailHandler.UseSSL)); } else if (Regex.Match(strVariable, @"SMTP-Server address").Success) { if (!String.IsNullOrEmpty(strValue)) { _EmailHandler.SMTPServer = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable("SMTP-Server address", typeof(String), _EmailHandler.SMTPServer)); } } else if (Regex.Match(strVariable, @"SMTP-Server port").Success) { Int32 iPort = Int32.Parse(strValue); if (iPort > 0) { _EmailHandler.SMTPPort = iPort; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable("SMTP-Server port", typeof(Int32), _EmailHandler.SMTPPort)); } } else if (Regex.Match(strVariable, @"Sender address").Success) { if (string.IsNullOrEmpty(strValue)) { _EmailHandler.SenderEmail = "SENDER_CANNOT_BE_EMPTY"; Log.Error("No sender for email was given! Cancelling Operation."); } else { _EmailHandler.SenderEmail = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable("Sender address", typeof(String), _EmailHandler.SenderEmail)); } } else if (Regex.Match(strVariable, @"SMTP-Server username").Success) { if (string.IsNullOrEmpty(strValue)) { _EmailHandler.SMTPUser = "SMTP_USERNAME_CANNOT_BE_EMPTY"; Log.Error("No username for SMTP was given! Cancelling Operation."); } else { _EmailHandler.SMTPUser = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable("SMTP-Server username", typeof(String), _EmailHandler.SMTPUser)); } } else if (Regex.Match(strVariable, @"SMTP-Server password").Success) { if (string.IsNullOrEmpty(strValue)) { _EmailHandler.SMTPPassword = "SMTP_PASSWORD_CANNOT_BE_EMPTY"; Log.Error("No password for SMTP was given! Cancelling Operation."); } else { _EmailHandler.SMTPPassword = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable("SMTP-Server password", typeof(String), _EmailHandler.SMTPPassword)); } } else if (Regex.Match(strVariable, @"Custom HTML Addition").Success) { _EmailHandler.CustomHTMLAddition = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable("Custom HTML Addition", typeof(String), _EmailHandler.CustomHTMLAddition)); } else if (Regex.Match(strVariable, @"Extra Recipient Email Addresses").Success) { _EmailHandler.RecipientEmails = CPluginVariable.DecodeStringArray(strValue).ToList(); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Extra Recipient Email Addresses", typeof(String), strValue)); } else if (Regex.Match(strVariable, @"Only Send Report Emails When Admins Offline").Success) { Boolean emailReportsOnlyWhenAdminless = Boolean.Parse(strValue); if (emailReportsOnlyWhenAdminless != _EmailReportsOnlyWhenAdminless) { _EmailReportsOnlyWhenAdminless = emailReportsOnlyWhenAdminless; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Only Send Report Emails When Admins Offline", typeof(Boolean), _EmailReportsOnlyWhenAdminless)); } } else if (Regex.Match(strVariable, @"Send PushBullet Reports").Success) { _UsePushBullet = Boolean.Parse(strValue); QueueSettingForUpload(new CPluginVariable(@"Send PushBullet Reports", typeof(Boolean), _UsePushBullet)); } else if (Regex.Match(strVariable, @"PushBullet Access Token").Success) { _PushBulletHandler.AccessToken = strValue; QueueSettingForUpload(new CPluginVariable(@"PushBullet Access Token", typeof(String), _PushBulletHandler.AccessToken)); } else if (Regex.Match(strVariable, @"PushBullet Note Target").Success) { switch (strValue) { case "Private": _PushBulletHandler.DefaultTarget = PushBulletHandler.Target.Private; break; case "Channel": _PushBulletHandler.DefaultTarget = PushBulletHandler.Target.Channel; break; default: Log.Error("Unknown setting when changing PushBullet note target."); return; } QueueSettingForUpload(new CPluginVariable(@"PushBullet Note Target", typeof(String), _PushBulletHandler.DefaultTarget.ToString())); } else if (Regex.Match(strVariable, @"PushBullet Channel Tag").Success) { _PushBulletHandler.DefaultChannelTag = strValue; QueueSettingForUpload(new CPluginVariable(@"PushBullet Channel Tag", typeof(String), _PushBulletHandler.DefaultChannelTag)); } else if (Regex.Match(strVariable, @"Only Send PushBullet Reports When Admins Offline").Success) { _PushBulletReportsOnlyWhenAdminless = Boolean.Parse(strValue); QueueSettingForUpload(new CPluginVariable(@"Only Send PushBullet Reports When Admins Offline", typeof(Boolean), _PushBulletReportsOnlyWhenAdminless)); } else if (Regex.Match(strVariable, @"Send Reports to Discord WebHook").Success) { _UseDiscordForReports = Boolean.Parse(strValue); if (_UseDiscordForReports && _firstPlayerListComplete && String.IsNullOrEmpty(_shortServerName)) { Log.Warn("The 'Short Server Name' setting must be filled in before posting discord reports."); } QueueSettingForUpload(new CPluginVariable(@"Send Reports to Discord WebHook", typeof(Boolean), _UseDiscordForReports)); } else if (Regex.Match(strVariable, @"Discord WebHook URL").Success) { _DiscordManager.URL = strValue; if (_UseDiscordForReports && _firstPlayerListComplete && String.IsNullOrEmpty(_shortServerName)) { Log.Warn("The 'Short Server Name' setting must be filled in before posting discord reports."); } QueueSettingForUpload(new CPluginVariable(@"Discord WebHook URL", typeof(String), _DiscordManager.URL)); } else if (Regex.Match(strVariable, @"Only Send Discord Reports When Admins Offline").Success) { _DiscordReportsOnlyWhenAdminless = Boolean.Parse(strValue); if (_UseDiscordForReports && _firstPlayerListComplete && String.IsNullOrEmpty(_shortServerName)) { Log.Warn("The 'Short Server Name' setting must be filled in before posting discord reports."); } QueueSettingForUpload(new CPluginVariable(@"Only Send Discord Reports When Admins Offline", typeof(Boolean), _DiscordReportsOnlyWhenAdminless)); } else if (Regex.Match(strVariable, @"Send update if reported players leave without action").Success) { _DiscordReportsLeftWithoutAction = Boolean.Parse(strValue); if (_UseDiscordForReports && _firstPlayerListComplete && String.IsNullOrEmpty(_shortServerName)) { Log.Warn("The 'Short Server Name' setting must be filled in before posting discord reports."); } QueueSettingForUpload(new CPluginVariable(@"Send update if reported players leave without action", typeof(Boolean), _DiscordReportsLeftWithoutAction)); } else if (Regex.Match(strVariable, @"On-Player-Muted Message").Success) { if (_MutedPlayerMuteMessage != strValue) { _MutedPlayerMuteMessage = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"On-Player-Muted Message", typeof(String), _MutedPlayerMuteMessage)); } } else if (Regex.Match(strVariable, @"On-Player-Killed Message").Success) { if (_MutedPlayerKillMessage != strValue) { _MutedPlayerKillMessage = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"On-Player-Killed Message", typeof(String), _MutedPlayerKillMessage)); } } else if (Regex.Match(strVariable, @"On-Player-Kicked Message").Success) { if (_MutedPlayerKickMessage != strValue) { _MutedPlayerKickMessage = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"On-Player-Kicked Message", typeof(String), _MutedPlayerKickMessage)); } } if (Regex.Match(strVariable, @"# Chances to give player before kicking").Success) { Int32 tmp = 5; int.TryParse(strValue, out tmp); if (_MutedPlayerChances != tmp) { _MutedPlayerChances = tmp; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"# Chances to give player before kicking", typeof(Int32), _MutedPlayerChances)); } } else if (Regex.Match(strVariable, @"Ignore commands for mute enforcement").Success) { Boolean ignoreCommands = Boolean.Parse(strValue); if (_MutedPlayerIgnoreCommands != ignoreCommands) { _MutedPlayerIgnoreCommands = ignoreCommands; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ignore commands for mute enforcement", typeof(Boolean), _MutedPlayerIgnoreCommands)); } } else if (Regex.Match(strVariable, @"Ticket Window High").Success) { Int32 tmp = 2; int.TryParse(strValue, out tmp); if (tmp != _TeamSwapTicketWindowHigh) { _TeamSwapTicketWindowHigh = tmp; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ticket Window High", typeof(Int32), _TeamSwapTicketWindowHigh)); } } else if (Regex.Match(strVariable, @"Ticket Window Low").Success) { Int32 tmp = 2; int.TryParse(strValue, out tmp); if (tmp != _TeamSwapTicketWindowLow) { _TeamSwapTicketWindowLow = tmp; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Ticket Window Low", typeof(Int32), _TeamSwapTicketWindowLow)); } } else if (Regex.Match(strVariable, @"Enable Admin Assistants").Success) { Boolean enableAA = Boolean.Parse(strValue); if (_EnableAdminAssistants != enableAA) { _EnableAdminAssistants = enableAA; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Enable Admin Assistants", typeof(Boolean), _EnableAdminAssistants)); } } else if (Regex.Match(strVariable, @"Enable Admin Assistant Perk").Success) { Boolean enableAA = Boolean.Parse(strValue); if (_EnableAdminAssistantPerk != enableAA) { _EnableAdminAssistantPerk = enableAA; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Enable Admin Assistant Perk", typeof(Boolean), _EnableAdminAssistantPerk)); } } else if (Regex.Match(strVariable, @"Use AA Report Auto Handler").Success) { Boolean useAAHandler = Boolean.Parse(strValue); if (useAAHandler != _UseAAReportAutoHandler) { _UseAAReportAutoHandler = useAAHandler; if (_UseAAReportAutoHandler) { if (_threadsReady) { Log.Info("Automatic Report Handler activated."); } } else { Log.Info("Automatic Report Handler disabled."); } //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Use AA Report Auto Handler", typeof(Boolean), _UseAAReportAutoHandler)); } } else if (Regex.Match(strVariable, @"Auto-Report-Handler Strings").Success) { _AutoReportHandleStrings = CPluginVariable.DecodeStringArray(strValue); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Auto-Report-Handler Strings", typeof(String), CPluginVariable.EncodeStringArray(_AutoReportHandleStrings))); } else if (Regex.Match(strVariable, @"Minimum Confirmed Reports Per Month").Success) { Int32 monthlyReports = Int32.Parse(strValue); if (_MinimumRequiredMonthlyReports != monthlyReports) { _MinimumRequiredMonthlyReports = monthlyReports; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Minimum Confirmed Reports Per Month", typeof(Int32), _MinimumRequiredMonthlyReports)); } } else if (Regex.Match(strVariable, @"Yell display time seconds").Success) { Int32 yellTime = Int32.Parse(strValue); if (_YellDuration != yellTime) { if (yellTime < 0) { Log.Error("Yell duration cannot be negative."); return; } if (yellTime > 10) { Log.Error("Yell duration cannot be greater than 10 seconds."); return; } _YellDuration = yellTime; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Yell display time seconds", typeof(Int32), _YellDuration)); } } else if (Regex.Match(strVariable, @"Pre-Message List").Success) { _PreMessageList = new List(CPluginVariable.DecodeStringArray(strValue)); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Pre-Message List", typeof(String), CPluginVariable.EncodeStringArray(_PreMessageList.ToArray()))); } else if (Regex.Match(strVariable, @"Require Use of Pre-Messages").Success) { Boolean require = Boolean.Parse(strValue); if (require != _RequirePreMessageUse) { _RequirePreMessageUse = require; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Require Use of Pre-Messages", typeof(Boolean), _RequirePreMessageUse)); } } else if (Regex.Match(strVariable, @"Use first spawn message").Success) { Boolean useFirstSpawnMessage = Boolean.Parse(strValue); if (useFirstSpawnMessage != _UseFirstSpawnMessage) { _UseFirstSpawnMessage = useFirstSpawnMessage; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Use first spawn message", typeof(Boolean), _UseFirstSpawnMessage)); } } else if (Regex.Match(strVariable, @"First spawn message text").Success) { if (_FirstSpawnMessage != strValue) { _FirstSpawnMessage = strValue; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"First spawn message text", typeof(String), _FirstSpawnMessage)); } } else if (Regex.Match(strVariable, @"Use First Spawn Reputation and Infraction Message").Success) { Boolean UseFirstSpawnRepMessage = Boolean.Parse(strValue); if (UseFirstSpawnRepMessage != _useFirstSpawnRepMessage) { _useFirstSpawnRepMessage = UseFirstSpawnRepMessage; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Use First Spawn Reputation and Infraction Message", typeof(Boolean), _useFirstSpawnRepMessage)); } } else if (Regex.Match(strVariable, @"Use Perk Expiration Notification").Success) { Boolean UsePerkExpirationNotify = Boolean.Parse(strValue); if (UsePerkExpirationNotify != _UsePerkExpirationNotify) { _UsePerkExpirationNotify = UsePerkExpirationNotify; QueueSettingForUpload(new CPluginVariable(@"Use Perk Expiration Notification", typeof(Boolean), _UsePerkExpirationNotify)); } } else if (Regex.Match(strVariable, @"Perk Expiration Notify Days Remaining").Success) { Int32 PerkExpirationNotifyDays = Int32.Parse(strValue); if (_PerkExpirationNotifyDays != PerkExpirationNotifyDays) { if (PerkExpirationNotifyDays <= 0) { Log.Error("Notify duration must be a positive number of days."); return; } if (PerkExpirationNotifyDays > 90) { Log.Error("Notify duration cannot be longer than 90 days."); return; } _PerkExpirationNotifyDays = PerkExpirationNotifyDays; QueueSettingForUpload(new CPluginVariable(@"Perk Expiration Notify Days Remaining", typeof(Int32), _PerkExpirationNotifyDays)); } } else if (Regex.Match(strVariable, @"SpamBot Enable").Success) { Boolean spamBotEnable = Boolean.Parse(strValue); if (spamBotEnable != _spamBotEnabled) { if (spamBotEnable) { _spamBotSayLastPost = UtcNow() - TimeSpan.FromSeconds(10); _spamBotYellLastPost = UtcNow() - TimeSpan.FromSeconds(10); _spamBotTellLastPost = UtcNow() - TimeSpan.FromSeconds(10); } _spamBotEnabled = spamBotEnable; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"SpamBot Enable", typeof(Boolean), _spamBotEnabled)); } } else if (Regex.Match(strVariable, @"SpamBot Say List").Success) { List spamBotSayList = new List(CPluginVariable.DecodeStringArray(strValue)); if (!_spamBotSayList.SequenceEqual(spamBotSayList)) { _spamBotSayQueue.Clear(); foreach (String line in spamBotSayList.Where(message => !String.IsNullOrEmpty(message)).ToList()) { _spamBotSayQueue.Enqueue(line); } } _spamBotSayList = spamBotSayList; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"SpamBot Say List", typeof(String), CPluginVariable.EncodeStringArray(_spamBotSayList.ToArray()))); } else if (Regex.Match(strVariable, @"SpamBot Say Delay Seconds").Success) { Int32 spamBotSayDelaySeconds = Int32.Parse(strValue); if (_spamBotSayDelaySeconds != spamBotSayDelaySeconds) { if (spamBotSayDelaySeconds < 60) { Log.Error("SpamBot Say Delay cannot be less than 60 seconds."); spamBotSayDelaySeconds = 60; } _spamBotSayDelaySeconds = spamBotSayDelaySeconds; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"SpamBot Say Delay Seconds", typeof(Int32), _spamBotSayDelaySeconds)); } } else if (Regex.Match(strVariable, @"SpamBot Yell List").Success) { List spamBotYellList = new List(CPluginVariable.DecodeStringArray(strValue)); if (!_spamBotYellList.SequenceEqual(spamBotYellList)) { _spamBotYellQueue.Clear(); foreach (String line in spamBotYellList.Where(message => !String.IsNullOrEmpty(message)).ToList()) { _spamBotYellQueue.Enqueue(line); } } _spamBotYellList = spamBotYellList; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"SpamBot Yell List", typeof(String), CPluginVariable.EncodeStringArray(_spamBotYellList.ToArray()))); } else if (Regex.Match(strVariable, @"SpamBot Yell Delay Seconds").Success) { Int32 spamBotYellDelaySeconds = Int32.Parse(strValue); if (_spamBotYellDelaySeconds != spamBotYellDelaySeconds) { if (spamBotYellDelaySeconds < 60) { Log.Error("SpamBot Yell Delay cannot be less than 60 seconds."); spamBotYellDelaySeconds = 60; } _spamBotYellDelaySeconds = spamBotYellDelaySeconds; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"SpamBot Yell Delay Seconds", typeof(Int32), _spamBotYellDelaySeconds)); } } else if (Regex.Match(strVariable, @"SpamBot Tell List").Success) { List spamBotTellList = new List(CPluginVariable.DecodeStringArray(strValue)); if (!_spamBotTellList.SequenceEqual(spamBotTellList)) { _spamBotTellQueue.Clear(); foreach (String line in spamBotTellList.Where(message => !String.IsNullOrEmpty(message)).ToList()) { _spamBotTellQueue.Enqueue(line); } } _spamBotTellList = spamBotTellList; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"SpamBot Tell List", typeof(String), CPluginVariable.EncodeStringArray(_spamBotTellList.ToArray()))); } else if (Regex.Match(strVariable, @"SpamBot Tell Delay Seconds").Success) { Int32 spamBotTellDelaySeconds = Int32.Parse(strValue); if (_spamBotTellDelaySeconds != spamBotTellDelaySeconds) { if (spamBotTellDelaySeconds < 60) { Log.Error("SpamBot Tell Delay cannot be less than 60 seconds."); spamBotTellDelaySeconds = 60; } _spamBotTellDelaySeconds = spamBotTellDelaySeconds; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"SpamBot Tell Delay Seconds", typeof(Int32), _spamBotTellDelaySeconds)); } } else if (Regex.Match(strVariable, @"Exclude Admins and Whitelist from Spam").Success) { Boolean spamBotExcludeAdmins = Boolean.Parse(strValue); if (spamBotExcludeAdmins != _spamBotExcludeAdminsAndWhitelist) { _spamBotExcludeAdminsAndWhitelist = spamBotExcludeAdmins; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Exclude Admins and Whitelist from Spam", typeof(Boolean), _spamBotExcludeAdminsAndWhitelist)); } } else if (Regex.Match(strVariable, @"Player Battlecry Volume").Success) { switch (strValue) { case "Disabled": _battlecryVolume = BattlecryVolume.Disabled; break; case "Say": _battlecryVolume = BattlecryVolume.Say; break; case "Yell": _battlecryVolume = BattlecryVolume.Yell; break; case "Tell": _battlecryVolume = BattlecryVolume.Tell; break; default: Log.Error("Unknown setting when updating player battlecry volume."); return; } QueueSettingForUpload(new CPluginVariable(@"Player Battlecry Volume", typeof(String), _battlecryVolume.ToString())); } else if (Regex.Match(strVariable, @"Player Battlecry Max Length").Success) { Int32 battleCryMaxLength = Int32.Parse(strValue); if (_battlecryMaxLength != battleCryMaxLength) { if (battleCryMaxLength < 20) { Log.Error("Battlecry max length cannot be less than 20 characters."); battleCryMaxLength = 20; } if (battleCryMaxLength > 300) { Log.Error("Battlecry max length cannot be more than 300 characters."); battleCryMaxLength = 300; } _battlecryMaxLength = battleCryMaxLength; QueueSettingForUpload(new CPluginVariable(@"Player Battlecry Max Length", typeof(Int32), _battlecryMaxLength)); } } else if (Regex.Match(strVariable, @"Player Battlecry Denied Words").Success) { _battlecryDeniedWords = CPluginVariable.DecodeStringArray(strValue); QueueSettingForUpload(new CPluginVariable(@"Player Battlecry Denied Words", typeof(String), CPluginVariable.EncodeStringArray(_battlecryDeniedWords))); } else if (Regex.Match(strVariable, @"Display Admin Name in ").Success) { Boolean display = Boolean.Parse(strValue); if (display != _ShowAdminNameInAnnouncement) { _ShowAdminNameInAnnouncement = display; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Display Admin Name in Kick and Ban Announcement", typeof(Boolean), _ShowAdminNameInAnnouncement)); } } else if (Regex.Match(strVariable, @"Display New Player Announcement").Success) { Boolean display = Boolean.Parse(strValue); if (display != _ShowNewPlayerAnnouncement) { _ShowNewPlayerAnnouncement = display; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Display New Player Announcement", typeof(Boolean), _ShowNewPlayerAnnouncement)); } } else if (Regex.Match(strVariable, @"Display Player Name Change Announcement").Success) { Boolean display = Boolean.Parse(strValue); if (display != _ShowPlayerNameChangeAnnouncement) { _ShowPlayerNameChangeAnnouncement = display; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Display Player Name Change Announcement", typeof(Boolean), _ShowPlayerNameChangeAnnouncement)); } } else if (Regex.Match(strVariable, @"Display Targeted Player Left Notification").Success) { Boolean ShowTargetedPlayerLeftNotification = Boolean.Parse(strValue); if (ShowTargetedPlayerLeftNotification != _ShowTargetedPlayerLeftNotification) { _ShowTargetedPlayerLeftNotification = ShowTargetedPlayerLeftNotification; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Display Targeted Player Left Notification", typeof(Boolean), _ShowTargetedPlayerLeftNotification)); } } else if (Regex.Match(strVariable, @"Display Ticket Rates in Procon Chat").Success) { Boolean display = Boolean.Parse(strValue); if (display != _DisplayTicketRatesInProconChat) { _DisplayTicketRatesInProconChat = display; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Display Ticket Rates in Procon Chat", typeof(Boolean), _DisplayTicketRatesInProconChat)); } } else if (Regex.Match(strVariable, @"Inform players of reports against them").Success) { Boolean inform = Boolean.Parse(strValue); if (inform != _InformReportedPlayers) { _InformReportedPlayers = inform; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Inform players of reports against them", typeof(Boolean), _InformReportedPlayers)); } } else if (Regex.Match(strVariable, @"Player Inform Exclusion Strings").Success) { _PlayerInformExclusionStrings = CPluginVariable.DecodeStringArray(strValue); //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Player Inform Exclusion Strings", typeof(String), CPluginVariable.EncodeStringArray(_PlayerInformExclusionStrings))); } else if (Regex.Match(strVariable, @"Inform reputable players of admin joins").Success) { Boolean InformReputablePlayersOfAdminJoins = Boolean.Parse(strValue); if (InformReputablePlayersOfAdminJoins != _InformReputablePlayersOfAdminJoins) { _InformReputablePlayersOfAdminJoins = InformReputablePlayersOfAdminJoins; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Inform reputable players of admin joins", typeof(Boolean), _InformReputablePlayersOfAdminJoins)); } } else if (Regex.Match(strVariable, @"Inform admins of admin joins").Success) { Boolean InformAdminsOfAdminJoins = Boolean.Parse(strValue); if (InformAdminsOfAdminJoins != _InformAdminsOfAdminJoins) { _InformAdminsOfAdminJoins = InformAdminsOfAdminJoins; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Inform admins of admin joins", typeof(Boolean), _InformAdminsOfAdminJoins)); } } else if (Regex.Match(strVariable, @"Use All Caps Limiter").Success) { Boolean UseAllCapsLimiter = Boolean.Parse(strValue); if (UseAllCapsLimiter != _UseAllCapsLimiter) { _UseAllCapsLimiter = UseAllCapsLimiter; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"Use All Caps Limiter", typeof(Boolean), _UseAllCapsLimiter)); } } else if (Regex.Match(strVariable, @"All Caps Limiter Only Limit Specified Players").Success) { Boolean AllCapsLimiterSpecifiedPlayersOnly = Boolean.Parse(strValue); if (AllCapsLimiterSpecifiedPlayersOnly != _AllCapsLimiterSpecifiedPlayersOnly) { _AllCapsLimiterSpecifiedPlayersOnly = AllCapsLimiterSpecifiedPlayersOnly; //Once setting has been changed, upload the change to database QueueSettingForUpload(new CPluginVariable(@"All Caps Limiter Only Limit Specified Players", typeof(Boolean), _AllCapsLimiterSpecifiedPlayersOnly)); } } else if (Regex.Match(strVariable, @"All Caps Limiter Character Percentage").Success) { Int32 AllCapsLimterPercentage = Int32.Parse(strValue); if (_AllCapsLimterPercentage != AllCapsLimterPercentage) { if (AllCapsLimterPercentage < 50) { Log.Error("All Caps Limiter Character Percentage cannot be less than 50%."); AllCapsLimterPercentage = 50; } if (AllCapsLimterPercentage > 100) { Log.Error("All Caps Limiter Character Percentage cannot be greater than 100%."); AllCapsLimterPercentage = 100; } _AllCapsLimterPercentage = AllCapsLimterPercentage; QueueSettingForUpload(new CPluginVariable(@"All Caps Limiter Character Percentage", typeof(Int32), _AllCapsLimterPercentage)); } } else if (Regex.Match(strVariable, @"All Caps Limiter Minimum Characters").Success) { Int32 AllCapsLimterMinimumCharacters = Int32.Parse(strValue); if (_AllCapsLimterMinimumCharacters != AllCapsLimterMinimumCharacters) { if (AllCapsLimterMinimumCharacters < 1) { Log.Error("All Caps Limiter Minimum Characters cannot be less than 1."); AllCapsLimterMinimumCharacters = 1; } if (AllCapsLimterMinimumCharacters > 100) { Log.Error("All Caps Limiter Minimum Characters cannot be greater than 100."); AllCapsLimterMinimumCharacters = 100; } _AllCapsLimterMinimumCharacters = AllCapsLimterMinimumCharacters; QueueSettingForUpload(new CPluginVariable(@"All Caps Limiter Minimum Characters", typeof(Int32), _AllCapsLimterMinimumCharacters)); } } else if (Regex.Match(strVariable, @"All Caps Limiter Warn Threshold").Success) { Int32 AllCapsLimiterWarnThreshold = Int32.Parse(strValue); if (_AllCapsLimiterWarnThreshold != AllCapsLimiterWarnThreshold) { if (AllCapsLimiterWarnThreshold < 1) { Log.Error("All Caps Limiter Warn Threshold cannot be less than 1."); AllCapsLimiterWarnThreshold = 1; } if (_threadsReady) { if (AllCapsLimiterWarnThreshold >= _AllCapsLimiterKillThreshold) { Log.Error("All Caps Limiter Warn Threshold must be less than All Caps Limiter Kill Threshold."); //Reset the value AllCapsLimiterWarnThreshold = _AllCapsLimiterWarnThreshold; } } _AllCapsLimiterWarnThreshold = AllCapsLimiterWarnThreshold; QueueSettingForUpload(new CPluginVariable(@"All Caps Limiter Warn Threshold", typeof(Int32), _AllCapsLimiterWarnThreshold)); } } else if (Regex.Match(strVariable, @"All Caps Limiter Kill Threshold").Success) { Int32 AllCapsLimiterKillThreshold = Int32.Parse(strValue); if (_AllCapsLimiterKillThreshold != AllCapsLimiterKillThreshold) { if (AllCapsLimiterKillThreshold < 2) { Log.Error("All Caps Limiter Kill Threshold cannot be less than 2."); AllCapsLimiterKillThreshold = 2; } if (_threadsReady) { if (AllCapsLimiterKillThreshold >= _AllCapsLimiterKickThreshold) { Log.Error("All Caps Limiter Kill Threshold must be less than All Caps Limiter Kick Threshold."); //Reset the value AllCapsLimiterKillThreshold = _AllCapsLimiterKillThreshold; } if (AllCapsLimiterKillThreshold <= _AllCapsLimiterWarnThreshold) { Log.Error("All Caps Limiter Kill Threshold must be greater than All Caps Limiter Warn Threshold."); //Reset the value AllCapsLimiterKillThreshold = _AllCapsLimiterKillThreshold; } } _AllCapsLimiterKillThreshold = AllCapsLimiterKillThreshold; QueueSettingForUpload(new CPluginVariable(@"All Caps Limiter Kill Threshold", typeof(Int32), _AllCapsLimiterKillThreshold)); } } else if (Regex.Match(strVariable, @"All Caps Limiter Kick Threshold").Success) { Int32 AllCapsLimiterKickThreshold = Int32.Parse(strValue); if (_AllCapsLimiterKickThreshold != AllCapsLimiterKickThreshold) { if (AllCapsLimiterKickThreshold < 3) { Log.Error("All Caps Limiter Kick Threshold cannot be less than 3."); AllCapsLimiterKickThreshold = 3; } if (_threadsReady) { if (AllCapsLimiterKickThreshold <= _AllCapsLimiterKillThreshold) { Log.Error("All Caps Limiter Kick Threshold must be greater than All Caps Limiter Kill Threshold."); //Reset the value AllCapsLimiterKickThreshold = _AllCapsLimiterKickThreshold; } } _AllCapsLimiterKickThreshold = AllCapsLimiterKickThreshold; QueueSettingForUpload(new CPluginVariable(@"All Caps Limiter Kick Threshold", typeof(Int32), _AllCapsLimiterKickThreshold)); } } else if (Regex.Match(strVariable, @"Add User").Success) { if (IsSoldierNameValid(strValue)) { AUser aUser = new AUser { user_name = strValue, user_expiration = UtcNow().AddYears(20), user_notes = "No Notes" }; if (_userCache.Values.Any(iUser => aUser.user_name == iUser.user_name)) { Log.Error("Unable to add " + aUser.user_name + ", a user with that name already exists."); return; } Thread addUserThread = new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "UserChange"; Log.Debug(() => "Starting a user change thread.", 2); //Attempt to add soldiers matching the user's name TryAddUserSoldier(aUser, aUser.user_name); QueueUserForUpload(aUser); Log.Debug(() => "Exiting a user change thread.", 2); Threading.StopWatchdog(); })); Threading.StartWatchdog(addUserThread); } else { Log.Error("User id had invalid formatting, please try again."); } } else if (Regex.Match(strVariable, @"Add Role").Success) { if (!String.IsNullOrEmpty(strValue)) { String roleName = new Regex("[^a-zA-Z0-9 _-]").Replace(strValue, ""); String roleKey = roleName.Replace(' ', '_'); if (!String.IsNullOrEmpty(roleName) && !String.IsNullOrEmpty(roleKey)) { ARole aRole = new ARole { role_key = roleKey, role_name = roleName }; //By default we should include all commands as allowed lock (_CommandIDDictionary) { foreach (ACommand aCommand in _RoleKeyDictionary["guest_default"].RoleAllowedCommands.Values) { aRole.RoleAllowedCommands.Add(aCommand.command_key, aCommand); } } //Queue it for upload _RoleCommandCacheUpdateBufferStart = UtcNow(); QueueRoleForUpload(aRole); } else { Log.Error("Role had invalid characters, please try again."); } } } else if (Regex.Match(strVariable, @"Add Definition?").Success) { var sanitizedName = strValue.Replace("|", ""); if (!String.IsNullOrEmpty(sanitizedName)) { ChallengeManager.CreateDefinition(sanitizedName); } } else if (strVariable.StartsWith("CDH")) { //CDH1 | 5 ARs | Change Name? //CDH1 | 5 ARs | Add Damage Type? //CDH1 | 5 ARs | Add Weapon? //CDH1 | 5 ARs | Delete Definition? //CDH1 | 5 ARs | CDD1 | Damage - Assault Rifle | Damage Type //CDH1 | 5 ARs | CDD1 | Damage - Assault Rifle | Weapon Count //CDH1 | 5 ARs | CDD1 | Damage - Assault Rifle | Kill Count //CDH1 | 5 ARs | CDD1 | Damage - Assault Rifle | Delete Detail? //CDH1 | 5 ARs | CDD2 | Weapon - AEK-971 | Weapon Name //CDH1 | 5 ARs | CDD2 | Weapon - AEK-971 | Kill Count //CDH1 | 5 ARs | CDD2 | Weapon - AEK-971 | Delete Detail? // Split the variable name on | characters using the library var variableSplit = CPluginVariable.DecodeStringArray(strVariable); var definitionIDStr = variableSplit[0].TrimStart("CDH".ToCharArray()).Trim(); var defID = Int64.Parse(definitionIDStr); if (defID <= 0) { Log.Error("Definition setting had an invalid definition ID of " + defID + "."); return; } var definition = ChallengeManager.GetDefinition(defID); if (definition == null) { Log.Error("Unable to fetch definition for ID " + defID + "."); return; } var section = variableSplit[2].Trim(); switch (section) { case "Change Name?": definition.SetNameByString(strValue); break; case "Add Damage Type?": if (strValue != "None") { definition.CreateDetail("Damage", strValue); } break; case "Add Weapon?": if (strValue != "None") { definition.CreateDetail("Weapon", strValue); } break; case "Delete Definition?": if (strValue.ToLower().Trim() == "delete") { definition.DBDelete(null); } break; default: // None of the main sections match. Maybe we're in a detail? if (section.StartsWith("CDD")) { // Yep, we're in a detail. Parse it. //CDH1 | 5 ARs | Change Name? //CDH1 | 5 ARs | Add Damage Type? //CDH1 | 5 ARs | Add Weapon? //CDH1 | 5 ARs | Delete Definition? //CDH1 | 5 ARs | CDD1 | Damage - Assault Rifle | Damage Type //CDH1 | 5 ARs | CDD1 | Damage - Assault Rifle | Weapon Count //CDH1 | 5 ARs | CDD1 | Damage - Assault Rifle | Kill Count //CDH1 | 5 ARs | CDD1 | Damage - Assault Rifle | Delete Detail? //CDH1 | 5 ARs | CDD2 | Weapon - AEK-971 | Weapon Name //CDH1 | 5 ARs | CDD2 | Weapon - AEK-971 | Kill Count //CDH1 | 5 ARs | CDD2 | Weapon - AEK-971 | Delete Detail? var detailIDStr = section.TrimStart("CDD".ToCharArray()).Trim(); var detailID = Int64.Parse(detailIDStr); if (detailID <= 0) { Log.Error("Definition setting had an invalid definition ID of " + detailID + "."); return; } var detail = definition.GetDetail(detailID); if (detail == null) { Log.Error("Unable to fetch definition " + definition.ID + " detail for ID " + detailID + "."); return; } var detailSection = variableSplit[4].Trim(); switch (detailSection) { case "Damage Type": detail.SetDamageTypeByString(strValue); break; case "Weapon Count": detail.SetWeaponCountByString(strValue); break; case "Weapon Name": detail.SetWeaponNameByString(strValue); break; case "Kill Count": detail.SetKillCountByString(strValue); break; case "Delete Detail?": if (strValue.ToLower().Trim() == "delete") { detail.DBDelete(null); definition.SortDetails(null); } break; default: Log.Error("Section " + detailSection + " not found."); break; } } else { // Nope, no idea where we are. Get out of here. Log.Error("Unknown setting section " + section + " parsed in challenge definition section."); return; } break; } } else if (Regex.Match(strVariable, @"Add Rule?").Success) { var sanitizedName = strValue.Replace("|", ""); if (!String.IsNullOrEmpty(sanitizedName)) { ChallengeManager.CreateRule(sanitizedName); } } else if (strVariable.StartsWith("CRH")) { //CRH1 | 5 ARs 1 Round | Definition //CRH1 | 5 ARs 1 Round | Name //CRH1 | 5 ARs 1 Round | Enabled //CRH1 | 5 ARs 1 Round | Tier //CRH1 | 5 ARs 1 Round | Completion //CRH1 | 5 ARs 1 Round | Round Count //CRH1 | 5 ARs 1 Round | Delete Rule? //CRH2 | 5 ARs 30 Mins | Definition //CRH2 | 5 ARs 30 Mins | Name //CRH2 | 5 ARs 30 Mins | Enabled //CRH2 | 5 ARs 30 Mins | Tier //CRH2 | 5 ARs 30 Mins | Completion //CRH2 | 5 ARs 30 Mins | Duration Minutes //CRH2 | 5 ARs 30 Mins | Delete Rule? // Split the variable name on | characters using the library var variableSplit = CPluginVariable.DecodeStringArray(strVariable); var ruleIDStr = variableSplit[0].TrimStart("CRH".ToCharArray()).Trim(); var ruleID = Int64.Parse(ruleIDStr); if (ruleID <= 0) { Log.Error("Rule setting had an invalid rule ID of " + ruleID + "."); return; } var rule = ChallengeManager.GetRule(ruleID); if (rule == null) { Log.Error("Unable to fetch rule for ID " + ruleID + "."); return; } var section = variableSplit[2].Trim(); switch (section) { case "Definition": rule.SetDefinitionByString(strValue); break; case "Name": rule.SetNameByString(strValue); break; case "Enabled": rule.SetEnabledByString(strValue); break; case "Tier": rule.SetTierByString(strValue); break; case "Completion": rule.SetCompletionTypeByString(strValue); break; case "Round Count": rule.SetRoundCountByString(strValue); break; case "Duration Minutes": rule.SetDurationMinutesByString(strValue); break; case "Death Count": rule.SetDeathCountByString(strValue); break; case "Delete Rule?": if (strValue.ToLower().Trim() == "delete") { rule.DBDelete(null); } break; case "^^^SET COMPLETION TYPE^^^": // Ignore this break; default: // No idea where we are. Get out of here. Log.Error("Unknown setting section " + section + " parsed in challenge rule section."); return; } } else if (Regex.Match(strVariable, @"Add Reward?").Success) { Int32 newTier = 1; try { newTier = Int32.Parse(strValue); if (newTier == 0) { return; } if (newTier < 1) { Log.Error("Rule tier cannot be less than 1."); newTier = 1; } if (newTier > 10) { Log.Error("Rule tier cannot be greter than 10."); newTier = 10; } } catch (Exception e) { Log.Error("Error parsing Tier. Create rewards with tier 1-10."); } ChallengeManager.CreateReward(newTier); } else if (strVariable.StartsWith("CCR")) { //CCR1 | Tier 1 - ReservedSlot | Tier Level //CCR1 | Tier 1 - ReservedSlot | Reward Type //CCR1 | Tier 1 - ReservedSlot | Enabled //CCR1 | Tier 1 - ReservedSlot | Duration Minutes //CCR1 | Tier 1 - ReservedSlot | Delete Reward? // Split the variable name on | characters using the library var variableSplit = CPluginVariable.DecodeStringArray(strVariable); var rewardIDStr = variableSplit[0].TrimStart("CCR".ToCharArray()).Trim(); var rewardID = Int64.Parse(rewardIDStr); if (rewardID <= 0) { Log.Error("Reward setting had an invalid reward ID of " + rewardID + "."); return; } var reward = ChallengeManager.GetReward(rewardID); if (reward == null) { Log.Error("Unable to fetch reward for ID " + rewardID + "."); return; } var section = variableSplit[2].Trim(); switch (section) { case "Tier Level": reward.SetTierByString(strValue); break; case "Reward Type": reward.SetRewardTypeByString(strValue); break; case "Enabled": reward.SetEnabledByString(strValue); break; case "Duration Minutes": reward.SetDurationMinutesByString(strValue); break; case "Delete Reward?": if (strValue.ToLower().Trim() == "delete") { reward.DBDelete(null); } break; default: // No idea where we are. Get out of here. Log.Error("Unknown setting section " + section + " parsed in challenge reward section."); return; } } else if (Regex.Match(strVariable, @"Use Proxy for Battlelog").Success) { _UseProxy = Boolean.Parse(strValue); if (_UseProxy && _firstPlayerListComplete && String.IsNullOrEmpty(_ProxyURL)) { Log.Warn("The 'Proxy URL' setting must be filled in before using a proxy."); } QueueSettingForUpload(new CPluginVariable(@"Use Proxy for Battlelog", typeof(Boolean), _UseProxy)); } else if (Regex.Match(strVariable, @"Proxy URL").Success) { try { if (!String.IsNullOrEmpty(strValue)) { Uri uri = new Uri(strValue); Log.Debug(() => "Proxy URL set to " + strValue + ".", 1); } } catch (UriFormatException) { strValue = _ProxyURL; Log.Warn("Invalid Proxy URL! Make sure that the URI is valid!"); } _ProxyURL = strValue; QueueSettingForUpload(new CPluginVariable(@"Proxy URL", typeof(String), _ProxyURL)); } } catch (Exception e) { Log.HandleException(new AException("Error occured while updating AdKats settings.", e)); } } public void OnPluginLoaded(String strHostName, String strPort, String strPRoConVersion) { Log.Debug(() => "Entering OnPluginLoaded", 7); try { //Set the server IP _serverInfo.ServerIP = strHostName + ":" + strPort; //Register all events RegisterEvents(GetType().Name, "OnVersion", "OnServerInfo", "OnSoldierHealth", "OnListPlayers", "OnPunkbusterPlayerInfo", "OnReservedSlotsList", "OnPlayerKilled", "OnPlayerIsAlive", "OnPlayerSpawned", "OnPlayerTeamChange", "OnPlayerSquadChange", "OnPlayerJoin", "OnPlayerLeft", "OnPlayerDisconnected", "OnGlobalChat", "OnTeamChat", "OnSquadChat", "OnLevelLoaded", "OnBanAdded", "OnBanRemoved", "OnBanListClear", "OnBanListSave", "OnBanListLoad", "OnBanList", "OnRoundOverTeamScores", "OnRoundOverPlayers", "OnSpectatorListLoad", "OnSpectatorListSave", "OnSpectatorListPlayerAdded", "OnSpectatorListPlayerRemoved", "OnSpectatorListCleared", "OnSpectatorListList", "OnGameAdminLoad", "OnGameAdminSave", "OnGameAdminPlayerAdded", "OnGameAdminPlayerRemoved", "OnGameAdminCleared", "OnGameAdminList", "OnFairFight", "OnIsHitIndicator", "OnCommander", "OnForceReloadWholeMags", "OnServerType", "OnMaxSpectators", "OnTeamFactionOverride", "OnPlayerPingedByAdmin", "OnMapListList", "OnMaplistLoad", "OnMaplistSave", "OnMaplistCleared", "OnMaplistMapAppended", "OnMaplistNextLevelIndex", "OnMaplistMapRemoved", "OnMaplistMapInserted"); } catch (Exception e) { Log.HandleException(new AException("FATAL ERROR on plugin load.", e)); } Log.Debug(() => "Exiting OnPluginLoaded", 7); } public void OnPluginEnable() { try { //If the finalizer is still alive, inform the user and disable if (_Finalizer != null && _Finalizer.IsAlive) { Log.Error("Cannot enable plugin while it is shutting down. Please Wait for it to shut down."); Threading.Wait(TimeSpan.FromSeconds(2)); //Disable the plugin Disable(); return; } //Create a new thread to activate the plugin _Activator = new Thread(new ThreadStart(delegate { try { Thread.CurrentThread.Name = "Enabler"; Thread.Sleep(250); _roundState = RoundState.Loaded; UpdateFactions(); // Populate the list of available maps _AvailableMapModes = this.GetMapDefines(); if (!_AvailableMapModes.Any()) { Log.Error("Available map modes were empty on load."); } //Add command informing other plugins that AdKats is enabling RegisterCommand(_PluginEnabledMatchCommand); RegisterCommand(_subscribeAsClientMatchCommand); if (_pluginRebootOnDisable) { if (!String.IsNullOrEmpty(_pluginRebootOnDisableSource)) { PlayerTellMessage(_pluginRebootOnDisableSource, "AdKats is Rebooting"); } _pluginRebootOnDisable = false; _pluginRebootOnDisableSource = null; } if ((UtcNow() - _proconStartTime).TotalSeconds <= 25) { Log.Write("Waiting a few seconds for requirements and other plugins to initialize, please wait..."); //Wait on all settings to be imported by procon for initial start, and for all other plugins to start and register. for (Int32 index = 25 - (Int32)(UtcNow() - _proconStartTime).TotalSeconds; index > 0; index--) { Log.Write(index + "..."); Threading.Wait(1000); } } //Make sure the default in-game admin is disabled ExecuteCommand("procon.protected.plugins.enable", "CInGameAdmin", "False"); //Initialize the stat library _StatLibrary = new StatLibrary(this); if (_StatLibrary.PopulateWeaponStats()) { Log.Success("Fetched " + _StatLibrary.Weapons.Count + " " + GameVersion + " weapon stat definitions."); } else { Log.Error("Failed to fetch weapon stat definitions. AdKats cannot be started."); Disable(); Threading.StopWatchdog(); return; } //Fetch all reputation information if (PopulateCommandReputationDictionaries()) { Log.Success("Fetched reputation definitions."); } else { Log.Error("Failed to fetch reputation definitions. AdKats cannot be started."); Disable(); Threading.StopWatchdog(); return; } if (GameVersion == GameVersionEnum.BF3 || GameVersion == GameVersionEnum.BF4 || GameVersion == GameVersionEnum.BFHL) { //Fetch all weapon information if (WeaponDictionary.PopulateDictionaries()) { Log.Success("Fetched weapon information."); } else { Log.Error("Failed to fetch weapon information. AdKats cannot be started."); Disable(); Threading.StopWatchdog(); return; } } //Fetch all special player group information if (PopulateSpecialGroupDictionaries()) { Log.Success("Fetched special player group definitions."); } else { Log.Error("Failed to fetch special player group definitions. AdKats cannot be started."); Disable(); Threading.StopWatchdog(); return; } //Fetch global timing TimeSpan diffUTCGlobal; _globalTimingValid = TestGlobalTiming(false, true, out diffUTCGlobal); _globalTimingOffset = diffUTCGlobal; //Inform of IP Log.Success("Server IP is " + _serverInfo.ServerIP.ToString()); //Set the enabled variable _pluginEnabled = true; //Init and start all the threads InitWaitHandles(); OpenAllHandles(); InitThreads(); StartThreads(); } catch (Exception e) { Log.HandleException(new AException("Error while enabling AdKats.", e)); } Threading.StopWatchdog(); })); Log.Write("^b^2ENABLED!^n^0 Beginning startup sequence..."); //Start the thread Threading.StartWatchdog(_Activator); } catch (Exception e) { Log.HandleException(new AException("Error while initializing activator thread.", e)); } } public void OnPluginDisable() { //If the plugin is already disabling then cancel if (_Finalizer != null && _Finalizer.IsAlive) { return; } try { //Create a new thread to disabled the plugin _Finalizer = new Thread(new ThreadStart(delegate { try { Thread.CurrentThread.Name = "Finalizer"; Log.Info("Shutting down AdKats."); //Disable settings _pluginEnabled = false; _threadsReady = false; //Remove all match commands UnregisterCommand(_PluginEnabledMatchCommand); UnregisterCommand(_issueCommandMatchCommand); UnregisterCommand(_fetchAuthorizedSoldiersMatchCommand); UnregisterCommand(_subscribeAsClientMatchCommand); //Open all handles. Threads will finish on their own. OpenAllHandles(); Threading.MonitorShutdown(); //Reset all caches and storage if (_UserRemovalQueue != null) { _UserRemovalQueue.Clear(); } if (_UserUploadQueue != null) { _UserUploadQueue.Clear(); } if (_TeamswapForceMoveQueue != null) { _TeamswapForceMoveQueue.Clear(); } if (_TeamswapOnDeathCheckingQueue != null) { _TeamswapOnDeathCheckingQueue.Clear(); } if (_TeamswapOnDeathMoveDic != null) { _TeamswapOnDeathMoveDic.Clear(); } if (_UnparsedCommandQueue != null) { _UnparsedCommandQueue.Clear(); } if (_UnparsedMessageQueue != null) { _UnparsedMessageQueue.Clear(); } if (_UnprocessedActionQueue != null) { _UnprocessedActionQueue.Clear(); } if (_UnprocessedRecordQueue != null) { _UnprocessedRecordQueue.Clear(); } if (_UnprocessedStatisticQueue != null) { _UnprocessedStatisticQueue.Clear(); } if (_BanEnforcerCheckingQueue != null) { _BanEnforcerCheckingQueue.Clear(); } if (_AssistAttemptQueue != null) { _AssistAttemptQueue.Clear(); } _toldCol = false; if (_Team2MoveQueue != null) { _Team2MoveQueue.Clear(); } if (_Team1MoveQueue != null) { _Team1MoveQueue.Clear(); } if (_RoundCookers != null) { _RoundCookers.Clear(); } if (_PlayerReports != null) { _PlayerReports.Clear(); } if (_RoundMutedPlayers != null) { _RoundMutedPlayers.Clear(); } if (_PlayerDictionary != null) { _PlayerDictionary.Clear(); } if (_PlayerLeftDictionary != null) { _PlayerLeftDictionary.Clear(); } if (_FetchedPlayers != null) { _FetchedPlayers.Clear(); } _firstPlayerListComplete = false; _firstUserListComplete = false; _firstPlayerListStarted = false; if (_userCache != null) { _userCache.Clear(); } if (FrostbitePlayerInfoList != null) { FrostbitePlayerInfoList.Clear(); } if (_CBanProcessingQueue != null) { _CBanProcessingQueue.Clear(); } if (_BanEnforcerProcessingQueue != null) { _BanEnforcerProcessingQueue.Clear(); } if (_ActOnSpawnDictionary != null) { _ActOnSpawnDictionary.Clear(); } if (_ActOnIsAliveDictionary != null) { _ActOnIsAliveDictionary.Clear(); } if (_ActionConfirmDic != null) { _ActionConfirmDic.Clear(); } if (_LoadoutConfirmDictionary != null) { _LoadoutConfirmDictionary.Clear(); } _AntiCheatCheckedPlayers.Clear(); _AntiCheatCheckedPlayersStats.Clear(); _unmatchedRoundDeathCounts.Clear(); _unmatchedRoundDeaths.Clear(); _endingRound = false; _surrenderVoteList.Clear(); _nosurrenderVoteList.Clear(); _surrenderVoteActive = false; _surrenderVoteSucceeded = false; _surrenderAutoSucceeded = false; _surrenderAutoTriggerCountCurrent = 0; _surrenderAutoTriggerCountPause = 0; _nukesThisRound.Clear(); _lastNukeTeam = null; _roundAssists.Clear(); _pluginUpdateServerInfoChecked = false; _databaseConnectionCriticalState = false; _databaseSuccess = 0; _databaseTimeouts = 0; _pingKicksTotal = 0; if (_subscribedClients.Any()) { Log.Warn("All active subscriptions removed."); _subscribedClients.Clear(); } //Now that plugin is disabled, update the settings page to reflect UpdateSettingPage(); Log.Write("^b^1AdKats " + GetPluginVersion() + " Disabled! =(^n^0"); //Automatic Enable if (_pluginRebootOnDisable && !_useKeepAlive) { Enable(); } } catch (Exception e) { Log.HandleException(new AException("Error occured while disabling Adkats.", e)); } })); //Start the finalizer thread _Finalizer.Start(); } catch (Exception e) { Log.HandleException(new AException("Error occured while initializing AdKats disable thread.", e)); } } private void FetchPluginDocumentation() { if (Threading.IsAlive("DescFetching")) { return; } _PluginDescriptionWaitHandle.Reset(); //Create a new thread to fetch the plugin description and changelog Thread descFetcher = new Thread(new ThreadStart(delegate { try { Thread.CurrentThread.Name = "DescFetching"; _pluginDescFetchProgress = "Started"; //Create web client GZipWebClient client = new GZipWebClient(compress: false); //Download the readme and changelog Log.Debug(() => "Fetching plugin links...", 2); try { _pluginLinks = Util.ClientDownloadTimer(client, "https://raw.githubusercontent.com/AdKats/AdKats/master/LINKS.md?cacherand=" + Environment.TickCount); Log.Debug(() => "Plugin links fetched.", 1); } catch (Exception) { try { _pluginLinks = Util.ClientDownloadTimer(client, "https://api.myrcon.net/plugins/adkats/links?cacherand=" + Environment.TickCount); Log.Debug(() => "Plugin links fetched from backup location.", 1); } catch (Exception) { Log.Error("Failed to fetch plugin links."); } } _pluginDescFetchProgress = "LinksFetched"; Log.Debug(() => "Fetching plugin readme...", 2); try { _pluginDescription = Util.ClientDownloadTimer(client, "https://raw.githubusercontent.com/AdKats/AdKats/master/README.md?cacherand=" + Environment.TickCount); Log.Debug(() => "Plugin readme fetched.", 1); } catch (Exception) { try { _pluginDescription = Util.ClientDownloadTimer(client, "https://api.myrcon.net/plugins/adkats/readme?cacherand=" + Environment.TickCount); Log.Debug(() => "Plugin readme fetched from backup location.", 1); } catch (Exception) { Log.Error("Failed to fetch plugin readme."); } } _pluginDescFetchProgress = "DescFetched"; Log.Debug(() => "Fetching plugin changelog...", 2); try { _pluginChangelog = Util.ClientDownloadTimer(client, "https://raw.githubusercontent.com/AdKats/AdKats/master/CHANGELOG.md?cacherand=" + Environment.TickCount); Log.Debug(() => "Plugin changelog fetched.", 1); } catch (Exception) { try { _pluginChangelog = Util.ClientDownloadTimer(client, "https://api.myrcon.net/plugins/adkats/changelog?cacherand=" + Environment.TickCount); Log.Debug(() => "Plugin changelog fetched from backup location.", 1); } catch (Exception) { Log.Error("Failed to fetch plugin changelog."); } } _pluginDescFetchProgress = "ChangeFetched"; if (!String.IsNullOrEmpty(_pluginDescription)) { //Extract the latest stable version String latestStableVersion = ExtractString(_pluginDescription, "latest_stable_release"); if (!String.IsNullOrEmpty(latestStableVersion)) { _latestPluginVersion = latestStableVersion; _latestPluginVersionInt = ConvertVersionInt(latestStableVersion); //Get current plugin version _currentPluginVersionInt = ConvertVersionInt(PluginVersion); String versionStatus = String.Empty; //Add the appropriate message to plugin description if (_latestPluginVersionInt > _currentPluginVersionInt) { if (_pluginUpdatePatched) { versionStatus = @"

You are running an outdated version! The update has been patched, reboot PRoCon to run version " + latestStableVersion + @"!

"; } else { versionStatus = @"

You are running an outdated version! Version " + latestStableVersion + @" is available for download!

Download Version " + latestStableVersion + @"!
Download link below."; } _pluginVersionStatus = VersionStatus.OutdatedBuild; } else if (_latestPluginVersionInt == _currentPluginVersionInt) { versionStatus = @"

Congrats! You are running the latest stable version!

"; _pluginVersionStatus = VersionStatus.StableBuild; } else if (_latestPluginVersionInt < _currentPluginVersionInt) { versionStatus = @"

CAUTION! You are running a TEST version! Functionality might not be completely tested.

"; _pluginVersionStatus = VersionStatus.TestBuild; } else { _pluginVersionStatus = VersionStatus.UnknownBuild; } //Prepend the message _pluginVersionStatusString = versionStatus; _pluginDescFetchProgress = "VersionStatusSet"; //Check for plugin updates CheckForPluginUpdates(false); _pluginDescFetchProgress = "UpdateChecked"; } } else if (!_fetchedPluginInformation) { Log.Error("Unable to fetch required documentation files. AdKats cannot be started."); Disable(); Threading.StopWatchdog(); return; } Log.Debug(() => "Setting desc fetch handle.", 1); _fetchedPluginInformation = true; _LastPluginDescFetch = UtcNow(); _PluginDescriptionWaitHandle.Set(); _pluginDescFetchProgress = "Completed"; } catch (Exception e) { Log.HandleException(new AException("Error while fetching plugin description and changelog.", e)); } Threading.StopWatchdog(); })); //Start the thread Threading.StartWatchdog(descFetcher); } private void RunMemoryMonitor() { try { //Memory Monitor - Every 60 seconds _MemoryUsageCurrent = (Int32)(GC.GetTotalMemory(true) / 1024 / 1024); if (NowDuration(_LastMemoryWarning).TotalSeconds > 60) { if (_MemoryUsageCurrent >= _MemoryUsageRestartProcon && NowDuration(_proconStartTime).TotalMinutes > 30 && _firstPlayerListComplete) { Environment.Exit(2232); } else if (_MemoryUsageCurrent >= _MemoryUsageRestartPlugin && NowDuration(_AdKatsRunningTime).TotalMinutes > 30 && _firstPlayerListComplete) { Log.Warn(_MemoryUsageCurrent + "MB estimated memory used."); QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("plugin_restart"), command_numeric = 0, target_name = "AdKats", source_name = "MemoryMonitor", record_message = _MemoryUsageCurrent + "MB estimated memory used", record_time = UtcNow() }); _LastMemoryWarning = UtcNow(); } else if (_MemoryUsageCurrent >= _MemoryUsageWarn) { String mm = " MAP: "; mm += "1:" + Threading.Count() + ", "; mm += "2:" + _populationPopulatingPlayers.Count() + ", "; mm += "3:" + _ActOnIsAliveDictionary.Count() + ", "; mm += "4:" + _ActOnSpawnDictionary.Count() + ", "; mm += "5:" + _LoadoutConfirmDictionary.Count() + ", "; mm += "6:" + _ActionConfirmDic.Count() + ", "; mm += "7:" + _PlayerReports.Count() + ", "; mm += "8:" + _userCache.Count() + ", "; mm += "9:" + _specialPlayerGroupIDDictionary.Count() + ", "; mm += "10:" + _specialPlayerGroupKeyDictionary.Count() + ", "; mm += "11:" + _baseSpecialPlayerCache.Count() + ", "; mm += "12:" + _verboseSpecialPlayerCache.Count() + ", "; mm += "13:" + _roundAssists.Count() + ", "; mm += "14:" + _PlayerDictionary.Count() + ", "; mm += "15:" + _RoundPrepSquads.Count() + ", "; mm += "16:" + _PlayerLeftDictionary.Count() + ", "; mm += "17:" + _FetchedPlayers.Count() + ", "; mm += "19:" + _populatorPlayers.Count() + ", "; mm += "20:" + _TeamspeakPlayers.Count() + ", "; mm += "21:" + _RoundCookers.Count() + ", "; mm += "22:" + _BanEnforcerCheckingQueue.Count() + ", "; mm += "23:" + _AntiCheatQueue.Count() + ", "; mm += "24:" + _KillProcessingQueue.Count() + ", "; mm += "25:" + _PlayerListProcessingQueue.Count() + ", "; mm += "26:" + _PlayerRemovalProcessingQueue.Count() + ", "; mm += "27:" + _SettingUploadQueue.Count() + ", "; mm += "28:" + _UnparsedCommandQueue.Count() + ", "; mm += "29:" + _UnparsedMessageQueue.Count() + ", "; mm += "30:" + _UnprocessedActionQueue.Count() + ", "; mm += "31:" + _UnprocessedRecordQueue.Count() + ", "; mm += "32:" + _UnprocessedStatisticQueue.Count() + ", "; mm += "33:" + _UserRemovalQueue.Count() + ", "; mm += "34:" + _UserUploadQueue.Count() + ", "; mm += "35:" + _BattlelogFetchQueue.Count() + ", "; mm += "36:" + _IPInfoFetchQueue.Count() + ", "; mm += "37:" + _CommandIDDictionary.Count() + ", "; mm += "38:" + _CommandKeyDictionary.Count() + ", "; mm += "39:" + _CommandNameDictionary.Count() + ", "; mm += "41:" + _CommandTextDictionary.Count() + ", "; mm += "42:" + _RoleIDDictionary.Count() + ", "; mm += "43:" + _RoleKeyDictionary.Count() + ", "; mm += "44:" + _RoleNameDictionary.Count() + ", "; mm += "45:" + _teamDictionary.Count() + ", "; mm += "46:" + _TeamswapOnDeathMoveDic.Count() + ", "; mm += "47:" + _DiscordPlayers.Count() + ", "; mm += "48:" + ChallengeManager.Definitions.Count() + ", "; mm += "49:" + ChallengeManager.Rules.Count() + ", "; mm += "50:" + ChallengeManager.Entries.Count() + ", "; mm += "51:" + ChallengeManager.CompletedRoundEntries.Count() + ", "; Log.Warn(_MemoryUsageCurrent + "MB estimated memory used." + mm); _LastMemoryWarning = UtcNow(); } } } catch (Exception e) { Log.HandleException(new AException("Error running memory monitor.", e)); } } private void RunPlayerListingStatMonitor() { try { if (_DebugPlayerListing && NowDuration(_LastDebugPlayerListingMessage).TotalSeconds > 30.0) { Log.Info("PlayerListing: " + Math.Round(getPlayerListTriggerRate(), 1) + " Triggered/min, " + Math.Round(getPlayerListReceiveRate(), 1) + " Received/min, " + Math.Round(getPlayerListAcceptRate(), 1) + " Accepted/min, " + Math.Round(getPlayerListProcessedRate(), 1) + " Processed/min"); _LastDebugPlayerListingMessage = UtcNow(); } } catch (Exception e) { Log.HandleException(new AException("Error running playerliststat monitor.", e)); } } private void RunUnswitcherMonitor() { try { //Check for unswitcher disable - every 5 seconds if (_pluginEnabled && _MULTIBalancerUnswitcherDisabled && (UtcNow() - _LastPlayerMoveIssued).TotalSeconds > 5) { Log.Debug(() => "MULTIBalancer Unswitcher Re-Enabled", 3); ExecuteCommand("procon.protected.plugins.call", "MULTIbalancer", "UpdatePluginData", "AdKats", "bool", "DisableUnswitcher", "False"); _MULTIBalancerUnswitcherDisabled = false; } } catch (Exception e) { Log.HandleException(new AException("Error running unswitcher monitor.", e)); } } private void RunDocumentationMonitor() { try { //Check for plugin updates at interval if ((UtcNow() - _LastPluginDescFetch).TotalHours > 1) { FetchPluginDocumentation(); } } catch (Exception e) { Log.HandleException(new AException("Error running documentation monitor.", e)); } } private void RunSpambotMonitor() { try { //SpamBot - Every 500ms var playerCount = GetPlayerCount(); if (_pluginEnabled && _spamBotEnabled && _firstPlayerListComplete && playerCount > 0) { if ((UtcNow() - _spamBotSayLastPost).TotalSeconds > _spamBotSayDelaySeconds && _spamBotSayQueue.Any()) { Boolean posted = false; Int32 attempts = 0; do { String message = _spamBotSayQueue.Peek(); message = ConfirmSpambotMessageValid(message); if (!String.IsNullOrEmpty(message)) { message = ReplaceSpambotEventInfo(message); message = "[SpamBotMessage]" + message; if (_spamBotExcludeAdminsAndWhitelist) { OnlineNonWhitelistSayMessage(message, playerCount > 5); } else { AdminSayMessage(message, playerCount > 5); } posted = true; _spamBotSayLastPost = UtcNow(); } _spamBotSayQueue.Enqueue(_spamBotSayQueue.Dequeue()); } while (!posted && ++attempts < _spamBotSayQueue.Count()); } if ((UtcNow() - _spamBotYellLastPost).TotalSeconds > _spamBotYellDelaySeconds && _spamBotYellQueue.Any()) { Boolean posted = false; Int32 attempts = 0; do { String message = _spamBotYellQueue.Peek(); message = ConfirmSpambotMessageValid(message); if (!String.IsNullOrEmpty(message)) { message = ReplaceSpambotEventInfo(message); message = "[SpamBotMessage]" + message; if (_spamBotExcludeAdminsAndWhitelist) { OnlineNonWhitelistYellMessage(message, playerCount > 5); } else { AdminYellMessage(message, playerCount > 5, 0); } posted = true; _spamBotYellLastPost = UtcNow(); } _spamBotYellQueue.Enqueue(_spamBotYellQueue.Dequeue()); } while (!posted && ++attempts < _spamBotYellQueue.Count()); } if ((UtcNow() - _spamBotTellLastPost).TotalSeconds > _spamBotTellDelaySeconds && _spamBotTellQueue.Any()) { Boolean posted = false; Int32 attempts = 0; do { String message = _spamBotTellQueue.Peek(); message = ConfirmSpambotMessageValid(message); if (!String.IsNullOrEmpty(message)) { message = ReplaceSpambotEventInfo(message); message = "[SpamBotMessage]" + message; if (_spamBotExcludeAdminsAndWhitelist) { OnlineNonWhitelistTellMessage(message, playerCount > 5); } else { AdminTellMessage(message, playerCount > 5); } posted = true; _spamBotTellLastPost = UtcNow(); } _spamBotTellQueue.Enqueue(_spamBotTellQueue.Dequeue()); } while (!posted && ++attempts < _spamBotTellQueue.Count()); } } } catch (Exception e) { Log.HandleException(new AException("Error running spambot monitor.", e)); } } private String ConfirmSpambotMessageValid(String messageString) { //Confirm that rule prefixes conform to the map/modes available var allMaps = _AvailableMapModes.Select(mapMode => mapMode.PublicLevelName).Distinct().ToArray(); var allModes = _AvailableMapModes.Select(mapMode => mapMode.GameMode).Distinct().ToArray(); var matchingMapMode = _AvailableMapModes.FirstOrDefault(mapMode => mapMode.FileName == _serverInfo.InfoObject.Map && mapMode.PlayList == _serverInfo.InfoObject.GameMode); if (matchingMapMode != null) { var serverMap = matchingMapMode.PublicLevelName; var serverMode = matchingMapMode.GameMode; //Check if the rule starts with any map foreach (var ruleMap in allMaps) { if (messageString.StartsWith(ruleMap + "/")) { //Remove the map from the rule text messageString = TrimStart(messageString, ruleMap + "/"); if (ruleMap != serverMap) { return null; } break; } } //Check if the rule starts with any mode foreach (var ruleMode in allModes) { if (messageString.StartsWith(ruleMode + "/")) { //Remove the mode from the rule text messageString = TrimStart(messageString, ruleMode + "/"); if (ruleMode != serverMode) { return null; } break; } } //Check again for maps, since they might have put them in a different order foreach (var ruleMap in allMaps) { if (messageString.StartsWith(ruleMap + "/")) { //Remove the map from the rule text messageString = TrimStart(messageString, ruleMap + "/"); if (ruleMap != serverMap) { return null; } break; } } } return messageString; } private String ReplaceSpambotEventInfo(String message) { var eventDate = GetEventRoundDateTime(); if (((_CurrentEventRoundNumber == 999999 && eventDate < DateTime.Now) || _CurrentEventRoundNumber < _roundID) && !EventActive()) { message = message.Replace("%EventDateDuration%", "TBD") .Replace("%EventDateTime%", "TBD") .Replace("%EventDate%", "TBD") .Replace("%EventRound%", "TBD") .Replace("%RemainingRounds%", "TBD") .Replace("%s%", "s") .Replace("%S%", "S"); } else { if (message.Contains("%EventDateDuration%")) { message = message.Replace("%EventDateDuration%", FormatTimeString(eventDate - DateTime.Now, 3)); } if (message.Contains("%EventDateTime%")) { message = message.Replace("%EventDateTime%", eventDate.ToShortDateString() + " " + eventDate.ToShortTimeString()); } if (message.Contains("%EventDate%")) { message = message.Replace("%EventDate%", eventDate.ToShortDateString()); } if (message.Contains("%CurrentRound%")) { message = message.Replace("%CurrentRound%", String.Format("{0:n0}", _roundID)); } if (message.Contains("%EventRound%")) { if (_CurrentEventRoundNumber != 999999) { message = message.Replace("%EventRound%", String.Format("{0:n0}", _CurrentEventRoundNumber)); } else { message = message.Replace("%EventRound%", String.Format("{0:n0}", FetchEstimatedEventRoundNumber())); } } if (message.Contains("%RemainingRounds%")) { var remainingRounds = 0; if (_CurrentEventRoundNumber != 999999) { remainingRounds = _CurrentEventRoundNumber - _roundID; message = message.Replace("%RemainingRounds%", String.Format("{0:n0}", Math.Max(remainingRounds, 0))); message = message.Replace("%s%", remainingRounds > 1 ? "s" : ""); message = message.Replace("%S%", remainingRounds > 1 ? "S" : ""); } else { remainingRounds = FetchEstimatedEventRoundNumber() - _roundID; message = message.Replace("%RemainingRounds%", String.Format("{0:n0}", Math.Max(remainingRounds, 0))); message = message.Replace("%s%", remainingRounds > 1 ? "s" : ""); message = message.Replace("%S%", remainingRounds > 1 ? "S" : ""); } } } return message; } private void RunAutoAssistMonitor() { try { //Automatic Assisting Check - Every 500ms // Are there any records to process if (_roundState == RoundState.Playing && _AssistAttemptQueue.Any() && !_Team1MoveQueue.Any() && !_Team2MoveQueue.Any() && NowDuration(_LastAutoAssist).TotalSeconds > 10.0) { lock (_AssistAttemptQueue) { // There are, look at the first one without pulling it var assistRecord = _AssistAttemptQueue.Peek(); if (NowDuration(assistRecord.record_creationTime).TotalMinutes > 5.0) { // If the record is more than 5 minutes old, get rid of it SendMessageToSource(assistRecord, Log.CViolet("Automatic assist has timed out. Please use " + GetChatCommandByKey("self_assist") + " again to re-queue yourself.")); OnlineAdminSayMessage("Automatic assist timed out for " + assistRecord.GetSourceName()); _AssistAttemptQueue.Dequeue(); return; } else { // The record is active, see if the player can be automatically assisted if (RunAssist(assistRecord.target_player, assistRecord, null, true)) { QueueRecordForProcessing(assistRecord); _AssistAttemptQueue.Dequeue(); _LastAutoAssist = UtcNow(); return; } } var team1Assist = _AssistAttemptQueue.FirstOrDefault(attempt => attempt.source_player != null && attempt.source_player.fbpInfo != null && attempt.source_player.fbpInfo.TeamID == 1); var team2Assist = _AssistAttemptQueue.FirstOrDefault(attempt => attempt.source_player != null && attempt.source_player.fbpInfo != null && attempt.source_player.fbpInfo.TeamID == 2); if (team1Assist != null && team2Assist != null) { // There is a player on each team attempting to switch. Allow the swap. AdminSayMessage(Log.CViolet(team1Assist.GetTargetNames() + " (" + Math.Round(team1Assist.target_player.GetPower(true)) + ") assist SWAP accepted, queueing.")); QueueRecordForProcessing(team1Assist); AdminSayMessage(Log.CViolet(team2Assist.GetTargetNames() + " (" + Math.Round(team2Assist.target_player.GetPower(true)) + ") assist SWAP accepted, queueing.")); QueueRecordForProcessing(team2Assist); //The players are queued, rebuild the attempt queue without them in it _AssistAttemptQueue = new Queue(_AssistAttemptQueue.Where(aRec => aRec != team1Assist && aRec != team2Assist)); _LastAutoAssist = UtcNow(); } } } } catch (Exception e) { Log.HandleException(new AException("Error running autoassist monitor.", e)); } } private void RunPurgeMonitor() { try { //Keep the extended round stats table clean if (_pluginEnabled && _firstPlayerListComplete) { PurgeExtendedRoundStats(); PurgeOutdatedStatistics(); PurgeOutdatedExceptions(); } } catch (Exception e) { Log.HandleException(new AException("Error running purge monitor.", e)); } } private void RunAutomaticRestartMonitor() { try { //Check for automatic restart window if (_automaticServerRestart && _pluginEnabled && _threadsReady && _firstPlayerListComplete) { Boolean restart = true; var uptime = TimeSpan.FromSeconds(_serverInfo.InfoObject.ServerUptime); var uptimeString = FormatTimeString(uptime, 3); if (uptime.TotalHours < _automaticServerRestartMinHours) { restart = false; } if (restart && GetPlayerCount() >= 1) { restart = false; } if (restart) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("server_shutdown"), target_name = "Server", source_name = "AutoAdmin", record_message = "Automatic Server Restart [" + FormatTimeString(uptime, 3) + "]", record_time = UtcNow() }); } } } catch (Exception e) { Log.HandleException(new AException("Error running restart monitor.", e)); } } private void RunPingStatisticsMonitor() { try { if (_UseExperimentalTools && _roundState == RoundState.Playing) { var players = _PlayerDictionary.Values.ToList(); double total = players.Count(); if (total > 0) { double over50 = players.Count(aPlayer => aPlayer.player_ping_avg > 50); double over100 = players.Count(aPlayer => aPlayer.player_ping_avg > 100); double over150 = players.Count(aPlayer => aPlayer.player_ping_avg > 150); double over50p = Math.Round(over50 / total * 100, 1); double over100p = Math.Round(over100 / total * 100, 1); double over150p = Math.Round(over150 / total * 100, 1); string over100t = "Over 50ms: (" + Math.Round(over50) + "/" + total + ") " + over50p + "%"; string over150t = "Over 100ms: (" + Math.Round(over100) + "/" + total + ") " + over100p + "%"; string over200t = "Over 150ms: (" + Math.Round(over150) + "/" + total + ") " + over150p + "%"; QueueStatisticForProcessing(new AStatistic() { stat_type = AStatistic.StatisticType.ping_over50, server_id = _serverInfo.ServerID, round_id = _roundID, target_name = _serverInfo.InfoObject.Map, stat_value = over50p, stat_comment = over100t, stat_time = UtcNow() }); QueueStatisticForProcessing(new AStatistic() { stat_type = AStatistic.StatisticType.ping_over100, server_id = _serverInfo.ServerID, round_id = _roundID, target_name = _serverInfo.InfoObject.Map, stat_value = over100p, stat_comment = over150t, stat_time = UtcNow() }); QueueStatisticForProcessing(new AStatistic() { stat_type = AStatistic.StatisticType.ping_over150, server_id = _serverInfo.ServerID, round_id = _roundID, target_name = _serverInfo.InfoObject.Map, stat_value = over150p, stat_comment = over200t, stat_time = UtcNow() }); } } } catch (Exception e) { Log.HandleException(new AException("Error running pingstats monitor.", e)); } } private void RunPlayerListingMonitor() { try { //Player listing check if (_pluginEnabled && _firstPlayerListComplete && NowDuration(_LastPlayerListProcessed).TotalMinutes > 7.5) { //Create report record QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_report"), command_numeric = 0, target_name = "AdKats", source_name = "AdKats", record_message = "Player listing offline. Inform ColColonCleaner.", record_time = UtcNow() }); } } catch (Exception e) { Log.HandleException(new AException("Error running playerlist monitor.", e)); } } private void RunTeamPowerStatMonitor() { try { if (_UseTeamPowerDisplayBalance && _firstPlayerListComplete) { ATeam t1, t2; if (_roundState != RoundState.Loaded && GetTeamByID(1, out t1) && GetTeamByID(2, out t2)) { Double t1Power = t1.GetTeamPower(); Double t2Power = t2.GetTeamPower(); Double percDiff = Math.Abs(t1Power - t2Power) / ((t1Power + t2Power) / 2.0) * 100.0; String message = ""; if (t1Power > t2Power) { message += t1.GetTeamIDKey() + " up " + Math.Round(((t1Power - t2Power) / t2Power) * 100) + "% "; } else { message += t2.GetTeamIDKey() + " up " + Math.Round(((t2Power - t1Power) / t1Power) * 100) + "% "; } message += "^n(" + t1.TeamKey + ":" + t1.GetTeamPower() + ":" + t1.GetTeamPower(false) + " / " + t2.TeamKey + ":" + t2.GetTeamPower() + ":" + t2.GetTeamPower(false) + ")"; if (_PlayerDictionary.ContainsKey(_debugSoldierName)) { PlayerSayMessage(_debugSoldierName, Log.FBold(message)); } else if (GetPlayerCount() > 5) { ProconChatWrite(Log.FBold(message)); } } } } catch (Exception e) { Log.HandleException(new AException("Error running teampowerstat monitor.", e)); } } private void RunEventMonitor() { try { // EVENTS if (_pluginEnabled && _firstPlayerListComplete) { if (_EventWeeklyRepeat) { _EventDate = GetNextWeekday(DateTime.Now.Date, _EventWeeklyDay); if (GetEventRoundDateTime() < DateTime.Now) { // If the given event date is today, but is already in the past // reset it to the same day next week _EventDate = _EventDate.AddDays(7); } QueueSettingForUpload(new CPluginVariable(@"Event Date", typeof(String), _EventDate.ToShortDateString())); } if (_UseExperimentalTools && _EventDate.ToShortDateString() != GetLocalEpochTime().ToShortDateString()) { var eventDate = GetEventRoundDateTime(); if (DateTime.Now < eventDate && _CurrentEventRoundNumber == 999999) { // The event date is set, and in the future var estimateEventRoundNumber = FetchEstimatedEventRoundNumber(); // At 3 rounds away, lock in the round number for the event if (Math.Abs(estimateEventRoundNumber - _roundID) <= 3) { _CurrentEventRoundNumber = estimateEventRoundNumber; QueueSettingForUpload(new CPluginVariable(@"Event Current Round Number", typeof(Int32), _CurrentEventRoundNumber)); UpdateSettingPage(); } } var serverName = ""; // During the event if (EventActive()) { serverName = _eventActiveServerName + " " + GetEventMessage(false); } // Immediately before the event else if (_CurrentEventRoundNumber != 999999 && _CurrentEventRoundNumber > _roundID) { serverName = _eventConcreteCountdownServerName; } // Before the event else if (DateTime.Now < eventDate && Math.Abs((eventDate - DateTime.Now).TotalDays) < _EventAnnounceDayDifference) { serverName = _eventCountdownServerName; } //After the event, and otherwise else { serverName = _eventBaseServerName; } this.ExecuteCommand("procon.protected.send", "vars.serverName", ProcessEventServerName(serverName, false, false)); // EVENT AUTOMATIC POLLING if (_EventPollAutomatic && !_EventRoundPolled && // Don't auto-poll after 20 event rounds, just in case nobody votes to end it (_EventRoundOptions.Count() < 20 || !EventActive()) && _roundState == RoundState.Playing && _serverInfo.GetRoundElapsedTime() >= _EventRoundAutoVoteDuration && _ActivePoll == null && (_CurrentEventRoundNumber == _roundID + 1 || EventActive())) { var options = String.Empty; if (_CurrentEventRoundNumber == _roundID + 1) { options = "reset"; } QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("poll_trigger"), command_numeric = 0, target_name = "event", source_name = "EventAutoPolling", record_message = options, record_time = UtcNow() }); } } } } catch (Exception e) { Log.HandleException(new AException("Error running event monitor.", e)); } } private void RunTeamOperationMonitor() { try { //Team operations ATeam team1, team2, winningTeam, losingTeam, mapUpTeam, mapDownTeam; if (GetTeamByID(1, out team1) && GetTeamByID(2, out team2)) { if (team1.TeamTicketCount > team2.TeamTicketCount) { winningTeam = team1; losingTeam = team2; } else { winningTeam = team2; losingTeam = team1; } // If the mode is rush, the attackers are team 1, use that team for the extra seeder Boolean isRush = false; if (_serverInfo != null && _serverInfo.InfoObject != null && !String.IsNullOrEmpty(_serverInfo.InfoObject.GameMode)) { isRush = _serverInfo.InfoObject.GameMode.ToLower().Contains("rush"); } if (team1.GetTicketDifferenceRate() > team2.GetTicketDifferenceRate() || isRush) { //Team1 has more map than Team2 mapUpTeam = team1; mapDownTeam = team2; } else { //Team2 has more map than Team1 mapUpTeam = team2; mapDownTeam = team1; } if (_roundState == RoundState.Playing && _serverInfo.GetRoundElapsedTime().TotalMinutes > 5 && Math.Abs(winningTeam.TeamTicketCount - losingTeam.TeamTicketCount) > 100 && !_Team1MoveQueue.Any() && !_Team2MoveQueue.Any()) { //Auto-assist foreach (var aPlayer in GetOnlinePlayerDictionaryOfGroup("blacklist_autoassist").Values .Where(dPlayer => dPlayer.fbpInfo.TeamID == winningTeam.TeamID)) { var assistRecord = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("self_assist"), command_action = GetCommandByKey("self_assist_unconfirmed"), target_name = aPlayer.player_name, target_player = aPlayer, source_name = "AUAManager", record_message = "Auto-assist Weak Team [" + winningTeam.TeamTicketCount + ":" + losingTeam.TeamTicketCount + "][" + FormatTimeString(_serverInfo.GetRoundElapsedTime(), 3) + "]", record_time = UtcNow() }; if (RunAssist(assistRecord.target_player, assistRecord, null, true)) { QueueRecordForProcessing(assistRecord); _PlayersAutoAssistedThisRound = true; Thread.Sleep(2000); } } //Server seeder balance if (_UseTeamPowerMonitorSeeders && _PlayerDictionary.Any()) { var seeders = _PlayerDictionary.Values.ToList().Where(dPlayer => dPlayer.player_type == PlayerType.Player && NowDuration(dPlayer.lastAction).TotalMinutes > 20); if (seeders.Any()) { var mapUpSeeders = seeders.Where(aPlayer => aPlayer.fbpInfo.TeamID == mapUpTeam.TeamID); var mapDownSeeders = seeders.Where(aPlayer => aPlayer.fbpInfo.TeamID == mapDownTeam.TeamID); // This code is fired every 30 seconds // At that interval move players so either both teams have the same number of seeders, // or the map up team has 1 more seeder. if (mapDownSeeders.Count() > mapUpSeeders.Count()) { var aPlayer = mapDownSeeders.First(); aPlayer.RequiredTeam = mapUpTeam; Log.Debug(() => "MULTIBalancer Unswitcher Disabled", 3); ExecuteCommand("procon.protected.plugins.call", "MULTIbalancer", "UpdatePluginData", "AdKats", "bool", "DisableUnswitcher", "True"); _MULTIBalancerUnswitcherDisabled = true; ExecuteCommand("procon.protected.send", "admin.movePlayer", aPlayer.player_name, aPlayer.RequiredTeam.TeamID.ToString(), "0", "true"); } } } } } } catch (Exception e) { Log.HandleException(new AException("Error running teamoperation monitor.", e)); } } private void RunVOIPMonitor() { try { Boolean accessUpdateRequired = false; if (_TeamspeakPlayerMonitorEnable) { List onlineTeamspeakPlayers = new List(); //Check for online teamspeak players foreach (TeamSpeakClientViewer.TeamspeakClient client in _TeamspeakManager.GetPlayersOnTs()) { IEnumerable matching = _PlayerDictionary.Values.ToList().Where(dPlayer => // Match by IP or by name (only if no IP is available), percent matching over 80% ((!String.IsNullOrEmpty(client.AdvIpAddress) && !String.IsNullOrEmpty(dPlayer.player_ip) && client.AdvIpAddress == dPlayer.player_ip) || ((String.IsNullOrEmpty(client.AdvIpAddress) || String.IsNullOrEmpty(dPlayer.player_ip)) && Util.PercentMatch(client.TsName, dPlayer.player_name) > 80))); if (_TeamspeakManager.DebugClients) { Log.Info("TSClient: " + client.TsName + " | " + client.AdvIpAddress + " | " + ((matching.Any()) ? (matching.Count() + " online players match client.") : ("No matching online players."))); } foreach (var match in matching) { match.TSClientObject = client; } onlineTeamspeakPlayers.AddRange(matching); } List validTsPlayers = new List(); foreach (APlayer aPlayer in onlineTeamspeakPlayers) { validTsPlayers.Add(aPlayer.player_name); if (!_TeamspeakPlayers.ContainsKey(aPlayer.player_name)) { if (_TeamspeakManager.DebugClients) { Log.Success("Teamspeak soldier " + aPlayer.player_name + " connected."); } var startDuration = NowDuration(_AdKatsStartTime).TotalSeconds; var startupDuration = TimeSpan.FromSeconds(_startupDurations.Average(span => span.TotalSeconds)).TotalSeconds; if (startDuration - startupDuration > 120 && aPlayer.player_type != PlayerType.Spectator && NowDuration(aPlayer.VoipJoinTime).TotalMinutes > 15.0) { var playerName = aPlayer.player_name; var username = aPlayer.TSClientObject.TsName; var playerUsername = playerName + ( aPlayer.player_name.ToLower() != username.ToLower() && !aPlayer.player_name.ToLower().Contains(username.ToLower()) && !username.ToLower().Contains(aPlayer.player_name.ToLower()) ? " (" + username + ")" : ""); var joinMessage = _TeamspeakManager.JoinDisplayMessage.Replace("%player%", playerName).Replace("%username%", username).Replace("%playerusername%", playerUsername); switch (_TeamspeakManager.JoinDisplay) { case VoipJoinDisplayType.Say: AdminSayMessage(joinMessage); break; case VoipJoinDisplayType.Yell: AdminYellMessage(joinMessage); break; case VoipJoinDisplayType.Tell: AdminTellMessage(joinMessage); break; } aPlayer.VoipJoinTime = UtcNow(); } accessUpdateRequired = true; } _TeamspeakPlayers[aPlayer.player_name] = aPlayer; } foreach (string removePlayer in _TeamspeakPlayers.Keys.ToList().Where(key => !validTsPlayers.Contains(key)).ToList()) { if (_TeamspeakManager.DebugClients) { Log.Success("Teamspeak soldier " + removePlayer + " disconnected."); } accessUpdateRequired = true; _TeamspeakPlayers[removePlayer].TSClientObject = null; _TeamspeakPlayers.Remove(removePlayer); } } if (_pluginEnabled && _firstPlayerListComplete && _DiscordPlayerMonitorEnable && _DiscordPlayerMonitorView) { List onlineDiscordPlayers = new List(); //Check for online discord players var members = _DiscordManager.GetMembers(false, true, true); foreach (var member in members) { var matching = _PlayerDictionary.Values.ToList().Where(dPlayer => // Match by ID member.ID == dPlayer.player_discord_id); if (!matching.Any()) { // If there are no results by ID, do a name search matching = _PlayerDictionary.Values.ToList().Where(dPlayer => // Ignore any online players who already have a discord ID String.IsNullOrEmpty(dPlayer.player_discord_id) && // Make sure there are no players already given this ID member.PlayerObject == null && member.PlayerTested && // Match name, percent matching over 80% Util.PercentMatch(member.Name, dPlayer.player_name) > 80); } if (_DiscordManager.DebugMembers) { Log.Info("DiscordMember: " + member.Name + " | " + member.ID + " | " + ((matching.Any()) ? (matching.Count() + " online players match member.") : ("No matching online players."))); } foreach (var match in matching) { match.DiscordObject = member; // If their name is an exact match, assign the ID association if (match.player_name == member.Name && String.IsNullOrEmpty(match.player_discord_id)) { match.player_discord_id = member.ID; UpdatePlayer(match); } } onlineDiscordPlayers.AddRange(matching); } List validDiscordPlayers = new List(); foreach (APlayer aPlayer in onlineDiscordPlayers) { validDiscordPlayers.Add(aPlayer.player_name); if (!_DiscordPlayers.ContainsKey(aPlayer.player_name)) { if (_DiscordManager.DebugMembers) { Log.Success("Discord soldier " + aPlayer.player_name + " connected."); } var startDuration = NowDuration(_AdKatsStartTime).TotalSeconds; var startupDuration = TimeSpan.FromSeconds(_startupDurations.Average(span => span.TotalSeconds)).TotalSeconds; if (startDuration - startupDuration > 120 && aPlayer.player_type != PlayerType.Spectator && NowDuration(aPlayer.VoipJoinTime).TotalMinutes > 15.0) { var playerName = aPlayer.player_name; var username = aPlayer.DiscordObject.Name; var playerUsername = playerName + ( aPlayer.player_name.ToLower() != username.ToLower() && !aPlayer.player_name.ToLower().Contains(username.ToLower()) && !username.ToLower().Contains(aPlayer.player_name.ToLower()) ? " (" + username + ")" : ""); var joinMessage = _DiscordManager.JoinMessage.Replace("%player%", playerName).Replace("%username%", username).Replace("%playerusername%", playerUsername); switch (_DiscordManager.JoinDisplay) { case VoipJoinDisplayType.Say: AdminSayMessage(joinMessage); break; case VoipJoinDisplayType.Yell: AdminYellMessage(joinMessage); break; case VoipJoinDisplayType.Tell: AdminTellMessage(joinMessage); break; } aPlayer.VoipJoinTime = UtcNow(); } accessUpdateRequired = true; } _DiscordPlayers[aPlayer.player_name] = aPlayer; } foreach (string removePlayer in _DiscordPlayers.Keys.ToList().Where(key => !validDiscordPlayers.Contains(key)).ToList()) { if (_DiscordManager.DebugMembers) { Log.Success("Discord soldier " + removePlayer + " disconnected."); } accessUpdateRequired = true; _DiscordPlayers[removePlayer].DiscordObject = null; _DiscordPlayers.Remove(removePlayer); } } if (accessUpdateRequired) { FetchAllAccess(true); } } catch (Exception e) { Log.HandleException(new AException("Error running voip monitor.", e)); } } private void RunAFKMonitor() { try { //Perform AFK processing if (_AFKManagerEnable && _AFKAutoKickEnable && GetPlayerCount() > _AFKTriggerMinimumPlayers) { //Double list conversion List afkPlayers = _PlayerDictionary.Values.ToList().Where(aPlayer => (UtcNow() - aPlayer.lastAction).TotalMinutes > _AFKTriggerDurationMinutes && aPlayer.player_type != PlayerType.Spectator && !PlayerIsAdmin(aPlayer)).Take(_PlayerDictionary.Values.Count(aPlayer => aPlayer.player_type == PlayerType.Player) - _AFKTriggerMinimumPlayers).ToList(); if (_AFKIgnoreUserList) { IEnumerable userSoldierGuids = FetchAllUserSoldiers().Select(aPlayer => aPlayer.player_guid); afkPlayers = afkPlayers.Where(aPlayer => !userSoldierGuids.Contains(aPlayer.player_guid)).ToList(); } else { afkPlayers = afkPlayers.Where(aPlayer => !_AFKIgnoreRoles.Contains(aPlayer.player_role.role_key) && !_AFKIgnoreRoles.Contains(aPlayer.player_role.role_name) && !_AFKIgnoreRoles.Contains(aPlayer.player_role.role_id.ToString()) && !_AFKIgnoreRoles.Contains("RLE" + aPlayer.player_role.role_id.ToString())).ToList(); } foreach (APlayer aPlayer in afkPlayers) { String afkTime = FormatTimeString(UtcNow() - aPlayer.lastAction, 2); Log.Debug(() => "Kicking " + aPlayer.player_name + " for being AFK " + afkTime + ".", 3); ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_kick"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "AFKManager", record_message = "AFK time exceeded [" + afkTime + "/" + GetPlayerTeamKey(aPlayer) + "]. Please rejoin once you return.", record_time = UtcNow() }; QueueRecordForProcessing(record); //Only take one break; } } } catch (Exception e) { Log.HandleException(new AException("Error running AFK monitor.", e)); } } private void RunNukeAnnounceMonitor() { try { //Nuke Countdowns - Every 50ms if (_lastNukeTeam != null) { //Auto-Nuke Slay Duration var duration = NowDuration(_lastNukeTime); var nukeInfoMessage = ""; var durationIncrease = 0; ATeam team1, team2; if (!_nukeAutoSlayActive && GetTeamByID(1, out team1) && GetTeamByID(2, out team2)) { if (Math.Abs(team1.TeamTicketCount - team2.TeamTicketCount) > _surrenderAutoNukeDurationIncreaseTicketDiff) { durationIncrease = _surrenderAutoNukeDurationIncrease * Math.Max(getNukeCount(_lastNukeTeam.TeamID) - 1, 0); } switch (_populationStatus) { case PopulationState.High: if (_surrenderAutoNukeDurationHigh + durationIncrease > 60) { durationIncrease = Math.Max(0, 60 - _surrenderAutoNukeDurationHigh); } _nukeAutoSlayActiveDuration = _surrenderAutoNukeDurationHigh + durationIncrease; nukeInfoMessage = "High population nuke: " + _surrenderAutoNukeDurationHigh + (durationIncrease > 0 ? " + " + durationIncrease : "") + " seconds."; break; case PopulationState.Medium: if (_surrenderAutoNukeDurationMed + durationIncrease > 45) { durationIncrease = Math.Max(0, 45 - _surrenderAutoNukeDurationMed); } _nukeAutoSlayActiveDuration = _surrenderAutoNukeDurationMed + durationIncrease; nukeInfoMessage = "Medium population nuke: " + _surrenderAutoNukeDurationMed + (durationIncrease > 0 ? " + " + durationIncrease : "") + " seconds."; break; case PopulationState.Low: if (_surrenderAutoNukeDurationLow + durationIncrease > 30) { durationIncrease = Math.Max(0, 30 - _surrenderAutoNukeDurationLow); } _nukeAutoSlayActiveDuration = _surrenderAutoNukeDurationLow + durationIncrease; nukeInfoMessage = "Low population nuke: " + _surrenderAutoNukeDurationLow + (durationIncrease > 0 ? " + " + durationIncrease : "") + " seconds."; break; } } if (_nukeAutoSlayActiveDuration > 0) { if (duration.TotalSeconds < _nukeAutoSlayActiveDuration && _roundState == RoundState.Playing) { if (!_nukeAutoSlayActive) { AdminSayMessage(nukeInfoMessage); } _nukeAutoSlayActive = true; Double endDuration = NowDuration(_lastNukeTime.AddSeconds(_nukeAutoSlayActiveDuration)).TotalSeconds; Int32 endDurationSeconds = (Int32)Math.Round(endDuration); String endDurationString = endDurationSeconds.ToString(); var durationMessage = _lastNukeTeam.TeamKey + " nuke active for " + endDurationString + " seconds!"; if (_lastNukeSlayDurationMessage != durationMessage && endDurationSeconds > 0 && (endDurationSeconds % 2 == 0 || endDuration <= 5)) { AdminTellMessage(durationMessage); _lastNukeSlayDurationMessage = durationMessage; } } else if (_nukeAutoSlayActive) { _nukeAutoSlayActive = false; _nukeAutoSlayActiveDuration = 0; AdminTellMessage(_lastNukeTeam.TeamKey + " nuke has ended!"); } } } } catch (Exception e) { Log.HandleException(new AException("Error running nukeannounce monitor.", e)); } } private void RunTeamPowerScramblerMonitor() { try { if (_UseTeamPowerMonitorScrambler && _serverInfo != null && !_ScrambleRequiredTeamsRemoved && _roundState == RoundState.Playing && _serverInfo.GetRoundElapsedTime().TotalMinutes > 2) { // Clear all required teams/squads 2 minutes into the round so the regular balancer can take over foreach (APlayer aPlayer in GetFetchedPlayers().Where(aPlayer => aPlayer.RequiredTeam != null)) { aPlayer.RequiredTeam = null; aPlayer.RequiredSquad = -1; } _ScrambleRequiredTeamsRemoved = true; } } catch (Exception e) { Log.HandleException(new AException("Error running team power scrambler monitor.", e)); } } private void RunChallengeMonitor() { try { if (ChallengeManager != null) { // Fail challenges as necessary var roundEntries = ChallengeManager.GetEntries().Where(entry => !entry.Completed && !entry.Failed && !entry.Canceled); foreach (var entry in roundEntries) { entry.CheckFailure(); } } } catch (Exception e) { Log.HandleException(new AException("Error running challenge monitor.", e)); } } private void RunReportMonitor() { try { if (_PlayerReports.Any()) { foreach (var report in FetchActivePlayerReports()) { FetchRecordUpdate(report); } } } catch (Exception e) { Log.HandleException(new AException("Error running report monitor.", e)); } } private void SetupStatusMonitor() { //Create a new thread to handle keep-alive //This thread will remain running for the duration the layer is online Thread statusMonitorThread = new Thread(new ThreadStart(delegate { try { Thread.CurrentThread.Name = "StatusMonitor"; DoServerInfoTrigger(); while (true) { try { RunMemoryMonitor(); RunPlayerListingStatMonitor(); RunUnswitcherMonitor(); RunDocumentationMonitor(); RunSpambotMonitor(); RunAutoAssistMonitor(); RunTeamPowerScramblerMonitor(); //Prune Watchdog Threads Threading.Prune(); //Batch very long keep alive - every 10 minutes if (NowDuration(_LastVeryLongKeepAliveCheck).TotalMinutes > 10.0) { RunPurgeMonitor(); FixInvalidCommandIds(); RunAutomaticRestartMonitor(); _LastVeryLongKeepAliveCheck = UtcNow(); } //Batch long keep alive - every 5 minutes if ((UtcNow() - _LastLongKeepAliveCheck).TotalMinutes > 5) { RunPingStatisticsMonitor(); _LastLongKeepAliveCheck = UtcNow(); } //Batch short keep alive - every 30 seconds if ((UtcNow() - _LastShortKeepAliveCheck).TotalSeconds > 30) { RunPlayerListingMonitor(); RunTeamPowerStatMonitor(); RunEventMonitor(); RunTeamOperationMonitor(); RunVOIPMonitor(); RunAFKMonitor(); RunChallengeMonitor(); RunReportMonitor(); if (_pluginEnabled && _threadsReady && _firstPlayerListComplete && _enforceSingleInstance) { AdminSayMessage("/AdKatsInstanceCheck " + _instanceKey + " " + Math.Round((UtcNow() - _AdKatsRunningTime).TotalSeconds), false); } //Enable if auto-enable wanted if (_useKeepAlive && !_pluginEnabled) { Enable(); } //Check for thread warning Threading.Monitor(); _LastShortKeepAliveCheck = UtcNow(); } //Server info fetch - every 5 seconds if (_threadsReady && NowDuration(_LastServerInfoReceive).TotalSeconds > 4.5 && NowDuration(_LastServerInfoTrigger).TotalSeconds > 4.5) { DoServerInfoTrigger(); } //Player Info fetch - every 5 seconds if (_threadsReady && NowDuration(_LastPlayerListAccept).TotalSeconds > 4.5 && NowDuration(_LastPlayerListTrigger).TotalSeconds > 4.5) { DoPlayerListTrigger(); } if (_pluginEnabled) { //Sleep 500ms between loops Thread.Sleep(TimeSpan.FromMilliseconds(500)); } else { //Sleep 1000ms between loops Thread.Sleep(TimeSpan.FromMilliseconds(2000)); } } catch (Exception e) { Log.HandleException(new AException("Error in status monitor. Skipping current loop.", e)); } } } catch (Exception e) { Log.HandleException(new AException("Error while running status monitor.", e)); } })); //Start the thread statusMonitorThread.Start(); } private void SetupFastStatusMonitor() { //This thread will remain running for the duration the layer is online Thread fastStatusMonitorThread = new Thread(new ThreadStart(delegate { try { Thread.CurrentThread.Name = "FastStatusMonitor"; while (true) { try { RunNukeAnnounceMonitor(); if (_pluginEnabled) { //Sleep 50ms between loops Thread.Sleep(TimeSpan.FromMilliseconds(50)); } else { //Sleep 1000ms between loops Thread.Sleep(TimeSpan.FromMilliseconds(1000)); } } catch (Exception e) { Log.HandleException(new AException("Error in fast status monitor. Skipping current loop.", e)); } } } catch (Exception e) { Log.HandleException(new AException("Error while running fast status monitor.", e)); } })); //Start the thread fastStatusMonitorThread.Start(); } public void InitWaitHandles() { //Initializes all wait handles Threading.Init(); _TeamswapWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); _PlayerProcessingWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); _AccessFetchWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); _KillProcessingWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); _PlayerListUpdateWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); _MessageParsingWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); _CommandParsingWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); _DbCommunicationWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); _ActionHandlingWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); _BanEnforcerWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); _AntiCheatWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); _ServerInfoWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); _StatLoggerStatusWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); _PluginDescriptionWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); _BattlelogCommWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); } public void OpenAllHandles() { Threading.Set(); _TeamswapWaitHandle.Set(); _PlayerProcessingWaitHandle.Set(); _AccessFetchWaitHandle.Set(); _KillProcessingWaitHandle.Set(); _PlayerListUpdateWaitHandle.Set(); _MessageParsingWaitHandle.Set(); _CommandParsingWaitHandle.Set(); _DbCommunicationWaitHandle.Set(); _ActionHandlingWaitHandle.Set(); _BanEnforcerWaitHandle.Set(); _AntiCheatWaitHandle.Set(); _ServerInfoWaitHandle.Set(); _StatLoggerStatusWaitHandle.Set(); _BattlelogCommWaitHandle.Set(); _EmailHandler._EmailProcessingWaitHandle.Set(); } public void InitThreads() { try { //Creats all threads with their starting methods and set to run in the background _PlayerListingThread = new Thread(PlayerListingThreadLoop) { IsBackground = true }; _AccessFetchingThread = new Thread(AccessFetchingThreadLoop) { IsBackground = true }; _KillProcessingThread = new Thread(KillProcessingThreadLoop) { IsBackground = true }; _MessageProcessingThread = new Thread(MessagingThreadLoop) { IsBackground = true }; _CommandParsingThread = new Thread(CommandParsingThreadLoop) { IsBackground = true }; _DatabaseCommunicationThread = new Thread(DatabaseCommunicationThreadLoop) { IsBackground = true }; _ActionHandlingThread = new Thread(ActionHandlingThreadLoop) { IsBackground = true }; _TeamSwapThread = new Thread(TeamswapThreadLoop) { IsBackground = true }; _BanEnforcerThread = new Thread(BanEnforcerThreadLoop) { IsBackground = true }; _AntiCheatThread = new Thread(AntiCheatThreadLoop) { IsBackground = true }; _BattlelogCommThread = new Thread(BattlelogCommThreadLoop) { IsBackground = true }; _IPAPICommThread = new Thread(IPAPICommThreadLoop) { IsBackground = true }; } catch (Exception e) { Log.HandleException(new AException("Error occured while initializing threads.", e)); } } public void StartThreads() { Log.Debug(() => "Entering StartThreads", 7); try { //Start the main thread OnlineAdminSayMessage("AdKats starting."); //Reset the master wait handle Threading.Reset(); //DB Comm is the heart of AdKats, everything revolves around that thread Threading.StartWatchdog(_DatabaseCommunicationThread); //Battlelog comm and IP API threads are independant Threading.StartWatchdog(_BattlelogCommThread); Threading.StartWatchdog(_IPAPICommThread); //Other threads are started within the db comm thread } catch (Exception e) { Log.HandleException(new AException("Error while starting processing threads.", e)); } Log.Debug(() => "Exiting StartThreads", 7); } private void Disable() { //Call Disable ExecuteCommand("procon.protected.plugins.enable", "AdKats", "False"); //Set enabled false so threads begin exiting _pluginEnabled = false; _threadsReady = false; } private void Enable() { if (Thread.CurrentThread.Name == "Finalizer") { Thread pluginRebootThread = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a reboot thread.", 5); try { Thread.CurrentThread.Name = "Reboot"; Thread.Sleep(1000); //Call Enable ExecuteCommand("procon.protected.plugins.enable", "AdKats", "True"); } catch (Exception) { Log.HandleException(new AException("Error while running reboot.")); } Log.Debug(() => "Exiting a reboot thread.", 5); Threading.StopWatchdog(); })); Threading.StartWatchdog(pluginRebootThread); } else { //Call Enable ExecuteCommand("procon.protected.plugins.enable", "AdKats", "True"); } } public void OnPluginLoadingEnv(List lstPluginEnv) { foreach (String env in lstPluginEnv) { Log.Debug(() => "^9OnPluginLoadingEnv: " + env, 7); } switch (lstPluginEnv[1]) { case "BF3": GameVersion = GameVersionEnum.BF3; break; case "BF4": GameVersion = GameVersionEnum.BF4; break; case "BFHL": GameVersion = GameVersionEnum.BFHL; break; } Log.Success("^1Game Version: " + GameVersion); //Initialize the Email Handler _EmailHandler = new EmailHandler(this); //Initialize PushBullet Handler _PushBulletHandler = new PushBulletHandler(this); } public override void OnVersion(String serverType, String version) { _serverInfo.GamePatchVersion = version; } public override void OnTeamFactionOverride(Int32 targetTeamID, Int32 overrideTeamId) { if (!_acceptingTeamUpdates) { return; } try { switch (overrideTeamId) { case -1: //Check for already existing Neutral team if (_serverInfo.GetRoundElapsedTime().TotalSeconds > 20 && _teamDictionary.ContainsKey(targetTeamID) && _teamDictionary[targetTeamID].TeamKey == "Neutral") { Log.Debug(() => "Neutral Team already set for team " + targetTeamID + ", cancelling override.", 4); break; } _teamDictionary[targetTeamID] = new ATeam(this, targetTeamID, "Neutral", "Neutral Team", "Neutral Team"); Log.Debug(() => "Assigning team ID " + targetTeamID + " to Neutral ", 4); break; case 0: switch (GameVersion) { case GameVersionEnum.BF3: case GameVersionEnum.BF4: //Check for already existing US team if (_serverInfo.GetRoundElapsedTime().TotalSeconds > 20 && _teamDictionary.ContainsKey(targetTeamID) && _teamDictionary[targetTeamID].TeamKey == "US") { Log.Debug(() => "Team US already set for team " + targetTeamID + ", cancelling override.", 4); break; } _teamDictionary[targetTeamID] = new ATeam(this, targetTeamID, "US", "US Army", "United States Army"); Log.Debug(() => "Assigning team ID " + targetTeamID + " to US ", 4); break; case GameVersionEnum.BFHL: //Check for already existing US team if (_serverInfo.GetRoundElapsedTime().TotalSeconds > 20 && _teamDictionary.ContainsKey(targetTeamID) && _teamDictionary[targetTeamID].TeamKey == "Cops") { Log.Debug(() => "Team Cops already set for team " + targetTeamID + ", cancelling override.", 4); break; } _teamDictionary[targetTeamID] = new ATeam(this, targetTeamID, "LE", "Cops", "Law Enforcement"); Log.Debug(() => "Assigning team ID " + targetTeamID + " to Cops ", 4); break; } break; case 1: switch (GameVersion) { case GameVersionEnum.BF3: case GameVersionEnum.BF4: //Check for already existing RU team if (_serverInfo.GetRoundElapsedTime().TotalSeconds > 20 && _teamDictionary.ContainsKey(targetTeamID) && _teamDictionary[targetTeamID].TeamKey == "RU") { Log.Debug(() => "Team RU already set for team " + targetTeamID + ", cancelling override.", 4); break; } _teamDictionary[targetTeamID] = new ATeam(this, targetTeamID, "RU", "Russian Army", "Russian Federation Army"); Log.Debug(() => "Assigning team ID " + targetTeamID + " to RU", 4); break; case GameVersionEnum.BFHL: //Check for already existing RU team if (_serverInfo.GetRoundElapsedTime().TotalSeconds > 20 && _teamDictionary.ContainsKey(targetTeamID) && _teamDictionary[targetTeamID].TeamKey == "Crims") { Log.Debug(() => "Team Crims already set for team " + targetTeamID + ", cancelling override.", 4); break; } _teamDictionary[targetTeamID] = new ATeam(this, targetTeamID, "CR", "Crims", "Criminals"); Log.Debug(() => "Assigning team ID " + targetTeamID + " to Crims", 4); break; } break; case 2: switch (GameVersion) { case GameVersionEnum.BF3: case GameVersionEnum.BF4: //Check for already existing CN team if (_serverInfo.GetRoundElapsedTime().TotalSeconds > 20 && _teamDictionary.ContainsKey(targetTeamID) && _teamDictionary[targetTeamID].TeamKey == "CN") { Log.Debug(() => "Team CN already set for team " + targetTeamID + ", cancelling override.", 4); break; } _teamDictionary[targetTeamID] = new ATeam(this, targetTeamID, "CN", "Chinese Army", "Chinese People's Liberation Army"); Log.Debug(() => "Assigning team ID " + targetTeamID + " to CN", 4); break; default: Log.Error("Attempted to use team key 2 on non-BF3/BF4 server."); break; } break; default: Log.Error("Team ID " + overrideTeamId + " was not understood."); break; } } catch (Exception e) { Log.HandleException(new AException("Error while processing team faction override.", e)); } } public override void OnFairFight(bool isEnabled) { _serverInfo.FairFightEnabled = isEnabled; } public override void OnIsHitIndicator(bool isEnabled) { _serverInfo.HitIndicatorEnabled = isEnabled; } public override void OnCommander(bool isEnabled) { _serverInfo.CommanderEnabled = isEnabled; } public override void OnForceReloadWholeMags(bool isEnabled) { _serverInfo.ForceReloadWholeMags = isEnabled; } public override void OnServerType(String serverType) { _serverInfo.ServerType = serverType; } public override void OnGameAdminLoad() { Log.Info("OnGameAdminLoad"); } public override void OnGameAdminSave() { Log.Info("OnGameAdminSave"); } public override void OnGameAdminPlayerAdded(String soldierName) { Log.Info("OnGameAdminPlayerAdded " + soldierName); } public override void OnGameAdminPlayerRemoved(String soldierName) { Log.Info("OnGameAdminPlayerRemoved " + soldierName); } public override void OnGameAdminCleared() { Log.Info("OnGameAdminCleared"); } public override void OnGameAdminList(List soldierNames) { foreach (string soldierName in soldierNames) { Log.Info("OnGameAdminList " + soldierName); } } public void UpdateFactions() { try { _acceptingTeamUpdates = true; _teamDictionary.Clear(); _teamDictionary[0] = new ATeam(this, 0, "Neutral", "Neutrals", "Neutral Players"); if (GameVersion == GameVersionEnum.BF3) { OnTeamFactionOverride(1, 0); OnTeamFactionOverride(2, 1); OnTeamFactionOverride(3, 0); OnTeamFactionOverride(4, 1); _acceptingTeamUpdates = false; } else if (GameVersion == GameVersionEnum.BF4) { Log.Debug(() => "Assigning team ID " + 0 + " to Spectator", 4); Thread.Sleep(500); ExecuteCommand("procon.protected.send", "vars.teamFactionOverride"); //Wait for proper team overrides to complete if (!Threading.IsAlive("TeamAssignmentConfirmation")) { Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "TeamAssignmentConfirmation"; Thread.Sleep(TimeSpan.FromSeconds(1)); DateTime starting = UtcNow(); while (true) { if (!_pluginEnabled) { break; } if ((UtcNow() - starting).TotalSeconds > 30) { Log.Warn("TeamAssignmentConfirmation took too long."); break; } if (!_teamDictionary.ContainsKey(1) || !_teamDictionary.ContainsKey(2) || !_teamDictionary.ContainsKey(3) || !_teamDictionary.ContainsKey(4)) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); continue; } _acceptingTeamUpdates = false; break; } Threading.StopWatchdog(); }))); } } else if (GameVersion == GameVersionEnum.BFHL) { Log.Debug(() => "Assigning team ID " + 0 + " to Spectator", 4); OnTeamFactionOverride(1, 0); OnTeamFactionOverride(2, 1); _acceptingTeamUpdates = false; } //Team power monitor assignment code if (_UseTeamPowerMonitorScrambler && _firstPlayerListComplete && _populationStatus != PopulationState.Low && !Threading.IsAlive("TeamPowerMonitorAssignment")) { Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "TeamPowerMonitorAssignment"; Thread.Sleep(TimeSpan.FromSeconds(0.1)); DateTime starting = UtcNow(); List playerList; while (true) { if (!_pluginEnabled) { break; } if ((UtcNow() - starting).TotalSeconds > 30) { Log.Warn("TeamPowerMonitorAssignment took too long."); break; } if (_acceptingTeamUpdates) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); continue; } ATeam team1, team2; if (!GetTeamByID(1, out team1)) { Log.Info("Team 1 was not found, waiting."); Thread.Sleep(TimeSpan.FromSeconds(0.5)); continue; } if (!GetTeamByID(2, out team2)) { Log.Info("Team 2 was not found, waiting."); Thread.Sleep(TimeSpan.FromSeconds(0.5)); continue; } if (_RoundPrepSquads.Count() < 1) { Log.Warn("No squads were stored from the previous round!"); } // Remove players from stored squads who have left the server foreach (var squad in _RoundPrepSquads.ToList()) { foreach (var aPlayer in squad.Players.ToList()) { if (!_PlayerDictionary.ContainsKey(aPlayer.player_name)) { Log.Info("Removing " + aPlayer.player_name + " from stored squads, as they have left the server."); squad.Players.Remove(aPlayer); } } // If the squad contains 1 or fewer players, disband the squad so the player is available for other squads if (squad.Players.Count() <= 1) { _RoundPrepSquads.Remove(squad); } } Log.Info(_RoundPrepSquads.Count() + " squads ready for dispersion."); // Print the squad list // Remove team IDs from the squads foreach (var squad in _RoundPrepSquads.OrderBy(squad => squad.TeamID).ThenByDescending(squad => squad.Players.Sum(member => member.GetPower(true)))) { Log.Info("Squad " + squad); squad.TeamID = 0; squad.SquadID = 0; } // Alternate between team 1 and 2 for dispersion every round // This decides where the first (most powerful) squad is sent during dispersion var requiredTeam = true; //Decide which teams the squads should be on foreach (var aSquad in _RoundPrepSquads.OrderByDescending(squad => squad.Players.Sum(member => member.GetPower(true)))) { var team1Squads = _RoundPrepSquads.Where(dSquad => dSquad.TeamID == team1.TeamID).ToList(); var team1Count = team1Squads.Sum(dSquad => dSquad.Players.Count()); var team1Power = team1Squads.Sum(dSquad => dSquad.Players.Sum(member => member.GetPower(true))); var team2Squads = _RoundPrepSquads.Where(dSquad => dSquad.TeamID == team2.TeamID).ToList(); var team2Count = team2Squads.Sum(dSquad => dSquad.Players.Count()); var team2Power = team2Squads.Sum(dSquad => dSquad.Players.Sum(member => member.GetPower(true))); // Assume max team size of 32 unless otherwise provided var maxTeamPlayerCount = 32; if (_serverInfo.InfoObject != null && _serverInfo.InfoObject.MaxPlayerCount != maxTeamPlayerCount) { maxTeamPlayerCount = _serverInfo.InfoObject.MaxPlayerCount / 2; } var team1Available = true; if (team1Count + aSquad.Players.Count() > maxTeamPlayerCount) { Log.Info("Cannot assign " + aSquad + " to team 1, max player count would be exceeded."); team1Available = false; } var team2Available = true; if (team2Count + aSquad.Players.Count() > maxTeamPlayerCount) { Log.Info("Cannot assign " + aSquad + " to team 2, max player count would be exceeded."); team2Available = false; } if (!team1Available && !team2Available) { Log.Error("Major failure, both teams would be over capacity when assigning " + aSquad); } else { Log.Info("Power: 1:" + team1Count + ":" + Math.Round(team1Power) + " | 2:" + team2Count + ":" + Math.Round(team2Power)); if (requiredTeam) { if (currentStartingTeam1) { Log.Success("First squad required to be team 1."); AssignTeam(aSquad, team1, team1Squads); } else { Log.Success("First squad required to be team 2."); AssignTeam(aSquad, team2, team2Squads); } } else { if (team1Power + aSquad.GetPower() <= team2Power || !team2Available) { AssignTeam(aSquad, team1, team1Squads); } else { AssignTeam(aSquad, team2, team2Squads); } } } requiredTeam = false; } // Toggle the starting team value currentStartingTeam1 = !currentStartingTeam1; // Merge anyone currently not in a squad into an existing squad // If more squads are needed, create them Log.Info("Merging remaining players into " + _RoundPrepSquads.Count() + " available squads."); // Only grab players who have a valid team and are of the player type foreach (var aPlayer in _PlayerDictionary.Values.ToList().Where(dPlayer => (dPlayer.fbpInfo.TeamID == team1.TeamID || dPlayer.fbpInfo.TeamID == team2.TeamID) && dPlayer.player_type == PlayerType.Player)) { // See if this player is in an existing squad Boolean found = false; foreach (var aSquad in _RoundPrepSquads) { if (aSquad.Players.Contains(aPlayer)) { found = true; break; } } if (!found) { Boolean added = false; // Only add players to squads on the weak team var team1Squads = _RoundPrepSquads.Where(dSquad => dSquad.TeamID == team1.TeamID).ToList(); var team1Count = team1Squads.Sum(dSquad => dSquad.Players.Count()); var team1Power = team1Squads.Sum(dSquad => dSquad.Players.Sum(member => member.GetPower(true))); var team2Squads = _RoundPrepSquads.Where(dSquad => dSquad.TeamID == team2.TeamID).ToList(); var team2Count = team2Squads.Sum(dSquad => dSquad.Players.Count()); var team2Power = team2Squads.Sum(dSquad => dSquad.Players.Sum(member => member.GetPower(true))); // Assume max team size of 32 unless otherwise provided var maxTeamPlayerCount = 32; if (_serverInfo.InfoObject != null && _serverInfo.InfoObject.MaxPlayerCount != maxTeamPlayerCount) { maxTeamPlayerCount = _serverInfo.InfoObject.MaxPlayerCount / 2; } var team1Available = true; if (team1Count + 1 > maxTeamPlayerCount) { Log.Info("Cannot assign " + aPlayer.player_name + " to team 1, max player count would be exceeded."); team1Available = false; } var team2Available = true; if (team2Count + 1 > maxTeamPlayerCount) { Log.Info("Cannot assign " + aPlayer.player_name + " to team 2, max player count would be exceeded."); team2Available = false; } var chosenTeam = 0; if (!team1Available && !team2Available) { Log.Error("Major failure, both teams would be over capacity when assigning " + aPlayer.player_name); } else { Log.Info("Power: 1:" + team1Count + ":" + Math.Round(team1Power) + " | 2:" + team2Count + ":" + Math.Round(team2Power)); if (team1Power + aPlayer.GetPower(true) <= team2Power || !team2Available) { chosenTeam = team1.TeamID; } else { chosenTeam = team2.TeamID; } // Add to the weakest squads first foreach (var aSquad in _RoundPrepSquads .Where(aSquad => aSquad.TeamID == chosenTeam) .OrderBy(dSquad => dSquad.Players.Sum(member => member.GetPower(true)))) { if (aSquad.Players.Count() < (GameVersion == GameVersionEnum.BF3 ? 4 : 5)) { Log.Info("Adding " + aPlayer.player_name + " to squad " + aSquad); aSquad.Players.Add(aPlayer); added = true; break; } } if (!added) { Log.Info("No squads available for " + aPlayer.player_name + ", creating new squad."); var newSquad = new ASquad(this) { TeamID = chosenTeam // Squad ID defaults to 0 }; newSquad.Players.Add(aPlayer); _RoundPrepSquads.Add(newSquad); } } } } var t1Squads = _RoundPrepSquads.Where(dSquad => dSquad.TeamID == team1.TeamID).ToList(); var t1Count = t1Squads.Sum(dSquad => dSquad.Players.Count()); var t1Power = t1Squads.Sum(dSquad => dSquad.Players.Sum(member => member.GetPower(true))); var t2Squads = _RoundPrepSquads.Where(dSquad => dSquad.TeamID == team2.TeamID).ToList(); var t2Count = t2Squads.Sum(dSquad => dSquad.Players.Count()); var t2Power = t2Squads.Sum(dSquad => dSquad.Players.Sum(member => member.GetPower(true))); // Fix the team distribution counts if needed if (Math.Abs(t1Count - t2Count) > 2) { if (t1Count > t2Count) { // Team 1 needs a bad squad moved to team 2 var worstSquad = t1Squads.OrderBy(dSquad => dSquad.Players.Sum(member => member.GetPower(true))).FirstOrDefault(); worstSquad.TeamID = team2.TeamID; Log.Info("REASSIGNED SQUAD TO TEAM 2: " + worstSquad); } else { // Team 2 needs a bad squad moved to team 1 var worstSquad = t2Squads.OrderBy(dSquad => dSquad.Players.Sum(member => member.GetPower(true))).FirstOrDefault(); worstSquad.TeamID = team1.TeamID; Log.Info("REASSIGNED SQUAD TO TEAM 1: " + worstSquad); } } t1Squads = _RoundPrepSquads.Where(dSquad => dSquad.TeamID == team1.TeamID).ToList(); t1Count = t1Squads.Sum(dSquad => dSquad.Players.Count()); t1Power = t1Squads.Sum(dSquad => dSquad.Players.Sum(member => member.GetPower(true))); t2Squads = _RoundPrepSquads.Where(dSquad => dSquad.TeamID == team2.TeamID).ToList(); t2Count = t2Squads.Sum(dSquad => dSquad.Players.Count()); t2Power = t2Squads.Sum(dSquad => dSquad.Players.Sum(member => member.GetPower(true))); Double percDiff = Math.Abs(t1Power - t2Power) / ((t1Power + t2Power) / 2.0) * 100.0; String message = ""; if (t1Power > t2Power) { message += "Team 1 up " + Math.Round(((t1Power - t2Power) / t2Power) * 100) + "% "; } else { message += "Team 2 up " + Math.Round(((t2Power - t1Power) / t1Power) * 100) + "% "; } message += "(1:" + t1Count + ":" + Math.Round(t1Power, 2) + " / 2:" + t2Count + ":" + Math.Round(t2Power, 2) + ")"; Log.Info("Team Power Dispersion: " + message); // Print the final team squad lists foreach (var squad in _RoundPrepSquads.OrderBy(squad => squad.TeamID).ThenByDescending(squad => squad.Players.Sum(member => member.GetPower(true)))) { Log.Info("Squad " + squad); } var movesToTeam1 = new Queue(); var movesToTeam2 = new Queue(); // Build the list of player moves to satisfy the decisions foreach (var squad in _RoundPrepSquads.ToList()) { foreach (var aPlayer in squad.Players.ToList()) { var move = new AMove() { Player = aPlayer, Squad = squad }; if (squad.TeamID == team1.TeamID) { movesToTeam1.Enqueue(move); } else if (squad.TeamID == team2.TeamID) { movesToTeam2.Enqueue(move); } else { Log.Error("Invalid team ID when building move list."); } } } // Build the move queue such that we don't try to move to a full team var currentTeam1Count = GetPlayerCount(true, true, true, 1); var currentTeam2Count = GetPlayerCount(true, true, true, 2); var moveList = new Queue(); while (movesToTeam1.Any() || movesToTeam2.Any()) { if (movesToTeam1.Any() && movesToTeam2.Any()) { // Both teams have available moves if (currentTeam1Count <= currentTeam2Count) { moveList.Enqueue(movesToTeam1.Dequeue()); currentTeam1Count++; } else { moveList.Enqueue(movesToTeam2.Dequeue()); currentTeam2Count++; } } else if (movesToTeam1.Any()) { // Only moves to team 1 are left moveList.Enqueue(movesToTeam1.Dequeue()); currentTeam1Count++; } else { // Only moves to team 2 are left moveList.Enqueue(movesToTeam2.Dequeue()); currentTeam2Count++; } } Log.Debug(() => "MULTIBalancer Unswitcher Disabled", 3); ExecuteCommand("procon.protected.plugins.call", "MULTIbalancer", "UpdatePluginData", "AdKats", "bool", "DisableUnswitcher", "True"); _MULTIBalancerUnswitcherDisabled = true; playerList = _PlayerDictionary.Values.ToList(); Log.Success("Built move queue."); Log.Info("Clearing squads."); foreach (var aPlayer in playerList.Where(dPlayer => dPlayer.player_type == PlayerType.Player)) { ExecuteCommand("procon.protected.send", "admin.movePlayer", aPlayer.player_name, aPlayer.fbpInfo.TeamID.ToString(), "0", "true"); Thread.Sleep(20); } Log.Success("Squads cleared."); Log.Info("Moving teams."); foreach (var aMove in moveList.ToList()) { ExecuteCommand("procon.protected.send", "admin.movePlayer", aMove.Player.player_name, aMove.Squad.TeamID.ToString(), "0", "true"); if (aMove.Squad.TeamID == team1.TeamID) { aMove.Player.RequiredTeam = team1; } else if (aMove.Squad.TeamID == team2.TeamID) { aMove.Player.RequiredTeam = team2; } else { Log.Error("Unable to assign required team for " + aMove.Player.player_name + "."); } Thread.Sleep(20); } Log.Success("Teams moved."); Log.Info("Assigning squads."); foreach (var aMove in moveList.ToList()) { ExecuteCommand("procon.protected.send", "admin.movePlayer", aMove.Player.player_name, aMove.Squad.TeamID.ToString(), aMove.Squad.SquadID.ToString(), "true"); aMove.Player.RequiredSquad = aMove.Squad.SquadID; Thread.Sleep(20); } Log.Success("Squads assigned."); // Update the cached player list just in case playerList = _PlayerDictionary.Values.ToList(); // Attempt to make sure every player stays on their assigned team/squad, despite the DICE balancer while (playerList.Count() > 10 && _roundState != RoundState.Playing) { foreach (var aPlayer in playerList.Where(dPlayer => !dPlayer.player_spawnedRound)) { if (_roundState == RoundState.Playing) { break; } if (!aPlayer.player_spawnedRound) { if (aPlayer.RequiredTeam != null) { if (aPlayer.fbpInfo.TeamID != aPlayer.RequiredTeam.TeamID || aPlayer.fbpInfo.SquadID != aPlayer.RequiredSquad) { ExecuteCommand("procon.protected.send", "admin.movePlayer", aPlayer.player_name, aPlayer.RequiredTeam.TeamID.ToString(), aPlayer.RequiredSquad.ToString(), "false"); Thread.Sleep(50); } } else { // Choose a squad for the player } } } playerList = _PlayerDictionary.Values.ToList(); } // Print the team squad lists foreach (var squad in _RoundPrepSquads.OrderBy(squad => squad.TeamID).ThenByDescending(squad => squad.Players.Sum(member => member.GetPower(true)))) { Log.Info("Squad " + squad); } _RoundPrepSquads.Clear(); break; } Log.Success("Team dispersion complete!"); Threading.Wait(TimeSpan.FromSeconds(15)); Log.Info("Checking players 1."); playerList = _PlayerDictionary.Values.ToList(); foreach (var aPlayer in playerList) { if (aPlayer.RequiredTeam != null) { if (aPlayer.RequiredTeam.TeamID != aPlayer.fbpInfo.TeamID) { Log.Warn("Dispersion: " + aPlayer.player_name + " assigned to " + aPlayer.RequiredTeam.TeamID + " but on " + aPlayer.fbpInfo.TeamID); } } else { Log.Warn("Dispersion: " + aPlayer.player_name + " not assigned to a team."); } } Log.Success("Team check 1 complete!"); Threading.Wait(TimeSpan.FromSeconds(30)); Log.Info("Checking players 2."); playerList = _PlayerDictionary.Values.ToList(); foreach (var aPlayer in playerList) { if (aPlayer.RequiredTeam != null) { if (aPlayer.RequiredTeam.TeamID != aPlayer.fbpInfo.TeamID) { Log.Warn("Dispersion: " + aPlayer.player_name + " assigned to " + aPlayer.RequiredTeam.TeamID + " but on " + aPlayer.fbpInfo.TeamID); } } else { Log.Warn("Dispersion: " + aPlayer.player_name + " not assigned to a team."); } } Log.Success("Team check 2 complete!"); Threading.StopWatchdog(); }))); } } catch (Exception e) { Log.HandleException(new AException("Error while running faction updates.", e)); } } private void AssignTeam(ASquad aSquad, ATeam aTeam, List squadList) { try { // Assign this squad to team aSquad.TeamID = aTeam.TeamID; Log.Info("Assigned " + aSquad + " to team " + aTeam.TeamID + "."); // Find the first available squad in team 2 // Do not include the "None" squad var named = false; foreach (var squadID in ASquad.Names.Keys.ToList().Where(sqaudKey => sqaudKey != 0).Reverse()) { if (!squadList.Any(dSquad => dSquad.SquadID == squadID)) { aSquad.SquadID = squadID; named = true; Log.Info("Named " + aSquad + "."); break; } } if (!named) { Log.Error("Unable to name squad " + aSquad); } } catch (Exception e) { Log.HandleException(new AException("Error assigning teams.", e)); } } public override void OnMaplistLoad() { getMapInfo(); } public override void OnMaplistSave() { getMapInfo(); } public override void OnMaplistCleared() { getMapInfo(); } public override void OnMaplistMapAppended(string mapFileName) { getMapInfo(); } public override void OnMaplistNextLevelIndex(int mapIndex) { getMapInfo(); } public override void OnMaplistMapRemoved(int mapIndex) { getMapInfo(); } public override void OnMaplistMapInserted(int mapIndex, string mapFileName) { getMapInfo(); } public void getMapInfo() { getMapList(); getMapIndices(); } public void getMapList() { ExecuteCommand("procon.protected.send", "mapList.list"); } public void getMapIndices() { ExecuteCommand("procon.protected.send", "mapList.getMapIndices"); } public override void OnMaplistList(List lstMaplist) { Log.Debug(() => "Entering OnMaplistList", 5); if (!_pluginEnabled || _serverInfo == null) { return; } _serverInfo.SetMapList(lstMaplist); Log.Debug(() => "Exiting OnMaplistList", 7); } public override void OnMaplistGetMapIndices(int mapIndex, int nextIndex) { Log.Debug(() => "Entering OnMaplistGetMapIndices", 5); if (!_pluginEnabled || _serverInfo == null) { return; } _serverInfo.SetMapListIndicies(mapIndex, nextIndex); Log.Debug(() => "Exiting OnMaplistGetMapIndices", 7); } public override void OnPlayerTeamChange(String soldierName, Int32 teamId, Int32 squadId) { Log.Debug(() => "Entering OnPlayerTeamChange", 7); try { if (!_firstPlayerListComplete) { return; } if (_PlayerDictionary.ContainsKey(soldierName)) { APlayer aPlayer = _PlayerDictionary[soldierName]; // Add to the move list Boolean moveAccepted = true; Boolean moveLoop = false; if (_roundState == RoundState.Playing) { aPlayer.TeamMoves.Add(UtcNow()); // Check if there were 8 or more moves in the last 5 seconds var movesLast5 = aPlayer.TeamMoves.Count(time => time > UtcNow().AddSeconds(-5)); if (movesLast5 >= 8 && NowDuration(aPlayer.JoinTime).TotalSeconds > 20) { // The player is stuck in a move loop, remove their required team and bow to whatever script/plugin is causing this moveLoop = true; var message = aPlayer.GetVerboseName() + " was stuck in a move loop."; if (aPlayer.RequiredTeam != null) { aPlayer.RequiredTeam = null; message += " Removing their required team."; } Log.Warn(message); OnlineAdminSayMessage(message); } } ATeam newTeam; if (!GetTeamByID(teamId, out newTeam)) { if (_roundState == RoundState.Playing) { Log.Error("Error fetching new team on team change."); } aPlayer.fbpInfo.TeamID = teamId; aPlayer.fbpInfo.SquadID = squadId; return; } ATeam oldTeam; if (!GetTeamByID(aPlayer.fbpInfo.TeamID, out oldTeam)) { if (_roundState == RoundState.Playing) { Log.Error("Error fetching old team on team change."); } aPlayer.fbpInfo.TeamID = teamId; aPlayer.fbpInfo.SquadID = squadId; return; } if (aPlayer.RequiredTeam != null && aPlayer.RequiredTeam.TeamKey != newTeam.TeamKey && (!PlayerIsAdmin(aPlayer) || !aPlayer.player_spawnedRound)) { if (aPlayer.fbpInfo.TeamID == 0) { // They aren't officially on a team yet, just force the required team until that happens. ExecuteCommand("procon.protected.send", "admin.movePlayer", soldierName, aPlayer.RequiredTeam.TeamID.ToString(), aPlayer.RequiredSquad > 0 ? aPlayer.RequiredSquad.ToString() : "1", "true"); moveAccepted = false; } else if (RunAssist(aPlayer, null, null, true) && _roundState == RoundState.Playing && _serverInfo.GetRoundElapsedTime().TotalMinutes > _minimumAssistMinutes) { if (_serverInfo.GetRoundElapsedTime().TotalMinutes > 3) { OnlineAdminSayMessage(Log.CViolet(aPlayer.GetVerboseName() + " (" + Math.Round(aPlayer.GetPower(true)) + ") REASSIGNED themselves from " + aPlayer.RequiredTeam.GetTeamIDKey() + " to " + newTeam.GetTeamIDKey() + ".")); } aPlayer.RequiredTeam = newTeam; } else { if (_roundState == RoundState.Playing && NowDuration(aPlayer.lastSwitchMessage).TotalSeconds > 5) { if (_UseExperimentalTools) { var message = aPlayer.GetVerboseName() + " (" + Math.Round(aPlayer.GetPower(true)) + ") attempted to switch teams after being assigned to " + aPlayer.RequiredTeam.GetTeamIDKey() + "."; if (_PlayerDictionary.ContainsKey(_debugSoldierName)) { PlayerSayMessage(_debugSoldierName, message); } else { ProconChatWrite(Log.CViolet(message)); } } PlayerTellMessage(aPlayer.player_name, Log.CViolet("You were assigned to " + aPlayer.RequiredTeam.TeamKey + ". Try using " + GetChatCommandByKey("self_assist") + " to switch.")); aPlayer.lastSwitchMessage = UtcNow(); } moveAccepted = false; var squadName = aPlayer.RequiredSquad > 0 ? ASquad.Names[aPlayer.RequiredSquad] : ASquad.Names[1]; Log.Debug(() => "MULTIBalancer Unswitcher Disabled", 3); ExecuteCommand("procon.protected.plugins.call", "MULTIbalancer", "UpdatePluginData", "AdKats", "bool", "DisableUnswitcher", "True"); _MULTIBalancerUnswitcherDisabled = true; ExecuteCommand("procon.protected.send", "admin.movePlayer", soldierName, aPlayer.RequiredTeam.TeamID.ToString(), aPlayer.RequiredSquad > 0 ? aPlayer.RequiredSquad.ToString() : "1", "true"); } } ATeam team1, team2, winningTeam, losingTeam, powerTeam, weakTeam, mapUpTeam, mapDownTeam; if (_firstPlayerListComplete && _roundState == RoundState.Playing && aPlayer.RequiredTeam == null && GetPlayerCount() > 15 && GetTeamByID(1, out team1) && GetTeamByID(2, out team2) && moveAccepted && !moveLoop) { // Wait for top stats var startTime = UtcNow(); while (_pluginEnabled && !aPlayer.TopStats.Fetched && NowDuration(startTime).TotalSeconds < 10) { Threading.Wait(200); } // set up the team variables if (team1.TeamTicketCount > team2.TeamTicketCount) { winningTeam = team1; losingTeam = team2; } else { winningTeam = team2; losingTeam = team1; } if (team1.GetTicketDifferenceRate() > team2.GetTicketDifferenceRate()) { mapUpTeam = team1; mapDownTeam = team2; } else { mapUpTeam = team2; mapDownTeam = team1; } var t1Power = team1.GetTeamPower(null, aPlayer); var t2Power = team2.GetTeamPower(null, aPlayer); var debugT1Power = t1Power; if (_serverInfo.InfoObject.Map == "XP0_Metro" && _serverInfo.InfoObject.GameMode == "ConquestLarge0") { // If this is metro, overstate the power of the lower team slightly // The upper team needs a slight stat boost over normal var roundMinutes = _serverInfo.GetRoundElapsedTime().TotalMinutes; if (team1 == mapUpTeam) { // If the lower team has the map, overstate its power even more if ((team2.TeamTicketCount + 500 < team1.TeamTicketCount || roundMinutes < 10) && _populationStatus == PopulationState.High) { t1Power *= 1.35; } else { t1Power *= 1.22; } } else if (team1.TeamTicketCount + 500 > team2.TeamTicketCount) { if (_serverInfo.GetRoundElapsedTime().TotalMinutes <= 10) { t1Power *= 1.12; } else if (_populationStatus == PopulationState.High) { t1Power *= 1.08; } } } if (t1Power > t2Power) { powerTeam = team1; weakTeam = team2; } else { powerTeam = team2; weakTeam = team1; } var powerGap = Math.Abs(((t1Power - t2Power) / t2Power) * 100); var players = _PlayerDictionary.Values.ToList(); var weakCount = players.Count(dPlayer => dPlayer.player_type == PlayerType.Player && (dPlayer.fbpInfo.TeamID == weakTeam.TeamID || (dPlayer.RequiredTeam != null && dPlayer.RequiredTeam.TeamID == weakTeam.TeamID))); var powerCount = players.Count(dPlayer => dPlayer.player_type == PlayerType.Player && dPlayer.fbpInfo.TeamID == powerTeam.TeamID || (dPlayer.RequiredTeam != null && dPlayer.RequiredTeam.TeamID == powerTeam.TeamID)); var teamCountLeniency = 1; // If it's not the early game, the server is populated, and the weak team is also losing, increase leniency to 2 players if (_serverInfo.GetRoundElapsedTime().TotalMinutes >= 10 && weakTeam == losingTeam && // Require high population state _populationStatus == PopulationState.High) { teamCountLeniency = 2; } // Assume max team size of 32 unless otherwise provided var maxTeamPlayerCount = 32; if (_serverInfo.InfoObject != null && _serverInfo.InfoObject.MaxPlayerCount != maxTeamPlayerCount) { maxTeamPlayerCount = _serverInfo.InfoObject.MaxPlayerCount / 2; } if (oldTeam.TeamKey == "Neutral") { // Do reassignment if ((aPlayer.GetPower(true) > 8 || aPlayer.fbpInfo.Rank > 15) && _UseTeamPowerMonitorReassign) { if (!aPlayer.TopStats.Fetched) { Log.Error(aPlayer.player_name + " assigned without top stats fetched."); } // If the current weak team is not map dominant, or is down by more than 30% power // and it doesn't have too many players, assign the player to that team var accepted = false; var acceptReason = "None"; if (weakTeam == mapDownTeam) { accepted = true; acceptReason = "Map"; } else if (_UseTeamPowerMonitorReassignLenient && powerGap > _TeamPowerMonitorReassignLenientPercent) { accepted = true; acceptReason = "P-" + Math.Round(powerGap); } if (accepted && weakCount - teamCountLeniency < powerCount && weakCount < maxTeamPlayerCount) { var message = Log.CViolet(aPlayer.GetVerboseName() + " (" + Math.Round(aPlayer.GetPower(true)) + ") join-assigned to " + weakTeam.GetTeamIDKey() + " [" + acceptReason + "]."); if (_PlayerDictionary.ContainsKey(_debugSoldierName)) { PlayerSayMessage(_debugSoldierName, message); } else if (_UseExperimentalTools) { ProconChatWrite(message); } moveAccepted = false; // Assign the team aPlayer.RequiredTeam = weakTeam; Log.Debug(() => "MULTIBalancer Unswitcher Disabled", 3); ExecuteCommand("procon.protected.plugins.call", "MULTIbalancer", "UpdatePluginData", "AdKats", "bool", "DisableUnswitcher", "True"); _MULTIBalancerUnswitcherDisabled = true; ExecuteCommand("procon.protected.send", "admin.movePlayer", aPlayer.player_name, aPlayer.RequiredTeam.TeamID.ToString(), "0", "true"); } } } else if (_UseTeamPowerMonitorUnswitcher) { // Do unswitching if (newTeam == powerTeam) { var message = Log.CViolet(aPlayer.GetVerboseName() + " (" + Math.Round(aPlayer.GetPower(true)) + ") unswitched back to " + weakTeam.GetTeamIDKey() + "."); if (_PlayerDictionary.ContainsKey(_debugSoldierName)) { PlayerSayMessage(_debugSoldierName, message); } else if (_UseExperimentalTools) { ProconChatWrite(message); } aPlayer.Say(Log.CViolet("Unswitched back to " + weakTeam.GetTeamIDKey() + ". Try using " + GetChatCommandByKey("self_assist") + " to switch.")); moveAccepted = false; Log.Debug(() => "MULTIBalancer Unswitcher Disabled", 3); ExecuteCommand("procon.protected.plugins.call", "MULTIbalancer", "UpdatePluginData", "AdKats", "bool", "DisableUnswitcher", "True"); _MULTIBalancerUnswitcherDisabled = true; ExecuteCommand("procon.protected.send", "admin.movePlayer", aPlayer.player_name, weakTeam.TeamID.ToString(), "0", "true"); } } } if (moveAccepted) { // Update their player object's team ID aPlayer.fbpInfo.TeamID = teamId; //If the player is queued for automatic assist, remove them from the queue if (_AssistAttemptQueue.Any()) { lock (_AssistAttemptQueue) { var matchingRecord = _AssistAttemptQueue.FirstOrDefault(assistRecord => assistRecord.target_player.player_id == aPlayer.player_id); if (matchingRecord != null) { //The player is queued, rebuild the queue without them in it SendMessageToSource(matchingRecord, Log.CViolet("You moved teams manually. Automatic assist cancelled.")); _AssistAttemptQueue = new Queue(_AssistAttemptQueue.Where(assistRecord => assistRecord != matchingRecord)); } } } } } else { Log.Warn(soldierName + " switched to team " + teamId + " without being in player list."); if (!_MissingPlayers.Contains(soldierName)) { _MissingPlayers.Add(soldierName); } } //When a player changes team, tell teamswap to recheck queues _TeamswapWaitHandle.Set(); } catch (Exception e) { Log.HandleException(new AException("Error while handling player team change.", e)); } Log.Debug(() => "Exiting OnPlayerTeamChange", 7); } public List GetSquadPlayers(APlayer aPlayer) { return GetSquadPlayers(aPlayer.fbpInfo.SquadID); } public List GetSquadPlayers(Int32 squadID) { return _PlayerDictionary.Values.ToList().Where( aPlayer => aPlayer.fbpInfo.SquadID == squadID).ToList(); } public override void OnPlayerSquadChange(string soldierName, int teamId, int squadId) { Log.Debug(() => "Entering OnPlayerSquadChange", 7); try { if (!_firstPlayerListComplete) { return; } if (_PlayerDictionary.ContainsKey(soldierName)) { APlayer aPlayer = _PlayerDictionary[soldierName]; ATeam newTeam; if (!GetTeamByID(teamId, out newTeam)) { if (_roundState == RoundState.Playing) { Log.Error("Error fetching new team on squad change."); } aPlayer.fbpInfo.TeamID = teamId; aPlayer.fbpInfo.SquadID = squadId; return; } ATeam oldTeam; if (!GetTeamByID(aPlayer.fbpInfo.TeamID, out oldTeam)) { if (_roundState == RoundState.Playing) { Log.Error("Error fetching old team on squad change."); } aPlayer.fbpInfo.TeamID = teamId; aPlayer.fbpInfo.SquadID = squadId; return; } Int32 oldSquad = aPlayer.fbpInfo.SquadID; aPlayer.fbpInfo.SquadID = squadId; if (aPlayer.RequiredTeam != null && aPlayer.RequiredSquad > 0 && aPlayer.RequiredSquad != squadId && // If they are being moved to the 'None' squad, don't try to move them back just yet squadId != 0 && _roundState != RoundState.Playing) { Log.Debug(() => "MULTIBalancer Unswitcher Disabled", 3); ExecuteCommand("procon.protected.plugins.call", "MULTIbalancer", "UpdatePluginData", "AdKats", "bool", "DisableUnswitcher", "True"); _MULTIBalancerUnswitcherDisabled = true; ExecuteCommand("procon.protected.send", "admin.movePlayer", soldierName, aPlayer.RequiredTeam.TeamID.ToString(), aPlayer.RequiredSquad.ToString(), "false"); } } } catch (Exception e) { Log.HandleException(new AException("Error while handling player squad change.", e)); } Log.Debug(() => "Exiting OnPlayerSquadChange", 7); } public override void OnListPlayers(List players, CPlayerSubset cpsSubset) { Log.Debug(() => "Entering OnListPlayers", 7); try { //Only handle the list if it is an "All players" list if (cpsSubset.Subset == CPlayerSubset.PlayerSubsetType.All) { DoPlayerListReceive(); //Return if small duration (1 second) since last accepted player list //But only if the plugin hasn't just started up if (NowDuration(_LastPlayerListAccept).TotalSeconds < 1.0 && NowDuration(_AdKatsRunningTime).TotalSeconds > 30) { return; } //Only perform the following if all threads are ready if (_threadsReady) { QueuePlayerListForProcessing(players); } } } catch (Exception e) { Log.HandleException(new AException("Error occured while listing players.", e)); } Log.Debug(() => "Exiting OnListPlayers", 7); } private void QueuePlayerListForProcessing(List players) { Log.Debug(() => "Entering QueuePlayerListForProcessing", 7); try { if (_pluginEnabled) { Log.Debug(() => "Preparing to queue player list for processing", 6); lock (_PlayerListProcessingQueue) { _PlayerListProcessingQueue.Enqueue(players); Log.Debug(() => "Player list queued for processing", 6); _PlayerProcessingWaitHandle.Set(); } DoPlayerListAccept(); } } catch (Exception e) { Log.HandleException(new AException("Error while queueing player list for processing.", e)); } Log.Debug(() => "Exiting QueuePlayerListForProcessing", 7); } private void QueuePlayerForRemoval(CPlayerInfo player) { Log.Debug(() => "Entering QueuePlayerForRemoval", 7); try { if (_pluginEnabled && _firstPlayerListComplete) { Log.Debug(() => "Preparing to queue player list for processing", 6); lock (_PlayerRemovalProcessingQueue) { _PlayerRemovalProcessingQueue.Enqueue(player); Log.Debug(() => "Player removal queued for processing", 6); _PlayerProcessingWaitHandle.Set(); } } } catch (Exception e) { Log.HandleException(new AException("Error while queueing player for removal.", e)); } Log.Debug(() => "Exiting QueuePlayerForRemoval", 7); } public void PlayerListingThreadLoop() { try { Log.Debug(() => "Starting Player Listing Thread", 1); Thread.CurrentThread.Name = "PlayerListing"; DateTime loopStart = UtcNow(); while (true) { try { Log.Debug(() => "Entering Player Listing Thread Loop", 7); if (!_pluginEnabled) { Log.Debug(() => "Detected AdKats not enabled. Exiting thread " + Thread.CurrentThread.Name, 6); break; } Boolean playerListFetched = false; APlayer pingPickedPlayer = null; //Get all unparsed inbound lists //Only allow player list fetching if the user list is already fetched List inboundPlayerList = null; if (_PlayerListProcessingQueue.Count > 0 && _firstUserListComplete) { Log.Debug(() => "Preparing to lock player list queues to retrive new player lists", 7); lock (_PlayerListProcessingQueue) { Log.Debug(() => "Inbound player lists found. Grabbing.", 6); while (_PlayerListProcessingQueue.Any()) { inboundPlayerList = _PlayerListProcessingQueue.Dequeue(); playerListFetched = true; _firstPlayerListStarted = true; } //Clear the queue for next run _PlayerListProcessingQueue.Clear(); } } else { inboundPlayerList = new List(); } //Get all unparsed inbound player removals Queue inboundPlayerRemoval = null; if (_PlayerRemovalProcessingQueue.Count > 0) { Log.Debug(() => "Preparing to lock player removal queue to retrive new player removals", 7); lock (_PlayerRemovalProcessingQueue) { Log.Debug(() => "Inbound player removals found. Grabbing.", 6); if (_PlayerRemovalProcessingQueue.Any()) { inboundPlayerRemoval = new Queue(_PlayerRemovalProcessingQueue.ToArray()); } //Clear the queue for next run _PlayerRemovalProcessingQueue.Clear(); } } else { inboundPlayerRemoval = new Queue(); } if (!inboundPlayerList.Any() && !inboundPlayerRemoval.Any() && !_PlayerRoleRefetch && !playerListFetched) { Log.Debug(() => "No inbound player listing actions. Waiting for Input.", 5); //Wait for input if (!_firstPlayerListStarted) { DoPlayerListTrigger(); Thread.Sleep(1000); } if ((UtcNow() - loopStart).TotalMilliseconds > 1000) { Log.Debug(() => "Warning. PlayerListing thread processing completed in " + ((int)((UtcNow() - loopStart).TotalMilliseconds)) + "ms", 4); } _PlayerProcessingWaitHandle.Reset(); _PlayerProcessingWaitHandle.WaitOne(TimeSpan.FromSeconds(60)); loopStart = UtcNow(); if (_firstPlayerListComplete) { //Case where all players are gone after first player list _LastPlayerListProcessed = UtcNow(); } continue; } List removedPlayers = new List(); lock (_PlayerDictionary) { //Firstly, go through removal queue, remove all names, and log them. while (inboundPlayerRemoval.Any()) { if (!_pluginEnabled) { break; } CPlayerInfo playerInfo = inboundPlayerRemoval.Dequeue(); APlayer aPlayer; if (_PlayerDictionary.TryGetValue(playerInfo.SoldierName, out aPlayer)) { //Show leaving messages Boolean toldAdmins = false; if (!aPlayer.TargetedRecords.Any(aRecord => aRecord.command_action.command_key == "player_kick" || aRecord.command_action.command_key == "player_ban_temp" || aRecord.command_action.command_key == "player_ban_perm")) { List meaningfulRecords = aPlayer.TargetedRecords.Where(aRecord => aRecord.command_action.command_key != "banenforcer_enforce" && aRecord.command_action.command_key != "player_changeip" && aRecord.command_action.command_key != "player_changename" && aRecord.command_action.command_key != "player_changetag" && aRecord.command_action.command_key != "player_repboost" && aRecord.command_action.command_key != "player_pm_send" && aRecord.command_action.command_key != "player_pm_reply" && aRecord.command_action.command_key != "player_pm_start" && aRecord.command_action.command_key != "player_pm_transmit" && aRecord.command_action.command_key != "player_pm_cancel" && !((aRecord.command_action.command_key == "player_say" || aRecord.command_action.command_key == "player_yell" || aRecord.command_action.command_key == "player_tell") && aRecord.source_player == null) && !aRecord.command_action.command_key.Contains("self_")).ToList(); if (meaningfulRecords.Any()) { List types = (from record in meaningfulRecords select record.command_action.command_name).Distinct().ToList(); String typeString = types.Aggregate("[", (current, type) => current + (type + ", ")); typeString = typeString.Trim().TrimEnd(',') + "]"; if (_ShowTargetedPlayerLeftNotification) { toldAdmins = true; OnlineAdminSayMessage(aPlayer.GetVerboseName() + " left from " + GetPlayerTeamKey(aPlayer) + " " + typeString, aPlayer.player_name); } var activeReports = aPlayer.TargetedRecords.Where(aRecord => aRecord.source_player != null && IsActiveReport(aRecord)).ToList(); // Update all the reports foreach (var report in activeReports) { FetchRecordUpdate(report); } activeReports = activeReports.Where(report => IsActiveReport(report)).ToList(); foreach (ARecord report in activeReports) { // Expire all active reports for the player report.command_action = GetCommandByKey("player_report_expire"); UpdateRecord(report); } foreach (APlayer player in activeReports.Where(report => report.source_player != null) .Select(report => report.source_player).Distinct()) { player.Say("Player " + aPlayer.GetVerboseName() + " you reported has left."); } if (activeReports.Any() && _UseDiscordForReports && _DiscordReportsLeftWithoutAction) { _DiscordManager.PostToDiscord("Reported player " + aPlayer.GetVerboseName() + " left without being acted on."); } } } if (!toldAdmins && aPlayer.player_type == PlayerType.Spectator) { OnlineAdminSayMessage(((PlayerIsAdmin(aPlayer)) ? ("Admin ") : ("")) + aPlayer.GetVerboseName() + " stopped spectating.", aPlayer.player_name); } //Shut down any running conversations if (aPlayer.conversationPartner != null) { APlayer partner = aPlayer.conversationPartner; if (PlayerIsExternal(aPlayer.conversationPartner)) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = partner.player_server.ServerID, record_orchestrate = true, command_type = GetCommandByKey("player_pm_cancel"), command_numeric = 0, target_name = partner.player_name, target_player = partner, source_name = aPlayer.player_name, source_player = aPlayer, record_message = aPlayer.GetVerboseName() + " has left their server. Private conversation closed.", record_time = UtcNow() }); } else { partner.Say(aPlayer.GetVerboseName() + " has left. Private conversation closed."); partner.conversationPartner = null; } aPlayer.conversationPartner = null; } if ((_roundState == RoundState.Loaded || (_roundState == RoundState.Playing && _serverInfo.GetRoundElapsedTime().TotalMinutes < 2)) && !PlayerIsAdmin(aPlayer)) { _mapDetrimentIndex++; } //Remove from populators _populationPopulatingPlayers.Remove(aPlayer.player_name); //Add player to the left dictionary aPlayer.player_online = false; aPlayer.player_new = false; aPlayer.player_server = null; aPlayer.player_spawnedOnce = false; aPlayer.player_chatOnce = false; aPlayer.LiveKills.Clear(); aPlayer.ClearPingEntries(); DequeuePlayer(aPlayer); //Remove all old values List removeNames = _PlayerLeftDictionary.Where(pair => (UtcNow() - pair.Value.LastUsage).TotalMinutes > 120).Select(pair => pair.Key).ToList(); foreach (String removeName in removeNames) { _PlayerLeftDictionary.Remove(removeName); } aPlayer.LastUsage = UtcNow(); _PlayerLeftDictionary[aPlayer.player_name] = aPlayer; } RemovePlayerFromDictionary(playerInfo.SoldierName, false); removedPlayers.Add(playerInfo.SoldierName); } List validPlayers = new List(); var fetchAccessAfterList = false; if (inboundPlayerList.Count > 0) { Log.Debug(() => "Listing Players", 5); //Loop over all players in the list List durations = new List(); IEnumerable trimmedInboundPlayers = inboundPlayerList.Where(player => !removedPlayers.Contains(player.SoldierName)); Int32 index = 0; foreach (CPlayerInfo playerInfo in trimmedInboundPlayers) { index++; Stopwatch timer = new Stopwatch(); timer.Start(); if (!_pluginEnabled) { break; } //Check for glitched players if (String.IsNullOrEmpty(playerInfo.GUID)) { if ((UtcNow() - _lastGlitchedPlayerNotification).TotalMinutes > 5) { OnlineAdminSayMessage(playerInfo.SoldierName + " is glitched, their player has no GUID."); Log.Warn(playerInfo.SoldierName + " is glitched, their player has no GUID."); _lastGlitchedPlayerNotification = UtcNow(); } continue; } //Check for invalid player names if (!IsSoldierNameValid(playerInfo.SoldierName)) { if ((UtcNow() - _lastInvalidPlayerNameNotification).TotalMinutes > 5) { OnlineAdminSayMessage(playerInfo.SoldierName + " had an invalid player name, unable to process."); Log.Warn(playerInfo.SoldierName + " has an invalid player name, unable to process."); KickPlayerMessage(playerInfo.SoldierName, "Your soldier name " + playerInfo.SoldierName + " is invalid.", 30); _lastInvalidPlayerNameNotification = UtcNow(); } continue; } validPlayers.Add(playerInfo.SoldierName); //Check if the player is already in the player dictionary APlayer aPlayer = null; if (_PlayerDictionary.TryGetValue(playerInfo.SoldierName, out aPlayer)) { //They are if (aPlayer.fbpInfo.Score != playerInfo.Score || aPlayer.fbpInfo.Kills != playerInfo.Kills || aPlayer.fbpInfo.Deaths != playerInfo.Deaths) { aPlayer.lastAction = UtcNow(); } aPlayer.fbpInfo = playerInfo; if (_MissingPlayers.Contains(aPlayer.player_name)) { Log.Success("Missing player " + aPlayer.GetVerboseName() + " finally loaded."); _MissingPlayers.Remove(aPlayer.player_name); } switch (aPlayer.fbpInfo.Type) { case 0: aPlayer.player_type = PlayerType.Player; break; case 1: aPlayer.player_type = PlayerType.Spectator; break; case 2: aPlayer.player_type = PlayerType.CommanderPC; break; case 3: aPlayer.player_type = PlayerType.CommanderMobile; break; default: Log.Error("Player type " + aPlayer.fbpInfo.Type + " is not valid."); break; } if (_roundState == RoundState.Playing) { Boolean proconFetched = false; Double ping = aPlayer.fbpInfo.Ping; if (((_pingEnforcerKickMissingPings && _attemptManualPingWhenMissing && ping < 0) || aPlayer.player_ping_manual) && !String.IsNullOrEmpty(aPlayer.player_ip)) { PingReply reply = null; try { reply = _PingProcessor.Send(aPlayer.player_ip, 1000); } catch (Exception e) { Log.HandleException(new AException("Error fetching manual player ping.", e)); } if (reply != null && reply.Status == IPStatus.Success) { ping = reply.RoundtripTime; proconFetched = true; } else { Log.Debug(() => "Ping status for " + aPlayer.GetVerboseName() + ": " + reply.Status, 5); ping = -1; } } aPlayer.AddPingEntry(ping); //Automatic ping kick if (_pingEnforcerEnable && aPlayer.player_type == PlayerType.Player && !PlayerIsAdmin(aPlayer) && !GetMatchingVerboseASPlayersOfGroup("whitelist_ping", aPlayer).Any() && !_pingEnforcerIgnoreRoles.Contains(aPlayer.player_role.role_key) && !(_pingEnforcerIgnoreUserList && FetchAllUserSoldiers().Any(sPlayer => sPlayer.player_guid == aPlayer.player_guid)) && GetPlayerCount() > _pingEnforcerTriggerMinimumPlayers) { Double currentTriggerMS = GetPingLimit(); //Warn players of limit and spikes if (ping > currentTriggerMS) { if (aPlayer.player_pings_full && aPlayer.player_ping_avg < currentTriggerMS && ping > (aPlayer.player_ping_avg * 1.5)) { aPlayer.Say("Warning, your ping is spiking. Current: [" + Math.Round(ping) + "ms] Avg: [" + Math.Round(aPlayer.player_ping_avg, 1) + "ms]" + ((proconFetched) ? ("[PR]") : ("")), _pingEnforcerDisplayProconChat, 1); } else { aPlayer.Say("Warning, your ping is over the limit. [" + Math.Round(aPlayer.player_ping, 1) + "ms]" + ((proconFetched) ? ("[PR]") : ("")), _pingEnforcerDisplayProconChat, 1); } } //Are they over the limit, or missing if (((aPlayer.player_ping_avg > currentTriggerMS && aPlayer.player_ping > aPlayer.player_ping_avg) || (_pingEnforcerKickMissingPings && aPlayer.player_ping_avg < 0 && (UtcNow() - aPlayer.JoinTime).TotalSeconds > 60)) && aPlayer.player_pings_full) { //Are they worse than the current picked player if (pingPickedPlayer == null || (aPlayer.player_ping_avg > pingPickedPlayer.player_ping_avg && pingPickedPlayer.player_ping_avg > 0)) { pingPickedPlayer = aPlayer; } } } } else { aPlayer.ClearPingEntries(); } if (_CMDRManagerEnable && _firstPlayerListComplete && (aPlayer.player_type == PlayerType.CommanderPC || aPlayer.player_type == PlayerType.CommanderMobile) && GetPlayerCount() < (0.75 * _CMDRMinimumPlayers)) { ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_kick"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "CMDRManager", record_message = "Commanders not allowed until " + _CMDRMinimumPlayers + " active players", record_time = UtcNow() }; QueueRecordForProcessing(record); } } else { //Player is not already online, handle fetching //First check if the player is rejoining current AdKats session aPlayer = _PlayerLeftDictionary.Values.FirstOrDefault(oPlayer => oPlayer.player_guid == playerInfo.GUID); if (aPlayer != null) { Log.Debug(() => "Player " + playerInfo.SoldierName + " re-joined.", 3); //Remove them from the left dictionary _PlayerLeftDictionary.Remove(playerInfo.SoldierName); //check for name changes if (!String.IsNullOrEmpty(playerInfo.SoldierName) && playerInfo.SoldierName != aPlayer.player_name) { aPlayer.player_name_previous = aPlayer.player_name; aPlayer.player_name = playerInfo.SoldierName; ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_changename"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "AdKats", record_message = aPlayer.player_name_previous, record_time = UtcNow() }; QueueRecordForProcessing(record); Log.Debug(() => aPlayer.player_name_previous + " changed their name to " + playerInfo.SoldierName + ". Updating the database.", 2); if (_ShowPlayerNameChangeAnnouncement) { OnlineAdminSayMessage(aPlayer.player_name_previous + " changed their name to " + playerInfo.SoldierName); } UpdatePlayer(aPlayer); } if (aPlayer.TargetedRecords.Any(aRecord => aRecord.command_action.command_key == "player_kick" && (UtcNow() - aRecord.record_time).TotalMinutes < 30) && aPlayer.TargetedRecords.All(aRecord => aRecord.command_action.command_key != "banenforcer_enforce" && // Don't show the message if the person kicked themselves aRecord.source_name != aPlayer.player_name)) { OnlineAdminSayMessage("Kicked player " + aPlayer.GetVerboseName() + " re-joined."); } // Increment the player's active session // Helps us determine which session a record came from aPlayer.ActiveSession++; } else { //If they aren't in the list, fetch their information from the database aPlayer = FetchPlayer(true, false, false, null, -1, playerInfo.SoldierName, playerInfo.GUID, null, null); if (aPlayer == null) { //Do not handle the player if not returned continue; } } if (aPlayer.player_firstseen > _AutoKickNewPlayerDate) { // This player is newer to the server than the maximum first seen date, kick them KickPlayerMessage(aPlayer.player_name, "Please Contact The Server Admin", 0); } aPlayer.player_online = true; aPlayer.JoinTime = UtcNow(); //Fetch their infraction points FetchPoints(aPlayer, false, true); //Team Power Information FetchPowerInformation(aPlayer); if (ChallengeManager != null) { if (ChallengeManager.Loading) { Threading.Wait(5000); } ChallengeManager.AssignActiveEntryForPlayer(aPlayer); } if (aPlayer.location == null || aPlayer.location.status != "success" || aPlayer.location.IP != aPlayer.player_ip) { //Update IP location QueuePlayerForIPInfoFetch(aPlayer); } //Last Punishment List punishments = FetchRecentRecords(aPlayer.player_id, GetCommandByKey("player_punish").command_id, 1000, 1, true, false); if (punishments.Any()) { aPlayer.LastPunishment = punishments.FirstOrDefault(); } //Last Forgive List forgives = FetchRecentRecords(aPlayer.player_id, GetCommandByKey("player_forgive").command_id, 1000, 1, true, false); if (forgives.Any()) { aPlayer.LastForgive = forgives.FirstOrDefault(); } aPlayer.player_server = _serverInfo; //Add the frostbite player info aPlayer.fbpInfo = playerInfo; if (_MissingPlayers.Contains(aPlayer.player_name)) { Log.Success("Missing player " + aPlayer.GetVerboseName() + " finally loaded."); _MissingPlayers.Remove(aPlayer.player_name); } String joinLocation = String.Empty; ATeam playerTeam = null; if (aPlayer.fbpInfo != null) { _teamDictionary.TryGetValue(aPlayer.fbpInfo.TeamID, out playerTeam); } //Check for moving to teams aside from the one they are required to be on if (aPlayer.RequiredTeam != null && playerTeam != null && aPlayer.player_type == PlayerType.Player && aPlayer.RequiredTeam.TeamKey != playerTeam.TeamKey && (!PlayerIsAdmin(aPlayer) || !aPlayer.player_spawnedRound)) { // Don't allow a player to be reassigned to the "neutral" team // Otherwise, run a mock assist command on them to see if they can reassign themselves if (playerTeam.TeamKey != "Neutral" && RunAssist(aPlayer, null, null, true) && _roundState == RoundState.Playing && _serverInfo.GetRoundElapsedTime().TotalMinutes > _minimumAssistMinutes) { if (_serverInfo.GetRoundElapsedTime().TotalMinutes > 3) { OnlineAdminSayMessage(aPlayer.GetVerboseName() + " (" + Math.Round(aPlayer.GetPower(true)) + ") REASSIGNED themselves from " + aPlayer.RequiredTeam.GetTeamIDKey() + " to " + playerTeam.GetTeamIDKey() + "."); } aPlayer.RequiredTeam = playerTeam; } else { if (_roundState == RoundState.Playing && NowDuration(aPlayer.lastSwitchMessage).TotalSeconds > 5) { if (_UseExperimentalTools) { var message = Log.CViolet(aPlayer.GetVerboseName() + " (" + Math.Round(aPlayer.GetPower(true)) + ") re-joined, sending them back to " + aPlayer.RequiredTeam.GetTeamIDKey() + "."); if (_PlayerDictionary.ContainsKey(_debugSoldierName)) { PlayerSayMessage(_debugSoldierName, message); } else { ProconChatWrite(Log.FBold(message)); } } PlayerTellMessage(aPlayer.player_name, "You were assigned to " + aPlayer.RequiredTeam.TeamKey + ". Try using " + GetChatCommandByKey("self_assist") + " to switch."); aPlayer.lastSwitchMessage = UtcNow(); } Log.Debug(() => "MULTIBalancer Unswitcher Disabled", 3); ExecuteCommand("procon.protected.plugins.call", "MULTIbalancer", "UpdatePluginData", "AdKats", "bool", "DisableUnswitcher", "True"); _MULTIBalancerUnswitcherDisabled = true; ExecuteCommand("procon.protected.send", "admin.movePlayer", aPlayer.player_name, aPlayer.RequiredTeam.TeamID.ToString(), "1", "false"); } } switch (aPlayer.fbpInfo.Type) { case 0: aPlayer.player_type = PlayerType.Player; if (playerTeam == null || playerTeam.TeamID == 0) { joinLocation += "player"; } else { joinLocation += playerTeam.GetTeamIDKey() + " player"; } break; case 1: aPlayer.player_type = PlayerType.Spectator; joinLocation += "spectator"; break; case 2: aPlayer.player_type = PlayerType.CommanderPC; joinLocation += "commander"; break; case 3: aPlayer.player_type = PlayerType.CommanderMobile; if (playerTeam != null) { joinLocation += playerTeam.GetTeamIDKey() + " "; } joinLocation += "tablet commander"; break; default: Log.Error("Player type " + aPlayer.fbpInfo.Type + " is not valid."); break; } if (aPlayer.player_type == PlayerType.Spectator) { if (GetMatchingVerboseASPlayersOfGroup("blacklist_spectator", aPlayer).Any()) { ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_kick"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "SpectatorManager", record_message = "You may not spectate at this time.", record_time = UtcNow() }; QueueRecordForProcessing(record); } if (GetVerboseASPlayersOfGroup("slot_spectator").Any() && !GetMatchingVerboseASPlayersOfGroup("slot_spectator", aPlayer).Any() && aPlayer.player_name != _debugSoldierName) { ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_kick"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "SpectatorManager", record_message = "Whitelist required to spectate.", record_time = UtcNow() }; QueueRecordForProcessing(record); } } bool isAdmin = PlayerIsAdmin(aPlayer); if (_firstPlayerListComplete) { //Notify reputable players if (isAdmin || aPlayer.player_aa) { String message = ((isAdmin) ? ("Admin ") : ("Admin assistant ")) + aPlayer.GetVerboseName() + " joined as a " + joinLocation + "."; if (_InformReputablePlayersOfAdminJoins) { List reputablePlayers = _PlayerDictionary.Values.Where(iPlayer => iPlayer.player_reputation >= _reputationThresholdGood && !PlayerIsAdmin(iPlayer)).ToList(); foreach (APlayer reputablePlayer in reputablePlayers) { reputablePlayer.Say(message); } } if (_InformAdminsOfAdminJoins) { OnlineAdminSayMessage(message); } } else if (aPlayer.player_type == PlayerType.Spectator) { OnlineAdminSayMessage(((PlayerIsAdmin(aPlayer)) ? ("Admin ") : ("")) + aPlayer.GetVerboseName() + " is now spectating."); } //If populating, add player if (_populationPopulating && _populationStatus == PopulationState.Low && aPlayer.player_type == PlayerType.Player && _populationPopulatingPlayers.Count < _lowPopulationPlayerCount) { _populationPopulatingPlayers[aPlayer.player_name] = aPlayer; } //Increment benefit index if ((_roundState == RoundState.Playing || _roundState == RoundState.Loaded) && !PlayerIsAdmin(aPlayer)) { _mapBenefitIndex++; } } //Set their last death/spawn times aPlayer.lastDeath = UtcNow(); aPlayer.lastSpawn = UtcNow(); aPlayer.lastAction = UtcNow(); //Add them to the dictionary _PlayerDictionary.Add(playerInfo.SoldierName, aPlayer); //Get their battlelog information, or update the already fetched battlelog info QueuePlayerForBattlelogInfoFetch(aPlayer); //If they are an admin, and if we protect admins from VIP kicks, update the user list if (_firstPlayerListComplete && isAdmin && _FeedServerReservedSlots && _FeedServerReservedSlots_Admins_VIPKickWhitelist) { fetchAccessAfterList = true; } //Update rep UpdatePlayerReputation(aPlayer, false); //If using ban enforcer, check the player's ban status if (_UseBanEnforcer) { QueuePlayerForBanCheck(aPlayer); } else { //Queue the player for a AntiCheat check QueuePlayerForAntiCheatCheck(aPlayer); } } if (_CMDRManagerEnable && _firstPlayerListComplete && (aPlayer.player_type == PlayerType.CommanderPC || aPlayer.player_type == PlayerType.CommanderMobile) && GetPlayerCount() < _CMDRMinimumPlayers) { ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_kick"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "CMDRManager", record_message = "Commanders not allowed until " + _CMDRMinimumPlayers + " active players", record_time = UtcNow() }; QueueRecordForProcessing(record); } //Update them to round players HashSet roundPlayers; if (!_RoundPlayerIDs.TryGetValue(_roundID, out roundPlayers)) { roundPlayers = new HashSet(); _RoundPlayerIDs[_roundID] = roundPlayers; } roundPlayers.Add(aPlayer.player_id); timer.Stop(); durations.Add(timer.Elapsed.TotalSeconds); if (!_firstPlayerListComplete) { Log.Write(index + "/" + trimmedInboundPlayers.Count() + " players loaded (" + aPlayer.player_name + "). " + Math.Round(durations.Sum() / durations.Count, 2) + "s per player."); } if (!aPlayer.RoundStats.ContainsKey(_roundID)) { aPlayer.RoundStats[_roundID] = new APlayerStats(_roundID); } if (_roundState == RoundState.Playing) { aPlayer.RoundStats[_roundID].LiveStats = aPlayer.fbpInfo; } } ATeam team1, team2; if (GetTeamByID(1, out team1)) { team1.UpdatePlayerCount(GetPlayerCount(true, true, true, 1)); } if (GetTeamByID(2, out team2)) { team2.UpdatePlayerCount(GetPlayerCount(true, true, true, 2)); } //Make sure the player dictionary is clean of any straglers Int32 straglerCount = 0; Int32 dicCount = _PlayerDictionary.Count; foreach (string playerName in _PlayerDictionary.Keys.Where(playerName => !validPlayers.Contains(playerName)).ToList()) { straglerCount++; Log.Debug(() => "Removing " + playerName + " from current player list (VIA CLEANUP).", 4); APlayer aPlayer; if (_PlayerDictionary.TryGetValue(playerName, out aPlayer)) { //Shut down any running conversations if (aPlayer.conversationPartner != null) { APlayer partner = aPlayer.conversationPartner; if (PlayerIsExternal(aPlayer.conversationPartner)) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = partner.player_server.ServerID, record_orchestrate = true, command_type = GetCommandByKey("player_pm_cancel"), command_numeric = 0, target_name = partner.player_name, target_player = partner, source_name = aPlayer.player_name, source_player = aPlayer, record_message = aPlayer.GetVerboseName() + " has left their server. Private conversation closed.", record_time = UtcNow() }); } else { partner.Say(aPlayer.GetVerboseName() + " has left. Private conversation closed."); partner.conversationPartner = null; } aPlayer.conversationPartner = null; } //Remove from populators _populationPopulatingPlayers.Remove(aPlayer.player_name); //Add player to the left dictionary aPlayer.player_online = false; aPlayer.player_new = false; aPlayer.player_server = null; aPlayer.player_spawnedOnce = false; aPlayer.player_chatOnce = false; aPlayer.ClearPingEntries(); aPlayer.LiveKills.Clear(); DequeuePlayer(aPlayer); //Remove all old values List removeNames = _PlayerLeftDictionary.Where(pair => (UtcNow() - pair.Value.LastUsage).TotalMinutes > 120).Select(pair => pair.Key).ToList(); foreach (String removeName in removeNames) { _PlayerLeftDictionary.Remove(removeName); } aPlayer.LastUsage = UtcNow(); _PlayerLeftDictionary[aPlayer.player_name] = aPlayer; } _PlayerDictionary.Remove(playerName); } if (straglerCount > 1 && straglerCount > (dicCount / 2)) { ARecord record = new ARecord { record_source = ARecord.Sources.Automated, isDebug = true, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_calladmin"), command_numeric = straglerCount, target_name = "Server", target_player = null, source_name = "AdKats", record_message = "Server Crashed (" + dicCount + " Players Lost)", record_time = UtcNow() }; //Process the record QueueRecordForProcessing(record); Log.Error(record.record_message); //Set round ended _roundState = RoundState.Ended; //Clear populators _populationPopulatingPlayers.Clear(); } } if (fetchAccessAfterList) { FetchAllAccess(true); } if (_PlayerRoleRefetch || !_firstPlayerListComplete) { //Update roles for all fetched players foreach (APlayer aPlayer in GetFetchedPlayers()) { AssignPlayerRole(aPlayer); } _PlayerRoleRefetch = false; } if (_firstPlayerListComplete) { Int32 playerCount = GetPlayerCount(); if (playerCount < _lowPopulationPlayerCount) { switch (_populationStatus) { case PopulationState.Unknown: _populationTransitionTime = UtcNow(); OnlineAdminSayMessage("Server in populating mode."); break; case PopulationState.Low: //Current state _populationDurations[PopulationState.Low] += (UtcNow() - _populationUpdateTime); break; case PopulationState.Medium: _populationTransitionTime = UtcNow(); _populationDurations[PopulationState.Medium] += (UtcNow() - _populationTransitionTime); OnlineAdminSayMessage("Server now in populating mode, with " + playerCount + " populators."); break; case PopulationState.High: _populationTransitionTime = UtcNow(); _populationDurations[PopulationState.High] += (UtcNow() - _populationTransitionTime); OnlineAdminSayMessage("Server now in populating mode, with " + playerCount + " populators."); break; default: break; } if (!_populationPopulating) { _populationPopulatingPlayers.Clear(); _populationPopulating = true; foreach (APlayer popPlayer in _PlayerDictionary.Values.ToList().Where(player => player.player_type == PlayerType.Player && NowDuration(player.lastAction).TotalMinutes < 20).ToList()) { _populationPopulatingPlayers[popPlayer.player_name] = popPlayer; } } _populationStatus = PopulationState.Low; } else if (playerCount < _highPopulationPlayerCount) { switch (_populationStatus) { case PopulationState.Unknown: _populationTransitionTime = UtcNow(); break; case PopulationState.Low: _populationTransitionTime = UtcNow(); _populationDurations[PopulationState.Low] += (UtcNow() - _populationTransitionTime); break; case PopulationState.Medium: //Current state _populationDurations[PopulationState.Medium] += (UtcNow() - _populationUpdateTime); break; case PopulationState.High: _populationTransitionTime = UtcNow(); _populationDurations[PopulationState.High] += (UtcNow() - _populationTransitionTime); break; default: break; } _populationStatus = PopulationState.Medium; } else { switch (_populationStatus) { case PopulationState.Unknown: _populationTransitionTime = UtcNow(); break; case PopulationState.Low: _populationTransitionTime = UtcNow(); _populationDurations[PopulationState.Low] += (UtcNow() - _populationTransitionTime); break; case PopulationState.Medium: _populationTransitionTime = UtcNow(); _populationDurations[PopulationState.Medium] += (UtcNow() - _populationTransitionTime); break; case PopulationState.High: //Current state _populationDurations[PopulationState.High] += (UtcNow() - _populationUpdateTime); break; default: break; } if (_populationPopulating) { foreach (APlayer popPlayer in _populationPopulatingPlayers.Values.Where(aPlayer => aPlayer.player_online && _PlayerDictionary.ContainsKey(aPlayer.player_name) && aPlayer.player_type == PlayerType.Player)) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_population_success"), command_numeric = 0, target_name = popPlayer.player_name, target_player = popPlayer, source_name = "PopulationManager", record_message = "Populated Server " + _serverInfo.ServerID, record_time = UtcNow() }); } _populationPopulatingPlayers.Clear(); _populationPopulating = false; } _populationStatus = PopulationState.High; } _populationUpdateTime = UtcNow(); } } if (pingPickedPlayer != null) { ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_kick"), command_numeric = 0, target_name = pingPickedPlayer.player_name, target_player = pingPickedPlayer, source_name = "PingEnforcer", record_message = _pingEnforcerMessagePrefix + " " + ((pingPickedPlayer.player_ping_avg > 0) ? ("Cur:[" + Math.Round(pingPickedPlayer.player_ping) + "ms] Avg:[" + Math.Round(pingPickedPlayer.player_ping_avg) + "ms]") : ("[Missing]")), record_time = UtcNow() }; QueueRecordForProcessing(record); } //Update last successful player list time DoPlayerListProcessed(); //Set required handles _PlayerListUpdateWaitHandle.Set(); _TeamswapWaitHandle.Set(); //Push online player subscription if (playerListFetched && _pluginEnabled) { SendOnlineSoldiers(); } if (!_firstPlayerListComplete && playerListFetched && _pluginEnabled) { _AdKatsRunningTime = UtcNow(); _firstPlayerListComplete = true; OnlineAdminSayMessage("Player listing complete [" + _PlayerDictionary.Count + " players]. Performing final startup."); Log.Success("Player listing complete [" + _PlayerDictionary.Count + " players]."); Log.Info("Performing final startup."); //Immediately request another player list to make sure we haven't missed anyone who just joined. DoPlayerListTrigger(); //Do another access fetch to make sure server information is current FetchAllAccess(true); //Register external plugin commands RegisterCommand(_issueCommandMatchCommand); RegisterCommand(_fetchAuthorizedSoldiersMatchCommand); Threading.Wait(500); var startupDuration = NowDuration(_AdKatsStartTime); _startupDurations.Enqueue(startupDuration); while (_startupDurations.Count() > 10) { _startupDurations.Dequeue(); } var averageStartupDuration = TimeSpan.FromSeconds(_startupDurations.Average(span => span.TotalSeconds)); var averageDurationString = "(" + FormatTimeString(averageStartupDuration, 3) + ":" + _startupDurations.Count() + ")"; OnlineAdminTellMessage("AdKats startup complete [" + FormatTimeString(startupDuration, 3) + "]" + averageDurationString + ". Commands are now online."); foreach (String playerName in _PlayersRequestingCommands) { APlayer aPlayer; if (_PlayerDictionary.TryGetValue(playerName, out aPlayer)) { if (!PlayerIsAdmin(aPlayer)) { PlayerTellMessage(aPlayer.player_name, "AdKats commands now online. Thank you for your patience."); } } } Log.Success("AdKats " + GetPluginVersion() + " startup complete [" + FormatTimeString(UtcNow() - _AdKatsStartTime, 3) + "]. Commands are now online."); if (_TeamspeakPlayerMonitorEnable) { _TeamspeakManager.Enable(); } if (_DiscordPlayerMonitorEnable && _DiscordPlayerMonitorView) { _DiscordManager.Enable(); } } } catch (Exception e) { if (e is ThreadAbortException) { Log.Warn("player listing thread was force aborted. Exiting."); break; } Log.HandleException(new AException("Error occured in player listing thread. Skipping loop.", e)); } } Log.Debug(() => "Ending Player Listing Thread", 1); Threading.StopWatchdog(); } catch (Exception e) { Log.HandleException(new AException("Error occured in player listing thread.", e)); } } public override void OnPunkbusterPlayerInfo(CPunkbusterInfo cpbiPlayer) { try { Log.Debug(() => "OnPunkbusterPlayerInfo fired!", 7); APlayer aPlayer; if (_PlayerDictionary.TryGetValue(cpbiPlayer.SoldierName, out aPlayer)) { Boolean updatePlayer = false; //Update the player with pb info aPlayer.PBPlayerInfo = cpbiPlayer; aPlayer.player_pbguid = cpbiPlayer.GUID; aPlayer.player_slot = cpbiPlayer.SlotID; String player_ip = cpbiPlayer.Ip.Split(':')[0]; if (player_ip != aPlayer.player_ip && !String.IsNullOrEmpty(player_ip)) { updatePlayer = true; if (!String.IsNullOrEmpty(aPlayer.player_ip)) { Log.Debug(() => aPlayer.GetVerboseName() + " changed their IP from " + aPlayer.player_ip + " to " + player_ip + ". Updating the database.", 2); ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_changeip"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "AdKats", record_message = aPlayer.player_ip, record_time = UtcNow() }; QueueRecordForProcessing(record); } } aPlayer.SetIP(player_ip); if (aPlayer.location == null || aPlayer.location.status != "success" || aPlayer.location.IP != aPlayer.player_ip) { //Update IP location QueuePlayerForIPInfoFetch(aPlayer); } if (updatePlayer) { Log.Debug(() => "Queueing existing player " + aPlayer.GetVerboseName() + " for update.", 4); UpdatePlayer(aPlayer); //If using ban enforcer, queue player for update if (_UseBanEnforcer) { QueuePlayerForBanCheck(aPlayer); } } } Log.Debug(() => "Player slot: " + cpbiPlayer.SlotID, 7); Log.Debug(() => "OnPunkbusterPlayerInfo finished!", 7); } catch (Exception e) { Log.HandleException(new AException("Error occured while processing punkbuster info.", e)); } } private void FetchAllAccess(Boolean async) { try { if (async) { _AccessFetchWaitHandle.Set(); } else if (_threadsReady) { lock (_userCache) { DateTime start = UtcNow(); FetchCommands(); Log.Debug(() => "Command fetch took " + (UtcNow() - start).TotalMilliseconds + "ms.", 4); start = UtcNow(); FetchRoles(); Log.Debug(() => "Role fetch took " + (UtcNow() - start).TotalMilliseconds + "ms.", 4); start = UtcNow(); FetchUserList(); Log.Debug(() => "User fetch took " + (UtcNow() - start).TotalMilliseconds + "ms.", 4); start = UtcNow(); } } } catch (Exception e) { Log.HandleException(new AException("Error fetching all access.", e)); } } private void AccessFetchingThreadLoop() { try { Log.Debug(() => "Starting Access Fetching Thread", 1); Thread.CurrentThread.Name = "AccessFetching"; DateTime loopStart = UtcNow(); while (true) { try { Log.Debug(() => "Entering Access Fetching Thread Loop", 7); if (!_pluginEnabled) { Log.Debug(() => "Detected AdKats not enabled. Exiting thread " + Thread.CurrentThread.Name, 6); break; } FetchAllAccess(false); Log.Debug(() => "Access fetch waiting for Input.", 5); if ((UtcNow() - loopStart).TotalMilliseconds > 1000) { Log.Debug(() => "Warning. " + Thread.CurrentThread.Name + " thread processing completed in " + ((int)((UtcNow() - loopStart).TotalMilliseconds)) + "ms", 4); } _AccessFetchWaitHandle.Reset(); _AccessFetchWaitHandle.WaitOne(TimeSpan.FromSeconds(300)); loopStart = UtcNow(); } catch (Exception e) { if (e is ThreadAbortException) { Log.Warn("Access Fetching thread was force aborted. Exiting."); break; } Log.HandleException(new AException("Error occured in Access Fetching thread. Skipping loop.", e)); } } Log.Debug(() => "Ending Access Fetching Thread", 1); Threading.StopWatchdog(); } catch (Exception e) { Log.HandleException(new AException("Error occured in Access Fetching thread.", e)); } } private void FetchRoundID(Boolean increment) { try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT IFNULL(MAX(`round_id`), 0) AS `max_round_id` FROM `tbl_extendedroundstats` WHERE `server_id` = @server_id"; command.Parameters.AddWithValue("server_id", _serverInfo.ServerID); using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { Int32 oldRoundID = reader.GetInt32("max_round_id"); if (increment) { _roundID = oldRoundID + 1; Log.Debug(() => "New round. Round ID is " + String.Format("{0:n0}", _roundID), 2); } else { _roundID = oldRoundID; Log.Debug(() => "Current round. Round ID is " + String.Format("{0:n0}", _roundID), 2); } } else { _roundID = 1; } } } } } catch (Exception e) { Log.HandleException(new AException("Error fetching round ID", e)); } } private void StartRoundTicketLogger(Int32 startingSeconds) { try { if (!_pluginEnabled || !_threadsReady || !_firstPlayerListComplete || _roundID < 1) { return; } Thread roundLoggerThread = new Thread(new ThreadStart(delegate { try { Thread.CurrentThread.Name = "RoundTicketLogger"; Int32 roundTimeSeconds = startingSeconds; ProconChatWrite(Log.FBold("Ticket logging started on round " + String.Format("{0:n0}", _roundID))); Stopwatch watch = new Stopwatch(); while (true) { if (!_pluginEnabled) { break; } watch.Reset(); watch.Start(); if (_roundState == RoundState.Loaded) { Threading.Wait(TimeSpan.FromSeconds(2)); continue; } ATeam team1 = _teamDictionary[1]; ATeam team2 = _teamDictionary[2]; if (_roundState == RoundState.Ended || !_pluginEnabled || GetPlayerCount() <= 1) { break; } using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { //Set the insert command structure command.CommandText = @" INSERT INTO `tbl_extendedroundstats` ( `server_id`, `round_id`, `round_elapsedTimeSec`, `team1_count`, `team2_count`, `team1_score`, `team2_score`, `team1_spm`, `team2_spm`, `team1_tickets`, `team2_tickets`, `team1_tpm`, `team2_tpm`, `roundstat_time` ) VALUES ( @server_id, @round_id, @round_elapsedTimeSec, @team1_count, @team2_count, @team1_score, @team2_score, @team1_spm, @team2_spm, @team1_tickets, @team2_tickets, @team1_tpm, @team2_tpm, UTC_TIMESTAMP() )"; command.Parameters.AddWithValue("@server_id", _serverInfo.ServerID); command.Parameters.AddWithValue("@round_id", _roundID); command.Parameters.AddWithValue("@round_elapsedTimeSec", roundTimeSeconds); command.Parameters.AddWithValue("@team1_count", team1.TeamPlayerCount); command.Parameters.AddWithValue("@team2_count", team2.TeamPlayerCount); command.Parameters.AddWithValue("@team1_score", Math.Round(team1.TeamTotalScore, 2)); command.Parameters.AddWithValue("@team2_score", Math.Round(team2.TeamTotalScore, 2)); command.Parameters.AddWithValue("@team1_spm", Math.Round(team1.TeamScoreDifferenceRate, 2)); command.Parameters.AddWithValue("@team2_spm", Math.Round(team2.TeamScoreDifferenceRate, 2)); command.Parameters.AddWithValue("@team1_tickets", team1.TeamTicketCount); command.Parameters.AddWithValue("@team2_tickets", team2.TeamTicketCount); command.Parameters.AddWithValue("@team1_tpm", Math.Round(team1.GetTicketDifferenceRate(), 2)); command.Parameters.AddWithValue("@team2_tpm", Math.Round(team2.GetTicketDifferenceRate(), 2)); if (team1.TeamPlayerCount > 0 || team2.TeamPlayerCount > 0) { try { //Attempt to execute the query if (SafeExecuteNonQuery(command) > 0) { Log.Debug(() => "round stat pushed to database", 5); } } catch (Exception e) { Log.HandleException(new AException("Invalid round stats when posting. " + FormatTimeString(_serverInfo.GetRoundElapsedTime(), 2) + "|" + team1.TeamPlayerCount + "|" + team2.TeamPlayerCount + "|" + Math.Round(team1.TeamTotalScore, 2) + "|" + Math.Round(team2.TeamTotalScore, 2) + "|" + Math.Round(team1.TeamScoreDifferenceRate, 2) + "|" + team1.TeamScoreDifferenceRate + "|" + Math.Round(team2.TeamScoreDifferenceRate, 2) + "|" + team2.TeamScoreDifferenceRate + "|" + team1.TeamTicketCount + "|" + team2.TeamTicketCount + "|" + Math.Round(team1.GetTicketDifferenceRate(), 2) + "|" + team1.GetTicketDifferenceRate() + "|" + Math.Round(team2.GetTicketDifferenceRate(), 2) + "|" + team2.GetTicketDifferenceRate(), e)); } } } } watch.Stop(); if (watch.Elapsed.TotalSeconds < 30) { Threading.Wait(TimeSpan.FromSeconds(30) - watch.Elapsed); } roundTimeSeconds += 30; } } catch (Exception e) { Log.HandleException(new AException("Error in round stat logger thread", e)); } Threading.StopWatchdog(); })); if (!Threading.IsAlive("RoundTicketLogger")) { //Start the thread Threading.StartWatchdog(roundLoggerThread); } } catch (Exception e) { Log.HandleException(new AException("Error while starting round ticket logger", e)); } } public override void OnServerInfo(CServerInfo serverInfo) { Log.Debug(() => "Entering OnServerInfo", 7); try { if (_pluginEnabled) { lock (_serverInfo) { if (serverInfo != null) { //Get the server info if (NowDuration(_LastServerInfoReceive).TotalSeconds < 9.5) { return; } _LastServerInfoReceive = UtcNow(); _serverInfo.SetInfoObject(serverInfo); if (serverInfo.TeamScores != null) { List listCurrTeamScore = serverInfo.TeamScores; //During round change, teams don't exist if (listCurrTeamScore.Count > 0) { foreach (TeamScore score in listCurrTeamScore) { ATeam currentTeam; if (!GetTeamByID(score.TeamID, out currentTeam)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } continue; } currentTeam.UpdateTicketCount(score.Score); currentTeam.UpdateTotalScore(_PlayerDictionary.Values.Where(aPlayer => aPlayer.fbpInfo.TeamID == score.TeamID).Aggregate(0, (current, aPlayer) => current + aPlayer.fbpInfo.Score)); } } else { Log.Debug(() => "Server info fired while changing rounds, no teams to parse.", 5); } } ATeam team1, team2; if (!GetTeamByID(1, out team1)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } return; } if (!GetTeamByID(2, out team2)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } return; } ATeam winningTeam = null; ATeam losingTeam = null; ATeam mapUpTeam = null; ATeam mapDownTeam = null; ATeam baserapingTeam = null; ATeam baserapedTeam = null; if (team1.TeamTicketCount > team2.TeamTicketCount) { winningTeam = team1; losingTeam = team2; } else { winningTeam = team2; losingTeam = team1; } if (team1.GetTicketDifferenceRate() > team2.GetTicketDifferenceRate()) { //Team1 has more map than Team2 mapUpTeam = team1; mapDownTeam = team2; } else { //Team2 has more map than Team1 mapUpTeam = team2; mapDownTeam = team1; } if (_DisplayTicketRatesInProconChat && _roundState == RoundState.Playing && GetPlayerCount() >= 4 && team1.TeamTicketCount != _startingTicketCount && team2.TeamTicketCount != _startingTicketCount) { String flagMessage = ""; String winMessage = ""; if (_serverInfo.InfoObject.GameMode == "ConquestLarge0" || _serverInfo.InfoObject.GameMode == "Chainlink0" || _serverInfo.InfoObject.GameMode == "Domination0") { Double winRate = mapUpTeam.GetTicketDifferenceRate(); Double loseRate = mapDownTeam.GetTicketDifferenceRate(); if (_serverInfo.InfoObject.GameMode == "ConquestLarge0" && GameVersion == GameVersionEnum.BF4) { Int32 maxFlags = Int32.MaxValue; switch (_serverInfo.InfoObject.Map) { case "XP0_Metro": maxFlags = 3; break; case "MP_Prison": maxFlags = 5; break; } if ((UtcNow() - _AdKatsRunningTime).TotalMinutes > 2.5 && _firstPlayerListComplete) { if (winRate > -20 && loseRate > -20) { flagMessage = " | Flags equal, "; } else if (loseRate <= -20 && loseRate > -34) { flagMessage = " | " + mapUpTeam.GetTeamIDKey() + " up 1 flag, "; } else if (loseRate <= -34 && loseRate > -38) { flagMessage = " | " + mapUpTeam.GetTeamIDKey() + " up 1-3 flags, "; } else if (loseRate <= -38 && loseRate > -44 || maxFlags == 3) { flagMessage = " | " + mapUpTeam.GetTeamIDKey() + " up 3 flags, "; } else if (loseRate <= -44 && loseRate > -48) { flagMessage = " | " + mapUpTeam.GetTeamIDKey() + " up 3-5 flags, "; } else if (loseRate <= -48 && loseRate > -54 || maxFlags == 5) { flagMessage = " | " + mapUpTeam.GetTeamIDKey() + " up 5 flags, "; } else if (loseRate <= -54 && loseRate > -58) { flagMessage = " | " + mapUpTeam.GetTeamIDKey() + " up 5-7 flags, "; } else if (loseRate <= -58 && loseRate > -64 || maxFlags == 7) { flagMessage = " | " + mapUpTeam.GetTeamIDKey() + " up 7 flags, "; } else if (loseRate <= -64 && loseRate > -68) { flagMessage = " | " + mapUpTeam.GetTeamIDKey() + " up 7-9 flags, "; } else if (loseRate <= -68 && loseRate > -74 || maxFlags == 9) { flagMessage = " | " + mapUpTeam.GetTeamIDKey() + " up 9 flags, "; } else if (loseRate < -74) { flagMessage = " | " + mapUpTeam.GetTeamIDKey() + " up many flags, "; } double t1t = team1.TeamAdjustedTicketAccellerationRate - team2.TeamAdjustedTicketAccellerationRate; double t2t = team2.TeamAdjustedTicketAccellerationRate - team1.TeamAdjustedTicketAccellerationRate; if (Math.Abs(t1t - t2t) < 10) { flagMessage += "not changing."; } else if (t1t > t2t) { flagMessage += team1.GetTeamIDKey() + " gaining ground."; } else { flagMessage += team2.GetTeamIDKey() + " gaining ground."; } } else { flagMessage = " | Calculating flag state."; } } else { flagMessage = " | " + _serverInfo.InfoObject.GameMode; } var t1RawRate = team1.GetRawTicketDifferenceRate(); var t2RawRate = team2.GetRawTicketDifferenceRate(); if (t1RawRate < 0 && t2RawRate < 0) { var t1Duration = TimeSpan.FromMinutes(team1.TeamTicketCount / Math.Abs(t1RawRate)); var t2Duration = TimeSpan.FromMinutes(team2.TeamTicketCount / Math.Abs(t2RawRate)); if (Math.Abs((t1Duration - t2Duration).TotalSeconds) < 60) { winMessage = " | Unsure of winning team."; } else if (t1Duration < t2Duration) { winMessage = " | " + team2.GetTeamIDKey() + " wins in " + FormatTimeString(t1Duration, 2) + "."; } else { winMessage = " | " + team1.GetTeamIDKey() + " wins in " + FormatTimeString(t2Duration, 2) + "."; } } } if ((UtcNow() - _LastTicketRateDisplay).TotalSeconds > 55 || _currentFlagMessage != flagMessage) { _LastTicketRateDisplay = UtcNow(); _currentFlagMessage = flagMessage; ProconChatWrite(Log.FBold(team1.TeamKey + " Rate: " + Math.Round(team1.GetTicketDifferenceRate(), 1) + " t/m | " + team2.TeamKey + " Rate: " + Math.Round(team2.GetTicketDifferenceRate(), 1) + " t/m" + flagMessage + winMessage)); } } if (team1.TeamTicketCount >= 0 && team2.TeamTicketCount >= 0) { _lowestTicketCount = Math.Min(team1.TeamTicketCount, team2.TeamTicketCount); _highestTicketCount = Math.Max(team1.TeamTicketCount, team2.TeamTicketCount); } //Auto-Surrender System if (_surrenderAutoEnable && _roundState == RoundState.Playing && !EventActive() && !_endingRound && (UtcNow() - _lastAutoSurrenderTriggerTime).TotalSeconds > 9.0 && _serverInfo.GetRoundElapsedTime().TotalSeconds > 60 && (UtcNow() - _AdKatsRunningTime).TotalMinutes > 2.5 && _firstPlayerListComplete && //Block system if all possible actions have already taken place this round (getNukeCount(mapUpTeam.TeamID) < _surrenderAutoMaxNukesEachRound || _surrenderAutoNukeResolveAfterMax) && //Block system while a nuke is active NowDuration(_lastNukeTime).TotalSeconds > _surrenderAutoNukeDurationHigh) { Boolean canFire = true; Boolean fired = false; Int32 denyReasonModulo = 1; String denyReason = "Unknown reason"; String readyPercentage = ""; //Action AutoSurrenderAction config_action = AutoSurrenderAction.None; if (_surrenderAutoNukeInstead) { if (getNukeCount(mapUpTeam.TeamID) < _surrenderAutoMaxNukesEachRound) { config_action = AutoSurrenderAction.Nuke; } else if (_surrenderAutoNukeResolveAfterMax) { config_action = AutoSurrenderAction.Surrender; } else { config_action = AutoSurrenderAction.None; } } else if (_surrenderAutoTriggerVote) { config_action = AutoSurrenderAction.Vote; } else { config_action = AutoSurrenderAction.Surrender; } if (config_action != AutoSurrenderAction.None) { //State Boolean config_resumed = _surrenderAutoTriggerCountCurrent > 0 && _surrenderAutoTriggerCountCurrent == _surrenderAutoTriggerCountPause; //Tickets Int32 config_tickets_min = 0; Int32 config_tickets_max = 9999; Int32 config_tickets_gap_min = 0; //Rates Double config_mapUp_rate_max = 0; Double config_mapUp_rate_min = 0; Double config_mapDown_rate_max = 0; Double config_mapDown_rate_min = 0; //Triggers Int32 config_triggers_min = 0; //Set automatic values for metro 2014 if (_surrenderAutoUseMetroValues) { //Tickets config_tickets_min = _surrenderAutoMinimumTicketCount; config_tickets_max = _surrenderAutoMaximumTicketCount; config_tickets_gap_min = _surrenderAutoMinimumTicketGap; //Rates config_mapDown_rate_max = -42; config_mapDown_rate_min = -1000; config_mapUp_rate_max = 1000; config_mapUp_rate_min = -5; //Triggers if (config_action == AutoSurrenderAction.Surrender) { config_triggers_min = 20; //Add modification based on ticket count if (losingTeam.TeamTicketCount <= 600) { config_triggers_min -= (600 - losingTeam.TeamTicketCount) / 30; } //Add modification based on automatic assist if (_PlayersAutoAssistedThisRound) { config_triggers_min *= 2; } } else { config_triggers_min = 4; } } //Set automatic values for operation locker else if (_surrenderAutoUseLockerValues) { //Tickets config_tickets_min = _surrenderAutoMinimumTicketCount; config_tickets_max = _surrenderAutoMaximumTicketCount; config_tickets_gap_min = _surrenderAutoMinimumTicketGap; //Rates config_mapDown_rate_max = -50; config_mapDown_rate_min = -1000; config_mapUp_rate_max = 1000; config_mapUp_rate_min = -5; //Triggers if (config_action == AutoSurrenderAction.Surrender) { config_triggers_min = 20; //Add modification based on ticket count if (losingTeam.TeamTicketCount <= 600) { config_triggers_min -= (600 - losingTeam.TeamTicketCount) / 30; } //Add modification based on automatic assist if (_PlayersAutoAssistedThisRound) { config_triggers_min *= 2; } } else { config_triggers_min = 4; } } //Set custom values based on the user else { //Tickets config_tickets_min = _surrenderAutoMinimumTicketCount; config_tickets_max = _surrenderAutoMaximumTicketCount; config_tickets_gap_min = _surrenderAutoMinimumTicketGap; //Rates config_mapDown_rate_max = _surrenderAutoLosingRateMax; config_mapDown_rate_min = _surrenderAutoLosingRateMin; config_mapUp_rate_max = _surrenderAutoWinningRateMax; config_mapUp_rate_min = _surrenderAutoWinningRateMin; //Triggers config_triggers_min = _surrenderAutoTriggerCountToSurrender; } //Add modification based on population if (config_action == AutoSurrenderAction.Nuke && config_triggers_min < 5 && (_populationStatus == PopulationState.Low || _populationStatus == PopulationState.Medium)) { config_triggers_min = 5; } int playerCount = GetPlayerCount(); int neededPlayers = Math.Max(_surrenderAutoMinimumPlayers - playerCount, 0); var ticketGap = Math.Abs(winningTeam.TeamTicketCount - losingTeam.TeamTicketCount); if (canFire && neededPlayers > 0) { canFire = false; denyReason = neededPlayers + " more players needed."; } var downRate = mapDownTeam.GetTicketDifferenceRate(); var upRate = mapUpTeam.GetTicketDifferenceRate(); var validRateWindow = downRate <= config_mapDown_rate_max && downRate >= config_mapDown_rate_min && upRate <= config_mapUp_rate_max && upRate >= config_mapUp_rate_min; var validTicketBasedNuke = config_action == AutoSurrenderAction.Nuke && mapUpTeam == winningTeam && ticketGap > _NukeWinningTeamUpTicketCount && (!_NukeWinningTeamUpTicketHigh || _populationStatus == PopulationState.High) && getNukeCount(mapUpTeam.TeamID) < 1; var validTeams = config_action == AutoSurrenderAction.Nuke || winningTeam == mapUpTeam; if ((validRateWindow || validTicketBasedNuke) && validTeams) { //Fire triggers _lastAutoSurrenderTriggerTime = UtcNow(); _surrenderAutoTriggerCountCurrent++; readyPercentage = Math.Round(Math.Min((_surrenderAutoTriggerCountCurrent / (Double)config_triggers_min) * 100.0, 100)) + "%"; if (canFire && config_action == AutoSurrenderAction.Nuke && getNukeCount(mapUpTeam.TeamID) > 0 && NowDuration(_lastNukeTime).TotalSeconds < _surrenderAutoNukeMinBetween) { canFire = false; denyReason = "~" + FormatNowDuration(_lastNukeTime.AddSeconds(_surrenderAutoNukeMinBetween), 2) + " till it can fire again."; } if (canFire && _surrenderAutoTriggerCountCurrent < config_triggers_min) { canFire = false; TimeSpan remaining = TimeSpan.FromSeconds((config_triggers_min - _surrenderAutoTriggerCountCurrent) * 10); denyReason = "~" + FormatTimeString(remaining, 2) + " till it can fire."; } if (canFire && config_action == AutoSurrenderAction.Nuke && mapUpTeam != winningTeam) { //Losing team is the one with all flags capped if (_surrenderAutoNukeLosingTeams) { if (ticketGap > _surrenderAutoNukeLosingMaxDiff) { canFire = false; denyReasonModulo = 3; denyReason = mapUpTeam.TeamKey + " losing by more than " + _surrenderAutoNukeLosingMaxDiff + " tickets."; } } else { canFire = false; denyReasonModulo = 3; denyReason = mapUpTeam.TeamKey + " is losing."; } } if (canFire && winningTeam.TeamTicketCount > config_tickets_max) { canFire = false; denyReasonModulo = 2; denyReason = winningTeam.TeamKey + " has more than " + config_tickets_max + " tickets. (" + winningTeam.TeamTicketCount + ")"; } if (canFire && losingTeam.TeamTicketCount < config_tickets_min) { canFire = false; denyReasonModulo = 2; denyReason = losingTeam.TeamKey + " has less than " + config_tickets_min + " tickets. (" + losingTeam.TeamTicketCount + ")"; } if (canFire && ticketGap < config_tickets_gap_min) { canFire = false; denyReasonModulo = 2; denyReason = "Less than " + config_tickets_gap_min + " tickets between teams. (" + ticketGap + ")"; } if (canFire) { fired = true; switch (config_action) { case AutoSurrenderAction.Surrender: baserapingTeam = winningTeam; baserapedTeam = losingTeam; break; case AutoSurrenderAction.Nuke: if (_surrenderAutoNukeLosingTeams) { baserapingTeam = mapUpTeam; baserapedTeam = mapDownTeam; } else { baserapingTeam = winningTeam; baserapedTeam = losingTeam; } break; case AutoSurrenderAction.Vote: baserapingTeam = winningTeam; baserapedTeam = losingTeam; break; } } else { if (config_resumed) { if (config_action == AutoSurrenderAction.Nuke) { if (_surrenderAutoAnnounceNukePrep) { AdminSayMessage("Auto-nuke countdown resumed at " + readyPercentage + ". " + denyReason); } } else { OnlineAdminSayMessage("Auto-surrender countdown resumed at " + readyPercentage + ". " + denyReason); } } //How often the message should be displayed else if ((_surrenderAutoTriggerCountCurrent == 1 || _surrenderAutoTriggerCountCurrent % 3 == 0 || (config_action == AutoSurrenderAction.Nuke && neededPlayers <= 10) || _surrenderAutoTriggerVote) // Only show the nuke messages for rounds of 4 or more players && playerCount >= 4) { if (_surrenderAutoTriggerCountCurrent < config_triggers_min) { if (config_action == AutoSurrenderAction.Nuke) { if (_surrenderAutoAnnounceNukePrep && (mapUpTeam.TeamID == winningTeam.TeamID || _surrenderAutoNukeLosingTeams)) { AdminSayMessage(mapUpTeam.TeamKey + " auto-nuke " + (getNukeCount(mapUpTeam.TeamID) + 1) + " " + readyPercentage + " ready. " + denyReason); } } else { OnlineAdminSayMessage("Auto-surrender " + readyPercentage + " ready. " + denyReason); } } else { if (config_action == AutoSurrenderAction.Nuke) { if (_surrenderAutoAnnounceNukePrep && _surrenderAutoTriggerCountCurrent % denyReasonModulo == 0) { AdminSayMessage(mapUpTeam.TeamKey + " auto-nuke " + (getNukeCount(mapUpTeam.TeamID) + 1) + " ready and waiting. " + denyReason); } } else { OnlineAdminSayMessage("Auto-surrender ready and waiting. " + denyReason); } } } if (!_Team1MoveQueue.Any() && !_Team2MoveQueue.Any() && _serverInfo.GetRoundElapsedTime().TotalSeconds > 120) { Dictionary auaPlayers = new Dictionary(); //Get players from the auto-assist blacklist foreach (APlayer aPlayer in GetOnlinePlayersOfGroup("blacklist_autoassist").Where(aPlayer => aPlayer.fbpInfo.TeamID == winningTeam.TeamID)) { if (!auaPlayers.ContainsKey(aPlayer.player_name)) { auaPlayers[aPlayer.player_name] = aPlayer; } } foreach (APlayer aPlayer in auaPlayers.Values) { if (PlayerIsAdmin(aPlayer)) { continue; } OnlineAdminSayMessage(aPlayer.GetVerboseName() + " being automatically assisted to weak team."); PlayerTellMessage(aPlayer.player_name, Log.CViolet("You are being automatically assisted to the weak team.")); Thread.Sleep(2000); _PlayersAutoAssistedThisRound = true; QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("self_assist"), command_action = GetCommandByKey("self_assist_unconfirmed"), target_name = aPlayer.player_name, target_player = aPlayer, source_name = "AUAManager", record_message = "Assist Weak Team [" + winningTeam.TeamTicketCount + ":" + losingTeam.TeamTicketCount + "][" + FormatTimeString(_serverInfo.GetRoundElapsedTime(), 3) + "]", record_time = UtcNow() }); } } } } else { //Server is outside of auto-surrender window, send update messages if needed readyPercentage = Math.Round((Math.Min(_surrenderAutoTriggerCountCurrent, config_triggers_min) / (Double)config_triggers_min) * 100.0) + "%"; if (_surrenderAutoResetTriggerCountOnCancel) { if (_surrenderAutoTriggerCountCurrent > 0) { if (config_action == AutoSurrenderAction.Nuke) { if (_surrenderAutoAnnounceNukePrep) { AdminSayMessage("Auto-nuke countdown cancelled."); } } else { OnlineAdminSayMessage("Auto-surrender countdown cancelled."); } } _surrenderAutoTriggerCountCurrent = 0; } else { if (_surrenderAutoTriggerCountCurrent > 0 && _surrenderAutoTriggerCountCurrent != _surrenderAutoTriggerCountPause) { _surrenderAutoTriggerCountPause = _surrenderAutoTriggerCountCurrent; if (_surrenderAutoTriggerCountCurrent < config_triggers_min) { if (config_action == AutoSurrenderAction.Nuke) { if (_surrenderAutoAnnounceNukePrep) { AdminSayMessage("Auto-nuke countdown paused at " + readyPercentage + "."); } } else { OnlineAdminSayMessage("Auto-surrender countdown paused at " + readyPercentage + "."); } } else { if (config_action == AutoSurrenderAction.Nuke) { if (_surrenderAutoAnnounceNukePrep) { AdminSayMessage("Auto-nuke countdown paused."); } } else { OnlineAdminSayMessage("Auto-surrender countdown paused."); } } } } } if (fired) { if (_surrenderAutoResetTriggerCountOnFire) { _surrenderAutoTriggerCountCurrent = 0; _surrenderAutoTriggerCountPause = 0; } if (config_action == AutoSurrenderAction.Nuke) { string autoNukeMessage = _surrenderAutoNukeMessage.Replace("%WinnerName%", baserapingTeam.TeamName); QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("server_nuke"), command_numeric = baserapingTeam.TeamID, target_name = baserapingTeam.TeamName, source_name = "RoundManager", record_message = autoNukeMessage, record_time = UtcNow() }); } else if (_surrenderAutoTriggerVote) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("self_votenext"), command_numeric = 0, target_name = "RoundManager", source_name = "RoundManager", record_message = "Auto-Starting Surrender Vote", record_time = UtcNow() }); } else if (!_endingRound) { _endingRound = true; _surrenderAutoSucceeded = true; Thread roundEndDelayThread = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a round end delay thread.", 5); try { Thread.CurrentThread.Name = "RoundEndDelay"; string autoSurrenderMessage = _surrenderAutoMessage.Replace("%WinnerName%", baserapingTeam.TeamName); for (int i = 0; i < 8; i++) { AdminTellMessage(autoSurrenderMessage); Thread.Sleep(50); } Threading.Wait(1000 * _YellDuration); ARecord repRecord = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("round_end"), command_numeric = baserapingTeam.TeamID, target_name = baserapingTeam.TeamName, source_name = "RoundManager", record_message = "Auto-Surrender (" + baserapingTeam.GetTeamIDKey() + " Win)(" + baserapingTeam.TeamTicketCount + ":" + baserapedTeam.TeamTicketCount + ")(" + FormatTimeString(_serverInfo.GetRoundElapsedTime(), 2) + ")", record_time = UtcNow() }; QueueRecordForProcessing(repRecord); } catch (Exception) { Log.HandleException(new AException("Error while running round end delay.")); } Log.Debug(() => "Exiting a round end delay thread.", 5); Threading.StopWatchdog(); })); Threading.StartWatchdog(roundEndDelayThread); } } } } _serverInfo.ServerName = serverInfo.ServerName; if (!_pluginUpdateServerInfoChecked) { _pluginUpdateServerInfoChecked = true; CheckForPluginUpdates(false); } FeedStatLoggerSettings(); } else { Log.HandleException(new AException("Server info was null")); } _ServerInfoWaitHandle.Set(); } } } catch (Exception e) { Log.HandleException(new AException("Error while processing server info.", e)); } Log.Debug(() => "Exiting OnServerInfo", 7); } public override void OnSoldierHealth(Int32 limit) { _soldierHealth = limit; } public void PostAndResetMapBenefitStatistics() { Log.Debug(() => "Entering PostAndResetMapBenefitStatistics", 7); try { if (_PostMapBenefitStatistics && _serverInfo != null && _serverInfo.InfoObject != null) { Int32 roundID = _roundID; String mapName = _serverInfo.InfoObject.Map; if (roundID > 0 && !String.IsNullOrEmpty(mapName)) { QueueStatisticForProcessing(new AStatistic() { stat_type = AStatistic.StatisticType.map_detriment, server_id = _serverInfo.ServerID, round_id = _roundID, target_name = mapName, stat_value = _mapDetrimentIndex, stat_comment = _mapDetrimentIndex + " players left because of " + mapName, stat_time = UtcNow() }); QueueStatisticForProcessing(new AStatistic() { stat_type = AStatistic.StatisticType.map_benefit, server_id = _serverInfo.ServerID, round_id = _roundID, target_name = mapName, stat_value = _mapBenefitIndex, stat_comment = _mapBenefitIndex + " players joined because of " + mapName, stat_time = UtcNow() }); } } } catch (Exception e) { Log.HandleException(new AException("Error while preparing map stats for upload", e)); } _mapDetrimentIndex = 0; _mapBenefitIndex = 0; Log.Debug(() => "Exiting PostAndResetMapBenefitStatistics", 7); } public void PostRoundStatistics(ATeam winningTeam, ATeam losingTeam) { Log.Debug(() => "Entering PostRoundStatistics", 7); try { List OrderedPlayers = _PlayerDictionary.Values .Where(aPlayer => aPlayer.player_type == PlayerType.Player).ToList(); // Do not use their stored power, since that would skew the numbers OrderedPlayers = OrderedPlayers.OrderByDescending(aPlayer => aPlayer.GetPower(false, true, false)).ToList(); List WinningPlayers = OrderedPlayers .Where(aPlayer => aPlayer.fbpInfo.TeamID == winningTeam.TeamID).ToList(); List LosingPlayers = OrderedPlayers .Where(aPlayer => aPlayer.fbpInfo.TeamID == losingTeam.TeamID).ToList(); foreach (APlayer aPlayer in WinningPlayers) { QueueStatisticForProcessing(new AStatistic() { stat_type = AStatistic.StatisticType.player_win, server_id = _serverInfo.ServerID, round_id = _roundID, target_name = aPlayer.player_name, target_player = aPlayer, stat_value = aPlayer.fbpInfo.SquadID, stat_comment = aPlayer.player_name + " won", stat_time = UtcNow() }); } foreach (APlayer aPlayer in LosingPlayers) { QueueStatisticForProcessing(new AStatistic() { stat_type = AStatistic.StatisticType.player_loss, server_id = _serverInfo.ServerID, round_id = _roundID, target_name = aPlayer.player_name, target_player = aPlayer, stat_value = aPlayer.fbpInfo.SquadID, stat_comment = aPlayer.player_name + " lost", stat_time = UtcNow() }); } var TopOrdered = OrderedPlayers.Take((Int32)(OrderedPlayers.Count / 3.75)).ToList(); foreach (APlayer aPlayer in TopOrdered) { QueueStatisticForProcessing(new AStatistic() { stat_type = AStatistic.StatisticType.player_top, server_id = _serverInfo.ServerID, round_id = _roundID, target_name = aPlayer.player_name, target_player = aPlayer, stat_value = aPlayer.fbpInfo.SquadID, stat_comment = aPlayer.player_name + " top player in position " + (WinningPlayers.IndexOf(aPlayer) + 1), stat_time = UtcNow() }); } } catch (Exception e) { Log.HandleException(new AException("Error while preparing round stats for upload", e)); } Log.Debug(() => "Exiting PostRoundStatistics", 7); } public override void OnLevelLoaded(String strMapFileName, String strMapMode, Int32 roundsPlayed, Int32 roundsTotal) { Log.Debug(() => "Entering OnLevelLoaded", 7); try { if (_pluginEnabled) { if (_LevelLoadShutdown) { Environment.Exit(2232); } //Upload map benefit/detriment statistics PostAndResetMapBenefitStatistics(); //Change round state _roundState = RoundState.Loaded; //Request new server info DoServerInfoTrigger(); //Completely clear all round-specific data _endingRound = false; _surrenderVoteList.Clear(); _nosurrenderVoteList.Clear(); _surrenderVoteActive = false; _surrenderVoteSucceeded = false; _surrenderAutoSucceeded = false; _surrenderAutoTriggerCountCurrent = 0; _surrenderAutoTriggerCountPause = 0; _nukesThisRound.Clear(); _lastNukeTeam = null; _roundAssists.Clear(); _PlayersAutoAssistedThisRound = false; _RoundMutedPlayers.Clear(); _ActionConfirmDic.Clear(); _ActOnSpawnDictionary.Clear(); _ActOnIsAliveDictionary.Clear(); _TeamswapOnDeathMoveDic.Clear(); _Team1MoveQueue.Clear(); _Team2MoveQueue.Clear(); lock (_AssistAttemptQueue) { _AssistAttemptQueue.Clear(); } _RoundCookers.Clear(); _unmatchedRoundDeathCounts.Clear(); _unmatchedRoundDeaths.Clear(); //Update the factions UpdateFactions(); getMapInfo(); StartRoundTicketLogger(0); if (ChallengeManager != null) { ChallengeManager.OnRoundLoaded(_roundID); } } } catch (Exception e) { Log.HandleException(new AException("Error while handling level load.", e)); } Log.Debug(() => "Exiting OnLevelLoaded", 7); } //Round ended stuff public override void OnRoundOverPlayers(List players) { _roundOverPlayers = players; } private void ProcessPresetHardcore() { var delayMS = 250; //ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.friendlyFire", "true"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.killCam", "false"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.miniMap", "false"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.nameTag", "false"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.regenerateHealth", "false"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.hud", "false"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.onlySquadLeaderSpawn", "true"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.3dSpotting", "false"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.3pCam", "false"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.hud", "false"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.soldierHealth", "60"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.hitIndicatorsEnabled", "false"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.forceReloadWholeMags", "true"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.preset", "HARDCORE", "false"); } private void ProcessPresetNormal() { var delayMS = 250; ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.friendlyFire", "false"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.killCam", "true"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.miniMap", "true"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.nameTag", "true"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.regenerateHealth", "true"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.hud", "true"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.onlySquadLeaderSpawn", "false"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.3dSpotting", "true"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.3pCam", "true"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.hud", "true"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.soldierHealth", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.hitIndicatorsEnabled", "true"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.forceReloadWholeMags", "false"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.preset", "NORMAL", "false"); } private void ProcessEventMapMode(Int32 eventRoundNumber) { ProcessEventMapMode(GetEventRoundMapCode(eventRoundNumber), GetEventRoundModeCode(eventRoundNumber)); } private void ProcessEventMapMode(AEventOption.MapCode mapCode, AEventOption.ModeCode modeCode) { var delayMS = 250; Log.Debug(() => "Entering ProcessEventMapMode", 7); try { var mapFile = "XP0_Metro"; switch (mapCode) { case AEventOption.MapCode.MET: mapFile = "XP0_Metro"; break; case AEventOption.MapCode.LOC: mapFile = "MP_Prison"; break; } switch (modeCode) { case AEventOption.ModeCode.UNKNOWN: Int32 GoalTickets = 0; Double TicketRatio = 0; Int32 GMC = 0; break; case AEventOption.ModeCode.T100: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "TeamDeathMatch0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", "100"); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.T200: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "TeamDeathMatch0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", "200"); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.T300: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "TeamDeathMatch0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", "300"); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.T400: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "TeamDeathMatch0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", "400"); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.R200: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "RushLarge0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", "200"); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.R300: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "RushLarge0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", "300"); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.R400: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "RushLarge0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", "400"); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.C500: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "ConquestLarge0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); GoalTickets = 500; TicketRatio = 800 / 100.0; GMC = (Int32)Math.Ceiling(GoalTickets / TicketRatio); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", GMC.ToString()); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.C1000: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "ConquestLarge0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); GoalTickets = 1000; TicketRatio = 800 / 100.0; GMC = (Int32)Math.Ceiling(GoalTickets / TicketRatio); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", GMC.ToString()); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.C2000: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "ConquestLarge0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); GoalTickets = 2000; TicketRatio = 800 / 100.0; GMC = (Int32)Math.Ceiling(GoalTickets / TicketRatio); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", GMC.ToString()); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.F9: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "CaptureTheFlag0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", "300"); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.F6: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "CaptureTheFlag0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", "200"); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.F3: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "CaptureTheFlag0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", "100"); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.D500: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "Domination0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); GoalTickets = 500; TicketRatio = 300 / 100.0; GMC = (Int32)Math.Ceiling(GoalTickets / TicketRatio); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", GMC.ToString()); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.HD500: ProcessPresetHardcore(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "Domination0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); GoalTickets = 500; TicketRatio = 300 / 100.0; GMC = (Int32)Math.Ceiling(GoalTickets / TicketRatio); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", GMC.ToString()); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.D750: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "Domination0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); GoalTickets = 750; TicketRatio = 300 / 100.0; GMC = (Int32)Math.Ceiling(GoalTickets / TicketRatio); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", GMC.ToString()); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.D1000: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "100"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", mapFile, "Domination0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); GoalTickets = 1000; TicketRatio = 300 / 100.0; GMC = (Int32)Math.Ceiling(GoalTickets / TicketRatio); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", GMC.ToString()); Log.Info("Event round setup complete!"); OnlineAdminSayMessage("Event round setup complete!"); break; case AEventOption.ModeCode.RESET: ProcessPresetNormal(); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.roundTimeLimit", "300"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.clear"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", "XP0_Metro", "ConquestLarge0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.add", "MP_Prison", "ConquestLarge0", "1"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.save"); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "mapList.setNextMapIndex", "0"); GoalTickets = 1600; TicketRatio = 800 / 100.0; GMC = (Int32)Math.Ceiling(GoalTickets / TicketRatio); ExecuteCommandWithDelay(delayMS, "procon.protected.send", "vars.gameModeCounter", GMC.ToString()); Log.Info("Event RESET complete!"); break; default: Log.Error("Unknown mode type when processing event transition."); break; } } catch (Exception e) { Log.HandleException(new AException("Error while processing event map mode.", e)); } finally { Log.Debug(() => "Exiting ProcessEventMapMode", 7); } } public override void OnRoundOverTeamScores(List teamScores) { try { //Set round duration _previousRoundDuration = _serverInfo.GetRoundElapsedTime(); //Update the live team scores if (teamScores != null) { foreach (var teamScore in teamScores) { ATeam aTeam; if (GetTeamByID(teamScore.TeamID, out aTeam)) { aTeam.UpdateTicketCount(teamScore.Score); } } } ATeam team1, team2; if (!GetTeamByID(1, out team1)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } return; } if (!GetTeamByID(2, out team2)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } return; } ATeam winningTeam, losingTeam; if (team1.TeamTicketCount > team2.TeamTicketCount) { winningTeam = team1; losingTeam = team2; } else { winningTeam = team2; losingTeam = team1; } lock (_AssistAttemptQueue) { _AssistAttemptQueue.Clear(); } // EVENT AUTOMATION if (_UseExperimentalTools && _EventRoundOptions.Any() && _EventDate.ToShortDateString() != GetLocalEpochTime().ToShortDateString()) { var nRound = _roundID + 1; Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "EventAnnounce"; // If there is an active poll auto-complete it and any subsequent polls for 5 seconds // This ensures that the next round is ready and configured var startTime = UtcNow(); Threading.Wait(100); while (NowDuration(startTime).TotalSeconds < 5) { try { if (_ActivePoll != null) { _ActivePoll.Completed = true; } } catch (Exception) { } Threading.Wait(100); } // The new _roundID is fetched by now if (EventActive(nRound + 1)) { // The round before the event, make sure xVotemap is not active // The map voting will be handled by the event script ExecuteCommand("procon.protected.plugins.enable", "xVotemap", "False"); } else if (EventActive(nRound)) { var nextCode = GetEventRoundRuleCode(GetActiveEventRoundNumber(false)); if (nextCode == AEventOption.RuleCode.AO || nextCode == AEventOption.RuleCode.ARO || nextCode == AEventOption.RuleCode.LMGO || nextCode == AEventOption.RuleCode.BKO || nextCode == AEventOption.RuleCode.CAI || nextCode == AEventOption.RuleCode.PO) { ExecuteCommand("procon.protected.plugins.enable", "AdKatsLRT", "True"); } else { ExecuteCommand("procon.protected.plugins.enable", "AdKatsLRT", "False"); } SetExternalPluginSetting("AdKatsLRT", "Spawn Enforce Admins", "True"); SetExternalPluginSetting("AdKatsLRT", "Spawn Enforce Reputable Players", "True"); ExecuteCommand("procon.protected.plugins.enable", "xVotemap", "False"); //ACTIVE ROUND for (int i = 0; i < 8; i++) { AdminTellMessage("PREPARING EVENT! " + GetEventMessage(false)); Thread.Sleep(2000); } ProcessEventMapMode(GetActiveEventRoundNumber(false)); } else if (nRound == _EventTestRoundNumber) { //TEST ROUND for (int i = 0; i < 8; i++) { AdminTellMessage("PREPARING EVENT! TESTING! TESTING!"); Thread.Sleep(2000); } ProcessEventMapMode(AEventOption.MapCode.MET, AEventOption.ModeCode.D500); } else if (nRound >= _CurrentEventRoundNumber + _EventRoundOptions.Count()) { //NORMAL ROUND // Reset the current event number, as the event has ended. _CurrentEventRoundNumber = 999999; QueueSettingForUpload(new CPluginVariable(@"Event Current Round Number", typeof(Int32), _CurrentEventRoundNumber)); ExecuteCommand("procon.protected.plugins.enable", "AdKatsLRT", "True"); ExecuteCommand("procon.protected.plugins.enable", "xVotemap", "True"); SetExternalPluginSetting("AdKatsLRT", "Spawn Enforce Admins", "False"); SetExternalPluginSetting("AdKatsLRT", "Spawn Enforce Reputable Players", "False"); for (int i = 0; i < 6; i++) { AdminTellMessage("EVENT IS OVER, THANK YOU FOR COMING!"); Thread.Sleep(2000); } ProcessEventMapMode(AEventOption.MapCode.MET, AEventOption.ModeCode.RESET); for (int i = 0; i < 10; i++) { AdminTellMessage("EVENT IS OVER, THANK YOU FOR COMING!"); Thread.Sleep(2000); } } UpdateSettingPage(); Threading.StopWatchdog(); }))); } if (_serverInfo.ServerName.Contains("[FPSG] 24/7 Operation Lockers")) { Int32 quality = 4; if (winningTeam.TeamTicketCount >= 1500) { quality = 0; } else if (winningTeam.TeamTicketCount >= 1100) { quality = 1; } else if (winningTeam.TeamTicketCount >= 700) { quality = 2; } else if (winningTeam.TeamTicketCount >= 350) { quality = 3; } QueueStatisticForProcessing(new AStatistic() { stat_type = AStatistic.StatisticType.round_quality, server_id = _serverInfo.ServerID, round_id = _roundID, target_name = _serverInfo.InfoObject.Map, stat_value = quality, stat_comment = "Quality level " + quality + " (" + winningTeam.TeamTicketCount + "|" + losingTeam.TeamTicketCount + ")", stat_time = UtcNow() }); } else if (_serverInfo.ServerName.Contains("[FPSG] 24/7 Metro Madness")) { Int32 quality = 4; if (winningTeam.TeamTicketCount >= 1100) { quality = 0; } else if (winningTeam.TeamTicketCount >= 800) { quality = 1; } else if (winningTeam.TeamTicketCount >= 600) { quality = 2; } else if (winningTeam.TeamTicketCount >= 400) { quality = 3; } QueueStatisticForProcessing(new AStatistic() { stat_type = AStatistic.StatisticType.round_quality, server_id = _serverInfo.ServerID, round_id = _roundID, target_name = _serverInfo.InfoObject.Map, stat_value = quality, stat_comment = "Quality level " + quality + " (" + winningTeam.TeamTicketCount + "|" + losingTeam.TeamTicketCount + ")", stat_time = UtcNow() }); } //Post round stats PostRoundStatistics(winningTeam, losingTeam); //Wait for round over players to be fired, if not already var start = UtcNow(); while (_roundOverPlayers == null && (UtcNow() - start).TotalSeconds < 10) { Thread.Sleep(100); } if ((UtcNow() - start).TotalSeconds >= 10) { Log.Error("Round over players waiting timed out!"); } if (_roundOverPlayers != null) { if (_UseTeamPowerMonitorScrambler) { //Clear out the round over squad list _RoundPrepSquads.Clear(); //Update all players with their final stats foreach (var roundPlayerData in _roundOverPlayers) { APlayer aPlayer; if (_PlayerDictionary.TryGetValue(roundPlayerData.SoldierName, out aPlayer) && aPlayer.player_type == PlayerType.Player) { aPlayer.fbpInfo = roundPlayerData; APlayerStats aStats; if (aPlayer.RoundStats.TryGetValue(_roundID, out aStats)) { aStats.LiveStats = roundPlayerData; } var squadIdentifier = aPlayer.fbpInfo.TeamID.ToString() + aPlayer.fbpInfo.SquadID.ToString(); ASquad squad = _RoundPrepSquads.FirstOrDefault(iSquad => iSquad.TeamID == aPlayer.fbpInfo.TeamID && iSquad.SquadID == aPlayer.fbpInfo.SquadID); // If the squad isn't loaded yet, load it if (squad == null) { squad = new ASquad(this) { TeamID = aPlayer.fbpInfo.TeamID, SquadID = aPlayer.fbpInfo.SquadID }; _RoundPrepSquads.Add(squad); } // Store the player squad.Players.Add(aPlayer); } } foreach (var squad in _RoundPrepSquads.OrderBy(squad => squad.TeamID).ThenByDescending(squad => squad.Players.Sum(member => member.GetPower(true)))) { Log.Info("Squad " + squad); } Log.Success(_RoundPrepSquads.Count() + " squads logged for next round."); } //Unassign round over players, wait for next round _roundOverPlayers = null; } else { Log.Error("Round over players not found/ready! Contact ColColonCleaner."); } if (ChallengeManager != null) { ChallengeManager.OnRoundEnded(_roundID); } //Stat refresh List roundPlayerObjects; HashSet roundPlayers; if (_roundID > 0 && _RoundPlayerIDs.TryGetValue(_roundID, out roundPlayers) && _useAntiCheatLIVESystem) { //Get players who where online this round roundPlayerObjects = GetFetchedPlayers().Where(dPlayer => roundPlayers.Contains(dPlayer.player_id)).ToList(); //TODO: Clear out the total score/kills/deaths //Queue players for stats refresh Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "StatRefetch"; Thread.Sleep(TimeSpan.FromSeconds(30)); foreach (var aPlayer in roundPlayerObjects) { if (_UseBanEnforcer) { QueuePlayerForBanCheck(aPlayer); } else { //Queue the player for a AntiCheat check QueuePlayerForAntiCheatCheck(aPlayer); } } Threading.StopWatchdog(); }))); } RunFactionRandomizer(); FetchRoundID(true); _roundState = RoundState.Ended; _EventRoundPolled = false; _pingKicksThisRound = 0; _ScrambleRequiredTeamsRemoved = false; foreach (APlayer aPlayer in GetFetchedPlayers().Where(aPlayer => aPlayer.RequiredTeam != null)) { aPlayer.RequiredTeam = null; aPlayer.RequiredSquad = -1; aPlayer.player_spawnedRound = false; } if (_UseExperimentalTools) { // This might look a little odd, but since AdKats is first in the // plugin loading list and the endround events are fired sync instead // of async I can artificially delay other plugins from acting on the // end round event for a few seconds. Obviously this is experimental. Threading.Wait(5000); } } catch (Exception e) { Log.HandleException(new AException("Error running round over teamscores.", e)); } } public void RunFactionRandomizer() { Log.Debug(() => "Entering RunFactionRandomizer", 6); try { //Faction Randomizer //Credit to LumPenPacK if (_factionRandomizerEnable && GameVersion == GameVersionEnum.BF4) { var nextMap = _serverInfo.GetNextMap(); if (((_serverInfo.InfoObject.CurrentRound + 1) >= _serverInfo.InfoObject.TotalRounds) && (nextMap != null && (nextMap.MapFileName == "XP3_UrbanGdn" || nextMap.MapFileName == "X0_Oman"))) { //Cannot change things on urban garden or oman Log.Info("Cannot run faction randomizer on urban garden or gulf of oman."); return; } var team1Selection = 0; var team2Selection = 1; var selectionValid = false; var attempts = 0; Random rnd = new Random(); Int32 US = 0; Int32 RU = 1; Int32 CN = 2; while (!selectionValid && ++attempts < 1000) { switch (_factionRandomizerRestriction) { case FactionRandomizerRestriction.NoRestriction: team1Selection = rnd.Next(0, 3); team2Selection = rnd.Next(0, 3); break; case FactionRandomizerRestriction.NeverSameFaction: team1Selection = rnd.Next(0, 3); team2Selection = rnd.Next(0, 3); if (team1Selection == team2Selection) { continue; } break; case FactionRandomizerRestriction.AlwaysSameFaction: team1Selection = rnd.Next(0, 3); team2Selection = team1Selection; break; case FactionRandomizerRestriction.AlwaysSwapUSvsRU: if (_factionRandomizerCurrentTeam1 == US && _factionRandomizerCurrentTeam2 == RU) { team1Selection = RU; team2Selection = US; } else { team1Selection = US; team2Selection = RU; } break; case FactionRandomizerRestriction.AlwaysSwapUSvsCN: if (_factionRandomizerCurrentTeam1 == US && _factionRandomizerCurrentTeam2 == CN) { team1Selection = CN; team2Selection = US; } else { team1Selection = US; team2Selection = CN; } break; case FactionRandomizerRestriction.AlwaysSwapRUvsCN: if (_factionRandomizerCurrentTeam1 == RU && _factionRandomizerCurrentTeam2 == CN) { team1Selection = CN; team2Selection = RU; } else { team1Selection = RU; team2Selection = CN; } break; case FactionRandomizerRestriction.AlwaysBothUS: team1Selection = US; team2Selection = US; break; case FactionRandomizerRestriction.AlwaysBothRU: team1Selection = RU; team2Selection = RU; break; case FactionRandomizerRestriction.AlwaysBothCN: team1Selection = CN; team2Selection = CN; break; case FactionRandomizerRestriction.AlwaysUSvsX: team1Selection = rnd.Next(0, 3); team2Selection = rnd.Next(0, 3); if (team1Selection != US && team2Selection != US) { continue; } break; case FactionRandomizerRestriction.AlwaysRUvsX: team1Selection = rnd.Next(0, 3); team2Selection = rnd.Next(0, 3); if (team1Selection != RU && team2Selection != RU) { continue; } break; case FactionRandomizerRestriction.AlwaysCNvsX: team1Selection = rnd.Next(0, 3); team2Selection = rnd.Next(0, 3); if (team1Selection != CN && team2Selection != CN) { continue; } break; case FactionRandomizerRestriction.NeverUSvsX: team1Selection = rnd.Next(0, 3); team2Selection = rnd.Next(0, 3); if (team1Selection == US || team2Selection == US) { continue; } break; case FactionRandomizerRestriction.NeverRUvsX: team1Selection = rnd.Next(0, 3); team2Selection = rnd.Next(0, 3); if (team1Selection == RU || team2Selection == RU) { continue; } break; case FactionRandomizerRestriction.NeverCNvsX: team1Selection = rnd.Next(0, 3); team2Selection = rnd.Next(0, 3); if (team1Selection == CN || team2Selection == CN) { continue; } break; default: break; } if (!_factionRandomizerAllowRepeatSelection) { //We cannot allow the same teams to be selected again if (_factionRandomizerCurrentTeam1 == team1Selection && _factionRandomizerCurrentTeam2 == team2Selection) { continue; } } selectionValid = true; } if (selectionValid) { _factionRandomizerCurrentTeam1 = team1Selection; _factionRandomizerCurrentTeam2 = team2Selection; ExecuteCommand("procon.protected.send", "vars.teamFactionOverride", "1", Convert.ToString(team1Selection)); ExecuteCommand("procon.protected.send", "vars.teamFactionOverride", "2", Convert.ToString(team2Selection)); ExecuteCommand("procon.protected.send", "vars.teamFactionOverride", "3", Convert.ToString(team1Selection)); ExecuteCommand("procon.protected.send", "vars.teamFactionOverride", "4", Convert.ToString(team2Selection)); } else { Log.Error("Faction randomizer failed!"); } } } catch (Exception e) { Log.HandleException(new AException("Error running faction randomizer.", e)); } Log.Debug(() => "Exiting RunFactionRandomizer", 6); } public override void OnRunNextLevel() { try { if (_roundState != RoundState.Ended) { getMapInfo(); _roundState = RoundState.Ended; _pingKicksThisRound = 0; foreach (APlayer aPlayer in GetFetchedPlayers().Where(aPlayer => aPlayer.RequiredTeam != null)) { aPlayer.RequiredTeam = null; aPlayer.RequiredSquad = -1; aPlayer.player_spawnedRound = false; } } } catch (Exception e) { Log.HandleException(new AException("Error handling next level.", e)); } } //Move delayed players when they are killed public override void OnPlayerKilled(Kill kKillerVictimDetails) { Log.Debug(() => "Entering OnPlayerKilled", 7); try { //If the plugin is not enabled just return if (!_pluginEnabled || !_threadsReady || !_firstPlayerListComplete) { return; } //Used for delayed player moving if (_TeamswapOnDeathMoveDic.Count > 0) { lock (_TeamswapOnDeathCheckingQueue) { _TeamswapOnDeathCheckingQueue.Enqueue(kKillerVictimDetails.Victim); _TeamswapWaitHandle.Set(); } } //Otherwise, queue the kill for processing QueueKillForProcessing(kKillerVictimDetails); } catch (Exception e) { Log.HandleException(new AException("Error while handling onPlayerKilled.", e)); } Log.Debug(() => "Exiting OnPlayerKilled", 7); } public override void OnPlayerIsAlive(string soldierName, bool isAlive) { Log.Debug(() => "Entering OnPlayerIsAlive", 7); try { if (!_pluginEnabled) { return; } if (!_ActOnIsAliveDictionary.ContainsKey(soldierName)) { return; } ARecord aRecord; lock (_ActOnIsAliveDictionary) { if (_ActOnIsAliveDictionary.TryGetValue(soldierName, out aRecord)) { _ActOnIsAliveDictionary.Remove(aRecord.target_player.player_name); aRecord.isAliveChecked = true; switch (aRecord.command_action.command_key) { case "player_kill": case "player_kill_lowpop": if (isAlive) { QueueRecordForActionHandling(aRecord); } else { if (!_ActOnSpawnDictionary.ContainsKey(aRecord.target_player.player_name)) { Log.Debug(() => aRecord.GetTargetNames() + " is dead. Queueing them for kill on-spawn.", 3); SendMessageToSource(aRecord, aRecord.GetTargetNames() + " is dead. Queueing them for kill on-spawn."); ExecuteCommand("procon.protected.send", "admin.killPlayer", aRecord.target_player.player_name); lock (_ActOnSpawnDictionary) { aRecord.command_action = GetCommandByKey("player_kill_repeat"); _ActOnSpawnDictionary.Add(aRecord.target_player.player_name, aRecord); } } } break; case "player_move": //If player is not alive, change to force move if (!isAlive) { aRecord.command_type = GetCommandByKey("player_fmove"); aRecord.command_action = GetCommandByKey("player_fmove"); } QueueRecordForActionHandling(aRecord); break; default: Log.Error("Command " + aRecord.command_action.command_key + " not useable in OnPlayerIsAlive"); break; } } else { Log.Warn(soldierName + " not fetchable from the isalive dictionary."); } } } catch (Exception e) { Log.HandleException(new AException("Error while handling OnPlayerIsAlive.", e)); } Log.Debug(() => "Exiting OnPlayerIsAlive", 7); } private void QueueKillForProcessing(Kill kKillerVictimDetails) { Log.Debug(() => "Entering queueKillForProcessing", 7); try { if (_pluginEnabled && _threadsReady && _firstPlayerListComplete) { Log.Debug(() => "Preparing to queue kill for processing", 6); lock (_KillProcessingQueue) { _KillProcessingQueue.Enqueue(kKillerVictimDetails); Log.Debug(() => "Kill queued for processing", 6); _KillProcessingWaitHandle.Set(); } } } catch (Exception e) { Log.HandleException(new AException("Error while queueing kill for processing.", e)); } Log.Debug(() => "Exiting queueKillForProcessing", 7); } public void KillProcessingThreadLoop() { try { Log.Debug(() => "Starting Kill Processing Thread", 1); Thread.CurrentThread.Name = "KillProcessing"; DateTime loopStart = UtcNow(); while (true) { loopStart = UtcNow(); try { Log.Debug(() => "Entering Kill Processing Thread Loop", 7); if (!_pluginEnabled) { Log.Debug(() => "Detected AdKats not enabled. Exiting thread " + Thread.CurrentThread.Name, 6); break; } //Get all unprocessed inbound kills Queue inboundPlayerKills; if (_KillProcessingQueue.Count > 0) { Log.Debug(() => "Preparing to lock inbound kill queue to retrive new player kills", 7); lock (_KillProcessingQueue) { Log.Debug(() => "Inbound kills found. Grabbing.", 6); //Grab all kills in the queue inboundPlayerKills = new Queue(_KillProcessingQueue.ToArray()); //Clear the queue for next run _KillProcessingQueue.Clear(); } } else { Log.Debug(() => "No inbound player kills. Waiting for Input.", 6); //Wait for input if ((UtcNow() - loopStart).TotalMilliseconds > 1000) { Log.Debug(() => "Warning. " + Thread.CurrentThread.Name + " thread processing completed in " + ((int)((UtcNow() - loopStart).TotalMilliseconds)) + "ms", 4); } _KillProcessingWaitHandle.Reset(); _KillProcessingWaitHandle.WaitOne(TimeSpan.FromSeconds(5)); loopStart = UtcNow(); continue; } //Loop through all kils in order that they came in while (inboundPlayerKills.Count > 0) { if (!_pluginEnabled) { break; } Log.Debug(() => "begin reading player kills", 6); //Dequeue the first/next kill Kill playerKill = inboundPlayerKills.Dequeue(); DamageTypes category = DamageTypes.None; if (playerKill != null && !String.IsNullOrEmpty(playerKill.DamageType)) { category = WeaponDictionary.GetDamageTypeByWeaponCode(playerKill.DamageType); } if (!_DetectedWeaponCodes.Contains(playerKill.DamageType)) { _DetectedWeaponCodes.Add(playerKill.DamageType); } if (!_firstPlayerListComplete) { continue; } APlayer victim = null; _PlayerDictionary.TryGetValue(playerKill.Victim.SoldierName, out victim); APlayer killer = null; _PlayerDictionary.TryGetValue(playerKill.Killer.SoldierName, out killer); if (killer == null || victim == null) { continue; } //Call processing on the player kill ProcessPlayerKill(new AKill(this) { killer = killer, killerCPI = playerKill.Killer, victim = victim, victimCPI = playerKill.Victim, weaponCode = String.IsNullOrEmpty(playerKill.DamageType) ? "NoDamageType" : playerKill.DamageType, weaponDamage = category, TimeStamp = playerKill.TimeOfDeath, UTCTimeStamp = playerKill.TimeOfDeath.ToUniversalTime(), IsSuicide = playerKill.IsSuicide, IsHeadshot = playerKill.Headshot, IsTeamkill = (playerKill.Killer.TeamID == playerKill.Victim.TeamID), RoundID = _roundID }); } } catch (Exception e) { if (e is ThreadAbortException) { Log.HandleException(new AException("kill processing thread aborted. Exiting.")); break; } Log.HandleException(new AException("Error occured in kill processing thread.", e)); } } Log.Debug(() => "Ending Kill Processing Thread", 1); Threading.StopWatchdog(); } catch (Exception e) { Log.HandleException(new AException("Error occured in kill processing thread.", e)); } } private String GetEventMessage(Boolean nextRound) { Log.Debug(() => "Entering GetEventMessage", 7); try { switch (GetEventRoundRuleCode(GetActiveEventRoundNumber(nextRound))) { case AEventOption.RuleCode.KO: return "KNIFE ONLY!"; case AEventOption.RuleCode.BSO: return "BOLT SNIPER ONLY!"; case AEventOption.RuleCode.MLO: return "Mares LEG ONLY!"; case AEventOption.RuleCode.DO: return "DEFIBS ONLY!"; case AEventOption.RuleCode.BKO: return "BOW/KNIVES ONLY!"; case AEventOption.RuleCode.RTO: return "REPAIR TOOL ONLY!"; case AEventOption.RuleCode.PO: return "PISTOLS ONLY!"; case AEventOption.RuleCode.SO: return "SHOTGUNS ONLY!"; case AEventOption.RuleCode.NE: return "NO EXPLOSIVES!"; case AEventOption.RuleCode.EO: return "EXPLOSIVES ONLY!"; case AEventOption.RuleCode.AO: return "AUTO-PRIMARIES ONLY!"; case AEventOption.RuleCode.ARO: return "ASSAULT RIFLES ONLY!"; case AEventOption.RuleCode.LMGO: return "LMGS ONLY!"; case AEventOption.RuleCode.GO: return "GRENADES ONLY!"; case AEventOption.RuleCode.HO: return "HEADSHOTS ONLY!"; case AEventOption.RuleCode.NH: return "NO HEADSHOTS!"; case AEventOption.RuleCode.AW: return "ALL WEAPONS!"; case AEventOption.RuleCode.CAI: return "COWBOYS AND INDIANS!"; case AEventOption.RuleCode.TR: return "TROLL RULES!"; } } catch (Exception e) { Log.HandleException(new AException("Error while getting event message.", e)); } Log.Debug(() => "Exiting GetEventMessage", 7); return "UNKNOWN"; } private String GetEventDescription(Boolean nextRound) { Log.Debug(() => "Entering GetEventDescription", 7); try { switch (GetEventRoundRuleCode(GetActiveEventRoundNumber(nextRound))) { case AEventOption.RuleCode.KO: return "KNIFE ONLY! Only kills with knives are allowed."; case AEventOption.RuleCode.BSO: return "BOLT SNIPER ONLY! Only bolt action sniper rifles are allowed. NO Knives."; case AEventOption.RuleCode.MLO: return "Mares LEG ONLY! Only kills with the mares leg are allowed. NO Knives."; case AEventOption.RuleCode.DO: return "DEFIBS ONLY! Only kills with the medic's defibrilators are allowed."; case AEventOption.RuleCode.BKO: return "BOW/KNIVES ONLY! Only Phantom Bow/Knives are allowed. NO poison/explosive arrows."; case AEventOption.RuleCode.RTO: return "REPAIR TOOL ONLY! Only kills with repair tools and EOD bots are allowed."; case AEventOption.RuleCode.PO: return "PISTOLS ONLY! Only kills with pistols are allowed. NO G18/93R. NO Shorty 12G. Knives allowed."; case AEventOption.RuleCode.SO: return "SHOTGUNS ONLY! Only kills with shotguns are allowed. Any ammo type. Knives allowed."; case AEventOption.RuleCode.NE: return "NO EXPLOSIVES! Kills with explosive weapons are NOT allowed, all others are allowed."; case AEventOption.RuleCode.EO: return "EXPLOSIVES ONLY! Only explosive weapons are allowed. NO shotgun frag rounds. NO Knives."; case AEventOption.RuleCode.AO: return "AUTO-PRIMARIES ONLY! Only automatic primary weapons. Assault rifles, LMGs, Burst, etc. Knives allowed."; case AEventOption.RuleCode.ARO: return "ASSAULT RIFLES ONLY! Only kills with assault rifles are allowed. Knives allowed."; case AEventOption.RuleCode.LMGO: return "LMGS ONLY! Only kills with light machine guns are allowed. Knives allowed."; case AEventOption.RuleCode.GO: return "GRENADES ONLY! Only kills with grenades are allowed. M67, V40, etc. NO Knives."; case AEventOption.RuleCode.HO: return "HEADSHOTS ONLY! All weapons, but only headshots. If you kill without a headshot you are slain."; case AEventOption.RuleCode.NH: return "NO HEADSHOTS! All weapons, but NO headshots. If you kill with a headshot you are slain"; case AEventOption.RuleCode.AW: return "ALL WEAPONS! No weapon restrictions. Go nuts."; case AEventOption.RuleCode.CAI: return "COWBOYS AND INDIANS! Phantom Bow, Mares Leg, Revolvers, and Knives only. NO poison/explosive arrows."; case AEventOption.RuleCode.TR: return "TROLL RULES! Knives, Defibs, RepairTools, Shields, EODBots, SUAVs, and Smoke Launchers."; } } catch (Exception e) { Log.HandleException(new AException("Error while getting event description.", e)); } Log.Debug(() => "Exiting GetEventDescription", 7); return "UNKNOWN"; } private Int32 GetActiveEventRoundNumber(Boolean nextRound) { Log.Debug(() => "Entering GetEventRoundProgress", 7); try { var roundID = nextRound ? _roundID + 1 : _roundID; if (_CurrentEventRoundNumber == 999999 || _CurrentEventRoundNumber > roundID) { Log.Error("Can't get active event round number, event not active for round " + roundID + "."); return 999999; } return roundID - _CurrentEventRoundNumber; } catch (Exception e) { Log.HandleException(new AException("Error while getting event round progress.", e)); } Log.Debug(() => "Exiting GetEventRoundProgress", 7); return 999999; } private AEventOption.MapCode GetEventRoundMapCode(Int32 eventRoundNumber) { Log.Debug(() => "Entering GetEventRoundMapCode", 7); try { if (!_EventRoundOptions.Any() || eventRoundNumber < 0 || eventRoundNumber >= _EventRoundOptions.Count()) { Log.Error("Event round number " + eventRoundNumber + " was invalid when fetching map code."); return AEventOption.MapCode.UNKNOWN; } return _EventRoundOptions[eventRoundNumber].Map; } catch (Exception e) { Log.HandleException(new AException("Error while getting event round map code.", e)); } Log.Debug(() => "Exiting GetEventRoundMapCode", 7); return AEventOption.MapCode.UNKNOWN; } private AEventOption.ModeCode GetEventRoundModeCode(Int32 eventRoundNumber) { Log.Debug(() => "Entering GetEventRoundMapModeCode", 7); try { if (!_EventRoundOptions.Any() || eventRoundNumber < 0 || eventRoundNumber >= _EventRoundOptions.Count()) { Log.Error("Event round number " + eventRoundNumber + " was invalid when fetching mode code."); return AEventOption.ModeCode.UNKNOWN; } return _EventRoundOptions[eventRoundNumber].Mode; } catch (Exception e) { Log.HandleException(new AException("Error while getting event round mode code.", e)); } Log.Debug(() => "Exiting GetEventRoundMapModeCode", 7); return AEventOption.ModeCode.UNKNOWN; } private AEventOption.RuleCode GetEventRoundRuleCode(Int32 eventRoundNumber) { Log.Debug(() => "Entering GetEventRoundRestrictionCode", 7); try { if (!_EventRoundOptions.Any() || eventRoundNumber < 0 || eventRoundNumber >= _EventRoundOptions.Count()) { Log.Error("Event round number " + eventRoundNumber + " was invalid when fetching restriction code."); return AEventOption.RuleCode.UNKNOWN; } return _EventRoundOptions[eventRoundNumber].Rule; } catch (Exception e) { Log.HandleException(new AException("Error while getting event round restriction code.", e)); } Log.Debug(() => "Exiting GetEventRoundRestrictionCode", 7); return AEventOption.RuleCode.UNKNOWN; } private Boolean ProcessEventKill(AKill aKill, out String message) { Log.Debug(() => "Entering ProcessEventKill", 7); try { message = GetEventMessage(false) + " EVENT"; switch (GetEventRoundRuleCode(GetActiveEventRoundNumber(false))) { case AEventOption.RuleCode.KO: // KNIFE ONLY! // Only 5 knife codes known, fuzzy match for unknown knife types if (!aKill.weaponCode.ToLower().Contains("knife") && !aKill.weaponCode.ToLower().Contains("melee") && aKill.weaponCode != "DamageArea") { return true; } break; case AEventOption.RuleCode.BSO: // BOLT ACTIONS ONLY! if ((aKill.weaponDamage != DamageTypes.SniperRifle) && aKill.weaponCode != "DamageArea") { return true; } break; case AEventOption.RuleCode.MLO: // Mares LEG ONLY! if (aKill.weaponCode != "U_SaddlegunSnp" && aKill.weaponCode != "DamageArea") { return true; } break; case AEventOption.RuleCode.DO: // DEFIBS ONLY! if (aKill.weaponCode != "U_Defib" && aKill.weaponCode != "DamageArea") { return true; } break; case AEventOption.RuleCode.BKO: // PHANTOM BOW AND KNIVES ONLY! if (!aKill.weaponCode.ToLower().Contains("knife") && !aKill.weaponCode.ToLower().Contains("melee") && aKill.weaponCode != "dlSHTR" && aKill.weaponCode != "DamageArea") { return true; } break; case AEventOption.RuleCode.RTO: // REPAIR TOOL ONLY! if (aKill.weaponCode != "U_Repairtool" && aKill.weaponCode != "EODBot" && aKill.weaponCode != "Death" && aKill.weaponCode != "DamageArea") { return true; } break; case AEventOption.RuleCode.PO: // PISTOLS ONLY! if ((aKill.weaponDamage != DamageTypes.Handgun || aKill.weaponCode == "U_M93R" || aKill.weaponCode == "U_Glock18") && !aKill.weaponCode.ToLower().Contains("knife") && !aKill.weaponCode.ToLower().Contains("melee") && aKill.weaponCode != "DamageArea") { return true; } break; case AEventOption.RuleCode.SO: // SHOTGUNS ONLY! if (aKill.weaponDamage != DamageTypes.Shotgun && !aKill.weaponCode.ToLower().Contains("knife") && !aKill.weaponCode.ToLower().Contains("melee") && aKill.weaponCode != "DamageArea") { return true; } break; case AEventOption.RuleCode.NE: // NO EXPLOSIVES! if (aKill.weaponDamage == DamageTypes.Explosive || aKill.weaponDamage == DamageTypes.ProjectileExplosive) { return true; } break; case AEventOption.RuleCode.EO: // EXPLOSIVES ONLY! if (aKill.weaponDamage != DamageTypes.Explosive && aKill.weaponDamage != DamageTypes.ProjectileExplosive && aKill.weaponCode != "DamageArea" && aKill.weaponCode != "Gameplay/Gadgets/MAV/MAV" && aKill.weaponCode != "Death") { return true; } break; case AEventOption.RuleCode.AO: // AUTOMATIC PRIMARIES ONLY! if ((!aKill.weaponCode.ToLower().Contains("knife") && !aKill.weaponCode.ToLower().Contains("melee") && aKill.weaponDamage != DamageTypes.AssaultRifle && aKill.weaponDamage != DamageTypes.Carbine && aKill.weaponDamage != DamageTypes.LMG && aKill.weaponDamage != DamageTypes.SMG && aKill.weaponCode != "U_Groza-4" && aKill.weaponCode != "DamageArea") || aKill.weaponCode == "dlSHTR") { return true; } break; case AEventOption.RuleCode.ARO: // ASSAULT RIFLES ONLY! if (!aKill.weaponCode.ToLower().Contains("knife") && !aKill.weaponCode.ToLower().Contains("melee") && aKill.weaponDamage != DamageTypes.AssaultRifle && aKill.weaponCode != "DamageArea") { return true; } break; case AEventOption.RuleCode.LMGO: // LMGS ONLY! if (!aKill.weaponCode.ToLower().Contains("knife") && !aKill.weaponCode.ToLower().Contains("melee") && aKill.weaponDamage != DamageTypes.LMG && aKill.weaponCode != "DamageArea") { return true; } break; case AEventOption.RuleCode.GO: // GRENADES ONLY! if (aKill.weaponCode != "U_M67" && aKill.weaponCode != "U_M34" && aKill.weaponCode != "U_V40" && aKill.weaponCode != "U_Grenade_RGO" && aKill.weaponCode != "DamageArea") { return true; } break; case AEventOption.RuleCode.HO: // HEADSHOTS ONLY! if (!aKill.IsHeadshot && aKill.weaponCode != "DamageArea") { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_kill"), command_numeric = _roundID, target_name = aKill.killer.player_name, target_player = aKill.killer, source_name = "AutoAdmin", record_time = UtcNow(), record_message = "HEADSHOTS ONLY THIS ROUND!" }); } return false; case AEventOption.RuleCode.NH: // NO HEADSHOTS! if (aKill.IsHeadshot && aKill.weaponCode != "DamageArea") { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_kill"), command_numeric = _roundID, target_name = aKill.killer.player_name, target_player = aKill.killer, source_name = "AutoAdmin", record_time = UtcNow(), record_message = "NO HEADSHOTS THIS ROUND!" }); } return false; case AEventOption.RuleCode.AW: // Everything is allowed, always return false return false; case AEventOption.RuleCode.CAI: // COWBOYS AND INDIANS! // Phantom Bow, Mares Leg, Revolvers, and Knives. if (!aKill.weaponCode.ToLower().Contains("knife") && !aKill.weaponCode.ToLower().Contains("melee") && aKill.weaponCode != "U_SaddlegunSnp" && aKill.weaponCode != "U_SW40" && aKill.weaponCode != "U_Taurus44" && aKill.weaponCode != "U_Unica6" && aKill.weaponCode != "U_MP412Rex" && aKill.weaponCode != "dlSHTR" && aKill.weaponCode != "DamageArea") { message = GetEventMessage(false) + " Use !rules for details."; return true; } return false; case AEventOption.RuleCode.TR: // TROLL RULES! if (!aKill.weaponCode.ToLower().Contains("knife") && !aKill.weaponCode.ToLower().Contains("melee") && aKill.weaponCode != "U_Defib" && aKill.weaponCode != "U_Repairtool" && aKill.weaponCode != "U_BallisticShield" && aKill.weaponCode != "EODBot" && aKill.weaponCode != "Death" && aKill.weaponCode != "U_SUAV" && aKill.weaponCode.ToLower() != "roadkill" && aKill.weaponCode != "Gameplay/Gadgets/MAV/MAV" && aKill.weaponCode != "XP4/Gameplay/Gadgets/MKV/MKV" && aKill.weaponCode != "U_XM25_Smoke" && !aKill.weaponCode.ToLower().Contains("m320_smk") && aKill.weaponCode != "DamageArea") { message = GetEventMessage(false) + " Use !rules for details."; return true; } break; default: Log.Error("Unknown restriction type when processing event kill"); break; } } catch (Exception e) { Log.HandleException(new AException("Error while processing event kill.", e)); } finally { Log.Debug(() => "Exiting ProcessEventKill", 7); } message = null; return false; } private Boolean EventActive() { return EventActive(_roundID); } private Boolean EventActive(Int32 roundID) { return _EventRoundOptions.Any() && _CurrentEventRoundNumber != 999999 && roundID >= _CurrentEventRoundNumber && roundID < _CurrentEventRoundNumber + _EventRoundOptions.Count(); } private void ProcessPlayerKill(AKill aKill) { try { aKill.victim.lastAction = UtcNow(); aKill.killer.lastAction = UtcNow(); if (_DebugKills) { Log.Info(aKill.ToString()); } //Add the unmatched unique round death if (!_unmatchedRoundDeaths.Contains(aKill.victim.player_name)) { _unmatchedRoundDeaths.Add(aKill.victim.player_name); } //Add the unmatched round death count if (_unmatchedRoundDeathCounts.ContainsKey(aKill.victim.player_name)) { _unmatchedRoundDeathCounts[aKill.victim.player_name] = _unmatchedRoundDeathCounts[aKill.victim.player_name] + 1; } else { _unmatchedRoundDeathCounts[aKill.victim.player_name] = 1; } Boolean gKillHandled = false; //Update player death information Log.Debug(() => "Setting " + aKill.victim.GetVerboseName() + " time of death to " + aKill.TimeStamp, 7); aKill.victim.lastDeath = UtcNow(); //Add the kill aKill.killer.LiveKills.Add(aKill); if (_useAntiCheatLIVESystem && _AntiCheatLIVESystemActiveStats && _serverInfo.ServerType != "OFFICIAL" && !PlayerProtected(aKill.killer) && !EventActive()) { //KPM check Int32 lowCountRecent = aKill.killer.LiveKills.Count(dKill => (DateTime.Now - dKill.TimeStamp).TotalSeconds < 60); int lowCountBan = ((GameVersion == GameVersionEnum.BF3) ? (25) : (20)) - ((aKill.killer.fbpInfo.Rank <= 15) ? (6) : (0)); if (lowCountRecent >= lowCountBan) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_ban_perm"), command_numeric = 0, target_name = aKill.killer.player_name, target_player = aKill.killer, source_name = "AutoAdmin", record_message = _AntiCheatKPMBanMessage + " [LIVE][5-L" + lowCountBan + "-" + lowCountRecent + "]", record_time = UtcNow() }); return; } Int32 highCountRecent = aKill.killer.LiveKills.Count(dKill => (DateTime.Now - dKill.TimeStamp).TotalSeconds < 120); int highCountBan = ((GameVersion == GameVersionEnum.BF3) ? (40) : (32)) - ((aKill.killer.fbpInfo.Rank <= 15) ? (8) : (0)); if (highCountRecent >= highCountBan) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_ban_perm"), command_numeric = 0, target_name = aKill.killer.player_name, target_player = aKill.killer, source_name = "AutoAdmin", record_message = _AntiCheatKPMBanMessage + " [LIVE][5-H" + highCountBan + "-" + highCountRecent + "]", record_time = UtcNow() }); return; } //HSK Check Int32 lowKillCount = 22; Double lowKillTriggerHSKP = 90; Int32 highKillCount = 47; Double highKillTriggerHSKP = 80; if (_serverInfo.InfoObject.Map == "XP0_Metro" || _serverInfo.InfoObject.Map == "MP_Prison") { lowKillCount = 30; highKillCount = 60; } var nonSniperKills = aKill.killer.LiveKills .Where(dKill => dKill.weaponDamage != DamageTypes.SniperRifle && dKill.weaponDamage != DamageTypes.DMR) .OrderByDescending(dKill => dKill.TimeStamp); var countAll = nonSniperKills.Count(); if (countAll >= lowKillCount) { var lowKillHSKP = nonSniperKills.Take(lowKillCount).Count(dKill => dKill.IsHeadshot) / ((Double)lowKillCount) * 100.0; var highKillHSKP = nonSniperKills.Take(highKillCount).Count(dKill => dKill.IsHeadshot) / ((Double)highKillCount) * 100.0; String actionMessage = null; if (countAll >= lowKillCount && lowKillHSKP >= lowKillTriggerHSKP) { actionMessage = _AntiCheatHSKBanMessage + " [LIVE][6-L" + lowKillCount + "-" + countAll + "-" + Math.Round(lowKillHSKP) + "]"; } else if (countAll >= highKillCount && highKillHSKP >= highKillTriggerHSKP) { actionMessage = _AntiCheatHSKBanMessage + " [LIVE][6-H" + highKillCount + "-" + countAll + "-" + Math.Round(highKillHSKP) + "]"; } if (!String.IsNullOrEmpty(actionMessage)) { //Create ban record QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_ban_perm"), command_numeric = 0, target_name = aKill.killer.player_name, target_player = aKill.killer, source_name = "AutoAdmin", record_message = actionMessage, record_time = UtcNow() }); return; } if (highKillHSKP >= 75 && !aKill.killer.TargetedRecords.Any(aRecord => aRecord.record_message.Contains("non-sniper HSKP") && (UtcNow() - aRecord.record_time).TotalMinutes <= 30)) { //Create report record QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_report"), command_numeric = 0, target_name = aKill.killer.player_name, target_player = aKill.killer, source_name = "AutoAdmin", record_message = Math.Round(highKillHSKP) + "% non-sniper HSKP", record_time = UtcNow() }); } } } // Catch BF4 gadget kills if (GameVersion == GameVersionEnum.BF4) { //Special weapons String actedCode = null; Int32 triggerCount = 3; if (aKill.killer.LiveKills.Count(dKill => dKill.RoundID == _roundID && dKill.weaponCode == "U_PortableAmmopack") >= triggerCount) { actedCode = "1"; } if (aKill.killer.LiveKills.Count(dKill => dKill.RoundID == _roundID && dKill.weaponCode == "U_RadioBeacon") >= triggerCount) { actedCode = "2"; } if (aKill.killer.LiveKills.Count(dKill => dKill.RoundID == _roundID && dKill.weaponCode == "Gameplay/Gadgets/SOFLAM/SOFLAM_Projectile") >= triggerCount) { actedCode = "3"; } if (aKill.killer.LiveKills.Count(dKill => dKill.RoundID == _roundID && dKill.weaponCode == "U_Motionsensor") >= triggerCount) { actedCode = "4"; } if (aKill.killer.LiveKills.Count(dKill => dKill.RoundID == _roundID && dKill.weaponCode == "U_PortableMedicpack" && !dKill.IsTeamkill) >= triggerCount) { actedCode = "5"; } if (aKill.killer.LiveKills.Count(dKill => dKill.RoundID == _roundID && dKill.weaponCode == "U_Medkit" && !dKill.IsTeamkill) >= triggerCount) { actedCode = "6"; } if (aKill.killer.LiveKills.Count(dKill => dKill.RoundID == _roundID && dKill.weaponCode == "U_Ammobag") >= triggerCount) { actedCode = "7"; } if (!String.IsNullOrEmpty(actedCode) && !PlayerProtected(aKill.killer)) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_ban_perm"), command_numeric = 0, target_name = aKill.killer.player_name, target_player = aKill.killer, source_name = "AutoAdmin", record_message = "[LIVE][Code 8-" + actedCode + "]: Dispute Requested", record_time = UtcNow() }); return; } } // Catch BF3 gadget kills if (GameVersion == GameVersionEnum.BF3) { //Special weapons String actedCode = null; Int32 triggerCount = 3; if (aKill.killer.LiveKills.Count(dKill => dKill.RoundID == _roundID && dKill.weaponCode == "AmmoBag") >= triggerCount) { actedCode = "1"; } if (aKill.killer.LiveKills.Count(dKill => dKill.RoundID == _roundID && dKill.weaponCode == "Weapons/Gadgets/RadioBeacon/Radio_Beacon") >= triggerCount) { actedCode = "2"; } if (aKill.killer.LiveKills.Count(dKill => dKill.RoundID == _roundID && dKill.weaponCode == "Weapons/Gadgets/SOFLAM/SOFLAM_PDA") >= triggerCount) { actedCode = "3"; } if (aKill.killer.LiveKills.Count(dKill => dKill.RoundID == _roundID && dKill.weaponCode == "Medkit" && !dKill.IsTeamkill) >= triggerCount) { actedCode = "4"; } if (!String.IsNullOrEmpty(actedCode) && !PlayerProtected(aKill.killer)) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_ban_perm"), command_numeric = 0, target_name = aKill.killer.player_name, target_player = aKill.killer, source_name = "AutoAdmin", record_message = "Code [8-" + actedCode + "]: Dispute Requested", record_time = UtcNow() }); return; } } //Grenade cooking catcher //Only add the last death if it's not a death by admin if (!String.IsNullOrEmpty(aKill.killer.player_name)) { try { if (_UseGrenadeCookCatcher) { if (_RoundCookers == null) { _RoundCookers = new Dictionary(); } const double possibleRange = 1100.00; //Check for cooked grenade and non-suicide if (aKill.weaponCode.Contains("M67") || aKill.weaponCode.Contains("V40")) { if (true) { Double fuseTime = 0; if (aKill.weaponCode.Contains("M67")) { if (GameVersion == GameVersionEnum.BF3) { fuseTime = 3735.00; } else if (GameVersion == GameVersionEnum.BF4) { fuseTime = 3132.00; } } else if (aKill.weaponCode.Contains("V40")) { fuseTime = 2865.00; } Boolean told = false; List> possible = new List>(); List> sure = new List>(); foreach (AKill cookerKill in aKill.killer.LiveKills .Where(dKill => (aKill.TimeStamp - dKill.TimeStamp).TotalSeconds < 10.0) .OrderBy(dKill => Math.Abs(aKill.TimeStamp.Subtract(dKill.TimeStamp).TotalMilliseconds - fuseTime))) { //Get the actual time since cooker value Double milli = aKill.TimeStamp.Subtract(cookerKill.TimeStamp).TotalMilliseconds; //Calculate the percentage probability Double probability; if (Math.Abs(milli - fuseTime) < possibleRange) { probability = (1 - Math.Abs((milli - fuseTime) / possibleRange)) * 100; Log.Debug(() => cookerKill.victim.GetVerboseName() + " cooking probability: " + probability + "%", 2); } else { probability = 0.00; } //If probability > 60% report the player and add them to the round cookers list if (probability > 60.00) { Log.Debug(() => cookerKill.victim.GetVerboseName() + " in " + aKill.killer.GetVerboseName() + "'s recent kills has a " + probability + "% cooking probability.", 2); gKillHandled = true; //Inform every player killed by the nade that it was a cooked nade PlayerTellMessage(aKill.victim.player_name, aKill.killer.GetVerboseName() + " was a victim of grenade cooking, they did not use explosives."); //Code to avoid spam if (aKill.killer.lastKill.AddSeconds(2) < UtcNow()) { aKill.killer.lastKill = UtcNow(); } else { Log.Debug(() => "Skipping additional auto-actions for multi-kill event.", 3); continue; } if (!told) { //Inform the victim player that they will not be punished PlayerTellMessage(aKill.killer.player_name, "You appear to be a victim of grenade cooking and will NOT be punished."); told = true; } //Create the probability String String probString = ((int)probability) + "-" + ((int)milli); //If the player is already on the round cooker list, punish them if (_RoundCookers.ContainsKey(cookerKill.victim.player_name)) { //Create the punish record ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_punish"), command_numeric = 0, target_name = cookerKill.victim.player_name, target_player = cookerKill.victim, source_name = "AutoAdmin", record_message = "Rules: Cooking Grenades [" + probString + "-X] [Victim " + aKill.killer.GetVerboseName() + " Protected]", record_time = UtcNow() }; //Process the record QueueRecordForProcessing(record); //adminSay("Punishing " + killer.player_name + " for " + record.record_message); Log.Debug(() => record.GetTargetNames() + " punished for " + record.record_message, 2); return; } //else if probability > 92.5% add them to the SURE list, and round cooker list if (probability > 92.5) { _RoundCookers.Add(cookerKill.victim.player_name, cookerKill.victim); Log.Debug(() => cookerKill.victim.GetVerboseName() + " added to round cooker list.", 2); //Add to SURE sure.Add(new KeyValuePair(cookerKill.victim, probString)); } //Otherwise add them to the round cooker list, and add to POSSIBLE list else { _RoundCookers.Add(cookerKill.victim.player_name, cookerKill.victim); Log.Debug(() => cookerKill.victim.GetVerboseName() + " added to round cooker list.", 2); //Add to POSSIBLE possible.Add(new KeyValuePair(cookerKill.victim, probString)); } } } if (sure.Count == 1 && possible.Count == 0 && GameVersion == GameVersionEnum.BF3) { APlayer player = sure[0].Key; String probString = sure[0].Value; //Create the ban record ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_punish"), command_numeric = 0, target_name = player.player_name, target_player = player, source_name = "AutoAdmin", record_message = "Rules: Cooking Grenades [" + probString + "] [Victim " + aKill.killer.GetVerboseName() + " Protected]", record_time = UtcNow() }; //Process the record QueueRecordForProcessing(record); //adminSay("Punishing " + killer.player_name + " for " + record.record_message); Log.Debug(() => record.GetTargetNames() + " punished for " + record.record_message, 2); } else { APlayer player; String probString; foreach (KeyValuePair playerPair in sure) { player = playerPair.Key; probString = playerPair.Value; //Create the report record ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_report"), command_numeric = 0, target_name = player.player_name, target_player = player, source_name = "AutoAdmin", record_message = "Possible Grenade Cooker [" + probString + "] [Victim " + aKill.killer.GetVerboseName() + " Protected]", record_time = UtcNow() }; //Process the record QueueRecordForProcessing(record); Log.Debug(() => record.GetTargetNames() + " reported for " + record.record_message, 2); } foreach (KeyValuePair playerPair in possible) { player = playerPair.Key; probString = playerPair.Value; //Create the report record ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_report"), command_numeric = 0, target_name = player.player_name, target_player = player, source_name = "AutoAdmin", record_message = "Possible Grenade Cooker [" + probString + "] [Victim " + aKill.killer.GetVerboseName() + " Protected]", record_time = UtcNow() }; //Process the record QueueRecordForProcessing(record); Log.Debug(() => record.GetTargetNames() + " reported for " + record.record_message, 2); } } } } } } catch (Exception e) { Log.HandleException(new AException("Error in grenade cook catcher.", e)); } } var acted = false; try { if (EventActive()) { if (aKill.killerCPI.TeamID != aKill.victimCPI.TeamID && _roundState == RoundState.Playing) { var killSpam = (aKill.killer.lastKill.AddSeconds(2) > UtcNow()); aKill.killer.lastKill = UtcNow(); String recordMessage; if (ProcessEventKill(aKill, out recordMessage)) { ACommand aCommand = GetCommandByKey("player_kill"); if (_populationStatus == PopulationState.High && aKill.killer.TargetedRecords.Any(targetedRecord => (targetedRecord.command_numeric == _roundID) && (targetedRecord.command_action.command_key == "player_kill" || targetedRecord.command_action.command_key == "player_kick") && (UtcNow() - targetedRecord.record_time).TotalMinutes < 7.5)) { aCommand = GetCommandByKey("player_kick"); } QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = aCommand, command_numeric = _roundID, target_name = aKill.killer.player_name, target_player = aKill.killer, source_name = "AutoAdmin", record_time = UtcNow(), record_message = recordMessage }); acted = true; } } } else if (_UseWeaponLimiter && !gKillHandled) { //Check for restricted weapon if (Regex.Match(aKill.weaponCode, @"(?:" + _WeaponLimiterString + ")", RegexOptions.IgnoreCase).Success) { //Check for exception type if (!Regex.Match(aKill.weaponCode, @"(?:" + _WeaponLimiterExceptionString + ")", RegexOptions.IgnoreCase).Success) { //Check if suicide if (!aKill.IsSuicide) { //Get player from the dictionary if (aKill.killer != null) { var killSpam = (aKill.killer.lastKill.AddSeconds(2) > UtcNow()); aKill.killer.lastKill = UtcNow(); const string removeWeapon = "Weapons/"; const string removeGadgets = "Gadgets/"; const string removePrefix = "U_"; String weapon = WeaponDictionary.GetShortWeaponNameByCode(aKill.weaponCode); Int32 index = weapon.IndexOf(removeWeapon, StringComparison.Ordinal); weapon = (index < 0) ? (weapon) : (weapon.Remove(index, removeWeapon.Length)); index = weapon.IndexOf(removeGadgets, StringComparison.Ordinal); weapon = (index < 0) ? (weapon) : (weapon.Remove(index, removeGadgets.Length)); index = weapon.IndexOf(removePrefix, StringComparison.Ordinal); weapon = (index < 0) ? (weapon) : (weapon.Remove(index, removePrefix.Length)); //Record to boost rep for victim if (aKill.killer.IsLocked()) { PlayerYellMessage(aKill.victim.player_name, aKill.killer.GetVerboseName() + " is currently locked from autoadmin actions for " + FormatTimeString(aKill.killer.GetLockRemaining(), 2) + "."); } else { PlayerYellMessage(aKill.victim.player_name, aKill.killer.GetVerboseName() + " was punished for killing you with " + weapon); } ARecord repRecord = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_repboost"), command_numeric = 0, target_name = aKill.victim.player_name, target_player = aKill.victim, source_name = "RepManager", record_message = "Player killed by restricted weapon " + weapon, record_time = UtcNow() }; QueueRecordForProcessing(repRecord); if (!killSpam && _serverInfo.ServerType != "OFFICIAL") { //Create the punish record ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_punish"), command_numeric = 0, target_name = aKill.killer.player_name, target_player = aKill.killer, source_name = "AutoAdmin", record_time = UtcNow() }; if (weapon.ToLower() == "roadkill") { record.record_message = "Rules: Roadkilling with EOD or MAV"; } else if (weapon == "Death") { if (GameVersion == GameVersionEnum.BF3) { record.record_message = "Rules: Using Mortar"; } else if (GameVersion == GameVersionEnum.BF4) { record.record_message = "Rules: Using EOD Bot"; } } else { record.record_message = "Rules: Using Explosives [" + weapon + "]"; } //Process the record QueueRecordForProcessing(record); acted = true; } else { Log.Debug(() => "Skipping additional auto-actions for multi-kill event.", 3); } } else { Log.Error("Killer was null when processing kill"); } } } } } } catch (Exception e) { Log.HandleException(new AException("Error in no explosives auto-admin.", e)); } try { if (!acted && ChallengeManager != null && ChallengeManager.Loaded && !EventActive() && GetPlayerCount() >= ChallengeManager.MinimumPlayers) { if (aKill.killer.ActiveChallenge == null) { ChallengeManager.AssignRoundChallengeIfKillValid(aKill); } if (aKill.killer.ActiveChallenge != null) { aKill.killer.ActiveChallenge.AddKill(aKill); } if (aKill.victim.ActiveChallenge != null) { aKill.victim.ActiveChallenge.AddDeath(aKill); } } } catch (Exception e) { Log.HandleException(new AException("Error while running challenge kill processing.", e)); } } catch (Exception e) { Log.HandleException(new AException("Error while processing player kill.", e)); } Log.Debug(() => "Exiting OnPlayerKilled", 7); } public override void OnPlayerSpawned(String soldierName, Inventory spawnedInventory) { Log.Debug(() => "Entering OnPlayerSpawned", 7); try { APlayer aPlayer = null; if (_pluginEnabled && _threadsReady && _firstPlayerListComplete) { //Fetch the player if (!_PlayerDictionary.TryGetValue(soldierName, out aPlayer)) { Log.Warn(soldierName + " spawned without being in player list."); if (!_MissingPlayers.Contains(soldierName)) { _MissingPlayers.Add(soldierName); } return; } aPlayer.player_spawnedRound = true; //Ensure frostbite player info if (aPlayer.fbpInfo == null) { return; } //Fetch teams ATeam team1, team2; if (!GetTeamByID(1, out team1)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } return; } if (!GetTeamByID(2, out team2)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } return; } ATeam friendlyTeam, enemyTeam; if (aPlayer.fbpInfo.TeamID == team1.TeamID) { friendlyTeam = team1; enemyTeam = team2; } else { friendlyTeam = team2; enemyTeam = team1; } if (_roundState == RoundState.Loaded) { _playingStartTime = UtcNow(); _roundState = RoundState.Playing; //Take minimum ticket count between teams (accounts for rush), but not less than 0 _startingTicketCount = Math.Max(0, Math.Min(team1.TeamTicketCount, team2.TeamTicketCount)); if (EventActive()) { Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "RoundWelcome"; Thread.Sleep(TimeSpan.FromSeconds(10)); AdminTellMessage("WELCOME TO ROUND EVENT " + String.Format("{0:n0}", _roundID) + "! " + GetEventMessage(false)); Int32 messages = 0; while (messages++ < 10) { Threading.Wait(TimeSpan.FromSeconds(3)); AdminSayMessage(GetEventMessage(false) + " Use !rules for details."); } Threading.StopWatchdog(); }))); } else if (_UseExperimentalTools && GameVersion == GameVersionEnum.BF4 && _serverInfo != null && _serverInfo.GetRoundElapsedTime().TotalSeconds < 30) { if (_serverInfo.ServerName.ToLower().Contains("metro") && _serverInfo.ServerName.ToLower().Contains("no explosives")) { Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "RoundWelcome"; Thread.Sleep(TimeSpan.FromSeconds(17)); AdminTellMessage("Welcome to round " + String.Format("{0:n0}", _roundID) + " of No Explosives Metro!"); Threading.StopWatchdog(); }))); } else if (_serverInfo.ServerName.ToLower().Contains("locker") && _serverInfo.ServerName.ToLower().Contains("pistol")) { Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "RoundWelcome"; Thread.Sleep(TimeSpan.FromSeconds(17)); AdminTellMessage("Welcome to round " + String.Format("{0:n0}", _roundID) + " of Pistols Only Locker!"); Threading.StopWatchdog(); }))); } } if (_useRoundTimer) { StartRoundTimer(); } if (ChallengeManager != null) { ChallengeManager.OnRoundPlaying(_roundID); } } if (_CommandNameDictionary.Count > 0) { //Handle TeamSwap notifications String command = GetChatCommandByKey("self_teamswap"); aPlayer.lastSpawn = UtcNow(); aPlayer.lastAction = UtcNow(); //Add matched spawn count if (_unmatchedRoundDeaths.Contains(aPlayer.player_name)) { friendlyTeam.IncrementTeamTicketAdjustment(); } //Removed unmatched death if applicable _unmatchedRoundDeaths.Remove(aPlayer.player_name); //Decrement unmatched death count if applicable if (_unmatchedRoundDeathCounts.ContainsKey(aPlayer.player_name)) { _unmatchedRoundDeathCounts[aPlayer.player_name] = _unmatchedRoundDeathCounts[aPlayer.player_name] - 1; } if (aPlayer.player_aa && !aPlayer.player_aa_told) { String adminAssistantMessage = "You are an Admin Assistant. "; if (!_UseAAReportAutoHandler && !_EnableAdminAssistantPerk) { adminAssistantMessage += "Thank you for your consistent reporting."; } else { adminAssistantMessage += "Perks: "; if (_UseAAReportAutoHandler) { adminAssistantMessage += "AutoAdmin can handle some of your reports. "; } if (_EnableAdminAssistantPerk) { adminAssistantMessage += "You can use the " + command + " command."; } } PlayerSayMessage(soldierName, adminAssistantMessage); aPlayer.player_aa_told = true; } } //Handle Dev Notifications if (soldierName == "ColColonCleaner" && !_toldCol) { PlayerTellMessage("ColColonCleaner", "AdKats " + PluginVersion + " running!"); _toldCol = true; } var startDuration = NowDuration(_AdKatsStartTime).TotalSeconds; var startupDuration = TimeSpan.FromSeconds(_startupDurations.Average(span => span.TotalSeconds)).TotalSeconds; if (!aPlayer.player_spawnedOnce && ChallengeManager != null) { // Make sure that they have their challenge entry assigned if applicable ChallengeManager.AssignActiveEntryForPlayer(aPlayer); } if (!aPlayer.player_spawnedOnce && startDuration - startupDuration > 120) { if (_ShowNewPlayerAnnouncement && aPlayer.player_new) { OnlineAdminSayMessage(aPlayer.GetVerboseName() + " just joined for the first time!"); } if (_UseFirstSpawnMessage || (_battlecryVolume != BattlecryVolume.Disabled && !String.IsNullOrEmpty(aPlayer.player_battlecry)) || _UsePerkExpirationNotify) { Thread spawnPrinter = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a spawn printer thread.", 5); try { Thread.CurrentThread.Name = "SpawnPrinter"; //Wait 2 seconds Threading.Wait(2000); //Send perk expiration notification if (_UsePerkExpirationNotify) { var groups = GetMatchingVerboseASPlayers(aPlayer); var expiringGroups = groups.Where(group => NowDuration(group.player_expiration).TotalDays < _PerkExpirationNotifyDays); if (expiringGroups.Any()) { PlayerTellMessage(aPlayer.player_name, "You have perks expiring soon. Use " + GetChatCommandByKey("player_perks") + " to see your perks."); Threading.Wait(TimeSpan.FromSeconds(_YellDuration)); } } if (_battlecryVolume != BattlecryVolume.Disabled && !String.IsNullOrEmpty(aPlayer.player_battlecry)) { switch (_battlecryVolume) { case BattlecryVolume.Say: AdminSayMessage(aPlayer.player_battlecry); break; case BattlecryVolume.Yell: AdminYellMessage(aPlayer.player_battlecry); break; case BattlecryVolume.Tell: AdminTellMessage(aPlayer.player_battlecry); break; } Threading.Wait(TimeSpan.FromSeconds(_YellDuration)); } else if (_UseFirstSpawnMessage) { PlayerTellMessage(aPlayer.player_name, _FirstSpawnMessage); Threading.Wait(TimeSpan.FromSeconds(_YellDuration)); } int points = FetchPoints(aPlayer, false, true); if (_useFirstSpawnRepMessage) { Boolean isAdmin = PlayerIsAdmin(aPlayer); String repMessage = "Your reputation is " + Math.Round(aPlayer.player_reputation, 2) + ", with "; if (points > 0) { repMessage += points + " infraction point(s). "; } else { repMessage += "a clean infraction record. "; } PlayerTellMessage(aPlayer.player_name, repMessage); } } catch (Exception e) { Log.HandleException(new AException("Error while printing spawn messages", e)); } Log.Debug(() => "Exiting a spawn printer.", 5); Threading.StopWatchdog(); })); //Start the thread Threading.StartWatchdog(spawnPrinter); } } aPlayer.player_spawnedOnce = true; if (_ActOnSpawnDictionary.Count > 0) { lock (_ActOnSpawnDictionary) { ARecord record; if (_ActOnSpawnDictionary.TryGetValue(soldierName, out record)) { //Remove it from the dic _ActOnSpawnDictionary.Remove(soldierName); //Wait 1.5 seconds to take action (no "killed by admin" message in BF3 without this wait) Threading.Wait(1500); //Queue the action QueueRecordForActionHandling(record); } } } if (_AutomaticForgives && aPlayer.player_infractionPoints > 0 && aPlayer.LastPunishment != null && (UtcNow() - aPlayer.LastPunishment.record_time).TotalDays > _AutomaticForgiveLastPunishDays && (aPlayer.LastForgive == null || (UtcNow() - aPlayer.LastForgive.record_time).TotalDays > _AutomaticForgiveLastForgiveDays)) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_forgive"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "InfractionManager", record_message = "Auto-Forgiven for Clean Play", record_time = UtcNow() }); } //Auto-Nuke Slay Duration var duration = NowDuration(_lastNukeTime); if (duration.TotalSeconds < _nukeAutoSlayActiveDuration && _lastNukeTeam != null && aPlayer.fbpInfo.TeamID == _lastNukeTeam.TeamID) { var endDuration = NowDuration(_lastNukeTime.AddSeconds(_nukeAutoSlayActiveDuration)); var durationRounded = Math.Round(endDuration.TotalSeconds, 1); if (durationRounded > 0) { PlayerTellMessage(aPlayer.player_name, _lastNukeTeam.TeamKey + " nuke active for " + Math.Round(endDuration.TotalSeconds, 1) + " seconds!"); ExecuteCommand("procon.protected.send", "admin.killPlayer", aPlayer.player_name); } } if (aPlayer.ActiveChallenge != null) { aPlayer.ActiveChallenge.AddSpawn(aPlayer); } } } catch (Exception e) { Log.HandleException(new AException("Error while handling player spawn.", e)); } Log.Debug(() => "Exiting OnPlayerSpawned", 7); } public override void OnPlayerJoin(string soldierName) { Log.Debug(() => "Entering OnPlayerJoin", 7); try { if (_pluginEnabled && _firstPlayerListComplete && GameVersion == GameVersionEnum.BF4 && !String.IsNullOrEmpty(_vipKickedPlayerName)) { var matchingPlayer = GetFetchedPlayers().FirstOrDefault(aPlayer => aPlayer.player_name == soldierName); if (matchingPlayer != null) { OnlineAdminSayMessage(_vipKickedPlayerName + " kicked for VIP " + matchingPlayer.GetVerboseName() + " to join."); } else { OnlineAdminSayMessage(_vipKickedPlayerName + " kicked for VIP " + soldierName + " to join."); } _vipKickedPlayerName = null; } } catch (Exception e) { Log.HandleException(new AException("Error while handling player join.", e)); } Log.Debug(() => "Exiting OnPlayerJoin", 7); } public override void OnPlayerLeft(CPlayerInfo playerInfo) { Log.Debug(() => "Entering OnPlayerLeft", 7); try { QueuePlayerForRemoval(playerInfo); } catch (Exception e) { Log.HandleException(new AException("Error while handling player left.", e)); } Log.Debug(() => "Exiting OnPlayerLeft", 7); } public override void OnPlayerDisconnected(string soldierName, string reason) { Log.Debug(() => "Entering OnPlayerDisconnected", 7); try { if (_pluginEnabled && _firstPlayerListComplete && GameVersion == GameVersionEnum.BF4 && reason == "PLAYER_KICKED") { var matchingPlayer = GetFetchedPlayers().FirstOrDefault(aPlayer => aPlayer.player_name == soldierName); if (matchingPlayer != null) { _vipKickedPlayerName = matchingPlayer.GetVerboseName(); } else { _vipKickedPlayerName = soldierName; } } } catch (Exception e) { Log.HandleException(new AException("Error while handling player disconnected.", e)); } Log.Debug(() => "Exiting OnPlayerDisconnected", 7); } private void QueueSettingImport(Int32 serverID) { Log.Debug(() => "Entering queueSettingImport", 7); try { if (_pluginEnabled) { Log.Debug(() => "Preparing to queue server ID for setting import", 6); _settingImportID = serverID; _DbCommunicationWaitHandle.Set(); } } catch (Exception e) { Log.HandleException(new AException("Error while preparing to import settings.", e)); } Log.Debug(() => "Exiting queueSettingImport", 7); } private void QueueSettingForUpload(CPluginVariable setting) { Log.Debug(() => "Entering queueSettingForUpload", 7); if (!_settingsFetched) { return; } try { if (_pluginEnabled) { Log.Debug(() => "Preparing to queue setting " + setting.Name + " for upload", 6); lock (_SettingUploadQueue) { _SettingUploadQueue.Enqueue(setting); _DbCommunicationWaitHandle.Set(); } } } catch (Exception e) { Log.HandleException(new AException("Error while queueing setting for upload.", e)); } Log.Debug(() => "Exiting queueSettingForUpload", 7); } private void QueueCommandForUpload(ACommand command) { Log.Debug(() => "Entering queueCommandForUpload", 7); try { if (_pluginEnabled) { Log.Debug(() => "Preparing to queue command " + command.command_key + " for upload", 6); lock (_CommandUploadQueue) { _CommandUploadQueue.Enqueue(command); _DbCommunicationWaitHandle.Set(); } } } catch (Exception e) { Log.HandleException(new AException("Error while queueing command for upload.", e)); } Log.Debug(() => "Exiting queueCommandForUpload", 7); } private void QueueRoleForUpload(ARole aRole) { Log.Debug(() => "Entering queueRoleForUpload", 7); try { if (_pluginEnabled) { Log.Debug(() => "Preparing to queue role " + aRole.role_key + " for upload", 6); lock (_RoleUploadQueue) { _RoleUploadQueue.Enqueue(aRole); _DbCommunicationWaitHandle.Set(); } } } catch (Exception e) { Log.HandleException(new AException("Error while queueing role for upload.", e)); } Log.Debug(() => "Exiting queueRoleForUpload", 7); } private void QueueRoleForRemoval(ARole aRole) { Log.Debug(() => "Entering queueRoleForRemoval", 7); try { if (_pluginEnabled) { Log.Debug(() => "Preparing to queue role " + aRole.role_key + " for removal", 6); lock (_RoleRemovalQueue) { _RoleRemovalQueue.Enqueue(aRole); _DbCommunicationWaitHandle.Set(); } } } catch (Exception e) { Log.HandleException(new AException("Error while queueing role for removal.", e)); } Log.Debug(() => "Exiting queueRoleForRemoval", 7); } private void QueuePlayerForBanCheck(APlayer player) { Log.Debug(() => "Entering queuePlayerForBanCheck", 7); try { if (_pluginEnabled) { Log.Debug(() => "Preparing to queue player for ban check", 6); lock (_BanEnforcerCheckingQueue) { _BanEnforcerCheckingQueue.Enqueue(player); Log.Debug(() => "Player queued for checking", 6); _BanEnforcerWaitHandle.Set(); } } } catch (Exception e) { Log.HandleException(new AException("Error while queueing player for ban check.", e)); } Log.Debug(() => "Exiting queuePlayerForBanCheck", 7); } private void QueueBanForProcessing(ABan aBan) { Log.Debug(() => "Entering queueBanForProcessing", 7); try { if (_pluginEnabled) { Log.Debug(() => "Preparing to queue ban for processing", 6); lock (_BanEnforcerProcessingQueue) { _BanEnforcerProcessingQueue.Enqueue(aBan); Log.Debug(() => "Ban queued for processing", 6); _DbCommunicationWaitHandle.Set(); } } } catch (Exception e) { Log.HandleException(new AException("Error while queueing ban for processing.", e)); } Log.Debug(() => "Exiting queueBanForProcessing", 7); } private void BanEnforcerThreadLoop() { try { Log.Debug(() => "Starting Ban Enforcer Thread", 1); Thread.CurrentThread.Name = "BanEnforcer"; DateTime loopStart = UtcNow(); while (true) { try { Log.Debug(() => "Entering Ban Enforcer Thread Loop", 7); if (!_pluginEnabled) { Log.Debug(() => "Detected AdKats not enabled. Exiting thread " + Thread.CurrentThread.Name, 6); break; } SendNonQuery("Updating Active Bans", "UPDATE `adkats_bans` SET `ban_status` = 'Expired' WHERE `ban_endTime` <= UTC_TIMESTAMP() AND `ban_status` = 'Active'", false); //Get all unchecked players Queue playerCheckingQueue; if (_BanEnforcerCheckingQueue.Count > 0 && _UseBanEnforcer) { Log.Debug(() => "Preparing to lock banEnforcerMutex to retrive new players", 6); lock (_BanEnforcerCheckingQueue) { Log.Debug(() => "Inbound ban enforcer players found. Grabbing.", 5); //Grab all players in the queue playerCheckingQueue = new Queue(_BanEnforcerCheckingQueue.ToArray()); //Clear the queue for next run _BanEnforcerCheckingQueue.Clear(); if (_databaseConnectionCriticalState) { continue; } } } else { Log.Debug(() => "No inbound ban checks. Waiting for Input.", 6); //Wait for input if ((UtcNow() - loopStart).TotalMilliseconds > 1000) { Log.Debug(() => "Warning. " + Thread.CurrentThread.Name + " thread processing completed in " + ((int)((UtcNow() - loopStart).TotalMilliseconds)) + "ms", 4); } _BanEnforcerWaitHandle.Reset(); _BanEnforcerWaitHandle.WaitOne(TimeSpan.FromSeconds(60)); loopStart = UtcNow(); continue; } //Get all checks in order that they came in while (playerCheckingQueue.Count > 0) { if (!_pluginEnabled) { break; } //Grab first/next player APlayer aPlayer = playerCheckingQueue.Dequeue(); Log.Debug(() => "begin ban enforcer reading player " + aPlayer.GetVerboseName(), 5); if (_PlayerDictionary.ContainsKey(aPlayer.player_name)) { List aBanList = FetchPlayerBans(aPlayer); if (aBanList.Count > 0) { //Check for specific ban on this player ABan playerBan = aBanList.Where(aBan => aBan.player_id == aPlayer.player_id || (aBan.ban_record.target_player != null && aBan.ban_record.target_player.player_id == aPlayer.player_id)).FirstOrDefault(); if (playerBan != null) { //Ensure the ban record has updated player information playerBan.ban_record.target_player = aPlayer; //Found specific ban QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, source_name = "BanEnforcer", isIRO = false, server_id = _serverInfo.ServerID, target_name = aPlayer.player_name, target_player = aPlayer, command_type = GetCommandByKey("banenforcer_enforce"), command_numeric = (int)playerBan.ban_id, record_message = playerBan.ban_record.record_message, record_time = UtcNow() }); } else { //No specific ban, use linked bans List linkedIDs = (from aBan in aBanList where aBan != null && aBan.ban_record != null && aBan.ban_record.target_player != null select aBan.ban_record.target_player.player_id.ToString()).ToList(); String strIDs = String.Join(", ", linkedIDs.ToArray()); //Use the first ban found playerBan = aBanList.FirstOrDefault(); if (playerBan != null) { //Ensure the ban record has updated player information playerBan.ban_record.target_player = aPlayer; //Queue record for upload QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, source_name = "BanEnforcer", isIRO = false, server_id = _serverInfo.ServerID, target_name = aPlayer.player_name, target_player = aPlayer, command_type = GetCommandByKey("banenforcer_enforce"), command_numeric = (int)playerBan.ban_id, record_message = playerBan.ban_record.record_message + " [LINKED ACCOUNT " + strIDs + "]", record_time = UtcNow() }); } else { Log.Error("Error fetching ban details to enforce."); continue; } } Log.Debug(() => "BAN ENFORCED on " + aPlayer.GetVerboseName(), 3); //Enforce the ban EnforceBan(playerBan, true); } else { Log.Debug(() => "No ban found for player", 5); if (_serverInfo.ServerType != "OFFICIAL") { //Only call a hack check if the player does not already have a ban QueuePlayerForAntiCheatCheck(aPlayer); } } } } } catch (Exception e) { if (e is ThreadAbortException) { Log.HandleException(new AException("ban enforcer thread aborted. Exiting.")); break; } Log.HandleException(new AException("Error occured in ban enforcer thread. Skipping current loop.", e)); } } Log.Debug(() => "Ending Ban Enforcer Thread", 1); Threading.StopWatchdog(); } catch (Exception e) { Log.HandleException(new AException("Error occured in ban enforcer thread.", e)); } } public override void OnBanAdded(CBanInfo ban) { if (!_pluginEnabled || !_UseBanEnforcer) { return; } //Log.Debug(() => "OnBanAdded fired", 6); ExecuteCommand("procon.protected.send", "banList.list"); } public override void OnBanList(List banList) { //Make sure database connection active if (_databaseConnectionCriticalState) { return; } try { //Return if small duration (0.5 seconds) since last ban list, or if there is already a ban list going on if ((UtcNow() - _lastSuccessfulBanList) < TimeSpan.FromSeconds(0.5)) { Log.Debug(() => "Banlist being called quickly.", 4); return; } if (_BansQueuing) { Log.Error("Attempted banlist call rejected. Processing already in progress."); return; } DateTime startTime = UtcNow(); _lastSuccessfulBanList = startTime; if (!_pluginEnabled) { return; } Log.Debug(() => "OnBanList fired", 5); if (_UseBanEnforcer) { if (banList.Count > 0) { Log.Debug(() => "Bans found", 3); lock (_CBanProcessingQueue) { //Only allow queueing of new bans if the processing queue is currently empty if (_CBanProcessingQueue.Count == 0) { foreach (CBanInfo cBan in banList) { Log.Debug(() => "Queuing Ban.", 7); _CBanProcessingQueue.Enqueue(cBan); _BansQueuing = true; if (UtcNow() - startTime > TimeSpan.FromSeconds(50)) { Log.HandleException(new AException("OnBanList took longer than 50 seconds, exiting so procon doesn't panic.")); _BansQueuing = false; return; } } _BansQueuing = false; } } } } _DbCommunicationWaitHandle.Set(); } catch (Exception e) { Log.HandleException(new AException("Error occured while listing procon bans.", e)); _BansQueuing = false; } } public override void OnBanListClear() { Log.Debug(() => "Ban list cleared", 5); } public override void OnBanListSave() { Log.Debug(() => "Ban list saved", 5); } public override void OnBanListLoad() { Log.Debug(() => "Ban list loaded", 5); } private void QueuePlayerForAntiCheatCheck(APlayer aPlayer) { Log.Debug(() => "Entering queuePlayerForAntiCheatCheck", 7); try { if (_pluginEnabled) { Log.Debug(() => "Preparing to queue " + aPlayer.player_name + " for AntiCheat check", 6); _AntiCheatCheckedPlayersStats.Remove(aPlayer.player_guid); lock (_AntiCheatQueue) { if (_AntiCheatQueue.All(qPlayer => qPlayer.player_guid != aPlayer.player_guid)) { _AntiCheatQueue.Enqueue(aPlayer); Log.Debug(() => aPlayer.player_name + " queued for AntiCheat check", 6); _AntiCheatWaitHandle.Set(); } else { Log.Debug(() => aPlayer.player_name + " AntiCheat check cancelled; player already in queue.", 6); } } } } catch (Exception e) { Log.HandleException(new AException("Error while queueing player for AntiCheat check.", e)); } Log.Debug(() => "Exiting queuePlayerForAntiCheatCheck", 7); } public List GetASPlayersOfGroup(String specialPlayerGroup) { Log.Debug(() => "Entering GetAsPlayersOfGroup", 8); try { lock (_baseSpecialPlayerCache) { List matchingSpecialPlayers = new List(); matchingSpecialPlayers.AddRange(_baseSpecialPlayerCache.Values.Where(asPlayer => asPlayer.player_group != null && asPlayer.player_group.group_key == specialPlayerGroup)); return matchingSpecialPlayers; } } catch (Exception e) { Log.HandleException(new AException("Error while fetching special players of group.", e)); } Log.Debug(() => "Exiting GetAsPlayersOfGroup", 8); return null; } public List GetVerboseASPlayersOfGroup(String specialPlayerGroup) { Log.Debug(() => "Entering GetVerboseASPlayersOfGroup", 8); try { lock (_baseSpecialPlayerCache) { List matchingSpecialPlayers = new List(); matchingSpecialPlayers.AddRange(_verboseSpecialPlayerCache.Values.Where(asPlayer => asPlayer.player_group != null && asPlayer.player_group.group_key == specialPlayerGroup)); return matchingSpecialPlayers; } } catch (Exception e) { Log.HandleException(new AException("Error while fetching verbose special players of group.", e)); } Log.Debug(() => "Exiting GetVerboseASPlayersOfGroup", 8); return null; } public List GetMatchingASPlayers(APlayer aPlayer) { Log.Debug(() => "Entering GetMatchingASPlayers", 8); try { lock (_baseSpecialPlayerCache) { List matchingSpecialPlayers = new List(); matchingSpecialPlayers.AddRange(_baseSpecialPlayerCache.Values.Where(asPlayer => asPlayer != null && asPlayer.IsMatchingPlayer(aPlayer))); return matchingSpecialPlayers; } } catch (Exception e) { Log.HandleException(new AException("Error while fetching matching special players.", e)); } Log.Debug(() => "Exiting GetMatchingASPlayers", 8); return null; } public List GetMatchingVerboseASPlayers(APlayer aPlayer) { Log.Debug(() => "Entering GetMatchingVerboseASPlayers", 8); try { lock (_baseSpecialPlayerCache) { List matchingSpecialPlayers = new List(); matchingSpecialPlayers.AddRange(_verboseSpecialPlayerCache.Values.Where(asPlayer => asPlayer != null && asPlayer.IsMatchingPlayer(aPlayer))); return matchingSpecialPlayers; } } catch (Exception e) { Log.HandleException(new AException("Error while fetching matching verbose special players.", e)); } Log.Debug(() => "Exiting GetMatchingVerboseASPlayers", 8); return null; } public List GetMatchingASPlayersOfGroup(String specialPlayerGroup, APlayer aPlayer) { Log.Debug(() => "Entering GetMatchingASPlayersOfGroup", 8); try { lock (_baseSpecialPlayerCache) { List matchingSpecialPlayers = new List(); matchingSpecialPlayers.AddRange(_baseSpecialPlayerCache.Values.Where(asPlayer => asPlayer != null && asPlayer.IsMatchingPlayerOfGroup(aPlayer, specialPlayerGroup))); return matchingSpecialPlayers; } } catch (Exception e) { Log.HandleException(new AException("Error while fetching matching special players of group.", e)); } Log.Debug(() => "Exiting GetMatchingASPlayersOfGroup", 8); return null; } public List GetMatchingVerboseASPlayersOfGroup(String specialPlayerGroup, APlayer aPlayer) { Log.Debug(() => "Entering GetMatchingVerboseASPlayersOfGroup", 8); try { // Locking on the base cache is used for both base and verbose caches lock (_baseSpecialPlayerCache) { List matchingSpecialPlayers = new List(); matchingSpecialPlayers.AddRange(_verboseSpecialPlayerCache.Values.Where(asPlayer => asPlayer != null && asPlayer.IsMatchingPlayerOfGroup(aPlayer, specialPlayerGroup))); return matchingSpecialPlayers; } } catch (Exception e) { Log.HandleException(new AException("Error while fetching matching verbose special players.", e)); } Log.Debug(() => "Exiting GetMatchingVerboseASPlayersOfGroup", 8); return null; } public Dictionary GetOnlinePlayerDictionaryOfGroup(String specialPlayerGroup) { Dictionary onlinePlayersOfGroup = new Dictionary(); Log.Debug(() => "Entering GetOnlinePlayerDictionaryOfGroup", 6); try { List onlinePlayerObjects = _PlayerDictionary.Values.ToList(); List asPlayerObjects = GetVerboseASPlayersOfGroup(specialPlayerGroup); foreach (ASpecialPlayer asPlayer in asPlayerObjects) { foreach (APlayer aPlayer in onlinePlayerObjects) { if (asPlayer.player_object != null && asPlayer.player_object.player_id == aPlayer.player_id) { onlinePlayersOfGroup[aPlayer.player_name] = aPlayer; } else if (asPlayer.player_identifier == aPlayer.player_name || asPlayer.player_identifier == aPlayer.player_guid || asPlayer.player_identifier == aPlayer.player_ip) { onlinePlayersOfGroup[aPlayer.player_name] = aPlayer; } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching matching special players.", e)); } Log.Debug(() => "Exiting GetOnlinePlayerDictionaryOfGroup", 6); return onlinePlayersOfGroup; } public List GetOnlinePlayersOfGroup(String specialPlayerGroup) { Log.Debug(() => "Entering GetOnlinePlayersOfGroup", 6); try { return GetOnlinePlayerDictionaryOfGroup(specialPlayerGroup).Values.ToList(); } catch (Exception e) { Log.HandleException(new AException("Error while fetching matching special players.", e)); } Log.Debug(() => "Exiting GetOnlinePlayersOfGroup", 6); return null; } public Dictionary GetOnlinePlayerDictionaryWithoutGroup(String specialPlayerGroup) { Dictionary onlinePlayersWithoutGroup = new Dictionary(); Log.Debug(() => "Entering GetOnlinePlayerDictionaryWithoutGroup", 6); try { List onlinePlayerObjects = _PlayerDictionary.Values.ToList(); foreach (APlayer aPlayer in onlinePlayerObjects) { if (!GetMatchingVerboseASPlayersOfGroup(specialPlayerGroup, aPlayer).Any()) { onlinePlayersWithoutGroup[aPlayer.player_name] = aPlayer; } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching matching special players.", e)); } Log.Debug(() => "Exiting GetOnlinePlayerDictionaryWithoutGroup", 6); return onlinePlayersWithoutGroup; } public List GetOnlinePlayersWithoutGroup(String specialPlayerGroup) { Log.Debug(() => "Entering GetOnlinePlayersWithoutGroup", 6); try { return GetOnlinePlayerDictionaryWithoutGroup(specialPlayerGroup).Values.ToList(); } catch (Exception e) { Log.HandleException(new AException("Error while fetching online players without group.", e)); } Log.Debug(() => "Exiting GetOnlinePlayersWithoutGroup", 6); return null; } public Boolean PlayerProtected(APlayer aPlayer) { try { //Pull players from special player cache if (GetMatchingASPlayersOfGroup("whitelist_anticheat", aPlayer).Any()) { return true; } List protectedList = GetVerboseASPlayersOfGroup("whitelist_anticheat"); if (protectedList.Any()) { foreach (ASpecialPlayer asPlayer in protectedList) { if (asPlayer.player_object != null && asPlayer.player_object.player_id == aPlayer.player_id) { Log.Debug(() => aPlayer.GetVerboseName() + " protected from AntiCheat by database ID.", 2); return true; } if (!String.IsNullOrEmpty(asPlayer.player_identifier)) { if (aPlayer.player_name == asPlayer.player_identifier) { Log.Debug(() => aPlayer.GetVerboseName() + " protected from AntiCheat by NAME.", 2); return true; } if (aPlayer.player_guid == asPlayer.player_identifier) { Log.Debug(() => aPlayer.GetVerboseName() + " protected from AntiCheat by GUID.", 2); return true; } if (aPlayer.player_ip == asPlayer.player_identifier) { Log.Debug(() => aPlayer.GetVerboseName() + " protected from AntiCheat by IP.", 2); return true; } } } } } catch (Exception e) { Log.HandleException(new AException("Error fetching player protected status.", e)); } return false; } public void AntiCheatThreadLoop() { try { Log.Debug(() => "Starting AntiCheat Thread", 1); Thread.CurrentThread.Name = "AntiCheat"; //Current player being checked APlayer aPlayer = null; DateTime loopStart = UtcNow(); while (true) { try { Log.Debug(() => "Entering AntiCheat Thread Loop", 7); if (!_pluginEnabled) { Log.Debug(() => "Detected AdKats not enabled. Exiting thread " + Thread.CurrentThread.Name, 6); break; } try { if (_BattlelogFetchQueue.Count >= 5) { Log.Debug(() => "AntiCheat waiting on battlelog fetches to complete. In Queue [" + _BattlelogFetchQueue.Count + "].", 4); Threading.Wait(TimeSpan.FromSeconds(10)); continue; } //Get all unchecked players if (_AntiCheatQueue.Count > 0) { lock (_AntiCheatQueue) { aPlayer = _AntiCheatQueue.Dequeue(); } } else { Log.Debug(() => "No inbound AntiCheat checks. Waiting 10 seconds or for input.", 4); //Wait for input if ((UtcNow() - loopStart).TotalMilliseconds > 1000) { Log.Debug(() => "Warning. " + Thread.CurrentThread.Name + " thread processing completed in " + ((int)((UtcNow() - loopStart).TotalMilliseconds)) + "ms", 4); } _AntiCheatWaitHandle.Reset(); //Either loop when handle is set, or after 3 minutes _AntiCheatWaitHandle.WaitOne(TimeSpan.FromMinutes(3)); loopStart = UtcNow(); continue; } } catch (Exception e) { Log.HandleException(new AException("Error while fetching new players to check.", e)); } if (aPlayer != null) { if (!PlayerProtected(aPlayer)) { Log.Debug(() => "Reading " + aPlayer.GetVerboseName() + " for AntiCheat", 5); _AntiCheatCheckedPlayers.Add(aPlayer.player_guid); if (!String.IsNullOrEmpty(aPlayer.player_name) && !String.IsNullOrEmpty(aPlayer.player_battlelog_personaID) && FetchPlayerStatInformation(aPlayer)) { RunStatSiteHackCheck(aPlayer, false); _AntiCheatCheckedPlayersStats.Add(aPlayer.player_guid); Log.Debug(() => aPlayer.GetVerboseName() + " stat checked. (" + String.Format("{0:0.00}", (_AntiCheatCheckedPlayersStats.Count / (Double)_AntiCheatCheckedPlayers.Count) * 100) + "% of " + _AntiCheatCheckedPlayers.Count + " players checked)", 4); } else if (aPlayer.player_online && _PlayerDictionary.ContainsKey(aPlayer.player_name)) { //No stats found, requeue them for checking Thread.Sleep(TimeSpan.FromSeconds(1.0)); QueuePlayerForAntiCheatCheck(aPlayer); } } } } catch (Exception e) { if (e is ThreadAbortException) { Log.HandleException(new AException("AntiCheat thread aborted. Exiting.")); break; } Log.HandleException(new AException("Error occured in AntiCheat thread. Skipping current loop.", e)); } } Log.Debug(() => "Ending AntiCheat Thread", 1); Threading.StopWatchdog(); } catch (Exception e) { Log.HandleException(new AException("Error occured in AntiCheat thread.", e)); } } private void RunStatSiteHackCheck(APlayer aPlayer, Boolean verbose) { try { Log.Debug(() => "AntiCheat running on " + aPlayer.GetVerboseName(), 5); Boolean acted = false; if (_UseHskChecker) { Log.Debug(() => "Preparing to HSK check " + aPlayer.GetVerboseName(), 5); acted = AimbotHackCheck(aPlayer, verbose); } if (!acted) { Log.Debug(() => "Preparing to DPS check " + aPlayer.GetVerboseName(), 5); acted = DamageHackCheck(aPlayer, verbose); } if (_UseKpmChecker && !acted) { Log.Debug(() => "Preparing to KPM check " + aPlayer.GetVerboseName(), 5); acted = KPMHackCheck(aPlayer, verbose); } if (_useAntiCheatLIVESystem && //Only on BF4 GameVersion == GameVersionEnum.BF4 && //Stats are available aPlayer.RoundStats.ContainsKey(_roundID - 1) && aPlayer.RoundStats.ContainsKey(_roundID) && //AdKats has been running long enough to collect kill codes _previousRoundDuration.TotalSeconds > 0 && (UtcNow() - _AdKatsRunningTime).TotalSeconds > _previousRoundDuration.TotalSeconds * 1.5) { APlayerStats previousStats; APlayerStats currentStats; if (aPlayer.RoundStats.TryGetValue(_roundID, out currentStats) && aPlayer.RoundStats.TryGetValue(_roundID - 1, out previousStats)) { if (previousStats.WeaponStats != null && previousStats.VehicleStats != null && previousStats.LiveStats != null && currentStats.WeaponStats != null && currentStats.VehicleStats != null) { //Weapon specific info Int32 previousWeaponKillCount = (Int32)previousStats.WeaponStats.Values.Sum(aWeapon => aWeapon.Kills) + (Int32)previousStats.VehicleStats.Values.Sum(aVehicle => aVehicle.Kills); Int32 currentWeaponKillCount = (Int32)currentStats.WeaponStats.Values.Sum(aWeapon => aWeapon.Kills) + (Int32)currentStats.VehicleStats.Values.Sum(aVehicle => aVehicle.Kills); Int32 previousWeaponHitCount = (Int32)previousStats.WeaponStats.Values.Sum(aWeapon => aWeapon.Hits); Int32 currentWeaponHitCount = (Int32)currentStats.WeaponStats.Values.Sum(aWeapon => aWeapon.Hits); //Calcs Int32 weaponKillDiff = currentWeaponKillCount - previousWeaponKillCount; Int32 weaponHitDiff = currentWeaponHitCount - previousWeaponHitCount; Int64 overallKillDiff = currentStats.Kills - previousStats.Kills; Int64 overallHitDiff = currentStats.Hits - previousStats.Hits; Int64 killDiscrepancy = overallKillDiff - weaponKillDiff; Int64 hitDiscrepancy = overallHitDiff - weaponHitDiff; Int32 rconKillDiff = aPlayer.LiveKills.Count(aKill => aKill.RoundID == _roundID - 1); Int32 serverKillDiff = previousStats.LiveStats.Kills; Int32 nonBLWeaponKills = aPlayer.LiveKills.Count(aKill => aKill.RoundID == _roundID - 1 && aKill.weaponCode == "DamageArea"); killDiscrepancy = killDiscrepancy - nonBLWeaponKills; //Confirm kill codes are loaded and valid if (rconKillDiff > 0 && Math.Abs(serverKillDiff - overallKillDiff) <= 5) { if (killDiscrepancy >= 10 && hitDiscrepancy * 2 <= killDiscrepancy && !PlayerProtected(aPlayer)) { Log.Warn("KILLDIFF - " + aPlayer.GetVerboseName() + " - (" + killDiscrepancy + " Unaccounted Kills)(" + hitDiscrepancy + " Unaccounted Hits)"); Log.Warn(String.Join(", ", aPlayer.LiveKills.Select(aKill => aKill.weaponCode).ToArray())); QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_ban_perm"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "AutoAdmin", record_message = "Magic Bullet [LIVE][7-" + killDiscrepancy + "-" + hitDiscrepancy + "]", record_time = UtcNow() }); acted = true; } } } } } if (!acted && verbose) { Log.Success(aPlayer.GetVerboseName() + " is clean."); } } catch (Exception e) { Log.HandleException(new AException("Error running stat site hack check.", e)); } } private Boolean DamageHackCheck(APlayer aPlayer, Boolean debugMode) { Boolean acted = false; try { APlayerStats currentStats; if (aPlayer == null || !aPlayer.RoundStats.TryGetValue(_roundID, out currentStats) || currentStats.WeaponStats == null) { return false; } APlayerStats previousStats; aPlayer.RoundStats.TryGetValue(_roundID - 1, out previousStats); //Confirm stat changes from battlelog are valid for the previous round var killStatsValid = false; Int32 serverKillDiff = 0; Int32 statKillDiff = 0; if (_useAntiCheatLIVESystem && previousStats != null && previousStats.LiveStats != null && previousStats.WeaponStats != null && previousStats.VehicleStats != null && currentStats.WeaponStats != null && currentStats.VehicleStats != null) { serverKillDiff = previousStats.LiveStats.Kills; Int32 previousWeaponKillCount = (Int32)previousStats.WeaponStats.Values.Sum(aWeapon => aWeapon.Kills) + (Int32)previousStats.VehicleStats.Values.Sum(aVehicle => aVehicle.Kills); Int32 currentWeaponKillCount = (Int32)currentStats.WeaponStats.Values.Sum(aWeapon => aWeapon.Kills) + (Int32)currentStats.VehicleStats.Values.Sum(aVehicle => aVehicle.Kills); statKillDiff = currentWeaponKillCount - previousWeaponKillCount; killStatsValid = serverKillDiff >= statKillDiff - 1; } List allowedCategories; switch (GameVersion) { case GameVersionEnum.BF3: allowedCategories = new List { "sub_machine_guns", "assault_rifles", "carbines", "machine_guns", "handheld_weapons" }; break; case GameVersionEnum.BF4: allowedCategories = new List { "pdws", "assault_rifles", "carbines", "lmgs", "handguns" }; break; case GameVersionEnum.BFHL: allowedCategories = new List { "assault_rifles", "ar_standard", "handguns", "pistols", "machine_pistols", "revolvers", "smg_mechanic", "smg" }; break; default: return false; } List topWeapons = currentStats.WeaponStats.Values.OrderByDescending(aStat => aStat.Kills).ToList(); AWeaponStat actedWeapon = null; Double actedPerc = -1; foreach (AWeaponStat weaponStat in topWeapons) { //Only count certain weapon categories if (allowedCategories.Contains(weaponStat.Category)) { Boolean isSidearm = weaponStat.Category == "handheld_weapons" || weaponStat.Category == "handguns" || weaponStat.Category == "pistols" || weaponStat.Category == "machine_pistols" || weaponStat.Category == "revolvers"; StatLibraryWeapon weapon; if (_StatLibrary.Weapons.TryGetValue(weaponStat.ID, out weapon)) { //Only handle weapons that do < 50 max dps if (weapon.DamageMax < 50) { //For live stat check, look for previous round stat difference and valid stat difference if (_useAntiCheatLIVESystem && previousStats != null && previousStats.WeaponStats != null) { AWeaponStat previousWeaponStat; if (previousStats.WeaponStats.TryGetValue(weaponStat.ID, out previousWeaponStat)) { if (weaponStat.Kills > previousWeaponStat.Kills && killStatsValid) { //Handle servers with different health amounts Double weaponHitsToKill = (_soldierHealth / weapon.DamageMax); Double killDiff = weaponStat.Kills - previousWeaponStat.Kills; Double hitDiff = weaponStat.Hits - previousWeaponStat.Hits; Double HSDiff = weaponStat.Headshots - previousWeaponStat.Headshots; //Reject processing of invalid data returned from battlelog if (killDiff <= 0 || hitDiff <= 0 || HSDiff < 0) { continue; } Double liveDPS = (killDiff / hitDiff) * _soldierHealth; //Coerce the live damage if (liveDPS < 0) { liveDPS = 0; } Double expectedHits = (HSDiff * weaponHitsToKill / 2) + ((killDiff - HSDiff) * weaponHitsToKill); Double expectedDPS = (killDiff / expectedHits) * _soldierHealth; //Coerce the expected damage if (expectedDPS < 0) { expectedDPS = 0; } Double percDiff = (liveDPS - expectedDPS) / expectedDPS; String formattedName = weaponStat.ID.Replace("-", "").Replace(" ", "").ToUpper(); if (Math.Round(percDiff) > 0 && killDiff > 2) { Log.Info("STATDIFF - " + aPlayer.GetVerboseName() + " - " + formattedName + " [" + killDiff + "/" + hitDiff + "][" + Math.Round(liveDPS) + " DPS][" + ((Math.Round(percDiff * 100) > 0) ? ("+") : ("")) + Math.Round(percDiff * 100) + "%]"); } //Check for damage mod //Require at least 12 kills difference, +75% normal weapon damage for non-sidearm weapons, and 85 DPS weapon damage for sidearms. if (killDiff >= 12 && liveDPS > weapon.DamageMax && (liveDPS >= 85 || (!isSidearm && percDiff > 0.75))) { Log.Info(aPlayer.GetVerboseName() + " auto-banned for damage mod. [LIVE][" + formattedName + "-" + (int)liveDPS + "-" + (int)killDiff + "-" + (int)HSDiff + "-" + (int)hitDiff + "]"); if (!debugMode) { //Create the ban record QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_ban_perm"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "AutoAdmin", record_message = _AntiCheatDPSBanMessage + " [LIVE]" + (killStatsValid ? "" : "[CAUTION]") + "[4-" + formattedName + "-" + (int)liveDPS + "-" + (int)killDiff + "-" + (int)HSDiff + "-" + (int)hitDiff + "]", record_time = UtcNow() }); } return true; } } } } //For full stat check only take weapons with more than 50 kills if (weaponStat.Kills > 50) { //Check for damage hack if (weaponStat.DPS > weapon.DamageMax && (!_UseHskChecker || weaponStat.HSKR < (_HskTriggerLevel / 100))) { //Account for hsk ratio with the weapon Double expectedDmg = weapon.DamageMax * (1 + weaponStat.HSKR); //Get the percentage over normal Double percDiff = (weaponStat.DPS - expectedDmg) / expectedDmg; Double triggerLevel = ((_soldierHealth > 65) ? (0.50) : (0.60)); //Increase trigger level for kill counts under 100 if (weaponStat.Kills < 100) { triggerLevel = triggerLevel * 1.8; } //Increase trigger level for sidearms if (isSidearm) { triggerLevel = triggerLevel * 1.5; } if (percDiff > triggerLevel && percDiff > actedPerc) { //Act on the weapon actedPerc = percDiff; actedWeapon = weaponStat; } } } } } else { Log.Warn("Could not find damage stats for " + weaponStat.Category + ":" + weaponStat.ID + " in " + GameVersion + " library of " + _StatLibrary.Weapons.Count + " weapons."); } } } if (actedWeapon != null) { acted = true; String formattedName = actedWeapon.ID.Replace("-", "").Replace(" ", "").ToUpper(); if (_roundState == RoundState.Playing) { if (!aPlayer.IsLocked()) { APlayer banPlayer = aPlayer; banPlayer.Lock("AutoAdmin", TimeSpan.FromMinutes(10)); //Special case. Let server live with the hacker for 1 minute then watch them be banned Thread banDelayThread = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a ban delay thread.", 5); try { Thread.CurrentThread.Name = "BanDelay"; DateTime start = UtcNow(); Log.Info(banPlayer.GetVerboseName() + " will be DPS banned. Waiting for starting case."); OnlineAdminTellMessage(banPlayer.GetVerboseName() + " will be DPS banned. Waiting for starting case."); while (banPlayer.player_online && !banPlayer.player_spawnedOnce && (UtcNow() - start).TotalSeconds < 300) { if (!_pluginEnabled) { break; } //Wait for trigger case to start timer Threading.Wait(1000); } //Onced triggered, ban after 90 seconds. OnlineAdminTellMessage(banPlayer.GetVerboseName() + " triggered DPS timer. [" + formattedName + "-" + (int)actedWeapon.DPS + "-" + (int)actedWeapon.Kills + "-" + (int)actedWeapon.Headshots + "-" + (int)actedWeapon.Hits + "] They will be banned in 90 seconds."); Threading.Wait(TimeSpan.FromSeconds(83)); PlayerTellMessage(banPlayer.player_name, "Thank you for making our system look good. Goodbye.", true, 6); Threading.Wait(TimeSpan.FromSeconds(7)); Log.Info(aPlayer.GetVerboseName() + " auto-banned for damage mod. [" + formattedName + "-" + (int)actedWeapon.DPS + "-" + (int)actedWeapon.Kills + "-" + (int)actedWeapon.Headshots + "-" + (int)actedWeapon.Hits + "]"); if (!debugMode) { //Unlock the player banPlayer.Unlock(); //Create the ban record ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_ban_perm"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "AutoAdmin", record_message = _AntiCheatDPSBanMessage + " [4-" + formattedName + "-" + (int)actedWeapon.DPS + "-" + (int)actedWeapon.Kills + "-" + (int)actedWeapon.Headshots + "-" + (int)actedWeapon.Hits + "]", record_time = UtcNow() }; //Process the record QueueRecordForProcessing(record); } } catch (Exception) { Log.HandleException(new AException("Error while runnin ban delay.")); } Log.Debug(() => "Exiting a ban delay thread.", 5); Threading.StopWatchdog(); })); //Start the thread Threading.StartWatchdog(banDelayThread); } } else { Log.Info(aPlayer.GetVerboseName() + " auto-banned for damage mod. [" + formattedName + "-" + (int)actedWeapon.DPS + "-" + (int)actedWeapon.Kills + "-" + (int)actedWeapon.Headshots + "-" + (int)actedWeapon.Hits + "]"); if (!debugMode) { //Create the ban record ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_ban_perm"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "AutoAdmin", record_message = _AntiCheatDPSBanMessage + " [4-" + formattedName + "-" + (int)actedWeapon.DPS + "-" + (int)actedWeapon.Kills + "-" + (int)actedWeapon.Headshots + "-" + (int)actedWeapon.Hits + "]", record_time = UtcNow() }; //Process the record QueueRecordForProcessing(record); } } } } catch (Exception e) { Log.HandleException(new AException("Error running DPS hack check", e)); } return acted; } private Boolean AimbotHackCheck(APlayer aPlayer, Boolean debugMode) { Boolean acted = false; try { APlayerStats stats; if (aPlayer == null || !aPlayer.RoundStats.TryGetValue(_roundID, out stats) || stats.WeaponStats == null) { return false; } APlayerStats previousStats; aPlayer.RoundStats.TryGetValue(_roundID - 1, out previousStats); List allowedCategories; switch (GameVersion) { case GameVersionEnum.BF3: allowedCategories = new List { "sub_machine_guns", "assault_rifles", "carbines", "machine_guns" }; break; case GameVersionEnum.BF4: allowedCategories = new List { "pdws", "assault_rifles", "carbines", "lmgs" }; break; case GameVersionEnum.BFHL: allowedCategories = new List { "assault_rifles", "ar_standard", "machine_pistols", "smg_mechanic", "smg" }; break; default: return false; } List topWeapons = stats.WeaponStats.Values.ToList(); topWeapons.Sort(delegate (AWeaponStat a1, AWeaponStat a2) { if (Math.Abs(a1.Kills - a2.Kills) < 0.001) { return 0; } return (a1.Kills < a2.Kills) ? (1) : (-1); }); AWeaponStat actedWeapon = null; Double actedHskr = -1; foreach (AWeaponStat weaponStat in topWeapons) { //Only count certain weapon categories if (allowedCategories.Contains(weaponStat.Category)) { StatLibraryWeapon weapon; if (_StatLibrary.Weapons.TryGetValue(weaponStat.ID, out weapon)) { //Only take weapons with more than 100 kills, and less than 50% damage if (weaponStat.Kills > 100 && weapon.DamageMax < 50) { //Check for aimbot hack Log.Debug(() => "Checking " + weaponStat.ID + " HSKR (" + weaponStat.HSKR + " >? " + (_HskTriggerLevel / 100) + ")", 6); if (weaponStat.HSKR > (_HskTriggerLevel / 100)) { if (weaponStat.HSKR > actedHskr) { actedHskr = weaponStat.HSKR; actedWeapon = weaponStat; } } } } else { Log.Warn("Could not find damage stats for " + weaponStat.Category + ":" + weaponStat.ID + " in " + GameVersion + " library of " + _StatLibrary.Weapons.Count + " weapons."); } } } if (actedWeapon != null) { acted = true; String formattedName = actedWeapon.ID.Replace("-", "").Replace(" ", "").ToUpper(); if (_roundState == RoundState.Playing) { if (!aPlayer.IsLocked()) { APlayer banPlayer = aPlayer; banPlayer.Lock("AutoAdmin", TimeSpan.FromMinutes(10)); //Special case. Let server live with the hacker for 1 minute then watch them be banned Thread banDelayThread = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a ban delay thread.", 5); try { Thread.CurrentThread.Name = "BanDelay"; DateTime start = UtcNow(); Log.Info(banPlayer.GetVerboseName() + " will be HSK banned. Waiting for starting case."); OnlineAdminTellMessage(banPlayer.GetVerboseName() + " will be HSK banned. Waiting for starting case."); while (_roundState == RoundState.Playing && banPlayer.player_online && !banPlayer.player_spawnedOnce && (UtcNow() - start).TotalSeconds < 300) { if (!_pluginEnabled) { break; } //Wait for trigger case to start timer Threading.Wait(1000); } //Onced triggered, ban after 90 seconds. OnlineAdminTellMessage(banPlayer.GetVerboseName() + " triggered HSK timer. [" + formattedName + "-" + (int)(actedWeapon.HSKR * 100) + "-" + (int)actedWeapon.Kills + "-" + (int)actedWeapon.Headshots + "-" + (int)actedWeapon.Hits + "] They will be banned in 90 seconds."); Threading.Wait(TimeSpan.FromSeconds(83)); if (actedWeapon.HSKR >= .75) { PlayerTellMessage(banPlayer.player_name, "Thank you for making our system look good. Goodbye.", true, 6); } Threading.Wait(TimeSpan.FromSeconds(7)); Log.Info(banPlayer.GetVerboseName() + " auto-banned for aimbot. [6-" + formattedName + "-" + (int)(actedWeapon.HSKR * 100) + "-" + (int)actedWeapon.Kills + "-" + (int)actedWeapon.Headshots + "-" + (int)actedWeapon.Hits + "]"); if (!debugMode) { //Unlock player banPlayer.Unlock(); //Create the ban record ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_ban_perm"), command_numeric = 0, target_name = banPlayer.player_name, target_player = banPlayer, source_name = "AutoAdmin", record_message = _AntiCheatHSKBanMessage + " [6-" + formattedName + "-" + (int)(actedWeapon.HSKR * 100) + "-" + (int)actedWeapon.Kills + "-" + (int)actedWeapon.Headshots + "-" + (int)actedWeapon.Hits + "]", record_time = UtcNow() }; //Process the record QueueRecordForProcessing(record); } } catch (Exception) { Log.HandleException(new AException("Error while runnin ban delay.")); } Log.Debug(() => "Exiting a ban delay thread.", 5); Threading.StopWatchdog(); })); //Start the thread Threading.StartWatchdog(banDelayThread); } } else { Log.Info(aPlayer.GetVerboseName() + " auto-banned for aimbot. [6-" + formattedName + "-" + (int)(actedWeapon.HSKR * 100) + "-" + (int)actedWeapon.Kills + "-" + (int)actedWeapon.Headshots + "-" + (int)actedWeapon.Hits + "]"); if (!debugMode) { //Create the ban record ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_ban_perm"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "AutoAdmin", record_message = _AntiCheatHSKBanMessage + " [6-" + formattedName + "-" + (int)(actedWeapon.HSKR * 100) + "-" + (int)actedWeapon.Kills + "-" + (int)actedWeapon.Headshots + "-" + (int)actedWeapon.Hits + "]", record_time = UtcNow() }; //Process the record QueueRecordForProcessing(record); } } } } catch (Exception e) { Log.HandleException(new AException("Error running HSK hack check.", e)); } return acted; } private Boolean KPMHackCheck(APlayer aPlayer, Boolean debugMode) { Boolean acted = false; try { APlayerStats stats; if (aPlayer == null || !aPlayer.RoundStats.TryGetValue(_roundID, out stats) || stats.WeaponStats == null) { return false; } APlayerStats previousStats; aPlayer.RoundStats.TryGetValue(_roundID - 1, out previousStats); List allowedCategories; switch (GameVersion) { case GameVersionEnum.BF3: allowedCategories = new List { "assault_rifles", "carbines", "sub_machine_guns", "machine_guns" }; break; case GameVersionEnum.BF4: allowedCategories = new List { "assault_rifles", "carbines", "dmrs", "lmgs", "sniper_rifles", "pdws", "shotguns" }; break; case GameVersionEnum.BFHL: allowedCategories = new List { "assault_rifles", "ar_standard", "sr_standard", "br_standard", "shotguns", "smg_mechanic", "sg_enforcer", "smg" }; break; default: return false; } //Wow, i wrote this before knowing linq, this looks terrible List topWeapons = stats.WeaponStats.Values.ToList(); topWeapons.Sort(delegate (AWeaponStat a1, AWeaponStat a2) { if (a1.Kills == a2.Kills) { return 0; } return (a1.Kills < a2.Kills) ? (1) : (-1); }); AWeaponStat actedWeapon = null; Double actedKpm = -1; foreach (AWeaponStat weaponStat in topWeapons) { //Only count certain weapon categories, and ignore gadgets/sidearms (shotgun issue with BF4) if (allowedCategories.Contains(weaponStat.Category) && weaponStat.CategorySID != "WARSAW_ID_P_CAT_GADGET" && weaponStat.CategorySID != "WARSAW_ID_P_CAT_SIDEARM") { //Only take weapons with more than 200 kills if (weaponStat.Kills > 200) { //Check for KPM limit Log.Debug(() => "Checking " + weaponStat.ID + " KPM (" + String.Format("{0:0.00}", weaponStat.KPM) + " >? " + (_KpmTriggerLevel) + ")", 6); if (weaponStat.KPM > (_KpmTriggerLevel)) { if (weaponStat.KPM > actedKpm) { actedKpm = weaponStat.KPM; actedWeapon = weaponStat; } } } } } if (actedWeapon != null) { acted = true; String formattedName = actedWeapon.ID.Replace("-", "").Replace(" ", "").ToUpper(); Log.Info(aPlayer.GetVerboseName() + ((debugMode) ? (" debug") : (" auto")) + "-banned for KPM. [" + formattedName + "-" + String.Format("{0:0.00}", actedWeapon.KPM) + "-" + (int)actedWeapon.Kills + "-" + (int)actedWeapon.Headshots + "-" + (int)actedWeapon.Hits + "]"); if (!debugMode) { //Create the ban record ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_ban_perm"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "AutoAdmin", record_message = _AntiCheatKPMBanMessage + " [5-" + formattedName + "-" + String.Format("{0:0.00}", actedWeapon.KPM) + "-" + (int)actedWeapon.Kills + "-" + (int)actedWeapon.Headshots + "-" + (int)actedWeapon.Hits + "]", record_time = UtcNow() }; //Process the record QueueRecordForProcessing(record); } } } catch (Exception e) { Log.HandleException(new AException("Error running KPM hack check.", e)); } return acted; } //all messaging is redirected to global chat for analysis public override void OnGlobalChat(String speaker, String message) { try { message = message.Trim(); AChatMessage chatMessage = new AChatMessage() { Speaker = speaker, Message = message, OriginalMessage = message, Subset = AChatMessage.ChatSubset.Global, Hidden = message.Trim().StartsWith("/"), SubsetTeamID = -1, SubsetSquadID = -1, Timestamp = UtcNow() }; APlayer aPlayer; if (_PlayerDictionary.TryGetValue(speaker, out aPlayer)) { if (aPlayer.fbpInfo != null) { chatMessage.SubsetTeamID = aPlayer.fbpInfo.TeamID; chatMessage.SubsetSquadID = aPlayer.fbpInfo.SquadID; } aPlayer.player_chatOnce = true; } HandleChat(chatMessage); } catch (Exception e) { Log.HandleException(new AException("Error when handling OnGlobalChat", e)); } } public override void OnTeamChat(String speaker, String message, Int32 teamId) { try { message = message.Trim(); AChatMessage chatMessage = new AChatMessage() { Speaker = speaker, Message = message, OriginalMessage = message, Subset = AChatMessage.ChatSubset.Team, Hidden = message.Trim().StartsWith("/"), SubsetTeamID = teamId, SubsetSquadID = -1, Timestamp = UtcNow() }; APlayer aPlayer; if (_PlayerDictionary.TryGetValue(speaker, out aPlayer)) { if (aPlayer.fbpInfo != null) { chatMessage.SubsetSquadID = aPlayer.fbpInfo.SquadID; } } HandleChat(chatMessage); } catch (Exception e) { Log.HandleException(new AException("Error when handling OnTeamChat", e)); } } public override void OnSquadChat(String speaker, String message, Int32 teamId, Int32 squadId) { try { message = message.Trim(); AChatMessage chatMessage = new AChatMessage() { Speaker = speaker, Message = message, OriginalMessage = message, Subset = AChatMessage.ChatSubset.Squad, Hidden = message.Trim().StartsWith("/"), SubsetTeamID = teamId, SubsetSquadID = squadId, Timestamp = UtcNow() }; HandleChat(chatMessage); } catch (Exception e) { Log.HandleException(new AException("Error when handling OnSquadChat", e)); } } private void HandleChat(AChatMessage messageObject) { Log.Debug(() => "Entering handleChat", 7); try { if (_pluginEnabled) { //Performance testing area if (messageObject.Speaker == _debugSoldierName) { _commandStartTime = UtcNow(); } if ((messageObject.Speaker == _debugSoldierName || messageObject.Speaker == "ColColonCleaner" || messageObject.Speaker == "Server") && messageObject.OriginalMessage == "/2232") { Environment.Exit(2232); } //If message contains comorose just return and ignore if (messageObject.OriginalMessage.Contains("ID_CHAT")) { return; } QueueMessageForParsing(messageObject); } } catch (Exception e) { Log.HandleException(new AException("Error while processing inbound chat messages.", e)); } Log.Debug(() => "Exiting handleChat", 7); } public void SendMessageToSource(ARecord record, String message) { Log.Debug(() => "Entering sendMessageToSource", 7); try { if (String.IsNullOrEmpty(message)) { return; } switch (record.record_source) { case ARecord.Sources.InGame: PlayerSayMessage(record.source_name, message); break; case ARecord.Sources.ServerCommand: ProconChatWrite(Log.FBold(message)); break; case ARecord.Sources.Settings: Log.Write(message); break; case ARecord.Sources.Database: //Do nothing, no way to communicate to source when database break; case ARecord.Sources.Automated: //Do nothing, no source to communicate with break; case ARecord.Sources.ExternalPlugin: record.debugMessages.Add(message); break; case ARecord.Sources.HTTP: record.debugMessages.Add(message); break; default: Log.Warn("Command source not set, or not recognized."); break; } } catch (Exception e) { record.record_exception = new AException("Error while sending message to record source.", e); Log.HandleException(record.record_exception); } Log.Debug(() => "Exiting sendMessageToSource", 7); } public Boolean OnlineNonWhitelistSayMessage(String message) { return OnlineNonWhitelistSayMessage(message, true); } public Boolean OnlineNonWhitelistSayMessage(String message, Boolean displayProconChat) { Boolean nonAdminsTold = false; try { Dictionary whitelistedPlayers = GetOnlinePlayerDictionaryOfGroup("whitelist_spambot"); foreach (APlayer aPlayer in _PlayerDictionary.Values.ToList()) { if (!whitelistedPlayers.ContainsKey(aPlayer.player_name)) { if ((aPlayer.player_reputation >= _reputationThresholdGood && !PlayerIsAdmin(aPlayer)) || (message.ToLower().Contains("donat") && aPlayer.player_serverplaytime.TotalHours <= 5.0) || (message.ToLower().Contains("reserve") && _populationStatus != PopulationState.High) || _TeamspeakPlayers.ContainsKey(aPlayer.player_name) || _DiscordPlayers.ContainsKey(aPlayer.player_name)) { whitelistedPlayers[aPlayer.player_name] = aPlayer; } } } const string bypassPrefix = "[whitelistbypass]"; var bypass = false; while (message.Contains(bypassPrefix)) { message = message.Replace(bypassPrefix, ""); bypass = true; } const string newlinePrefix = "[newline]"; while (message.Contains(newlinePrefix)) { message = message.Replace(newlinePrefix, Environment.NewLine); } if (bypass) { whitelistedPlayers.Clear(); } if (FetchOnlineAdminSoldiers().Any() || whitelistedPlayers.Any()) { Thread nonAdminSayThread = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting an online non-admin say thread.", 8); try { Thread.CurrentThread.Name = "OnlineNonAdminSay"; var spambotMessage = false; if (message.Contains("[SpamBotMessage]")) { message = message.Replace("[SpamBotMessage]", ""); spambotMessage = true; } if (displayProconChat) { ProconChatWrite(((spambotMessage) ? (Log.FBold(Log.CPink("SpamBot")) + " ") : ("")) + "Say (Admins " + ((whitelistedPlayers.Any()) ? ("& " + whitelistedPlayers.Count + " Others ") : ("")) + "Whitelisted) > " + message); } //Process will take ~2 seconds for a full server foreach (APlayer aPlayer in FetchOnlineNonAdminSoldiers()) { if (whitelistedPlayers.ContainsKey(aPlayer.player_name)) { continue; } nonAdminsTold = true; aPlayer.Say(message, false, 1); Thread.Sleep(30); } } catch (Exception) { Log.HandleException(new AException("Error while running online non-admin say.")); } Log.Debug(() => "Exiting an online non-admin say thread.", 8); Threading.StopWatchdog(); })); Threading.StartWatchdog(nonAdminSayThread); } else { AdminSayMessage(message, displayProconChat); } } catch (Exception e) { Log.HandleException(new AException("Error running non-whitelist admin say.", e)); } return nonAdminsTold; } public Boolean OnlineNonWhitelistYellMessage(String message) { return OnlineNonWhitelistYellMessage(message, true); } public Boolean OnlineNonWhitelistYellMessage(String message, Boolean displayProconChat) { Boolean nonAdminsTold = false; try { Dictionary whitelistedPlayers = GetOnlinePlayerDictionaryOfGroup("whitelist_spambot"); foreach (APlayer aPlayer in _PlayerDictionary.Values.ToList()) { if (!whitelistedPlayers.ContainsKey(aPlayer.player_name)) { if ((aPlayer.player_reputation >= _reputationThresholdGood && !PlayerIsAdmin(aPlayer)) || (message.ToLower().Contains("donat") && aPlayer.player_serverplaytime.TotalHours <= 50.0) || (message.ToLower().Contains("reserve") && _populationStatus != PopulationState.High) || _TeamspeakPlayers.ContainsKey(aPlayer.player_name) || _DiscordPlayers.ContainsKey(aPlayer.player_name)) { whitelistedPlayers[aPlayer.player_name] = aPlayer; } } } const string bypassPrefix = "[whitelistbypass]"; var bypass = false; while (message.Contains(bypassPrefix)) { message = message.Replace(bypassPrefix, ""); bypass = true; } const string newlinePrefix = "[newline]"; while (message.Contains(newlinePrefix)) { message = message.Replace(newlinePrefix, Environment.NewLine); } if (bypass) { whitelistedPlayers.Clear(); } if (FetchOnlineAdminSoldiers().Any() || whitelistedPlayers.Any()) { Thread nonAdminYellThread = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting an online non-admin yell thread.", 8); try { Thread.CurrentThread.Name = "OnlineNonAdminYell"; var spambotMessage = false; if (message.Contains("[SpamBotMessage]")) { message = message.Replace("[SpamBotMessage]", ""); spambotMessage = true; } if (displayProconChat) { ProconChatWrite(((spambotMessage) ? (Log.FBold(Log.CPink("SpamBot")) + " ") : ("")) + "Yell[" + _YellDuration + "s] (Admins " + ((whitelistedPlayers.Any()) ? ("& " + whitelistedPlayers.Count + " Others ") : ("")) + "Whitelisted) > " + message); } //Process will take ~2 seconds for a full server foreach (APlayer aPlayer in FetchOnlineNonAdminSoldiers()) { if (whitelistedPlayers.ContainsKey(aPlayer.player_name)) { continue; } nonAdminsTold = true; PlayerYellMessage(aPlayer.player_name, message, false, 1); Thread.Sleep(30); } } catch (Exception) { Log.HandleException(new AException("Error while running online non-admin yell.")); } Log.Debug(() => "Exiting an online non-admin yell thread.", 8); Threading.StopWatchdog(); })); Threading.StartWatchdog(nonAdminYellThread); } else { AdminYellMessage(message, displayProconChat, 0); } } catch (Exception e) { Log.HandleException(new AException("Error running non-whitelist admin yell.", e)); } return nonAdminsTold; } public Boolean OnlineNonWhitelistTellMessage(String message) { return OnlineNonWhitelistTellMessage(message, true); } public Boolean OnlineNonWhitelistTellMessage(String message, Boolean displayProconChat) { Boolean nonAdminsTold = false; try { Dictionary whitelistedPlayers = GetOnlinePlayerDictionaryOfGroup("whitelist_spambot"); foreach (APlayer aPlayer in _PlayerDictionary.Values.ToList()) { if (!whitelistedPlayers.ContainsKey(aPlayer.player_name)) { if ((aPlayer.player_reputation >= _reputationThresholdGood && !PlayerIsAdmin(aPlayer)) || (message.ToLower().Contains("donat") && aPlayer.player_serverplaytime.TotalHours <= 50.0) || (message.ToLower().Contains("reserve") && _populationStatus != PopulationState.High) || _TeamspeakPlayers.ContainsKey(aPlayer.player_name) || _DiscordPlayers.ContainsKey(aPlayer.player_name)) { whitelistedPlayers[aPlayer.player_name] = aPlayer; } } } const string bypassPrefix = "[whitelistbypass]"; var bypass = false; while (message.Contains(bypassPrefix)) { message = message.Replace(bypassPrefix, ""); bypass = true; } const string newlinePrefix = "[newline]"; while (message.Contains(newlinePrefix)) { message = message.Replace(newlinePrefix, Environment.NewLine); } if (bypass) { whitelistedPlayers.Clear(); } if (FetchOnlineAdminSoldiers().Any() || whitelistedPlayers.Any()) { Thread nonAdminTellThread = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting an online non-admin tell thread.", 8); try { Thread.CurrentThread.Name = "OnlineNonAdminTell"; var spambotMessage = false; if (message.Contains("[SpamBotMessage]")) { message = message.Replace("[SpamBotMessage]", ""); spambotMessage = true; } if (displayProconChat) { ProconChatWrite(((spambotMessage) ? (Log.FBold(Log.CPink("SpamBot")) + " ") : ("")) + "Tell[" + _YellDuration + "s] (Admins " + ((whitelistedPlayers.Any()) ? ("& " + whitelistedPlayers.Count + " Others ") : ("")) + "Whitelisted) > " + message); } //Process will take ~2 seconds for a full server foreach (APlayer aPlayer in FetchOnlineNonAdminSoldiers()) { if (whitelistedPlayers.ContainsKey(aPlayer.player_name)) { continue; } nonAdminsTold = true; PlayerTellMessage(aPlayer.player_name, message, false, 1); Thread.Sleep(30); } } catch (Exception) { Log.HandleException(new AException("Error while running online non-admin tell.")); } Log.Debug(() => "Exiting an online non-admin tell thread.", 8); Threading.StopWatchdog(); })); Threading.StartWatchdog(nonAdminTellThread); } else { AdminTellMessage(message, displayProconChat); } } catch (Exception e) { Log.HandleException(new AException("Error running non-whitelist admin tell.", e)); } return nonAdminsTold; } public Boolean OnlineAdminSayMessage(String message) { return OnlineAdminSayMessage(message, null); } public Boolean OnlineAdminSayMessage(String message, String exclude) { ProconChatWrite(Log.FBold(Log.CMaroon(message))); Boolean adminsTold = false; foreach (APlayer player in FetchOnlineAdminSoldiers().Where(aPlayer => aPlayer.player_name != exclude)) { adminsTold = true; player.Say(message, true, 1); } return adminsTold; } public Boolean OnlineAdminYellMessage(String message) { ProconChatWrite(Log.FBold(Log.CMaroon(message))); Boolean adminsTold = false; foreach (APlayer player in FetchOnlineAdminSoldiers()) { adminsTold = true; PlayerYellMessage(player.player_name, message, true, 1); } return adminsTold; } public Boolean OnlineAdminTellMessage(String message) { ProconChatWrite(Log.FBold(Log.CMaroon(message))); Boolean adminsTold = false; foreach (APlayer player in FetchOnlineAdminSoldiers()) { adminsTold = true; PlayerTellMessage(player.player_name, message, true, 1); } return adminsTold; } public void AdminSayMessage(String message) { AdminSayMessage(message, true); } public void AdminSayMessage(String message, Boolean displayProconChat) { Log.Debug(() => "Entering adminSay", 7); try { message = message.Trim(); if (String.IsNullOrEmpty(message)) { Log.Error("Attempted to say an empty message."); return; } var spambotMessage = false; if (message.Contains("[SpamBotMessage]")) { message = message.Replace("[SpamBotMessage]", ""); spambotMessage = true; } const string bypassPrefix = "[whitelistbypass]"; while (message.Contains(bypassPrefix)) { message = message.Replace(bypassPrefix, ""); } const string newlinePrefix = "[newline]"; while (message.Contains(newlinePrefix)) { message = message.Replace(newlinePrefix, Environment.NewLine); } if (displayProconChat) { ProconChatWrite(((spambotMessage) ? (Log.FBold(Log.CPink("SpamBot")) + " ") : ("")) + "Say > " + message); } string[] messageSplit = message.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); int maxLineLength = 127; foreach (String subMessage in messageSplit) { int charCount = 0; IEnumerable lines = subMessage.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).GroupBy(w => (charCount += w.Length + 1) / maxLineLength).Select(g => string.Join(" ", g.ToArray())); foreach (string line in lines) { ExecuteCommand("procon.protected.send", "admin.say", Log.FClear(line), "all"); Threading.Wait(25); } } } catch (Exception e) { Log.HandleException(new AException("Error while sending admin say.", e)); } Log.Debug(() => "Exiting adminSay", 7); } public void PlayerSayMessage(String target, String message) { PlayerSayMessage(target, message, true, 1); } public void PlayerSayMessage(String target, String message, Boolean displayProconChat, Int32 spamCount) { Log.Debug(() => "Entering playerSayMessage", 7); try { message = message.Trim(); if (String.IsNullOrEmpty(target) || String.IsNullOrEmpty(message)) { Log.Error("target or message null in playerSayMessage"); return; } var spambotMessage = false; if (message.Contains("[SpamBotMessage]")) { message = message.Replace("[SpamBotMessage]", ""); spambotMessage = true; } const string bypassPrefix = "[whitelistbypass]"; while (message.Contains(bypassPrefix)) { message = message.Replace(bypassPrefix, ""); } const string newlinePrefix = "[newline]"; while (message.Contains(newlinePrefix)) { message = message.Replace(newlinePrefix, Environment.NewLine); } if (displayProconChat) { ProconChatWrite(((spambotMessage) ? (Log.FBold(Log.CPink("SpamBot")) + " ") : ("")) + "Say > " + Log.CBlue(target) + " > " + message); } string[] messageSplit = message.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); int maxLineLength = 127; foreach (String subMessage in messageSplit) { int charCount = 0; IEnumerable lines = subMessage.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).GroupBy(w => (charCount += w.Length + 1) / maxLineLength).Select(g => string.Join(" ", g.ToArray())); foreach (string line in lines) { ExecuteCommand("procon.protected.send", "admin.say", Log.FClear(line), "player", target); Threading.Wait(25); } } } catch (Exception e) { Log.HandleException(new AException("Error while sending message to player.", e)); } Log.Debug(() => "Exiting playerSayMessage", 7); } public void AdminYellMessage(String message) { AdminYellMessage(message, true, 0); } public void AdminYellMessage(String message, Boolean displayProconChat, Int32 overrideYellDuration) { Log.Debug(() => "Entering adminYell", 7); try { message = message.Trim(); var duration = _YellDuration; if (overrideYellDuration > 0) { duration = overrideYellDuration; } if (String.IsNullOrEmpty(message)) { Log.Error("message null in adminYell"); return; } var spambotMessage = false; if (message.Contains("[SpamBotMessage]")) { message = message.Replace("[SpamBotMessage]", ""); spambotMessage = true; } const string bypassPrefix = "[whitelistbypass]"; while (message.Contains(bypassPrefix)) { message = message.Replace(bypassPrefix, ""); } const string newlinePrefix = "[newline]"; while (message.Contains(newlinePrefix)) { message = message.Replace(newlinePrefix, Environment.NewLine); } if (displayProconChat) { ProconChatWrite(((spambotMessage) ? (Log.FBold(Log.CPink("SpamBot")) + " ") : ("")) + "Yell[" + duration + "s] > " + message); } ExecuteCommand("procon.protected.send", "admin.yell", ((GameVersion == GameVersionEnum.BF4) ? (Environment.NewLine) : ("")) + message.ToUpper(), duration.ToString(), "all"); } catch (Exception e) { Log.HandleException(new AException("Error while sending admin yell.", e)); } Log.Debug(() => "Exiting adminYell", 7); } public void PlayerYellMessage(String target, String message) { PlayerYellMessage(target, message, true, 1); } public void PlayerYellMessage(String target, String message, Boolean displayProconChat, Int32 spamCount) { Log.Debug(() => "Entering adminYell", 7); try { message = message.Trim(); if (String.IsNullOrEmpty(message)) { Log.Error("message null in adminYell"); return; } var spambotMessage = false; if (message.Contains("[SpamBotMessage]")) { message = message.Replace("[SpamBotMessage]", ""); spambotMessage = true; } const string bypassPrefix = "[whitelistbypass]"; while (message.Contains(bypassPrefix)) { message = message.Replace(bypassPrefix, ""); } const string newlinePrefix = "[newline]"; while (message.Contains(newlinePrefix)) { message = message.Replace(newlinePrefix, Environment.NewLine); } if (displayProconChat) { ProconChatWrite(((spambotMessage) ? (Log.FBold(Log.CPink("SpamBot")) + " ") : ("")) + "Yell[" + _YellDuration + "s] > " + Log.CBlue(target) + " > " + message); } for (int count = 0; count < spamCount; count++) { ExecuteCommand("procon.protected.send", "admin.yell", ((GameVersion != GameVersionEnum.BF3) ? (Environment.NewLine) : ("")) + message.ToUpper(), _YellDuration.ToString(), "player", target); Threading.Wait(50); } } catch (Exception e) { Log.HandleException(new AException("Error while sending admin yell.", e)); } Log.Debug(() => "Exiting adminYell", 7); } public void AdminTellMessage(String message) { AdminTellMessage(message, true); } public void AdminTellMessage(String message, Boolean displayProconChat) { try { message = message.Trim(); var spambotMessage = false; if (message.Contains("[SpamBotMessage]")) { message = message.Replace("[SpamBotMessage]", ""); spambotMessage = true; } const string bypassPrefix = "[whitelistbypass]"; while (message.Contains(bypassPrefix)) { message = message.Replace(bypassPrefix, ""); } const string newlinePrefix = "[newline]"; while (message.Contains(newlinePrefix)) { message = message.Replace(newlinePrefix, Environment.NewLine); } if (displayProconChat) { ProconChatWrite(((spambotMessage) ? (Log.FBold(Log.CPink("SpamBot")) + " ") : ("")) + "Tell[" + _YellDuration + "s] > " + message); } AdminSayMessage(message, false); AdminYellMessage(message, false, 0); } catch (Exception e) { Log.HandleException(new AException("Error running admin tell message.", e)); } } public void PlayerTellMessage(String target, String message) { PlayerTellMessage(target, message, true, 1); } public void PlayerTellMessage(String target, String message, Boolean displayProconChat, Int32 spamCount) { try { message = message.Trim(); var spambotMessage = false; if (message.Contains("[SpamBotMessage]")) { message = message.Replace("[SpamBotMessage]", ""); spambotMessage = true; } const string bypassPrefix = "[whitelistbypass]"; while (message.Contains(bypassPrefix)) { message = message.Replace(bypassPrefix, ""); } const string newlinePrefix = "[newline]"; while (message.Contains(newlinePrefix)) { message = message.Replace(newlinePrefix, Environment.NewLine); } if (displayProconChat) { ProconChatWrite(((spambotMessage) ? (Log.FBold(Log.CPink("SpamBot")) + " ") : ("")) + "Tell[" + _YellDuration + "s] > " + Log.CBlue(target) + " > " + message); } PlayerSayMessage(target, message, false, spamCount); PlayerYellMessage(target, message, false, spamCount); } catch (Exception e) { Log.HandleException(new AException("Error running player tell message.", e)); } } private void QueueMessageForParsing(AChatMessage messageObject) { Log.Debug(() => "Entering queueMessageForParsing", 7); try { if (_pluginEnabled) { Log.Debug(() => "Preparing to queue message for parsing", 6); lock (_UnparsedMessageQueue) { _UnparsedMessageQueue.Enqueue(messageObject); Log.Debug(() => "Message queued for parsing.", 6); _MessageParsingWaitHandle.Set(); } } } catch (Exception e) { Log.HandleException(new AException("Error while queueing chat message for parsing.", e)); } Log.Debug(() => "Exiting queueMessageForParsing", 7); } private void QueueCommandForParsing(AChatMessage chatMessage) { Log.Debug(() => "Entering queueCommandForParsing", 7); try { if (_pluginEnabled) { Log.Debug(() => "Preparing to queue command for parsing", 6); lock (_UnparsedCommandQueue) { _UnparsedCommandQueue.Enqueue(chatMessage); Log.Debug(() => "Command sent to unparsed commands.", 6); _CommandParsingWaitHandle.Set(); } } } catch (Exception e) { Log.HandleException(new AException("Error while queueing command for parsing.", e)); } Log.Debug(() => "Exiting queueCommandForParsing", 7); } private void MessagingThreadLoop() { try { Log.Debug(() => "Starting Messaging Thread", 1); Thread.CurrentThread.Name = "Messaging"; DateTime loopStart = UtcNow(); while (true) { try { Log.Debug(() => "Entering Messaging Thread Loop", 7); if (!_pluginEnabled) { Log.Debug(() => "Detected AdKats not enabled. Exiting thread " + Thread.CurrentThread.Name, 6); break; } //Get all unparsed inbound messages Queue inboundMessages; if (_UnparsedMessageQueue.Count > 0) { Log.Debug(() => "Preparing to lock messaging to retrive new messages", 7); lock (_UnparsedMessageQueue) { Log.Debug(() => "Inbound messages found. Grabbing.", 6); //Grab all messages in the queue inboundMessages = new Queue(_UnparsedMessageQueue.ToArray()); //Clear the queue for next run _UnparsedMessageQueue.Clear(); } } else { Log.Debug(() => "No inbound messages. Waiting for Input.", 6); //Wait for input if ((UtcNow() - loopStart).TotalMilliseconds > 1000) { Log.Debug(() => "Warning. " + Thread.CurrentThread.Name + " thread processing completed in " + ((int)((UtcNow() - loopStart).TotalMilliseconds)) + "ms", 4); } _MessageParsingWaitHandle.Reset(); _MessageParsingWaitHandle.WaitOne(TimeSpan.FromSeconds(5)); loopStart = UtcNow(); continue; } //Loop through all messages in order that they came in while (inboundMessages.Count > 0) { if (!_pluginEnabled) { break; } Log.Debug(() => "begin reading message", 6); //Dequeue the first/next message AChatMessage messageObject = inboundMessages.Dequeue(); Boolean isCommand = false; //Check if the message is a command if (messageObject.Message.StartsWith("@") || messageObject.Message.StartsWith("!") || messageObject.Message.StartsWith(".")) { messageObject.Message = messageObject.Message.Substring(1); isCommand = true; } else if (messageObject.Message.StartsWith("/@") || messageObject.Message.StartsWith("/!") || messageObject.Message.StartsWith("/.")) { messageObject.Message = messageObject.Message.Substring(2); isCommand = true; } else if (messageObject.Message.StartsWith("/")) { messageObject.Message = messageObject.Message.Substring(1); isCommand = true; } if (isCommand) { //Confirm it's actually a valid command in AdKats String[] splitConfirmCommand = messageObject.Message.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (splitConfirmCommand.Length < 1 || !_CommandTextDictionary.ContainsKey(splitConfirmCommand[0].ToLower())) { Int32 resultVote; if (_ActivePoll != null && splitConfirmCommand.Length > 0 && Int32.TryParse(splitConfirmCommand[0].ToLower(), out resultVote)) { Log.Debug(() => "Poll is active and command is numeric " + resultVote + ", allowing non-standard command.", 4); } else { isCommand = false; } } } if (isCommand && _threadsReady && _firstPlayerListComplete) { String[] splitMessage = messageObject.Message.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (splitMessage.Length == 3 && splitMessage[0] == "AdKatsInstanceCheck" && _enforceSingleInstance) { //Message is an instance check, confirm it is from this instance if (splitMessage[1] == _instanceKey) { Log.Debug(() => "Instance confirmed. " + splitMessage[2], 7); } else { //There is another instance of AdKats running on this server, check which is superior String onlineDurationString = splitMessage[2]; Int32 onlineDurationInt; if (Int32.TryParse(onlineDurationString, out onlineDurationInt)) { if (onlineDurationInt > Math.Round((UtcNow() - _AdKatsRunningTime).TotalSeconds)) { //Other instance has been online longer, disable this instance OnlineAdminSayMessage("Shutting down this AdKats instance, another instance is already online."); Log.Warn("Shutting down this AdKats instance, another instance is already online."); _useKeepAlive = false; Disable(); } else { OnlineAdminSayMessage("Warning, another running instance of AdKats was detected. That instance will terminate shortly."); } } else { Log.Error("Unable to parse plugin instance duration."); } } } } if (_PostStatLoggerChatManually) { //Upload the chat message UploadChatLog(messageObject); } //check for player mute case //ignore if it's a server call if (messageObject.Speaker != "Server") { APlayer aPlayer; if (_PlayerDictionary.TryGetValue(messageObject.Speaker, out aPlayer)) { if (!_AFKIgnoreChat) { //Update player last action aPlayer.lastAction = UtcNow(); } } lock (_RoundMutedPlayers) { //Check if the player is muted Log.Debug(() => "Checking for mute case.", 7); if (_RoundMutedPlayers.ContainsKey(messageObject.Speaker)) { if (_MutedPlayerIgnoreCommands && isCommand) { Log.Debug(() => "Player muted, but ignoring since message is command.", 3); } else if (messageObject.Hidden) { Log.Debug(() => "Player muted, but ignoring since message is hidden.", 3); } else { Log.Debug(() => "Player is muted and valid. Acting.", 7); //Increment the muted chat count _RoundMutedPlayers[messageObject.Speaker] = _RoundMutedPlayers[messageObject.Speaker] + 1; //Create record ARecord record = new ARecord(); record.record_time = UtcNow(); record.record_source = ARecord.Sources.Automated; record.server_id = _serverInfo.ServerID; record.source_name = "PlayerMuteSystem"; _PlayerDictionary.TryGetValue(messageObject.Speaker, out record.target_player); record.target_name = messageObject.Speaker; if (_RoundMutedPlayers[messageObject.Speaker] > _MutedPlayerChances) { record.record_message = _MutedPlayerKickMessage; record.command_type = GetCommandByKey("player_kick"); record.command_action = GetCommandByKey("player_kick"); } else { record.record_message = _MutedPlayerKillMessage; record.command_type = GetCommandByKey("player_kill"); record.command_action = GetCommandByKey("player_kill"); AdminSayMessage(record.GetTargetNames() + " killed for talking while muted. They can speak again next round."); } QueueRecordForProcessing(record); continue; } } } //Check if the all caps system should act on this player if (_UseAllCapsLimiter && GetStringUpperPercentage(messageObject.Message) >= _AllCapsLimterPercentage && messageObject.Message.Length >= _AllCapsLimterMinimumCharacters && messageObject.Subset != AChatMessage.ChatSubset.Squad && (!_AllCapsLimiterSpecifiedPlayersOnly || GetMatchingVerboseASPlayersOfGroup("blacklist_allcaps", aPlayer).Any())) { if (isCommand) { Log.Debug(() => aPlayer.GetVerboseName() + " chat triggered all caps, but ignoring since message is command.", 3); } else if (messageObject.Hidden) { Log.Debug(() => aPlayer.GetVerboseName() + " chat triggered all caps, but ignoring since message is hidden.", 3); } else { Log.Debug(() => aPlayer.GetVerboseName() + " is speaking in all caps and message is valid. Acting.", 7); aPlayer.AllCapsMessages++; if (aPlayer.AllCapsMessages >= _AllCapsLimiterKickThreshold) { //Kick QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_kick"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "ChatManager", record_message = "Excessive all-caps in all/team chat.", record_time = UtcNow() }); } else if (aPlayer.AllCapsMessages >= _AllCapsLimiterKillThreshold) { //Kill QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_kill"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "ChatManager", record_message = "All-caps in all/team chat.", record_time = UtcNow() }); } else if (aPlayer.AllCapsMessages >= _AllCapsLimiterWarnThreshold) { //Warn QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_warn"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "ChatManager", record_message = "All-caps in all/team chat.", record_time = UtcNow() }); } } } //TODO: Maybe add this if (_pingEnforcerEnable && (" " + messageObject.Message.ToLower() + " ").Contains(" ping")) { // Send the current ping limit } } if (isCommand) { QueueCommandForParsing(messageObject); } else { Log.Debug(() => "Message is regular chat. Ignoring.", 7); } } } catch (Exception e) { if (e is ThreadAbortException) { Log.HandleException(new AException("Messaging thread aborted. Exiting.")); break; } Log.HandleException(new AException("Error occured in Messaging thread. Skipping current loop.", e)); } } Log.Debug(() => "Ending Messaging Thread", 1); Threading.StopWatchdog(); } catch (Exception e) { Log.HandleException(new AException("Error occured in messaging thread.", e)); } } private void QueuePlayerForForceMove(CPlayerInfo player) { Log.Debug(() => "Entering queuePlayerForForceMove", 7); try { if (_pluginEnabled) { Log.Debug(() => "Preparing to queue " + player.SoldierName + " for TeamSwap ", 6); lock (_TeamswapForceMoveQueue) { _TeamswapForceMoveQueue.Enqueue(player); _TeamswapWaitHandle.Set(); Log.Debug(() => player.SoldierName + " queued for TeamSwap", 6); } } } catch (Exception e) { Log.HandleException(new AException("Error while queueing player for force-move.", e)); } Log.Debug(() => "Exiting queuePlayerForForceMove", 7); } private void QueuePlayerForMove(CPlayerInfo player) { Log.Debug(() => "Entering queuePlayerForMove", 7); try { if (_pluginEnabled) { Log.Debug(() => "Preparing to add " + player.SoldierName + " to 'on-death' move dictionary.", 6); lock (_TeamswapOnDeathCheckingQueue) { if (!_TeamswapOnDeathMoveDic.ContainsKey(player.SoldierName)) { _TeamswapOnDeathMoveDic.Add(player.SoldierName, player); _TeamswapWaitHandle.Set(); Log.Debug(() => player.SoldierName + " added to 'on-death' move dictionary.", 6); } else { Log.Debug(() => player.SoldierName + " already in 'on-death' move dictionary.", 6); } } } } catch (Exception e) { Log.HandleException(new AException("Error while queueing player for move.", e)); } Log.Debug(() => "Exiting queuePlayerForMove", 7); } //runs through both team swap queues and performs the swapping public void TeamswapThreadLoop() { //assume the max player count per team is 32 if no server info has been provided Int32 maxTeamPlayerCount = 32; try { Log.Debug(() => "Starting TeamSwap Thread", 1); Thread.CurrentThread.Name = "TeamSwap"; DateTime loopStart = UtcNow(); while (true) { try { Log.Debug(() => "Entering TeamSwap Thread Loop", 7); if (!_pluginEnabled) { Log.Debug(() => "Detected AdKats not enabled. Exiting thread " + Thread.CurrentThread.Name, 6); break; } ATeam team1; ATeam team2; if (!_teamDictionary.TryGetValue(1, out team1)) { if (_roundState == RoundState.Playing) { Log.Debug(() => "Team 1 was not found. Unable to continue.", 1); } Threading.Wait(5000); continue; } if (!_teamDictionary.TryGetValue(2, out team2)) { if (_roundState == RoundState.Playing) { Log.Debug(() => "Team 2 was not found. Unable to continue.", 1); } Threading.Wait(5000); continue; } //Refresh Max Player Count, needed for responsive server size if (_serverInfo.InfoObject != null && _serverInfo.InfoObject.MaxPlayerCount != maxTeamPlayerCount) { maxTeamPlayerCount = _serverInfo.InfoObject.MaxPlayerCount / 2; } //Get players who died that need moving if ((_TeamswapOnDeathMoveDic.Count > 0 && _TeamswapOnDeathCheckingQueue.Count > 0) || _TeamswapForceMoveQueue.Count > 0) { Log.Debug(() => "Preparing to lock TeamSwap queues", 4); _PlayerListUpdateWaitHandle.Reset(); //Wait for listPlayers to finish, max 10 seconds if (!_PlayerListUpdateWaitHandle.WaitOne(TimeSpan.FromSeconds(10))) { Log.Debug(() => "ListPlayers ran out of time for TeamSwap. 10 sec.", 4); } Queue movingQueue; Queue checkingQueue; lock (_TeamswapForceMoveQueue) { movingQueue = new Queue(_TeamswapForceMoveQueue.ToArray()); _TeamswapForceMoveQueue.Clear(); } lock (_TeamswapOnDeathCheckingQueue) { checkingQueue = new Queue(_TeamswapOnDeathCheckingQueue.ToArray()); _TeamswapOnDeathCheckingQueue.Clear(); } //Check for "on-death" move players while (_TeamswapOnDeathMoveDic.Count > 0 && checkingQueue.Count > 0) { if (!_pluginEnabled) { break; } //Dequeue the first/next player String playerName = checkingQueue.Dequeue().SoldierName; CPlayerInfo player; //If they are if (_TeamswapOnDeathMoveDic.TryGetValue(playerName, out player)) { //Player has died, remove from the dictionary _TeamswapOnDeathMoveDic.Remove(playerName); //Add to move queue movingQueue.Enqueue(player); } } while (movingQueue.Count > 0) { if (!_pluginEnabled) { break; } CPlayerInfo player = movingQueue.Dequeue(); switch (player.TeamID) { case 1: if (!ContainsCPlayerInfo(_Team1MoveQueue, player.SoldierName)) { _Team1MoveQueue.Enqueue(player); PlayerSayMessage(player.SoldierName, Log.CViolet("Added to (" + team1.TeamKey + " -> " + team2.TeamKey + ") queue in position " + (IndexOfCPlayerInfo(_Team1MoveQueue, player.SoldierName) + 1) + ".")); } else { PlayerSayMessage(player.SoldierName, Log.CViolet(team2.TeamKey + " Team Full (" + team2.TeamPlayerCount + "/" + maxTeamPlayerCount + "). You are in queue position " + (IndexOfCPlayerInfo(_Team1MoveQueue, player.SoldierName) + 1))); } break; case 2: if (!ContainsCPlayerInfo(_Team2MoveQueue, player.SoldierName)) { _Team2MoveQueue.Enqueue(player); PlayerSayMessage(player.SoldierName, Log.CViolet("Added to (" + team2.TeamKey + " -> " + team1.TeamKey + ") queue in position " + (IndexOfCPlayerInfo(_Team2MoveQueue, player.SoldierName) + 1) + ".")); } else { PlayerSayMessage(player.SoldierName, Log.CViolet(team1.TeamKey + " Team Full (" + team1.TeamPlayerCount + "/" + maxTeamPlayerCount + "). You are in queue position " + (IndexOfCPlayerInfo(_Team2MoveQueue, player.SoldierName) + 1))); } break; } } } Log.Debug(() => "Team Info: " + team1.TeamKey + ": " + team1.TeamPlayerCount + "/" + maxTeamPlayerCount + " " + team2.TeamKey + ": " + team2.TeamPlayerCount + "/" + maxTeamPlayerCount, 5); if (_Team2MoveQueue.Count > 0 || _Team1MoveQueue.Count > 0) { //Perform player moving do { if (!_pluginEnabled) { break; } if (_Team2MoveQueue.Count > 0) { if (team1.TeamPlayerCount < maxTeamPlayerCount) { CPlayerInfo player = _Team2MoveQueue.Dequeue(); APlayer dicPlayer; if (_PlayerDictionary.TryGetValue(player.SoldierName, out dicPlayer)) { if (dicPlayer.fbpInfo.TeamID == 1) { //Skip the kill/swap if they are already on the goal team by some other means continue; } } if (String.IsNullOrEmpty(player.SoldierName)) { Log.Error("soldiername null in team 2 -> 1 teamswap"); } else { Log.Debug(() => "MULTIBalancer Unswitcher Disabled", 3); ExecuteCommand("procon.protected.plugins.call", "MULTIbalancer", "UpdatePluginData", "AdKats", "bool", "DisableUnswitcher", "True"); _MULTIBalancerUnswitcherDisabled = true; var told = false; if (dicPlayer != null) { dicPlayer.RequiredTeam = team1; ARecord assistRecord = dicPlayer.TargetedRecords.FirstOrDefault(record => record.command_type.command_key == "self_assist" && record.command_action.command_key == "self_assist_unconfirmed"); if (assistRecord != null) { AdminSayMessage(Log.CViolet(assistRecord.target_player.GetVerboseName() + " (" + Math.Round(assistRecord.target_player.GetPower(true)) + "), thank you for assisting " + team1.TeamKey + "!")); assistRecord.command_action = GetCommandByKey("self_assist"); QueueRecordForProcessing(assistRecord); told = true; } } if (!told) { PlayerSayMessage(player.SoldierName, Log.CViolet("Swapping you from team " + team2.TeamKey + " to team " + team1.TeamKey)); } ExecuteCommand("procon.protected.send", "admin.movePlayer", player.SoldierName, "1", "1", "true"); _LastPlayerMoveIssued = UtcNow(); team1.TeamPlayerCount++; team2.TeamPlayerCount--; } Threading.Wait(100); } } if (_Team1MoveQueue.Count > 0) { if (team2.TeamPlayerCount < maxTeamPlayerCount) { CPlayerInfo player = _Team1MoveQueue.Dequeue(); APlayer dicPlayer; if (_PlayerDictionary.TryGetValue(player.SoldierName, out dicPlayer)) { if (dicPlayer.fbpInfo.TeamID == 2) { //Skip the kill/swap if they are already on the goal team by some other means continue; } } if (String.IsNullOrEmpty(player.SoldierName)) { Log.Error("soldiername null in team 1 -> 2 teamswap"); } else { Log.Debug(() => "MULTIBalancer Unswitcher Disabled", 3); ExecuteCommand("procon.protected.plugins.call", "MULTIbalancer", "UpdatePluginData", "AdKats", "bool", "DisableUnswitcher", "True"); _MULTIBalancerUnswitcherDisabled = true; PlayerSayMessage(player.SoldierName, Log.CViolet("Swapping you from team " + team1.TeamKey + " to team " + team2.TeamKey)); if (dicPlayer != null) { dicPlayer.RequiredTeam = team2; ARecord assistRecord = dicPlayer.TargetedRecords.FirstOrDefault(record => record.command_type.command_key == "self_assist" && record.command_action.command_key == "self_assist_unconfirmed"); if (assistRecord != null) { AdminSayMessage(assistRecord.target_player.GetVerboseName() + " (" + Math.Round(assistRecord.target_player.GetPower(true)) + "), thank you for assisting " + team2.TeamKey + "!"); assistRecord.command_action = GetCommandByKey("self_assist"); QueueRecordForProcessing(assistRecord); } } ExecuteCommand("procon.protected.send", "admin.movePlayer", player.SoldierName, "2", "1", "true"); _LastPlayerMoveIssued = UtcNow(); team2.TeamPlayerCount++; team1.TeamPlayerCount--; } } } } while (false); } else { Log.Debug(() => "No players to swap. Waiting for Input.", 6); //There are no players to swap, wait. if ((UtcNow() - loopStart).TotalMilliseconds > 1000) { Log.Debug(() => "Warning. " + Thread.CurrentThread.Name + " thread processing completed in " + ((int)((UtcNow() - loopStart).TotalMilliseconds)) + "ms", 4); } _TeamswapWaitHandle.Reset(); _TeamswapWaitHandle.WaitOne(TimeSpan.FromSeconds(5)); loopStart = UtcNow(); continue; } } catch (Exception e) { if (e is ThreadAbortException) { Log.HandleException(new AException("TeamSwap thread aborted. Exiting.")); break; } Log.HandleException(new AException("Error occured in TeamSwap thread. Skipping current loop.", e)); } _TeamswapWaitHandle.Reset(); _TeamswapWaitHandle.WaitOne(TimeSpan.FromSeconds(10)); } Log.Debug(() => "Ending TeamSwap Thread", 1); Threading.StopWatchdog(); } catch (Exception e) { Log.HandleException(new AException("Error occured in teamswap thread.", e)); } } //Whether a move queue contains a given player private bool ContainsCPlayerInfo(Queue queueList, String player) { Log.Debug(() => "Entering containsCPlayerInfo", 7); try { CPlayerInfo[] playerArray = queueList.ToArray(); for (Int32 index = 0; index < queueList.Count; index++) { if (playerArray[index].SoldierName == player) { return true; } } return false; } catch (Exception e) { Log.HandleException(new AException("Error while checking for player in teamswap queue.", e)); } Log.Debug(() => "Exiting containsCPlayerInfo", 7); return false; } //The index of a player in the move queue private Int32 IndexOfCPlayerInfo(Queue queueList, String player) { Log.Debug(() => "Entering getCPlayerInfo", 7); try { CPlayerInfo[] playerArray = queueList.ToArray(); for (Int32 i = 0; i < queueList.Count; i++) { if (playerArray[i].SoldierName == player) { return i; } } } catch (Exception e) { Log.HandleException(new AException("Error while getting index of player in teamswap queue.", e)); } Log.Debug(() => "Exiting getCPlayerInfo", 7); return -1; } private void QueueRecordForProcessing(ARecord record) { Log.Debug(() => "Entering queueRecordForProcessing", 7); try { if (record.command_action == null) { if (record.command_type == null) { record.record_exception = Log.HandleException(new AException("Attempted to create a record with no command. " + ((String.IsNullOrEmpty(record.source_name)) ? ("NOSOURCE") : (record.source_name)) + "|" + ((String.IsNullOrEmpty(record.record_message)) ? ("NOMESSAGE") : (record.record_message)))); FinalizeRecord(record); return; } record.command_action = record.command_type; } if (!record.record_action_executed) { //Check for command lock if (record.target_player != null && record.target_player.IsLocked() && record.target_player.GetLockSource() != record.source_name && (!_UseExperimentalTools || record.source_name != "ProconAdmin")) { SendMessageToSource(record, record.GetTargetNames() + " is command locked by " + record.target_player.GetLockSource() + ". Please wait for unlock [" + FormatTimeString(record.target_player.GetLockRemaining(), 3) + "]."); FinalizeRecord(record); return; } //Power level exclusion if (record.source_player != null && record.target_player != null && record.source_player.player_role.role_powerLevel < record.target_player.player_role.role_powerLevel && (record.command_type.command_key == "player_kill" || record.command_type.command_key == "player_kill_force" || record.command_type.command_key == "player_kick" || record.command_type.command_key == "player_ban_temp" || record.command_type.command_key == "player_ban_perm" || record.command_type.command_key == "player_ban_perm_future" || record.command_type.command_key == "player_punish" || record.command_type.command_key == "player_forgive" || record.command_type.command_key == "player_mute" || record.command_type.command_key == "player_move" || record.command_type.command_key == "player_fmove" || record.command_type.command_key == "self_lead" || record.command_type.command_key == "player_pull" || record.command_type.command_key == "player_lock")) { SendMessageToSource(record, "You cannot issue " + record.command_type.command_name + " on " + record.target_player.GetVerboseName() + " their power level (" + record.target_player.player_role.role_powerLevel + ") is higher than yours (" + record.source_player.player_role.role_powerLevel + ")"); FinalizeRecord(record); return; } if (record.target_player != null && _CommandTargetWhitelistCommands.Contains(record.command_type.command_text) && GetMatchingVerboseASPlayersOfGroup("whitelist_commandtarget", record.target_player).Any()) { SendMessageToSource(record, record.command_type.command_name + " cannot be issued on " + record.target_player.GetVerboseName()); FinalizeRecord(record); return; } //Command timeouts if (record.command_action != null && _commandTimeoutDictionary.ContainsKey(record.command_action.command_key) && !record.record_action_executed) { if (record.target_player != null && !record.TargetPlayersLocal.Any()) { //Cancel call if record is on timeout for single player if (record.target_player.TargetedRecords.Any(aRecord => aRecord.command_action.command_key == record.command_action.command_key && aRecord.record_time.AddSeconds(Math.Abs(_commandTimeoutDictionary[record.command_action.command_key](this))) > UtcNow())) { SendMessageToSource(record, record.command_type.command_name + " on timeout for " + record.GetTargetNames()); FinalizeRecord(record); return; } } else if (record.TargetPlayersLocal.Any()) { //Cancel call if record is on timeout for any targeted players foreach (APlayer aPlayer in record.TargetPlayersLocal) { if (aPlayer.TargetedRecords.Any(aRecord => aRecord.command_action.command_key == record.command_action.command_key && aRecord.record_time.AddSeconds(Math.Abs(_commandTimeoutDictionary[record.command_action.command_key](this))) > UtcNow())) { SendMessageToSource(record, record.command_type.command_name + " on timeout for " + aPlayer.GetVerboseName()); FinalizeRecord(record); return; } } } } if (record.target_player != null && (record.command_type.command_key == "player_report" || record.command_type.command_key == "player_calladmin") && record.target_player.TargetedRecords.Any(targetedRecord => (targetedRecord.command_action.command_key == "player_kill" || targetedRecord.command_action.command_key == "player_kill_lowpop" || targetedRecord.command_action.command_key == "player_kill_repeat" || targetedRecord.command_action.command_key == "player_kill_force" || targetedRecord.command_action.command_key == "player_kick" || targetedRecord.command_action.command_key == "player_ban_temp" || targetedRecord.command_action.command_key == "player_ban_perm" || targetedRecord.command_action.command_key == "player_ban_perm_future" || targetedRecord.command_action.command_key == "player_punish" || targetedRecord.command_action.command_key == "player_mute" || targetedRecord.command_action.command_key == "player_say" || targetedRecord.command_action.command_key == "player_yell" || targetedRecord.command_action.command_key == "player_tell") && (UtcNow() - targetedRecord.record_time).TotalSeconds < 60)) { OnlineAdminSayMessage("Report on " + record.GetTargetNames() + " blocked. Player already acted on."); SendMessageToSource(record, "Report on " + record.GetTargetNames() + " blocked. Player already acted on."); FinalizeRecord(record); return; } //Special command case Log.Debug(() => "Preparing to check " + record.command_type.command_key + " record for pre-upload processing.", 5); switch (record.command_type.command_key) { case "self_rules": { if (record.source_name != record.target_name && record.target_player != null && record.source_player != null && !PlayerIsAdmin(record.source_player)) { if (PlayerIsAdmin(record.target_player)) { SendMessageToSource(record, record.GetTargetNames() + " is an admin, they already know the rules."); FinalizeRecord(record); return; } if (record.target_player.player_reputation > _reputationThresholdGood) { SendMessageToSource(record, record.GetTargetNames() + " is reputable, they know the rules."); FinalizeRecord(record); return; } } } break; case "player_forgive": { if (record.target_player != null && FetchPoints(record.target_player, _CombineServerPunishments, true) <= 0) { SendMessageToSource(record, record.GetTargetNames() + " does not have any infractions to forgive."); FinalizeRecord(record); return; } if (record.source_name == record.target_name && record.source_name != _debugSoldierName) { SendMessageToSource(record, "You may not issue forgives against yourself, contant another administrator."); FinalizeRecord(record); return; } } break; case "player_report": case "player_calladmin": { if (record.target_player != null && !record.target_player.player_online && record.target_player.TargetedRecords.Any(aRecord => (aRecord.command_action.command_key == "player_kick" || aRecord.command_action.command_key == "player_ban_temp" || aRecord.command_action.command_key == "player_ban_perm") && (UtcNow() - aRecord.record_time).TotalSeconds < 300)) { SendMessageToSource(record, record.GetTargetNames() + " has already been removed by an admin."); FinalizeRecord(record); return; } if (record.target_player != null && GetMatchingVerboseASPlayersOfGroup("whitelist_report", record.target_player).Any()) { SendMessageToSource(record, record.GetTargetNames() + " is whitelisted from reports. Please contact an admin directly if this is urgent."); FinalizeRecord(record); return; } if (record.source_player != null && GetMatchingVerboseASPlayersOfGroup("blacklist_report", record.source_player).Any()) { SendMessageToSource(record, "You may not report players at this time."); FinalizeRecord(record); return; } if (record.source_name == record.target_name) { SendMessageToSource(record, "You may not report yourself."); FinalizeRecord(record); return; } if (EventActive()) { SendMessageToSource(record, "REPORTING IS DISABLED DURING EVENTS."); FinalizeRecord(record); return; } string lowerM = " " + record.record_message.ToLower() + " "; if (_UseExperimentalTools) { if (lowerM.Contains("headgl") || lowerM.Contains("head gl")) { SendMessageToSource(record, "'Head Glitching' related actions are not bannable."); FinalizeRecord(record); return; } //Block reports for false reports if (lowerM.Contains(" false r")) { SendMessageToSource(record, "Do not report for false reports, use !contest."); FinalizeRecord(record); return; } } //Block reports for ping if the ping enforcer is enabled if ((lowerM.Contains(" ping") || lowerM.Contains(" pings")) && _pingEnforcerEnable) { SendMessageToSource(record, "Automatic system handles ping, do not report for it."); FinalizeRecord(record); return; } //Block report wars if (record.target_player != null && record.target_player.TargetedRecords.Count(aRecord => aRecord.source_name == record.source_name && (aRecord.command_type.command_key == "player_report" || aRecord.command_type.command_key == "player_calladmin") && NowDuration(aRecord.record_time).TotalMinutes < 5 && aRecord.command_action.command_key != "player_report_confirm") >= 1 && record.source_player != null && record.source_player.TargetedRecords.Count(aRecord => aRecord.source_name == record.target_name && (aRecord.command_type.command_key == "player_report" || aRecord.command_type.command_key == "player_calladmin") && NowDuration(aRecord.record_time).TotalMinutes < 5 && aRecord.command_action.command_key != "player_report_confirm") >= 1) { SendMessageToSource(record, "Do not have report wars. If this is urgent please contact an admin in teamspeak; " + GetChatCommandByKey("self_voip") + " for the address."); QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_log"), command_numeric = 0, target_name = record.source_name, target_player = record.source_player, source_name = "AutoAdmin", record_message = "Report war blocked bwetween " + record.GetSourceName() + " and " + record.GetTargetNames(), record_time = UtcNow() }); FinalizeRecord(record); return; } //Block multiple reports of the same player from one source if (record.target_player != null && record.target_player.TargetedRecords.Count(aRecord => aRecord.source_name == record.source_name && (aRecord.command_type.command_key == "player_report" || aRecord.command_type.command_key == "player_calladmin") && NowDuration(aRecord.record_time).TotalMinutes < 5 && aRecord.command_action.command_key != "player_report_confirm") >= 2) { SendMessageToSource(record, "You already reported " + record.target_player.GetVerboseName() + ". If this is urgent please contact an admin in teamspeak; @ts for the address."); QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_log"), command_numeric = 0, target_name = record.source_name, target_player = record.source_player, source_name = "AutoAdmin", record_message = "Report spam blocked on " + record.GetTargetNames(), record_time = UtcNow() }); FinalizeRecord(record); return; } //Block multiple reports of the same player from multiple sources if (record.target_player != null && record.target_player.TargetedRecords.Count(aRecord => (aRecord.command_type.command_key == "player_report" || aRecord.command_type.command_key == "player_calladmin") && NowDuration(aRecord.record_time).TotalMinutes < 5 && aRecord.command_action.command_key != "player_report_confirm") >= 3) { SendMessageToSource(record, record.target_player.GetVerboseName() + " has already been reported. If this is urgent please contact an admin in teamspeak; @ts for the address."); QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_log"), command_numeric = 0, target_name = record.source_name, target_player = record.source_player, source_name = "AutoAdmin", record_message = "Report spam blocked on " + record.GetTargetNames(), record_time = UtcNow() }); FinalizeRecord(record); return; } } break; case "player_pm_send": { if (record.target_player != null && record.source_player != null) { if (record.target_player.player_guid == record.source_player.player_guid) { SendMessageToSource(record, "foreveralone.jpg (You can't start a conversation with yourself)"); } } else { SendMessageToSource(record, "Invalid players when trying to start conversation."); FinalizeRecord(record); return; } } break; case "player_lock": { //Check if already locked if (record.target_player != null && record.target_player.IsLocked()) { SendMessageToSource(record, record.GetTargetNames() + " is already locked by " + record.target_player.GetLockSource() + " for " + FormatTimeString(record.target_player.GetLockRemaining(), 3) + "."); FinalizeRecord(record); return; } } break; case "player_unlock": { //Check if already locked if (record.target_player != null && record.target_player.IsLocked() && record.target_player.GetLockSource() != record.source_name) { SendMessageToSource(record, record.GetTargetNames() + " is locked by " + record.target_player.GetLockSource() + ", either they can unlock them, or after " + FormatTimeString(record.target_player.GetLockRemaining(), 3) + " the player will be automatically unlocked."); FinalizeRecord(record); return; } } break; case "self_surrender": case "self_votenext": { if (EventActive()) { SendMessageToSource(record, "Surrender Vote is not available during events."); FinalizeRecord(record); return; } if (!_surrenderVoteEnable) { SendMessageToSource(record, "Surrender Vote must be enabled in AdKats settings to use this command."); FinalizeRecord(record); return; } if (_roundState != RoundState.Playing) { SendMessageToSource(record, "Round state must be playing to use surrender. Current: " + _roundState); FinalizeRecord(record); return; } if (record.source_player != null && record.source_player.player_type == PlayerType.Spectator && !PlayerIsAdmin(record.source_player)) { SendMessageToSource(record, "You cannot use " + GetChatCommandByKey("self_surrender") + " or " + GetChatCommandByKey("self_votenext") + " as a spectator."); FinalizeRecord(record); return; } if (_surrenderVoteSucceeded) { SendMessageToSource(record, "Surrender already succeeded."); FinalizeRecord(record); return; } if (_surrenderVoteList.Contains(record.source_name)) { SendMessageToSource(record, "You already voted! You can cancel your vote with " + GetChatCommandByKey("command_cancel")); FinalizeRecord(record); return; } if (!_surrenderVoteActive) { Int32 playerCount = GetPlayerCount(); if (playerCount < _surrenderVoteMinimumPlayerCount) { SendMessageToSource(record, _surrenderVoteMinimumPlayerCount + " players needed to start surrender vote. Current: " + playerCount); FinalizeRecord(record); return; } ATeam team1, team2; if (!GetTeamByID(1, out team1)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } return; } if (!GetTeamByID(2, out team2)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } return; } Int32 ticketGap = Math.Abs(team1.TeamTicketCount - team2.TeamTicketCount); if (ticketGap < _surrenderVoteMinimumTicketGap) { SendMessageToSource(record, _surrenderVoteMinimumTicketGap + " ticket gap needed to start Surrender Vote. Current: " + ticketGap); FinalizeRecord(record); return; } Double ticketRateGap = Math.Abs(team1.GetTicketDifferenceRate() - team2.GetTicketDifferenceRate()); if (_surrenderVoteTicketRateGapEnable && ticketRateGap < _surrenderVoteMinimumTicketRateGap) { SendMessageToSource(record, _surrenderVoteMinimumTicketRateGap + " ticket rate gap needed to start Surrender Vote. Current: " + Math.Round(ticketRateGap, 2)); FinalizeRecord(record); return; } } //Replace type if needed ACommand surrenderCommand = GetCommandByKey("self_surrender"); ACommand votenextCommand = GetCommandByKey("self_votenext"); if (record.source_player == null) { //Record is external, votenext must me used if (record.command_type.command_key == surrenderCommand.command_key) { record.command_type = votenextCommand; record.command_action = votenextCommand; record.record_message = "Player Voted for Next Round"; } } else if (PlayerIsWinning(record.source_player)) { //Player is winning, votenext must me used if (record.command_type.command_key == surrenderCommand.command_key) { record.command_type = votenextCommand; record.command_action = votenextCommand; record.record_message = "Player Voted for Next Round"; } } else { //Player is losing, surrender must me used if (record.command_type.command_key == votenextCommand.command_key) { record.command_type = surrenderCommand; record.command_action = surrenderCommand; record.record_message = "Player Voted for Surrender"; } } } break; case "self_nosurrender": { if (EventActive()) { SendMessageToSource(record, "Surrender Vote is not available during events."); FinalizeRecord(record); return; } if (!_surrenderVoteEnable) { SendMessageToSource(record, "Surrender Vote must be enabled in AdKats settings to use this command."); FinalizeRecord(record); return; } if (_roundState != RoundState.Playing) { SendMessageToSource(record, "Round state must be playing to vote against surrender. Current: " + _roundState); FinalizeRecord(record); return; } if (_surrenderVoteSucceeded) { SendMessageToSource(record, "Surrender already succeeded."); FinalizeRecord(record); return; } if (_nosurrenderVoteList.Contains(record.source_name)) { SendMessageToSource(record, "You already voted against surrender!"); FinalizeRecord(record); return; } if (!_surrenderVoteActive) { SendMessageToSource(record, "A surrender vote must be active to vote against it."); FinalizeRecord(record); return; } if (record.source_player != null && PlayerIsWinning(record.source_player)) { AdminSayMessage("You cannot use " + GetChatCommandByKey("self_nosurrender") + " from the winning team."); FinalizeRecord(record); return; } if (record.source_player != null && record.source_player.player_type == PlayerType.Spectator && !PlayerIsAdmin(record.source_player)) { SendMessageToSource(record, "You cannot use " + GetChatCommandByKey("self_nosurrender") + " as a spectator."); FinalizeRecord(record); return; } } break; case "player_join": if (record.target_name == record.source_name) { SendMessageToSource(record, "You are already in squad with yourself."); FinalizeRecord(record); return; } if (record.target_player != null && record.source_player != null && record.target_player.fbpInfo.TeamID == record.source_player.fbpInfo.TeamID && record.target_player.fbpInfo.SquadID == record.source_player.fbpInfo.SquadID) { SendMessageToSource(record, "You are already in squad with " + record.target_player.GetVerboseName() + "."); FinalizeRecord(record); return; } break; case "self_battlecry": case "player_battlecry": if (String.IsNullOrEmpty(record.record_message) && String.IsNullOrEmpty(record.target_player.player_battlecry)) { SendMessageToSource(record, "You do not have a battlecry to remove."); FinalizeRecord(record); return; } break; case "player_whitelistreport_remove": if (!GetMatchingASPlayersOfGroup("whitelist_report", record.target_player).Any()) { SendMessageToSource(record, "Matching player not in the Report whitelist."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_whitelistspambot_remove": if (!GetMatchingASPlayersOfGroup("whitelist_spambot", record.target_player).Any()) { SendMessageToSource(record, "Matching player not in the SpamBot whitelist."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_whitelistaa_remove": if (!GetMatchingASPlayersOfGroup("whitelist_adminassistant", record.target_player).Any()) { SendMessageToSource(record, "Matching player not in the Admin Assistant whitelist."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_whitelistping_remove": if (!GetMatchingASPlayersOfGroup("whitelist_ping", record.target_player).Any()) { SendMessageToSource(record, "Matching player not in the Ping whitelist."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_whitelistanticheat_remove": if (!GetMatchingASPlayersOfGroup("whitelist_anticheat", record.target_player).Any()) { SendMessageToSource(record, "Matching player not in the AntiCheat whitelist."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_slotspectator_remove": if (!GetMatchingASPlayersOfGroup("slot_spectator", record.target_player).Any()) { SendMessageToSource(record, "Matching player not in spectator slot list."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_slotreserved_remove": if (!GetMatchingASPlayersOfGroup("slot_reserved", record.target_player).Any()) { SendMessageToSource(record, "Matching player not in reserved slot list."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_whitelistbalance_remove": if (!GetMatchingASPlayersOfGroup("whitelist_multibalancer", record.target_player).Any()) { SendMessageToSource(record, "Matching player not in the autobalance whitelist."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_blacklistdisperse_remove": if (!GetMatchingASPlayersOfGroup("blacklist_dispersion", record.target_player).Any()) { SendMessageToSource(record, "Matching player not under autobalance dispersion."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_whitelistpopulator_remove": if (!GetMatchingASPlayersOfGroup("whitelist_populator", record.target_player).Any()) { SendMessageToSource(record, "Matching player not under populator whitelist."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_whitelistteamkill_remove": if (!GetMatchingASPlayersOfGroup("whitelist_teamkill", record.target_player).Any()) { SendMessageToSource(record, "Matching player not under TeamKillTracker whitelist."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_blacklistspectator_remove": if (!GetMatchingASPlayersOfGroup("blacklist_spectator", record.target_player).Any()) { SendMessageToSource(record, "Matching player not in the spectator blacklist."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_blacklistreport_remove": if (!GetMatchingASPlayersOfGroup("blacklist_report", record.target_player).Any()) { SendMessageToSource(record, "Matching player not in the report source blacklist."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_whitelistcommand_remove": if (!GetMatchingASPlayersOfGroup("whitelist_commandtarget", record.target_player).Any()) { SendMessageToSource(record, "Matching player not in the command target whitelist."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_blacklistautoassist_remove": if (!GetMatchingASPlayersOfGroup("blacklist_autoassist", record.target_player).Any()) { SendMessageToSource(record, "Matching player not in the auto-assist blacklist."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_blacklistallcaps_remove": if (!GetMatchingASPlayersOfGroup("blacklist_allcaps", record.target_player).Any()) { SendMessageToSource(record, "Matching player not under all-caps chat blacklist."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_challenge_play_remove": if (!GetMatchingASPlayersOfGroup("challenge_play", record.target_player).Any()) { SendMessageToSource(record, "Matching player not in challenge playing status."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_challenge_ignore_remove": if (!GetMatchingASPlayersOfGroup("challenge_ignore", record.target_player).Any()) { SendMessageToSource(record, "Matching player not in challenge ignoring status."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; case "player_challenge_autokill_remove": if (!GetMatchingASPlayersOfGroup("challenge_autokill", record.target_player).Any()) { SendMessageToSource(record, "Matching player not in challenge autokill status."); FinalizeRecord(record); return; } Log.Debug(() => record.command_type.command_key + " record allowed to continue processing.", 5); break; } if (record.target_player != null) { //Add the record to the player's targeted records record.target_player.TargetedRecords.Add(record); } } if (_pluginVersionStatus == VersionStatus.OutdatedBuild && !record.record_action_executed && (record.source_player == null || PlayerIsAdmin(record.source_player))) { if (_pluginUpdatePatched) { SendMessageToSource(record, "AdKats has been updated to version " + _latestPluginVersion + "! Reboot PRoCon to activate this patch."); } else { SendMessageToSource(record, "You are running an outdated version of AdKats. Update " + _latestPluginVersion + " is released."); } } if (record.SourceSession == 0 && record.source_player != null) { record.SourceSession = record.source_player.ActiveSession; } if (record.TargetSession == 0 && record.target_player != null) { record.TargetSession = record.target_player.ActiveSession; } Log.Debug(() => "Preparing to queue " + record.command_type.command_key + " record for processing", 6); //Set the record update time record.record_time_update = UtcNow(); lock (_UnprocessedRecordQueue) { //Queue the record for processing _UnprocessedRecordQueue.Enqueue(record); Log.Debug(() => "Record queued for processing", 6); _DbCommunicationWaitHandle.Set(); } } catch (Exception e) { record.record_exception = new AException("Error while queueing record for processing.", e); Log.HandleException(record.record_exception); } Log.Debug(() => "Exiting queueRecordForProcessing", 7); } private void QueueStatisticForProcessing(AStatistic aStat) { Log.Debug(() => "Entering QueueStatisticForProcessing", 6); try { Log.Debug(() => "Preparing to queue statistic for processing", 6); lock (_UnprocessedStatisticQueue) { //Queue the statistic for processing _UnprocessedStatisticQueue.Enqueue(aStat); Log.Debug(() => "Statistic queued for processing", 6); _DbCommunicationWaitHandle.Set(); } } catch (Exception e) { Log.HandleException(new AException("Error while queuing statistic for processing.", e)); } Log.Debug(() => "Exiting QueueStatisticForProcessing", 6); } private void CommandParsingThreadLoop() { try { Log.Debug(() => "Starting Command Parsing Thread", 1); Thread.CurrentThread.Name = "Command"; DateTime loopStart = UtcNow(); while (true) { try { Log.Debug(() => "Entering Command Parsing Thread Loop", 7); if (!_pluginEnabled) { Log.Debug(() => "Detected AdKats not enabled. Exiting thread " + Thread.CurrentThread.Name, 6); break; } //Sleep for 10ms Threading.Wait(10); //Get all unparsed inbound messages if (_UnparsedCommandQueue.Count > 0) { Log.Debug(() => "Preparing to lock command queue to retrive new commands", 7); Queue unparsedCommands; lock (_UnparsedCommandQueue) { Log.Debug(() => "Inbound commands found. Grabbing.", 6); //Grab all messages in the queue unparsedCommands = new Queue(_UnparsedCommandQueue.ToArray()); //Clear the queue for next run _UnparsedCommandQueue.Clear(); } //Loop through all commands in order that they came in while (unparsedCommands.Count > 0) { if (!_pluginEnabled) { break; } Log.Debug(() => "begin reading command", 6); //Dequeue the first/next command AChatMessage commandMessage = unparsedCommands.Dequeue(); ARecord record; if (commandMessage.Speaker == "Server") { record = new ARecord { record_source = ARecord.Sources.ServerCommand, record_access = ARecord.AccessMethod.HiddenExternal, source_name = "ProconAdmin", record_time = commandMessage.Timestamp }; } else { record = new ARecord { record_source = ARecord.Sources.InGame, source_name = commandMessage.Speaker, record_time = commandMessage.Timestamp }; //Assign access method if (commandMessage.Hidden) { if (commandMessage.Subset == AChatMessage.ChatSubset.Global) { record.record_access = ARecord.AccessMethod.HiddenGlobal; } else if (commandMessage.Subset == AChatMessage.ChatSubset.Team) { record.record_access = ARecord.AccessMethod.HiddenTeam; } else if (commandMessage.Subset == AChatMessage.ChatSubset.Squad) { record.record_access = ARecord.AccessMethod.HiddenSquad; } } else { if (commandMessage.Subset == AChatMessage.ChatSubset.Global) { record.record_access = ARecord.AccessMethod.PublicGlobal; } else if (commandMessage.Subset == AChatMessage.ChatSubset.Team) { record.record_access = ARecord.AccessMethod.PublicTeam; } else if (commandMessage.Subset == AChatMessage.ChatSubset.Squad) { record.record_access = ARecord.AccessMethod.PublicSquad; } } } //Complete the record creation CompleteRecordInformation(record, commandMessage); } } else { Log.Debug(() => "No inbound commands, ready.", 7); //No commands to parse, ready. if ((UtcNow() - loopStart).TotalMilliseconds > 1000) { Log.Debug(() => "Warning. " + Thread.CurrentThread.Name + " thread processing completed in " + ((int)((UtcNow() - loopStart).TotalMilliseconds)) + "ms", 4); } _CommandParsingWaitHandle.Reset(); _CommandParsingWaitHandle.WaitOne(TimeSpan.FromSeconds(5)); loopStart = UtcNow(); } } catch (Exception e) { if (e is ThreadAbortException) { Log.HandleException(new AException("Command thread aborted. Exiting.")); break; } Log.HandleException(new AException("Error occured in Command thread. Skipping current loop.", e)); } } Log.Debug(() => "Ending Command Thread", 1); Threading.StopWatchdog(); } catch (Exception e) { Log.HandleException(new AException("Error occured in command parsing thread.", e)); } } //Before calling this, the record is initialized, and command_source/source_name are filled public void CompleteRecordInformation(ARecord record, AChatMessage message) { try { //Initial split of command by whitespace String[] splitMessage = message.Message.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (splitMessage.Length < 1) { Log.Debug(() => "Completely blank command entered", 5); SendMessageToSource(record, "You entered a completely blank command."); FinalizeRecord(record); return; } String commandString = splitMessage[0].ToLower(); Log.Debug(() => "Raw " + commandString, 6); String remainingMessage = message.Message.TrimStart(splitMessage[0].ToCharArray()).Trim(); record.server_id = _serverInfo.ServerID; record.record_time = UtcNow(); // Modify the command message if they are voting in a poll Int32 resultVote; if (_ActivePoll != null && Int32.TryParse(commandString, out resultVote)) { // They entered a format consistent with the xVoteMap voting method. !2, /2, etc // Reformat the text so AdKats understands it as the vote command commandString = GetCommandByKey("poll_vote").command_text; // Set the parameter for the vote command to the number they entered remainingMessage = resultVote.ToString(); } //GATE 1: Add Command ACommand commandType = null; if (_CommandTextDictionary.TryGetValue(commandString, out commandType) && commandType.command_active == ACommand.CommandActive.Active) { record.command_type = commandType; record.command_action = commandType; Log.Debug(() => "Command parsed. Command is " + commandType.command_key + ".", 5); } else { //If command not parsable, return without creating Log.Debug(() => "Command not parsable", 6); if (record.record_source == ARecord.Sources.ExternalPlugin) { SendMessageToSource(record, "Command not parsable."); } FinalizeRecord(record); return; } //GATE 2: Check Access Rights if (record.record_source == ARecord.Sources.ServerCommand && !_AllowAdminSayCommands) { SendMessageToSource(record, "Access to commands using that method has been disabled in AdKats settings."); FinalizeRecord(record); return; } if (!_firstPlayerListComplete) { if (!_PlayersRequestingCommands.Contains(record.source_name)) { _PlayersRequestingCommands.Add(record.source_name); } if (_startupDurations.Count() >= 2) { var averageStartupDuration = TimeSpan.FromSeconds(_startupDurations.Average(span => span.TotalSeconds)); var currentStartupDuration = NowDuration(_AdKatsStartTime); if (averageStartupDuration > currentStartupDuration) { //Give estimated remaining time SendMessageToSource(record, "AdKats starting up; ~" + Math.Round(currentStartupDuration.TotalSeconds / averageStartupDuration.TotalSeconds * 100) + "% ready, ~" + FormatTimeString(averageStartupDuration - currentStartupDuration, 3) + " remaining."); } else { //Just say 'shortly' SendMessageToSource(record, "AdKats starting up; Ready shortly."); } } else { if (!_firstUserListComplete) { SendMessageToSource(record, "AdKats starting up; 1/3 ready, " + FormatTimeString(UtcNow() - _AdKatsStartTime, 3) + " elapsed."); } else { SendMessageToSource(record, "AdKats starting up; 2/3 ready, " + FormatTimeString(UtcNow() - _AdKatsStartTime, 3) + " elapsed."); } } FinalizeRecord(record); return; } _PlayersRequestingCommands.Clear(); //Check if player has the right to perform what he's asking, only perform for InGame actions if (record.record_source == ARecord.Sources.InGame) { //Attempt to fetch the source player if (!_PlayerDictionary.TryGetValue(record.source_name, out record.source_player)) { Log.Error("Source player not found in server for in-game " + record.command_type.command_key + " command, unable to complete command."); FinalizeRecord(record); return; } if (!HasAccess(record.source_player, record.command_type)) { Log.Debug(() => "No rights to call command", 6); //Only tell the user they dont have access if the command is active if (record.command_type.command_active == ACommand.CommandActive.Active) { if (record.command_type.command_playerInteraction && !PlayerIsAdmin(record.source_player) && !message.Hidden && message.Subset != AChatMessage.ChatSubset.Squad) { AdminSayMessage(record.source_player.GetVerboseName() + " is not an admin, they cannot use " + record.command_type.command_name + "."); } var powerLevel = ""; if (PlayerIsAdmin(record.source_player)) { powerLevel = " (Power Level " + record.source_player.player_role.role_powerLevel + ")"; } SendMessageToSource(record, "Your role " + record.source_player.player_role.role_name + powerLevel + " cannot use " + record.command_type.command_name + "."); } FinalizeRecord(record); return; } if (_DiscordPlayerMonitorEnable && _DiscordPlayerMonitorView && _DiscordPlayerRequireVoiceForAdmin && NowDuration(_DiscordManager.LastUpdate).TotalMinutes < 2.5 && record.command_type.command_playerInteraction && record.source_player.DiscordObject == null) { SendMessageToSource(record, "Admin commands may only be issued while in discord."); FinalizeRecord(record); return; } } //GATE 3: Command access method if (record.record_source == ARecord.Sources.InGame) { switch (record.command_type.command_access) { case ACommand.CommandAccess.AnyHidden: //Require source to be any hidden if (record.record_access != ARecord.AccessMethod.HiddenExternal && record.record_access != ARecord.AccessMethod.HiddenGlobal && record.record_access != ARecord.AccessMethod.HiddenTeam && record.record_access != ARecord.AccessMethod.HiddenSquad) { SendMessageToSource(record, "Use /" + record.command_type.command_text + " to access the " + record.command_type.command_name + " command."); FinalizeRecord(record); return; } break; case ACommand.CommandAccess.AnyVisible: //Require source to be any visible if (record.record_access != ARecord.AccessMethod.PublicExternal && record.record_access != ARecord.AccessMethod.PublicGlobal && record.record_access != ARecord.AccessMethod.PublicTeam && record.record_access != ARecord.AccessMethod.PublicSquad) { SendMessageToSource(record, "Use !" + record.command_type.command_text + ", !" + record.command_type.command_text + ", or ." + record.command_type.command_text + " to access the " + record.command_type.command_name + " command."); FinalizeRecord(record); return; } break; case ACommand.CommandAccess.GlobalVisible: //Require source to be global visible if (record.record_access != ARecord.AccessMethod.PublicGlobal) { SendMessageToSource(record, "Use !" + record.command_type.command_text + ", !" + record.command_type.command_text + ", or ." + record.command_type.command_text + " in GLOBAL chat to access the " + record.command_type.command_name + " command."); FinalizeRecord(record); return; } break; case ACommand.CommandAccess.TeamVisible: //Require source to be global visible if (record.record_access != ARecord.AccessMethod.PublicTeam) { SendMessageToSource(record, "Use !" + record.command_type.command_text + ", !" + record.command_type.command_text + ", or ." + record.command_type.command_text + " in TEAM chat to access the " + record.command_type.command_name + " command."); FinalizeRecord(record); return; } break; case ACommand.CommandAccess.SquadVisible: //Require source to be global visible if (record.record_access != ARecord.AccessMethod.PublicSquad) { SendMessageToSource(record, "Use !" + record.command_type.command_text + ", !" + record.command_type.command_text + ", or ." + record.command_type.command_text + " in SQUAD chat to access the " + record.command_type.command_name + " command."); FinalizeRecord(record); return; } break; } } Log.Debug(() => "Access type " + record.record_access + " is allowed for " + record.command_type.command_key + ".", 4); //GATE 4: Specific data based on command type. switch (record.command_type.command_key) { case "player_move": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); break; } record.record_message = "Self-Inflicted"; record.target_name = record.source_name; CompleteTargetInformation(record, true, false, false); break; case 1: record.record_message = "MovePlayer"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, false, false); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_fmove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Self-Inflicted"; record.target_name = record.source_name; CompleteTargetInformation(record, true, false, false); break; case 1: record.record_message = "ForceMovePlayer"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, false, false); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "self_teamswap": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } //May only call this command from in-game if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "TeamSwap"; record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); } break; case "self_assist": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } //May only call this command from in-game if (record.record_source != ARecord.Sources.InGame || record.source_player == null) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } //Cannot call this command when game not active if (_roundState != RoundState.Playing) { SendMessageToSource(record, Log.CViolet("You can't use assist unless a round is active.")); FinalizeRecord(record); return; } //Player Info Check record.record_message = "Assist Losing Team"; record.target_name = record.source_name; if (!_PlayerDictionary.TryGetValue(record.target_name, out record.target_player)) { SendMessageToSource(record, "Player information not found. Unable to process command."); FinalizeRecord(record); return; } //May only call this command from in-game if (record.source_player.player_type != PlayerType.Player) { SendMessageToSource(record, Log.CViolet("You must be a player to use assist.")); FinalizeRecord(record); return; } var assists = _roundAssists.Values; if (assists.Any()) { //Timeout or over-queueing Double secondTimeout = 20; Double timeout = (secondTimeout - (UtcNow() - assists.Max(aRecord => aRecord.record_time_update)).TotalSeconds); if (timeout > 0) { SendMessageToSource(record, Log.CViolet("Assist recently used. Please wait " + Math.Ceiling(timeout) + " seconds before using it. Thank you.")); FinalizeRecord(record); return; } } if (_AssistAttemptQueue.Any()) { lock (_AssistAttemptQueue) { var aRecord = _AssistAttemptQueue.FirstOrDefault(assistRecord => assistRecord.target_player.player_id == record.target_player.player_id); if (aRecord != null) { // Refresh the creation time since they manually requested it again aRecord.record_creationTime = UtcNow(); SendMessageToSource(record, Log.CViolet("You are already queued for automatic assist. You will be moved if possible.")); FinalizeRecord(record); return; } } } if (RunAssist(record.target_player, record, null, false)) { QueueRecordForProcessing(record); } } break; case "player_debugassist": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Debug Assist Self"; record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); break; case 1: record.record_message = "Debug Assist Player"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, false, false); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "self_kill": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } //May only call this command from in-game if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Self-Inflicted"; record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); } break; case "player_kill": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Self-Inflicted"; record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); break; case 1: record.target_name = parameters[0]; //Handle based on report ID as only option if (!HandlePlayerReport(record)) { SendMessageToSource(record, "No reason given, unable to submit."); } FinalizeRecord(record); return; case 2: record.target_name = parameters[0]; //attempt to handle via pre-message ID record.record_message = GetPreMessage(parameters[1], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } //Handle based on report ID if possible if (!HandlePlayerReport(record)) { if (record.record_message.Length >= _RequiredReasonLength) { CompleteTargetInformation(record, false, false, true); } else { SendMessageToSource(record, "Reason too short, unable to submit."); FinalizeRecord(record); } } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_kill_force": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Self-Inflicted"; record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); break; case 1: record.target_name = parameters[0]; //Handle based on report ID as only option if (!HandlePlayerReport(record)) { SendMessageToSource(record, "No reason given, unable to submit."); } FinalizeRecord(record); return; case 2: record.target_name = parameters[0]; //attempt to handle via pre-message ID record.record_message = GetPreMessage(parameters[1], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } //Handle based on report ID if possible if (!HandlePlayerReport(record)) { if (record.record_message.Length >= _RequiredReasonLength) { CompleteTargetInformation(record, false, false, true); } else { SendMessageToSource(record, "Reason too short, unable to submit."); FinalizeRecord(record); } } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_warn": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Warning Yourself"; record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); break; case 1: record.target_name = parameters[0]; //Handle based on report ID as only option if (!HandlePlayerReport(record)) { SendMessageToSource(record, "No reason given, unable to submit."); } FinalizeRecord(record); return; case 2: record.target_name = parameters[0]; //attempt to handle via pre-message ID record.record_message = GetPreMessage(parameters[1], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } //Handle based on report ID if possible if (!HandlePlayerReport(record)) { if (record.record_message.Length >= _RequiredReasonLength) { CompleteTargetInformation(record, false, false, true); } else { SendMessageToSource(record, "Reason too short, unable to submit."); FinalizeRecord(record); } } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_kick": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Self-Inflicted"; record.target_name = record.source_name; CompleteTargetInformation(record, true, false, false); break; case 1: record.target_name = parameters[0]; //Handle based on report ID as only option if (!HandlePlayerReport(record)) { SendMessageToSource(record, "No reason given, unable to submit."); FinalizeRecord(record); } break; case 2: record.target_name = parameters[0]; //attempt to handle via pre-message ID record.record_message = GetPreMessage(parameters[1], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } //Handle based on report ID if possible if (!HandlePlayerReport(record)) { if (record.record_message.Length >= _RequiredReasonLength) { CompleteTargetInformation(record, false, false, true); } else { SendMessageToSource(record, "Reason too short, unable to submit."); FinalizeRecord(record); } } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_ban_temp": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric > 5259490.0) { SendMessageToSource(record, "You cannot temp ban for longer than 10 years. Issue a permanent ban instead."); FinalizeRecord(record); return; } if (record.command_numeric > _MaxTempBanDuration.TotalMinutes) { SendMessageToSource(record, "You cannot temp ban for longer than " + FormatTimeString(_MaxTempBanDuration, 2) + ". Defaulting to max temp ban time."); record.command_numeric = (int)_MaxTempBanDuration.TotalMinutes; } } switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } //Target is source record.record_message = "Self-Inflicted"; record.target_name = record.source_name; CompleteTargetInformation(record, true, false, false); break; case 2: record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); //Handle based on report ID as only option if (!HandlePlayerReport(record)) { SendMessageToSource(record, "No reason given, unable to submit."); FinalizeRecord(record); } break; case 3: record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); //attempt to handle via pre-message ID record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "reason: " + record.record_message, 6); //Handle based on report ID if possible if (!HandlePlayerReport(record)) { if (record.record_message.Length >= _RequiredReasonLength) { CompleteTargetInformation(record, false, true, true); } else { SendMessageToSource(record, "Reason too short, unable to submit."); FinalizeRecord(record); } } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_ban_perm": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Self-Inflicted"; record.target_name = record.source_name; CompleteTargetInformation(record, true, false, false); break; case 1: record.target_name = parameters[0]; //Handle based on report ID as only option if (!HandlePlayerReport(record)) { SendMessageToSource(record, "No reason given, unable to submit."); FinalizeRecord(record); } break; case 2: record.target_name = parameters[0]; //attempt to handle via pre-message ID record.record_message = GetPreMessage(parameters[1], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } //Handle based on report ID if possible if (!HandlePlayerReport(record)) { if (record.record_message.Length >= _RequiredReasonLength) { CompleteTargetInformation(record, false, true, true); } else { SendMessageToSource(record, "Reason too short, unable to submit."); FinalizeRecord(record); } } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_ban_perm_future": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } if (!_UseBanEnforcer || !_UseBanEnforcerPreviousState) { SendMessageToSource(record, " can only be used when ban enforcer is enabled."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid time given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); } switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); //Target is source record.record_message = "Self-Inflicted"; record.target_name = record.source_name; CompleteTargetInformation(record, true, false, false); break; case 2: record.command_numeric = (int)(recordDuration * durationMultiplier); record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); //Handle based on report ID as only option if (!HandlePlayerReport(record)) { SendMessageToSource(record, "No reason given, unable to submit."); FinalizeRecord(record); } break; case 3: record.command_numeric = (int)(recordDuration * durationMultiplier); record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); //attempt to handle via pre-message ID record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "reason: " + record.record_message, 6); //Handle based on report ID if possible if (!HandlePlayerReport(record)) { if (record.record_message.Length >= _RequiredReasonLength) { CompleteTargetInformation(record, false, true, true); } else { SendMessageToSource(record, "Reason too short, unable to submit."); FinalizeRecord(record); } } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_unban": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } if (!_UseBanEnforcer || !_UseBanEnforcerPreviousState) { SendMessageToSource(record, "The unban command can only be used when ban enforcer is enabled."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); String partialName; List matchingBans; switch (parameters.Length) { case 0: //Unban the last player you've banned SendMessageToSource(record, "Unbanning the last person you banned is not implemented yet."); FinalizeRecord(record); return; case 1: //Unban the target player partialName = parameters[0]; record.record_message = "Admin Unban"; break; case 2: //Unban the target player partialName = parameters[0]; //attempt to handle via pre-message ID record.record_message = GetPreMessage(parameters[1], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } if (String.IsNullOrEmpty(partialName) || partialName.Length < 3) { SendMessageToSource(record, "Name search must be at least 3 characters."); FinalizeRecord(record); return; } SendMessageToSource(record, "Fetching matching bans. Please wait."); matchingBans = FetchMatchingBans(partialName, 4); if (matchingBans.Count == 0) { SendMessageToSource(record, "No players matching '" + partialName + "' have active bans."); FinalizeRecord(record); return; } if (matchingBans.Count <= 3) { foreach (ABan innerBan in matchingBans) { SendMessageToSource(record, innerBan.ban_record.GetTargetNames() + " | " + innerBan.ban_record.record_time.ToShortDateString() + " | " + innerBan.ban_record.record_message); } ABan aBan = matchingBans[0]; record.target_name = aBan.ban_record.target_player.player_name; record.target_player = aBan.ban_record.target_player; ConfirmActionWithSource(record); } else { SendMessageToSource(record, "Too many banned players match your search, try again."); FinalizeRecord(record); } } break; case "player_whitelistanticheat": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } String defaultReason = "AntiCheat Whitelist"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistping": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } if (!_pingEnforcerEnable) { SendMessageToSource(record, "Enable Ping Enforcer to use this command."); FinalizeRecord(record); return; } String defaultReason = "Ping Whitelist"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistaa": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (!_EnableAdminAssistants) { SendMessageToSource(record, "Enable Admin Assistants to use this command."); FinalizeRecord(record); return; } String defaultReason = "Admin Assistant Whitelist"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistreport": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); String defaultReason = "Report Whitelist"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistspambot": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (!_spamBotExcludeAdminsAndWhitelist) { SendMessageToSource(record, "'Exclude Admins and Whitelist from Spam' must be enabled to use this command."); FinalizeRecord(record); return; } String defaultReason = "Spambot Whitelist"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_blacklistspectator": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } String defaultReason = "Spectator Blacklist"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_blacklistreport": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); String defaultReason = "Report Source Blacklist"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistcommand": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); String defaultReason = "Command Target Whitelist"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_blacklistautoassist": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); String defaultReason = "Auto-Assist Blacklist"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistpopulator": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (!_PopulatorMonitor) { SendMessageToSource(record, "'Monitor Populator Players' must be enabled to use this command."); FinalizeRecord(record); return; } String defaultReason = "Populator Whitelist"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistteamkill": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } if (!_FeedTeamKillTrackerWhitelist) { SendMessageToSource(record, "'Feed TeamKillTracker Whitelist' must be enabled to use this command."); FinalizeRecord(record); return; } String defaultReason = "TeamKillTracker Whitelist"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_punish": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } if (!_dbTimingValid) { SendMessageToSource(record, record.command_type.command_name + " cannot be performed when database timing is mismatched."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Self-Inflicted"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, true); break; case 1: record.target_name = parameters[0]; //Handle based on report ID as only option if (!HandlePlayerReport(record)) { SendMessageToSource(record, "No reason given, unable to submit."); FinalizeRecord(record); } break; case 2: record.target_name = parameters[0]; //attempt to handle via pre-message ID record.record_message = GetPreMessage(parameters[1], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } //Handle based on report ID if possible if (!HandlePlayerReport(record)) { if (record.record_message.Length >= _RequiredReasonLength) { CompleteTargetInformation(record, false, true, true); } else { SendMessageToSource(record, "Reason too short, unable to submit."); FinalizeRecord(record); } } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_forgive": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Self-Inflicted"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, true); break; case 1: record.target_name = parameters[0]; //Handle based on report ID as only option if (!HandlePlayerReport(record)) { SendMessageToSource(record, "No reason given, unable to submit."); FinalizeRecord(record); } break; case 2: record.target_name = parameters[0]; //attempt to handle via pre-message ID record.record_message = GetPreMessage(parameters[1], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } //Handle based on report ID if possible if (!HandlePlayerReport(record)) { if (record.record_message.Length >= _RequiredReasonLength) { CompleteTargetInformation(record, false, true, true); } else { SendMessageToSource(record, "Reason too short, unable to submit."); FinalizeRecord(record); } } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_mute": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Self-Inflicted"; record.target_name = record.source_name; CompleteTargetInformation(record, true, false, true); break; case 1: record.target_name = parameters[0]; //Handle based on report ID as only option if (!HandlePlayerReport(record)) { SendMessageToSource(record, "No reason given, unable to submit."); FinalizeRecord(record); } break; case 2: record.target_name = parameters[0]; //attempt to handle via pre-message ID record.record_message = GetPreMessage(parameters[1], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } //Handle based on report ID if possible if (!HandlePlayerReport(record)) { if (record.record_message.Length >= _RequiredReasonLength) { CompleteTargetInformation(record, false, false, true); } else { SendMessageToSource(record, "Reason too short, unable to submit."); FinalizeRecord(record); } } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_join": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } if (record.source_player != null && record.source_player.player_type == PlayerType.Spectator) { SendMessageToSource(record, "You cannot use " + GetChatCommandByKey("player_join") + " as a spectator."); FinalizeRecord(record); return; } if (record.source_player != null && (record.source_player.player_type == PlayerType.CommanderMobile || record.source_player.player_type == PlayerType.CommanderPC)) { SendMessageToSource(record, "You cannot use " + GetChatCommandByKey("player_join") + " as a commander."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: SendMessageToSource(record, "You are already in squad with yourself."); FinalizeRecord(record); return; case 1: record.target_name = parameters[0]; record.record_message = "Joining Player"; if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, false, false); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_pull": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } if (record.source_player != null && record.source_player.player_type == PlayerType.Spectator) { SendMessageToSource(record, "You cannot use " + GetChatCommandByKey("player_pull") + " as a spectator."); FinalizeRecord(record); return; } if (record.source_player != null && (record.source_player.player_type == PlayerType.CommanderMobile || record.source_player.player_type == PlayerType.CommanderPC)) { SendMessageToSource(record, "You cannot use " + GetChatCommandByKey("player_pull") + " as a commander."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: SendMessageToSource(record, "foreveralone.jpg (You cannot pull yourself.)"); FinalizeRecord(record); return; case 1: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use this command from outside the game."); FinalizeRecord(record); return; } record.target_name = parameters[0]; record.record_message = "Pulling Player"; CompleteTargetInformation(record, false, false, false); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_roundwhitelist": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); SendMessageToSource(record, "This command has been permanently disabled. - ColColonCleaner"); FinalizeRecord(record); } break; case "player_report": { //Get the command text for report String command = GetChatCommandByKey("player_report"); //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: SendMessageToSource(record, "Format must be: " + command + " playername reason"); FinalizeRecord(record); return; case 1: SendMessageToSource(record, "Format must be: " + command + " playername reason"); FinalizeRecord(record); return; case 2: record.target_name = parameters[0]; Log.Debug(() => "target: " + record.target_name, 6); //attempt to handle via pre-message ID record.record_message = GetPreMessage(parameters[1], false); Log.Debug(() => "reason: " + record.record_message, 6); //Only 1 character reasons are required for reports and admin calls if (record.record_message.Length >= 1) { CompleteTargetInformation(record, true, false, false); } else { Log.Debug(() => "reason too short", 6); SendMessageToSource(record, "Reason too short, unable to submit."); FinalizeRecord(record); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_calladmin": { //Get the command text for call admin String command = GetChatCommandByKey("player_calladmin"); //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: SendMessageToSource(record, "Format must be: " + command + " playername reason"); FinalizeRecord(record); return; case 1: SendMessageToSource(record, "Format must be: " + command + " playername reason"); FinalizeRecord(record); return; case 2: record.target_name = parameters[0]; Log.Debug(() => "target: " + record.target_name, 6); //attempt to handle via pre-message ID record.record_message = GetPreMessage(parameters[1], false); Log.Debug(() => "reason: " + record.record_message, 6); //Only 1 character reasons are required for reports and admin calls if (record.record_message.Length >= 1) { CompleteTargetInformation(record, false, false, false); } else { Log.Debug(() => "reason too short", 6); SendMessageToSource(record, "Reason too short, unable to submit."); FinalizeRecord(record); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_info": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Fetching Own Info"; record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); break; case 1: record.target_name = parameters[0]; record.record_message = "Fetching Player Info"; CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_perks": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 1: if (record.source_player != null && !PlayerIsAdmin(record.source_player) && record.source_name != _debugSoldierName) { SendMessageToSource(record, "You cannot see another player's perks. Admin only."); FinalizeRecord(record); return; } record.target_name = parameters[0]; record.record_message = "Fetching Player Perks"; CompleteTargetInformation(record, false, true, true); break; default: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Fetching Own Perks"; record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); break; } } break; case "poll_trigger": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_roundState != RoundState.Playing) { SendMessageToSource(record, record.command_type.command_name + " cannot be used between rounds."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); String pollCode; switch (parameters.Length) { case 1: pollCode = parameters[0].ToLower(); if (!_AvailablePolls.Contains(pollCode)) { SendMessageToSource(record, pollCode + " is not an available poll. Available Polls: " + String.Join(", ", _AvailablePolls)); FinalizeRecord(record); return; } record.target_name = pollCode; record.record_message = ""; QueueRecordForProcessing(record); break; case 2: pollCode = parameters[0].ToLower(); if (!_AvailablePolls.Contains(pollCode)) { SendMessageToSource(record, pollCode + " is not an available poll. Available Polls: " + String.Join(", ", _AvailablePolls)); FinalizeRecord(record); return; } record.target_name = pollCode; record.record_message = parameters[1].ToLower(); QueueRecordForProcessing(record); break; default: SendMessageToSource(record, "No poll code provided. Available Polls: " + String.Join(", ", _AvailablePolls)); FinalizeRecord(record); return; } } break; case "poll_vote": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (record.record_source != ARecord.Sources.InGame) { // Do not inform here, simply ignore it FinalizeRecord(record); return; } if (_ActivePoll == null) { SendMessageToSource(record, "There is no active poll to vote on."); FinalizeRecord(record); return; } if (_roundState != RoundState.Playing) { SendMessageToSource(record, record.command_type.command_name + " cannot be used between rounds."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); Int32 voteNumber; switch (parameters.Length) { case 1: var paramString = parameters[0].ToLower(); if (!Int32.TryParse(paramString, out voteNumber)) { SendMessageToSource(record, paramString + " is not a number. Vote options are numbers."); FinalizeRecord(record); return; } if (!_ActivePoll.AddVote(record.source_player, voteNumber)) { FinalizeRecord(record); return; } record.target_name = _ActivePoll.ID; record.record_message = voteNumber.ToString(); QueueRecordForProcessing(record); break; default: SendMessageToSource(record, "No vote option provided."); FinalizeRecord(record); return; } } break; case "poll_cancel": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_ActivePoll == null) { SendMessageToSource(record, "There is no active poll to cancel."); FinalizeRecord(record); return; } if (_roundState != RoundState.Playing) { SendMessageToSource(record, record.command_type.command_name + " cannot be used between rounds."); FinalizeRecord(record); return; } _ActivePoll.Canceled = true; } break; case "poll_complete": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_ActivePoll == null) { SendMessageToSource(record, "There is no active poll to complete"); FinalizeRecord(record); return; } if (_roundState != RoundState.Playing) { SendMessageToSource(record, record.command_type.command_name + " cannot be used between rounds."); FinalizeRecord(record); return; } _ActivePoll.Completed = true; } break; case "player_chat": { /* * This command will get chat history for a player. Comes in 4 variations. * Variable number of seconds between printed lines, based on the number of characters in the message. * Oldest to newest. Default last 5 lines, max 30. Spam protection enabled. * * /pchat - returns your chat history, default length. * /pchat (#) - returns your chat history, custom length. * /pchat (playername) - returns player chat history, default length. * /pchat (#) (playername) - returns player chat history, custom length. * /pchat self (playername) - returns last conversation between you and player, default length. * /pchat (#) self (playername) - returns last conversation between you and player, custom length. * /pchat (playernameA) (playernameB) - returns last conversation between playerA and playerB, default length. * /pchat (#) (playernameA) (playernameB) - returns last conversation between playerA and playerB, custom length. */ //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); Int32 numeric; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); switch (parameters.Length) { case 0: //One case, assign to self if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Fetching own chat history"; record.target_name = record.source_name; record.command_numeric = 5; CompleteTargetInformation(record, false, false, false); break; case 1: //Two cases if (Int32.TryParse(parameters[0], out numeric) && numeric <= 30) { //Case numeric, assign to duration record.record_message = "Fetching own chat history"; record.target_name = record.source_name; record.command_numeric = numeric; CompleteTargetInformation(record, false, false, false); } else { //Case player, assign to target name record.record_message = "Fetching player chat history"; record.target_name = parameters[0]; record.command_numeric = 5; CompleteTargetInformation(record, false, true, true); } break; case 2: //Three cases if (Int32.TryParse(parameters[0], out numeric) && numeric <= 30) { //Case numeric, assign to duration record.record_message = "Fetching player chat history"; record.target_name = parameters[1]; record.command_numeric = numeric; CompleteTargetInformation(record, false, true, true); } else { SendMessageToSource(record, ""); //Two target case, assign both players if (parameters[0].ToLower() == "self") { //Players are self and target record.record_message = "Fetching own conversation history."; record.TargetNamesLocal.Add(record.source_name); record.TargetNamesLocal.Add(parameters[0]); record.command_numeric = 5; CompleteTargetInformation(record, false, true, true); } else { //Players are target 1 and target 2 record.record_message = "Fetching player conversation history."; record.TargetNamesLocal.Add(parameters[0]); record.TargetNamesLocal.Add(parameters[1]); record.command_numeric = 5; CompleteTargetInformation(record, false, true, true); } } break; case 3: //Two cases if (Int32.TryParse(parameters[0], out numeric) && numeric <= 30) { //Two target case, assign both players if (parameters[1].ToLower() == "self") { //Players are self and target record.record_message = "Fetching own conversation history."; record.TargetNamesLocal.Add(record.source_name); record.TargetNamesLocal.Add(parameters[2]); record.command_numeric = numeric; CompleteTargetInformation(record, false, true, true); } else { //Players are target 1 and target 2 record.record_message = "Fetching player conversation history."; record.TargetNamesLocal.Add(parameters[1]); record.TargetNamesLocal.Add(parameters[2]); record.command_numeric = numeric; CompleteTargetInformation(record, false, true, true); } } else { SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_find": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Finding Self"; record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); break; case 1: record.target_name = parameters[0]; record.record_message = "Finding Player"; CompleteTargetInformation(record, false, false, false); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_lock": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: SendMessageToSource(record, "You can't lock yourself..."); FinalizeRecord(record); return; case 1: record.target_name = parameters[0]; record.record_message = "Locking Player"; CompleteTargetInformation(record, false, false, false); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_unlock": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: SendMessageToSource(record, "You can't unlock yourself..."); FinalizeRecord(record); return; case 1: record.target_name = parameters[0]; record.record_message = "Unlocking Player"; CompleteTargetInformation(record, false, false, false); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_mark": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Marking Self"; record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); break; case 1: record.target_name = parameters[0]; record.record_message = "Marking Player"; if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, false, false); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_loadout": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Loadout Fetching Self"; record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); break; case 1: record.target_name = parameters[0]; record.record_message = "Loadout Fetching Player"; if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, false, false); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_ping": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Fetching Own Ping"; record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); break; case 1: record.target_name = parameters[0]; record.record_message = "Fetching Player Ping"; if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, false, false); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_forceping": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Forcing Own Manual Ping"; record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); break; case 1: record.target_name = parameters[0]; record.record_message = "Forcing Player Manual Ping"; if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, false, false); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_loadout_force": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Loadout Forcing Self"; record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); break; case 1: record.target_name = parameters[0]; record.record_message = "Loadout Forcing Player"; if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, false, false); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_loadout_ignore": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Loadout Ignoring Self"; record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); break; case 1: record.target_name = parameters[0]; record.record_message = "Loadout Ignoring Player"; if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, false, false); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_log": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 1: record.target_name = parameters[0]; //Handle based on report ID as only option if (!HandlePlayerReport(record)) { SendMessageToSource(record, "No log message given, unable to submit."); } FinalizeRecord(record); return; case 2: record.target_name = parameters[0]; //attempt to handle via pre-message ID record.record_message = GetPreMessage(parameters[1], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } //Handle based on report ID if possible if (!HandlePlayerReport(record)) { if (record.record_message.Length >= _RequiredReasonLength) { CompleteTargetInformation(record, false, true, true); } else { SendMessageToSource(record, "Log message too short, unable to submit."); FinalizeRecord(record); } } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "self_feedback": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 1: record.record_message = parameters[0]; record.target_name = "Server"; if (record.record_message.Length < 5) { SendMessageToSource(record, "Feedback message too short, unable to submit."); FinalizeRecord(record); return; } QueueRecordForProcessing(record); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "server_afk": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (_AFKAutoKickEnable) { SendMessageToSource(record, "AFK players are being managed automatically; Disable to use this command."); FinalizeRecord(record); return; } record.record_message = "Manage AFK Players"; record.target_name = "Server"; QueueRecordForProcessing(record); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "server_nuke": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_roundState != RoundState.Playing && record.source_name != "ProconAdmin") { SendMessageToSource(record, record.command_type.command_name + " cannot be used between rounds."); FinalizeRecord(record); return; } if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: String targetTeam = parameters[0].Trim().ToLower(); record.record_message = "Nuke Server"; Log.Debug(() => "target: " + targetTeam, 6); List validTeams = _teamDictionary.Values.Where(aTeam => aTeam.TeamID == 1 || aTeam.TeamID == 2).ToList(); ATeam matchingTeam = validTeams.FirstOrDefault(aTeam => aTeam.TeamKey.ToLower() == targetTeam || aTeam.TeamID.ToString() == targetTeam); if (matchingTeam != null) { record.target_name = matchingTeam.TeamName; record.command_numeric = matchingTeam.TeamID; record.record_message += " (" + matchingTeam.GetTeamIDKey() + ")"; } else if (targetTeam == "all") { record.target_name = "Everyone"; record.record_message += " (Everyone)"; } else { SendMessageToSource(record, "Team " + targetTeam.ToUpper() + " not found. Available: " + String.Join(", ", validTeams.Select(aTeam => aTeam.GetTeamIDKey()).ToArray())); FinalizeRecord(record); return; } //Have the admin confirm the action ConfirmActionWithSource(record); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "server_nuke_winning": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_roundState != RoundState.Playing && record.source_name != "ProconAdmin") { SendMessageToSource(record, record.command_type.command_name + " cannot be used between rounds."); FinalizeRecord(record); return; } if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } ATeam team1, team2, winningTeam, losingTeam, mapUpTeam, mapDownTeam; if (GetTeamByID(1, out team1) && GetTeamByID(2, out team2)) { if (team1.TeamTicketCount > team2.TeamTicketCount) { winningTeam = team1; losingTeam = team2; } else { winningTeam = team2; losingTeam = team1; } if (team1.GetTicketDifferenceRate() > team2.GetTicketDifferenceRate()) { //Team1 has more map than Team2 mapUpTeam = team1; mapDownTeam = team2; } else { //Team2 has more map than Team1 mapUpTeam = team2; mapDownTeam = team1; } if (winningTeam == mapUpTeam) { record.command_action = GetCommandByKey("server_nuke"); record.target_name = winningTeam.TeamName; record.command_numeric = winningTeam.TeamID; record.record_message = "Nuke Winning Team (" + winningTeam.GetTeamIDKey() + ")"; } else { SendMessageToSource(record, "Winning team must also be map-dominant to issue this command."); FinalizeRecord(record); return; } //Have the admin confirm the action ConfirmActionWithSource(record); } else { SendMessageToSource(record, "Unable to fetch teams for nuke winning team command."); FinalizeRecord(record); return; } } break; case "server_countdown": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 3: //Max 30 seconds Int32 countdownSeconds; if (!Int32.TryParse(parameters[0], out countdownSeconds) || countdownSeconds < 1 || countdownSeconds > 30) { SendMessageToSource(record, "Invalid duration, must be 1-30. Unable to submit."); FinalizeRecord(record); return; } String targetSubset = parameters[1].ToLower().Trim(); if (String.IsNullOrEmpty(targetSubset)) { SendMessageToSource(record, "Invalid target, must be squad, team, or all. Unable to submit."); FinalizeRecord(record); return; } Log.Debug(() => "target: " + targetSubset, 6); List validTeams = _teamDictionary.Values.Where(aTeam => aTeam.TeamID == 1 || aTeam.TeamID == 2).ToList(); ATeam matchingTeam = validTeams.FirstOrDefault(aTeam => aTeam.TeamKey.ToLower() == targetSubset.ToLower()); if (matchingTeam != null) { record.target_name = matchingTeam.TeamKey; } else { switch (targetSubset) { case "squad": if (record.source_player == null || !record.source_player.player_online || !_PlayerDictionary.ContainsKey(record.source_player.player_name) || record.source_player.player_type == PlayerType.Spectator) { SendMessageToSource(record, "Must be a player to use squad option. Unable to submit."); FinalizeRecord(record); return; } record.target_name = "Squad"; break; case "team": if (record.source_player == null || !record.source_player.player_online || !_PlayerDictionary.ContainsKey(record.source_player.player_name) || record.source_player.player_type == PlayerType.Spectator) { SendMessageToSource(record, "Must be a player to use team option. Unable to submit."); FinalizeRecord(record); return; } record.target_name = "Team"; break; case "all": record.target_name = "All"; break; default: SendMessageToSource(record, "Invalid target, must be squad, team, or all. Unable to submit."); FinalizeRecord(record); return; } } record.command_numeric = countdownSeconds; String countdownMessage = parameters[2]; if (String.IsNullOrEmpty(countdownMessage)) { SendMessageToSource(record, "Invalid countdown message, unable to submit."); FinalizeRecord(record); return; } record.record_message = countdownMessage; //Have the admin confirm the action QueueRecordForProcessing(record); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "server_kickall": CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } record.target_name = "Non-Admins"; record.record_message = "Kick All Players"; ConfirmActionWithSource(record); break; case "server_swapnuke": CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } record.target_name = "Everyone"; record.record_message = "TeamSwap All Players"; ConfirmActionWithSource(record); break; case "round_end": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: String targetTeam = parameters[0]; Log.Debug(() => "target team: " + targetTeam, 6); record.record_message = "End Round"; List validTeams = _teamDictionary.Values.Where(aTeam => aTeam.TeamID == 1 || aTeam.TeamID == 2).ToList(); ATeam matchingTeam = validTeams.FirstOrDefault(aTeam => aTeam.TeamKey.ToLower() == targetTeam.ToLower()); if (matchingTeam != null) { record.target_name = matchingTeam.TeamName; record.command_numeric = matchingTeam.TeamID; record.record_message += " (" + matchingTeam.TeamName + ")"; } else { SendMessageToSource(record, "Team " + targetTeam.ToUpper() + " not found. Available: " + String.Join(", ", validTeams.Select(aTeam => aTeam.TeamKey).ToArray())); FinalizeRecord(record); return; } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } //Have the admin confirm the action ConfirmActionWithSource(record); } break; case "round_restart": CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } record.target_name = "Server"; record.record_message = "Restart Round"; ConfirmActionWithSource(record); break; case "round_next": CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } record.target_name = "Server"; record.record_message = "Run Next Map"; ConfirmActionWithSource(record); break; case "self_whatis": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: record.record_message = GetPreMessage(parameters[0], true); if (record.record_message == null) { ACommand aCommand; if (_CommandTextDictionary.TryGetValue(parameters[0], out aCommand)) { if (record.source_player == null || HasAccess(record.source_player, aCommand)) { record.record_message = _CommandDescriptionDictionary[aCommand.command_key]; } else { record.record_message = "Your user role " + record.source_player.player_role.role_name + " does not have access to " + aCommand.command_name + "."; } } else { record.record_message = "Invalid PreMessage ID or command name. " + GetChatCommandByKey("self_help") + " for command list. Valid PreMessage IDs are 1-" + _PreMessageList.Count; } } SendMessageToSource(record, record.record_message); FinalizeRecord(record); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } //This type is not processed } break; case "self_voip": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Send them voip information SendMessageToSource(record, _ServerVoipAddress); FinalizeRecord(record); } break; case "self_challenge": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (EventActive()) { SendMessageToSource(record, "Challenges are not enabled during server events."); FinalizeRecord(record); return; } // Target for these commands is always the source. // If there is no source player, then only allow access to info. //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: record.record_message = "info"; record.target_name = record.source_name; QueueRecordForProcessing(record); break; default: record.record_message = parameters[0]; record.target_name = record.source_name; QueueRecordForProcessing(record); break; } } break; case "self_rules": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (EventActive()) { SendMessageToSource(record, GetEventDescription(false)); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: record.target_name = record.source_name; record.record_message = "Player Requested Rules"; if (record.record_source == ARecord.Sources.InGame) { if (!_PlayerDictionary.TryGetValue(record.target_name, out record.target_player)) { SendMessageToSource(record, "Source player not found, unable to submit."); FinalizeRecord(record); return; } } else { record.target_name = "ExternalSource"; } QueueRecordForProcessing(record); break; case 1: record.target_name = parameters[0]; record.record_message = "Telling Player Rules"; if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, false, false); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "self_surrender": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (EventActive()) { SendMessageToSource(record, "Surrender Vote is not available during events."); FinalizeRecord(record); return; } if (!_surrenderVoteEnable) { SendMessageToSource(record, "Surrender Vote must be enabled in AdKats settings to use this command."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 0); switch (parameters.Length) { case 0: record.target_name = record.source_name; record.record_message = "Player Voted for Surrender"; if (record.record_source == ARecord.Sources.InGame) { if (!_PlayerDictionary.TryGetValue(record.target_name, out record.target_player)) { SendMessageToSource(record, "Source player not found, unable to submit."); FinalizeRecord(record); return; } } else { record.target_name = "ExternalSource"; } QueueRecordForProcessing(record); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "self_votenext": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (EventActive()) { SendMessageToSource(record, "Surrender Vote is not available during events."); FinalizeRecord(record); return; } if (!_surrenderVoteEnable) { SendMessageToSource(record, "Surrender Vote must be enabled in AdKats settings to use this command."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 0); switch (parameters.Length) { case 0: record.target_name = record.source_name; record.record_message = "Player Voted for Next Round"; if (record.record_source == ARecord.Sources.InGame) { if (!_PlayerDictionary.TryGetValue(record.target_name, out record.target_player)) { SendMessageToSource(record, "Source player not found, unable to submit."); FinalizeRecord(record); return; } } else { record.target_name = "ExternalSource"; } QueueRecordForProcessing(record); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "self_nosurrender": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (EventActive()) { SendMessageToSource(record, "Surrender Vote is not available during events."); FinalizeRecord(record); return; } if (!_surrenderVoteEnable) { SendMessageToSource(record, "Surrender Vote must be enabled in AdKats settings to use this command."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 0); switch (parameters.Length) { case 0: record.target_name = record.source_name; record.record_message = "Player Voted against Surrender"; if (record.record_source == ARecord.Sources.InGame) { if (!_PlayerDictionary.TryGetValue(record.target_name, out record.target_player)) { SendMessageToSource(record, "Source player not found, unable to submit."); FinalizeRecord(record); return; } } else { record.target_name = "ExternalSource"; } QueueRecordForProcessing(record); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "self_help": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: record.target_name = record.source_name; record.record_message = "Player Requested Commands"; if (record.record_source == ARecord.Sources.InGame) { if (!_PlayerDictionary.TryGetValue(record.target_name, out record.target_player)) { SendMessageToSource(record, "Source player not found, unable to submit."); FinalizeRecord(record); return; } } else { record.target_name = "ExternalSource"; } QueueRecordForProcessing(record); break; case 1: record.target_name = parameters[0]; record.record_message = "Telling Player Commands"; if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, false, false); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "self_rep": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: record.target_name = record.source_name; record.record_message = "Player Requested Reputation"; if (record.record_source == ARecord.Sources.InGame) { if (!_PlayerDictionary.TryGetValue(record.target_name, out record.target_player)) { SendMessageToSource(record, "Source player not found, unable to submit."); FinalizeRecord(record); return; } } else { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } QueueRecordForProcessing(record); break; case 1: if (record.source_player != null && !PlayerIsAdmin(record.source_player)) { SendMessageToSource(record, "You cannot see another player's reputation. Admin only."); FinalizeRecord(record); return; } record.target_name = parameters[0]; record.record_message = "Requesting Player Reputation"; if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, false, false); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_isadmin": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: record.target_name = record.source_name; record.record_message = "Requesting Admin Status"; if (record.record_source == ARecord.Sources.InGame) { if (!_PlayerDictionary.TryGetValue(record.target_name, out record.target_player)) { SendMessageToSource(record, "Source player not found, unable to submit."); FinalizeRecord(record); return; } } else { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } QueueRecordForProcessing(record); break; case 1: record.target_name = parameters[0]; record.record_message = "Requesting Player Admin Status"; CompleteTargetInformation(record, false, false, false); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "self_uptime": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); record.record_message = "Player Requested Uptime"; if (record.record_source == ARecord.Sources.InGame) { record.target_name = record.source_name; if (_PlayerDictionary.TryGetValue(record.target_name, out record.target_player)) { record.target_name = record.target_player.player_name; } else { Log.Error("48204928 this error should never happen."); FinalizeRecord(record); return; } } else { record.target_name = "ExternalSource"; } QueueRecordForProcessing(record); } break; case "self_contest": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //May only call this command from in-game if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } //Player Info Check record.record_message = "Player Contested Report"; record.target_name = record.source_name; if (!_PlayerDictionary.TryGetValue(record.target_name, out record.target_player)) { SendMessageToSource(record, "Player information not found. Unable to process command."); FinalizeRecord(record); return; } // Get the latest active report associated with this player ARecord aRecord = null; foreach (ARecord reportRecord in FetchActivePlayerReports()) { if (reportRecord.target_player.player_id == record.target_player.player_id) { if (aRecord == null || reportRecord.record_time > aRecord.record_time) { aRecord = reportRecord; } } } if (aRecord == null) { SendMessageToSource(record, "You have no reports to contest."); FinalizeRecord(record); return; } //Assign the report ID housed in command numeric record.command_numeric = aRecord.command_numeric; //Set Contested aRecord.isContested = true; //Inform All Parties SendMessageToSource(aRecord, aRecord.GetTargetNames() + " has contested your report against them."); SendMessageToSource(record, "You have contested " + aRecord.GetSourceName() + "'s report against you."); OnlineAdminSayMessage(record.GetSourceName() + " has contested report [" + aRecord.command_numeric + "] for " + aRecord.record_message); QueueRecordForProcessing(record); } break; case "self_admins": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); record.record_message = "Player Requested Online Admins"; if (record.record_source == ARecord.Sources.InGame) { record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); } else { record.target_name = "ExternalSource"; QueueRecordForProcessing(record); } } break; case "self_lead": { //Remove previous commands awaiting confirmationf CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = "Player Taking Squad Lead"; CompleteTargetInformation(record, false, false, false); break; case 1: record.target_name = parameters[0]; record.record_message = "Giving Player Squad Lead"; if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, false, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "self_reportlist": { //Remove previous commands awaiting confirmationf CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: record.record_message = "Listing player reports"; if (record.record_source == ARecord.Sources.InGame) { record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); } else { record.target_name = "ExternalSource"; QueueRecordForProcessing(record); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "admin_accept": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: SendMessageToSource(record, "Report ID must be given. Unable to submit."); FinalizeRecord(record); break; case 1: record.target_name = parameters[0]; //Handle based on report ID as only option AcceptPlayerReport(record); FinalizeRecord(record); return; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } record.record_action_executed = true; } break; case "admin_deny": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: SendMessageToSource(record, "Report ID must be given. Unable to submit."); FinalizeRecord(record); break; case 1: record.target_name = parameters[0]; //Handle based on report ID as only option DenyPlayerReport(record); FinalizeRecord(record); return; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } record.record_action_executed = true; } break; case "admin_ignore": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: SendMessageToSource(record, "Report ID must be given. Unable to submit."); FinalizeRecord(record); break; case 1: record.target_name = parameters[0]; //Handle based on report ID as only option IgnorePlayerReport(record); FinalizeRecord(record); return; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } record.record_action_executed = true; } break; case "admin_say": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: record.record_message = GetPreMessage(parameters[0], false); Log.Debug(() => "" + record.record_message, 6); record.target_name = "Server"; QueueRecordForProcessing(record); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_say": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: SendMessageToSource(record, "No message given, unable to submit."); FinalizeRecord(record); return; case 2: record.target_name = parameters[0]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[1], false); Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, false, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "admin_yell": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: record.record_message = GetPreMessage(parameters[0], false); Log.Debug(() => "" + record.record_message, 6); record.target_name = "Server"; QueueRecordForProcessing(record); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_yell": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: SendMessageToSource(record, "No message given, unable to submit."); FinalizeRecord(record); return; case 2: record.target_name = parameters[0]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[1], false); Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, false, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "admin_tell": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: record.record_message = GetPreMessage(parameters[0], false); Log.Debug(() => "" + record.record_message, 6); record.target_name = "Server"; QueueRecordForProcessing(record); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_tell": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: SendMessageToSource(record, "No message given, unable to submit."); FinalizeRecord(record); return; case 2: record.target_name = parameters[0]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[1], false); Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, false, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_pm_send": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't start private conversations from outside the game. Use player say."); break; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: SendMessageToSource(record, "No message given, unable to submit."); FinalizeRecord(record); return; case 2: record.target_name = parameters[0]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[1], false); Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, false, false, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_pm_reply": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't reply to private conversations from outside the game. Use player say."); break; } if (record.source_player == null || record.source_player.conversationPartner == null) { SendMessageToSource(record, "You are not in a private conversation. Use /" + GetCommandByKey("player_pm_send").command_text + " player message, to start one."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: record.record_message = GetPreMessage(parameters[0], false); record.target_name = record.source_player.conversationPartner.player_name; record.target_player = record.source_player.conversationPartner; QueueRecordForProcessing(record); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "admin_pm_send": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: record.record_message = GetPreMessage(parameters[0], false); QueueRecordForProcessing(record); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_dequeue": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Dequeueing Self"; record.target_name = record.source_name; CompleteTargetInformation(record, false, false, false); break; case 1: record.target_name = parameters[0]; record.record_message = "Dequeueing Player"; Log.Debug(() => "target: " + record.target_name, 6); CompleteTargetInformation(record, false, false, false); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_blacklistdisperse": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } if (!_FeedMultiBalancerDisperseList) { SendMessageToSource(record, "Enable 'Feed MULTIBalancer Even Dispersion List' to use this command."); FinalizeRecord(record); return; } String defaultReason = "Autobalancer Dispersion"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistbalance": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (!_FeedMultiBalancerWhitelist) { SendMessageToSource(record, "Enable 'Feed MULTIBalancer Whitelist' to use this command."); FinalizeRecord(record); return; } String defaultReason = "Autobalancer Whitelist"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_slotreserved": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (!_FeedServerReservedSlots && !_FeedServerReservedSlots_VSM) { SendMessageToSource(record, "Enable 'Feed Server Reserved Slots' to use this command."); FinalizeRecord(record); return; } String defaultReason = "Reserved Slot"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_slotspectator": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (!_FeedServerSpectatorList) { SendMessageToSource(record, "Enable 'Feed Server Spectator Slots' to use this command."); FinalizeRecord(record); return; } String defaultReason = "Spectator Slot"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistreport_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing Report Whitelist"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing Report Whitelist"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistspambot_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (!_spamBotExcludeAdminsAndWhitelist) { SendMessageToSource(record, "'Exclude Admins and Whitelist from Spam' must be enabled to use this command."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing SpamBot Whitelist"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing SpamBot Whitelist"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_blacklistspectator_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing Spectator Blacklist"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing Spectator Blacklist"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_blacklistreport_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing Report Source Blacklist"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing Report Source Blacklist"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistcommand_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing Command Target Whitelist"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing Report Target Whitelist"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_blacklistautoassist_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing Auto-Assist Blacklist"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing Auto-Assist Blacklist"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistaa_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (!_EnableAdminAssistants) { SendMessageToSource(record, "Enable Admin Assistants to use this command."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing Admin Assistant Whitelist"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing Admin Assistant Whitelist"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistping_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (!_pingEnforcerEnable) { SendMessageToSource(record, "Enable Ping Enforcer to use this command."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing Ping Whitelist"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing Ping Whitelist"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistanticheat_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing AntiCheat Whitelist"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing AntiCheat Whitelist"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_slotspectator_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (!_FeedServerSpectatorList) { SendMessageToSource(record, "Enable 'Feed Server Spectator Slots' to use this command."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing Spectator Slot"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing Spectator Slot"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_slotreserved_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (!_FeedServerReservedSlots) { SendMessageToSource(record, "Enable 'Feed Server Reserved Slots' to use this command."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing Reserved Slot"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing Reserved Slot"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistbalance_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (!_FeedMultiBalancerWhitelist) { SendMessageToSource(record, "Enable 'Feed MULTIBalancer Whitelist' to use this command."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing Autobalance Whitelist"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing Autobalance Whitelist"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_blacklistdisperse_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (!_FeedMultiBalancerDisperseList) { SendMessageToSource(record, "Enable 'Feed MULTIBalancer Even Dispersion List' to use this command."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing Autobalance Dispersion"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing Autobalance Dispersion"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistpopulator_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (!_PopulatorMonitor) { SendMessageToSource(record, "'Monitor Populator Players' must be enabled to use this command."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing Populator Whitelist"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing Populator Whitelist"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_whitelistteamkill_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (!_FeedTeamKillTrackerWhitelist) { SendMessageToSource(record, "Enable 'Feed TeamKillTracker Whitelist' to use this command."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing TeamKillTracker Whitelist"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing TeamKillTracker Whitelist"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "plugin_restart": { //Remove previous commands awaiting confirmationf CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: record.target_name = "AdKats"; record.record_message = "Restart AdKats"; ConfirmActionWithSource(record); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "plugin_update": { //Remove previous commands awaiting confirmationf CancelSourcePendingAction(record); record.target_name = "AdKats"; record.record_message = "Update AdKats"; QueueRecordForProcessing(record); } break; case "server_shutdown": { //Remove previous commands awaiting confirmationf CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: record.target_name = "Server"; record.record_message = "Shutdown Server"; ConfirmActionWithSource(record); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "self_battlecry": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: record.record_message = ""; record.target_name = record.source_name; if (!_PlayerDictionary.TryGetValue(record.target_name, out record.target_player)) { SendMessageToSource(record, "Source player not found, unable to submit."); FinalizeRecord(record); return; } QueueRecordForProcessing(record); break; case 1: record.record_message = GetPreMessage(parameters[0], false); if (record.record_message.Length > _battlecryMaxLength) { SendMessageToSource(record, "Battlecries cannot be longer than " + _battlecryMaxLength + " characters."); FinalizeRecord(record); return; } var messageLower = record.record_message.Trim().ToLowerInvariant(); foreach (String deniedWord in _battlecryDeniedWords) { if (!String.IsNullOrEmpty(deniedWord.Trim()) && messageLower.Contains(deniedWord.Trim().ToLowerInvariant())) { SendMessageToSource(record, "Your battlecry contains denied words. Talk to an admin if this message is in error."); FinalizeRecord(record); return; } } record.target_name = record.source_name; if (!_PlayerDictionary.TryGetValue(record.target_name, out record.target_player)) { SendMessageToSource(record, "Source player not found, unable to submit."); FinalizeRecord(record); return; } QueueRecordForProcessing(record); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_battlecry": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: record.target_name = parameters[0]; record.record_message = ""; CompleteTargetInformation(record, false, true, true); break; case 2: record.target_name = parameters[0]; record.record_message = GetPreMessage(parameters[1], false); if (record.record_message.Length > _battlecryMaxLength) { SendMessageToSource(record, "Battlecries cannot be longer than " + _battlecryMaxLength + " characters."); FinalizeRecord(record); return; } CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_discordlink": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 2); String tempMemberName = null; DiscordManager.DiscordMember matchingMember = null; switch (parameters.Length) { case 0: SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: record.record_message = ""; record.target_name = record.source_name; if (!_PlayerDictionary.TryGetValue(record.target_name, out record.target_player)) { SendMessageToSource(record, "Source player not found, unable to submit."); FinalizeRecord(record); return; } tempMemberName = parameters[0]; // Pull the discord member matchingMember = _DiscordManager.GetMembers(false, true, true) .FirstOrDefault(aMember => aMember.Name.ToLower().Contains(tempMemberName.ToLower())); if (matchingMember == null) { SendMessageToSource(record, "No matching discord member for '" + tempMemberName + "'."); FinalizeRecord(record); return; } if (matchingMember.ID == record.target_player.player_discord_id) { SendMessageToSource(record, record.target_player.GetVerboseName() + " already linked with discord member " + matchingMember.Name + "."); FinalizeRecord(record); return; } record.record_message = matchingMember.ID; QueueRecordForProcessing(record); break; case 2: record.target_name = parameters[0]; tempMemberName = parameters[1]; // Pull the discord member matchingMember = _DiscordManager.GetMembers(false, true, true) .FirstOrDefault(aMember => aMember.Name.ToLower().Contains(tempMemberName.ToLower())); if (matchingMember == null) { SendMessageToSource(record, "No matching discord member for '" + tempMemberName + "'."); FinalizeRecord(record); return; } record.record_message = matchingMember.ID; CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Discord link needs a player and a discord member, unable to submit."); FinalizeRecord(record); return; } } break; case "player_blacklistallcaps": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (_serverInfo.ServerType == "OFFICIAL") { SendMessageToSource(record, record.command_type.command_name + " cannot be performed on official servers."); FinalizeRecord(record); return; } if (!_UseAllCapsLimiter) { SendMessageToSource(record, "Enable 'Use All Caps Limiter' to use this command."); FinalizeRecord(record); return; } String defaultReason = "All-Caps Chat Blacklist"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_blacklistallcaps_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); if (!_UseAllCapsLimiter) { SendMessageToSource(record, "Enable 'Use All Caps Limiter' to use this command."); FinalizeRecord(record); return; } //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing All-Caps Chat Blacklist"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing All-Caps Chat Blacklist"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "command_confirm": Log.Debug(() => "attempting to confirm command", 6); ARecord recordAttempt = null; _ActionConfirmDic.TryGetValue(record.source_name, out recordAttempt); if (recordAttempt != null) { Log.Debug(() => "command found, calling processing", 6); _ActionConfirmDic.Remove(record.source_name); QueueRecordForProcessing(recordAttempt); FinalizeRecord(record); return; } FinalizeRecord(record); break; case "command_cancel": Log.Debug(() => "attempting to cancel command", 6); if (_ActionConfirmDic.Remove(record.source_name)) { SendMessageToSource(record, "Previous command cancelled."); } else if (!_surrenderVoteSucceeded && _surrenderVoteList.Contains(record.source_name)) { if (_surrenderVoteList.Remove(record.source_name)) { SendMessageToSource(record, "Your vote has been removed!"); Int32 requiredVotes = (Int32)((GetPlayerCount() / 2.0) * (_surrenderVoteMinimumPlayerPercentage / 100.0)); Int32 voteCount = _surrenderVoteList.Count - _nosurrenderVoteList.Count; OnlineAdminSayMessage(record.GetSourceName() + " removed their surrender vote."); AdminSayMessage((requiredVotes - voteCount) + " votes needed for surrender/scramble. Use " + GetChatCommandByKey("self_surrender") + ", " + GetChatCommandByKey("self_votenext") + ", or " + GetChatCommandByKey("self_nosurrender") + " to vote."); AdminYellMessage((requiredVotes - voteCount) + " votes needed for surrender/scramble"); } } FinalizeRecord(record); break; case "player_challenge_play": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); String defaultReason = "Challenge Playing Status"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_challenge_play_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing Challenge Playing Status"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing Challenge Playing Status"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_challenge_ignore": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); String defaultReason = "Challenge Ignoring Status"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_challenge_ignore_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Removing Challenge Ignoring Status"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Removing Challenge Ignoring Status"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_challenge_autokill": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); String defaultReason = "Challenge Autokill Status"; //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 3); if (parameters.Length > 0) { String stringDuration = parameters[0].ToLower(); Log.Debug(() => "Raw Duration: " + stringDuration, 6); if (stringDuration == "perm") { //20 years in minutes record.command_numeric = 10518984; defaultReason = "Permanent " + defaultReason; } else { //Default is minutes Double recordDuration = 0.0; Double durationMultiplier = 1.0; if (stringDuration.EndsWith("s")) { stringDuration = stringDuration.TrimEnd('s'); durationMultiplier = (1.0 / 60.0); } else if (stringDuration.EndsWith("m")) { stringDuration = stringDuration.TrimEnd('m'); durationMultiplier = 1.0; } else if (stringDuration.EndsWith("h")) { stringDuration = stringDuration.TrimEnd('h'); durationMultiplier = 60.0; } else if (stringDuration.EndsWith("d")) { stringDuration = stringDuration.TrimEnd('d'); durationMultiplier = 1440.0; } else if (stringDuration.EndsWith("w")) { stringDuration = stringDuration.TrimEnd('w'); durationMultiplier = 10080.0; } else if (stringDuration.EndsWith("y")) { stringDuration = stringDuration.TrimEnd('y'); durationMultiplier = 525949.0; } if (!Double.TryParse(stringDuration, out recordDuration)) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } record.command_numeric = (int)(recordDuration * durationMultiplier); if (record.command_numeric <= 0) { SendMessageToSource(record, "Invalid duration given, unable to submit."); FinalizeRecord(record); return; } defaultReason = FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2) + " " + defaultReason; } } switch (parameters.Length) { case 0: //No parameters SendMessageToSource(record, "No parameters given, unable to submit."); FinalizeRecord(record); return; case 1: //time if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.target_name = record.source_name; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 2: //time //player record.target_name = parameters[1]; record.record_message = defaultReason; CompleteTargetInformation(record, false, true, true); break; case 3: //time //player //reason record.target_name = parameters[1]; Log.Debug(() => "target: " + record.target_name, 6); record.record_message = GetPreMessage(parameters[2], _RequirePreMessageUse); if (record.record_message == null) { SendMessageToSource(record, "Invalid PreMessage ID, valid PreMessage IDs are 1-" + _PreMessageList.Count); FinalizeRecord(record); return; } Log.Debug(() => "" + record.record_message, 6); CompleteTargetInformation(record, false, true, true); break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; case "player_challenge_autokill_remove": { //Remove previous commands awaiting confirmation CancelSourcePendingAction(record); //Parse parameters using max param count String[] parameters = Util.ParseParameters(remainingMessage, 1); switch (parameters.Length) { case 0: if (record.record_source != ARecord.Sources.InGame) { SendMessageToSource(record, "You can't use a self-targeted command from outside the game."); FinalizeRecord(record); return; } record.record_message = "Challenge AutoKill Status"; record.target_name = record.source_name; CompleteTargetInformation(record, true, true, false); break; case 1: record.record_message = "Challenge AutoKill Status"; record.target_name = parameters[0]; //Handle based on report ID if possible if (!HandlePlayerReport(record)) { CompleteTargetInformation(record, false, true, true); } break; default: SendMessageToSource(record, "Invalid parameters, unable to submit."); FinalizeRecord(record); return; } } break; default: Log.Error("Unable to complete record for " + record.command_type.command_key + ", handler not found."); FinalizeRecord(record); return; } } catch (Exception e) { record.record_exception = Log.HandleException(new AException("Error occured while completing record information.", e)); FinalizeRecord(record); } } private ATeam GetTeamByKey(String teamKey) { return _teamDictionary.Values.FirstOrDefault(dTeam => dTeam.TeamKey == teamKey); } public void FinalizeRecord(ARecord record) { Log.Debug(() => "Entering FinalizeRecord", 7); try { //Make sure commands are assigned properly if (record.command_action == null) { if (record.command_type != null) { record.command_action = record.command_type; } else { //Record has no command. Ignore it. return; } } if (record.external_responseRequested) { Hashtable responseHashtable = new Hashtable { {"caller_identity", "AdKats"}, {"response_requested", false}, {"response_type", "IssueCommand"}, {"response_value", CPluginVariable.EncodeStringArray(record.debugMessages.ToArray())} }; ExecuteCommand("procon.protected.plugins.call", record.external_responseClass, record.external_responseMethod, "AdKats", JSON.JsonEncode(responseHashtable)); } //Performance testing area if (record.source_name == _debugSoldierName) { SendMessageToSource(record, "Duration: " + ((int)UtcNow().Subtract(_commandStartTime).TotalMilliseconds) + "ms"); } if (record.record_source == ARecord.Sources.InGame || record.record_source == ARecord.Sources.Automated) { Log.Debug(() => "In-Game/Automated " + record.command_action.command_key + " record took " + Math.Round((DateTime.UtcNow - record.record_creationTime).TotalMilliseconds) + "ms to complete actions.", 3); } //Add event log if (String.IsNullOrEmpty(record.target_name)) { if (record.target_player != null) { record.target_name = record.target_player.player_name; } else { record.target_name = "UnknownTarget"; } } if (String.IsNullOrEmpty(record.source_name)) { if (record.source_player != null) { record.source_name = record.source_player.player_name; } else { record.source_name = "UnknownSource"; } } String message; if (record.record_action_executed) { message = record.GetSourceName() + " issued " + record.command_action.command_name + " on " + record.GetTargetNames() + " for " + record.record_message; } else { message = record.GetSourceName() + " FAILED to issue " + record.command_action.command_name + " on " + record.GetTargetNames() + " for " + record.record_message; } ExecuteCommand("procon.protected.events.write", "Plugins", "PluginAction", message, record.GetSourceName()); } catch (Exception e) { Log.HandleException(new AException("Error while finalizing record.", e)); } Log.Debug(() => "Exiting FinalizeRecord", 7); } public void CompleteTargetInformation(ARecord record, Boolean requireConfirm, Boolean externalFetch, Boolean externalOnlineFetch) { CompleteTargetInformation(record, true, requireConfirm, externalFetch, externalOnlineFetch); } public void CompleteTargetInformation(ARecord record, Boolean includeLeftPlayers, Boolean requireConfirm, Boolean externalFetch, Boolean externalOnlineFetch) { try { Boolean confirmNeeded = false; //Multiple target case if (record.TargetNamesLocal.Any()) { foreach (string targetName in record.TargetNamesLocal) { //Attempt to get the player object APlayer aPlayer; String resultMessage; Boolean curConfirm; if (FetchPlayerFromName(targetName, includeLeftPlayers, externalFetch, externalOnlineFetch, out aPlayer, out curConfirm, out resultMessage)) { record.TargetPlayersLocal.Add(aPlayer); if (curConfirm) { SendMessageToSource(record, resultMessage); confirmNeeded = true; } } else { SendMessageToSource(record, resultMessage); } } //Ensure main target player is null record.target_player = null; } //Single target case else { //Attempt to get the player object APlayer aPlayer; String resultMessage; Boolean curConfirm; if (FetchPlayerFromName(record.target_name, includeLeftPlayers, externalFetch, externalOnlineFetch, out aPlayer, out curConfirm, out resultMessage)) { record.target_name = aPlayer.player_name; record.target_player = aPlayer; if (curConfirm) { SendMessageToSource(record, resultMessage); confirmNeeded = true; } } else { SendMessageToSource(record, resultMessage); FinalizeRecord(record); return; } } if (confirmNeeded) { ConfirmActionWithSource(record); } else { QueueRecordForProcessing(record); } } catch (Exception e) { record.record_exception = Log.HandleException(new AException("Error while completing target information.", e)); FinalizeRecord(record); } } public Boolean FetchPlayerFromName(String playerNameInput, Boolean includeLeftPlayers, Boolean externalFetch, Boolean externalOnlineFetch, out APlayer aPlayer, out Boolean confirmNeeded, out String resultMessage) { //Set default return values resultMessage = "No valid player found for '" + playerNameInput + "'"; confirmNeeded = false; aPlayer = null; try { if (!IsSoldierNameValid(playerNameInput)) { resultMessage = "'" + playerNameInput + "' is an invalid player name."; return false; } //Check for an exact match if (_PlayerDictionary.TryGetValue(playerNameInput, out aPlayer)) { aPlayer.LastUsage = UtcNow(); return true; } if (includeLeftPlayers && _PlayerLeftDictionary.TryGetValue(playerNameInput, out aPlayer)) { aPlayer.LastUsage = UtcNow(); return true; } //Check online players for substring match List currentPlayerNames = _PlayerDictionary.Keys.ToList(); List leftPlayerNames = _PlayerLeftDictionary.Keys.ToList(); //Get all subString matches List subStringMatches = new List(); subStringMatches.AddRange(currentPlayerNames.Where(playerName => Regex.Match(playerName, playerNameInput, RegexOptions.IgnoreCase).Success)); if (subStringMatches.Count == 1) { //Only one subString match, call processing without confirmation if able if (_PlayerDictionary.TryGetValue(subStringMatches[0], out aPlayer)) { aPlayer.LastUsage = UtcNow(); resultMessage = "Player match found for '" + playerNameInput + "'"; return true; } Log.Error("Error fetching player for substring match."); resultMessage = "Error fetching player for substring match."; return false; } if (subStringMatches.Count > 1) { //Multiple players matched the query, choose correct one String msg = "'" + playerNameInput + "' matches multiple players: "; bool first = true; String suggestion = null; foreach (String playerName in subStringMatches) { if (first) { msg = msg + playerName; first = false; } else { msg = msg + ", " + playerName; } //Suggest player names that start with the text admins entered over others if (playerName.ToLower().StartsWith(playerNameInput.ToLower())) { suggestion = playerName; } } if (suggestion == null) { //If no player id starts with what admins typed, suggest subString id with lowest Levenshtein distance Int32 bestDistance = Int32.MaxValue; foreach (String playerName in subStringMatches) { Int32 distance = Util.LevenshteinDistance(playerNameInput, playerName); if (distance < bestDistance) { bestDistance = distance; suggestion = playerName; } } } //If the suggestion is still null, something has failed if (suggestion == null) { Log.Error("Name suggestion system failed substring match"); resultMessage = "Name suggestion system failed substring match"; return false; } //Use suggestion for target if (_PlayerDictionary.TryGetValue(suggestion, out aPlayer)) { resultMessage = msg; confirmNeeded = true; aPlayer.LastUsage = UtcNow(); return true; } Log.Error("Substring match fetch failed."); resultMessage = "Substring match fetch failed."; return false; } if (includeLeftPlayers) { //There were no players found in the online dictionary. Run a search on the offline dictionary //Get all subString matches List subStringLeftMatches = new List(); subStringLeftMatches.AddRange(leftPlayerNames.Where(playerName => Regex.Match(playerName, playerNameInput, RegexOptions.IgnoreCase).Success)); if (subStringLeftMatches.Count == 1) { //Only one subString match, call processing without confirmation if able if (_PlayerLeftDictionary.TryGetValue(subStringLeftMatches[0], out aPlayer)) { resultMessage = "OFFLINE player match found for '" + playerNameInput + "'"; confirmNeeded = true; aPlayer.LastUsage = UtcNow(); return true; } Log.Error("Error fetching player for substring match."); resultMessage = "Error fetching player for substring match."; return false; } if (subStringLeftMatches.Count > 1) { //Multiple players matched the query, choose correct one String msg = "'" + playerNameInput + "' matches multiple OFFLINE players: "; bool first = true; String suggestion = null; foreach (String playerName in subStringLeftMatches) { if (first) { msg = msg + playerName; first = false; } else { msg = msg + ", " + playerName; } //Suggest player names that start with the text admins entered over others if (playerName.ToLower().StartsWith(playerNameInput.ToLower())) { suggestion = playerName; } } if (suggestion == null) { //If no player id starts with what admins typed, suggest subString id with lowest Levenshtein distance Int32 bestDistance = Int32.MaxValue; foreach (String playerName in subStringLeftMatches) { Int32 distance = Util.LevenshteinDistance(playerNameInput, playerName); if (distance < bestDistance) { bestDistance = distance; suggestion = playerName; } } } //If the suggestion is still null, something has failed if (suggestion == null) { Log.Error("Name suggestion system failed subString match"); resultMessage = "Name suggestion system failed subString match"; return false; } //Use suggestion for target if (_PlayerLeftDictionary.TryGetValue(suggestion, out aPlayer)) { resultMessage = msg; confirmNeeded = true; aPlayer.LastUsage = UtcNow(); return true; } Log.Error("Substring match fetch failed."); resultMessage = "Substring match fetch failed."; return false; } } if (externalFetch) { if (playerNameInput.Length < 3) { resultMessage = "No matching online player found, offline search must be at least 3 characters long."; return false; } //No online or left player found, run external fetch over checking for fuzzy match aPlayer = FetchPlayer(false, false, true, null, -1, playerNameInput, null, null, null); if (aPlayer != null) { resultMessage = "Offline player found."; aPlayer.player_online = false; aPlayer.LiveKills.Clear(); aPlayer.player_server = null; confirmNeeded = true; aPlayer.LastUsage = UtcNow(); return true; } } if (externalOnlineFetch) { //No online or left player found, run external online player fetch over checking for fuzzy match aPlayer = FetchMatchingExternalOnlinePlayer(playerNameInput); if (aPlayer != null) { resultMessage = "Online player found in '" + aPlayer.player_server.ServerName.Substring(0, 20) + "'."; confirmNeeded = true; aPlayer.LastUsage = UtcNow(); return true; } } //No other option, run fuzzy match if (currentPlayerNames.Count > 0) { //Player not found in either dictionary, run a fuzzy search using Levenshtein Distance on all players in server String fuzzyMatch = null; Int32 bestFuzzyDistance = Int32.MaxValue; foreach (String playerName in currentPlayerNames) { Int32 distance = Util.LevenshteinDistance(playerNameInput, playerName); if (distance < bestFuzzyDistance) { bestFuzzyDistance = distance; fuzzyMatch = playerName; } } //If the suggestion is still null, something has failed if (fuzzyMatch == null) { Log.Error("Name suggestion system failed fuzzy match"); resultMessage = "Name suggestion system failed fuzzy match"; return false; } if (_PlayerDictionary.TryGetValue(fuzzyMatch, out aPlayer)) { resultMessage = "Fuzzy player match found for '" + playerNameInput + "'"; confirmNeeded = true; aPlayer.LastUsage = UtcNow(); return true; } Log.Error("Player suggestion found matching player, but it could not be fetched."); resultMessage = "Player suggestion found matching player, but it could not be fetched."; return false; } if (includeLeftPlayers && leftPlayerNames.Count > 0) { //No players in the online dictionary, but there are players in the offline dictionary, //run a fuzzy search using Levenshtein Distance on all players who have left String fuzzyMatch = null; Int32 bestFuzzyDistance = Int32.MaxValue; foreach (String playerName in leftPlayerNames) { Int32 distance = Util.LevenshteinDistance(playerNameInput, playerName); if (distance < bestFuzzyDistance) { bestFuzzyDistance = distance; fuzzyMatch = playerName; } } //If the suggestion is still null, something has failed if (fuzzyMatch == null) { Log.Error("Name suggestion system failed fuzzy match"); resultMessage = "Name suggestion system failed fuzzy match"; return false; } if (_PlayerLeftDictionary.TryGetValue(fuzzyMatch, out aPlayer)) { resultMessage = "Fuzzy player match found for '" + playerNameInput + "'"; confirmNeeded = true; aPlayer.LastUsage = UtcNow(); return true; } Log.Error("Player suggestion found matching player, but it could not be fetched."); resultMessage = "Player suggestion found matching player, but it could not be fetched."; return false; } Log.Error("Unable to find a matching player."); resultMessage = "Unable to find a matching player."; return false; } catch (Exception e) { Log.HandleException(new AException("Error while fetching player from name.", e)); } return false; } public void ConfirmActionWithSource(ARecord record) { Log.Debug(() => "Entering confirmActionWithSource", 7); try { if (_bypassCommandConfirmation) { QueueRecordForProcessing(record); return; } lock (_ActionConfirmDic) { //Cancel any source pending action CancelSourcePendingAction(record); //Send record to attempt list _ActionConfirmDic.Add(record.source_name, record); SendMessageToSource(record, record.command_type.command_name + "->" + record.GetTargetNames() + " for " + record.record_message + "?"); } } catch (Exception e) { record.record_exception = Log.HandleException(new AException("Error while confirming action with record source.", e)); } Log.Debug(() => "Exiting confirmActionWithSource", 7); } public void CancelSourcePendingAction(ARecord record) { Log.Debug(() => "Entering cancelSourcePendingAction", 7); try { Log.Debug(() => "attempting to cancel command", 6); lock (_ActionConfirmDic) { if (_ActionConfirmDic.Remove(record.source_name)) { SendMessageToSource(record, "Previous command Canceled."); } } } catch (Exception e) { record.record_exception = Log.HandleException(new AException("Error while Cancelling source pending action.", e)); } Log.Debug(() => "Exiting cancelSourcePendingAction", 7); } public List FetchActivePlayerReports() { try { return _PlayerReports.ToList().Where(dRecord => IsActiveReport(dRecord)).ToList(); } catch (Exception e) { Log.HandleException(new AException("Error while fetching active player reports.", e)); } return new List(); } public ARecord FetchPlayerReportByID(String reportIDString) { ARecord reportedRecord = null; try { Int32 parsedReportID = 0; if (Int32.TryParse(reportIDString, out parsedReportID) && parsedReportID > 0) { lock (_PlayerReports) { reportedRecord = _PlayerReports.ToList().FirstOrDefault(dRecord => dRecord.command_numeric == parsedReportID); } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching player report by report ID.", e)); } return reportedRecord; } private Int32 AddRecordToReports(ARecord aRecord) { if (aRecord == null) { throw new ArgumentNullException("Records cannot be null when adding to reports."); } try { Int32 reportID = aRecord.command_numeric; if (reportID == 0) { var currentReports = _PlayerReports.ToList(); if (currentReports.Count() > 500) { // Remove the oldest 50 reports if we are over 500 reports in the log var oldest50Reports = currentReports.OrderBy(record => record.record_id).Take(50); // Can't call RemoveAll with a list -_- foreach (var report in oldest50Reports) { _PlayerReports.Remove(report); } } Random random = new Random(); do { reportID = random.Next(100, 999); } while (_PlayerReports.Any(dRecord => dRecord.command_numeric == reportID && IsActiveReport(dRecord))); aRecord.command_numeric = reportID; _PlayerReports.Add(aRecord); } return reportID; } catch (Exception e) { Log.HandleException(new AException("Error while adding record to report list.", e)); } return 0; } public Boolean DenyPlayerReport(ARecord record) { try { Log.Debug(() => "Attempting to handle based on player report.", 6); ARecord reportedRecord = FetchPlayerReportByID(record.target_name); if (reportedRecord != null) { if (!IsActiveReport(reportedRecord)) { if (reportedRecord.command_action.command_key == "player_report_ignore") { SendMessageToSource(record, "Report " + reportedRecord.command_numeric + " was ignored, will now be denied."); } else if (reportedRecord.command_action.command_key == "player_report_confirm") { SendMessageToSource(record, "Report " + reportedRecord.command_numeric + " was accepted, will now be denied."); } else if (reportedRecord.command_action.command_key == "player_report_deny") { SendMessageToSource(record, "Report " + reportedRecord.command_numeric + " is already denied."); FinalizeRecord(record); return false; } else if (reportedRecord.command_action.command_key == "player_report_expire") { SendMessageToSource(record, "Report " + reportedRecord.command_numeric + " was expired, will now be denied."); } } Log.Debug(() => "Denying player report.", 5); reportedRecord.command_action = GetCommandByKey("player_report_deny"); UpdateRecord(reportedRecord); SendMessageToSource(reportedRecord, "Your report [" + reportedRecord.command_numeric + "] has been denied."); OnlineAdminSayMessage("Report [" + reportedRecord.command_numeric + "] has been denied by " + record.GetSourceName() + "."); record.target_name = reportedRecord.source_name; record.target_player = reportedRecord.source_player; QueueRecordForProcessing(record); return true; } else { SendMessageToSource(record, "Invalid report ID given, unable to submit."); } } catch (Exception e) { Log.HandleException(new AException("Error while denying player report.", e)); } return false; } public Boolean IgnorePlayerReport(ARecord record) { try { Log.Debug(() => "Attempting to handle based on player report.", 6); ARecord reportedRecord = FetchPlayerReportByID(record.target_name); if (reportedRecord != null) { if (!IsActiveReport(reportedRecord)) { if (reportedRecord.command_action.command_key == "player_report_ignore") { SendMessageToSource(record, "Report " + reportedRecord.command_numeric + " is already ignored."); FinalizeRecord(record); return false; } else if (reportedRecord.command_action.command_key == "player_report_confirm") { SendMessageToSource(record, "Report " + reportedRecord.command_numeric + " was accepted, will now be ignored."); } else if (reportedRecord.command_action.command_key == "player_report_deny") { SendMessageToSource(record, "Report " + reportedRecord.command_numeric + " was denied, will now be ignored."); } else if (reportedRecord.command_action.command_key == "player_report_expire") { SendMessageToSource(record, "Report " + reportedRecord.command_numeric + " was expired, will now be ignored."); } } Log.Debug(() => "Ignoring player report.", 5); reportedRecord.command_action = GetCommandByKey("player_report_ignore"); UpdateRecord(reportedRecord); OnlineAdminSayMessage("Report [" + reportedRecord.command_numeric + "] has been ignored by " + record.GetSourceName() + "."); record.target_name = reportedRecord.source_name; record.target_player = reportedRecord.source_player; QueueRecordForProcessing(record); return true; } else { SendMessageToSource(record, "Invalid report ID given, unable to submit."); } } catch (Exception e) { Log.HandleException(new AException("Error while ignoring player report.", e)); } return false; } public Boolean AcceptPlayerReport(ARecord record) { try { Log.Debug(() => "Attempting to handle based on player report.", 6); ARecord reportedRecord = FetchPlayerReportByID(record.target_name); if (reportedRecord != null) { if (!IsActiveReport(reportedRecord)) { if (reportedRecord.command_action.command_key == "player_report_ignore") { SendMessageToSource(record, "Report " + reportedRecord.command_numeric + " was ignored, will now be accepted."); } else if (reportedRecord.command_action.command_key == "player_report_confirm") { SendMessageToSource(record, "Report " + reportedRecord.command_numeric + " is already accepted."); FinalizeRecord(record); return false; } else if (reportedRecord.command_action.command_key == "player_report_deny") { SendMessageToSource(record, "Report " + reportedRecord.command_numeric + " was denied, will now be accepted."); } else if (reportedRecord.command_action.command_key == "player_report_expire") { SendMessageToSource(record, "Report " + reportedRecord.command_numeric + " was expired, will now be accepted."); } } Log.Debug(() => "Accepting player report.", 5); ConfirmActiveReport(reportedRecord); SendMessageToSource(reportedRecord, "Your report [" + reportedRecord.command_numeric + "] has been accepted. Thank you."); OnlineAdminSayMessage("Report [" + reportedRecord.command_numeric + "] has been accepted by " + record.GetSourceName() + "."); record.target_name = reportedRecord.source_name; record.target_player = reportedRecord.source_player; record.record_action_executed = true; QueueRecordForProcessing(record); return true; } else { SendMessageToSource(record, "Invalid report ID given, unable to submit."); } } catch (Exception e) { Log.HandleException(new AException("Error while denying player report.", e)); } return false; } public Boolean HandlePlayerReport(ARecord record) { try { Log.Debug(() => "Attempting to handle based on player report.", 6); ARecord reportedRecord = FetchPlayerReportByID(record.target_name); if (reportedRecord != null) { if (!IsActiveReport(reportedRecord)) { SendMessageToSource(record, "Report [" + record.target_name + "] has already been acted on."); return true; } if (record.source_player != null && !PlayerIsAdmin(record.source_player)) { return false; } if ((UtcNow() - reportedRecord.record_time).TotalSeconds < _MinimumReportHandleSeconds) { SendMessageToSource(record, "Report [" + record.target_name + "] cannot be acted on. " + FormatTimeString(TimeSpan.FromSeconds(_MinimumReportHandleSeconds - (UtcNow() - reportedRecord.record_time).TotalSeconds), 2) + " remaining."); return true; } if (reportedRecord.isContested) { SendMessageToSource(record, "Report [" + reportedRecord.command_numeric + "] is contested. Please investigate."); if (record.source_player != null) { PlayerYellMessage(record.source_player.player_name, "Report [" + reportedRecord.command_numeric + "] is contested. Please investigate."); } return true; } Log.Debug(() => "Handling player report.", 5); SendMessageToSource(reportedRecord, "Your report [" + reportedRecord.command_numeric + "] has been acted on. Thank you."); OnlineAdminSayMessage("Report [" + reportedRecord.command_numeric + "] has been acted on by " + record.GetSourceName() + "."); ConfirmActiveReport(reportedRecord); record.target_name = reportedRecord.target_name; record.target_player = reportedRecord.target_player; if (String.IsNullOrEmpty(record.record_message) || record.record_message.Length < _RequiredReasonLength) { record.record_message = reportedRecord.record_message; } QueueRecordForProcessing(record); return true; } } catch (Exception e) { record.record_exception = new AException("Error while handling player report.", e); Log.HandleException(record.record_exception); } return false; } public Boolean IsActiveReport(ARecord aRecord) { try { if (aRecord == null || aRecord.record_id < 1 || aRecord.command_type == null || (aRecord.command_type.command_key != "player_report" && aRecord.command_type.command_key != "player_calladmin") || aRecord.command_action == null || aRecord.command_action.command_key == "player_report_confirm" || aRecord.command_action.command_key == "player_report_ignore" || aRecord.command_action.command_key == "player_report_deny" || aRecord.command_action.command_key == "player_report_expire" || aRecord.target_player == null || aRecord.TargetSession != aRecord.target_player.ActiveSession) { return false; } } catch (Exception e) { Log.HandleException(new AException("Error while checking if a report was active.", e)); } return true; } public void ConfirmActiveReport(ARecord report) { try { if (report != null && IsActiveReport(report)) { // Expire all other active reports against the player since this is the one that we acted on var reportsToExpire = report.target_player.TargetedRecords.Where(dRecord => IsActiveReport(dRecord) && dRecord.record_id != report.record_id); foreach (var eReport in reportsToExpire) { ExpireActiveReport(eReport); } report.command_action = GetCommandByKey("player_report_confirm"); UpdateRecord(report); } } catch (Exception e) { Log.HandleException(new AException("Error while confirming an active report.", e)); } } public void ExpireActiveReport(ARecord aRecord) { try { if (aRecord != null && IsActiveReport(aRecord)) { aRecord.command_action = GetCommandByKey("player_report_expire"); UpdateRecord(aRecord); } } catch (Exception e) { Log.HandleException(new AException("Error while expiring an active report.", e)); } } //replaces the message with a pre-message public String GetPreMessage(String message, Boolean required) { Log.Debug(() => "Entering getPreMessage", 7); try { if (!string.IsNullOrEmpty(message)) { //Attempt to fill the message via pre-message ID Int32 preMessageID = 0; Log.Debug(() => "Raw preMessageID: " + message, 6); Boolean valid = Int32.TryParse(message, out preMessageID); if (valid && (preMessageID > 0) && (preMessageID <= _PreMessageList.Count)) { message = _PreMessageList[preMessageID - 1]; } else if (required) { return null; } } } catch (Exception e) { Log.HandleException(new AException("Error while getting pre-message.", e)); } Log.Debug(() => "Exiting getPreMessage", 7); return message; } private void QueuePlayerForIPInfoFetch(APlayer aPlayer) { Log.Debug(() => "Entering QueuePlayerForIPInfoFetch", 6); try { Log.Debug(() => "Preparing to queue player for IP info fetch.", 6); lock (_IPInfoFetchQueue) { if (_IPInfoFetchQueue.Count() > 70) { //There are more players in the queue than can exist in the server, empty the queue //If players require an info fetch, they will be re-queued by player listing _IPInfoFetchQueue.Clear(); } if (//Player is already in the queue, don't re-queue them _IPInfoFetchQueue.Any(qPlayer => aPlayer.player_id == qPlayer.player_id || aPlayer.player_guid == qPlayer.player_guid) || //Player is marked as not online, don't re-queue them !aPlayer.player_online || //Player is not in the online player dictionary, don't re-queue them !_PlayerDictionary.Values.Any(dPlayer => aPlayer.player_id == dPlayer.player_id || aPlayer.player_guid == dPlayer.player_guid)) { return; } _IPInfoFetchQueue.Enqueue(aPlayer); Log.Debug(() => "Player queued for IP info fetch.", 6); _IPInfoWaitHandle.Set(); } } catch (Exception e) { Log.HandleException(new AException("Error while queuing player for IP info fetch.", e)); } Log.Debug(() => "Exiting QueuePlayerForIPInfoFetch", 6); } public void IPAPICommThreadLoop() { try { Log.Debug(() => "Starting IP API Comm Thread", 1); Thread.CurrentThread.Name = "IPAPIComm"; DateTime loopStart = UtcNow(); while (true) { try { Log.Debug(() => "Entering IP API Comm Thread Loop", 7); if (!_pluginEnabled) { Log.Debug(() => "Detected AdKats not enabled. Exiting thread " + Thread.CurrentThread.Name, 6); break; } //Sleep for 10ms Threading.Wait(10); //Handle Inbound player fetches if (_IPInfoFetchQueue.Count > 0) { Queue unprocessedPlayers; lock (_IPInfoFetchQueue) { Log.Debug(() => "Inbound players found. Grabbing.", 6); //Grab all items in the queue unprocessedPlayers = new Queue(_IPInfoFetchQueue.ToArray()); //Clear the queue for next run _IPInfoFetchQueue.Clear(); } //Loop through all players in order that they came in while (unprocessedPlayers.Count > 0) { if (!_pluginEnabled) { break; } Log.Debug(() => "Preparing to fetch IP API info for player", 6); //Dequeue the record APlayer aPlayer = unprocessedPlayers.Dequeue(); //Run the appropriate action FetchIPLocation(aPlayer); } } else { Log.Debug(() => "No inbound players. Waiting.", 6); //Wait for new actions if ((UtcNow() - loopStart).TotalMilliseconds > 1000) { Log.Debug(() => "Warning. " + Thread.CurrentThread.Name + " thread processing completed in " + ((int)((UtcNow() - loopStart).TotalMilliseconds)) + "ms", 4); } _IPInfoWaitHandle.Reset(); _IPInfoWaitHandle.WaitOne(TimeSpan.FromSeconds(5)); loopStart = UtcNow(); } } catch (Exception e) { if (e is ThreadAbortException) { Log.HandleException(new AException("IP API comm thread aborted. Exiting.")); break; } Log.HandleException(new AException("Error occured in IP API comm thread. Skipping current loop.", e)); } } Log.Debug(() => "Ending IP API Comm Thread", 1); Threading.StopWatchdog(); } catch (Exception e) { Log.HandleException(new AException("Error occured in IP API comm thread.", e)); } } private void QueuePlayerForBattlelogInfoFetch(APlayer aPlayer) { Log.Debug(() => "Entering QueuePlayerForBattlelogInfoFetch", 6); try { Log.Debug(() => "Preparing to queue player for battlelog info fetch.", 6); lock (_BattlelogFetchQueue) { if (!_BattlelogFetchQueue.Contains(aPlayer)) { _BattlelogFetchQueue.Enqueue(aPlayer); Log.Debug(() => "Player queued for battlelog info fetch.", 6); } _BattlelogCommWaitHandle.Set(); } } catch (Exception e) { Log.HandleException(new AException("Error while queuing player for battlelog info fetch.", e)); } Log.Debug(() => "Exiting QueuePlayerForBattlelogInfoFetch", 6); } public void BattlelogCommThreadLoop() { try { Log.Debug(() => "Starting Battlelog Comm Thread", 1); Thread.CurrentThread.Name = "BattlelogComm"; DateTime loopStart = UtcNow(); while (true) { try { Log.Debug(() => "Entering Battlelog Comm Thread Loop", 7); if (!_pluginEnabled) { Log.Debug(() => "Detected AdKats not enabled. Exiting thread " + Thread.CurrentThread.Name, 6); break; } //Sleep for 10ms Threading.Wait(10); if (!_firstPlayerListComplete) { Log.Debug(() => "Playerlist not complete yet. Skipping loop until completed.", 6); continue; } //Handle Inbound player fetches if (_BattlelogFetchQueue.Count > 0) { APlayer aPlayer; lock (_BattlelogFetchQueue) { //Dequeue the record aPlayer = _BattlelogFetchQueue.Dequeue(); } // Skip player if they left the server already while in queue. if (!_PlayerDictionary.ContainsKey(aPlayer.player_name)) { Log.Debug(() => "Player not in PlayerDictionary when fetching battlelog info. Skipping.", 6); continue; } Log.Debug(() => "Preparing to fetch battlelog info for player", 6); //Old Tag String oldTag = aPlayer.player_clanTag; //Run the appropriate action FetchPlayerBattlelogInformation(aPlayer); // Avoid requeueing players if we can't fetch their battlelog infomation. /*if (_PlayerDictionary.ContainsKey(aPlayer.player_name)) { //No info found/error, requeue them for fetching Log.Debug(() => "Battlelog info fetch for " + aPlayer.GetVerboseName() + " failed. Requeueing.", 6); Thread.Sleep(TimeSpan.FromSeconds(1.0)); QueuePlayerForBattlelogInfoFetch(aPlayer); }*/ Log.Debug(() => "Battlelog info fetched for " + aPlayer.GetVerboseName() + ".", 6); //Check for clan tag changes if (!String.IsNullOrEmpty(aPlayer.player_clanTag) && (String.IsNullOrEmpty(oldTag) || aPlayer.player_clanTag != oldTag)) { ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_changetag"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "AdKats", record_message = oldTag, record_time = UtcNow() }; QueueRecordForProcessing(record); var changeMessage = aPlayer.player_name + " changed their tag from " + (String.IsNullOrEmpty(oldTag) ? "NOTHING" : "[" + oldTag + "]") + " to " + (String.IsNullOrEmpty(aPlayer.player_clanTag) ? "NOTHING" : "[" + aPlayer.player_clanTag + "]") + "."; Log.Debug(() => changeMessage + " Updating the database.", 2); if (_ShowPlayerNameChangeAnnouncement && !String.IsNullOrEmpty(oldTag)) { OnlineAdminSayMessage(changeMessage); } UpdatePlayer(aPlayer); } } else { Log.Debug(() => "No inbound players. Waiting.", 6); //Wait for new actions if ((UtcNow() - loopStart).TotalMilliseconds > 1000) { Log.Debug(() => "Warning. " + Thread.CurrentThread.Name + " thread processing completed in " + ((int)((UtcNow() - loopStart).TotalMilliseconds)) + "ms", 4); } _BattlelogCommWaitHandle.Reset(); _BattlelogCommWaitHandle.WaitOne(TimeSpan.FromSeconds(5)); loopStart = UtcNow(); } } catch (Exception e) { if (e is ThreadAbortException) { Log.HandleException(new AException("Battlelog comm thread aborted. Exiting.")); break; } Log.HandleException(new AException("Error occured in Battlelog comm thread. Skipping current loop.", e)); } } Log.Debug(() => "Ending Battlelog Comm Thread", 1); Threading.StopWatchdog(); } catch (Exception e) { Log.HandleException(new AException("Error occured in battlelog comm thread.", e)); } } private void QueueRecordForActionHandling(ARecord record) { Log.Debug(() => "Entering queueRecordForActionHandling", 6); try { Log.Debug(() => "Preparing to queue record for action handling", 6); lock (_UnprocessedActionQueue) { _UnprocessedActionQueue.Enqueue(record); Log.Debug(() => "Record queued for action handling", 6); _ActionHandlingWaitHandle.Set(); } } catch (Exception e) { record.record_exception = new AException("Error while queuing record for action handling.", e); Log.HandleException(record.record_exception); } Log.Debug(() => "Exiting queueRecordForActionHandling", 6); } private void ActionHandlingThreadLoop() { try { Log.Debug(() => "Starting Action Thread", 1); Thread.CurrentThread.Name = "ActionHandling"; DateTime loopStart = UtcNow(); while (true) { try { Log.Debug(() => "Entering Action Thread Loop", 7); if (!_pluginEnabled) { Log.Debug(() => "Detected AdKats not enabled. Exiting thread " + Thread.CurrentThread.Name, 6); break; } //Sleep for 10ms Threading.Wait(10); //Handle Inbound Actions if (_UnprocessedActionQueue.Count > 0) { Queue unprocessedActions; lock (_UnprocessedActionQueue) { Log.Debug(() => "Inbound actions found. Grabbing.", 6); //Grab all messages in the queue unprocessedActions = new Queue(_UnprocessedActionQueue.ToArray()); //Clear the queue for next run _UnprocessedActionQueue.Clear(); } //Loop through all records in order that they came in while (unprocessedActions.Count > 0) { if (!_pluginEnabled) { break; } Log.Debug(() => "Preparing to Run Actions for record", 6); //Dequeue the record ARecord record = unprocessedActions.Dequeue(); //Run the appropriate action RunAction(record); //If more processing is needed, then perform it //If any errors exist in the record, do not re-queue if (record.record_exception == null) { QueueRecordForProcessing(record); } else { Log.Debug(() => "Record has errors, not re-queueing after action.", 3); } } } else { Log.Debug(() => "No inbound actions. Waiting.", 6); //Wait for new actions if ((UtcNow() - loopStart).TotalMilliseconds > 1000) { Log.Debug(() => "Warning. " + Thread.CurrentThread.Name + " thread processing completed in " + ((int)((UtcNow() - loopStart).TotalMilliseconds)) + "ms", 4); } _ActionHandlingWaitHandle.Reset(); _ActionHandlingWaitHandle.WaitOne(TimeSpan.FromSeconds(5)); loopStart = UtcNow(); } } catch (Exception e) { if (e is ThreadAbortException) { Log.HandleException(new AException("Action Handling thread aborted. Exiting.")); break; } Log.HandleException(new AException("Error occured in Action Handling thread. Skipping current loop.", e)); } } Log.Debug(() => "Ending Action Handling Thread", 1); Threading.StopWatchdog(); } catch (Exception e) { Log.HandleException(new AException("Error occured in action handling thread.", e)); } } private void RunAction(ARecord record) { Log.Debug(() => "Entering runAction", 6); try { //Make sure record has an action if (record.command_action == null) { record.command_action = record.command_type; } //Automatic player locking if (!record.record_action_executed && record.target_player != null && record.source_name != record.target_name && (record.source_player == null || PlayerIsAdmin(record.source_player)) && _playerLockingAutomaticLock && !record.target_player.IsLocked()) { record.target_player.Lock(record.source_name, TimeSpan.FromMinutes(_playerLockingAutomaticDuration)); } //Perform Actions switch (record.command_action.command_key) { case "player_move": MoveTarget(record); break; case "player_fmove": ForceMoveTarget(record); break; case "self_teamswap": ForceMoveTarget(record); break; case "self_assist": case "self_assist_unconfirmed": AssistWeakTeam(record); break; case "player_debugassist": DubugAssistWeakTeam(record); break; case "self_kill": ForceKillTarget(record); break; case "player_kill": KillTarget(record); break; case "player_kill_force": ForceKillTarget(record); break; case "player_warn": WarnTarget(record); break; case "player_kill_lowpop": KillTarget(record); break; case "player_kill_repeat": KillTarget(record); break; case "player_kick": KickTarget(record); break; case "player_ban_temp": TempBanTarget(record); break; case "player_ban_perm": PermaBanTarget(record); break; case "player_ban_perm_future": FuturePermaBanTarget(record); break; case "player_unban": UnBanTarget(record); break; case "player_punish": PunishTarget(record); break; case "player_forgive": ForgiveTarget(record); break; case "player_mute": MuteTarget(record); break; case "player_join": JoinTarget(record); break; case "player_pull": PullTarget(record); break; case "player_report": ReportTarget(record); break; case "player_calladmin": CallAdminOnTarget(record); break; case "player_info": SendTargetInfo(record); break; case "player_perks": SendTargetPerks(record); break; case "poll_trigger": TriggerTargetPoll(record); break; case "player_chat": SendTargetChat(record); break; case "player_find": FindTarget(record); break; case "player_lock": LockTarget(record); break; case "player_unlock": UnlockTarget(record); break; case "player_mark": MarkTarget(record); break; case "player_loadout": LoadoutFetchTarget(record); break; case "player_loadout_force": LoadoutForceTarget(record); break; case "player_loadout_ignore": LoadoutIgnoreTarget(record); break; case "player_ping": PingFetchTarget(record); break; case "player_forceping": ForcePingTarget(record); break; case "server_afk": ManageAFKPlayers(record); break; case "round_restart": RestartLevel(record); break; case "round_next": NextLevel(record); break; case "round_end": EndLevel(record); break; case "server_nuke": NukeTarget(record); break; case "server_countdown": CountdownTarget(record); break; case "server_kickall": KickAllPlayers(record); break; case "server_swapnuke": SwapNukeServer(record); break; case "admin_say": AdminSay(record); break; case "player_say": PlayerSay(record); break; case "admin_yell": AdminYell(record); break; case "player_yell": PlayerYell(record); break; case "admin_tell": AdminTell(record); break; case "player_tell": PlayerTell(record); break; case "player_pm_send": PMSendTarget(record); break; case "player_pm_reply": PMReplyTarget(record); break; case "player_pm_start": PMStartTarget(record); break; case "player_pm_cancel": PMCancelTarget(record); break; case "player_pm_transmit": PMTransmitTarget(record); break; case "admin_pm_send": PMOnlineAdmins(record); break; case "player_dequeue": DequeueTarget(record); break; case "player_blacklistdisperse": BalanceDisperseTarget(record); break; case "player_whitelistbalance": BalanceWhitelistTarget(record); break; case "player_slotreserved": ReservedSlotTarget(record); break; case "player_slotspectator": SpectatorSlotTarget(record); break; case "player_whitelistanticheat": AntiCheatWhitelistTarget(record); break; case "player_whitelistping": PingWhitelistTarget(record); break; case "player_whitelistaa": AAWhitelistTarget(record); break; case "player_whitelistreport": ReportWhitelistTarget(record); break; case "player_whitelistspambot": SpamBotWhitelistTarget(record); break; case "player_whitelistspambot_remove": SpamBotWhitelistRemoveTarget(record); break; case "player_blacklistspectator": SpectatorBlacklistTarget(record); break; case "player_blacklistspectator_remove": SpectatorBlacklistRemoveTarget(record); break; case "player_blacklistreport": ReportSourceBlacklistTarget(record); break; case "player_blacklistreport_remove": ReportSourceBlacklistRemoveTarget(record); break; case "player_challenge_play": ChallengePlayTarget(record); break; case "player_challenge_play_remove": ChallengePlayRemoveTarget(record); break; case "player_challenge_ignore": ChallengeIgnoreTarget(record); break; case "player_challenge_ignore_remove": ChallengeIgnoreRemoveTarget(record); break; case "player_challenge_autokill": ChallengeAutoKillTarget(record); break; case "player_challenge_autokill_remove": ChallengeAutoKillRemoveTarget(record); break; case "player_whitelistcommand": CommandTargetWhitelistTarget(record); break; case "player_whitelistcommand_remove": CommandTargetWhitelistRemoveTarget(record); break; case "player_blacklistautoassist": AutoAssistBlacklistTarget(record); break; case "player_blacklistautoassist_remove": AutoAssistBlacklistRemoveTarget(record); break; case "player_whitelistreport_remove": ReportWhitelistRemoveTarget(record); break; case "player_whitelistaa_remove": AAWhitelistRemoveTarget(record); break; case "player_whitelistping_remove": PingWhitelistRemoveTarget(record); break; case "player_whitelistanticheat_remove": AntiCheatWhitelistRemoveTarget(record); break; case "player_slotspectator_remove": SpectatorSlotRemoveTarget(record); break; case "player_slotreserved_remove": ReservedSlotRemoveTarget(record); break; case "player_whitelistbalance_remove": BalanceWhitelistRemoveTarget(record); break; case "player_blacklistdisperse_remove": BalanceDisperseRemoveTarget(record); break; case "player_whitelistpopulator": PopulatorWhitelistTarget(record); break; case "player_whitelistpopulator_remove": PopulatorWhitelistRemoveTarget(record); break; case "player_whitelistteamkill": TeamKillTrackerWhitelistTarget(record); break; case "player_whitelistteamkill_remove": TeamKillTrackerWhitelistRemoveTarget(record); break; case "player_log": SendMessageToSource(record, "Log saved for " + record.GetTargetNames()); break; case "self_feedback": SendMessageToSource(record, "Feedback saved."); break; case "player_population_success": SendPopulationSuccess(record); break; case "self_rules": SendServerRules(record); break; case "self_surrender": SourceVoteSurrender(record); break; case "self_nosurrender": SourceVoteNoSurrender(record); break; case "self_votenext": SourceVoteSurrender(record); break; case "self_help": SendServerCommands(record); break; case "self_rep": SendTargetRep(record); break; case "player_isadmin": SendTargetIsAdmin(record); break; case "self_uptime": SendUptime(record); break; case "self_admins": SendOnlineAdmins(record); break; case "self_lead": LeadCurrentSquad(record); break; case "self_reportlist": SendPlayerReports(record); break; case "plugin_restart": RebootPlugin(record); break; case "plugin_update": UpdatePlugin(record); break; case "server_shutdown": ShutdownServer(record); break; case "adkats_exception": record.record_action_executed = true; break; case "self_battlecry": case "player_battlecry": UpdatePlayerBattlecry(record); break; case "player_discordlink": UpdatePlayerDiscordLink(record); break; case "player_blacklistallcaps": AllCapsBlacklistTarget(record); break; case "player_blacklistallcaps_remove": AllCapsBlacklistRemoveTarget(record); break; case "self_challenge": SendChallengeInfo(record); break; case "player_changename": case "player_changetag": case "player_changeip": case "player_challenge_complete": case "admin_accept": case "admin_deny": case "admin_ignore": case "self_contest": case "banenforcer_enforce": case "player_repboost": case "server_map_detriment": case "server_map_benefit": case "poll_vote": case "poll_cancel": case "poll_complete": record.record_action_executed = true; //Don't do anything here break; default: record.record_action_executed = true; SendMessageToSource(record, "Command not recognized when running " + record.command_action.command_key + " action."); record.record_exception = Log.HandleException(new AException("Command " + record.command_action + " not found in runAction. Record ID " + record.record_id)); FinalizeRecord(record); break; } Log.Debug(() => record.command_type.command_key + " last used " + FormatTimeString(UtcNow() - _commandUsageTimes[record.command_type.command_key], 10) + " ago.", 3); _commandUsageTimes[record.command_type.command_key] = UtcNow(); } catch (Exception e) { record.record_exception = Log.HandleException(new AException("Error while choosing action for record.", e)); } Log.Debug(() => "Exiting runAction", 6); } public void MoveTarget(ARecord record) { Log.Debug(() => "Entering moveTarget", 6); try { record.record_action_executed = true; if (GameVersion != GameVersionEnum.BF3 && !record.isAliveChecked) { if (!_ActOnIsAliveDictionary.ContainsKey(record.target_player.player_name)) { lock (_ActOnIsAliveDictionary) { _ActOnIsAliveDictionary.Add(record.target_player.player_name, record); } } ExecuteCommand("procon.protected.send", "player.isAlive", record.target_name); return; } QueuePlayerForMove(record.target_player.fbpInfo); record.target_player.Say("On your next death you will be moved to the opposing team."); SendMessageToSource(record, Log.CViolet(record.GetTargetNames() + " will be sent to TeamSwap on their next death.")); } catch (Exception e) { record.record_exception = new AException("Error while taking action for move record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting moveTarget", 6); } public void ForceMoveTarget(ARecord record) { Log.Debug(() => "Entering forceMoveTarget", 6); String message = null; try { record.record_action_executed = true; if (record.command_type == GetCommandByKey("self_teamswap")) { if ((record.source_player != null && HasAccess(record.source_player, GetCommandByKey("self_teamswap"))) || ((_TeamSwapTicketWindowHigh >= _highestTicketCount) && (_TeamSwapTicketWindowLow <= _lowestTicketCount))) { message = "Calling Teamswap on self"; Log.Debug(() => message, 6); QueuePlayerForForceMove(record.target_player.fbpInfo); } else { message = "Player unable to TeamSwap"; Log.Debug(() => message, 6); SendMessageToSource(record, "You cannot TeamSwap at this time. Game outside ticket window [" + _TeamSwapTicketWindowLow + ", " + _TeamSwapTicketWindowHigh + "]."); } } else { message = "TeamSwap called on " + record.GetTargetNames(); Log.Debug(() => "Calling Teamswap on target", 6); SendMessageToSource(record, Log.CViolet(record.GetTargetNames() + " sent to TeamSwap.")); QueuePlayerForForceMove(record.target_player.fbpInfo); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for force-move/teamswap record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting forceMoveTarget", 6); } public void AssistWeakTeam(ARecord record) { Log.Debug(() => "Entering AssistLosingTeam", 6); try { record.record_action_executed = true; if (record.source_name == record.target_name) { _roundAssists[record.target_player.player_name] = record; } QueuePlayerForForceMove(record.target_player.fbpInfo); } catch (Exception e) { record.record_exception = new AException("Error while taking action for assist record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting AssistLosingTeam", 6); } public void DubugAssistWeakTeam(ARecord record) { Log.Debug(() => "Entering DubugAssistWeakTeam", 6); try { record.record_action_executed = true; RunAssist(record.target_player, null, record, false); } catch (Exception e) { record.record_exception = new AException("Error while taking action for debug assist record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting DubugAssistWeakTeam", 6); } public void KillTarget(ARecord record) { Log.Debug(() => "Entering killTarget", 6); try { record.record_action_executed = true; if (record.source_name != record.target_name) { switch (GameVersion) { case GameVersionEnum.BF3: if (record.command_type.command_key == "player_punish") { if (record.source_name == "AutoAdmin" || record.source_name == "ProconAdmin") { AdminSayMessage(Log.FBold(Log.CRed("Punishing " + record.GetTargetNames() + " for " + record.record_message))); } else { AdminSayMessage(Log.FBold(Log.CRed(record.GetTargetNames() + " PUNISHED" + (_ShowAdminNameInAnnouncement ? (" by " + record.GetSourceName()) : ("")) + " for " + record.record_message))); } } else if (record.source_name != "PlayerMuteSystem") { AdminSayMessage(Log.FBold(Log.CRed(record.GetTargetNames() + " KILLED" + (_ShowAdminNameInAnnouncement ? (" by " + record.GetSourceName()) : ("")) + " for " + record.record_message))); } int seconds = (int)UtcNow().Subtract(record.target_player.lastDeath).TotalSeconds; Log.Debug(() => "Killing player. Player last died " + seconds + " seconds ago.", 3); if (seconds < 6 && record.command_action.command_key != "player_kill_repeat") { Log.Debug(() => "Queueing player for kill on spawn. (" + seconds + ")&(" + record.command_action + ")", 3); if (!_ActOnSpawnDictionary.ContainsKey(record.target_player.player_name)) { lock (_ActOnSpawnDictionary) { record.command_action = GetCommandByKey("player_kill_repeat"); _ActOnSpawnDictionary.Add(record.target_player.player_name, record); } } } break; case GameVersionEnum.BF4: case GameVersionEnum.BFHL: if (!record.isAliveChecked) { if (record.command_type.command_key == "player_punish") { if (record.source_name == "AutoAdmin" || record.source_name == "ProconAdmin") { AdminSayMessage(Log.FBold(Log.CRed("Punishing " + record.GetTargetNames() + " for " + record.record_message))); } else { AdminSayMessage(Log.FBold(Log.CRed(record.GetTargetNames() + " PUNISHED" + (_ShowAdminNameInAnnouncement ? (" by " + record.GetSourceName()) : ("")) + " for " + record.record_message))); } } else if (record.source_name != "PlayerMuteSystem") { AdminSayMessage(Log.FBold(Log.CRed(record.GetTargetNames() + " KILLED" + (_ShowAdminNameInAnnouncement ? (" by " + record.GetSourceName()) : ("")) + " for " + record.record_message))); } if (!_ActOnIsAliveDictionary.ContainsKey(record.target_player.player_name)) { lock (_ActOnIsAliveDictionary) { _ActOnIsAliveDictionary.Add(record.target_player.player_name, record); } } ExecuteCommand("procon.protected.send", "player.isAlive", record.target_name); return; } break; default: Log.Error("Invalid game version in killtarget"); return; } } //Perform actions if (String.IsNullOrEmpty(record.target_player.player_name)) { Log.Error("playername null in 5437"); } else { ExecuteCommand("procon.protected.send", "admin.killPlayer", record.target_player.player_name); if (record.source_name != record.target_name || record.command_type.command_key == "player_punish") { PlayerTellMessage(record.target_name, "KILLED by " + (record.source_name == "AutoAdmin" ? "AutoAdmin" : "admin") + " for " + record.record_message); SendMessageToSource(record, "You KILLED " + record.GetTargetNames() + " for " + record.record_message); } else { PlayerTellMessage(record.target_name, "You killed yourself"); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for kill record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting killTarget", 6); } public void ForceKillTarget(ARecord record) { Log.Debug(() => "Entering ForceKillTarget", 6); try { record.record_action_executed = true; //Perform actions if (String.IsNullOrEmpty(record.target_player.player_name)) { Log.Error("playername null in 14491"); } else { ExecuteCommand("procon.protected.send", "admin.killPlayer", record.target_player.player_name); if (record.source_name != record.target_name) { PlayerTellMessage(record.target_name, "KILLED by " + (record.source_name == "AutoAdmin" ? "AutoAdmin" : "admin") + " for " + record.record_message); SendMessageToSource(record, "You KILLED " + record.GetTargetNames() + " for " + record.record_message); } else { PlayerTellMessage(record.target_name, "You killed yourself"); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for kill record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting ForceKillTarget", 6); } public void WarnTarget(ARecord record) { Log.Debug(() => "Entering WarnTarget", 6); try { record.record_action_executed = true; //Perform actions if (String.IsNullOrEmpty(record.target_player.player_name)) { Log.Error("playername null in 14526"); } else { if (record.record_source != ARecord.Sources.InGame && record.record_source != ARecord.Sources.Automated && record.record_source != ARecord.Sources.ServerCommand) { SendMessageToSource(record, "You WARNED " + record.GetTargetNames() + " for " + record.record_message); } AdminSayMessage(Log.FBold(Log.CRed(record.GetTargetNames() + " WARNED" + (_ShowAdminNameInAnnouncement ? (" by " + record.GetSourceName()) : ("")) + " for " + record.record_message))); PlayerTellMessage(record.target_name, "Warned for " + record.record_message, true, 3); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for warn record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting WarnTarget", 6); } public void DequeueTarget(ARecord record) { Log.Debug(() => "Entering DequeueTarget", 6); try { record.record_action_executed = true; if (record.target_player != null) { DequeuePlayer(record.target_player); record.target_player.Say("All queued actions canceled."); SendMessageToSource(record, "All queued actions for " + record.GetTargetNames() + " canceled."); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for dequeue record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting DequeueTarget", 6); } public void DequeuePlayer(APlayer aPlayer) { Log.Debug(() => "Entering DequeuePlayer", 6); try { //Handle spawn action if (_ActOnSpawnDictionary.ContainsKey(aPlayer.player_name)) { _ActOnSpawnDictionary.Remove(aPlayer.player_name); } //Handle teamswap action lock (_Team1MoveQueue) { CPlayerInfo info = _Team1MoveQueue.FirstOrDefault(playerInfo => playerInfo.SoldierName == aPlayer.player_name); if (info != null) { _Team1MoveQueue = new Queue(_Team1MoveQueue.Where(p => p != info)); } } lock (_Team2MoveQueue) { CPlayerInfo info = _Team2MoveQueue.FirstOrDefault(playerInfo => playerInfo.SoldierName == aPlayer.player_name); if (info != null) { _Team2MoveQueue = new Queue(_Team2MoveQueue.Where(p => p != info)); } } lock (_TeamswapForceMoveQueue) { CPlayerInfo info = _TeamswapForceMoveQueue.FirstOrDefault(playerInfo => playerInfo.SoldierName == aPlayer.player_name); if (info != null) { _TeamswapForceMoveQueue = new Queue(_TeamswapForceMoveQueue.Where(p => p != info)); } } lock (_TeamswapOnDeathCheckingQueue) { CPlayerInfo info = _TeamswapOnDeathCheckingQueue.FirstOrDefault(playerInfo => playerInfo.SoldierName == aPlayer.player_name); if (info != null) { _TeamswapOnDeathCheckingQueue = new Queue(_TeamswapOnDeathCheckingQueue.Where(p => p != info)); } } lock (_AssistAttemptQueue) { var record = _AssistAttemptQueue.FirstOrDefault(dRecord => dRecord.target_name == aPlayer.player_name); if (record != null) { _AssistAttemptQueue = new Queue(_AssistAttemptQueue.Where(p => p != record)); } } if (_TeamswapOnDeathMoveDic.ContainsKey(aPlayer.player_name)) { _TeamswapOnDeathMoveDic.Remove(aPlayer.player_name); } } catch (Exception e) { Log.HandleException(new AException("Error while dequeuing player.", e)); } Log.Debug(() => "Exiting DequeuePlayer", 6); } public void KickTarget(ARecord record) { Log.Debug(() => "Entering kickTarget", 6); try { record.record_action_executed = true; String kickReason = GenerateKickReason(record); //Perform Actions Log.Debug(() => "Kick '" + kickReason + "'", 3); if (String.IsNullOrEmpty(record.target_player.player_name) || String.IsNullOrEmpty(kickReason)) { Log.Error("Item null in 5464"); } else { if (record.target_name != record.source_name) { if (record.source_name == "PingEnforcer") { AdminSayMessage(Log.FBold(Log.CRed(record.GetTargetNames() + " KICKED for " + ((record.target_player.player_ping_avg > 0) ? (Math.Round(record.target_player.player_ping) + "ms ping. Avg:" + Math.Round(record.target_player.player_ping_avg) + "ms") : ("missing ping."))))); } else if (record.source_name != "AFKManager" && record.source_name != "SpectatorManager") { AdminSayMessage(Log.FBold(Log.CRed(record.GetTargetNames() + " KICKED" + (_ShowAdminNameInAnnouncement ? (" by " + record.GetSourceName()) : ("")) + " for " + record.record_message))); } } if (record.target_player.fbpInfo != null) { SendMessageToSource(record, "You KICKED " + record.GetTargetNames() + " from " + GetPlayerTeamName(record.target_player) + " for " + record.record_message); } else { SendMessageToSource(record, "You KICKED " + record.GetTargetNames() + " for " + record.record_message); } if (record.target_name != record.source_name) { KickPlayerMessage(record.target_player, kickReason); } else { // Don't have any delay if the kick is self targeted KickPlayerMessage(record.target_player.player_name, kickReason, 0); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for kick record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting kickTarget", 6); } public void TempBanTarget(ARecord record) { Log.Debug(() => "Entering tempBanTarget", 6); try { record.record_action_executed = true; //Subtract 1 second for visual effect Int32 seconds = (record.command_numeric * 60) - 1; //Perform Actions //Only post to ban enforcer if there are no exceptions if (_UseBanEnforcer && record.record_exception == null) { //Update the ban enforcement depending on available information Boolean nameAvailable = !String.IsNullOrEmpty(record.target_player.player_name); Boolean guidAvailable = !String.IsNullOrEmpty(record.target_player.player_guid); Boolean ipAvailable = !String.IsNullOrEmpty(record.target_player.player_ip); //Create the ban ABan aBan = new ABan { ban_record = record, ban_enforceName = nameAvailable && (_DefaultEnforceName || (!guidAvailable && !ipAvailable)), ban_enforceGUID = guidAvailable && (_DefaultEnforceGUID || (!nameAvailable && !ipAvailable)), ban_enforceIP = ipAvailable && (_DefaultEnforceIP || (!nameAvailable && !guidAvailable)) }; //Queue the ban for upload QueueBanForProcessing(aBan); } else { if (record.record_exception != null) { Log.HandleException(new AException("Defaulting to procon banlist usage since exceptions existed in record")); } //Trim the ban message if necessary String banMessage = record.record_message + " [" + record.source_name + "]"; Int32 cutLength = banMessage.Length - 80; if (cutLength > 0) { banMessage = banMessage.Substring(0, banMessage.Length - cutLength); } Log.Debug(() => "Ban '" + banMessage + "'", 3); if (!String.IsNullOrEmpty(record.target_player.player_guid)) { ExecuteCommand("procon.protected.send", "banList.add", "guid", record.target_player.player_guid, "seconds", seconds.ToString(), banMessage); ExecuteCommand("procon.protected.send", "banList.save"); ExecuteCommand("procon.protected.send", "banList.list"); } else if (!String.IsNullOrEmpty(record.target_player.player_ip)) { ExecuteCommand("procon.protected.send", "banList.add", "ip", record.target_player.player_ip, "seconds", seconds.ToString(), banMessage); ExecuteCommand("procon.protected.send", "banList.save"); ExecuteCommand("procon.protected.send", "banList.list"); } else if (!String.IsNullOrEmpty(record.target_player.player_name)) { ExecuteCommand("procon.protected.send", "banList.add", "id", record.target_player.player_name, "seconds", seconds.ToString(), banMessage); ExecuteCommand("procon.protected.send", "banList.save"); ExecuteCommand("procon.protected.send", "banList.list"); } else { Log.Error("Player has no information to ban with."); SendMessageToSource(record, "ERROR"); } } if (record.target_name != record.source_name) { AdminSayMessage(Log.FBold(Log.CRed(record.GetTargetNames() + " BANNED" + (_ShowAdminNameInAnnouncement ? (" by " + record.GetSourceName()) : ("")) + " for " + record.record_message))); } SendMessageToSource(record, "You TEMP BANNED " + record.GetTargetNames() + " for " + FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 3)); } catch (Exception e) { record.record_exception = new AException("Error while taking action for TempBan record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting tempBanTarget", 6); } public void PermaBanTarget(ARecord record) { Log.Debug(() => "Entering permaBanTarget", 6); try { record.record_action_executed = true; //Perform Actions //Only post to ban enforcer if there are no exceptions if (_UseBanEnforcer && record.record_exception == null) { //Update the ban enforcement depending on available information Boolean nameAvailable = !String.IsNullOrEmpty(record.target_player.player_name); Boolean guidAvailable = !String.IsNullOrEmpty(record.target_player.player_guid); Boolean ipAvailable = !String.IsNullOrEmpty(record.target_player.player_ip); //Create the ban ABan aBan = new ABan { ban_record = record, ban_enforceName = nameAvailable && (_DefaultEnforceName || (!guidAvailable && !ipAvailable)), ban_enforceGUID = guidAvailable && (_DefaultEnforceGUID || (!nameAvailable && !ipAvailable)), ban_enforceIP = ipAvailable && (_DefaultEnforceIP || (!nameAvailable && !guidAvailable)) }; //Queue the ban for upload QueueBanForProcessing(aBan); } else { if (record.record_exception != null) { Log.HandleException(new AException("Defaulting to procon banlist usage since exceptions existed in record")); } //Trim the ban message if necessary String banMessage = record.record_message + " [" + record.source_name + "]"; Int32 cutLength = banMessage.Length - 80; if (cutLength > 0) { banMessage = banMessage.Substring(0, banMessage.Length - cutLength); } Log.Debug(() => "Ban '" + banMessage + "'", 3); if (!String.IsNullOrEmpty(record.target_player.player_guid)) { ExecuteCommand("procon.protected.send", "banList.add", "guid", record.target_player.player_guid, "perm", banMessage); ExecuteCommand("procon.protected.send", "banList.save"); ExecuteCommand("procon.protected.send", "banList.list"); } else if (!String.IsNullOrEmpty(record.target_player.player_ip)) { ExecuteCommand("procon.protected.send", "banList.add", "ip", record.target_player.player_ip, "perm", banMessage); ExecuteCommand("procon.protected.send", "banList.save"); ExecuteCommand("procon.protected.send", "banList.list"); } else if (!String.IsNullOrEmpty(record.target_player.player_name)) { ExecuteCommand("procon.protected.send", "banList.add", "id", record.target_player.player_name, "perm", banMessage); ExecuteCommand("procon.protected.send", "banList.save"); ExecuteCommand("procon.protected.send", "banList.list"); } else { Log.Error("Player has no information to ban with."); SendMessageToSource(record, "ERROR"); } } if (record.target_name != record.source_name) { AdminSayMessage(Log.FBold(Log.CRed(record.GetTargetNames() + " BANNED" + (_ShowAdminNameInAnnouncement ? (" by " + record.GetSourceName()) : ("")) + " for " + record.record_message))); } SendMessageToSource(record, "You PERMA BANNED " + record.GetTargetNames()); } catch (Exception e) { record.record_exception = new AException("Error while taking action for PermaBan record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting permaBanTarget", 6); } public void FuturePermaBanTarget(ARecord record) { Log.Debug(() => "Entering permaBanTarget", 6); try { record.record_action_executed = true; if (_UseBanEnforcer && record.record_exception == null) { Boolean nameAvailable = !String.IsNullOrEmpty(record.target_player.player_name); Boolean guidAvailable = !String.IsNullOrEmpty(record.target_player.player_guid); Boolean ipAvailable = !String.IsNullOrEmpty(record.target_player.player_ip); ABan aBan = new ABan { ban_record = record, ban_enforceName = nameAvailable && (_DefaultEnforceName || (!guidAvailable && !ipAvailable)), ban_enforceGUID = guidAvailable && (_DefaultEnforceGUID || (!nameAvailable && !ipAvailable)), ban_enforceIP = ipAvailable && (_DefaultEnforceIP || (!nameAvailable && !guidAvailable)) }; QueueBanForProcessing(aBan); DateTime endTime = record.record_time + TimeSpan.FromMinutes(record.command_numeric); SendMessageToSource(record, "You FUTURE BANNED " + record.GetTargetNames() + ". Their ban will activate at " + endTime + " UTC."); } else { SendMessageToSource(record, "Future ban cannot be posted."); FinalizeRecord(record); return; } } catch (Exception e) { record.record_exception = new AException("Error while taking action for PermaBan record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting permaBanTarget", 6); } public void UnBanTarget(ARecord record) { Log.Debug(() => "Entering UnBanTarget", 6); try { record.record_action_executed = true; //Cancel call if not using ban enforcer if (!_UseBanEnforcer || !_UseBanEnforcerPreviousState) { Log.Error("Attempted to issue unban when ban enforcer is disabled."); return; } if (record.target_player == null) { Log.Error("Player was null when attempting to unban."); FinalizeRecord(record); return; } List banList = FetchPlayerBans(record.target_player); if (banList.Count == 0) { FinalizeRecord(record); return; } foreach (ABan aBan in banList) { aBan.ban_status = "Disabled"; UpdateBanStatus(aBan); if (aBan.ban_record.command_action.command_key == "player_ban_perm" || aBan.ban_record.command_action.command_key == "player_ban_perm_future") { aBan.ban_record.command_action = GetCommandByKey("player_ban_perm_old"); } else if (aBan.ban_record.command_action.command_key == "player_ban_temp") { aBan.ban_record.command_action = GetCommandByKey("player_ban_temp_old"); } UpdateRecord(aBan.ban_record); } SendMessageToSource(record, record.GetTargetNames() + " is now unbanned."); } catch (Exception e) { record.record_exception = new AException("Error while taking action for UnBan record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting UnBanTarget", 6); } public void EnforceBan(ABan aBan, Boolean verbose) { Log.Debug(() => "Entering enforceBan", 6); try { //Create the total kick message String generatedBanReason = GenerateBanReason(aBan); Log.Debug(() => "Ban Enforce '" + generatedBanReason + "'", 3); //Perform Actions aBan.ban_record.target_player.BanEnforceCount++; if (aBan.ban_record.target_name != aBan.ban_record.source_name) { BanKickPlayerMessage(aBan.ban_record.target_player, generatedBanReason); } else { // Don't have any delay if the ban is self targeted BanKickPlayerMessage(aBan.ban_record.target_player, generatedBanReason, 0); } if (_PlayerDictionary.ContainsKey(aBan.ban_record.target_player.player_name) && aBan.ban_startTime < UtcNow()) { //Inform the server of the enforced ban if (verbose) { String banDurationString; //If ban time > 1000 days just say perm TimeSpan remainingTime = GetRemainingBanTime(aBan); TimeSpan totalTime = aBan.ban_endTime.Subtract(aBan.ban_startTime); if (remainingTime.TotalDays > 500.0) { banDurationString = "permanent"; } else { banDurationString = FormatTimeString(totalTime, 2) + " (" + FormatTimeString(remainingTime, 2) + ")"; } AdminSayMessage(Log.FBold(Log.CRed("Enforcing " + banDurationString + " ban on " + aBan.ban_record.GetTargetNames() + " for " + aBan.ban_record.record_message))); } } } catch (Exception e) { aBan.ban_exception = new AException("Error while enforcing ban.", e); Log.HandleException(aBan.ban_exception); } Log.Debug(() => "Exiting enforceBan", 6); } public void PunishTarget(ARecord record) { Log.Debug(() => "Entering PunishTarget", 6); try { record.record_action_executed = true; //If the record has any exceptions, skip everything else and just kill the player if (record.record_exception == null) { //Get number of points the player from server Int32 points = FetchPoints(record.target_player, false, true); Log.Debug(() => record.GetTargetNames() + " has " + points + " points.", 5); //Get the proper action to take for player punishment String action = "noaction"; String skippedAction = null; if (points > (_PunishmentHierarchy.Length - 1)) { action = _PunishmentHierarchy[_PunishmentHierarchy.Length - 1]; } else if (points > 1) { action = _PunishmentHierarchy[points - 1]; if (record.isIRO) { skippedAction = _PunishmentHierarchy[points - 2]; } } else { action = _PunishmentHierarchy[0]; } //Handle the case where and IRO punish skips higher level punishment for a lower one, use the higher one if (skippedAction != null && _PunishmentSeverityIndex.IndexOf(skippedAction) > _PunishmentSeverityIndex.IndexOf(action)) { action = skippedAction; } //Set additional message String pointMessage = " [" + ((record.isIRO) ? ("IRO ") : ("")) + points + "pts]"; if (!record.record_message.Contains(pointMessage)) { record.record_message += pointMessage; } Boolean isLowPop = _OnlyKillOnLowPop && (GetPlayerCount() < _highPopulationPlayerCount); Boolean iroOverride = record.isIRO && _IROOverridesLowPop && points >= _IROOverridesLowPopInfractions; Log.Debug(() => "Server low population: " + isLowPop + " (" + GetPlayerCount() + " client.ClientName == "AdKatsLRT" && client.SubscriptionEnabled)) { ExecuteCommand("procon.protected.plugins.call", "AdKatsLRT", "CallLoadoutCheckOnPlayer", "AdKats", JSON.JsonEncode(new Hashtable { {"caller_identity", "AdKats"}, {"response_requested", false}, {"player_name", record.target_player.player_name}, {"loadoutCheck_reason", "punished"} })); } KillTarget(record); } else if (action == "kick") { record.command_action = GetCommandByKey("player_kick"); KickTarget(record); } else if (action == "tban60") { record.command_numeric = 60; record.command_action = GetCommandByKey("player_ban_temp"); TempBanTarget(record); } else if (action == "tban120") { record.command_numeric = 120; record.command_action = GetCommandByKey("player_ban_temp"); TempBanTarget(record); } else if (action == "tbanday") { record.command_numeric = 1440; record.command_action = GetCommandByKey("player_ban_temp"); TempBanTarget(record); } else if (action == "tban2days") { record.command_numeric = 2880; record.command_action = GetCommandByKey("player_ban_temp"); TempBanTarget(record); } else if (action == "tban3days") { record.command_numeric = 4320; record.command_action = GetCommandByKey("player_ban_temp"); TempBanTarget(record); } else if (action == "tbanweek") { record.command_numeric = 10080; record.command_action = GetCommandByKey("player_ban_temp"); TempBanTarget(record); } else if (action == "tban2weeks") { record.command_numeric = 20160; record.command_action = GetCommandByKey("player_ban_temp"); TempBanTarget(record); } else if (action == "tbanmonth") { record.command_numeric = 43200; record.command_action = GetCommandByKey("player_ban_temp"); TempBanTarget(record); } else if (action == "ban") { record.command_action = GetCommandByKey("player_ban_perm"); PermaBanTarget(record); } else { record.command_action = GetCommandByKey("player_kill"); if (_subscribedClients.Any(client => client.ClientName == "AdKatsLRT" && client.SubscriptionEnabled) && record.target_player != null && record.target_player.player_reputation <= 0 && record.target_player.player_online) { ExecuteCommand("procon.protected.plugins.call", "AdKatsLRT", "CallLoadoutCheckOnPlayer", "AdKats", JSON.JsonEncode(new Hashtable { {"caller_identity", "AdKats"}, {"response_requested", false}, {"player_name", record.target_player.player_name}, {"loadoutCheck_reason", "punished"} })); } KillTarget(record); record.record_exception = new AException("Punish options are set incorrectly. '" + action + "' not found. Inform plugin setting manager."); Log.HandleException(record.record_exception); } record.target_player.LastPunishment = record; } else { //Exception found, just kill the player record.command_action = GetCommandByKey("player_kill"); KillTarget(record); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Punish record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting PunishTarget", 6); } public void ForgiveTarget(ARecord record) { Log.Debug(() => "Entering forgiveTarget", 6); try { record.record_action_executed = true; //If the record has any exceptions, skip everything if (record.record_exception == null) { Int32 points = FetchPoints(record.target_player, false, true); PlayerSayMessage(record.target_player.player_name, Log.CGreen("Forgiven 1 infraction point. You now have " + points + " point(s) against you.")); SendMessageToSource(record, "Forgive Logged for " + record.GetTargetNames() + ". They now have " + points + " infraction points."); record.target_player.LastForgive = record; } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Forgive record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting forgiveTarget", 6); } public void BalanceDisperseTarget(ARecord record) { Log.Debug(() => "Entering DisperseTarget", 6); try { record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_specialplayers` WHERE `player_group` = @player_group AND (`player_id` = @player_id OR `player_identifier` = @player_name); INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_server`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( @player_group, @player_id, @player_server, @player_identifier, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_group", "blacklist_dispersion"); command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_server", _serverInfo.ServerID); command.Parameters.AddWithValue("@player_identifier", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " autobalance dispersion."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to even dispersion. Error uploading."); } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Disperse record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting DisperseTarget", 6); } public void AllCapsBlacklistTarget(ARecord record) { Log.Debug(() => "Entering AllCapsBlacklistTarget", 6); try { record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_specialplayers` WHERE `player_group` = @player_group AND (`player_id` = @player_id OR `player_identifier` = @player_name); INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_server`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( @player_group, @player_id, @player_server, @player_identifier, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_group", "blacklist_allcaps"); command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_server", _serverInfo.ServerID); command.Parameters.AddWithValue("@player_identifier", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " all-caps chat blacklist."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to all-caps chat blacklist. Error uploading."); } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for all-caps chat blacklist record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting AllCapsBlacklistTarget", 6); } public void BalanceWhitelistTarget(ARecord record) { Log.Debug(() => "Entering BalanceWhitelistTarget", 6); try { record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_specialplayers` WHERE `player_group` = @player_group AND (`player_id` = @player_id OR `player_identifier` = @player_name); INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_server`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( @player_group, @player_id, @player_server, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_group", "whitelist_multibalancer"); command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_server", _serverInfo.ServerID); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " autobalance whitelist."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to autobalance whitelist. Error uploading."); } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Balance Whitelist record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting BalanceWhitelistTarget", 6); } public void ReservedSlotTarget(ARecord record) { Log.Debug(() => "Entering ReservedSlotTarget", 6); try { record.record_action_executed = true; // Thanks XTheLoneShadowX for this idea if (!_FeedServerReservedSlots && _FeedServerReservedSlots_VSM) { var commandString = "/vsm-addvip " + record.target_player.player_name + " +" + Math.Round(TimeSpan.FromMinutes(record.command_numeric).TotalDays); AdminSayMessage(commandString); FetchAllAccess(true); } else { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_specialplayers` WHERE `player_group` = @player_group AND (`player_id` = @player_id OR `player_identifier` = @player_name); INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_server`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( @player_group, @player_id, @player_server, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_group", "slot_reserved"); command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_server", _serverInfo.ServerID); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " reserved slot."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to reserved slot. Error uploading."); } } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Reserved Slot record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting ReservedSlotTarget", 6); } public void SpectatorSlotTarget(ARecord record) { Log.Debug(() => "Entering SpectatorSlotTarget", 6); try { record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_specialplayers` WHERE `player_group` = @player_group AND (`player_id` = @player_id OR `player_identifier` = @player_name); INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_server`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( @player_group, @player_id, @player_server, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_group", "slot_spectator"); command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_server", _serverInfo.ServerID); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " spectator slot."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to spectator slot. Error uploading."); } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Spectator Slot record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SpectatorSlotTarget", 6); } public void AntiCheatWhitelistTarget(ARecord record) { Log.Debug(() => "Entering AntiCheatWhitelistTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "AntiCheatWhitelistTarget not available for multiple targets."); Log.Error("AntiCheatWhitelistTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_specialplayers` WHERE `player_group` = @player_group AND (`player_id` = @player_id OR `player_identifier` = @player_name); INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( @player_group, @player_id, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_group", "whitelist_anticheat"); command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " AntiCheat whitelist for all servers."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to AntiCheat whitelist. Error uploading."); } } } //Unban the player UnBanTarget(record); } catch (Exception e) { record.record_exception = new AException("Error while taking action for AntiCheat Whitelist record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting AntiCheatWhitelistTarget", 6); } public void PingWhitelistTarget(ARecord record) { Log.Debug(() => "Entering PingWhitelistTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "PingWhitelistTarget not available for multiple targets."); Log.Error("PingWhitelistTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_specialplayers` WHERE `player_group` = @player_group AND (`player_id` = @player_id OR `player_identifier` = @player_name); INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( @player_group, @player_id, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_group", "whitelist_ping"); command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " ping whitelist for all servers."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to ping whitelist. Error uploading."); } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Ping Whitelist record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting PingWhitelistTarget", 6); } public void AAWhitelistTarget(ARecord record) { Log.Debug(() => "Entering AAWhitelistTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "AAWhitelistTarget not available for multiple targets."); Log.Error("AAWhitelistTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_specialplayers` WHERE `player_group` = @player_group AND (`player_id` = @player_id OR `player_identifier` = @player_name); INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( @player_group, @player_id, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_group", "whitelist_adminassistant"); command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " admin assistant whitelist for all servers."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to Admin Assistant whitelist. Error uploading."); } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Admin Assistant Whitelist record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting AAWhitelistTarget", 6); } public void ReportWhitelistTarget(ARecord record) { Log.Debug(() => "Entering ReportWhitelistTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "ReportWhitelistTarget not available for multiple targets."); Log.Error("ReportWhitelistTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_specialplayers` WHERE `player_group` = @player_group AND (`player_id` = @player_id OR `player_identifier` = @player_name); INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( @player_group, @player_id, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_group", "whitelist_report"); command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " report whitelist for all servers."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to Report whitelist. Error uploading."); } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Report Whitelist record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting ReportWhitelistTarget", 6); } public void SpamBotWhitelistTarget(ARecord record) { Log.Debug(() => "Entering SpamBotWhitelistTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "SpamBotWhitelistTarget not available for multiple targets."); Log.Error("SpamBotWhitelistTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_specialplayers` WHERE `player_group` = @player_group AND (`player_id` = @player_id OR `player_identifier` = @player_name); INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( @player_group, @player_id, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_group", "whitelist_spambot"); command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " spambot whitelist for all servers."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to SpamBot whitelist. Error uploading."); } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for SpamBot Whitelist record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SpamBotWhitelistTarget", 6); } public void SpamBotWhitelistRemoveTarget(ARecord record) { Log.Debug(() => "Entering SpamBotWhitelistRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "SpamBotWhitelistRemoveTarget not available for multiple targets."); Log.Error("SpamBotWhitelistRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; List matchingPlayers = GetMatchingASPlayersOfGroup("whitelist_spambot", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the SpamBot whitelist."); FinalizeRecord(record); return; } using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from SpamBot whitelist."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from SpamBot whitelist. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from SpamBot whitelist."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SpamBotWhitelistRemoveTarget", 6); } public void SpectatorBlacklistTarget(ARecord record) { Log.Debug(() => "Entering SpectatorBlacklistTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "SpectatorBlacklistTarget not available for multiple targets."); Log.Error("SpectatorBlacklistTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_specialplayers` WHERE `player_group` = @player_group AND (`player_id` = @player_id OR `player_identifier` = @player_name); INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( @player_group, @player_id, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_group", "blacklist_spectator"); command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " spectator blacklist for all servers."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to spectator blacklist. Error uploading."); } } } //Kick target if they are currently spectating if (record.target_player.player_online && record.target_player.player_type == PlayerType.Spectator) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_kick"), command_numeric = 0, target_name = record.target_player.player_name, target_player = record.target_player, source_name = "SpectatorManager", record_message = "You may not spectate at this time.", record_time = UtcNow() }); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for spectator blacklist record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SpectatorBlacklistTarget", 6); } public void SpectatorBlacklistRemoveTarget(ARecord record) { Log.Debug(() => "Entering SpectatorBlacklistRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "SpectatorBlacklistRemoveTarget not available for multiple targets."); Log.Error("SpectatorBlacklistRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; List matchingPlayers = GetMatchingASPlayersOfGroup("blacklist_spectator", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the spectator blacklist."); FinalizeRecord(record); return; } using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from spectator blacklist."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from spectator blacklist. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from spectator blacklist."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SpectatorBlacklistRemoveTarget", 6); } public void ReportSourceBlacklistTarget(ARecord record) { Log.Debug(() => "Entering ReportSourceBlacklistTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "ReportSourceBlacklistTarget not available for multiple targets."); Log.Error("ReportSourceBlacklistTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_specialplayers` WHERE `player_group` = @player_group AND (`player_id` = @player_id OR `player_identifier` = @player_name); INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( @player_group, @player_id, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_group", "blacklist_report"); command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " report source blacklist for all servers."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to report source blacklist. Error uploading."); } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for report source blacklist record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting ReportSourceBlacklistTarget", 6); } public void ReportSourceBlacklistRemoveTarget(ARecord record) { Log.Debug(() => "Entering ReportSourceBlacklistRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "ReportSourceBlacklistRemoveTarget not available for multiple targets."); Log.Error("ReportSourceBlacklistRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; List matchingPlayers = GetMatchingASPlayersOfGroup("blacklist_report", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the report source blacklist."); FinalizeRecord(record); return; } using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from report source blacklist."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from report source blacklist. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from report source blacklist."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting ReportSourceBlacklistRemoveTarget", 6); } public void CommandTargetWhitelistTarget(ARecord record) { Log.Debug(() => "Entering CommandTargetWhitelistTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "CommandTargetWhitelistTarget not available for multiple targets."); Log.Error("CommandTargetWhitelistTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_specialplayers` WHERE `player_group` = @player_group AND (`player_id` = @player_id OR `player_identifier` = @player_name); INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( @player_group, @player_id, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_group", "whitelist_commandtarget"); command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " command target whitelist for all servers."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to command target whitelist. Error uploading."); } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for command target whitelist.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting CommandTargetWhitelistTarget", 6); } public void CommandTargetWhitelistRemoveTarget(ARecord record) { Log.Debug(() => "Entering CommandTargetWhitelistRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "CommandTargetWhitelistRemoveTarget not available for multiple targets."); Log.Error("CommandTargetWhitelistRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; List matchingPlayers = GetMatchingASPlayersOfGroup("whitelist_commandtarget", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the command target whitelist."); FinalizeRecord(record); return; } using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from command target whitelist."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from command target whitelist. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from command target whitelist."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting CommandTargetWhitelistRemoveTarget", 6); } public void AutoAssistBlacklistTarget(ARecord record) { Log.Debug(() => "Entering AutoAssistBlacklistTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "AutoAssistBlacklistTarget not available for multiple targets."); Log.Error("AutoAssistBlacklistTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_specialplayers` WHERE `player_group` = @player_group AND (`player_id` = @player_id OR `player_identifier` = @player_name); INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( @player_group, @player_id, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_group", "blacklist_autoassist"); command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " auto-assist blacklist for all servers."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to auto-assist blacklist. Error uploading."); } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for command target whitelist.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting AutoAssistBlacklistTarget", 6); } public void AutoAssistBlacklistRemoveTarget(ARecord record) { Log.Debug(() => "Entering AutoAssistBlacklistRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "AutoAssistBlacklistRemoveTarget not available for multiple targets."); Log.Error("AutoAssistBlacklistRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } List matchingPlayers = GetMatchingASPlayersOfGroup("blacklist_autoassist", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the auto-assist blacklist."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from auto-assist blacklist."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from auto-assist blacklist. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from auto-assist blacklist."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting AutoAssistBlacklistRemoveTarget", 6); } public void ReportWhitelistRemoveTarget(ARecord record) { Log.Debug(() => "Entering ReportWhitelistRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "ReportWhitelistRemoveTarget not available for multiple targets."); Log.Error("ReportWhitelistRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } List matchingPlayers = GetMatchingASPlayersOfGroup("whitelist_report", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the Report whitelist."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from Report whitelist."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from Report whitelist. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from Report whitelist."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting ReportWhitelistRemoveTarget", 6); } public void AAWhitelistRemoveTarget(ARecord record) { Log.Debug(() => "Entering AAWhitelistRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "AAWhitelistRemoveTarget not available for multiple targets."); Log.Error("AAWhitelistRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } List matchingPlayers = GetMatchingASPlayersOfGroup("whitelist_adminassistant", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the Admin Assistant whitelist."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from Admin Assistant whitelist."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from Admin Assistant whitelist. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from Admin Assistant whitelist."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting AAWhitelistRemoveTarget", 6); } public void PingWhitelistRemoveTarget(ARecord record) { Log.Debug(() => "Entering PingWhitelistRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "PingWhitelistRemoveTarget not available for multiple targets."); Log.Error("PingWhitelistRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } List matchingPlayers = GetMatchingASPlayersOfGroup("whitelist_ping", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the Ping whitelist."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from Ping whitelist."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from Ping whitelist. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from Ping whitelist."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting PingWhitelistRemoveTarget", 6); } public void AntiCheatWhitelistRemoveTarget(ARecord record) { Log.Debug(() => "Entering AntiCheatWhitelistRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "AntiCheatWhitelistRemoveTarget not available for multiple targets."); Log.Error("AntiCheatWhitelistRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } List matchingPlayers = GetMatchingASPlayersOfGroup("whitelist_anticheat", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the AntiCheat whitelist."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from AntiCheat whitelist."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from AntiCheat whitelist. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from AntiCheat whitelist."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting AntiCheatWhitelistRemoveTarget", 6); } public void SpectatorSlotRemoveTarget(ARecord record) { Log.Debug(() => "Entering SpectatorSlotRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "SpectatorSlotRemoveTarget not available for multiple targets."); Log.Error("SpectatorSlotRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } List matchingPlayers = GetMatchingASPlayersOfGroup("slot_spectator", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the spectator slot list."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from spectator slot list."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from spectator slot list. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from spectator slot list."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SpectatorSlotRemoveTarget", 6); } public void ReservedSlotRemoveTarget(ARecord record) { Log.Debug(() => "Entering ReservedSlotRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "ReservedSlotRemoveTarget not available for multiple targets."); Log.Error("ReservedSlotRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } List matchingPlayers = GetMatchingASPlayersOfGroup("slot_reserved", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the reserved slot list."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from reserved slot list."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from reserved slot list. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from reserved slot list."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting ReservedSlotRemoveTarget", 6); } public void BalanceWhitelistRemoveTarget(ARecord record) { Log.Debug(() => "Entering BalanceWhitelistRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "BalanceWhitelistRemoveTarget not available for multiple targets."); Log.Error("BalanceWhitelistRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } List matchingPlayers = GetMatchingASPlayersOfGroup("whitelist_multibalancer", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the autobalance whitelist."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from autobalance whitelist."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from autobalance whitelist. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from autobalance whitelist."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting BalanceWhitelistRemoveTarget", 6); } public void BalanceDisperseRemoveTarget(ARecord record) { Log.Debug(() => "Entering BalanceDisperseRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "BalanceDisperseRemoveTarget not available for multiple targets."); Log.Error("BalanceDisperseRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } List matchingPlayers = GetMatchingASPlayersOfGroup("blacklist_dispersion", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not under autobalance dispersion."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from autobalance dispersion."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from autobalance dispersion. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from autobalance dispersion."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting BalanceDisperseRemoveTarget", 6); } public void AllCapsBlacklistRemoveTarget(ARecord record) { Log.Debug(() => "Entering AllCapsBlacklistRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "AllCapsBlacklistRemoveTarget not available for multiple targets."); Log.Error("AllCapsBlacklistRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } List matchingPlayers = GetMatchingASPlayersOfGroup("blacklist_allcaps", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not under all-caps chat blacklist."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from all-caps chat blacklist."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from all-caps chat blacklist. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from all-caps chat blacklist."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting AllCapsBlacklistRemoveTarget", 6); } public void PopulatorWhitelistTarget(ARecord record) { Log.Debug(() => "Entering PopulatorWhitelistTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "PopulatorWhitelistTarget not available for multiple targets."); Log.Error("PopulatorWhitelistTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_specialplayers` WHERE `player_group` = @player_group AND (`player_id` = @player_id OR `player_identifier` = @player_name); INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( @player_group, @player_id, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_group", "whitelist_populator"); command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " populator whitelist for all servers."; SendMessageToSource(record, message); Log.Debug(() => message, 3); } else { Log.Error("Unable to add player to Populator whitelist. Error uploading."); } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Populator Whitelist record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting PopulatorWhitelistTarget", 6); } public void PopulatorWhitelistRemoveTarget(ARecord record) { Log.Debug(() => "Entering PopulatorWhitelistRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "PopulatorWhitelistRemoveTarget not available for multiple targets."); Log.Error("PopulatorWhitelistRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } List matchingPlayers = GetMatchingASPlayersOfGroup("whitelist_populator", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the populator whitelist."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from populator whitelist."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from populator whitelist. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from populator whitelist."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting PopulatorWhitelistRemoveTarget", 6); } public void TeamKillTrackerWhitelistTarget(ARecord record) { Log.Debug(() => "Entering TeamKillTrackerWhitelistTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "TeamKillTrackerWhitelistTarget not available for multiple targets."); Log.Error("TeamKillTrackerWhitelistTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_specialplayers` WHERE `player_group` = @player_group AND (`player_id` = @player_id OR `player_identifier` = @player_name); INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( @player_group, @player_id, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_group", "whitelist_teamkill"); command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " TeamKillTracker whitelist for all servers."; SendMessageToSource(record, message); Log.Debug(() => message, 3); } else { Log.Error("Unable to add player to TeamKillTracker whitelist. Error uploading."); } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for TeamKillTracker Whitelist record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting TeamKillTrackerWhitelistTarget", 6); } public void TeamKillTrackerWhitelistRemoveTarget(ARecord record) { Log.Debug(() => "Entering TeamKillTrackerWhitelistRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "TeamKillTrackerWhitelistRemoveTarget not available for multiple targets."); Log.Error("TeamKillTrackerWhitelistRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } List matchingPlayers = GetMatchingASPlayersOfGroup("whitelist_teamkill", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the TeamKillTracker whitelist."); FinalizeRecord(record); return; } record.record_action_executed = true; using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from TeamKillTracker whitelist."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from TeamKillTracker whitelist. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from TeamKillTracker whitelist."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting TeamKillTrackerWhitelistRemoveTarget", 6); } public void UpdatePlayerBattlecry(ARecord record) { Log.Debug(() => "Entering UpdatePlayerBattlecry", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "UpdatePlayerBattlecry not available for multiple targets."); Log.Error("UpdatePlayerBattlecry not available for multiple targets."); FinalizeRecord(record); return; } if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning player battlecry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning player battlecry. Unable to complete."); FinalizeRecord(record); return; } record.record_action_executed = true; //Update the player's battlecry on the object record.target_player.player_battlecry = record.record_message; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { if (String.IsNullOrEmpty(record.target_player.player_battlecry)) { command.CommandText = @"DELETE FROM `adkats_battlecries` WHERE `player_id` = @player_id"; command.Parameters.AddWithValue("@player_id", record.target_player.player_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { if (record.source_name == record.target_name) { SendMessageToSource(record, "Your battlecry has been removed."); } else { SendMessageToSource(record, record.GetTargetNames() + "'s battlecry has been removed."); } } else { Log.Error("Unable to remove player battlecry."); SendMessageToSource(record, "Unable to remove your battlecry."); } } else { command.CommandText = @" REPLACE INTO `adkats_battlecries` ( `player_id`, `player_battlecry` ) VALUES ( @player_id, @player_battlecry )"; command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_battlecry", record.target_player.player_battlecry); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "'" + record.target_player.player_battlecry + "'."; if (record.source_name == record.target_name) { message = "Your new battlecry: " + message; } else { message = record.GetTargetNames() + "'s new battlecry: " + message; } SendMessageToSource(record, message); } else { Log.Error("Unable to update player battlecry. Error uploading."); } } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Player Battlecry record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting UpdatePlayerBattlecry", 6); } public void UpdatePlayerDiscordLink(ARecord record) { Log.Debug(() => "Entering UpdatePlayerDiscordLink", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "UpdatePlayerDiscordLink not available for multiple targets."); Log.Error("UpdatePlayerDiscordLink not available for multiple targets."); FinalizeRecord(record); return; } if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when linking player to discord member. Unable to complete."); SendMessageToSource(record, "Player ID invalid when linking player to discord member. Unable to complete."); FinalizeRecord(record); return; } record.record_action_executed = true; // Pull the discord member var matchingMember = _DiscordManager.GetMembers(false, true, true) .FirstOrDefault(aMember => aMember.ID == record.record_message); if (matchingMember == null) { SendMessageToSource(record, "No matching discord member for ID " + record.record_message + "."); FinalizeRecord(record); return; } //Update the player's discord ID on the object record.target_player.player_discord_id = matchingMember.ID; //Update the member object with the player matchingMember.PlayerObject = record.target_player; matchingMember.PlayerTested = true; //Save info to the database UpdatePlayer(record.target_player); SendMessageToSource(record, record.target_player.GetVerboseName() + " linked with discord member " + matchingMember.Name + "."); // Update the setting page since the list there needs to be updated UpdateSettingPage(); } catch (Exception e) { record.record_exception = new AException("Error while taking action for Link Player to Discord Member record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting UpdatePlayerDiscordLink", 6); } public void MuteTarget(ARecord record) { Log.Debug(() => "Entering muteTarget", 6); try { record.record_action_executed = true; if (!HasAccess(record.target_player, GetCommandByKey("player_mute"))) { if (!_RoundMutedPlayers.ContainsKey(record.target_player.player_name)) { _RoundMutedPlayers.Add(record.target_player.player_name, 0); AdminSayMessage(record.GetTargetNames() + " has been muted for this round."); if (record.record_source != ARecord.Sources.InGame && record.record_source != ARecord.Sources.Automated && record.record_source != ARecord.Sources.ServerCommand) { SendMessageToSource(record, record.GetTargetNames() + " has been muted for this round."); } PlayerSayMessage(record.target_player.player_name, _MutedPlayerMuteMessage); } else { SendMessageToSource(record, record.GetTargetNames() + " already muted for this round."); } } else { SendMessageToSource(record, "You can't mute an admin."); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Mute record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting muteTarget", 6); } public void JoinTarget(ARecord record) { Log.Debug(() => "Entering joinTarget", 6); try { record.record_action_executed = true; //Get source player APlayer sourcePlayer = null; if (_PlayerDictionary.TryGetValue(record.source_name, out sourcePlayer)) { sourcePlayer.LastUsage = UtcNow(); //If the source has access to move players, then the squad will be unlocked for their entry if (HasAccess(record.source_player, GetCommandByKey("player_move"))) { //Unlock target squad SendMessageToSource(record, "Unlocking target squad if needed, please wait."); ExecuteCommand("procon.protected.send", "squad.private", record.target_player.fbpInfo.TeamID.ToString(), record.target_player.fbpInfo.SquadID.ToString(), "false"); //If anything longer is needed...tisk tisk Threading.Wait(500); } //Check for player access to change teams if (record.target_player.fbpInfo.TeamID != sourcePlayer.fbpInfo.TeamID && !HasAccess(record.source_player, GetCommandByKey("self_teamswap"))) { SendMessageToSource(record, "Target player is not on your team, you need " + GetChatCommandByKey("self_teamswap") + " access to join them."); } else { //Move to specific squad Log.Debug(() => "MULTIBalancer Unswitcher Disabled", 3); ExecuteCommand("procon.protected.plugins.call", "MULTIbalancer", "UpdatePluginData", "AdKats", "bool", "DisableUnswitcher", "True"); _MULTIBalancerUnswitcherDisabled = true; ATeam targetTeam; if (GetTeamByID(record.target_player.fbpInfo.TeamID, out targetTeam)) { record.source_player.RequiredTeam = targetTeam; _LastPlayerMoveIssued = UtcNow(); SendMessageToSource(record, "Attempting to join " + record.GetTargetNames()); ExecuteCommand("procon.protected.send", "admin.movePlayer", record.source_player.player_name, record.target_player.fbpInfo.TeamID.ToString(), record.target_player.fbpInfo.SquadID.ToString(), "true"); } } } else { SendMessageToSource(record, "Unable to find you in the player list, please try again."); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Join record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting joinTarget", 6); } public void PullTarget(ARecord record) { Log.Debug(() => "Entering PullTarget", 6); try { record.record_action_executed = true; //Unlock squad SendMessageToSource(record, "Unlocking your squad for entry."); ExecuteCommand("procon.protected.send", "squad.private", record.source_player.fbpInfo.TeamID.ToString(), record.source_player.fbpInfo.SquadID.ToString(), "false"); Threading.Wait(500); //Move to specific squad Log.Debug(() => "MULTIBalancer Unswitcher Disabled", 3); ExecuteCommand("procon.protected.plugins.call", "MULTIbalancer", "UpdatePluginData", "AdKats", "bool", "DisableUnswitcher", "True"); _MULTIBalancerUnswitcherDisabled = true; ATeam sourceTeam; if (GetTeamByID(record.source_player.fbpInfo.TeamID, out sourceTeam)) { record.target_player.RequiredTeam = sourceTeam; _LastPlayerMoveIssued = UtcNow(); SendMessageToSource(record, "Pulling " + record.GetTargetNames() + " to your squad."); ExecuteCommand("procon.protected.send", "admin.movePlayer", record.target_player.player_name, sourceTeam.TeamID.ToString(), record.source_player.fbpInfo.SquadID.ToString(), "true"); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Join record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting PullTarget", 6); } public void ReportTarget(ARecord record) { Log.Debug(() => "Entering reportTarget", 6); try { Int32 reportID = AddRecordToReports(record); record.record_action_executed = true; if (_subscribedClients.Any(client => client.ClientName == "AdKatsLRT" && client.SubscriptionEnabled) && record.target_player != null && record.target_player.player_reputation <= 0 && record.target_player.player_online) { Log.Info("Running loadout case for report record " + reportID); if (!record.isLoadoutChecked) { lock (_LoadoutConfirmDictionary) { _LoadoutConfirmDictionary[record.target_player.player_name] = record; } Log.Info("Report " + reportID + " waiting for loadout confirmation."); ExecuteCommand("procon.protected.plugins.call", "AdKatsLRT", "CallLoadoutCheckOnPlayer", "AdKats", JSON.JsonEncode(new Hashtable { {"caller_identity", "AdKats"}, {"response_requested", false}, {"player_name", record.target_player.player_name}, {"loadoutCheck_reason", "reported"} })); return; } if (record.targetLoadoutActed) { SendMessageToSource(record, "Your report [" + reportID + "] has been acted on. Thank you."); OnlineAdminSayMessage("Report " + reportID + " is being acted on by Loadout Enforcer."); ConfirmActiveReport(record); return; } } AttemptReportAutoAction(record, reportID.ToString()); String sourceAAIdentifier = (record.source_player != null && record.source_player.player_aa) ? ("(AA)") : (""); String targetAAIdentifier = (record.target_player != null && record.target_player.player_aa) ? ("(AA)") : (""); String slotID = (record.target_player != null) ? (record.target_player.player_slot) : (null); if (!String.IsNullOrEmpty(slotID)) { ExecuteCommand("procon.protected.send", "punkBuster.pb_sv_command", "pb_sv_getss " + slotID); } String sourcePlayerInfo = ""; if (record.source_player != null && record.source_player.fbpInfo != null) { if (record.source_player.player_online) { sourcePlayerInfo = " (" + Math.Round(record.source_player.player_reputation, 1) + ")(" + GetPlayerTeamKey(record.source_player) + "/" + (_PlayerDictionary.Values.Where(aPlayer => aPlayer.fbpInfo.TeamID == record.source_player.fbpInfo.TeamID).OrderBy(aPlayer => aPlayer.fbpInfo.Score).Reverse().ToList().IndexOf(record.target_player) + 1) + ")"; } else { sourcePlayerInfo = " (OFFLINE)"; } } var sourceString = sourceAAIdentifier + record.GetSourceName() + sourcePlayerInfo; String targetPlayerInfo = ""; if (record.target_player != null && record.target_player.fbpInfo != null) { if (record.target_player.player_online) { targetPlayerInfo = " (" + Math.Round(record.target_player.player_reputation, 1) + ")(" + GetPlayerTeamKey(record.target_player) + "/" + (_PlayerDictionary.Values.Where(aPlayer => aPlayer.fbpInfo.TeamID == record.target_player.fbpInfo.TeamID).OrderBy(aPlayer => aPlayer.fbpInfo.Score).Reverse().ToList().IndexOf(record.target_player) + 1) + ")"; } else { targetPlayerInfo = " (OFFLINE)"; } } var targetString = targetAAIdentifier + record.GetTargetNames() + targetPlayerInfo; OnlineAdminSayMessage("R[" + reportID + "] Source: " + sourceString); OnlineAdminSayMessage("R[" + reportID + "] Target: " + targetString); OnlineAdminSayMessage("R[" + reportID + "] Reason: " + record.record_message); if (record.isLoadoutChecked) { if (record.target_player.loadout_valid) { OnlineAdminSayMessage("R[" + reportID + "] Loadout(VALID): " + record.target_player.loadout_items); } else { OnlineAdminSayMessage("R[" + reportID + "] Loadout(INVALID): " + record.target_player.loadout_deniedItems); } } if (record.target_player != null && (record.target_player.player_reputation > _reputationThresholdGood || PlayerIsAdmin(record.target_player))) { //Set Contested record.isContested = true; //Inform All Parties SendMessageToSource(record, record.GetTargetNames() + "'s reputation has automatically contested your report against them."); PlayerTellMessage(record.target_player.player_name, "Your reputation has automatically contested " + record.GetSourceName() + "'s report against you."); OnlineAdminSayMessage(record.GetTargetNames() + "'s reputation has automatically contested report [" + record.command_numeric + "]"); } else if (_InformReportedPlayers) { String mesLow = record.record_message.ToLower(); if (!_PlayerInformExclusionStrings.Any(exc => mesLow.Contains(exc.ToLower())) && record.source_name != "AutoAdmin") { PlayerTellMessage(record.target_name, record.GetSourceName() + " reported you for " + record.record_message, true, 6); } } if (_UseEmail) { if (_EmailReportsOnlyWhenAdminless && FetchOnlineAdminSoldiers().Any()) { Log.Debug(() => "Email cancelled, admins online.", 3); } else { Log.Debug(() => "Preparing to send report email.", 3); _EmailHandler.SendReport(record); } } if (_UsePushBullet) { if (_PushBulletReportsOnlyWhenAdminless && FetchOnlineAdminSoldiers().Any()) { Log.Debug(() => "PushBullet report cancelled, admins online.", 3); } else { Log.Debug(() => "Preparing to send PushBullet report.", 3); _PushBulletHandler.PushReport(record); } } if (_UseDiscordForReports) { if (_DiscordReportsOnlyWhenAdminless && FetchOnlineAdminSoldiers().Any()) { Log.Debug(() => "Discord report cancelled, admins online.", 3); } else { Log.Debug(() => "Preparing to send Discord report.", 3); _DiscordManager.PostReport(record, "REPORT", sourceString, targetString); } } if (record.source_player != null && record.source_name != record.target_name && record.source_player.player_type == PlayerType.Spectator) { //Custom record to boost rep for reporting from spectator mode ARecord repRecord = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_repboost"), command_numeric = 0, target_name = record.source_player.player_name, target_player = record.source_player, source_name = "RepManager", record_message = "Player reported from Spectator Mode", record_time = UtcNow() }; UploadRecord(repRecord); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Report record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting reportTarget", 6); } public void CallAdminOnTarget(ARecord record) { Log.Debug(() => "Entering callAdminOnTarget", 6); try { Int32 reportID = AddRecordToReports(record); record.record_action_executed = true; if (_subscribedClients.Any(client => client.ClientName == "AdKatsLRT" && client.SubscriptionEnabled) && record.target_player != null && record.target_player.player_reputation <= 0 && record.target_player.player_online) { Log.Info("Running loadout case for report record " + reportID); if (!record.isLoadoutChecked) { if (!_LoadoutConfirmDictionary.ContainsKey(record.target_player.player_name)) { lock (_LoadoutConfirmDictionary) { _LoadoutConfirmDictionary[record.target_player.player_name] = record; } Log.Info("Admin call " + reportID + " waiting for loadout confirmation."); ExecuteCommand("procon.protected.plugins.call", "AdKatsLRT", "CallLoadoutCheckOnPlayer", "AdKats", JSON.JsonEncode(new Hashtable { {"caller_identity", "AdKats"}, {"response_requested", false}, {"player_name", record.target_player.player_name}, {"loadoutCheck_reason", "reported"} })); } return; } if (record.targetLoadoutActed) { SendMessageToSource(record, "Your report [" + reportID + "] has been acted on. Thank you."); OnlineAdminSayMessage("Report " + reportID + " is being acted on by Loadout Enforcer."); ConfirmActiveReport(record); return; } } AttemptReportAutoAction(record, reportID.ToString()); String sourceAAIdentifier = (record.source_player != null && record.source_player.player_aa) ? ("(AA)") : (""); String targetAAIdentifier = (record.target_player != null && record.target_player.player_aa) ? ("(AA)") : (""); String slotID = (record.target_player != null) ? (record.target_player.player_slot) : (null); if (!String.IsNullOrEmpty(slotID)) { ExecuteCommand("procon.protected.send", "punkBuster.pb_sv_command", "pb_sv_getss " + slotID); } String sourcePlayerInfo = ""; if (record.source_player != null && record.source_player.fbpInfo != null) { if (record.source_player.player_online) { sourcePlayerInfo = " (" + Math.Round(record.source_player.player_reputation, 1) + ")(" + GetPlayerTeamKey(record.source_player) + "/" + (_PlayerDictionary.Values.Where(aPlayer => aPlayer.fbpInfo.TeamID == record.source_player.fbpInfo.TeamID).OrderBy(aPlayer => aPlayer.fbpInfo.Score).Reverse().ToList().IndexOf(record.target_player) + 1) + ")"; } else { sourcePlayerInfo = " (OFFLINE)"; } } var sourceString = sourceAAIdentifier + record.GetSourceName() + sourcePlayerInfo; String targetPlayerInfo = ""; if (record.target_player != null && record.target_player.fbpInfo != null) { if (record.target_player.player_online) { targetPlayerInfo = " (" + Math.Round(record.target_player.player_reputation, 1) + ")(" + GetPlayerTeamKey(record.target_player) + "/" + (_PlayerDictionary.Values.Where(aPlayer => aPlayer.fbpInfo.TeamID == record.target_player.fbpInfo.TeamID).OrderBy(aPlayer => aPlayer.fbpInfo.Score).Reverse().ToList().IndexOf(record.target_player) + 1) + ")"; } else { targetPlayerInfo = " (OFFLINE)"; } } var targetString = targetAAIdentifier + record.GetTargetNames() + targetPlayerInfo; OnlineAdminSayMessage("A[" + reportID + "] Source: " + sourceString); OnlineAdminSayMessage("A[" + reportID + "] Target: " + targetString); OnlineAdminSayMessage("A[" + reportID + "] Reason: " + record.record_message); if (record.isLoadoutChecked) { if (record.target_player.loadout_valid) { OnlineAdminSayMessage("A[" + reportID + "] Loadout(VALID): " + record.target_player.loadout_items); } else { OnlineAdminSayMessage("A[" + reportID + "] Loadout(INVALID): " + record.target_player.loadout_deniedItems); } } if (_InformReportedPlayers) { String mesLow = record.record_message.ToLower(); if (!_PlayerInformExclusionStrings.Any(exc => mesLow.Contains(exc.ToLower()))) { PlayerTellMessage(record.target_name, record.GetSourceName() + " reported you for " + record.record_message, true, 6); } } if (_UseEmail) { if (_EmailReportsOnlyWhenAdminless && FetchOnlineAdminSoldiers().Any()) { Log.Debug(() => "Email cancelled, admins online.", 3); } else { Log.Debug(() => "Preparing to send admin call email.", 3); _EmailHandler.SendReport(record); } } if (_UsePushBullet) { if (_PushBulletReportsOnlyWhenAdminless && FetchOnlineAdminSoldiers().Any()) { Log.Debug(() => "PushBullet admin call cancelled, admins online.", 3); } else { Log.Debug(() => "Preparing to send PushBullet admin call.", 3); _PushBulletHandler.PushReport(record); } } if (_UseDiscordForReports) { if (_DiscordReportsOnlyWhenAdminless && FetchOnlineAdminSoldiers().Any()) { Log.Debug(() => "Discord admin call cancelled, admins online.", 3); } else { Log.Debug(() => "Preparing to send Discord admin call.", 3); _DiscordManager.PostReport(record, "ADMIN CALL", sourceString, targetString); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for CallAdmin record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting callAdminOnTarget", 6); } public void AttemptReportAutoAction(ARecord record, String reportID) { Boolean sourceAA = record.source_player != null && record.source_player.player_aa; Int32 onlineAdminCount = FetchOnlineAdminSoldiers().Count; String messageLower = record.record_message.ToLower(); Boolean canAutoHandle = _UseAAReportAutoHandler && sourceAA && _AutoReportHandleStrings.Any() && !String.IsNullOrEmpty(_AutoReportHandleStrings[0]) && _AutoReportHandleStrings.Any(messageLower.Contains) && !record.target_player.player_aa && !PlayerIsAdmin(record.target_player); Boolean adminsOnline = onlineAdminCount > 0; String reportMessage = ""; if (!sourceAA || !adminsOnline || !canAutoHandle) { reportMessage = "REPORT [" + reportID + "] sent on " + record.GetTargetNames() + " for " + record.record_message; } else { reportMessage = "REPORT [" + reportID + "] on " + record.GetTargetNames() + " sent to " + onlineAdminCount + " in-game admin" + ((onlineAdminCount > 1) ? ("s") : ("")) + ". " + ((canAutoHandle) ? ("Admins have 45 seconds before auto-handling.") : ("")); } SendMessageToSource(record, reportMessage); if (!canAutoHandle) { //Log.Warn("Cancelling auto-handler."); return; } Thread reportAutoHandler = new Thread(new ThreadStart(delegate { //Log.Warn("Starting report auto-handler thread."); try { Thread.CurrentThread.Name = "ReportAutoHandler"; //If admins are online, act after 45 seconds. If they are not, act after 5 seconds. Threading.Wait(TimeSpan.FromSeconds((adminsOnline) ? (45.0) : (5.0))); //Get the report record ARecord reportRecord = FetchPlayerReportByID(reportID); if (reportRecord != null) { if (CanPunish(reportRecord, 90) || !adminsOnline) { //Update it in the database ConfirmActiveReport(reportRecord); //Get target information ARecord aRecord = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_punish"), command_numeric = 0, target_name = reportRecord.target_player.player_name, target_player = reportRecord.target_player, source_name = "ProconAdmin", record_message = reportRecord.record_message, record_time = UtcNow() }; //Inform the reporter that they helped the admins SendMessageToSource(reportRecord, "Your report [" + reportRecord.command_numeric + "] has been acted on. Thank you."); //Queue for processing QueueRecordForProcessing(aRecord); } else { SendMessageToSource(reportRecord, "Reported player has already been acted on."); } } } catch (Exception) { Log.HandleException(new AException("Error while auto-handling report.")); } Log.Debug(() => "Exiting a report auto-handler.", 5); })); //Start the thread Threading.StartWatchdog(reportAutoHandler); } public void ChallengePlayTarget(ARecord record) { Log.Debug(() => "Entering ChallengePlayTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "ChallengePlayTarget not available for multiple targets."); Log.Error("ChallengePlayTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; List matchingPlayers = GetMatchingASPlayersOfGroup("challenge_play", record.target_player); if (matchingPlayers.Count > 0) { SendMessageToSource(record, matchingPlayers.Count + " matching player(s) already in the challenge playing status."); return; } using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( 'challenge_play', @player_id, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " challenge playing status for all servers."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to challenge playing status. Error uploading."); } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for challenge playing status.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting ChallengePlayTarget", 6); } public void ChallengePlayRemoveTarget(ARecord record) { Log.Debug(() => "Entering ChallengePlayRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "ChallengePlayRemoveTarget not available for multiple targets."); Log.Error("ChallengePlayRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; List matchingPlayers = GetMatchingASPlayersOfGroup("challenge_play", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the challenge playing status."); FinalizeRecord(record); return; } using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from challenge playing status."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from challenge playing status. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from challenge playing status."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting ChallengePlayRemoveTarget", 6); } public void ChallengeIgnoreTarget(ARecord record) { Log.Debug(() => "Entering ChallengeIgnoreTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "ChallengeIgnoreTarget not available for multiple targets."); Log.Error("ChallengeIgnoreTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; List matchingPlayers = GetMatchingASPlayersOfGroup("challenge_ignore", record.target_player); if (matchingPlayers.Count > 0) { SendMessageToSource(record, matchingPlayers.Count + " matching player(s) already in the challenge ignoring status."); return; } using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( 'challenge_ignore', @player_id, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " challenge ignoring status for all servers."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to challenge ignoring status. Error uploading."); } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for challenge ignoring status.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting ChallengeIgnoreTarget", 6); } public void ChallengeIgnoreRemoveTarget(ARecord record) { Log.Debug(() => "Entering ChallengeIgnoreRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "ChallengeIgnoreRemoveTarget not available for multiple targets."); Log.Error("ChallengeIgnoreRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; List matchingPlayers = GetMatchingASPlayersOfGroup("challenge_ignore", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the challenge ignoring status."); FinalizeRecord(record); return; } using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from challenge ignoring status."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from challenge ignoring status. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from challenge ignoring status."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting ChallengeIgnoreRemoveTarget", 6); } public void ChallengeAutoKillTarget(ARecord record) { Log.Debug(() => "Entering ChallengeAutoKillTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "ChallengeAutoKillTarget not available for multiple targets."); Log.Error("ChallengeAutoKillTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; List matchingPlayers = GetMatchingASPlayersOfGroup("challenge_autokill", record.target_player); if (matchingPlayers.Count > 0) { SendMessageToSource(record, matchingPlayers.Count + " matching player(s) already in the challenge playing status."); return; } using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" INSERT INTO `adkats_specialplayers` ( `player_group`, `player_id`, `player_identifier`, `player_effective`, `player_expiration` ) VALUES ( 'challenge_autokill', @player_id, @player_name, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL @duration_minutes MINUTE) )"; if (record.target_player.player_id <= 0) { Log.Error("Player ID invalid when assigning special player entry. Unable to complete."); SendMessageToSource(record, "Player ID invalid when assigning special player entry. Unable to complete."); FinalizeRecord(record); return; } if (record.command_numeric > 10518984) { record.command_numeric = 10518984; } command.Parameters.AddWithValue("@player_id", record.target_player.player_id); command.Parameters.AddWithValue("@player_name", record.target_player.player_name); command.Parameters.AddWithValue("@duration_minutes", record.command_numeric); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " given " + ((record.command_numeric == 10518984) ? ("permanent") : (FormatTimeString(TimeSpan.FromMinutes(record.command_numeric), 2))) + " challenge autokill status for all servers."; SendMessageToSource(record, message); Log.Debug(() => message, 3); FetchAllAccess(true); } else { Log.Error("Unable to add player to challenge playing status. Error uploading."); } } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for challenge playing status.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting ChallengeAutoKillTarget", 6); } public void ChallengeAutoKillRemoveTarget(ARecord record) { Log.Debug(() => "Entering ChallengeAutoKillRemoveTarget", 6); try { //Case for multiple targets if (record.target_player == null) { SendMessageToSource(record, "ChallengeAutoKillRemoveTarget not available for multiple targets."); Log.Error("ChallengeAutoKillRemoveTarget not available for multiple targets."); FinalizeRecord(record); return; } record.record_action_executed = true; List matchingPlayers = GetMatchingASPlayersOfGroup("challenge_autokill", record.target_player); if (!matchingPlayers.Any()) { SendMessageToSource(record, "Matching player not in the challenge autokill status."); FinalizeRecord(record); return; } using (MySqlConnection connection = GetDatabaseConnection()) { Boolean updated = false; foreach (ASpecialPlayer asPlayer in matchingPlayers) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_specialplayers` WHERE `specialplayer_id` = @sp_id"; command.Parameters.AddWithValue("@sp_id", asPlayer.specialplayer_id); Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { String message = "Player " + record.GetTargetNames() + " removed from challenge autokill status."; Log.Debug(() => message, 3); updated = true; } else { Log.Error("Unable to remove player from challenge autokill status. Error uploading."); } } } if (updated) { String message = "Player " + record.GetTargetNames() + " removed from challenge autokill status."; SendMessageToSource(record, message); FetchAllAccess(true); } } } catch (Exception e) { record.record_exception = new AException("Error while taking action for " + record.command_action.command_name + " record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting ChallengeAutoKillRemoveTarget", 6); } public void PurgeExtendedRoundStats() { Log.Debug(() => "Entering PurgeExtendedRoundStats", 6); try { //Purge all extended round stats older than 60 days using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"delete from tbl_extendedroundstats where tbl_extendedroundstats.roundstat_time < date_sub(sysdate(), interval 60 day)"; Int32 affectedRows = SafeExecuteNonQuery(command); if (affectedRows > 0) { Log.Debug(() => "Purged " + affectedRows + " extended round stats older than 60 days.", 5); } } } } catch (Exception e) { Log.HandleException(new AException("Error while purging extended round statistics.", e)); } Log.Debug(() => "Exiting PurgeExtendedRoundStats", 6); } public void PurgeOutdatedStatistics() { Log.Debug(() => "Entering PurgeOutdatedStatistics", 6); try { //Purge all Adkats statistics older than 90 days using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"delete from adkats_statistics where adkats_statistics.stat_time < date_sub(sysdate(), interval 90 day)"; Int32 affectedRows = SafeExecuteNonQuery(command); if (affectedRows > 0) { Log.Debug(() => "Purged " + affectedRows + " AdKats statistics older than 90 days.", 5); } } } } catch (Exception e) { Log.HandleException(new AException("Error while purging Adkats statistics.", e)); } Log.Debug(() => "Exiting PurgeOutdatedStatistics", 6); } public void PurgeOutdatedExceptions() { Log.Debug(() => "Entering PurgeOutdatedExceptions", 6); try { //Purge all extended round stats older than 60 days using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"delete from adkats_records_debug where record_time < '2017-10-12'"; Int32 affectedRows = SafeExecuteNonQuery(command); if (affectedRows > 0) { Log.Debug(() => "Purged " + affectedRows + " debug records older than current stable version.", 5); } } } } catch (Exception e) { Log.HandleException(new AException("Error while purging outdated exceptions.", e)); } Log.Debug(() => "Exiting PurgeOutdatedExceptions", 6); } public void FixInvalidCommandIds() { Log.Debug(() => "Entering FixInvalidCommandIds", 6); try { //The BFACP has an old bug that people keep running into. //The incorrect command action values are used when taking some actions. //This monitor will fix those with the correct ones. using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"update adkats_records_main set command_action = 62 where command_action = 61 and command_type in (18, 20); update adkats_records_main set command_action = 42 where command_action = 41 and command_type in (18, 20); update adkats_records_main set command_action = 19 where command_action = 40 and command_type in (18, 20);"; Int32 affectedRows = SafeExecuteNonQuery(command); if (affectedRows > 0) { Log.Debug(() => "Fixed " + affectedRows + " invalid command action values.", 5); } } } } catch (Exception e) { Log.HandleException(new AException("Error while fixing invalid command action values.", e)); } Log.Debug(() => "Exiting FixInvalidCommandIds", 6); } public void RestartLevel(ARecord record) { Log.Debug(() => "Entering restartLevel", 6); try { record.record_action_executed = true; ExecuteCommand("procon.protected.send", "mapList.restartRound"); SendMessageToSource(record, "Round Restarted."); } catch (Exception e) { record.record_exception = new AException("Error while taking action for RestartLevel record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting restartLevel", 6); } public void NextLevel(ARecord record) { Log.Debug(() => "Entering nextLevel", 6); try { record.record_action_executed = true; ExecuteCommand("procon.protected.send", "mapList.runNextRound"); SendMessageToSource(record, "Next round has been run."); } catch (Exception e) { record.record_exception = new AException("Error while taking action for NextLevel record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting nextLevel", 6); } public void EndLevel(ARecord record) { Log.Debug(() => "Entering EndLevel", 6); try { record.record_action_executed = true; ExecuteCommand("procon.protected.send", "mapList.endRound", record.command_numeric.ToString()); SendMessageToSource(record, "Ended round with " + record.GetTargetNames() + " winning."); } catch (Exception e) { record.record_exception = new AException("Error while taking action for EndLevel record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting EndLevel", 6); } public void NukeTarget(ARecord record) { Log.Debug(() => "Entering NukeTarget", 6); try { record.record_action_executed = true; if (_NukeCountdownDurationSeconds > 0) { //Start the thread Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a nuke countdown printer.", 5); try { Thread.CurrentThread.Name = "NukeCountdownPrinter"; for (Int32 countdown = _NukeCountdownDurationSeconds; countdown > 0; countdown--) { if (!_pluginEnabled) { Threading.StopWatchdog(); return; } AdminTellMessage("Nuking " + record.GetTargetNames() + " team in " + countdown + "..."); Threading.Wait(TimeSpan.FromSeconds(1)); } _lastNukeTime = UtcNow(); ATeam team1 = null; ATeam team2 = null; ATeam nukedTeam = null; ATeam advancingTeam = null; if (!GetTeamByID(1, out team1)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } FinalizeRecord(record); return; } if (!GetTeamByID(2, out team2)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } FinalizeRecord(record); return; } if (record.target_name != "Everyone") { if (record.command_numeric == team1.TeamID) { incNukeCount(team1.TeamID); _lastNukeTeam = team1; nukedTeam = team1; advancingTeam = team2; } else if (record.command_numeric == team2.TeamID) { incNukeCount(team2.TeamID); _lastNukeTeam = team2; nukedTeam = team2; advancingTeam = team1; } } AdminTellMessage(record.source_name == "RoundManager" ? record.record_message : "Nuking " + record.GetTargetNames() + "!"); var nukeTargets = _PlayerDictionary.Values.ToList().Where(player => (nukedTeam != null && player.fbpInfo.TeamID == nukedTeam.TeamID) || (record.target_name == "Everyone")); foreach (APlayer player in nukeTargets) { // Initial kills for nuke ExecuteCommand("procon.protected.send", "admin.killPlayer", player.player_name); } Thread.Sleep(TimeSpan.FromSeconds(1)); foreach (APlayer player in nukeTargets) { // Secondary kills for nuke ExecuteCommand("procon.protected.send", "admin.killPlayer", player.player_name); } Thread.Sleep(TimeSpan.FromSeconds(1)); foreach (APlayer player in nukeTargets) { // Tertiary kills for nuke ExecuteCommand("procon.protected.send", "admin.killPlayer", player.player_name); } } catch (Exception) { Log.HandleException(new AException("Error while printing nuke countdown")); } Log.Debug(() => "Exiting a nuke countdown printer.", 5); Threading.StopWatchdog(); }))); } else { _lastNukeTime = UtcNow(); AdminTellMessage(record.source_name == "RoundManager" ? record.record_message : "Nuking " + record.GetTargetNames() + " team!"); var nukeTargets = _PlayerDictionary.Values.ToList().Where(player => (player.fbpInfo.TeamID == record.command_numeric) || (record.target_name == "Everyone")); foreach (APlayer player in nukeTargets) { // Initial kills for nuke ExecuteCommand("procon.protected.send", "admin.killPlayer", player.player_name); } Thread.Sleep(TimeSpan.FromSeconds(1)); foreach (APlayer player in nukeTargets) { // Secondary kills for nuke ExecuteCommand("procon.protected.send", "admin.killPlayer", player.player_name); } Thread.Sleep(TimeSpan.FromSeconds(1)); foreach (APlayer player in nukeTargets) { // Tertiary kills for nuke ExecuteCommand("procon.protected.send", "admin.killPlayer", player.player_name); } } SendMessageToSource(record, "You NUKED " + record.GetTargetNames() + "."); } catch (Exception e) { record.record_exception = new AException("Error while taking action for NukeServer record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting NukeTarget", 6); } public void CountdownTarget(ARecord record) { Log.Debug(() => "Entering CountdownTarget", 6); try { record.record_action_executed = true; if (record.command_numeric < 1 || record.command_numeric > 30) { SendMessageToSource(record, "Invalid duration, must be 1-30. Unable to act."); FinalizeRecord(record); return; } if (String.IsNullOrEmpty(record.record_message)) { SendMessageToSource(record, "Invalid countdown message, unable to act."); FinalizeRecord(record); return; } List targetedPlayers = new List(); switch (record.target_name) { case "Squad": if (record.source_player == null || !record.source_player.player_online || !_PlayerDictionary.ContainsKey(record.source_player.player_name) || record.source_player.player_type == PlayerType.Spectator) { SendMessageToSource(record, "Source must be an online player to use squad option. Unable to act."); FinalizeRecord(record); return; } targetedPlayers.AddRange(_PlayerDictionary.Values.ToList().Where(aPlayer => aPlayer.fbpInfo.TeamID == record.source_player.fbpInfo.TeamID && aPlayer.fbpInfo.SquadID == record.source_player.fbpInfo.SquadID).ToList()); break; case "Team": if (record.source_player == null || !record.source_player.player_online || !_PlayerDictionary.ContainsKey(record.source_player.player_name) || record.source_player.player_type == PlayerType.Spectator) { SendMessageToSource(record, "Source must be an online player to use team option. Unable to act."); FinalizeRecord(record); return; } targetedPlayers.AddRange(_PlayerDictionary.Values.ToList().Where(aPlayer => aPlayer.fbpInfo.TeamID == record.source_player.fbpInfo.TeamID).ToList()); break; case "All": //All players, so include spectators and commanders break; default: //Check for specific team targeting var teamTarget = GetTeamByKey(record.target_name); if (teamTarget != null) { //Send to target and neutral team targetedPlayers.AddRange(_PlayerDictionary.Values.ToList().Where(aPlayer => aPlayer.fbpInfo.TeamID == teamTarget.TeamID || aPlayer.fbpInfo.TeamID == 0).ToList()); } else { SendMessageToSource(record, "Invalid target, must be Squad, Team, or All. Unable to Act."); FinalizeRecord(record); return; } break; } //Start the thread Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a countdown printer thread.", 5); try { Thread.CurrentThread.Name = "CountdownPrinter"; for (Int32 countdown = record.command_numeric; countdown > 0; countdown--) { if (!_pluginEnabled) { Threading.StopWatchdog(); return; } if (record.target_name == "All") { AdminTellMessage(record.record_message + " in " + countdown + "..."); } else { //Threads spawned from threads...oh god Threading.StartWatchdog(new Thread(new ThreadStart(delegate { try { Thread.CurrentThread.Name = "CountdownPrinter_Private"; var inCount = countdown; foreach (APlayer aPlayer in targetedPlayers) { PlayerTellMessage(aPlayer.player_name, record.record_message + " in " + inCount + "...", false, 1); } } catch (Exception) { Log.HandleException(new AException("Error while printing private countdown")); } Threading.StopWatchdog(); }))); } Threading.Wait(TimeSpan.FromSeconds(1)); } if (record.target_name == "All") { AdminTellMessage(record.record_message + " NOW!"); } else { foreach (APlayer aPlayer in targetedPlayers) { PlayerTellMessage(aPlayer.player_name, record.record_message + " NOW!", false, 1); } } } catch (Exception e) { Log.HandleException(new AException("Error while printing server countdown", e)); } Log.Debug(() => "Exiting a countdown printer.", 5); Threading.StopWatchdog(); }))); } catch (Exception e) { record.record_exception = new AException("Error while taking action for ServerCountdown record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting CountdownTarget", 6); } public void SwapNukeServer(ARecord record) { Log.Debug(() => "Entering SwapNukeServer", 6); try { record.record_action_executed = true; foreach (APlayer player in _PlayerDictionary.Values.ToList().Where(aPlayer => aPlayer.player_type == PlayerType.Player)) { QueuePlayerForForceMove(player.fbpInfo); } SendMessageToSource(record, "You SwapNuked the server."); } catch (Exception e) { record.record_exception = new AException("Error while taking action for SwapNuke record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SwapNukeServer", 6); } public void KickAllPlayers(ARecord record) { Log.Debug(() => "Entering kickAllPlayers", 6); try { record.record_action_executed = true; foreach (APlayer player in _PlayerDictionary.Values.ToList().Where(player => player.player_role.role_key == "guest_default")) { Threading.Wait(50); ExecuteCommand("procon.protected.send", "admin.kickPlayer", player.player_name, "(" + record.source_name + ") " + record.record_message); } AdminSayMessage("All guest players have been kicked."); SendMessageToSource(record, "You KICKED EVERYONE for '" + record.record_message + "'"); } catch (Exception e) { record.record_exception = new AException("Error while taking action for KickAll record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting kickAllPlayers", 6); } public void AdminSay(ARecord record) { Log.Debug(() => "Entering adminSay", 6); try { record.record_action_executed = true; AdminSayMessage(record.record_message); if (record.record_source != ARecord.Sources.InGame && record.record_source != ARecord.Sources.ServerCommand) { SendMessageToSource(record, "Server has been told '" + record.record_message + "' by SAY"); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for AdminSay record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting adminSay", 6); } public void PlayerSay(ARecord record) { Log.Debug(() => "Entering playerSay", 6); try { record.record_action_executed = true; PlayerSayMessage(record.target_player.player_name, record.record_message); if (record.record_source != ARecord.Sources.ServerCommand) { SendMessageToSource(record, record.GetTargetNames() + " has been told '" + record.record_message + "' by SAY"); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for playerSay record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting playerSay", 6); } public void PMSendTarget(ARecord record) { Log.Debug(() => "Entering PMSendTarget", 6); try { record.record_action_executed = true; APlayer sender = record.source_player; APlayer partner = record.target_player; Boolean adminInformedChange = false; //Check sender conditions if (sender.conversationPartner == null) { //No conversation partner exists. Inform of the new one. if (PlayerIsAdmin(sender) && !PlayerIsAdmin(partner) && !adminInformedChange) { OnlineAdminSayMessage("Admin " + sender.GetVerboseName() + " is now in a private conversation with " + partner.GetVerboseName(), sender.player_name); adminInformedChange = true; } else { PlayerSayMessage(sender.player_name, "You are now in a private conversation with " + partner.GetVerboseName() + ". Use /" + GetCommandByKey("player_pm_reply").command_text + " msg to reply."); } } else { //Conversation partner exists. Cancel that conversation. APlayer oldPartner = sender.conversationPartner; if (oldPartner.player_guid != partner.player_guid) { if (PlayerIsAdmin(sender) && !PlayerIsAdmin(partner) && !adminInformedChange) { OnlineAdminSayMessage("Admin " + sender.GetVerboseName() + " is now in a private conversation with " + partner.GetVerboseName(), sender.player_name); adminInformedChange = true; } else { PlayerSayMessage(sender.player_name, "Private conversation partner changed from " + oldPartner.GetVerboseName() + " to " + partner.GetVerboseName()); } } else { PlayerSayMessage(sender.player_name, "You are already in a conversation with " + oldPartner.GetVerboseName() + ". Use /" + GetCommandByKey("player_pm_reply").command_text + " msg to reply."); return; } if (PlayerIsExternal(sender.conversationPartner)) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = oldPartner.player_server.ServerID, record_orchestrate = true, command_type = GetCommandByKey("player_pm_cancel"), command_numeric = 0, target_name = oldPartner.player_name, target_player = oldPartner, source_name = sender.player_name, source_player = sender, record_message = sender.GetVerboseName() + " has left the private conversation.", record_time = UtcNow() }); } else { PlayerSayMessage(oldPartner.player_name, sender.GetVerboseName() + " has left the private conversation."); oldPartner.conversationPartner = null; } } //Assign local conversation partner sender.conversationPartner = partner; //Check for external case on new conversation partner if (PlayerIsExternal(partner)) { //Player is external, have that instance handle the needed actions QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = partner.player_server.ServerID, record_orchestrate = true, command_type = GetCommandByKey("player_pm_start"), command_numeric = 0, target_name = partner.player_name, target_player = partner, source_name = sender.player_name, source_player = sender, record_message = record.record_message, record_time = UtcNow() }); } else { //Player is local, inform them of the conversation start/change. if (partner.conversationPartner == null) { //No conversation partner exists. Inform of the new one. if (PlayerIsAdmin(partner) && !PlayerIsAdmin(sender) && !adminInformedChange) { OnlineAdminSayMessage("Admin " + sender.GetVerboseName() + " is now in a private conversation with " + partner.GetVerboseName(), sender.player_name); adminInformedChange = true; } else { partner.Say("You are now in a private conversation with " + sender.GetVerboseName() + ". Use /" + GetCommandByKey("player_pm_reply").command_text + " msg to reply."); } partner.conversationPartner = sender; } else { //Conversation partner exists. Cancel that conversation. Inform all parties. APlayer oldPartner = partner.conversationPartner; if (oldPartner.player_guid != sender.player_guid) { //Inform partner of change partner.Say("Private conversation partner changed from " + oldPartner.GetVerboseName() + " to " + sender.GetVerboseName()); //Cancel oldPartner conversation if (PlayerIsExternal(oldPartner)) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = oldPartner.player_server.ServerID, record_orchestrate = true, command_type = GetCommandByKey("player_pm_cancel"), command_numeric = 0, target_name = oldPartner.player_name, target_player = oldPartner, source_name = sender.player_name, source_player = sender, record_message = partner.GetVerboseName() + " has left the private conversation.", record_time = UtcNow() }); } else { PlayerSayMessage(oldPartner.player_name, partner.GetVerboseName() + " has left the private conversation."); oldPartner.conversationPartner = null; } } else { Log.Error("Code 14211: Inform ColColonCleaner"); return; } } partner.conversationPartner = sender; } //Post the first message to the sender PlayerSayMessage(sender.player_name, "(MSG)(" + sender.player_name + "): " + record.record_message); //Post the first message to the partner if (!PlayerIsExternal(partner)) { partner.Say("(MSG)(" + sender.player_name + "): " + record.record_message); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Private Message record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting PMSendTarget", 6); } public void PMReplyTarget(ARecord record) { Log.Debug(() => "Entering PMReplyTarget", 6); try { record.record_action_executed = true; APlayer sender = record.source_player; APlayer partner = record.target_player; if (PlayerIsExternal(partner)) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = partner.player_server.ServerID, record_orchestrate = true, command_type = GetCommandByKey("player_pm_transmit"), command_numeric = 0, target_name = partner.player_name, target_player = partner, source_name = sender.player_name, source_player = sender, record_message = record.record_message, record_time = UtcNow() }); } else { partner.Say("(MSG)(" + sender.player_name + "): " + record.record_message); } PlayerSayMessage(sender.player_name, "(MSG)(" + sender.player_name + "): " + record.record_message); } catch (Exception e) { record.record_exception = new AException("Error while taking action for Private Message Reply record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting PMReplyTarget", 6); } public void PMStartTarget(ARecord record) { Log.Debug(() => "Entering PMStartTarget", 6); try { record.record_action_executed = true; APlayer sender = record.source_player; APlayer partner = record.target_player; Boolean adminInformedChange = false; //Sender may not be in this server sender = FetchMatchingExternalOnlinePlayer(sender.player_name); if (sender == null) { return; } //Inform partner of the conversation start/change. if (partner.conversationPartner == null) { //No conversation partner exists. Inform of the new one. if (PlayerIsAdmin(partner) && !PlayerIsAdmin(sender) && !adminInformedChange) { OnlineAdminSayMessage("Admin " + sender.GetVerboseName() + " is now in a private conversation with " + partner.GetVerboseName(), sender.player_name); adminInformedChange = true; } else { partner.Say("You are now in a private conversation with " + sender.GetVerboseName() + ". Use /" + GetCommandByKey("player_pm_reply").command_text + " msg to reply."); } partner.conversationPartner = sender; } else { //Conversation partner exists. Cancel that conversation. Inform all parties. APlayer oldPartner = partner.conversationPartner; if (oldPartner.player_guid != sender.player_guid) { //Inform partner of change partner.Say("Private conversation partner changed from " + oldPartner.GetVerboseName() + " to " + sender.GetVerboseName()); //Cancel oldPartner conversation if (PlayerIsExternal(oldPartner)) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = oldPartner.player_server.ServerID, record_orchestrate = true, command_type = GetCommandByKey("player_pm_cancel"), command_numeric = 0, target_name = oldPartner.player_name, target_player = oldPartner, source_name = sender.player_name, source_player = sender, record_message = partner.GetVerboseName() + " has left the private conversation.", record_time = UtcNow() }); } else { PlayerSayMessage(oldPartner.player_name, partner.GetVerboseName() + " has left the private conversation."); oldPartner.conversationPartner = null; } } else { PlayerSayMessage(sender.player_name, "You are already in a private conversation with " + partner.GetVerboseName() + ". Use /" + GetCommandByKey("player_pm_reply").command_text + " msg to reply."); return; } } partner.conversationPartner = sender; partner.Say("(MSG)(" + sender.player_name + "): " + record.record_message); } catch (Exception e) { record.record_exception = new AException("Error while taking action for Private Message Start record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting PMStartTarget", 6); } public void PMCancelTarget(ARecord record) { Log.Debug(() => "Entering PMCancelTarget", 6); try { record.record_action_executed = true; APlayer sender = record.source_player; APlayer partner = record.target_player; if (partner.conversationPartner != null) { partner.Say(record.record_message); partner.conversationPartner = null; } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Private Message Cancel record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting PMCancelTarget", 6); } public void PMTransmitTarget(ARecord record) { Log.Debug(() => "Entering PMTransmitTarget", 6); try { record.record_action_executed = true; APlayer sender = record.source_player; APlayer partner = record.target_player; //Sender may not be in this server sender = FetchMatchingExternalOnlinePlayer(sender.player_name); if (sender == null) { return; } if (partner.conversationPartner == null || partner.conversationPartner.player_guid != sender.player_guid) { //Cancel oldPartner conversation if (PlayerIsExternal(sender)) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = sender.player_server.ServerID, record_orchestrate = true, command_type = GetCommandByKey("player_pm_cancel"), command_numeric = 0, target_name = sender.player_name, target_player = sender, source_name = partner.player_name, source_player = partner, record_message = partner.GetVerboseName() + " is not in a private conversation with you.", record_time = UtcNow() }); } else { partner.Say(partner.GetVerboseName() + " is not in a private conversation with you."); sender.conversationPartner = null; } } else { partner.Say("(MSG)(" + sender.player_name + "): " + record.record_message); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for Private Message Transmit record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting PMTransmitTarget", 6); } public void PMOnlineAdmins(ARecord record) { Log.Debug(() => "Entering PMAdmin", 6); try { record.record_action_executed = true; if (record.source_player != null && !PlayerIsAdmin(record.source_player)) { SendMessageToSource(record, "(MSG)(" + record.source_name + "): " + record.record_message); } OnlineAdminSayMessage("(MSG)(" + record.source_name + "): " + record.record_message); } catch (Exception e) { record.record_exception = new AException("Error while taking action for Private Message Admin record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting PMAdmin", 6); } public void AdminYell(ARecord record) { Log.Debug(() => "Entering adminYell", 6); try { record.record_action_executed = true; AdminYellMessage(record.record_message); if (record.record_source != ARecord.Sources.InGame && record.record_source != ARecord.Sources.ServerCommand) { SendMessageToSource(record, "Server has been told '" + record.record_message + "' by YELL"); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for AdminYell record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting adminYell", 6); } public void PlayerYell(ARecord record) { Log.Debug(() => "Entering playerYell", 6); try { record.record_action_executed = true; PlayerYellMessage(record.target_player.player_name, record.record_message); if (record.record_source != ARecord.Sources.ServerCommand) { SendMessageToSource(record, record.GetTargetNames() + " has been told '" + record.record_message + "' by YELL"); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for playerYell record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting playerYell", 6); } public void AdminTell(ARecord record) { Log.Debug(() => "Entering adminTell", 6); try { record.record_action_executed = true; AdminTellMessage(record.record_message); if (record.record_source != ARecord.Sources.InGame && record.record_source != ARecord.Sources.ServerCommand) { SendMessageToSource(record, "Server has been told '" + record.record_message + "' by TELL"); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for AdminYell record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting adminTell", 6); } public void PlayerTell(ARecord record) { Log.Debug(() => "Entering playerTell", 6); try { record.record_action_executed = true; PlayerTellMessage(record.target_player.player_name, record.record_message); if (record.record_source != ARecord.Sources.ServerCommand) { SendMessageToSource(record, record.GetTargetNames() + " has been told '" + record.record_message + "' by TELL"); } } catch (Exception e) { record.record_exception = new AException("Error while taking action for playerTell record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting playerTell", 6); } public void SendPopulationSuccess(ARecord record) { Log.Debug(() => "Entering SendPopulationSuccess", 6); try { record.record_action_executed = true; PlayerTellMessage(record.target_player.player_name, "Thank you for helping populate!"); } catch (Exception e) { record.record_exception = new AException("Error while taking action for population success record.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SendPopulationSuccess", 6); } public void SendServerRules(ARecord record) { Log.Debug(() => "Entering sendServerRules", 6); try { record.record_action_executed = true; //If server has rules if (_ServerRulesList.Length > 0) { //If requesting rules on yourself as an admin, rules should be sent to the whole server. Boolean sourceIsAdmin = ((record.source_player != null && PlayerIsAdmin(record.source_player) || record.source_player == null)); Boolean allPlayers = (sourceIsAdmin) && (record.target_player == null || record.target_name == record.source_name); if (record.source_name != record.target_name) { if (!sourceIsAdmin) { SendMessageToSource(record, "Telling server rules to " + record.GetTargetNames()); } OnlineAdminSayMessage(((sourceIsAdmin) ? ("Admin ") : ("")) + record.GetSourceName() + " told server rules to " + record.GetTargetNames() + "."); } else { OnlineAdminSayMessage(((sourceIsAdmin) ? ("Admin ") : ("")) + record.GetSourceName() + " requested server rules."); } Thread rulePrinter = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a rule printer thread.", 5); try { Thread.CurrentThread.Name = "RulePrinter"; //Wait the rule delay duration Threading.Wait(TimeSpan.FromSeconds(_ServerRulesDelay)); Int32 ruleIndex = 0; List validRules = new List(); if (_AvailableMapModes.Any() && _serverInfo.InfoObject != null && !String.IsNullOrEmpty(_serverInfo.InfoObject.Map) && _AvailableMapModes.FirstOrDefault(mapMode => mapMode.FileName == _serverInfo.InfoObject.Map && mapMode.PlayList == _serverInfo.InfoObject.GameMode) != null) { //Confirm that rule prefixes conform to the map/modes available var allMaps = _AvailableMapModes.Where(mapMode => !String.IsNullOrEmpty(mapMode.PublicLevelName)) .Select(mapMode => mapMode.PublicLevelName).Distinct().ToArray(); var allModes = _AvailableMapModes.Where(mapMode => !String.IsNullOrEmpty(mapMode.GameMode)) .Select(mapMode => mapMode.GameMode).Distinct().ToArray(); var matchingMapMode = _AvailableMapModes.First(mapMode => mapMode.FileName == _serverInfo.InfoObject.Map && mapMode.PlayList == _serverInfo.InfoObject.GameMode); var serverMap = matchingMapMode.PublicLevelName; var serverMode = matchingMapMode.GameMode; foreach (var rule in _ServerRulesList.Where(rule => !String.IsNullOrEmpty(rule))) { // Need to pull rule into a new var since foreach vars can't be modified var ruleString = rule; var useRule = true; //Check if the rule starts with any map foreach (var ruleMap in allMaps) { if (ruleString.StartsWith(ruleMap + "/")) { //Remove the map from the rule text ruleString = TrimStart(ruleString, ruleMap + "/"); if (ruleMap != serverMap) { useRule = false; } break; } } //Check if the rule starts with any mode foreach (var ruleMode in allModes) { if (ruleString.StartsWith(ruleMode + "/")) { //Remove the mode from the rule text ruleString = TrimStart(ruleString, ruleMode + "/"); if (ruleMode != serverMode) { useRule = false; } break; } } //Check again for maps, since they might have put them in a different order foreach (var ruleMap in allMaps) { if (ruleString.StartsWith(ruleMap + "/")) { //Remove the map from the rule text ruleString = TrimStart(ruleString, ruleMap + "/"); if (ruleMap != serverMap) { useRule = false; } break; } } if (useRule) { validRules.Add(ruleString); } } } else { foreach (var rule in _ServerRulesList.Where(rule => !String.IsNullOrEmpty(rule))) { validRules.Add(rule); } } foreach (string rule in validRules) { String currentPrefix = (_ServerRulesNumbers) ? ("(" + (++ruleIndex) + "/" + validRules.Count() + ") ") : (""); if (allPlayers) { AdminSayMessage(currentPrefix + GetPreMessage(rule, false)); } else { if (record.target_player != null) { record.target_player.PrintThreadID = Thread.CurrentThread.Name; if (_ServerRulesYell) { PlayerTellMessage(record.target_player.player_name, currentPrefix + GetPreMessage(rule, false)); } else { PlayerSayMessage(record.target_player.player_name, currentPrefix + GetPreMessage(rule, false)); } } else { SendMessageToSource(record, currentPrefix + GetPreMessage(rule, false)); } } Threading.Wait(TimeSpan.FromSeconds(_ServerRulesInterval)); } } catch (Exception e) { Log.HandleException(new AException("Error while printing server rules", e)); } Log.Debug(() => "Exiting a rule printer.", 5); Threading.StopWatchdog(); })); //Start the thread Threading.StartWatchdog(rulePrinter); } } catch (Exception e) { record.record_exception = new AException("Error while sending server rules.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting sendServerRules", 6); } public void SourceVoteSurrender(ARecord record) { Log.Debug(() => "Entering SourceVoteSurrender", 6); try { record.record_action_executed = true; if (EventActive()) { SendMessageToSource(record, "Surrender Vote is not available during events."); FinalizeRecord(record); return; } //Case for database added records if (!_surrenderVoteEnable) { SendMessageToSource(record, "Surrender Vote must be enabled in AdKats settings to use this command."); FinalizeRecord(record); return; } if (_roundState != RoundState.Playing) { SendMessageToSource(record, "Round state must be playing to use surrender. Current: " + _roundState); FinalizeRecord(record); return; } if (_surrenderVoteSucceeded) { SendMessageToSource(record, "Surrender already succeeded."); FinalizeRecord(record); return; } if (_surrenderVoteList.Contains(record.source_name)) { SendMessageToSource(record, "You already voted! You can cancel your vote with " + GetChatCommandByKey("command_cancel")); FinalizeRecord(record); return; } bool voteEnabled = false; ATeam team1, team2; if (!GetTeamByID(1, out team1)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } return; } if (!GetTeamByID(2, out team2)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } return; } ATeam winningTeam, losingTeam; if (team1.TeamTicketCount > team2.TeamTicketCount) { winningTeam = team1; losingTeam = team2; } else { winningTeam = team2; losingTeam = team1; } if (!_surrenderVoteActive) { Int32 playerCount = GetPlayerCount(); if (playerCount < _surrenderVoteMinimumPlayerCount) { SendMessageToSource(record, _surrenderVoteMinimumPlayerCount + " players needed to start Surrender Vote. Current: " + playerCount); FinalizeRecord(record); return; } Int32 ticketGap = Math.Abs(team1.TeamTicketCount - team2.TeamTicketCount); if (ticketGap < _surrenderVoteMinimumTicketGap) { SendMessageToSource(record, _surrenderVoteMinimumTicketGap + " ticket gap needed to start Surrender Vote. Current: " + ticketGap); FinalizeRecord(record); return; } Double ticketRateGap = Math.Abs(team1.GetTicketDifferenceRate() - team2.GetTicketDifferenceRate()); if (_surrenderVoteTicketRateGapEnable && ticketRateGap < _surrenderVoteMinimumTicketRateGap) { SendMessageToSource(record, _surrenderVoteMinimumTicketRateGap + " ticket rate gap needed to start Surrender Vote. Current: " + Math.Round(ticketRateGap, 2)); FinalizeRecord(record); return; } _surrenderVoteActive = true; voteEnabled = true; _surrenderVoteStartTime = UtcNow(); if (_surrenderVoteTimeoutEnable) { Thread surrenderTimingThread = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a surrender timing thread.", 5); try { while (_pluginEnabled && (UtcNow() - _surrenderVoteStartTime).TotalMinutes < _surrenderVoteTimeoutMinutes && !_surrenderVoteSucceeded && _surrenderVoteActive) { Threading.Wait(500); } if (!_surrenderVoteSucceeded && _roundState == RoundState.Playing && _pluginEnabled) { _surrenderVoteActive = false; _surrenderVoteList.Clear(); AdminTellMessage("Surrender Vote Timed Out. Votes removed."); } } catch (Exception) { Log.HandleException(new AException("Error while running surrender timing.")); } Log.Debug(() => "Exiting a surrender timing thread.", 5); Threading.StopWatchdog(); })); Threading.StartWatchdog(surrenderTimingThread); } } //Remove nosurrender vote if any _nosurrenderVoteList.Remove(record.source_name); //Add the vote _surrenderVoteList.Add(record.source_name); Int32 requiredVotes = (Int32)((GetPlayerCount() / 2.0) * (_surrenderVoteMinimumPlayerPercentage / 100.0)); Int32 voteCount = _surrenderVoteList.Count - _nosurrenderVoteList.Count; if (voteCount >= requiredVotes) { //Vote succeeded, trigger winning team _surrenderVoteSucceeded = true; if (!_endingRound) { _endingRound = true; Thread roundEndDelayThread = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a round end delay thread.", 5); try { Thread.CurrentThread.Name = "RoundEndDelay"; for (int i = 0; i < 8; i++) { AdminTellMessage("Surrender Vote Succeeded. " + winningTeam.TeamName + " wins!"); Thread.Sleep(50); } Threading.Wait(7000); ARecord repRecord = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("round_end"), command_numeric = winningTeam.TeamID, target_name = winningTeam.TeamName, source_name = "RoundManager", record_message = "Surrender Vote (" + winningTeam.GetTeamIDKey() + " Win)(" + winningTeam.TeamTicketCount + ":" + losingTeam.TeamTicketCount + ")(" + FormatTimeString(_serverInfo.GetRoundElapsedTime(), 3) + ")", record_time = UtcNow() }; QueueRecordForProcessing(repRecord); } catch (Exception) { Log.HandleException(new AException("Error while running round end delay.")); } Log.Debug(() => "Exiting a round end delay thread.", 5); Threading.StopWatchdog(); })); Threading.StartWatchdog(roundEndDelayThread); } } else { SendMessageToSource(record, "You voted to end the round!"); if (voteEnabled) { AdminTellMessage("Surrender Vote started! Use " + GetChatCommandByKey("self_surrender") + ", " + GetChatCommandByKey("self_votenext") + ", or " + GetChatCommandByKey("self_nosurrender") + " to vote."); } else { AdminSayMessage((requiredVotes - voteCount) + " votes needed for surrender/scramble. Use " + GetChatCommandByKey("self_surrender") + ", " + GetChatCommandByKey("self_votenext") + ", or " + GetChatCommandByKey("self_nosurrender") + " to vote."); AdminYellMessage((requiredVotes - voteCount) + " votes needed for surrender/scramble"); } OnlineAdminSayMessage(record.GetSourceName() + " voted for round surrender."); } } catch (Exception e) { record.record_exception = new AException("Error while voting surrender.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SourceVoteSurrender", 6); } public void SourceVoteNoSurrender(ARecord record) { Log.Debug(() => "Entering SourceVoteNoSurrender", 6); try { record.record_action_executed = true; if (EventActive()) { SendMessageToSource(record, "Surrender Vote is not available during events."); FinalizeRecord(record); return; } //Case for database added records if (!_surrenderVoteEnable) { SendMessageToSource(record, "Surrender Vote must be enabled in AdKats settings to use this command."); FinalizeRecord(record); return; } if (_roundState != RoundState.Playing) { SendMessageToSource(record, "Round state must be playing to vote against surrender. Current: " + _roundState); FinalizeRecord(record); return; } if (_surrenderVoteSucceeded) { SendMessageToSource(record, "Surrender already succeeded."); FinalizeRecord(record); return; } if (_nosurrenderVoteList.Contains(record.source_name)) { SendMessageToSource(record, "You already voted against surrender!"); FinalizeRecord(record); return; } if (!_surrenderVoteActive) { SendMessageToSource(record, "A surrender vote must be active to vote against it."); FinalizeRecord(record); return; } //Remove surrender vote if any _surrenderVoteList.Remove(record.source_name); //Add the vote _nosurrenderVoteList.Add(record.source_name); Int32 requiredVotes = (Int32)((GetPlayerCount() / 2.0) * (_surrenderVoteMinimumPlayerPercentage / 100.0)); Int32 voteCount = _surrenderVoteList.Count - _nosurrenderVoteList.Count; SendMessageToSource(record, "You voted against ending the round!"); AdminSayMessage((requiredVotes - voteCount) + " votes needed for surrender/scramble. Use " + GetChatCommandByKey("self_surrender") + ", " + GetChatCommandByKey("self_votenext") + ", or " + GetChatCommandByKey("self_nosurrender") + " to vote."); AdminYellMessage((requiredVotes - voteCount) + " votes needed for surrender/scramble"); OnlineAdminSayMessage(record.GetSourceName() + " voted against round surrender."); } catch (Exception e) { record.record_exception = new AException("Error while voting against surrender.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SourceVoteNoSurrender", 6); } public void SendServerCommands(ARecord record) { Log.Debug(() => "Entering SendServerCommands", 6); try { record.record_action_executed = true; Thread commandPrinter = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a command printer thread.", 5); try { Thread.CurrentThread.Name = "CommandPrinter"; List fullCommandList = new List(); foreach (ACommand aCommand in _CommandIDDictionary.Values.ToList().Where(dCommand => dCommand.command_active == ACommand.CommandActive.Active)) { if (record.target_player == null || HasAccess(record.target_player, aCommand)) { fullCommandList.Add("!" + aCommand.command_text); } } if (record.target_player == null || PlayerIsAdmin(record.target_player)) { fullCommandList.AddRange(_ExternalAdminCommands); } else { fullCommandList.AddRange(_ExternalPlayerCommands); } List> commandSplits = fullCommandList.Select((x, i) => new { Index = i, Value = x }).GroupBy(x => x.Index / 5).Select(x => x.Select(v => v.Value).ToList()).ToList(); foreach (List curCommands in commandSplits) { String curCommandsStr = ""; foreach (String cur in curCommands) { curCommandsStr += cur + ", "; } SendMessageToSource(record, curCommandsStr); Threading.Wait(TimeSpan.FromSeconds(2)); } } catch (Exception) { Log.HandleException(new AException("Error while printing server commands")); } Log.Debug(() => "Exiting a command printer.", 5); Threading.StopWatchdog(); })); //Start the thread Threading.StartWatchdog(commandPrinter); if (record.source_name != record.target_name) { SendMessageToSource(record, "Telling server commands to " + record.GetTargetNames()); } } catch (Exception e) { record.record_exception = new AException("Error while sending server commands.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SendServerCommands", 6); } public void SendTargetRep(ARecord record) { Log.Debug(() => "Entering SendTargetRep", 6); try { record.record_action_executed = true; if (record.target_player == null) { SendMessageToSource(record, "Reputation fetch player not found, unable to continue."); FinalizeRecord(record); return; } record.command_numeric = (Int32)record.target_player.player_reputation; record.record_message = record.target_player.player_name + "'s reputation is " + Math.Round(record.target_player.player_reputation, 2); var isAdmin = PlayerIsAdmin(record.target_player); var points = FetchPoints(record.target_player, false, true); if (record.source_name == record.target_name) { String repMessage = "Your server reputation is " + Math.Round(record.target_player.player_reputation, 2) + ", with "; if (points > 0) { repMessage += points + " infraction point(s). "; } else { repMessage += "a clean infraction record. "; } if (!isAdmin && points < 1 && record.target_player.player_reputation > _reputationThresholdBad) { repMessage += "Thank you for helping the admins!"; } if (points > 0 && _AutomaticForgives) { var forgiveTime = UtcNow(); if (record.target_player.LastForgive != null) { var forgiveDiff = record.target_player.LastForgive.record_time.AddDays(_AutomaticForgiveLastForgiveDays); if (forgiveDiff > forgiveTime) { forgiveTime = forgiveDiff; } } if (record.target_player.LastPunishment != null) { var punishDiff = record.target_player.LastPunishment.record_time.AddDays(_AutomaticForgiveLastPunishDays); if (punishDiff > forgiveTime) { forgiveTime = punishDiff; } } repMessage += Environment.NewLine + "Next auto-forgive after you spawn " + FormatNowDuration(forgiveTime, 2) + " from now."; } SendMessageToSource(record, repMessage); } else { String repMessage = record.GetTargetNames() + "'s server reputation is " + Math.Round(record.target_player.player_reputation, 2) + ", with "; if (points > 0) { repMessage += points + " infraction point(s). "; } else { repMessage += "a clean infraction record. "; } SendMessageToSource(record, repMessage); } } catch (Exception e) { record.record_exception = new AException("Error while sending server rep.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SendTargetRep", 6); } public void SendTargetIsAdmin(ARecord record) { Log.Debug(() => "Entering SendTargetIsAdmin", 6); try { record.record_action_executed = true; if (record.target_player == null) { SendMessageToSource(record, "Player not found, unable to continue."); FinalizeRecord(record); return; } if (record.source_name == record.target_name) { SendMessageToSource(record, "You are " + ((PlayerIsAdmin(record.source_player)) ? ("") : ("not ")) + "an admin. [" + record.source_player.player_role.role_name + "]"); } else { SendMessageToSource(record, record.target_player.GetVerboseName() + " is " + ((PlayerIsAdmin(record.target_player)) ? ("") : ("not ")) + "an admin. [" + record.target_player.player_role.role_name + "]"); } } catch (Exception e) { record.record_exception = new AException("Error while sending admin status.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SendTargetIsAdmin", 6); } public void SendUptime(ARecord record) { Log.Debug(() => "Entering SendUptime", 6); try { record.record_action_executed = true; Thread uptimePrinter = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a uptime printer thread.", 5); try { Thread.CurrentThread.Name = "UptimePrinter"; SendMessageToSource(record, "Server: " + FormatTimeString(TimeSpan.FromSeconds(_serverInfo.InfoObject.ServerUptime), 10)); Threading.Wait(3000); SendMessageToSource(record, "Procon: " + FormatNowDuration(_proconStartTime, 10)); Threading.Wait(3000); SendMessageToSource(record, "AdKats " + PluginVersion + ": " + FormatNowDuration(_AdKatsRunningTime, 10)); Threading.Wait(3000); SendMessageToSource(record, "Last Player List: " + FormatNowDuration(_LastPlayerListProcessed, 10) + " ago"); Threading.Wait(3000); SendMessageToSource(record, "Server has been in " + _populationStatus.ToString().ToLower() + " population for " + FormatNowDuration(_populationTransitionTime, 3)); Double totalPopulationDuration = _populationDurations[PopulationState.Low].TotalSeconds + _populationDurations[PopulationState.Medium].TotalSeconds + _populationDurations[PopulationState.High].TotalSeconds; if (totalPopulationDuration > 0) { Threading.Wait(5000); Int32 lowPopPercentage = (int)Math.Round(_populationDurations[PopulationState.Low].TotalSeconds / totalPopulationDuration * 100); Int32 medPopPercentage = (int)Math.Round(_populationDurations[PopulationState.Medium].TotalSeconds / totalPopulationDuration * 100); Int32 highPopPercentage = (int)Math.Round(_populationDurations[PopulationState.High].TotalSeconds / totalPopulationDuration * 100); SendMessageToSource(record, "Population since AdKats start: " + lowPopPercentage + "% low. " + medPopPercentage + "% medium. " + highPopPercentage + "% high."); } } catch (Exception) { Log.HandleException(new AException("Error while printing uptime")); } Log.Debug(() => "Exiting a uptime printer.", 5); Threading.StopWatchdog(); })); //Start the thread Threading.StartWatchdog(uptimePrinter); } catch (Exception e) { record.record_exception = new AException("Error while sending uptime.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SendUptime", 6); } public void SendPlayerReports(ARecord record) { Log.Debug(() => "Entering SendPlayerReports", 6); try { record.record_action_executed = true; List lastMissedReports = FetchActivePlayerReports().OrderByDescending(aRecord => aRecord.record_time).Take(6).Reverse().ToList(); Boolean listed = false; foreach (ARecord rRecord in lastMissedReports) { String location; if (rRecord.target_player.player_online) { location = "/" + GetPlayerTeamKey(rRecord.target_player) + "/" + (_PlayerDictionary.Values.Where(aPlayer => aPlayer.fbpInfo.TeamID == rRecord.target_player.fbpInfo.TeamID).OrderBy(aPlayer => aPlayer.fbpInfo.Score).Reverse().ToList().IndexOf(rRecord.target_player) + 1); } else { location = ""; } SendMessageToSource(record, "(" + rRecord.command_numeric + ")(" + FormatTimeString(UtcNow() - rRecord.record_time, 2) + ")(" + rRecord.GetTargetNames() + location + "):" + rRecord.record_message); Thread.Sleep(30); listed = true; } if (!listed) { SendMessageToSource(record, "No missed player reports were found."); } } catch (Exception e) { record.record_exception = new AException("Error while sending player reports.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SendPlayerReports", 6); } public void RebootPlugin(ARecord record) { Log.Debug(() => "Entering RebootPlugin", 6); try { record.record_action_executed = true; _pluginRebootOnDisable = true; if (record.record_source == ARecord.Sources.InGame) { _pluginRebootOnDisableSource = record.source_name; } SendMessageToSource(record, "Rebooting AdKats shortly."); //Run the reboot delay thread Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "RebootDelay"; Thread.Sleep(10000); Disable(); Threading.StopWatchdog(); }))); } catch (Exception e) { record.record_exception = new AException("Error while rebooting plugin.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting RebootPlugin", 6); } public void UpdatePlugin(ARecord record) { Log.Debug(() => "Entering UpdatePlugin", 6); try { record.record_action_executed = true; _pluginUpdateCaller = record; CheckForPluginUpdates(true); } catch (Exception e) { record.record_exception = new AException("Error while rebooting plugin.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting UpdatePlugin", 6); } public void ShutdownServer(ARecord record) { Log.Debug(() => "Entering ShutdownServer", 6); try { record.record_action_executed = true; Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "ShutdownRunner"; AdminTellMessage("SERVER RESTART IN 5..."); Thread.Sleep(1000); AdminTellMessage("SERVER RESTART IN 4..."); Thread.Sleep(1000); AdminTellMessage("SERVER RESTART IN 3..."); Thread.Sleep(1000); AdminTellMessage("SERVER RESTART IN 2..."); Thread.Sleep(1000); AdminTellMessage("SERVER RESTART IN 1..."); Thread.Sleep(1000); AdminTellMessage("REBOOTING SERVER"); ExecuteCommand("procon.protected.send", "admin.shutDown"); if (record.source_name == "AutoAdmin" && _automaticServerRestart && _automaticServerRestartProcon) { // Wait 30 seconds for the server to reboot Thread.Sleep(TimeSpan.FromSeconds(30)); Environment.Exit(2232); } Threading.StopWatchdog(); }))); } catch (Exception e) { record.record_exception = new AException("Error while shutting down server.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting ShutdownServer", 6); } public void SendTargetInfo(ARecord record) { Log.Debug(() => "Entering SendTargetInfo", 6); try { record.record_action_executed = true; Thread infoPrinter = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a player info printer thread.", 5); try { Thread.CurrentThread.Name = "PlayerInfoPrinter"; if (record.target_player == null) { Log.Error("Player null in player info printer."); return; } Threading.Wait(500); String playerInfo = record.target_player.GetVerboseName() + ": " + record.target_player.player_id + ", " + record.target_player.player_role.role_name; if (record.target_player != null && record.target_player.fbpInfo != null) { if (record.target_player.player_online) { playerInfo += ", " + GetPlayerTeamName(record.target_player) + "/" + (_PlayerDictionary.Values.Where(aPlayer => aPlayer.fbpInfo.TeamID == record.target_player.fbpInfo.TeamID).OrderBy(aPlayer => aPlayer.fbpInfo.Score).Reverse().ToList().IndexOf(record.target_player) + 1) + "/" + record.target_player.fbpInfo.Score; } else { playerInfo += ", OFFLINE"; } } SendMessageToSource(record, playerInfo); Threading.Wait(2000); SendMessageToSource(record, "First seen: " + FormatTimeString(UtcNow() - record.target_player.player_firstseen, 3) + " ago."); Threading.Wait(2000); if (_PlayerDictionary.ContainsKey(record.target_player.player_name)) { var duration = record.target_player.player_serverplaytime + NowDuration(record.target_player.JoinTime); SendMessageToSource(record, "Time on server: " + Math.Round(duration.TotalHours, 1) + "hrs (" + FormatTimeString(duration, 3) + ")."); } else { var duration = record.target_player.player_serverplaytime; SendMessageToSource(record, "Time on server: " + Math.Round(duration.TotalHours, 1) + "hrs (" + FormatTimeString(duration, 3) + ")."); } Threading.Wait(2000); String playerLoc = "Unknown"; if (!String.IsNullOrEmpty(record.target_player.player_ip)) { IPAPILocation loc = record.target_player.location; if (loc != null && loc.status == "success") { playerLoc = String.Empty; if (!String.IsNullOrEmpty(loc.city)) { playerLoc += loc.city + ", "; } if (!String.IsNullOrEmpty(loc.regionName)) { playerLoc += loc.regionName + ", "; } playerLoc += loc.country; List locRecords = FetchRecentRecords(record.target_player.player_id, GetCommandByKey("player_changeip").command_id, 1000, 50, true, false).Where(aRecord => aRecord.record_message != "No previous IP on record").ToList(); if (locRecords.Any()) { playerLoc += " with " + locRecords.GroupBy(locRecord => locRecord.record_message).Select(group => group.First()).Count() + " different IPs."; } } } else { playerLoc = "Player IP not found"; } SendMessageToSource(record, "Location: " + playerLoc); Threading.Wait(2000); IEnumerable reportsFrom = _PlayerReports.Where(aRecord => aRecord.source_name == record.target_name); IEnumerable reportsAgainst = _PlayerReports.Where(aRecord => aRecord.target_name == record.target_name); String playerReps = "None from or against."; if (reportsAgainst.Any() || reportsFrom.Any()) { playerReps = String.Empty; if (reportsAgainst.Any()) { playerReps += "[" + reportsAgainst.Count() + " against:"; playerReps = reportsAgainst.Aggregate(playerReps, (current, playerRep) => current + (" (" + ((playerRep.isContested) ? ("-CONTESTED- ") : ("")) + playerRep.record_message + ")")); playerReps += "]"; } if (reportsFrom.Any()) { playerReps += "[" + reportsFrom.Count() + " from:"; playerReps = reportsFrom.Aggregate(playerReps, (current, playerRep) => current + (" (" + ((playerRep.isContested) ? ("-CONTESTED- ") : ("")) + playerRep.record_message + ")")); playerReps += "]"; } } SendMessageToSource(record, "Reports: " + playerReps); Threading.Wait(2000); //Infraction Points String playerInf = "Player in good standing."; Int64 infPoints = FetchPoints(record.target_player, false, true); if (infPoints > 0) { playerInf = infPoints + " points."; } SendMessageToSource(record, "Infractions: " + playerInf); Threading.Wait(2000); //Last Punishment String lastPunishText = "No punishments found."; List punishments = FetchRecentRecords(record.target_player.player_id, GetCommandByKey("player_punish").command_id, 1000, 1, true, false); if (punishments.Any()) { ARecord lastPunish = punishments[0]; lastPunishText = FormatTimeString(UtcNow() - lastPunish.record_time, 2) + " ago by " + lastPunish.GetSourceName() + ": " + lastPunish.record_message; } SendMessageToSource(record, "Last Punishment: " + lastPunishText); Threading.Wait(2000); //Last Forgive String lastForgiveText = "No forgives found."; List forgives = FetchRecentRecords(record.target_player.player_id, GetCommandByKey("player_forgive").command_id, 1000, 1, true, false); if (forgives.Any()) { ARecord lastForgive = forgives[0]; lastForgiveText = FormatTimeString(UtcNow() - lastForgive.record_time, 2) + " ago by " + lastForgive.GetSourceName() + ": " + lastForgive.record_message; } SendMessageToSource(record, "Last Forgive: " + lastForgiveText); Threading.Wait(2000); //Rules requests String rulesRequestsText = "Player has never requested rules."; List rulesRequests = FetchRecentRecords(record.target_player.player_id, GetCommandByKey("self_rules").command_id, 1000, 50, true, false); if (rulesRequests.Any(innerRecord => innerRecord.source_player != null && innerRecord.source_player.player_id == record.target_player.player_id)) { ARecord lastRulesRequest = rulesRequests[0]; rulesRequestsText = FormatTimeString(UtcNow() - lastRulesRequest.record_time, 2) + " ago."; } SendMessageToSource(record, "Last Rules Request: " + rulesRequestsText); Threading.Wait(2000); //Ping Kicks String pingKicksText = "Player never kicked for ping."; IEnumerable pingKicks = FetchRecentRecords(record.target_player.player_id, GetCommandByKey("player_kick").command_id, 1000, 50, true, false).Where(innerRecord => innerRecord.source_name == "PingEnforcer"); if (pingKicks.Any()) { pingKicksText = "Kicked " + pingKicks.Count() + " time(s) for high ping."; } SendMessageToSource(record, "Ping Kicks: " + pingKicksText + " Current Ping [" + ((record.target_player.player_ping_avg > 0) ? (Math.Round(record.target_player.player_ping_avg, 2).ToString()) : ("Missing")) + "]."); Threading.Wait(2000); //Reputation SendMessageToSource(record, "Reputation: " + Math.Round(record.target_player.player_reputation, 2)); Threading.Wait(2000); //Previous Names String playerNames = record.target_player.player_name; List nameRecords = FetchRecentRecords(record.target_player.player_id, GetCommandByKey("player_changename").command_id, 1000, 50, true, false).GroupBy(nameRecord => nameRecord.record_message).Select(group => group.First()).ToList(); if (nameRecords.Any(rec => !String.IsNullOrEmpty(rec.record_message))) { var previousNames = nameRecords.Where(rec => !String.IsNullOrEmpty(rec.record_message) && rec.record_message != record.target_player.player_name).Select(rec => rec.record_message).ToArray(); playerNames += ", " + String.Join(", ", previousNames); } SendMessageToSource(record, "Player names: " + playerNames); //Previous Tags String playerTags = ""; List playerTagList = new List(); if (!String.IsNullOrEmpty(record.target_player.player_clanTag)) { playerTagList.Add(record.target_player.player_clanTag); } List tagRecords = FetchRecentRecords(record.target_player.player_id, GetCommandByKey("player_changetag").command_id, 1000, 50, true, false).GroupBy(tagRecord => tagRecord.record_message).Select(group => group.First()).ToList(); var previousTags = tagRecords.Where(rec => !String.IsNullOrEmpty(rec.record_message) && rec.record_message != record.target_player.player_clanTag).Select(rec => rec.record_message).ToList(); playerTagList.AddRange(previousTags); playerTagList = playerTagList.Distinct().ToList(); if (playerTagList.Any()) { playerTags = String.Join(", ", playerTagList.ToArray()); } if (String.IsNullOrEmpty(playerTags)) { playerTags = "No clan tags."; } SendMessageToSource(record, "Player tags: " + playerTags); } catch (Exception) { Log.HandleException(new AException("Error while printing player info")); } Log.Debug(() => "Exiting a player info printer.", 5); Threading.StopWatchdog(); })); //Start the thread Threading.StartWatchdog(infoPrinter); } catch (Exception e) { record.record_exception = new AException("Error while sending player info.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SendTargetInfo", 6); } public void SendTargetPerks(ARecord record) { Log.Debug(() => "Entering SendTargetPerks", 6); try { record.record_action_executed = true; Thread perkPrinter = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a player perk printer thread.", 5); try { Thread.CurrentThread.Name = "PlayerPerkPrinter"; if (record.target_player == null) { Log.Error("Player null in player perk printer."); return; } Threading.Wait(500); var asPlayers = GetMatchingVerboseASPlayers(record.target_player); if (!asPlayers.Any()) { if (record.source_name == record.target_name) { SendMessageToSource(record, "You do not have any active perks. Contact your admin for more information!"); } else { SendMessageToSource(record, record.target_player.GetVerboseName() + " does not have any active perks."); } FinalizeRecord(record); return; } if (record.source_name == record.target_name) { SendMessageToSource(record, "Showing your active perks:"); } else { SendMessageToSource(record, "Showing " + record.target_player.GetVerboseName() + "'s active perks:"); } foreach (var groupKey in _PerkSpecialPlayerGroups) { var asPlayer = asPlayers.FirstOrDefault(dPlayer => dPlayer.player_group.group_key == groupKey); if (asPlayer != null) { Threading.Wait(1000); var expireDuration = NowDuration(asPlayer.player_expiration); String expiration = (expireDuration.TotalDays > 500.0) ? ("Permanent") : (FormatTimeString(expireDuration, 3)); var groupName = !String.IsNullOrEmpty(asPlayer.tempCreationType) ? asPlayer.tempCreationType : asPlayer.player_group.group_name; SendMessageToSource(record, groupName + ": " + expiration); if (groupKey == "slot_reserved" && record.target_player != null && record.target_player.player_role != null && record.target_player.player_role.RoleAllowedCommands != null && (_ReservedSelfKill || _ReservedSelfMove || _ReservedSquadLead)) { var allowedCommands = record.target_player.player_role.RoleAllowedCommands; if (!allowedCommands.ContainsKey("self_lead") && _ReservedSquadLead) { Threading.Wait(1000); SendMessageToSource(record, GetChatCommandByKey("self_lead") + " Command Access: " + expiration); } if (!allowedCommands.ContainsKey("self_teamswap") && _ReservedSelfMove) { Threading.Wait(1000); SendMessageToSource(record, GetChatCommandByKey("self_teamswap") + " Command Access: " + expiration); } if (!allowedCommands.ContainsKey("self_kill") && _ReservedSelfKill) { Threading.Wait(1000); SendMessageToSource(record, GetChatCommandByKey("self_kill") + " Command Access: " + expiration); } } } } } catch (Exception) { Log.HandleException(new AException("Error while printing player perks")); } Log.Debug(() => "Exiting a player perk printer.", 5); Threading.StopWatchdog(); })); //Start the thread Threading.StartWatchdog(perkPrinter); } catch (Exception e) { record.record_exception = new AException("Error while sending player perks.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SendTargetPerks", 6); } public void TriggerTargetPoll(ARecord record) { Log.Debug(() => "Entering TriggerTargetPoll", 6); try { record.record_action_executed = true; if (_roundState != RoundState.Playing) { SendMessageToSource(record, record.command_type.command_name + " cannot be used between rounds."); FinalizeRecord(record); return; } //Take care of the previous poll if one exists if (_ActivePoll != null) { // Automatically cancel the previous poll SendMessageToSource(record, "Cancelling current " + _ActivePoll.ID + " poll."); _ActivePoll.Canceled = true; var cancelTime = UtcNow(); while (_ActivePoll != null) { if (!_pluginEnabled || NowDuration(cancelTime).TotalSeconds > 10) { SendMessageToSource(record, "Unable to cancel previous poll."); FinalizeRecord(record); return; } Threading.Wait(500); }; AdminSayMessage("Current poll canceled."); } //Determine whether this poll can be executed if (record.target_name == "event") { // Check for options var optionsString = record.record_message.ToLower().Trim(); if (optionsString.Contains("reset") && !optionsString.Contains("start")) { if (!EventActive()) { // This option will clear the existing event options from the plugin and start new SendMessageToSource(record, "Removing all existing event rounds."); _EventRoundOptions.Clear(); QueueSettingForUpload(new CPluginVariable(@"Event Round Codes", typeof(String[]), _EventRoundOptions.Select(round => round.getCode()).ToArray())); UpdateSettingPage(); } else { SendMessageToSource(record, "Cannot remove existing event rounds while an event is active."); } } if (optionsString.Contains("start") && _EventRoundOptions.Any()) { // If the event isn't active, make the event start next round. if (!EventActive()) { _CurrentEventRoundNumber = _roundID + 1; QueueSettingForUpload(new CPluginVariable(@"Event Current Round Number", typeof(Int32), _CurrentEventRoundNumber)); SendMessageToSource(record, "Event will be started next round."); FinalizeRecord(record); return; } } // Run the event poll Thread pollRunner = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting an event poll runner thread.", 5); try { Thread.CurrentThread.Name = "EventPollRunner"; Threading.Wait(500); //Create the poll object _ActivePoll = new APoll(this) { ID = "EVENT" }; _EventRoundPolled = true; // This poll has two stages. Choosing the rules and choosing the mode. AEventOption.RuleCode chosenRule = AEventOption.RuleCode.UNKNOWN; AEventOption.ModeCode chosenMode = AEventOption.ModeCode.UNKNOWN; AEventOption.MapCode chosenMap = AEventOption.MapCode.UNKNOWN; ///////////////////////////RULE POLL///////////////////////////// DoEventRulePoll(chosenRule, chosenMode, chosenMap); if (_ActivePoll.Canceled) { AdminSayMessage("Poll canceled."); } } catch (Exception e) { Log.HandleException(new AException("Error while processing event poll.", e)); } Threading.Wait(500); // Remove the active poll _ActivePoll = null; Log.Debug(() => "Exiting an event poll runner thread.", 5); Threading.StopWatchdog(); })); //Start the thread Threading.StartWatchdog(pollRunner); } else { SendMessageToSource(record, "Unable to process event code '" + record.target_name + "'."); FinalizeRecord(record); return; } } catch (Exception e) { record.record_exception = new AException("Error while processing general poll.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting TriggerTargetPoll", 6); } private void DoEventRulePoll(AEventOption.RuleCode chosenRule, AEventOption.ModeCode chosenMode, AEventOption.MapCode chosenMap) { Log.Debug(() => "Entering DoEventRulePoll", 3); try { ///////////////////////////RULE POLL///////////////////////////// _ActivePoll.Title = "Choose event rules with !#"; // Get the available rule options var existingEventRules = _EventRoundOptions .Select(option => option.Rule) .Distinct() .ToList(); var rng = new Random(Environment.TickCount); var availableRuleOptions = _EventRoundPollOptions .Where(option => !existingEventRules.Contains(option.Rule)) .Select(option => option.Rule) .Distinct() .OrderBy(option => rng.Next()) .ToList(); // Conditionally remove some rule options if (_populationStatus != PopulationState.High) { // In medium/low population, do not allow melee/explosive rules to show up availableRuleOptions.Remove(AEventOption.RuleCode.DO); availableRuleOptions.Remove(AEventOption.RuleCode.EO); availableRuleOptions.Remove(AEventOption.RuleCode.GO); availableRuleOptions.Remove(AEventOption.RuleCode.KO); availableRuleOptions.Remove(AEventOption.RuleCode.RTO); availableRuleOptions.Remove(AEventOption.RuleCode.TR); } if (availableRuleOptions.Count() <= 3) { availableRuleOptions = _EventRoundPollOptions .Select(option => option.Rule) .Distinct() .OrderBy(option => rng.Next()) .ToList(); // Conditionally remove some rule options if (_populationStatus != PopulationState.High) { // In medium/low population, do not allow melee/explosive rules to show up availableRuleOptions.Remove(AEventOption.RuleCode.DO); availableRuleOptions.Remove(AEventOption.RuleCode.EO); availableRuleOptions.Remove(AEventOption.RuleCode.GO); availableRuleOptions.Remove(AEventOption.RuleCode.KO); availableRuleOptions.Remove(AEventOption.RuleCode.RTO); availableRuleOptions.Remove(AEventOption.RuleCode.TR); } } List chosenRules = new List(); // Add the remaining available rules to the chosen list foreach (var rule in availableRuleOptions) { if (!chosenRules.Contains(rule)) { chosenRules.Add(rule); } } foreach (var option in chosenRules) { if (_ActivePoll.Options.Count() >= _EventPollMaxOptions) { break; } // Add the name of the option to the chosen list _ActivePoll.AddOption(AEventOption.RuleNames[option], false); } if (_EventRoundOptions.Count() >= _EventRoundAutoPollsMax) { _ActivePoll.AddOption(AEventOption.RuleNames[AEventOption.RuleCode.ENDEVENT], false); } while (_pluginEnabled && _roundState == RoundState.Playing && NowDuration(_ActivePoll.StartTime) < _PollMaxDuration && _ActivePoll.Votes.Count() < _PollMaxVotes && !_ActivePoll.Completed && !_ActivePoll.Canceled) { if (NowDuration(_ActivePoll.PrintTime) > _PollPrintInterval) { // Print the poll _ActivePoll.PrintPoll(_eventPollYellWinningRule); } Threading.Wait(100); } if (_ActivePoll.Completed) { AdminSayMessage("Event rule poll completed with current winner."); } // Only continue if the poll has not been canceled if (_pluginEnabled && !_ActivePoll.Canceled) { // Get the outcome var ruleString = _ActivePoll.GetWinningOption("won", false); chosenRule = AEventOption.RuleFromDisplay(ruleString); if (chosenRule == AEventOption.RuleCode.ENDEVENT) { for (int i = 0; i < 6; i++) { AdminTellMessage("Event ended by vote. Normal rules next round."); Thread.Sleep(500); } } else { ///////////////////////////MODE POLL///////////////////////////// DoEventModePoll(chosenRule, chosenMode, chosenMap); } } } catch (Exception e) { Log.HandleException(new AException("Error while handling poll completion.", e)); } Log.Debug(() => "Exiting DoEventRulePoll", 3); } private void DoEventModePoll(AEventOption.RuleCode chosenRule, AEventOption.ModeCode chosenMode, AEventOption.MapCode chosenMap) { Log.Debug(() => "Entering DoEventModePoll", 3); try { ///////////////////////////MODE POLL///////////////////////////// // Get the available mode options for the chosen rule var rng = new Random(Environment.TickCount); var availableModeOptions = _EventRoundPollOptions .Where(option => option.Rule == chosenRule) .Select(option => option.Mode) .Distinct() .OrderBy(option => rng.Next()) .ToList(); if (availableModeOptions.Count() == 1) { // There is only one option for the poll, so just select it chosenMode = availableModeOptions.First(); ///////////////////////////MAP POLL///////////////////////////// DoEventMapPoll(chosenRule, chosenMode, chosenMap); } else { // Reset the poll for the next stage _ActivePoll.Reset(); _ActivePoll.Title = "Choose '" + AEventOption.RuleNames[chosenRule] + "' mode with !#"; foreach (var option in availableModeOptions) { if (_ActivePoll.Options.Count() >= _EventPollMaxOptions) { break; } // Add the name of the option to the chosen list _ActivePoll.AddOption(AEventOption.ModeNames[option], false); } while (_pluginEnabled && _roundState == RoundState.Playing && NowDuration(_ActivePoll.StartTime) < _PollMaxDuration && _ActivePoll.Votes.Count() < _PollMaxVotes && !_ActivePoll.Completed && !_ActivePoll.Canceled) { if (NowDuration(_ActivePoll.PrintTime) > _PollPrintInterval) { // Print the poll _ActivePoll.PrintPoll(_eventPollYellWinningRule); } Threading.Wait(100); } if (_ActivePoll.Completed) { AdminSayMessage("Event mode poll completed with current winner."); } // Only continue if the poll has not been canceled if (_pluginEnabled && !_ActivePoll.Canceled) { // Get the outcome chosenMode = AEventOption.ModeFromDisplay(_ActivePoll.GetWinningOption("won", false)); ///////////////////////////MAP POLL///////////////////////////// DoEventMapPoll(chosenRule, chosenMode, chosenMap); } } } catch (Exception e) { Log.HandleException(new AException("Error while handling poll completion.", e)); } Log.Debug(() => "Exiting DoEventModePoll", 3); } private void DoEventMapPoll(AEventOption.RuleCode chosenRule, AEventOption.ModeCode chosenMode, AEventOption.MapCode chosenMap) { Log.Debug(() => "Entering DoEventMapPoll", 3); try { ///////////////////////////MAP POLL///////////////////////////// // Get the available map options for the chosen rule var rng = new Random(Environment.TickCount); var availableMapOptions = _EventRoundPollOptions .Where(option => option.Rule == chosenRule && option.Mode == chosenMode) .Select(option => option.Map) .Distinct() .OrderBy(option => rng.Next()) .ToList(); if (availableMapOptions.Count() == 1) { // There is only one option for the poll, so just select it and finish chosenMap = availableMapOptions.First(); ///////////////////////////COMPLETION////////////////////// DoEventPollCompletion(chosenRule, chosenMode, chosenMap); } else { // Reset the poll for the next stage _ActivePoll.Reset(); _ActivePoll.Title = "Choose '" + AEventOption.RuleNames[chosenRule] + "' map with !#"; foreach (var option in availableMapOptions) { if (_ActivePoll.Options.Count() >= _EventPollMaxOptions) { break; } // Add the name of the option to the chosen list _ActivePoll.AddOption(AEventOption.MapNames[option], false); } while (_pluginEnabled && _roundState == RoundState.Playing && NowDuration(_ActivePoll.StartTime) < _PollMaxDuration && _ActivePoll.Votes.Count() < _PollMaxVotes && !_ActivePoll.Completed && !_ActivePoll.Canceled) { if (NowDuration(_ActivePoll.PrintTime) > _PollPrintInterval) { // Print the poll // Do not announce the current map leader _ActivePoll.PrintPoll(false); } Threading.Wait(100); } if (_ActivePoll.Completed) { AdminSayMessage("Event map poll completed with current winner."); } // Only continue if the poll has not been canceled if (_pluginEnabled && !_ActivePoll.Canceled) { // Get the outcome chosenMap = AEventOption.MapFromDisplay(_ActivePoll.GetWinningOption("won", false)); ///////////////////////////COMPLETION////////////////////// DoEventPollCompletion(chosenRule, chosenMode, chosenMap); } } } catch (Exception e) { Log.HandleException(new AException("Error while handling poll completion.", e)); } Log.Debug(() => "Exiting DoEventMapPoll", 3); } private void DoEventPollCompletion(AEventOption.RuleCode chosenRule, AEventOption.ModeCode chosenMode, AEventOption.MapCode chosenMap) { Log.Debug(() => "Entering DoEventPollCompletion", 3); try { ///////////////////////////COMPLETION////////////////////// var option = new AEventOption() { Map = chosenMap, Mode = chosenMode, Rule = chosenRule }; _EventRoundOptions.Add(option); QueueSettingForUpload(new CPluginVariable(@"Event Round Codes", typeof(String[]), _EventRoundOptions.Select(round => round.getCode()).ToArray())); AdminTellMessage("EVENT POLL COMPLETE! Next event round is " + option.getDisplay()); // If the event isn't active or set up with a date, make the event start next round. if (!EventActive() && _CurrentEventRoundNumber == 999999) { _CurrentEventRoundNumber = _roundID + 1; QueueSettingForUpload(new CPluginVariable(@"Event Current Round Number", typeof(Int32), _CurrentEventRoundNumber)); } UpdateSettingPage(); } catch (Exception e) { Log.HandleException(new AException("Error while handling poll completion.", e)); } Log.Debug(() => "Exiting DoEventPollCompletion", 3); } public void SendTargetChat(ARecord record) { Log.Debug(() => "Entering SendTargetChat", 6); try { record.record_action_executed = true; Thread chatPrinter = new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a player chat printer thread.", 5); try { Thread.CurrentThread.Name = "PlayerChatPrinter"; if (record.target_player != null) { List> chatList = FetchChat(record.target_player.player_id, record.command_numeric, 30); if (chatList.Any()) { int index = 1; foreach (KeyValuePair chatLine in chatList) { SendMessageToSource(record, "(" + index++ + ") " + chatLine.Value); Threading.Wait(2000); } } else { SendMessageToSource(record, "Target player(s) have no chat to fetch."); } } else if (record.TargetPlayersLocal.Count == 2) { long firstPlayerID = record.TargetPlayersLocal[0].player_id; long secondPlayerID = record.TargetPlayersLocal[1].player_id; List>> chatList = FetchConversation(firstPlayerID, secondPlayerID, record.command_numeric, 30); if (chatList.Any()) { int index = 1; foreach (KeyValuePair> chatLine in chatList) { SendMessageToSource(record, "(" + index++ + "/" + chatLine.Value.Key + ") " + chatLine.Value.Value); Threading.Wait(2000); } } else { SendMessageToSource(record, "Target player(s) have no chat to fetch."); } } else { Log.Error("Invalid target conditions when printing chat."); SendMessageToSource(record, "Unable to fetch chat for target players."); } } catch (Exception) { Log.HandleException(new AException("Error while printing player chat")); } Log.Debug(() => "Exiting a player chat printer.", 5); Threading.StopWatchdog(); })); //Start the thread Threading.StartWatchdog(chatPrinter); } catch (Exception e) { record.record_exception = new AException("Error while sending player chat.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SendTargetChat", 6); } public void FindTarget(ARecord record) { Log.Debug(() => "Entering FindTarget", 6); try { record.record_action_executed = true; if (record.target_player == null) { Log.Error("Player null when finding player."); return; } String playerInfo = record.GetTargetNames() + ": "; if (record.target_player.player_online) { playerInfo += GetPlayerTeamName(record.target_player) + "/" + (_PlayerDictionary.Values.Where(aPlayer => aPlayer.fbpInfo.TeamID == record.target_player.fbpInfo.TeamID).OrderBy(aPlayer => aPlayer.fbpInfo.Score).Reverse().ToList().IndexOf(record.target_player) + 1) + "/" + record.target_player.fbpInfo.Score; } else { playerInfo += "OFFLINE"; } SendMessageToSource(record, playerInfo); } catch (Exception e) { record.record_exception = new AException("Error while sending player info.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting FindTarget", 6); } public void LockTarget(ARecord record) { Log.Debug(() => "Entering LockTarget", 6); try { if (record.target_player == null) { Log.Error("Player null when locking player."); FinalizeRecord(record); return; } if (String.IsNullOrEmpty(record.source_name) && record.source_player == null) { SendMessageToSource(record, "No source provided to lock player. Unable to lock."); FinalizeRecord(record); return; } //Set the executed bool record.record_action_executed = true; //Check if already locked if (record.target_player.IsLocked()) { SendMessageToSource(record, record.GetTargetNames() + " is already locked by " + record.target_player.GetLockSource() + " for " + FormatTimeString(record.target_player.GetLockRemaining(), 3) + "."); FinalizeRecord(record); return; } //Assign the new lock Double inputDuration = 1; if (record.command_numeric > 0) { inputDuration = record.command_numeric; } else { inputDuration = _playerLockingManualDuration; } TimeSpan duration = TimeSpan.FromMinutes(inputDuration); record.target_player.Lock(record.source_name, duration); SendMessageToSource(record, record.GetTargetNames() + " is now locked for " + FormatTimeString(duration, 3) + ", or until you unlock them."); } catch (Exception e) { record.record_exception = new AException("Error while locking player.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting LockTarget", 6); } public void UnlockTarget(ARecord record) { Log.Debug(() => "Entering UnlockTarget", 6); try { if (record.target_player == null) { Log.Error("Player null when unlocking player."); return; } //Set the executed bool record.record_action_executed = true; //Check if already locked if (!record.target_player.IsLocked()) { SendMessageToSource(record, record.GetTargetNames() + " is not locked."); FinalizeRecord(record); return; } record.target_player.Unlock(); SendMessageToSource(record, record.GetTargetNames() + " is now unlocked."); } catch (Exception e) { record.record_exception = new AException("Error while unlocking player.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting UnlockTarget", 6); } public void MarkTarget(ARecord record) { Log.Debug(() => "Entering MarkTarget", 6); try { if (record.target_player == null) { Log.Error("Player null when marking player."); return; } record.record_action_executed = true; SendMessageToSource(record, record.GetTargetNames() + " marked for leave notification."); } catch (Exception e) { record.record_exception = new AException("Error while marking player.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting MarkTarget", 6); } public void LoadoutFetchTarget(ARecord record) { Log.Debug(() => "Entering LoadoutFetchTarget", 6); try { if (record.target_player == null) { Log.Error("Player null when fetching loadout for player."); SendMessageToSource(record, "Error checking loadout for " + record.GetTargetNames() + "."); return; } record.record_action_executed = true; if (_subscribedClients.Any(client => client.ClientName == "AdKatsLRT" && client.SubscriptionEnabled)) { lock (_LoadoutConfirmDictionary) { _LoadoutConfirmDictionary[record.target_player.player_name] = record; } SendMessageToSource(record, "Fetching loadout for " + record.GetTargetNames() + "."); var startDuration = NowDuration(_AdKatsStartTime).TotalMinutes; var startupDuration = TimeSpan.FromSeconds(_startupDurations.Average(span => span.TotalSeconds)).TotalMinutes; if (startDuration - startupDuration < 10) { SendMessageToSource(record, "WARNING: AdKats/LRT just started, loadouts may not be available for a few minutes."); } ExecuteCommand("procon.protected.plugins.call", "AdKatsLRT", "CallLoadoutCheckOnPlayer", "AdKats", JSON.JsonEncode(new Hashtable { {"caller_identity", "AdKats"}, {"response_requested", false}, {"player_name", record.target_player.player_name}, {"loadoutCheck_reason", "fetch"} })); } else { SendMessageToSource(record, "AdKatsLRT not installed/integrated, loadout for " + record.GetTargetNames() + " cannot be fetched."); } } catch (Exception e) { record.record_exception = new AException("Error while fetching loadout for player.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting LoadoutFetchTarget", 6); } public void LoadoutForceTarget(ARecord record) { Log.Debug(() => "Entering LoadoutForceTarget", 6); try { if (record.target_player == null) { Log.Error("Player null when forcing loadout on player."); SendMessageToSource(record, "Error forcing loadout on " + record.GetTargetNames() + "."); return; } if (!record.target_player.player_online) { SendMessageToSource(record, record.GetTargetNames() + " is not online, loadout cannot be forced."); return; } record.record_action_executed = true; if (_subscribedClients.Any(client => client.ClientName == "AdKatsLRT" && client.SubscriptionEnabled)) { ExecuteCommand("procon.protected.plugins.call", "AdKatsLRT", "CallLoadoutCheckOnPlayer", "AdKats", JSON.JsonEncode(new Hashtable { {"caller_identity", "AdKats"}, {"response_requested", false}, {"player_name", record.target_player.player_name}, {"loadoutCheck_reason", "forced"} })); SendMessageToSource(record, record.GetTargetNames() + " forced up to trigger level loadout enforcement."); } else { SendMessageToSource(record, "AdKatsLRT not installed/integrated, " + record.GetTargetNames() + " CANNOT be forced up to trigger level loadout enforcement."); } } catch (Exception e) { record.record_exception = new AException("Error while forcing loadout on player.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting LoadoutForceTarget", 6); } public void LoadoutIgnoreTarget(ARecord record) { Log.Debug(() => "Entering LoadoutIgnoreTarget", 6); try { if (record.target_player == null) { Log.Error("Player null when ignoring loadout for player."); SendMessageToSource(record, "Error ignoring loadout for " + record.GetTargetNames() + "."); return; } if (!record.target_player.player_online) { SendMessageToSource(record, record.GetTargetNames() + " is not online, loadout cannot be ignored."); return; } record.record_action_executed = true; if (_subscribedClients.Any(client => client.ClientName == "AdKatsLRT" && client.SubscriptionEnabled)) { ExecuteCommand("procon.protected.plugins.call", "AdKatsLRT", "CallLoadoutCheckOnPlayer", "AdKats", JSON.JsonEncode(new Hashtable { {"caller_identity", "AdKats"}, {"response_requested", false}, {"player_name", record.target_player.player_name}, {"loadoutCheck_reason", "ignored"} })); SendMessageToSource(record, record.GetTargetNames() + " is now temporarily ignored by the loadout enforcer."); } else { SendMessageToSource(record, "AdKatsLRT not installed/integrated."); } } catch (Exception e) { record.record_exception = new AException("Error while ignoring loadout for player.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting LoadoutIgnoreTarget", 6); } public void PingFetchTarget(ARecord record) { Log.Debug(() => "Entering PingFetchTarget", 6); try { if (record.target_player == null) { Log.Error("Player null when fetching player ping."); SendMessageToSource(record, "Error fetching ping for " + record.GetTargetNames() + "."); return; } if (!record.target_player.player_online) { SendMessageToSource(record, record.GetTargetNames() + " is not online, ping cannot be fetched."); return; } record.record_action_executed = true; record.command_numeric = (int)record.target_player.player_ping; String currentString = record.target_player.player_ping > 0 ? Math.Round(record.target_player.player_ping).ToString() : "Missing"; String averageString = record.target_player.player_ping_avg > 0 ? Math.Round(record.target_player.player_ping_avg).ToString() : "Missing"; SendMessageToSource(record, record.target_player.GetVerboseName() + "'s Ping: " + (record.target_player.player_ping_manual ? "[M] " : "") + currentString + "ms (Avg: " + averageString + "ms)"); } catch (Exception e) { record.record_exception = new AException("Error while fetching player ping.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting PingFetchTarget", 6); } public void ForcePingTarget(ARecord record) { Log.Debug(() => "Entering ForcePingTarget", 6); try { if (record.target_player == null) { Log.Error("Player null when forcing manual ping on player."); SendMessageToSource(record, "Error forcing manual ping on " + record.GetTargetNames() + "."); return; } if (!record.target_player.player_online) { SendMessageToSource(record, record.GetTargetNames() + " is not online, ping cannot be manually fetched."); return; } if (String.IsNullOrEmpty(record.target_player.player_ip)) { SendMessageToSource(record, "We don't have an IP for " + record.GetTargetNames() + ", we can't manually fetch their ping."); return; } record.record_action_executed = true; record.target_player.player_ping_manual = true; record.target_player.ClearPingEntries(); SendMessageToSource(record, record.target_player.GetVerboseName() + "'s ping will now be manually fetched. Deleting old pings for them."); } catch (Exception e) { record.record_exception = new AException("Error while forcing manual player ping.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting ForcePingTarget", 6); } public void ManageAFKPlayers(ARecord record) { Log.Debug(() => "Entering ManageAFKPlayers", 6); try { record.record_action_executed = true; if (GetPlayerCount() < _AFKTriggerMinimumPlayers) { SendMessageToSource(record, "Server contains less than " + _AFKTriggerMinimumPlayers + ", unable to kick AFK players."); FinalizeRecord(record); return; } List afkPlayers = _PlayerDictionary.Values.Where(aPlayer => (UtcNow() - aPlayer.lastAction).TotalMinutes > _AFKTriggerDurationMinutes && aPlayer.player_type != PlayerType.Spectator && !PlayerIsAdmin(aPlayer)).Take(_PlayerDictionary.Values.Count(aPlayer => aPlayer.player_type == PlayerType.Player) - _AFKTriggerMinimumPlayers).ToList(); if (_AFKIgnoreUserList) { IEnumerable userSoldierGuids = FetchAllUserSoldiers().Select(aPlayer => aPlayer.player_guid); afkPlayers = afkPlayers.Where(aPlayer => !userSoldierGuids.Contains(aPlayer.player_guid)).ToList(); } else { afkPlayers = afkPlayers.Where(aPlayer => !_AFKIgnoreRoles.Contains(aPlayer.player_role.role_key) && !_AFKIgnoreRoles.Contains(aPlayer.player_role.role_name) && !_AFKIgnoreRoles.Contains(aPlayer.player_role.role_id.ToString()) && !_AFKIgnoreRoles.Contains("RLE" + aPlayer.player_role.role_id.ToString())).ToList(); } if (afkPlayers.Any()) { foreach (APlayer aPlayer in afkPlayers) { string afkTime = FormatTimeString(UtcNow() - aPlayer.lastAction, 2); Log.Debug(() => "Kicking " + aPlayer.GetVerboseName() + " for being AFK " + afkTime + ".", 3); ARecord kickRecord = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_kick"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "AFKManager", record_message = "AFK time exceeded [" + afkTime + "/" + GetPlayerTeamKey(aPlayer) + "]. Please rejoin once you return.", record_time = UtcNow() }; QueueRecordForProcessing(kickRecord); } SendMessageToSource(record, afkPlayers.Count() + " players kicked for being AFK."); } else { SendMessageToSource(record, "No AFK players found or kickable."); } } catch (Exception e) { record.record_exception = new AException("Error while managing AFK players.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting ManageAFKPlayers", 6); } public void SendOnlineAdmins(ARecord record) { Log.Debug(() => "Entering SendOnlineAdmins", 6); try { record.record_action_executed = true; List onlineAdminList = FetchOnlineAdminSoldiers(); String onlineAdmins = "Admins: [" + onlineAdminList.Count + " Online] "; onlineAdmins = onlineAdminList.Aggregate(onlineAdmins, (current, aPlayer) => current + (aPlayer.GetVerboseName() + " (" + GetPlayerTeamKey(aPlayer).Replace("Neutral", "Spectator") + "/" + (_PlayerDictionary.Values.Where(innerPlayer => innerPlayer.fbpInfo.TeamID == aPlayer.fbpInfo.TeamID).OrderBy(innerPlayer => innerPlayer.fbpInfo.Score).Reverse().ToList().IndexOf(aPlayer) + 1) + "), ")); //Send online admins SendMessageToSource(record, onlineAdmins.Trim().TrimEnd(',')); } catch (Exception e) { record.record_exception = new AException("Error while sending online admins.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SendOnlineAdmins", 6); } public void LeadCurrentSquad(ARecord record) { Log.Debug(() => "Entering LeadCurrentSquad", 6); try { record.record_action_executed = true; ExecuteCommand("procon.protected.send", "squad.leader", record.target_player.fbpInfo.TeamID.ToString(), record.target_player.fbpInfo.SquadID.ToString(), record.target_player.player_name); PlayerSayMessage(record.target_player.player_name, "You are now the leader of your current squad."); if (record.source_name != record.target_name) { SendMessageToSource(record, record.GetTargetNames() + " is now the leader of their current squad."); } } catch (Exception e) { record.record_exception = new AException("Error while leading curring squad.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting LeadCurrentSquad", 6); } public void SendChallengeInfo(ARecord record) { Log.Debug(() => "Entering SendChallengeInfo", 6); try { record.record_action_executed = true; if (record.target_player == null) { record.target_player = record.source_player; } var commandText = GetChatCommandByKey("self_challenge"); var option = record.record_message.ToLower().Trim(); if (option.StartsWith("help")) { var waitMS = 2000; SendMessageToSource(record, commandText + " info (Current challenge info.)"); Threading.Wait(waitMS); SendMessageToSource(record, commandText + " p (Current challenge progress.)"); Threading.Wait(waitMS); SendMessageToSource(record, commandText + " rewards (List of challenge rewards.)"); Threading.Wait(waitMS); SendMessageToSource(record, commandText + " list (List of available challenges.)"); Threading.Wait(waitMS); SendMessageToSource(record, commandText + " list # (List of available tier # challenges.)"); Threading.Wait(waitMS); SendMessageToSource(record, commandText + " # (Start challenge #.)"); Threading.Wait(waitMS); SendMessageToSource(record, commandText + " random (Start a random challenge of any tier.)"); Threading.Wait(waitMS); SendMessageToSource(record, commandText + " random # (Start a random tier # challenge.)"); Threading.Wait(waitMS); SendMessageToSource(record, commandText + " k (Activates after you complete a challenge weapon. Admin slays you manually.)"); Threading.Wait(waitMS); SendMessageToSource(record, commandText + " autokill (Toggle being automatically slain when completing challenge weapons.)"); Threading.Wait(waitMS); SendMessageToSource(record, commandText + " ignore (Toggle ignoring the challenge system completely.)"); Threading.Wait(waitMS); SendMessageToSource(record, commandText + " help (Show this help message.)"); } else if (option == "list" || option.StartsWith("list ")) { if (record.target_player != null && GetMatchingVerboseASPlayersOfGroup("challenge_ignore", record.target_player).Any()) { SendMessageToSource(record, "You are currently ignoring challenges. To stop ignoring challenges type " + commandText + " ignore"); FinalizeRecord(record); return; } var split = option.Split(' '); Int32 tier = 0; if (split.Count() >= 2) { Int32.TryParse(split[1], out tier); } if (tier < 0) { tier = 0; } if (tier > 10) { tier = 10; } // Immediately get the rule list, then go async var rules = ChallengeManager.GetRules().Where(rule => rule.Enabled && rule.Definition.GetDetails().Any()) .OrderBy(rule => rule.Tier) .ThenBy(rule => rule.Name).ToList(); if (tier != 0) { rules = rules.Where(rule => rule.Tier == tier).ToList(); } var ruleStrings = rules.Select(rule => rule.ToString()); if (ruleStrings.Any()) { Threading.StartWatchdog(new Thread(new ThreadStart(delegate { try { Thread.CurrentThread.Name = "ChallengeRulePrinter"; Threading.Wait(100); foreach (var ruleString in ruleStrings) { SendMessageToSource(record, "" + commandText + " " + ruleString); Threading.Wait(1800); } } catch (Exception e) { Log.HandleException(new AException("Error while printing challenge rules.", e)); } Threading.StopWatchdog(); }))); } else { SendMessageToSource(record, "No challenges are available" + (tier != 0 ? " at tier " + tier : "") + "."); } } else if (option == "random" || option.StartsWith("random ")) { if (record.target_player != null && GetMatchingVerboseASPlayersOfGroup("challenge_ignore", record.target_player).Any()) { SendMessageToSource(record, "You are currently ignoring challenges. To stop ignoring challenges type " + commandText + " ignore"); FinalizeRecord(record); return; } var split = option.Split(' '); Int32 tier = 0; if (split.Count() >= 2) { Int32.TryParse(split[1], out tier); } ChallengeManager.CreateAndAssignRandomEntry(record.target_player, tier, true); } // Be agnostic of plural else if (option.StartsWith("reward")) { if (record.target_player != null && GetMatchingVerboseASPlayersOfGroup("challenge_ignore", record.target_player).Any()) { SendMessageToSource(record, "You are currently ignoring challenges. To stop ignoring challenges type " + commandText + " ignore"); FinalizeRecord(record); return; } var activeRules = ChallengeManager.GetRules().Where(dRule => dRule.Enabled); var activeRewards = ChallengeManager.GetRewards().Where(dReward => dReward.Enabled && dReward.Reward != AChallengeManager.CReward.RewardType.None && activeRules.Any(dRule => dRule.Tier == dReward.Tier)) .OrderBy(dReward => dReward.Tier).ThenBy(dReward => dReward.Reward); List rewardMessages = new List(); if (activeRewards.Any()) { var rewardGroups = activeRewards.GroupBy(dReward => dReward.Tier); foreach (var rewardGroup in rewardGroups) { var groupString = "Tier " + rewardGroup.First().Tier + ": "; var rewardStrings = rewardGroup.OrderBy(dReward => dReward.Reward.ToString()) .Select(dReward => dReward.getDescriptionString(record.target_player)) .Distinct(); groupString += String.Join(", ", rewardStrings.ToArray()); rewardMessages.Add(groupString); } } if (rewardMessages.Any()) { Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a challenge reward printer.", 5); try { Thread.CurrentThread.Name = "ChallengeRewardPrinter"; Threading.Wait(100); foreach (var message in rewardMessages) { if (String.IsNullOrEmpty(message.Replace(Environment.NewLine, "").Trim())) { continue; } SendMessageToSource(record, message); Threading.Wait(1500); } } catch (Exception e) { Log.HandleException(new AException("Error while printing challenge rewards.", e)); } Log.Debug(() => "Exiting a challenge rewards printer.", 5); Threading.StopWatchdog(); }))); } else { SendMessageToSource(record, "No challenge rewards are enabled at this time."); } } else if (option == "info") { if (record.target_player != null && GetMatchingVerboseASPlayersOfGroup("challenge_ignore", record.target_player).Any()) { SendMessageToSource(record, "You are currently ignoring challenges. To stop ignoring challenges type " + commandText + " ignore"); FinalizeRecord(record); return; } // Immediately get the challenge info, then go async var infoMessages = ChallengeManager.GetChallengeInfo(record.target_player, true).Split( new[] { Environment.NewLine }, StringSplitOptions.None ); Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a challenge info printer.", 5); try { Thread.CurrentThread.Name = "ChallengeInfoPrinter"; Threading.Wait(100); foreach (var message in infoMessages) { if (String.IsNullOrEmpty(message.Replace(Environment.NewLine, "").Trim())) { continue; } SendMessageToSource(record, message); Threading.Wait(1500); } } catch (Exception e) { Log.HandleException(new AException("Error while printing challenge info.", e)); } Log.Debug(() => "Exiting a challenge info printer.", 5); Threading.StopWatchdog(); }))); } else if (option == "p") { if (record.target_player != null && GetMatchingVerboseASPlayersOfGroup("challenge_ignore", record.target_player).Any()) { SendMessageToSource(record, "You are currently ignoring challenges. To stop ignoring challenges type " + commandText + " ignore"); FinalizeRecord(record); return; } // Immediately get the challenge progress, then go async var progressMessages = ChallengeManager.GetChallengeInfo(record.target_player, false).Split( new[] { Environment.NewLine }, StringSplitOptions.None ); Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Log.Debug(() => "Starting a challenge progress printer.", 5); try { Thread.CurrentThread.Name = "ChallengeProgressPrinter"; Threading.Wait(100); foreach (var message in progressMessages) { if (String.IsNullOrEmpty(message.Replace(Environment.NewLine, "").Trim())) { continue; } SendMessageToSource(record, message); Threading.Wait(1500); } } catch (Exception e) { Log.HandleException(new AException("Error while printing challenge progress.", e)); } Log.Debug(() => "Exiting a challenge progress printer.", 5); Threading.StopWatchdog(); }))); } else if (option == "k") { if (record.target_player != null && GetMatchingVerboseASPlayersOfGroup("challenge_ignore", record.target_player).Any()) { SendMessageToSource(record, "You are currently ignoring challenges. To stop ignoring challenges type " + commandText + " ignore"); FinalizeRecord(record); return; } if (record.target_player.ActiveChallenge == null) { SendMessageToSource(record, "You do not have a challenge active."); FinalizeRecord(record); return; } if (GetMatchingVerboseASPlayersOfGroup("challenge_autokill", record.target_player).Any()) { SendMessageToSource(record, "You have autokill enabled, you will be slain automatically. No need to manually request it."); FinalizeRecord(record); return; } if (!record.target_player.ActiveChallenge.kAllowed) { SendMessageToSource(record, "You must complete a challenge weapon to use the challenge admin kill."); FinalizeRecord(record); return; } ExecuteCommand("procon.protected.send", "admin.killPlayer", record.target_player.player_name); record.target_player.Say(Log.CPink("Challenge admin kill activated.")); record.target_player.ActiveChallenge.kAllowed = false; } else if (option == "autokill") { if (record.target_player == null) { SendMessageToSource(record, "Cannot change autokill status without being a player."); FinalizeRecord(record); return; } if (GetMatchingVerboseASPlayersOfGroup("challenge_autokill", record.target_player).Any()) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_challenge_autokill_remove"), command_numeric = 0, target_name = record.target_player.player_name, target_player = record.target_player, source_name = "ChallengeManager", record_message = "Removing Challenge AutoKill Status", record_time = UtcNow() }); SendMessageToSource(record, "You will NOT be slain when completing challenge weapons."); } else { if (record.target_player != null && GetMatchingVerboseASPlayersOfGroup("challenge_ignore", record.target_player).Any()) { SendMessageToSource(record, "You are currently ignoring challenges. To stop ignoring challenges type " + commandText + " ignore"); FinalizeRecord(record); return; } QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_challenge_autokill"), command_numeric = 10518984, target_name = record.target_player.player_name, target_player = record.target_player, source_name = "ChallengeManager", record_message = "Adding Challenge AutoKill Status", record_time = UtcNow() }); SendMessageToSource(record, "You will now be slain when completing challenge weapons."); } } else if (option == "ignore") { if (record.target_player == null) { SendMessageToSource(record, "Cannot change ignoring status without being a player."); FinalizeRecord(record); return; } if (GetMatchingVerboseASPlayersOfGroup("challenge_ignore", record.target_player).Any()) { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_challenge_ignore_remove"), command_numeric = 0, target_name = record.target_player.player_name, target_player = record.target_player, source_name = "ChallengeManager", record_message = "Removing Challenge Ignoring Status", record_time = UtcNow() }); SendMessageToSource(record, "You are no longer ignoring challenge related messages."); } else { QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_challenge_ignore"), command_numeric = 10518984, target_name = record.target_player.player_name, target_player = record.target_player, source_name = "ChallengeManager", record_message = "Adding Challenge Ignore Status", record_time = UtcNow() }); SendMessageToSource(record, "You are now ignoring challenge related messages."); if (record.target_player.ActiveChallenge != null) { // They are ignoring challenges but have an active challenge. Cancel it. record.target_player.ActiveChallenge.DoCancel(); } } } else { if (record.target_player != null && GetMatchingVerboseASPlayersOfGroup("challenge_ignore", record.target_player).Any()) { SendMessageToSource(record, "You are currently ignoring challenges. To stop ignoring challenges type " + commandText + " ignore"); FinalizeRecord(record); return; } var split = option.Split(' '); if (split.Any()) { Int32 parseID; if (Int32.TryParse(split[0], out parseID)) { // They entered a number. See if it's a challenge ID, and if so, assign it to them. var selectRules = ChallengeManager.GetRules().Where(rule => rule.Enabled && rule.Definition.GetDetails().Any()) .OrderBy(rule => rule.Tier) .ThenBy(rule => rule.Name); var selected = selectRules.FirstOrDefault(rule => rule.ID == parseID); if (selected != null) { // Make sure they aren't overwriting their current challenge if (record.target_player.ActiveChallenge != null && record.target_player.ActiveChallenge.Rule.ID == selected.ID) { record.target_player.Say("You are already playing a " + selected.Name + " challenge. To see your progress type " + commandText + " p"); return; } else { ChallengeManager.CreateAndAssignEntry(record.target_player, selected, true); return; } } else { SendMessageToSource(record, "Challenge " + parseID + " does not exist. To see the list type " + commandText + " list"); return; } } else if (split[0].Contains("#")) { SendMessageToSource(record, "You need to enter the challenge number from the list. For example " + commandText + " 1"); return; } } SendMessageToSource(record, "'" + record.record_message + "' was an invalid option. Type " + commandText + " help"); } } catch (Exception e) { record.record_exception = new AException("Error while sending challenge info.", e); Log.HandleException(record.record_exception); FinalizeRecord(record); } Log.Debug(() => "Exiting SendCHallengeInfo", 6); } private void QueueUserForUpload(AUser user) { try { Log.Debug(() => "Preparing to queue user for access upload.", 6); lock (_UserUploadQueue) { _UserUploadQueue.Enqueue(user); Log.Debug(() => "User queued for access upload", 6); _DbCommunicationWaitHandle.Set(); } } catch (Exception e) { Log.HandleException(new AException("Error while queuing user upload.", e)); } } private void QueueUserForRemoval(AUser user) { try { Log.Debug(() => "Preparing to queue user for access removal", 6); lock (_UserRemovalQueue) { _UserRemovalQueue.Enqueue(user); Log.Debug(() => "User queued for access removal", 6); _DbCommunicationWaitHandle.Set(); } } catch (Exception e) { Log.HandleException(new AException("Error while queuing access removal.", e)); } } private Boolean HasAccess(APlayer aPlayer, ACommand command) { try { if (aPlayer == null) { Log.Error("player was null in hasAccess."); return false; } if (aPlayer.player_name == _debugSoldierName) { return true; } if (aPlayer.player_role == null) { Log.Error("player role was null in hasAccess."); return false; } if (command == null) { Log.Error("Command was null in hasAccess."); return false; } if ((_ReservedSelfKill || _ReservedSelfMove || _ReservedSquadLead) && GetMatchingVerboseASPlayersOfGroup("slot_reserved", aPlayer).Any()) { // Yes these could be just one if block. readability yo. if (_ReservedSquadLead && command.command_key == "self_lead") { return true; } if (_ReservedSelfMove && command.command_key == "self_teamswap") { return true; } if (_ReservedSelfKill && command.command_key == "self_kill") { return true; } } lock (aPlayer.player_role) { lock (aPlayer.player_role.RoleAllowedCommands) { if (aPlayer.player_role.RoleAllowedCommands.ContainsKey(command.command_key)) { return true; } if (aPlayer.player_role.ConditionalAllowedCommands.Values.Any(innerCommand => (innerCommand.Value.command_key == command.command_key) && innerCommand.Key(this, aPlayer))) { return true; } } } } catch (Exception e) { Log.HandleException(new AException("Error while checking command access on player.", e)); } return false; } private void DatabaseCommunicationThreadLoop() { try { Log.Debug(() => "Starting Database Comm Thread", 1); Thread.CurrentThread.Name = "DatabaseComm"; Boolean firstRun = true; DateTime loopStart; Stopwatch counter = new Stopwatch(); while (true) { loopStart = UtcNow(); try { Log.Debug(() => "Entering Database Comm Thread Loop", 7); if (!_pluginEnabled) { Log.Debug(() => "Detected AdKats not enabled. Exiting thread " + Thread.CurrentThread.Name, 6); break; } //Check if database connection settings have changed if (_dbSettingsChanged) { Log.Debug(() => "DB Settings have changed, calling test.", 6); if (TestDatabaseConnection()) { Log.Debug(() => "Database Connection Good. Continuing Thread.", 6); } else { _dbSettingsChanged = true; continue; } } //On first run, pull all roles and commands, update database if needed if (firstRun) { //Run any available SQL Updates counter.Reset(); counter.Start(); RunSQLUpdates(false); counter.Stop(); //Log.Write("RunSQLUpdates took " + counter.ElapsedMilliseconds + "ms"); counter.Reset(); counter.Start(); FetchCommands(); FetchRoles(); counter.Stop(); //Log.Write("Initial command fetch took " + counter.ElapsedMilliseconds + "ms"); } counter.Reset(); counter.Start(); //FeedStatLoggerSettings(); //Log.Write("FeedStatLoggerSettings took " + counter.ElapsedMilliseconds + "ms"); //Update server ID if (_serverInfo.ServerID <= 0) { //Checking for database server info if (FetchDBServerInfo()) { if (_serverInfo.ServerID <= 0) { //Inform the user Log.Error("Database Server info could not be fetched! Make sure XpKiller's Stat Logger is running on this server!"); //Disable the plugin Disable(); break; } Log.Success("Database server info fetched. Server ID is " + _serverInfo.ServerID + "."); //Push all settings for this instance to the database UploadAllSettings(); } else { //Inform the user Log.Error("Database Server info could not be fetched! Make sure XpKiller's Stat Logger is running on this server!"); //Disable the plugin Disable(); break; } } else { Log.Debug(() => "Skipping server ID fetch. Server ID: " + _serverInfo.ServerID, 7); } //Check if settings need sync if (firstRun || _settingImportID != _serverInfo.ServerID || _lastDbSettingFetch.AddSeconds(DbSettingFetchFrequency) < UtcNow()) { Log.Debug(() => "Preparing to fetch settings from server " + _serverInfo.ServerID, 6); //Fetch new settings from the database FetchSettings(_settingImportID, _settingImportID != _serverInfo.ServerID); counter.Reset(); counter.Start(); RunPluginOrchestration(); counter.Stop(); //Log.Write("RunPluginOrchestration took " + counter.ElapsedMilliseconds + "ms"); //Run any available SQL Updates counter.Reset(); counter.Start(); RunSQLUpdates(true); counter.Stop(); //Log.Write("RunSQLUpdates took " + counter.ElapsedMilliseconds + "ms"); } Boolean displayUpdate = false; counter.Reset(); counter.Start(); HandleSettingUploads(); counter.Stop(); //Log.Write("HandleSettingUploads took " + counter.ElapsedMilliseconds + "ms"); counter.Reset(); counter.Start(); if (HandleCommandUploads()) { displayUpdate = true; } counter.Stop(); //Log.Write("HandleCommandUploads took " + counter.ElapsedMilliseconds + "ms"); counter.Reset(); counter.Start(); if (HandleRoleUploads()) { displayUpdate = true; } counter.Stop(); //Log.Write("HandleRoleUploads took " + counter.ElapsedMilliseconds + "ms"); counter.Reset(); counter.Start(); if (HandleRoleRemovals()) { displayUpdate = true; } counter.Stop(); //Log.Write("HandleRoleRemovals took " + counter.ElapsedMilliseconds + "ms"); counter.Reset(); counter.Start(); HandleStatisticUploads(); counter.Stop(); //Log.Write("HandleStatisticUploads took " + counter.ElapsedMilliseconds + "ms"); if (displayUpdate) { UpdateSettingPage(); } counter.Reset(); counter.Start(); //Check for new actions from the database at given interval if (_fetchActionsFromDb && (UtcNow() > _lastDbActionFetch.AddSeconds(DbActionFetchFrequency))) { RunActionsFromDB(); } else { Log.Debug(() => "Skipping DB action fetch", 7); } counter.Stop(); HandleUserChanges(); //Start the other threads if (firstRun) { //Set the start time _AdKatsStartTime = UtcNow(); //Import round ID FetchRoundID(false); //Start other threads Threading.StartWatchdog(_PlayerListingThread); Threading.StartWatchdog(_AccessFetchingThread); Threading.StartWatchdog(_KillProcessingThread); Threading.StartWatchdog(_MessageProcessingThread); Threading.StartWatchdog(_CommandParsingThread); Threading.StartWatchdog(_ActionHandlingThread); Threading.StartWatchdog(_TeamSwapThread); Threading.StartWatchdog(_BanEnforcerThread); Threading.StartWatchdog(_AntiCheatThread); firstRun = false; _threadsReady = true; } if (ChallengeManager != null) { ChallengeManager.HandleRead(null, false); } counter.Reset(); counter.Start(); if (_UseBanEnforcer) { HandleActiveBanEnforcer(); } else { if (_UseBanEnforcerPreviousState) { RepopulateProconBanList(); _UseBanEnforcerPreviousState = false; } } counter.Stop(); //Log.Write("HandleActiveBanEnforcer took " + counter.ElapsedMilliseconds + "ms"); if (_UnprocessedRecordQueue.Count > 0) { counter.Reset(); counter.Start(); Log.Debug(() => "Unprocessed Record: " + _UnprocessedRecordQueue.Count + " Current: 0", 4); Log.Debug(() => "Preparing to lock inbound record queue to retrive new records", 7); Queue inboundRecords; lock (_UnprocessedRecordQueue) { Log.Debug(() => "Inbound records found. Grabbing.", 6); //Grab all records in the queue inboundRecords = new Queue(_UnprocessedRecordQueue.ToArray()); //Clear the queue for next run _UnprocessedRecordQueue.Clear(); } //Loop through all records in order that they came in while (inboundRecords.Count > 0) { if (!_pluginEnabled) { break; } Log.Debug(() => "Unprocessed Record: " + _UnprocessedRecordQueue.Count + " Current: " + inboundRecords.Count, 4); //Pull the next record ARecord record = inboundRecords.Dequeue(); //Process the record message record.record_message = ReplacePlayerInformation(record.record_message, record.target_player); //Upload the record Boolean success = HandleRecordUpload(record); //Check for action handling needs if (success && !record.record_action_executed && !record.record_orchestrate) { //Action is only called after initial upload, not after update Log.Debug(() => "Upload success. Attempting to add to action queue.", 6); //Only queue the record for action handling if it's not an enforced ban if (record.command_type.command_key != "banenforcer_enforce") { QueueRecordForActionHandling(record); } } else { Log.Debug(() => "Record does not need action handling by this server.", 6); //finalize the record FinalizeRecord(record); } } counter.Stop(); //Log.Write("UnprocessedRecords took " + counter.ElapsedMilliseconds + "ms"); if ((UtcNow() - loopStart).TotalMilliseconds > 1000) { Log.Debug(() => "Warning. " + Thread.CurrentThread.Name + " thread processing completed in " + ((int)((UtcNow() - loopStart).TotalMilliseconds)) + "ms", 4); } } else { counter.Reset(); counter.Start(); Log.Debug(() => "No unprocessed records. Waiting for input", 7); _DbCommunicationWaitHandle.Reset(); if ((UtcNow() - loopStart).TotalMilliseconds > 1000) { Log.Debug(() => "Warning. " + Thread.CurrentThread.Name + " thread processing completed in " + ((int)((UtcNow() - loopStart).TotalMilliseconds)) + "ms", 4); } _DbCommunicationWaitHandle.WaitOne(TimeSpan.FromSeconds(5)); counter.Stop(); //Log.Write("Waiting after complete took " + counter.ElapsedMilliseconds + "ms"); } } catch (Exception e) { if (e is ThreadAbortException) { Log.HandleException(new AException("Database Comm thread aborted. Exiting.")); break; } Log.HandleException(new AException("Error occured in Database Comm thread. Skipping current loop.", e)); } } Log.Debug(() => "Ending Database Comm Thread", 1); Threading.StopWatchdog(); } catch (Exception e) { Log.HandleException(new AException("Error occured in database comm thread.", e)); } } private void FeedStatLoggerSettings() { Log.Debug(() => "FeedStatLoggerSettings starting!", 6); //Every 60 minutes feed stat logger settings if (_lastStatLoggerStatusUpdateTime.AddMinutes(60) < UtcNow()) { if (Threading.IsAlive("StatLoggerSettingsFeeder")) { return; } Thread statLoggerFeedingThread = new Thread(new ThreadStart(delegate { try { Thread.CurrentThread.Name = "StatLoggerSettingsFeeder"; Thread.Sleep(250); Log.Debug(() => "Starting a stat logger setting feeder thread.", 5); _lastStatLoggerStatusUpdateTime = UtcNow(); if (_statLoggerVersion == "BF3") { SetExternalPluginSetting("CChatGUIDStatsLoggerBF3", "Enable Livescoreboard in DB?", "Yes"); if (_FeedStatLoggerSettings) { SetExternalPluginSetting("CChatGUIDStatsLoggerBF3", "Enable Statslogging?", "Yes"); SetExternalPluginSetting("CChatGUIDStatsLoggerBF3", "Enable Weaponstats?", "Yes"); SetExternalPluginSetting("CChatGUIDStatsLoggerBF3", "Enable KDR correction?", "Yes"); SetExternalPluginSetting("CChatGUIDStatsLoggerBF3", "MapStats ON?", "Yes"); SetExternalPluginSetting("CChatGUIDStatsLoggerBF3", "Session ON?", "Yes"); SetExternalPluginSetting("CChatGUIDStatsLoggerBF3", "Save Sessiondata to DB?", "Yes"); SetExternalPluginSetting("CChatGUIDStatsLoggerBF3", "Log playerdata only (no playerstats)?", "No"); Double slOffset = UtcNow().Subtract(DateTime.Now).TotalHours; SetExternalPluginSetting("CChatGUIDStatsLoggerBF3", "Servertime Offset", slOffset.ToString()); } if (_PostStatLoggerChatManually) { SetExternalPluginSetting("CChatGUIDStatsLoggerBF3", "Enable Chatlogging?", "No"); SetExternalPluginSetting("CChatGUIDStatsLoggerBF3", "Instant Logging of Chat Messages?", "No"); } else if (_FeedStatLoggerSettings) { SetExternalPluginSetting("CChatGUIDStatsLoggerBF3", "Enable Chatlogging?", "Yes"); SetExternalPluginSetting("CChatGUIDStatsLoggerBF3", "Instant Logging of Chat Messages?", "Yes"); } } else if (_statLoggerVersion == "UNIVERSAL") { SetExternalPluginSetting("CChatGUIDStatsLogger", "Enable Livescoreboard in DB?", "Yes"); if (_FeedStatLoggerSettings) { SetExternalPluginSetting("CChatGUIDStatsLogger", "Enable Statslogging?", "Yes"); SetExternalPluginSetting("CChatGUIDStatsLogger", "Enable Weaponstats?", "Yes"); SetExternalPluginSetting("CChatGUIDStatsLogger", "Enable KDR correction?", "Yes"); SetExternalPluginSetting("CChatGUIDStatsLogger", "MapStats ON?", "Yes"); SetExternalPluginSetting("CChatGUIDStatsLogger", "Session ON?", "Yes"); SetExternalPluginSetting("CChatGUIDStatsLogger", "Save Sessiondata to DB?", "Yes"); SetExternalPluginSetting("CChatGUIDStatsLogger", "Log playerdata only (no playerstats)?", "No"); Double slOffset = UtcNow().Subtract(DateTime.Now).TotalHours; SetExternalPluginSetting("CChatGUIDStatsLogger", "Servertime Offset", slOffset.ToString()); } if (_PostStatLoggerChatManually) { SetExternalPluginSetting("CChatGUIDStatsLogger", "Enable Chatlogging?", "No"); SetExternalPluginSetting("CChatGUIDStatsLogger", "Instant Logging of Chat Messages?", "No"); } else if (_FeedStatLoggerSettings) { SetExternalPluginSetting("CChatGUIDStatsLogger", "Enable Chatlogging?", "Yes"); SetExternalPluginSetting("CChatGUIDStatsLogger", "Instant Logging of Chat Messages?", "Yes"); } } else { Log.Error("Stat logger version is unknown, unable to feed stat logger settings."); } //TODO put back in the future //confirmStatLoggerSetup(); Log.Debug(() => "Exiting a stat logger setting feeder thread.", 5); } catch (Exception e) { Log.HandleException(new AException("Error while feeding stat logger settings.", e)); } Threading.StopWatchdog(); })); Threading.StartWatchdog(statLoggerFeedingThread); } Log.Debug(() => "FeedStatLoggerSettings finished!", 6); } private void HandleSettingUploads() { try { if (_SettingUploadQueue.Count > 0) { if (Threading.IsAlive("SettingUploader")) { return; } Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "SettingUploader"; Thread.Sleep(250); try { Log.Debug(() => "Preparing to lock inbound setting queue to get new settings", 7); Queue inboundSettingUpload; lock (_SettingUploadQueue) { Log.Debug(() => "Inbound settings found. Grabbing.", 6); //Grab all settings in the queue inboundSettingUpload = new Queue(_SettingUploadQueue.ToArray()); //Clear the queue for next run _SettingUploadQueue.Clear(); } //Loop through all settings in order that they came in while (inboundSettingUpload.Count > 0) { if (!_pluginEnabled) { break; } CPluginVariable setting = inboundSettingUpload.Dequeue(); UploadSetting(setting); } } catch (Exception e) { Log.HandleException(new AException("Error while uploading settings.", e)); } Threading.StopWatchdog(); }))); } } catch (Exception e) { Log.HandleException(new AException("Error while handling setting uploads.", e)); } } private Boolean HandleCommandUploads() { try { //Handle Inbound Command Uploads if (_CommandUploadQueue.Count > 0) { Log.Debug(() => "Preparing to lock inbound command queue to get new commands", 7); Queue inboundCommandUpload; lock (_CommandUploadQueue) { Log.Debug(() => "Inbound commands found. Grabbing.", 6); //Grab all commands in the queue inboundCommandUpload = new Queue(_CommandUploadQueue.ToArray()); //Clear the queue for next run _CommandUploadQueue.Clear(); } //Loop through all commands in order that they came in while (inboundCommandUpload.Count > 0) { ACommand command = inboundCommandUpload.Dequeue(); UploadCommand(command); } return true; } } catch (Exception e) { Log.HandleException(new AException("Error while handling command uploads.", e)); } return false; } private void HandleStatisticUploads() { try { if (_UnprocessedStatisticQueue.Count > 0) { Log.Debug(() => "Unprocessed Statistic: " + _UnprocessedStatisticQueue.Count + " Current: 0", 4); Log.Debug(() => "Preparing to lock inbound statistic queue to retrive new records", 7); Queue inboundStats; lock (_UnprocessedStatisticQueue) { Log.Debug(() => "Inbound statistics found. Grabbing.", 6); //Grab all statistics in the queue inboundStats = new Queue(_UnprocessedStatisticQueue.ToArray()); //Clear the queue for next run _UnprocessedStatisticQueue.Clear(); } //Loop through all statistics in order that they came in while (inboundStats.Count > 0) { if (!_pluginEnabled) { break; } Log.Debug(() => "Unprocessed Statistic: " + _UnprocessedStatisticQueue.Count + " Current: " + inboundStats.Count, 4); //Pull the next statistic AStatistic aStat = inboundStats.Dequeue(); //Upload the statistic UploadStatistic(aStat); } } } catch (Exception e) { Log.HandleException(new AException("Error while handling statistic uploads.", e)); } } private Boolean HandleRoleUploads() { try { if (_RoleUploadQueue.Count > 0) { Log.Debug(() => "Preparing to lock inbound role queue to get new roles", 7); Queue inboundRoleUpload; lock (_RoleUploadQueue) { Log.Debug(() => "Inbound roles found. Grabbing.", 6); //Grab all roles in the queue inboundRoleUpload = new Queue(_RoleUploadQueue.ToArray()); //Clear the queue for next run _RoleUploadQueue.Clear(); } //Loop through all roles in order that they came in var uploaded = false; while (inboundRoleUpload.Count > 0) { ARole aRole = inboundRoleUpload.Dequeue(); UploadRole(aRole); lock (_RoleIDDictionary) { if (_RoleIDDictionary.ContainsKey(aRole.role_id)) { _RoleIDDictionary[aRole.role_id] = aRole; } else { _RoleIDDictionary.Add(aRole.role_id, aRole); } if (_RoleKeyDictionary.ContainsKey(aRole.role_key)) { _RoleKeyDictionary[aRole.role_key] = aRole; } else { _RoleKeyDictionary.Add(aRole.role_key, aRole); } if (_RoleNameDictionary.ContainsKey(aRole.role_name)) { _RoleNameDictionary[aRole.role_name] = aRole; } else { _RoleNameDictionary.Add(aRole.role_name, aRole); } } uploaded = true; } if (uploaded) { FetchAllAccess(true); } return true; } } catch (Exception e) { Log.HandleException(new AException("Error while handling role uploads.", e)); } return false; } private Boolean HandleRoleRemovals() { try { if (_RoleRemovalQueue.Count > 0) { Log.Debug(() => "Preparing to lock removal role queue to get new roles", 7); Queue inboundRoleRemoval; lock (_RoleRemovalQueue) { Log.Debug(() => "Inbound roles found. Grabbing.", 6); //Grab all roles in the queue inboundRoleRemoval = new Queue(_RoleRemovalQueue.ToArray()); //Clear the queue for next run _RoleRemovalQueue.Clear(); } //Loop through all commands in order that they came in while (inboundRoleRemoval.Count > 0) { ARole aRole = inboundRoleRemoval.Dequeue(); RemoveRole(aRole); lock (_RoleIDDictionary) { if (_RoleIDDictionary.ContainsKey(aRole.role_id)) { _RoleIDDictionary.Remove(aRole.role_id); } if (_RoleKeyDictionary.ContainsKey(aRole.role_key)) { _RoleKeyDictionary.Remove(aRole.role_key); } if (_RoleNameDictionary.ContainsKey(aRole.role_name)) { _RoleNameDictionary.Remove(aRole.role_name); } } } return true; } } catch (Exception e) { Log.HandleException(new AException("Error while handling role removals.", e)); } return false; } private void HandleUserChanges() { try { if (_UserUploadQueue.Count > 0 || _UserRemovalQueue.Count > 0) { Log.Debug(() => "Inbound access changes found. Grabbing.", 6); Queue inboundUserUploads; lock (_UserUploadQueue) { inboundUserUploads = new Queue(_UserUploadQueue.ToArray()); _UserUploadQueue.Clear(); } Queue inboundUserRemoval; lock (_UserRemovalQueue) { inboundUserRemoval = new Queue(_UserRemovalQueue.ToArray()); _UserRemovalQueue.Clear(); } //Loop through all records in order that they came in while (inboundUserUploads.Count > 0) { AUser user = inboundUserUploads.Dequeue(); UploadUser(user); } //Loop through all records in order that they came in while (inboundUserRemoval.Count > 0) { AUser user = inboundUserRemoval.Dequeue(); Log.Info("Removing user " + user.user_name); RemoveUser(user); } FetchAllAccess(true); } else if (UtcNow() > _lastUserFetch.AddSeconds(DbUserFetchFrequency) || !_firstUserListComplete) { FetchAllAccess(true); } else { Log.Debug(() => "No inbound user changes.", 7); } } catch (Exception e) { Log.HandleException(new AException("Error while handling user changes.", e)); } } private void HandleActiveBanEnforcer() { try { //Call banlist at set interval (20 seconds) if (_UseBanEnforcerPreviousState && (UtcNow() > _lastBanListCall.AddSeconds(20))) { _lastBanListCall = UtcNow(); Log.Debug(() => "banlist.list called at interval.", 6); ExecuteCommand("procon.protected.send", "banList.list"); FetchNameBanCount(); FetchGUIDBanCount(); FetchIPBanCount(); } if (!_UseBanEnforcerPreviousState || (UtcNow() > _lastDbBanFetch.AddSeconds(DbBanFetchFrequency))) { //Load all bans on startup if (!_UseBanEnforcerPreviousState) { //Get all bans from procon Log.Info("Preparing to queue procon bans for import. Please wait."); _DbCommunicationWaitHandle.Reset(); ExecuteCommand("procon.protected.send", "banList.list"); _DbCommunicationWaitHandle.WaitOne(TimeSpan.FromMinutes(5)); if (_CBanProcessingQueue.Count > 0) { Log.Write(_CBanProcessingQueue.Count + " procon bans queued for import. Import might take several minutes if you have many bans!"); } else { Log.Write("No procon bans to import into Ban Enforcer."); } } } else { Log.Debug(() => "Skipping DB ban fetch", 7); } //Handle Inbound Ban Comms if (_BanEnforcerProcessingQueue.Count > 0) { Log.Debug(() => "Preparing to lock inbound ban enforcer queue to retrive new bans", 7); Queue inboundBans; lock (_BanEnforcerProcessingQueue) { Log.Debug(() => "Inbound bans found. Grabbing.", 6); //Grab all messages in the queue inboundBans = new Queue(_BanEnforcerProcessingQueue.ToArray()); //Clear the queue for next run _BanEnforcerProcessingQueue.Clear(); } Int32 index = 1; //Loop through all bans in order that they came in while (inboundBans.Count > 0) { if (!_pluginEnabled || !_UseBanEnforcer) { Log.Warn("Cancelling ban import mid-operation."); break; } //Grab the ban ABan aBan = inboundBans.Dequeue(); Log.Debug(() => "Processing Frostbite Ban: " + index++, 6); //Upload the ban UploadBan(aBan); //Only perform special action when ban is direct //Indirect bans are through the procon banlist, so the player has already been kicked if (aBan.ban_record.source_name != "BanEnforcer") { //Enforce the ban EnforceBan(aBan, false); } } } //Handle BF3 Ban Manager imports if (!_UseBanEnforcerPreviousState) { //Import all bans from BF3 Ban Manager ImportBansFromBBM5108(); } //Handle Inbound CBan Uploads if (_CBanProcessingQueue.Count > 0) { if (!_UseBanEnforcerPreviousState) { Log.Warn("Do not disable AdKats or change any settings until upload is complete!"); } Log.Debug(() => "Preparing to lock inbound cBan queue to retrive new cBans", 7); Double totalCBans = 0; Double bansImported = 0; Boolean earlyExit = false; DateTime startTime = UtcNow(); Queue inboundCBans; lock (_CBanProcessingQueue) { Log.Debug(() => "Inbound cBans found. Grabbing.", 6); //Grab all cBans in the queue inboundCBans = new Queue(_CBanProcessingQueue.ToArray()); totalCBans = inboundCBans.Count; //Clear the queue for next run _CBanProcessingQueue.Clear(); } //Loop through all cBans in order that they came in Boolean bansFound = false; while (inboundCBans.Count > 0) { //Break from the loop if the plugin is disabled or the setting is reverted. if (!_pluginEnabled || !_UseBanEnforcer) { Log.Warn("You exited the ban upload process early, the process was not completed."); earlyExit = true; break; } bansFound = true; CBanInfo cBan = inboundCBans.Dequeue(); //Create the record ARecord record = new ARecord(); record.record_time = UtcNow(); record.record_source = ARecord.Sources.Automated; //Permabans and Temp bans longer than 1 year will be defaulted to permaban switch (cBan.BanLength.Subset) { case TimeoutSubset.TimeoutSubsetType.Seconds: //Don't import bans 1s or less. BA/BF4DB kick players using 1s bans. if (cBan.BanLength.Seconds <= 1) { Log.Debug(() => "Skipping import of ban with 1 second length, likely from BA/BF4DB plugins", 5); continue; } record.command_type = GetCommandByKey("player_ban_temp"); record.command_action = GetCommandByKey("player_ban_temp"); record.command_numeric = cBan.BanLength.Seconds / 60; break; case TimeoutSubset.TimeoutSubsetType.Permanent: record.command_type = GetCommandByKey("player_ban_perm"); record.command_action = GetCommandByKey("player_ban_perm"); record.command_numeric = 0; break; case TimeoutSubset.TimeoutSubsetType.Round: //Accept round ban as 1 hour timeban record.command_type = GetCommandByKey("player_ban_temp"); record.command_action = GetCommandByKey("player_ban_temp"); record.command_numeric = 60; break; default: //Ban type is unknown, unable to process continue; } record.source_name = _CBanAdminName; record.server_id = _serverInfo.ServerID; if (String.IsNullOrEmpty(cBan.SoldierName) && String.IsNullOrEmpty(cBan.Guid) && String.IsNullOrEmpty(cBan.IpAddress)) { Log.Error("Player did not contain any identifiers when processing CBan. Ignoring."); continue; } record.target_player = FetchPlayer(true, false, false, null, -1, cBan.SoldierName, (!String.IsNullOrEmpty(cBan.Guid)) ? (cBan.Guid.ToUpper()) : (null), cBan.IpAddress, null); if (record.target_player == null) { Log.Error("Player could not be found/added when processing CBan. Ignoring."); continue; } if (!String.IsNullOrEmpty(record.target_player.player_name)) { record.target_name = record.target_player.player_name; } record.isIRO = false; record.record_message = cBan.Reason; //Update the ban enforcement depending on available information Boolean nameAvailable = !String.IsNullOrEmpty(record.target_player.player_name); Boolean guidAvailable = !String.IsNullOrEmpty(record.target_player.player_guid); Boolean ipAvailable = !String.IsNullOrEmpty(record.target_player.player_ip); //Create the ban ABan aBan = new ABan { ban_record = record, ban_enforceName = nameAvailable && (_DefaultEnforceName || (!guidAvailable && !ipAvailable) || !String.IsNullOrEmpty(cBan.SoldierName)), ban_enforceGUID = guidAvailable && (_DefaultEnforceGUID || (!nameAvailable && !ipAvailable) || !String.IsNullOrEmpty(cBan.Guid)), ban_enforceIP = ipAvailable && (_DefaultEnforceIP || (!nameAvailable && !guidAvailable) || !String.IsNullOrEmpty(cBan.IpAddress)) }; if (!aBan.ban_enforceName && !aBan.ban_enforceGUID && !aBan.ban_enforceIP) { Log.Error("Unable to create ban, no proper player information"); continue; } //Check for duplicate ban posting Boolean duplicateFound = false; foreach (ABan storedBan in FetchPlayerBans(record.target_player)) { if (storedBan.ban_record.record_message == record.record_message && storedBan.ban_record.source_name == record.source_name) { duplicateFound = true; } } if (duplicateFound) { continue; } //Upload the ban Log.Debug(() => "Uploading ban from procon.", 5); UploadBan(aBan); if (!_UseBanEnforcerPreviousState && (++bansImported % 25 == 0)) { Log.Write(Math.Round(100 * bansImported / totalCBans, 2) + "% of bans uploaded. AVG " + Math.Round(bansImported / ((UtcNow() - startTime).TotalSeconds), 2) + " uploads/sec."); } } if (bansFound && !earlyExit) { //If all bans have been queued for processing, clear the ban list ExecuteCommand("procon.protected.send", "banList.clear"); ExecuteCommand("procon.protected.send", "banList.save"); ExecuteCommand("procon.protected.send", "banList.list"); if (!_UseBanEnforcerPreviousState) { Log.Success("All bans uploaded into AdKats database."); } } } _UseBanEnforcerPreviousState = true; } catch (Exception e) { Log.HandleException(new AException("Error while handling active ban enforcer.", e)); } } public void api_ExecuteCommand(params string[] commands) { ExecuteCommand(commands); } private Boolean ConnectionCapable() { if (!string.IsNullOrEmpty(_mySqlSchemaName) && !string.IsNullOrEmpty(_mySqlHostname) && !string.IsNullOrEmpty(_mySqlPassword) && !string.IsNullOrEmpty(_mySqlPort) && !string.IsNullOrEmpty(_mySqlUsername)) { Log.Debug(() => "MySql Connection capable. All variables in place.", 8); return true; } return false; } private MySqlConnection GetDatabaseConnection() { if (ConnectionCapable()) { MySqlConnection conn = new MySqlConnection(PrepareMySqlConnectionString()); conn.Open(); return conn; } Log.Error("Attempted to connect to database without all variables in place."); return null; } private void UpdateMySqlConnectionStringBuilder() { lock (_dbCommStringBuilder) { UInt32 uintport = 3306; UInt32.TryParse(_mySqlPort, out uintport); //Add connection variables _dbCommStringBuilder.Port = uintport; _dbCommStringBuilder.Server = _mySqlHostname; _dbCommStringBuilder.UserID = _mySqlUsername; _dbCommStringBuilder.Password = _mySqlPassword; _dbCommStringBuilder.Database = _mySqlSchemaName; //Set up connection pooling if (UseConnectionPooling) { _dbCommStringBuilder.Pooling = true; _dbCommStringBuilder.MinimumPoolSize = Convert.ToUInt32(MinConnectionPoolSize); _dbCommStringBuilder.MaximumPoolSize = Convert.ToUInt32(MaxConnectionPoolSize); _dbCommStringBuilder.ConnectionLifeTime = 600; } else { _dbCommStringBuilder.Pooling = false; } //Set Compression _dbCommStringBuilder.UseCompression = UseCompressedConnection; //Allow User Settings _dbCommStringBuilder.AllowUserVariables = true; //Set Timeout Settings _dbCommStringBuilder.DefaultCommandTimeout = 3600; _dbCommStringBuilder.ConnectionTimeout = 50; } } private String PrepareMySqlConnectionString() { return _dbCommStringBuilder.ConnectionString; } private Boolean TestDatabaseConnection() { Boolean databaseValid = false; Log.Debug(() => "testDatabaseConnection starting!", 6); if (ConnectionCapable()) { Boolean success = false; Int32 attempt = 0; do { if (!_pluginEnabled) { return false; } attempt++; try { UpdateMySqlConnectionStringBuilder(); //Prepare the connection String and create the connection object using (MySqlConnection connection = GetDatabaseConnection()) { if (attempt > 1) { Log.Write("Attempting database connection. Attempt " + attempt + " of 5."); } //Attempt a ping through the connection if (connection.Ping()) { //Connection good Log.Success("Database connection open."); success = true; } else { //Connection poor Log.Error("Database connection FAILED ping test."); } } //databaseConnection gets closed here if (success) { //Make sure database structure is good if (ConfirmDatabaseSetup()) { //Confirm the database is valid databaseValid = true; //clear setting change monitor _dbSettingsChanged = false; } else { Disable(); break; } } } catch (Exception e) { //Only perform retries if the error was a timeout if (e.ToString().Contains("Unable to connect")) { Log.Error("Database connection failed. Attempt " + attempt + " of 5. " + ((attempt <= 5) ? ("Retrying in 5 seconds. ") : (""))); Threading.Wait(5000); } else { break; } } } while (!success && attempt < 5); if (!success) { //Invalid credentials or no connection to database Log.Error("Database connection FAILED with EXCEPTION. Bad credentials, invalid hostname, or invalid port."); Disable(); } else { TimeSpan diffDBUTC; _dbTimingValid = TestDBTiming(true, out diffDBUTC); _dbTimingOffset = diffDBUTC; } } else { Log.Error("Not DB connection capable yet, complete SQL connection variables."); Disable(); Threading.Wait(500); } Log.Debug(() => "testDatabaseConnection finished!", 6); return databaseValid; } private Boolean ConfirmDatabaseSetup() { Log.Debug(() => "Confirming Database Structure.", 3); try { if (!ConfirmStatLoggerTables()) { Log.Error("Tables from XPKiller's Stat Logger not present in the database. Enable that plugin then re-run AdKats!"); return false; } if (!ConfirmAdKatsTables()) { Log.Error("AdKats tables not present or valid in the database. Have you run the AdKats database setup script yet? If so, are your tables InnoDB?"); return false; } Log.Success("Database confirmed functional for AdKats use."); return true; } catch (Exception e) { Log.Error("ERROR in ConfirmDatabaseSetup: " + e); return false; } } private Boolean runDBSetupScript() { try { Log.Write("Running database setup script. You will not lose any data."); using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { GZipWebClient client = new GZipWebClient(compress: false); Log.Debug(() => "Fetching plugin changelog...", 2); try { command.CommandText = Util.ClientDownloadTimer(client, "https://raw.githubusercontent.com/AdKats/AdKats/master/adkats.sql?cacherand=" + Environment.TickCount); Log.Debug(() => "SQL setup script fetched.", 1); } catch (Exception) { try { command.CommandText = Util.ClientDownloadTimer(client, "https://api.myrcon.net/plugins/adkats/sqlsetup?cacherand=" + Environment.TickCount); Log.Debug(() => "SQL setup script fetched from backup location.", 1); } catch (Exception) { Log.Error("Failed to fetch SQL setup script."); return false; } } try { //Attempt to execute the query Int32 rowsAffected = SafeExecuteNonQuery(command); Log.Write("Setup script successful, your database is now prepared for use by AdKats " + GetPluginVersion()); return true; } catch (Exception e) { Log.HandleException(new AException("Your database did not accept the script. Does your account have access to table, trigger, and stored procedure creation?", e)); } } } } catch (Exception e) { Log.Error("Unable to set up the database for AdKats use." + e); } return false; } private Boolean ConfirmAdKatsTables() { if (_databaseConnectionCriticalState) { return false; } if (!ConfirmTable("adkats_battlelog_players")) { Log.Info("Battlelog information table not found. Attempting to add."); SendNonQuery("Adding battlelog information table", @" CREATE TABLE `adkats_battlelog_players` ( `player_id` int(10) unsigned NOT NULL, `persona_id` bigint(20) unsigned NOT NULL, `user_id` bigint(20) unsigned NOT NULL, `gravatar` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL, `persona_banned` tinyint(1) NOT NULL DEFAULT 0, PRIMARY KEY (`player_id`), UNIQUE KEY `adkats_battlelog_players_player_id_persona_id_unique` (`player_id`,`persona_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='AdKats - Player Battlelog Info';", true); SendNonQuery("Adding battlelog information table foreign keys", @" ALTER TABLE `adkats_battlelog_players` ADD CONSTRAINT `adkats_battlelog_players_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `tbl_playerdata` (`PlayerID`) ON DELETE CASCADE ON UPDATE CASCADE", true); } if (!ConfirmTable("adkats_battlecries")) { Log.Info("Battlecries table not found. Attempting to add."); SendNonQuery("Adding battlecries table", @" CREATE TABLE IF NOT EXISTS `adkats_battlecries`( `player_id` int(10) UNSIGNED NOT NULL, `player_battlecry` varchar(300) COLLATE utf8_unicode_ci DEFAULT NULL, PRIMARY KEY (`player_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='AdKats - Battlecries List'", true); SendNonQuery("Adding battlecries table foreign keys", @" ALTER TABLE `adkats_battlecries` ADD CONSTRAINT `adkats_battlecries_player_id` FOREIGN KEY (`player_id`) REFERENCES `tbl_playerdata`(`PlayerID`) ON UPDATE NO ACTION ON DELETE CASCADE", true); } if (!ConfirmTable("adkats_specialplayers")) { Log.Info("Special players table not found. Attempting to add."); SendNonQuery("Adding special soldiers table", @" CREATE TABLE IF NOT EXISTS `adkats_specialplayers`( `specialplayer_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `player_group` varchar(100) COLLATE utf8_unicode_ci NOT NULL, `player_id` int(10) UNSIGNED DEFAULT NULL, `player_game` tinyint(4) UNSIGNED DEFAULT NULL, `player_server` smallint(5) UNSIGNED DEFAULT NULL, `player_identifier` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL, `player_expiration` DATETIME NOT NULL, PRIMARY KEY (`specialplayer_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='AdKats - Special Player List'", true); SendNonQuery("Adding special soldiers table foreign keys", @" ALTER TABLE `adkats_specialplayers` ADD CONSTRAINT `adkats_specialplayers_game_id` FOREIGN KEY (`player_game`) REFERENCES `tbl_games`(`GameID`) ON UPDATE NO ACTION ON DELETE CASCADE, ADD CONSTRAINT `adkats_specialplayers_server_id` FOREIGN KEY (`player_server`) REFERENCES `tbl_server`(`ServerID`) ON UPDATE NO ACTION ON DELETE CASCADE, ADD CONSTRAINT `adkats_specialplayers_player_id` FOREIGN KEY (`player_id`) REFERENCES `tbl_playerdata`(`PlayerID`) ON UPDATE NO ACTION ON DELETE CASCADE", true); } if (!ConfirmTable("adkats_player_reputation")) { Log.Info("Player reputation table not found. Attempting to add."); SendNonQuery("Adding player reputation table", @" CREATE TABLE `adkats_player_reputation` ( `player_id` int(10) unsigned NOT NULL, `game_id` tinyint(4) unsigned NOT NULL, `target_rep` float NOT NULL, `source_rep` float NOT NULL, `total_rep` float NOT NULL, `total_rep_co` float NOT NULL, PRIMARY KEY (`player_id`), KEY `game_id` (`game_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='AdKats - Player Reputation'", true); SendNonQuery("Adding player reputation table foreign keys", @" ALTER TABLE `adkats_player_reputation` ADD CONSTRAINT `adkats_player_reputation_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `tbl_playerdata` (`PlayerID`) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT `adkats_player_reputation_ibfk_2` FOREIGN KEY (`game_id`) REFERENCES `tbl_games` (`GameID`) ON DELETE CASCADE ON UPDATE CASCADE", true); } if (!ConfirmTable("adkats_orchestration")) { Log.Info("Plugin orchestration table not found. Attempting to add."); SendNonQuery("Adding plugin orchestration table", @" CREATE TABLE `adkats_orchestration` ( `setting_id` int(10) NOT NULL AUTO_INCREMENT, `setting_server` SMALLINT(5) NOT NULL, `setting_plugin` VARCHAR(100) NOT NULL, `setting_name` VARCHAR(100) NOT NULL, `setting_value` VARCHAR (5000) NOT NULL, PRIMARY KEY (`setting_id`), UNIQUE(`setting_server`, `setting_plugin`, `setting_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='AdKats - Plugin Orchestration'", true); } if (!ConfirmTable("tbl_extendedroundstats")) { Log.Info("Extended round stats table not found. Attempting to add."); SendNonQuery("Adding extended round stats table", @" CREATE TABLE `tbl_extendedroundstats` ( `roundstat_id` int(10) unsigned NOT NULL AUTO_INCREMENT, `server_id` smallint(5) unsigned NOT NULL, `round_id` int(10) unsigned NOT NULL, `round_elapsedTimeSec` int(10) unsigned NOT NULL, `team1_count` int(10) unsigned NOT NULL, `team2_count` int(10) unsigned NOT NULL, `team1_score` int(10) NOT NULL, `team2_score` int(10) NOT NULL, `team1_spm` double NOT NULL, `team2_spm` double NOT NULL, `team1_tickets` int(10) NOT NULL, `team2_tickets` int(10) NOT NULL, `team1_tpm` double NOT NULL, `team2_tpm` double NOT NULL, `roundstat_time` datetime NOT NULL, PRIMARY KEY (`roundstat_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='AdKats - Extended Round Stats'", true); } if (!ConfirmTable("adkats_statistics")) { Log.Info("AdKats statistics table not found. Attempting to add."); SendNonQuery("Adding AdKats statistics table", @" CREATE TABLE `adkats_statistics` ( `stat_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `server_id` SMALLINT(5) UNSIGNED NOT NULL, `round_id` INT(10) UNSIGNED NOT NULL, `stat_type` varchar(50) NOT NULL, `target_name` varchar(50) NOT NULL, `target_id` INT(10) UNSIGNED DEFAULT NULL, `stat_value` FLOAT NOT NULL, `stat_comment` TEXT, `stat_time` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00', PRIMARY KEY (`stat_id`), KEY `server_id` (`server_id`), KEY `stat_type` (`stat_type`), KEY `target_id` (`target_id`), KEY `stat_time` (`stat_time`), CONSTRAINT `adkats_statistics_target_id_fk` FOREIGN KEY (`target_id`) REFERENCES `tbl_playerdata` (`PlayerID`) ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT `adkats_statistics_server_id_fk` FOREIGN KEY (`server_id`) REFERENCES `tbl_server` (`ServerID`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='AdKats - Statistics'", true); } if (!SendQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE ( TABLE_SCHEMA = '" + _mySqlSchemaName + "' AND TABLE_NAME = 'adkats_specialplayers' AND COLUMN_NAME = 'player_effective' )", false)) { Log.Info("Special player effective not found. Attempting to add."); SendNonQuery("Adding special player effective.", "ALTER TABLE `adkats_specialplayers` ADD COLUMN `player_effective` DATETIME NOT NULL AFTER `player_identifier`", true); SendNonQuery("Adding initial special player effective values.", "UPDATE `adkats_specialplayers` SET `player_effective` = UTC_TIMESTAMP()", true); } if (!SendQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE ( TABLE_SCHEMA = '" + _mySqlSchemaName + "' AND TABLE_NAME = 'adkats_specialplayers' AND COLUMN_NAME = 'player_expiration' )", false)) { Log.Info("Special player expiration not found. Attempting to add."); SendNonQuery("Adding special player expiration.", "ALTER TABLE `adkats_specialplayers` ADD COLUMN `player_expiration` DATETIME NOT NULL AFTER `player_effective`", true); SendNonQuery("Adding initial special player expiration values.", "UPDATE `adkats_specialplayers` SET `player_expiration` = DATE_ADD(UTC_TIMESTAMP(), INTERVAL 20 YEAR)", true); } if (!SendQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE ( TABLE_SCHEMA = '" + _mySqlSchemaName + "' AND TABLE_NAME = 'tbl_playerdata' AND COLUMN_NAME = 'DiscordID' )", false)) { Log.Info("Player discord info column not found. Attempting to add."); SendNonQuery("Adding special player expiration.", "ALTER TABLE `tbl_playerdata` ADD COLUMN `DiscordID` VARCHAR(50) AFTER `IP_Address`", true); } if (SendQuery("SELECT specialplayer_id FROM adkats_specialplayers WHERE adkats_specialplayers.player_group = 'whitelist_hackerchecker'", false)) { Log.Info("Updating whitelist_hackerchecker to new definition whitelist_anticheat."); SendNonQuery("Updating whitelist_hackerchecker to new definition.", "update adkats_specialplayers set adkats_specialplayers.player_group = 'whitelist_anticheat' WHERE adkats_specialplayers.player_group = 'whitelist_hackerchecker'", true); } if (!ConfirmTable("adkats_rolegroups")) { Log.Info("AdKats role groups table not found. Attempting to add."); SendNonQuery("Adding AdKats role groups table", @" CREATE TABLE `adkats_rolegroups` ( `role_id` int(11) unsigned NOT NULL, `group_key` VARCHAR(100) NOT NULL, PRIMARY KEY (`role_id`,`group_key`), KEY `adkats_rolegroups_fk_role` (`role_id`), KEY `adkats_rolegroups_fk_command` (`group_key`), CONSTRAINT `adkats_rolegroups_fk_role` FOREIGN KEY (`role_id`) REFERENCES `adkats_roles` (`role_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='AdKats - Connection of groups to roles'", true); } if (!ConfirmTable("adkats_challenge_definition")) { Log.Info("AdKats challenge definition table not found. Attempting to add."); SendNonQuery("Adding challenge definition table", @" CREATE TABLE IF NOT EXISTS `adkats_challenge_definition` ( `ID` int(10) unsigned NOT NULL AUTO_INCREMENT, `Name` varchar(200) COLLATE utf8_unicode_ci NOT NULL, `CreateTime` datetime NOT NULL, `ModifyTime` datetime NOT NULL, PRIMARY KEY (`ID`), UNIQUE KEY `adkats_challenge_definition_idx_Name` (`Name`), KEY `adkats_challenge_definition_idx_CreateTime` (`CreateTime`), KEY `adkats_challenge_definition_idx_ModifyTime` (`ModifyTime`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='AdKats - Challenge Definitions'", true); } if (!ConfirmTable("adkats_challenge_definition_detail")) { Log.Info("AdKats challenge definition detail table not found. Attempting to add."); SendNonQuery("Adding challenge definition detail table", @" CREATE TABLE IF NOT EXISTS `adkats_challenge_definition_detail` ( `DefID` int(10) unsigned NOT NULL, `DetailID` int(10) unsigned NOT NULL, `Type` varchar(100) COLLATE utf8_unicode_ci NOT NULL, `Damage` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL, `WeaponCount` int(10) unsigned NOT NULL, `Weapon` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL, `KillCount` int(10) unsigned NOT NULL, `CreateTime` datetime NOT NULL, `ModifyTime` datetime NOT NULL, PRIMARY KEY (`DefID`, `DetailID`), KEY `adkats_challenge_definition_detail_idx_CreateTime` (`CreateTime`), KEY `adkats_challenge_definition_detail_idx_ModifyTime` (`ModifyTime`), CONSTRAINT `adkats_challenge_definition_detail_fk_DefID` FOREIGN KEY (`DefID`) REFERENCES `adkats_challenge_definition` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='AdKats - Challenge Definition Details'", true); } if (!ConfirmTable("adkats_challenge_rule")) { Log.Info("AdKats challenge rule table not found. Attempting to add."); SendNonQuery("Adding challenge rule table", @" CREATE TABLE IF NOT EXISTS `adkats_challenge_rule` ( `ID` int(10) unsigned NOT NULL AUTO_INCREMENT, `ServerID` smallint(5) unsigned NOT NULL, `DefID` int(10) unsigned NOT NULL, `Enabled` int(1) unsigned NOT NULL DEFAULT 1, `Name` varchar(200) COLLATE utf8_unicode_ci NOT NULL, `Tier` int(10) unsigned NOT NULL DEFAULT 1, `CompletionType` varchar(100) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'None', `RoundCount` int(10) unsigned NOT NULL DEFAULT 1, `DurationMinutes` int(10) unsigned NOT NULL DEFAULT 60, -- 4294967295 `DeathCount` int(10) unsigned NOT NULL DEFAULT 1, `CreateTime` datetime NOT NULL, `ModifyTime` datetime NOT NULL, `RoundLastUsedTime` datetime NOT NULL DEFAULT '1970-01-01 00:00:00', `PersonalLastUsedTime` datetime NOT NULL DEFAULT '1970-01-01 00:00:00', PRIMARY KEY (`ID`), UNIQUE KEY `adkats_challenge_rule_idx_Name_Server` (`Name`, `ServerID`), KEY `adkats_challenge_rule_idx_ServerID` (`ServerID`), KEY `adkats_challenge_rule_idx_DefID` (`DefID`), KEY `adkats_challenge_rule_idx_CreateTime` (`CreateTime`), KEY `adkats_challenge_rule_idx_ModifyTime` (`ModifyTime`), KEY `adkats_challenge_rule_idx_RoundLastUsedTime` (`RoundLastUsedTime`), KEY `adkats_challenge_rule_idx_PersonalLastUsedTime` (`PersonalLastUsedTime`), CONSTRAINT `adkats_challenge_rule_fk_ServerID` FOREIGN KEY (`ServerID`) REFERENCES `tbl_server` (`ServerID`) ON DELETE NO ACTION ON UPDATE CASCADE, -- No action for delete. If people move their servers, don't want to lose this record. CONSTRAINT `adkats_challenge_rule_fk_DefID` FOREIGN KEY (`DefID`) REFERENCES `adkats_challenge_definition` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='AdKats - Challenge Rules'", true); } if (!ConfirmTable("adkats_challenge_entry")) { Log.Info("AdKats challenge entry table not found. Attempting to add."); SendNonQuery("Adding challenge entry table", @" CREATE TABLE IF NOT EXISTS `adkats_challenge_entry` ( `ID` int(10) unsigned NOT NULL AUTO_INCREMENT, `PlayerID` int(10) unsigned NOT NULL, `RuleID` int(10) unsigned NOT NULL, `Completed` int(1) unsigned NOT NULL, `Failed` int(1) unsigned NOT NULL, `Canceled` int(1) unsigned NOT NULL, `StartRound` int(10) unsigned NOT NULL, `StartTime` datetime NOT NULL, `CompleteTime` datetime NOT NULL, PRIMARY KEY (`ID`), KEY `adkats_challenge_entry_idx_PlayerID` (`PlayerID`), KEY `adkats_challenge_entry_idx_RuleID` (`RuleID`), KEY `adkats_challenge_entry_idx_StartTime` (`StartTime`), KEY `adkats_challenge_entry_idx_CompleteTime` (`CompleteTime`), CONSTRAINT `adkats_challenge_entry_fk_Play erID` FOREIGN KEY (`PlayerID`) REFERENCES `tbl_playerdata` (`PlayerID`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `adkats_challenge_entry_fk_RuleID` FOREIGN KEY (`RuleID`) REFERENCES `adkats_challenge_rule` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='AdKats - Challenge Entries'", true); } if (!ConfirmTable("adkats_challenge_entry_detail")) { Log.Info("AdKats challenge entry detail table not found. Attempting to add."); SendNonQuery("Adding challenge entry detail table", @" CREATE TABLE IF NOT EXISTS `adkats_challenge_entry_detail` ( `EntryID` int(10) unsigned NOT NULL, `DetailID` int(10) unsigned NOT NULL, `VictimID` int(10) unsigned NOT NULL, `Weapon` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL, `RoundID` int(10) unsigned NOT NULL, `DetailTime` datetime NOT NULL, PRIMARY KEY (`EntryID`, `DetailID`), KEY `adkats_challenge_entry_detail_idx_VictimID` (`VictimID`), KEY `adkats_challenge_entry_detail_idx_DetailTime` (`DetailTime`), CONSTRAINT `adkats_challenge_entry_detail_fk_EntryID` FOREIGN KEY (`EntryID`) REFERENCES `adkats_challenge_entry` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `adkats_challenge_entry_detail_fk_VictimID` FOREIGN KEY (`VictimID`) REFERENCES `tbl_playerdata` (`PlayerID`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='AdKats - Challenge Entry Details'", true); } if (!ConfirmTable("adkats_challenge_reward")) { Log.Info("AdKats challenge reward table not found. Attempting to add."); SendNonQuery("Adding challenge reward table", @" CREATE TABLE IF NOT EXISTS `adkats_challenge_reward` ( `ID` int(10) unsigned NOT NULL AUTO_INCREMENT, `ServerID` smallint(5) unsigned NOT NULL, `Tier` int(10) unsigned NOT NULL, `Reward` varchar(100) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'None', `Enabled` int(1) unsigned NOT NULL DEFAULT 0, `DurationMinutes` int(10) unsigned NOT NULL DEFAULT 60, -- 4294967295 `CreateTime` datetime NOT NULL, `ModifyTime` datetime NOT NULL, PRIMARY KEY (`ID`), UNIQUE (`ServerID`, `Tier`, `Reward`), KEY `adkats_challenge_reward_idx_CreateTime` (`CreateTime`), KEY `adkats_challenge_reward_idx_ModifyTime` (`ModifyTime`), CONSTRAINT `adkats_challenge_reward_fk_ServerID` FOREIGN KEY (`ServerID`) REFERENCES `tbl_server` (`ServerID`) ON DELETE NO ACTION ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='AdKats - Challenge Rewards'", true); } SendNonQuery("Updating setting value length to 10000.", "ALTER TABLE adkats_settings MODIFY setting_value varchar(10000)", false); return ConfirmTable("adkats_bans") && ConfirmTable("adkats_commands") && ConfirmTable("adkats_infractions_global") && ConfirmTable("adkats_infractions_server") && ConfirmTable("adkats_records_debug") && ConfirmTable("adkats_records_main") && ConfirmTable("adkats_rolecommands") && ConfirmTable("adkats_roles") && ConfirmTable("adkats_settings") && ConfirmTable("adkats_users") && ConfirmTable("adkats_usersoldiers") && ConfirmTable("adkats_specialplayers") && ConfirmTable("adkats_player_reputation") && ConfirmTable("adkats_orchestration") && ConfirmTable("adkats_statistics") && ConfirmTable("adkats_rolegroups") && ConfirmTable("adkats_challenge_definition") && ConfirmTable("adkats_challenge_definition_detail") && ConfirmTable("adkats_challenge_rule") && ConfirmTable("adkats_challenge_entry") && ConfirmTable("adkats_challenge_entry_detail") && ConfirmTable("adkats_challenge_reward") && ConfirmTable("tbl_extendedroundstats") && !SendQuery("SELECT `TABLE_NAME` AS `table_name` FROM `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA` = '" + _mySqlSchemaName + "' AND `TABLE_NAME` LIKE 'adkats_%' AND ENGINE <> 'InnoDB'", false); } private Boolean ConfirmStatLoggerTables() { Boolean confirmed = true; //All versions of stat logger should have these tables if (ConfirmTable("tbl_playerdata") && ConfirmTable("tbl_server") && ConfirmTable("tbl_chatlog")) { //The universal version has a tbl_games table, detect that if (ConfirmTable("tbl_games")) { _statLoggerVersion = "UNIVERSAL"; Boolean gameIDFound = false; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { //Attempt to execute the query command.CommandText = @" SELECT `GameID` AS `game_id`, `Name` AS `game_name` FROM `tbl_games`"; using (MySqlDataReader reader = SafeExecuteReader(command)) { lock (_gameIDDictionary) { _gameIDDictionary.Clear(); while (reader.Read()) { String gameName = reader.GetString("game_name"); Int32 gameID = reader.GetInt32("game_id"); if (!_gameIDDictionary.ContainsKey(gameID)) { if (GameVersion.ToString() == gameName) { _serverInfo.GameID = gameID; gameIDFound = true; } switch (gameName) { case "BF3": _gameIDDictionary.Add(gameID, GameVersionEnum.BF3); break; case "BF4": _gameIDDictionary.Add(gameID, GameVersionEnum.BF4); break; case "BFHL": _gameIDDictionary.Add(gameID, GameVersionEnum.BFHL); break; default: Log.Error("Game name " + gameName + " not recognized."); break; } } } } } } confirmed = gameIDFound; } } } else { confirmed = false; } return confirmed; } private Boolean ConfirmTable(String tableName) { try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '" + _mySqlSchemaName + "' AND TABLE_NAME= '" + tableName + "'"; using (MySqlDataReader reader = SafeExecuteReader(command)) { bool confirmed = reader.Read(); return confirmed; } } } } catch (Exception e) { Log.HandleException(new AException("Error while confirming table '" + tableName + "'", e)); return false; } } private void UploadAllSettings() { if (!_pluginEnabled) { return; } //Make sure database connection active if (_databaseConnectionCriticalState) { return; } try { Log.Debug(() => "uploadAllSettings starting!", 6); QueueSettingForUpload(new CPluginVariable(@"Auto-Enable/Keep-Alive", typeof(Boolean), _useKeepAlive)); QueueSettingForUpload(new CPluginVariable(@"Override Timing Confirmation", typeof(Boolean), _timingValidOverride)); QueueSettingForUpload(new CPluginVariable(@"Debug level", typeof(int), Log.DebugLevel)); QueueSettingForUpload(new CPluginVariable(@"Debug Soldier Name", typeof(String), _debugSoldierName)); QueueSettingForUpload(new CPluginVariable(@"Server VOIP Address", typeof(String), _ServerVoipAddress)); QueueSettingForUpload(new CPluginVariable(@"Rule Print Delay", typeof(Double), _ServerRulesDelay)); QueueSettingForUpload(new CPluginVariable(@"Rule Print Interval", typeof(Double), _ServerRulesInterval)); QueueSettingForUpload(new CPluginVariable(@"Server Rule List", typeof(String), CPluginVariable.EncodeStringArray(_ServerRulesList))); QueueSettingForUpload(new CPluginVariable(@"Server Rule Numbers", typeof(Boolean), _ServerRulesNumbers)); QueueSettingForUpload(new CPluginVariable(@"Yell Server Rules", typeof(Boolean), _ServerRulesYell)); QueueSettingForUpload(new CPluginVariable(@"Feed MULTIBalancer Whitelist", typeof(Boolean), _FeedMultiBalancerWhitelist)); QueueSettingForUpload(new CPluginVariable(@"Feed MULTIBalancer Even Dispersion List", typeof(Boolean), _FeedMultiBalancerDisperseList)); QueueSettingForUpload(new CPluginVariable(@"Automatic MULTIBalancer Whitelist for Admins", typeof(Boolean), _FeedMultiBalancerWhitelist_Admins)); QueueSettingForUpload(new CPluginVariable(@"Feed TeamKillTracker Whitelist", typeof(Boolean), _FeedTeamKillTrackerWhitelist)); QueueSettingForUpload(new CPluginVariable(@"Automatic TeamKillTracker Whitelist for Admins", typeof(Boolean), _FeedTeamKillTrackerWhitelist_Admins)); QueueSettingForUpload(new CPluginVariable(@"Automatic Reserved Slot for Admins", typeof(Boolean), _FeedServerReservedSlots_Admins)); QueueSettingForUpload(new CPluginVariable(@"Automatic VIP Kick Whitelist for Admins", typeof(Boolean), _FeedServerReservedSlots_Admins_VIPKickWhitelist)); QueueSettingForUpload(new CPluginVariable(@"Send new reserved slots to VIP Slot Manager", typeof(Boolean), _FeedServerReservedSlots_VSM)); QueueSettingForUpload(new CPluginVariable(@"Automatic Spectator Slot for Admins", typeof(Boolean), _FeedServerSpectatorList_Admins)); QueueSettingForUpload(new CPluginVariable(@"Feed Server Reserved Slots", typeof(Boolean), _FeedServerReservedSlots)); QueueSettingForUpload(new CPluginVariable(@"Feed Server Spectator List", typeof(Boolean), _FeedServerSpectatorList)); QueueSettingForUpload(new CPluginVariable(@"Feed Stat Logger Settings", typeof(Boolean), _FeedStatLoggerSettings)); QueueSettingForUpload(new CPluginVariable(@"Post Stat Logger Chat Manually", typeof(Boolean), _PostStatLoggerChatManually)); QueueSettingForUpload(new CPluginVariable(@"Post Server Chat Spam", typeof(Boolean), _PostStatLoggerChatManually_PostServerChatSpam)); QueueSettingForUpload(new CPluginVariable(@"Exclude Commands from Chat Logs", typeof(Boolean), _PostStatLoggerChatManually_IgnoreCommands)); QueueSettingForUpload(new CPluginVariable(@"Post Map Benefit/Detriment Statistics", typeof(Boolean), _PostMapBenefitStatistics)); // Populator Monitor QueueSettingForUpload(new CPluginVariable(@"Monitor Populator Players", typeof(Boolean), _PopulatorMonitor)); QueueSettingForUpload(new CPluginVariable(@"Monitor Specified Populators Only", typeof(Boolean), _PopulatorUseSpecifiedPopulatorsOnly)); QueueSettingForUpload(new CPluginVariable(@"Monitor Populators of This Server Only", typeof(Boolean), _PopulatorPopulatingThisServerOnly)); QueueSettingForUpload(new CPluginVariable(@"Count to Consider Populator Past Week", typeof(Int32), _PopulatorMinimumPopulationCountPastWeek)); QueueSettingForUpload(new CPluginVariable(@"Count to Consider Populator Past 2 Weeks", typeof(Int32), _PopulatorMinimumPopulationCountPast2Weeks)); QueueSettingForUpload(new CPluginVariable(@"Enable Populator Perks", typeof(Boolean), _PopulatorPerksEnable)); QueueSettingForUpload(new CPluginVariable(@"Populator Perks - Reserved Slot", typeof(Boolean), _PopulatorPerksReservedSlot)); QueueSettingForUpload(new CPluginVariable(@"Populator Perks - Autobalance Whitelist", typeof(Boolean), _PopulatorPerksBalanceWhitelist)); QueueSettingForUpload(new CPluginVariable(@"Populator Perks - Ping Whitelist", typeof(Boolean), _PopulatorPerksPingWhitelist)); QueueSettingForUpload(new CPluginVariable(@"Populator Perks - TeamKillTracker Whitelist", typeof(Boolean), _PopulatorPerksTeamKillTrackerWhitelist)); // Teamspeak Monitor QueueSettingForUpload(new CPluginVariable(@"Monitor Teamspeak Players", typeof(Boolean), _TeamspeakPlayerMonitorView)); QueueSettingForUpload(new CPluginVariable(@"Enable Teamspeak Player Monitor", typeof(Boolean), _TeamspeakPlayerMonitorEnable)); QueueSettingForUpload(new CPluginVariable(@"Teamspeak Server IP", typeof(String), _TeamspeakManager.Ts3ServerIp)); QueueSettingForUpload(new CPluginVariable(@"Teamspeak Server Port", typeof(Int32), _TeamspeakManager.Ts3ServerPort)); QueueSettingForUpload(new CPluginVariable(@"Teamspeak Server Query Port", typeof(Int32), _TeamspeakManager.Ts3QueryPort)); QueueSettingForUpload(new CPluginVariable(@"Teamspeak Server Query Username", typeof(String), _TeamspeakManager.Ts3QueryUsername)); QueueSettingForUpload(new CPluginVariable(@"Teamspeak Server Query Password", typeof(String), _TeamspeakManager.Ts3QueryPassword)); QueueSettingForUpload(new CPluginVariable(@"Teamspeak Server Query Nickname", typeof(String), _TeamspeakManager.Ts3QueryNickname)); QueueSettingForUpload(new CPluginVariable(@"Teamspeak Main Channel Name", typeof(String), _TeamspeakManager.Ts3MainChannelName)); QueueSettingForUpload(new CPluginVariable(@"Teamspeak Secondary Channel Names", typeof(String), CPluginVariable.EncodeStringArray(_TeamspeakManager.Ts3SubChannelNames))); QueueSettingForUpload(new CPluginVariable(@"Debug Display Teamspeak Clients", typeof(Boolean), _TeamspeakManager.DebugClients)); QueueSettingForUpload(new CPluginVariable(@"TeamSpeak Player Join Announcement", typeof(String), _TeamspeakManager.JoinDisplay.ToString())); QueueSettingForUpload(new CPluginVariable(@"TeamSpeak Player Join Message", typeof(String), _TeamspeakManager.JoinDisplayMessage)); QueueSettingForUpload(new CPluginVariable(@"TeamSpeak Player Update Seconds", typeof(Int32), _TeamspeakManager.UpdateIntervalSeconds)); QueueSettingForUpload(new CPluginVariable(@"Enable Teamspeak Player Perks", typeof(Boolean), _TeamspeakPlayerPerksEnable)); QueueSettingForUpload(new CPluginVariable(@"Teamspeak Player Perks - VIP Kick Whitelist", typeof(Boolean), _TeamspeakPlayerPerksVIPKickWhitelist)); QueueSettingForUpload(new CPluginVariable(@"Teamspeak Player Perks - Autobalance Whitelist", typeof(Boolean), _TeamspeakPlayerPerksBalanceWhitelist)); QueueSettingForUpload(new CPluginVariable(@"Teamspeak Player Perks - Ping Whitelist", typeof(Boolean), _TeamspeakPlayerPerksPingWhitelist)); QueueSettingForUpload(new CPluginVariable(@"Teamspeak Player Perks - TeamKillTracker Whitelist", typeof(Boolean), _TeamspeakPlayerPerksTeamKillTrackerWhitelist)); // Discord Monitor QueueSettingForUpload(new CPluginVariable(@"Monitor Discord Players", typeof(Boolean), _DiscordPlayerMonitorView)); QueueSettingForUpload(new CPluginVariable(@"Enable Discord Player Monitor", typeof(Boolean), _DiscordPlayerMonitorEnable)); QueueSettingForUpload(new CPluginVariable(@"Discord Server ID", typeof(String), _DiscordManager.ServerID)); QueueSettingForUpload(new CPluginVariable(@"Discord Channel Names", typeof(String), CPluginVariable.EncodeStringArray(_DiscordManager.ChannelNames))); QueueSettingForUpload(new CPluginVariable(@"Require Voice in Discord to Issue Admin Commands", typeof(Boolean), _DiscordPlayerRequireVoiceForAdmin)); QueueSettingForUpload(new CPluginVariable(@"Discord Player Join Announcement", typeof(String), _DiscordManager.JoinDisplay.ToString())); QueueSettingForUpload(new CPluginVariable(@"Discord Player Join Message", typeof(String), _DiscordManager.JoinMessage)); QueueSettingForUpload(new CPluginVariable(@"Enable Discord Player Perks", typeof(Boolean), _DiscordPlayerPerksEnable)); QueueSettingForUpload(new CPluginVariable(@"Discord Player Perks - VIP Kick Whitelist", typeof(Boolean), _DiscordPlayerPerksVIPKickWhitelist)); QueueSettingForUpload(new CPluginVariable(@"Discord Player Perks - Autobalance Whitelist", typeof(Boolean), _DiscordPlayerPerksBalanceWhitelist)); QueueSettingForUpload(new CPluginVariable(@"Discord Player Perks - Ping Whitelist", typeof(Boolean), _DiscordPlayerPerksPingWhitelist)); QueueSettingForUpload(new CPluginVariable(@"Discord Player Perks - TeamKillTracker Whitelist", typeof(Boolean), _DiscordPlayerPerksTeamKillTrackerWhitelist)); QueueSettingForUpload(new CPluginVariable(@"Debug Display Discord Members", typeof(Boolean), _DiscordManager.DebugMembers)); // Team Power Monitor QueueSettingForUpload(new CPluginVariable(@"Team Power Active Influence", typeof(Double), _TeamPowerActiveInfluence)); QueueSettingForUpload(new CPluginVariable(@"Display Team Power In Procon Chat", typeof(Boolean), _UseTeamPowerDisplayBalance)); QueueSettingForUpload(new CPluginVariable(@"Enable Team Power Scrambler", typeof(Boolean), _UseTeamPowerMonitorScrambler)); QueueSettingForUpload(new CPluginVariable(@"Enable Team Power Join Reassignment", typeof(Boolean), _UseTeamPowerMonitorReassign)); QueueSettingForUpload(new CPluginVariable(@"Team Power Join Reassignment Leniency", typeof(Boolean), _UseTeamPowerMonitorReassignLenient)); QueueSettingForUpload(new CPluginVariable(@"Team Power Join Reassignment Leniency Percent", typeof(Double), _TeamPowerMonitorReassignLenientPercent)); QueueSettingForUpload(new CPluginVariable(@"Enable Team Power Unswitcher", typeof(Boolean), _UseTeamPowerMonitorUnswitcher)); QueueSettingForUpload(new CPluginVariable(@"Enable Team Power Seeder Control", typeof(Boolean), _UseTeamPowerMonitorSeeders)); QueueSettingForUpload(new CPluginVariable(@"Round Timer: Enable", typeof(Boolean), _useRoundTimer)); QueueSettingForUpload(new CPluginVariable(@"Round Timer: Round Duration Minutes", typeof(Double), _maxRoundTimeMinutes)); QueueSettingForUpload(new CPluginVariable(@"Use NO EXPLOSIVES Limiter", typeof(Boolean), _UseWeaponLimiter)); QueueSettingForUpload(new CPluginVariable(@"NO EXPLOSIVES Weapon String", typeof(String), _WeaponLimiterString)); QueueSettingForUpload(new CPluginVariable(@"NO EXPLOSIVES Exception String", typeof(String), _WeaponLimiterExceptionString)); QueueSettingForUpload(new CPluginVariable(@"Use AA Report Auto Handler", typeof(Boolean), _UseAAReportAutoHandler)); QueueSettingForUpload(new CPluginVariable(@"Auto-Report-Handler Strings", typeof(String), CPluginVariable.EncodeStringArray(_AutoReportHandleStrings))); QueueSettingForUpload(new CPluginVariable(@"Use Grenade Cook Catcher", typeof(Boolean), _UseGrenadeCookCatcher)); QueueSettingForUpload(new CPluginVariable(@"Automatically Poll Server For Event Options", typeof(Boolean), _EventPollAutomatic)); QueueSettingForUpload(new CPluginVariable(@"Max Automatic Polls Per Event", typeof(Double), _EventRoundAutoPollsMax)); QueueSettingForUpload(new CPluginVariable(@"Yell Current Winning Rule Option", typeof(Boolean), _eventPollYellWinningRule)); QueueSettingForUpload(new CPluginVariable(@"Weekly Events", typeof(Boolean), _EventWeeklyRepeat)); QueueSettingForUpload(new CPluginVariable(@"Event Day", typeof(String), _EventWeeklyDay.ToString())); QueueSettingForUpload(new CPluginVariable(@"Event Date", typeof(String), _EventDate.ToShortDateString())); QueueSettingForUpload(new CPluginVariable(@"Event Hour in 24 format", typeof(Double), _EventHour)); QueueSettingForUpload(new CPluginVariable(@"Event Test Round Number", typeof(Int32), _EventTestRoundNumber)); QueueSettingForUpload(new CPluginVariable(@"Event Current Round Number", typeof(Int32), _CurrentEventRoundNumber)); QueueSettingForUpload(new CPluginVariable(@"Event Announce Day Difference", typeof(Double), _EventAnnounceDayDifference)); QueueSettingForUpload(new CPluginVariable(@"Event Round Codes", typeof(String[]), _EventRoundOptions.Select(round => round.getCode()).ToArray())); QueueSettingForUpload(new CPluginVariable(@"Poll Max Option Count", typeof(Double), _EventPollMaxOptions)); QueueSettingForUpload(new CPluginVariable(@"Event Round Poll Codes", typeof(String[]), _EventRoundPollOptions.Select(option => option.getCode()).ToArray())); QueueSettingForUpload(new CPluginVariable(@"Event Base Server Name", typeof(String), _eventBaseServerName)); QueueSettingForUpload(new CPluginVariable(@"Event Countdown Server Name", typeof(String), _eventCountdownServerName)); QueueSettingForUpload(new CPluginVariable(@"Event Concrete Countdown Server Name", typeof(String), _eventConcreteCountdownServerName)); QueueSettingForUpload(new CPluginVariable(@"Event Active Server Name", typeof(String), _eventActiveServerName)); QueueSettingForUpload(new CPluginVariable(@"Auto-Kick Players Who First Joined After This Date", typeof(String), _AutoKickNewPlayerDate.ToShortDateString())); QueueSettingForUpload(new CPluginVariable(@"Use LIVE Anti Cheat System", typeof(Boolean), _useAntiCheatLIVESystem)); QueueSettingForUpload(new CPluginVariable(@"LIVE System Includes Mass Murder and Aimbot Checks", typeof(Boolean), _AntiCheatLIVESystemActiveStats)); QueueSettingForUpload(new CPluginVariable(@"DPS Checker: Ban Message", typeof(String), _AntiCheatDPSBanMessage)); QueueSettingForUpload(new CPluginVariable(@"HSK Checker: Enable", typeof(Boolean), _UseHskChecker)); QueueSettingForUpload(new CPluginVariable(@"HSK Checker: Trigger Level", typeof(Double), _HskTriggerLevel)); QueueSettingForUpload(new CPluginVariable(@"HSK Checker: Ban Message", typeof(String), _AntiCheatHSKBanMessage)); QueueSettingForUpload(new CPluginVariable(@"KPM Checker: Enable", typeof(Boolean), _UseKpmChecker)); QueueSettingForUpload(new CPluginVariable(@"KPM Checker: Trigger Level", typeof(Double), _KpmTriggerLevel)); QueueSettingForUpload(new CPluginVariable(@"KPM Checker: Ban Message", typeof(String), _AntiCheatKPMBanMessage)); QueueSettingForUpload(new CPluginVariable(@"AdkatsLRT Extension Token", typeof(String), _AdKatsLRTExtensionToken)); QueueSettingForUpload(new CPluginVariable(@"Fetch Actions from Database", typeof(Boolean), _fetchActionsFromDb)); QueueSettingForUpload(new CPluginVariable(@"Use Additional Ban Message", typeof(Boolean), _UseBanAppend)); QueueSettingForUpload(new CPluginVariable(@"Additional Ban Message", typeof(String), _BanAppend)); QueueSettingForUpload(new CPluginVariable(@"Procon Ban Admin Name", typeof(String), _CBanAdminName)); QueueSettingForUpload(new CPluginVariable(@"Use Ban Enforcer", typeof(Boolean), _UseBanEnforcer)); QueueSettingForUpload(new CPluginVariable(@"Ban Enforcer BF4 Lenient Kick", typeof(Boolean), _BanEnforcerBF4LenientKick)); QueueSettingForUpload(new CPluginVariable(@"Enforce New Bans by NAME", typeof(Boolean), _DefaultEnforceName)); QueueSettingForUpload(new CPluginVariable(@"Enforce New Bans by GUID", typeof(Boolean), _DefaultEnforceGUID)); QueueSettingForUpload(new CPluginVariable(@"Enforce New Bans by IP", typeof(Boolean), _DefaultEnforceIP)); QueueSettingForUpload(new CPluginVariable(@"Countdown Duration before a Nuke is fired", typeof(Int32), _NukeCountdownDurationSeconds)); QueueSettingForUpload(new CPluginVariable(@"Minimum Required Reason Length", typeof(Int32), _RequiredReasonLength)); QueueSettingForUpload(new CPluginVariable(@"Minimum Report Handle Seconds", typeof(Int32), _MinimumReportHandleSeconds)); QueueSettingForUpload(new CPluginVariable(@"Minimum Minutes Into Round To Use Assist", typeof(Int32), _minimumAssistMinutes)); QueueSettingForUpload(new CPluginVariable(@"Allow Commands from Admin Say", typeof(Boolean), _AllowAdminSayCommands)); QueueSettingForUpload(new CPluginVariable(@"Reserved slot grants access to squad lead command", typeof(Boolean), _ReservedSquadLead)); QueueSettingForUpload(new CPluginVariable(@"Reserved slot grants access to self-move command", typeof(Boolean), _ReservedSelfMove)); QueueSettingForUpload(new CPluginVariable(@"Reserved slot grants access to self-kill command", typeof(Boolean), _ReservedSelfKill)); QueueSettingForUpload(new CPluginVariable(@"Banned Tags", typeof(String), CPluginVariable.EncodeStringArray(_BannedTags))); QueueSettingForUpload(new CPluginVariable(@"Punishment Hierarchy", typeof(String), CPluginVariable.EncodeStringArray(_PunishmentHierarchy))); QueueSettingForUpload(new CPluginVariable(@"Combine Server Punishments", typeof(Boolean), _CombineServerPunishments)); QueueSettingForUpload(new CPluginVariable(@"Automatic Forgives", typeof(Boolean), _AutomaticForgives)); QueueSettingForUpload(new CPluginVariable(@"Only Kill Players when Server in low population", typeof(Boolean), _OnlyKillOnLowPop)); QueueSettingForUpload(new CPluginVariable(@"Short Server Name", typeof(String), _shortServerName)); QueueSettingForUpload(new CPluginVariable(@"Low Population Value", typeof(Int32), _lowPopulationPlayerCount)); QueueSettingForUpload(new CPluginVariable(@"High Population Value", typeof(Int32), _highPopulationPlayerCount)); QueueSettingForUpload(new CPluginVariable(@"Automatic Server Restart When Empty", typeof(Boolean), _automaticServerRestart)); QueueSettingForUpload(new CPluginVariable(@"Automatic Restart Minimum Uptime Hours", typeof(Int32), _automaticServerRestartMinHours)); QueueSettingForUpload(new CPluginVariable(@"Automatic Procon Reboot When Server Reboots", typeof(Boolean), _automaticServerRestartProcon)); QueueSettingForUpload(new CPluginVariable(@"Procon Memory Usage MB Warning", typeof(Int32), _MemoryUsageWarn)); QueueSettingForUpload(new CPluginVariable(@"Procon Memory Usage MB AdKats Restart", typeof(Int32), _MemoryUsageRestartPlugin)); QueueSettingForUpload(new CPluginVariable(@"Procon Memory Usage MB Procon Restart", typeof(Int32), _MemoryUsageRestartProcon)); QueueSettingForUpload(new CPluginVariable(@"Use IRO Punishment", typeof(Boolean), _IROActive)); QueueSettingForUpload(new CPluginVariable(@"IRO Punishment Overrides Low Pop", typeof(Boolean), _IROOverridesLowPop)); QueueSettingForUpload(new CPluginVariable(@"IRO Punishment Infractions Required to Override", typeof(Int32), _IROOverridesLowPopInfractions)); QueueSettingForUpload(new CPluginVariable(@"IRO Timeout Minutes", typeof(Int32), _IROTimeout)); QueueSettingForUpload(new CPluginVariable(@"Maximum Temp-Ban Duration Minutes", typeof(Double), _MaxTempBanDuration.TotalMinutes)); QueueSettingForUpload(new CPluginVariable(@"Send Emails", typeof(Boolean), _UseEmail)); QueueSettingForUpload(new CPluginVariable(@"Use SSL?", typeof(Boolean), _EmailHandler.UseSSL)); QueueSettingForUpload(new CPluginVariable(@"SMTP-Server address", typeof(String), _EmailHandler.SMTPServer)); QueueSettingForUpload(new CPluginVariable(@"SMTP-Server port", typeof(Int32), _EmailHandler.SMTPPort)); QueueSettingForUpload(new CPluginVariable(@"Sender address", typeof(String), _EmailHandler.SenderEmail)); QueueSettingForUpload(new CPluginVariable(@"SMTP-Server username", typeof(String), _EmailHandler.SMTPUser)); QueueSettingForUpload(new CPluginVariable(@"SMTP-Server password", typeof(String), _EmailHandler.SMTPPassword)); QueueSettingForUpload(new CPluginVariable(@"Custom HTML Addition", typeof(String), _EmailHandler.CustomHTMLAddition)); QueueSettingForUpload(new CPluginVariable(@"Extra Recipient Email Addresses", typeof(String[]), _EmailHandler.RecipientEmails.ToArray())); QueueSettingForUpload(new CPluginVariable(@"Only Send Report Emails When Admins Offline", typeof(Boolean), _EmailReportsOnlyWhenAdminless)); QueueSettingForUpload(new CPluginVariable(@"Send PushBullet Reports", typeof(Boolean), _UsePushBullet)); QueueSettingForUpload(new CPluginVariable(@"PushBullet Access Token", typeof(String), _PushBulletHandler.AccessToken)); QueueSettingForUpload(new CPluginVariable(@"PushBullet Note Target", typeof(String), _PushBulletHandler.DefaultTarget.ToString())); QueueSettingForUpload(new CPluginVariable(@"PushBullet Channel Tag", typeof(String), _PushBulletHandler.DefaultChannelTag)); QueueSettingForUpload(new CPluginVariable(@"Only Send PushBullet Reports When Admins Offline", typeof(Boolean), _PushBulletReportsOnlyWhenAdminless)); QueueSettingForUpload(new CPluginVariable(@"Send Reports to Discord WebHook", typeof(Boolean), _UseDiscordForReports)); QueueSettingForUpload(new CPluginVariable(@"Discord WebHook URL", typeof(String), _DiscordManager.URL)); QueueSettingForUpload(new CPluginVariable(@"Only Send Discord Reports When Admins Offline", typeof(Boolean), _DiscordReportsOnlyWhenAdminless)); QueueSettingForUpload(new CPluginVariable(@"Send update if reported players leave without action", typeof(Boolean), _DiscordReportsLeftWithoutAction)); QueueSettingForUpload(new CPluginVariable(@"On-Player-Muted Message", typeof(String), _MutedPlayerMuteMessage)); QueueSettingForUpload(new CPluginVariable(@"On-Player-Killed Message", typeof(String), _MutedPlayerKillMessage)); QueueSettingForUpload(new CPluginVariable(@"On-Player-Kicked Message", typeof(String), _MutedPlayerKickMessage)); QueueSettingForUpload(new CPluginVariable(@"# Chances to give player before kicking", typeof(Int32), _MutedPlayerChances)); QueueSettingForUpload(new CPluginVariable(@"Ignore commands for mute enforcement", typeof(Boolean), _MutedPlayerIgnoreCommands)); QueueSettingForUpload(new CPluginVariable(@"Ticket Window High", typeof(Int32), _TeamSwapTicketWindowHigh)); QueueSettingForUpload(new CPluginVariable(@"Ticket Window Low", typeof(Int32), _TeamSwapTicketWindowLow)); QueueSettingForUpload(new CPluginVariable(@"Enable Admin Assistants", typeof(Boolean), _EnableAdminAssistants)); QueueSettingForUpload(new CPluginVariable(@"Enable Admin Assistant Perk", typeof(Boolean), _EnableAdminAssistantPerk)); QueueSettingForUpload(new CPluginVariable(@"Minimum Confirmed Reports Per Month", typeof(Int32), _MinimumRequiredMonthlyReports)); QueueSettingForUpload(new CPluginVariable(@"Yell display time seconds", typeof(Int32), _YellDuration)); QueueSettingForUpload(new CPluginVariable(@"Pre-Message List", typeof(String), CPluginVariable.EncodeStringArray(_PreMessageList.ToArray()))); QueueSettingForUpload(new CPluginVariable(@"Require Use of Pre-Messages", typeof(Boolean), _RequirePreMessageUse)); QueueSettingForUpload(new CPluginVariable(@"Use first spawn message", typeof(Boolean), _UseFirstSpawnMessage)); QueueSettingForUpload(new CPluginVariable(@"First spawn message text", typeof(String), _FirstSpawnMessage)); QueueSettingForUpload(new CPluginVariable(@"Use First Spawn Reputation and Infraction Message", typeof(Boolean), _useFirstSpawnRepMessage)); QueueSettingForUpload(new CPluginVariable(@"Use Perk Expiration Notification", typeof(Boolean), _UsePerkExpirationNotify)); QueueSettingForUpload(new CPluginVariable(@"Perk Expiration Notify Days Remaining", typeof(Int32), _PerkExpirationNotifyDays)); QueueSettingForUpload(new CPluginVariable(@"SpamBot Enable", typeof(Boolean), _spamBotEnabled)); QueueSettingForUpload(new CPluginVariable(@"SpamBot Say List", typeof(String), CPluginVariable.EncodeStringArray(_spamBotSayList.ToArray()))); QueueSettingForUpload(new CPluginVariable(@"SpamBot Say Delay Seconds", typeof(Int32), _spamBotSayDelaySeconds)); QueueSettingForUpload(new CPluginVariable(@"SpamBot Yell List", typeof(String), CPluginVariable.EncodeStringArray(_spamBotYellList.ToArray()))); QueueSettingForUpload(new CPluginVariable(@"SpamBot Yell Delay Seconds", typeof(Int32), _spamBotYellDelaySeconds)); QueueSettingForUpload(new CPluginVariable(@"SpamBot Tell List", typeof(String), CPluginVariable.EncodeStringArray(_spamBotTellList.ToArray()))); QueueSettingForUpload(new CPluginVariable(@"SpamBot Tell Delay Seconds", typeof(Int32), _spamBotTellDelaySeconds)); QueueSettingForUpload(new CPluginVariable(@"Exclude Admins and Whitelist from Spam", typeof(Boolean), _spamBotExcludeAdminsAndWhitelist)); QueueSettingForUpload(new CPluginVariable(@"Player Battlecry Volume", typeof(String), _battlecryVolume.ToString())); QueueSettingForUpload(new CPluginVariable(@"Player Battlecry Max Length", typeof(Int32), _battlecryMaxLength)); QueueSettingForUpload(new CPluginVariable(@"Player Battlecry Denied Words", typeof(String), CPluginVariable.EncodeStringArray(_battlecryDeniedWords))); QueueSettingForUpload(new CPluginVariable(@"Display Admin Name in Kick and Ban Announcement", typeof(Boolean), _ShowAdminNameInAnnouncement)); QueueSettingForUpload(new CPluginVariable(@"Display New Player Announcement", typeof(Boolean), _ShowNewPlayerAnnouncement)); QueueSettingForUpload(new CPluginVariable(@"Display Player Name Change Announcement", typeof(Boolean), _ShowPlayerNameChangeAnnouncement)); QueueSettingForUpload(new CPluginVariable(@"Display Targeted Player Left Notification", typeof(Boolean), _ShowTargetedPlayerLeftNotification)); QueueSettingForUpload(new CPluginVariable(@"Inform players of reports against them", typeof(Boolean), _InformReportedPlayers)); QueueSettingForUpload(new CPluginVariable(@"Inform reputable players of admin joins", typeof(Boolean), _InformReputablePlayersOfAdminJoins)); QueueSettingForUpload(new CPluginVariable(@"Inform admins of admin joins", typeof(Boolean), _InformAdminsOfAdminJoins)); QueueSettingForUpload(new CPluginVariable(@"Use All Caps Limiter", typeof(Boolean), _UseAllCapsLimiter)); QueueSettingForUpload(new CPluginVariable(@"All Caps Limiter Only Limit Specified Players", typeof(Boolean), _AllCapsLimiterSpecifiedPlayersOnly)); QueueSettingForUpload(new CPluginVariable(@"All Caps Limiter Character Percentage", typeof(Int32), _AllCapsLimterPercentage)); QueueSettingForUpload(new CPluginVariable(@"All Caps Limiter Minimum Characters", typeof(Int32), _AllCapsLimterMinimumCharacters)); QueueSettingForUpload(new CPluginVariable(@"All Caps Limiter Warn Threshold", typeof(Int32), _AllCapsLimiterWarnThreshold)); QueueSettingForUpload(new CPluginVariable(@"All Caps Limiter Kill Threshold", typeof(Int32), _AllCapsLimiterKillThreshold)); QueueSettingForUpload(new CPluginVariable(@"All Caps Limiter Kick Threshold", typeof(Int32), _AllCapsLimiterKickThreshold)); QueueSettingForUpload(new CPluginVariable(@"Player Inform Exclusion Strings", typeof(String), CPluginVariable.EncodeStringArray(_PlayerInformExclusionStrings))); QueueSettingForUpload(new CPluginVariable(@"Disable Automatic Updates", typeof(Boolean), _automaticUpdatesDisabled)); QueueSettingForUpload(new CPluginVariable(@"Enforce Single Instance", typeof(Boolean), _enforceSingleInstance)); QueueSettingForUpload(new CPluginVariable(@"AFK System Enable", typeof(Boolean), _AFKManagerEnable)); QueueSettingForUpload(new CPluginVariable(@"AFK Ignore Chat", typeof(Boolean), _AFKIgnoreChat)); QueueSettingForUpload(new CPluginVariable(@"AFK Auto-Kick Enable", typeof(Boolean), _AFKAutoKickEnable)); QueueSettingForUpload(new CPluginVariable(@"AFK Trigger Minutes", typeof(Double), _AFKTriggerDurationMinutes)); QueueSettingForUpload(new CPluginVariable(@"AFK Minimum Players", typeof(Int32), _AFKTriggerMinimumPlayers)); QueueSettingForUpload(new CPluginVariable(@"AFK Ignore User List", typeof(Boolean), _AFKIgnoreUserList)); QueueSettingForUpload(new CPluginVariable(@"AFK Ignore Roles", typeof(String), CPluginVariable.EncodeStringArray(_AFKIgnoreRoles))); QueueSettingForUpload(new CPluginVariable(@"Ping Enforcer Enable", typeof(Boolean), _pingEnforcerEnable)); QueueSettingForUpload(new CPluginVariable(@"Ping Moving Average Duration sec", typeof(Double), _pingMovingAverageDurationSeconds)); QueueSettingForUpload(new CPluginVariable(@"Ping Kick Low Population Trigger ms", typeof(Double), _pingEnforcerLowTriggerMS)); QueueSettingForUpload(new CPluginVariable(@"Ping Kick Medium Population Trigger ms", typeof(Double), _pingEnforcerMedTriggerMS)); QueueSettingForUpload(new CPluginVariable(@"Ping Kick High Population Trigger ms", typeof(Double), _pingEnforcerHighTriggerMS)); QueueSettingForUpload(new CPluginVariable(@"Ping Kick Full Population Trigger ms", typeof(Double), _pingEnforcerFullTriggerMS)); QueueSettingForUpload(new CPluginVariable(@"Ping Kick Low Population Time Modifier", typeof(String), CPluginVariable.EncodeStringArray(_pingEnforcerLowTimeModifier.Select(x => x.ToString()).ToArray()))); QueueSettingForUpload(new CPluginVariable(@"Ping Kick Medium Population Time Modifier", typeof(String), CPluginVariable.EncodeStringArray(_pingEnforcerMedTimeModifier.Select(x => x.ToString()).ToArray()))); QueueSettingForUpload(new CPluginVariable(@"Ping Kick High Population Time Modifier", typeof(String), CPluginVariable.EncodeStringArray(_pingEnforcerHighTimeModifier.Select(x => x.ToString()).ToArray()))); QueueSettingForUpload(new CPluginVariable(@"Ping Kick Full Population Time Modifier", typeof(String), CPluginVariable.EncodeStringArray(_pingEnforcerFullTimeModifier.Select(x => x.ToString()).ToArray()))); QueueSettingForUpload(new CPluginVariable(@"Ping Kick Minimum Players", typeof(Int32), _pingEnforcerTriggerMinimumPlayers)); QueueSettingForUpload(new CPluginVariable(@"Kick Missing Pings", typeof(Boolean), _pingEnforcerKickMissingPings)); QueueSettingForUpload(new CPluginVariable(@"Attempt Manual Ping when Missing", typeof(Boolean), _attemptManualPingWhenMissing)); QueueSettingForUpload(new CPluginVariable(@"Display Ping Enforcer Messages In Procon Chat", typeof(Boolean), _pingEnforcerDisplayProconChat)); QueueSettingForUpload(new CPluginVariable(@"Ping Kick Ignore User List", typeof(Boolean), _pingEnforcerIgnoreUserList)); QueueSettingForUpload(new CPluginVariable(@"Ping Kick Ignore Roles", typeof(String), CPluginVariable.EncodeStringArray(_pingEnforcerIgnoreRoles))); QueueSettingForUpload(new CPluginVariable(@"Ping Kick Message Prefix", typeof(String), _pingEnforcerMessagePrefix)); QueueSettingForUpload(new CPluginVariable(@"Commander Manager Enable", typeof(Boolean), _CMDRManagerEnable)); QueueSettingForUpload(new CPluginVariable(@"Minimum Players to Allow Commanders", typeof(Int32), _CMDRMinimumPlayers)); QueueSettingForUpload(new CPluginVariable(@"Player Lock Manual Duration Minutes", typeof(Double), _playerLockingManualDuration)); QueueSettingForUpload(new CPluginVariable(@"Automatically Lock Players on Admin Action", typeof(Boolean), _playerLockingAutomaticLock)); QueueSettingForUpload(new CPluginVariable(@"Player Lock Automatic Duration Minutes", typeof(Double), _playerLockingAutomaticDuration)); QueueSettingForUpload(new CPluginVariable(@"Display Ticket Rates in Procon Chat", typeof(Boolean), _DisplayTicketRatesInProconChat)); QueueSettingForUpload(new CPluginVariable(@"Surrender Vote Enable", typeof(Boolean), _surrenderVoteEnable)); QueueSettingForUpload(new CPluginVariable(@"Percentage Votes Needed for Surrender", typeof(Double), _surrenderVoteMinimumPlayerPercentage)); QueueSettingForUpload(new CPluginVariable(@"Minimum Player Count to Enable Surrender", typeof(Int32), _surrenderVoteMinimumPlayerCount)); QueueSettingForUpload(new CPluginVariable(@"Minimum Ticket Gap to Surrender", typeof(Int32), _surrenderVoteMinimumTicketGap)); QueueSettingForUpload(new CPluginVariable(@"Enable Required Ticket Rate Gap to Surrender", typeof(Boolean), _surrenderVoteTicketRateGapEnable)); QueueSettingForUpload(new CPluginVariable(@"Minimum Ticket Rate Gap to Surrender", typeof(Double), _surrenderVoteMinimumTicketRateGap)); QueueSettingForUpload(new CPluginVariable(@"Surrender Vote Timeout Enable", typeof(Boolean), _surrenderVoteTimeoutEnable)); QueueSettingForUpload(new CPluginVariable(@"Surrender Vote Timeout Minutes", typeof(Int32), _surrenderVoteTimeoutMinutes)); QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Enable", typeof(Boolean), _surrenderAutoEnable)); QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Use Optimal Values for Metro Conquest", typeof(Boolean), _surrenderAutoUseMetroValues)); QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Use Optimal Values for Locker Conquest", typeof(Boolean), _surrenderAutoUseLockerValues)); QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Reset Trigger Count on Cancel", typeof(Boolean), _surrenderAutoResetTriggerCountOnCancel)); QueueSettingForUpload(new CPluginVariable(@"Reset Auto-Nuke Trigger Count on Fire", typeof(Boolean), _surrenderAutoResetTriggerCountOnFire)); QueueSettingForUpload(new CPluginVariable(@"Nuke Winning Team Instead of Surrendering Losing Team", typeof(Boolean), _surrenderAutoNukeInstead)); QueueSettingForUpload(new CPluginVariable(@"Fire Nuke Triggers if Winning Team up by X Tickets", typeof(Int32), _NukeWinningTeamUpTicketCount)); QueueSettingForUpload(new CPluginVariable(@"Switch to surrender after max nukes", typeof(Boolean), _surrenderAutoNukeResolveAfterMax)); QueueSettingForUpload(new CPluginVariable(@"Only fire ticket difference nukes in high population", typeof(Boolean), _NukeWinningTeamUpTicketHigh)); QueueSettingForUpload(new CPluginVariable(@"Announce Nuke Preparation to Players", typeof(Boolean), _surrenderAutoAnnounceNukePrep)); QueueSettingForUpload(new CPluginVariable(@"Allow Auto-Nuke to fire on losing teams", typeof(Boolean), _surrenderAutoNukeLosingTeams)); QueueSettingForUpload(new CPluginVariable(@"Maximum Nuke Ticket Difference for Losing Team", typeof(Int32), _surrenderAutoNukeLosingMaxDiff)); QueueSettingForUpload(new CPluginVariable(@"Start Surrender Vote Instead of Surrendering Losing Team", typeof(Boolean), _surrenderAutoTriggerVote)); QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Minimum Ticket Gap", typeof(Int32), _surrenderAutoMinimumTicketGap)); QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Minimum Ticket Count", typeof(Int32), _surrenderAutoMinimumTicketCount)); QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Maximum Ticket Count", typeof(Int32), _surrenderAutoMaximumTicketCount)); QueueSettingForUpload(new CPluginVariable(@"Maximum Auto-Nukes Each Round", typeof(Int32), _surrenderAutoMaxNukesEachRound)); QueueSettingForUpload(new CPluginVariable(@"Minimum Seconds Between Nukes", typeof(Int32), _surrenderAutoNukeMinBetween)); QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Losing Team Rate Window Max", typeof(Double), _surrenderAutoLosingRateMax)); QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Losing Team Rate Window Min", typeof(Double), _surrenderAutoLosingRateMin)); QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Winning Team Rate Window Max", typeof(Double), _surrenderAutoWinningRateMax)); QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Winning Team Rate Window Min", typeof(Double), _surrenderAutoWinningRateMin)); QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Message", typeof(String), _surrenderAutoMessage)); QueueSettingForUpload(new CPluginVariable(@"Auto-Nuke Message", typeof(String), _surrenderAutoNukeMessage)); QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Trigger Count to Surrender", typeof(Int32), _surrenderAutoTriggerCountToSurrender)); QueueSettingForUpload(new CPluginVariable(@"Auto-Surrender Minimum Players", typeof(Int32), _surrenderAutoMinimumPlayers)); QueueSettingForUpload(new CPluginVariable(@"Auto-Nuke High Pop Duration Seconds", typeof(Int32), _surrenderAutoNukeDurationHigh)); QueueSettingForUpload(new CPluginVariable(@"Auto-Nuke Medium Pop Duration Seconds", typeof(Int32), _surrenderAutoNukeDurationMed)); QueueSettingForUpload(new CPluginVariable(@"Auto-Nuke Low Pop Duration Seconds", typeof(Int32), _surrenderAutoNukeDurationLow)); QueueSettingForUpload(new CPluginVariable(@"Auto-Nuke Consecutive Duration Increase", typeof(Int32), _surrenderAutoNukeDurationIncrease)); QueueSettingForUpload(new CPluginVariable(@"Auto-Nuke Duration Increase Minimum Ticket Difference", typeof(Int32), _surrenderAutoNukeDurationIncreaseTicketDiff)); QueueSettingForUpload(new CPluginVariable(@"Faction Randomizer: Enable", typeof(Boolean), _factionRandomizerEnable)); QueueSettingForUpload(new CPluginVariable(@"Faction Randomizer: Restriction", typeof(String), _factionRandomizerRestriction.ToString())); QueueSettingForUpload(new CPluginVariable(@"Faction Randomizer: Allow Repeat Team Selections", typeof(Boolean), _factionRandomizerAllowRepeatSelection)); if (ChallengeManager != null) { QueueSettingForUpload(new CPluginVariable(@"Use Challenge System", typeof(Boolean), ChallengeManager.Enabled)); QueueSettingForUpload(new CPluginVariable(@"Challenge System Minimum Players", typeof(Int32), ChallengeManager.MinimumPlayers)); QueueSettingForUpload(new CPluginVariable(@"Challenge Command Lock Timeout Hours", typeof(Int32), ChallengeManager.CommandLockTimeoutHours)); QueueSettingForUpload(new CPluginVariable(@"Challenge System Auto-Assign Round rules", typeof(Boolean), ChallengeManager.AutoPlay)); QueueSettingForUpload(new CPluginVariable(@"Use Server-Wide Round Rules", typeof(Boolean), ChallengeManager.EnableServerRoundRules)); QueueSettingForUpload(new CPluginVariable(@"Use Different Round Rule For Each Player", typeof(Boolean), ChallengeManager.RandomPlayerRoundRules)); } QueueSettingForUpload(new CPluginVariable(@"Use Proxy for Battlelog", typeof(Boolean), _UseProxy)); QueueSettingForUpload(new CPluginVariable(@"Proxy URL", typeof(String), _ProxyURL)); Log.Debug(() => "uploadAllSettings finished!", 6); } catch (Exception e) { Log.HandleException(new AException("Error while queueing all settings for upload.", e)); } } private void UploadSetting(CPluginVariable var) { Log.Debug(() => "uploadSetting starting!", 7); //Make sure database connection active if (_databaseConnectionCriticalState || !_settingsFetched) { return; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { //Check for length too great if (var.Value.Length > 9999) { Log.Error("Unable to upload setting, length of setting too great. Really dude? It's 10000+ characters. This is battlefield, not a book club."); return; } Log.Debug(() => var.Value, 7); //Set the insert command structure command.CommandText = @" INSERT INTO `" + _mySqlSchemaName + @"`.`adkats_settings` ( `server_id`, `setting_name`, `setting_type`, `setting_value` ) VALUES ( @server_id, @setting_name, @setting_type, @setting_value ) ON DUPLICATE KEY UPDATE `setting_value` = @setting_value"; command.Parameters.AddWithValue("@server_id", _serverInfo.ServerID); command.Parameters.AddWithValue("@setting_name", var.Name); command.Parameters.AddWithValue("@setting_type", var.Type); command.Parameters.AddWithValue("@setting_value", var.Value); //Attempt to execute the query if (SafeExecuteNonQuery(command) > 0) { Log.Debug(() => "Setting " + var.Name + " pushed to database", 7); } } } } catch (Exception e) { Log.HandleException(new AException("Error while uploading setting to database.", e)); } Log.Debug(() => "uploadSetting finished!", 7); } private void FetchSettings(long serverID, Boolean verbose) { Log.Debug(() => "fetchSettings starting!", 6); Boolean success = false; //Make sure database connection active if (_databaseConnectionCriticalState) { return; } try { //Success fetching settings using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { String sql = @" SELECT `setting_name`, `setting_type`, `setting_value` FROM `" + _mySqlSchemaName + @"`.`adkats_settings` WHERE `server_id` = " + serverID; command.CommandText = sql; using (MySqlDataReader reader = SafeExecuteReader(command)) { //Grab the settings while (reader.Read()) { success = true; //Create as variable in case needed later CPluginVariable var = new CPluginVariable(reader.GetString("setting_name"), reader.GetString("setting_type"), reader.GetString("setting_value")); SetPluginVariable(var.Name, var.Value); } if (success) { _lastDbSettingFetch = UtcNow(); UpdateSettingPage(); } else if (verbose) { Log.Error("Settings could not be loaded. Server " + serverID + " invalid."); } UploadAllSettings(); _settingsFetched = true; _settingImportID = _serverInfo.ServerID; } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching settings from database.", e)); } Log.Debug(() => "fetchSettings finished!", 6); } private void UploadCommand(ACommand aCommand) { Log.Debug(() => "uploadCommand starting!", 6); //Make sure database connection active if (_databaseConnectionCriticalState) { return; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { //Set the insert command structure command.CommandText = @" INSERT INTO `" + _mySqlSchemaName + @"`.`adkats_commands` ( `command_id`, `command_active`, `command_key`, `command_logging`, `command_name`, `command_text`, `command_playerInteraction`, `command_access` ) VALUES ( @command_id, @command_active, @command_key, @command_logging, @command_name, @command_text, @command_playerInteraction, @command_access ) ON DUPLICATE KEY UPDATE `command_active` = @command_active, `command_logging` = @command_logging, `command_name` = @command_name, `command_text` = @command_text, `command_playerInteraction` = @command_playerInteraction, `command_access` = @command_access"; //Fill the command command.Parameters.AddWithValue("@command_id", aCommand.command_id); command.Parameters.AddWithValue("@command_active", aCommand.command_active.ToString()); command.Parameters.AddWithValue("@command_key", aCommand.command_key); command.Parameters.AddWithValue("@command_logging", aCommand.command_logging.ToString()); command.Parameters.AddWithValue("@command_name", aCommand.command_name); command.Parameters.AddWithValue("@command_text", aCommand.command_text); command.Parameters.AddWithValue("@command_playerInteraction", aCommand.command_playerInteraction); command.Parameters.AddWithValue("@command_access", aCommand.command_access.ToString()); //Attempt to execute the query if (SafeExecuteNonQuery(command) > 0) { } } } Log.Debug(() => "uploadCommand finished!", 6); } catch (Exception e) { Log.HandleException(new AException("Unexpected error uploading command.", e)); } } private List FetchAdminSoldiers() { List adminSoldiers = new List(); try { //Loop over the user list foreach (AUser user in _userCache.Values.ToList().Where(UserIsAdmin)) { adminSoldiers.AddRange(user.soldierDictionary.Values); } } catch (Exception e) { Log.HandleException(new AException("Error while fetching admin soldiers.", e)); } return adminSoldiers; } private List FetchOnlineAdminSoldiers() { List onlineAdminSoldiers = new List(); try { onlineAdminSoldiers.AddRange(_PlayerDictionary.Values.ToList().Where(PlayerIsAdmin)); } catch (Exception e) { Log.HandleException(new AException("Error while fetching online admin soldiers", e)); } return onlineAdminSoldiers; } private List FetchOnlineNonAdminSoldiers() { List nonAdminSoldiers = new List(); try { nonAdminSoldiers.AddRange(_PlayerDictionary.Values.ToList().Where(aPlayer => !PlayerIsAdmin(aPlayer))); } catch (Exception e) { Log.HandleException(new AException("Error while fetching online non-admin soldiers", e)); } return nonAdminSoldiers; } private List FetchElevatedSoldiers() { List elevatedSoldiers = new List(); //Loop over the user list foreach (AUser aUser in _userCache.Values.ToList().Where(user => !UserIsAdmin(user) && user.user_role.role_key != "guest_default")) { elevatedSoldiers.AddRange(aUser.soldierDictionary.Values); } return elevatedSoldiers; } private List FetchSoldiersOfRole(ARole aRole) { List roleSoldiers = new List(); //Loop over the user list foreach (AUser user in _userCache.Values.ToList().Where(user => user.user_role.role_key == aRole.role_key)) { roleSoldiers.AddRange(user.soldierDictionary.Values); } return roleSoldiers; } private List FetchAllUserSoldiers() { List userSoldiers = new List(); //Loop over the user list foreach (AUser user in _userCache.Values.ToList().Where(aUser => aUser.user_role.role_key != "guest_default")) { userSoldiers.AddRange(user.soldierDictionary.Values); } return userSoldiers; } private Boolean HandleRecordUpload(ARecord record) { //Make sure database connection active if (_databaseConnectionCriticalState) { record.record_exception = new AException("Database not connected."); return true; } try { Log.Debug(() => "Entering handle record upload", 5); if (record.record_id != -1 || record.record_action_executed) { //Record already has a record ID, or action has already been taken, it can only be updated if (record.command_type.command_logging != ACommand.CommandLogging.Ignore && record.command_type.command_logging != ACommand.CommandLogging.Unable && !record.record_orchestrate) { if (record.record_exception == null) { //Only call update if the record contained no errors Log.Debug(() => "UPDATING record " + record.record_id + " for " + record.command_type, 5); //Update Record UpdateRecord(record); return false; } Log.Debug(() => "" + record.command_type + " record contained errors, skipping UPDATE", 4); } else { Log.Debug(() => "Skipping record UPDATE for " + record.command_type, 5); } } else { Log.Debug(() => "Record needs full upload, checking.", 5); //No record ID. Perform full upload switch (record.command_type.command_key) { //TODO: Add ability for multiple targets case "player_punish": //Upload for punish is required if (CanPunish(record, 20)) { //Check if the punish will be Double counted Boolean iroStatus = _IROActive && FetchIROStatus(record); if (iroStatus) { record.isIRO = true; //Upload record twice Log.Debug(() => "UPLOADING IRO Punish", 5); //IRO - Immediate Repeat Offence UploadRecord(record); UploadRecord(record); } else { //Upload record once Log.Debug(() => "UPLOADING Punish", 5); UploadRecord(record); } } else { SendMessageToSource(record, record.GetTargetNames() + " already acted on in the last 20 seconds."); FinalizeRecord(record); return false; } break; //TODO: Add ability for multiple targets case "player_forgive": //Upload for forgive is required //No restriction on forgives/minute Log.Debug(() => "UPLOADING Forgive", 5); UploadRecord(record); break; default: //Case for any other command //Check logging setting for record command type if (record.command_type.command_logging != ACommand.CommandLogging.Ignore && record.command_type.command_logging != ACommand.CommandLogging.Unable) { Log.Debug(() => "UPLOADING record for " + record.command_type, 5); //Upload Record UploadRecord(record); } else { Log.Debug(() => "Skipping record UPLOAD for " + record.command_type, 6); } break; } } } catch (Exception e) { record.record_exception = Log.HandleException(new AException("Error while handling record upload.", e)); } return true; } private Boolean UploadRecord(ARecord record) { Boolean success = true; //If record has multiple targets, create a new record for each target if (record.TargetPlayersLocal.Any()) { record.TargetInnerRecords.Clear(); foreach (APlayer aPlayer in record.TargetPlayersLocal) { ARecord aRecord = new ARecord { isAliveChecked = record.isAliveChecked, isContested = record.isContested, isDebug = record.isDebug, isIRO = record.isIRO, record_source = record.record_source, record_access = record.record_access, server_id = record.server_id, command_type = record.command_type, command_action = record.command_action, command_numeric = record.command_numeric, target_name = aPlayer.player_name, target_player = aPlayer, source_name = record.source_name, source_player = record.source_player, record_message = record.record_message, record_action_executed = record.record_action_executed, record_time = record.record_time }; record.TargetInnerRecords.Add(aRecord); if (!UploadInnerRecord(aRecord)) { success = false; } } } else { success = UploadInnerRecord(record); } return success; } private Boolean UploadInnerRecord(ARecord record) { Log.Debug(() => "uploadRecord starting!", 6); Boolean success = false; //Make sure database connection active if (_databaseConnectionCriticalState) { record.record_exception = new AException("Database not connected."); return false; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { //Decide which table the record should be added to String tablename = (record.isDebug) ? ("`adkats_records_debug`") : ("`adkats_records_main`"); //Set the insert command structure if (record.record_held) { command.CommandText = @" INSERT INTO " + tablename + @" ( `server_id`, `command_type`, `command_action`, `command_numeric`, `target_name`, `target_id`, `source_name`, `source_id`, `record_message`, `record_time`, `adkats_read` ) VALUES ( @server_id, @command_type, @command_action, @command_numeric, @target_name, @target_id, @source_name, @source_id, @record_message, @record_time, @record_orchestrate )"; command.Parameters.AddWithValue("@record_time", record.record_time); } else { command.CommandText = @" INSERT INTO " + tablename + @" ( `server_id`, `command_type`, `command_action`, `command_numeric`, `target_name`, `target_id`, `source_name`, `source_id`, `record_message`, `record_time`, `adkats_read` ) VALUES ( @server_id, @command_type, @command_action, @command_numeric, @target_name, @target_id, @source_name, @source_id, @record_message, UTC_TIMESTAMP(), @adkats_read )"; } //Fill the command if (record.server_id == 0) { Log.Error("Record server ID was invalid, unable to continue."); return false; } command.Parameters.AddWithValue("@server_id", record.server_id); if (record.command_type == null) { Log.Error("Command type was null in uploadRecord, unable to continue."); return false; } command.Parameters.AddWithValue("@command_type", record.command_type.command_id); if (record.command_action == null) { record.command_action = record.command_type; } command.Parameters.AddWithValue("@command_action", record.command_action.command_id); command.Parameters.AddWithValue("@command_numeric", record.command_numeric); String tName = "NoNameTarget"; if (!String.IsNullOrEmpty(record.target_name)) { tName = record.target_name; } if (record.target_player != null) { if (!String.IsNullOrEmpty(record.target_player.player_name)) { tName = record.target_player.player_name; } if (record.target_player.player_id <= 0) { Log.Error("Target ID invalid when uploading record. Unable to complete."); record.record_exception = new AException("Target ID invalid when uploading record. Unable to complete."); SendMessageToSource(record, "Target ID invalid when uploading record. Unable to complete."); FinalizeRecord(record); return false; } command.Parameters.AddWithValue("@target_id", record.target_player.player_id); } else { command.Parameters.AddWithValue("@target_id", null); } command.Parameters.AddWithValue("@target_name", tName); String sName = "NoNameSource"; if (!String.IsNullOrEmpty(record.source_name)) { sName = record.source_name; } if (record.source_player != null) { if (!String.IsNullOrEmpty(record.source_player.player_name)) { sName = record.source_player.player_name; } if (record.source_player.player_id <= 0) { Log.Error("Source ID invalid when uploading record. Unable to complete."); record.record_exception = new AException("Source ID invalid when uploading record. Unable to complete."); SendMessageToSource(record, "Source ID invalid when uploading record. Unable to complete."); FinalizeRecord(record); return false; } command.Parameters.AddWithValue("@source_id", record.source_player.player_id); } else { command.Parameters.AddWithValue("@source_id", null); } command.Parameters.AddWithValue("@source_name", sName); String messageIRO = record.record_message + ((record.isIRO) ? (" [IRO]") : ("")); //Trim to 500 characters (Should only hit this limit when processing error messages) messageIRO = messageIRO.Length <= 500 ? messageIRO : messageIRO.Substring(0, 500); command.Parameters.AddWithValue("@record_message", messageIRO); //Orchestration of other AdKats instances if (record.record_orchestrate) { command.Parameters.AddWithValue("@adkats_read", "N"); } else { command.Parameters.AddWithValue("@adkats_read", "Y"); } //Get reference to the command in case of error //Attempt to execute the query if (SafeExecuteNonQuery(command) > 0) { success = true; record.record_id = command.LastInsertedId; } } } if (success) { Log.Debug(() => record.command_action.command_key + " upload for " + record.GetTargetNames() + " by " + record.GetSourceName() + " SUCCESSFUL!", 3); } else { record.record_exception = new AException("Unknown error uploading record."); Log.HandleException(record.record_exception); } Log.Debug(() => "uploadRecord finished!", 6); return success; } catch (Exception e) { record.record_exception = new AException("Unexpected error uploading Record.", e); Log.HandleException(record.record_exception); return false; } } private Boolean UploadStatistic(AStatistic aStat) { Log.Debug(() => "UploadStatistic starting!", 6); Boolean success = false; //Make sure database connection active if (_databaseConnectionCriticalState) { return false; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { //Set the insert command structure command.CommandText = @" INSERT INTO `adkats_statistics` ( `server_id`, `round_id`, `stat_type`, `target_name`, `target_id`, `stat_value`, `stat_comment`, `stat_time` ) VALUES ( @server_id, @round_id, @stat_type, @target_name, @target_id, @stat_value, @stat_comment, @stat_time )"; //Fill the command if (aStat.server_id == 0) { Log.HandleException(new AException("Statistic server ID was invalid when uploading, unable to continue.")); return false; } command.Parameters.AddWithValue("@server_id", aStat.server_id); if (aStat.round_id == 0) { return false; } command.Parameters.AddWithValue("@round_id", aStat.round_id); command.Parameters.AddWithValue("@stat_type", aStat.stat_type.ToString()); String tName = null; if (aStat.target_player != null) { if (!String.IsNullOrEmpty(aStat.target_player.player_name)) { tName = aStat.target_player.player_name; } command.Parameters.AddWithValue("@target_id", aStat.target_player.player_id); } else { command.Parameters.AddWithValue("@target_id", null); } if (!String.IsNullOrEmpty(tName)) { command.Parameters.AddWithValue("@target_name", tName); } else { if (!String.IsNullOrEmpty(aStat.target_name)) { command.Parameters.AddWithValue("@target_name", aStat.target_name); } else { Log.HandleException(new AException("Statistic target name null or empty when uploading, unable to continue.")); return false; } } command.Parameters.AddWithValue("@stat_value", aStat.stat_value); command.Parameters.AddWithValue("@stat_comment", aStat.stat_comment); command.Parameters.AddWithValue("@stat_time", aStat.stat_time); //Get reference to the command in case of error //Attempt to execute the query if (SafeExecuteNonQuery(command) > 0) { success = true; aStat.stat_id = command.LastInsertedId; } } } if (success) { Log.Debug(() => aStat.stat_type + " stat upload for " + aStat.target_name + " SUCCESSFUL!", 4); } else { Log.HandleException(new AException("Unknown error uploading statistic.")); } Log.Debug(() => "UploadStatistic finished!", 6); return success; } catch (Exception e) { Log.HandleException(new AException("Unexpected error uploading statistic.", e)); return false; } } private Boolean UploadChatLog(AChatMessage messageObject) { Log.Debug(() => "UploadChatLog starting!", 6); Boolean success = false; if (!_threadsReady) { return success; } //comorose BF4/BFHL chat handle if (messageObject.OriginalMessage.Contains("ID_CHAT") || messageObject.OriginalMessage.Contains("AdKatsInstanceCheck")) { success = true; return success; } //Make sure database connection active if (_databaseConnectionCriticalState) { Log.HandleException(new AException("Database not connected on chat upload.")); return success; } //Server spam check if (!_PostStatLoggerChatManually_PostServerChatSpam && messageObject.Speaker == "Server") { success = true; return success; } //Ignore command check if (_PostStatLoggerChatManually_IgnoreCommands && (messageObject.OriginalMessage.StartsWith("@") || messageObject.OriginalMessage.StartsWith("!") || messageObject.OriginalMessage.StartsWith(".") || messageObject.OriginalMessage.StartsWith("/"))) { success = true; return success; } MySqlCommand commandAttempt = null; try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { //Set the insert command structure command.CommandText = @"INSERT INTO `tbl_chatlog` ( `logDate`, `ServerID`, `logSubset`, `logPlayerID`, `logSoldierName`, `logMessage` ) VALUES ( UTC_TIMESTAMP(), @server_id, @log_subset, @log_player_id, @log_player_name, @log_message )"; //Fetch the player from player dictionary APlayer aPlayer = null; if (_PlayerDictionary.TryGetValue(messageObject.Speaker, out aPlayer)) { aPlayer.LastUsage = UtcNow(); Log.Debug(() => "Player found for chat log upload.", 5); } //Fill the log command.Parameters.AddWithValue("@server_id", _serverInfo.ServerID); command.Parameters.AddWithValue("@log_subset", messageObject.Subset.ToString()); if (aPlayer != null && aPlayer.player_id > 0) { command.Parameters.AddWithValue("@log_player_id", aPlayer.player_id); } else { command.Parameters.AddWithValue("@log_player_id", null); } command.Parameters.AddWithValue("@log_player_name", messageObject.Speaker); //Trim to 255 characters String logMessage = messageObject.Message.Length <= 255 ? messageObject.OriginalMessage : messageObject.OriginalMessage.Substring(0, 255); command.Parameters.AddWithValue("@log_message", logMessage); //Get reference to the command in case of error commandAttempt = command; //Attempt to execute the query if (SafeExecuteNonQuery(command) > 0) { success = true; } } } if (success) { Log.Debug(() => "Chat upload for " + messageObject.Speaker + " SUCCESSFUL!", 5); } else { Log.HandleException(new AException("Error uploading chat log. Success not reached.")); return success; } Log.Debug(() => "UploadChatLog finished!", 6); return success; } catch (Exception e) { Log.HandleException(new AException("Unexpected error uploading chat log.", e)); return success; } } private void UpdateRecordEndPointReputations(ARecord aRecord) { Log.Debug(() => "Updating endpoint reputation for " + aRecord.command_action + " record.", 5); if (aRecord.source_player != null && aRecord.source_player.player_id > 0) { UpdatePlayerReputation(aRecord.source_player, true); } if (aRecord.target_player != null && aRecord.target_player.player_id > 0) { UpdatePlayerReputation(aRecord.target_player, true); } if (aRecord.TargetPlayersLocal != null) { foreach (APlayer aPlayer in aRecord.TargetPlayersLocal) { UpdatePlayerReputation(aPlayer, true); } } } private void UpdatePlayerReputation(APlayer aPlayer, Boolean informPlayer) { try { if (aPlayer == null) { Log.Error("Attempted to update reputation of invalid player."); return; } if (_commandSourceReputationDictionary == null || !_commandSourceReputationDictionary.Any() || _commandTargetReputationDictionary == null || !_commandTargetReputationDictionary.Any()) { Log.Debug(() => "Reputation dictionaries not populated. Can't update reputation for " + aPlayer.GetVerboseName() + ".", 4); } double sourceReputation = 0.0; double targetReputation = 0.0; double pointReputation = 0; List recentPunishments = FetchRecentRecords(aPlayer.player_id, GetCommandByKey("player_punish").command_id, 10000, 10000, true, false); foreach (ARecord punishment in recentPunishments) { TimeSpan timeSince = UtcNow() - punishment.record_time; if (timeSince.TotalDays < 50) { pointReputation -= 20 * ((50 - timeSince.TotalDays) / 50); } } List recentForgives = FetchRecentRecords(aPlayer.player_id, GetCommandByKey("player_forgive").command_id, 10000, 10000, true, false); foreach (ARecord forgive in recentForgives) { TimeSpan timeSince = UtcNow() - forgive.record_time; if (timeSince.TotalDays < 50) { pointReputation += 20 * ((50 - timeSince.TotalDays) / 50); } } if (pointReputation > 0) { pointReputation = 0; } targetReputation = pointReputation; double totalReputation = 0; double totalReputationConstrained = 0; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT command_type, command_action, count(record_id) command_count FROM adkats_records_main WHERE source_id = @player_id AND target_name <> source_name GROUP BY command_type, command_action"; command.Parameters.AddWithValue("player_id", aPlayer.player_id); using (MySqlDataReader reader = SafeExecuteReader(command)) { while (reader.Read()) { String typeAction = reader.GetInt32("command_type") + "|" + reader.GetInt32("command_action"); Double command_count = reader.GetDouble("command_count"); Double weight = 0; if (_commandSourceReputationDictionary.TryGetValue(typeAction, out weight)) { sourceReputation += (weight * command_count); } else { Log.Warn("Unable to find source weight for command " + typeAction); } } } } using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT command_type, command_action, count(record_id) command_count FROM adkats_records_main WHERE target_id = @player_id AND target_name <> source_name GROUP BY command_type, command_action"; command.Parameters.AddWithValue("player_id", aPlayer.player_id); using (MySqlDataReader reader = SafeExecuteReader(command)) { while (reader.Read()) { String typeAction = reader.GetInt32("command_type") + "|" + reader.GetInt32("command_action"); Double command_count = reader.GetDouble("command_count"); Double weight = 0; if (_commandTargetReputationDictionary.TryGetValue(typeAction, out weight)) { targetReputation += (weight * command_count); } else { Log.Warn("Unable to find target weight for command " + typeAction); } } } } //Special case for certain commands with same source and target, but should still be counted //Currently only the assist command is counted (Command ID 51) using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT command_type, command_action, count(record_id) command_count FROM adkats_records_main WHERE source_id = @player_id AND target_id = source_id AND command_type = 51 AND command_action = 51 GROUP BY command_type, command_action"; command.Parameters.AddWithValue("player_id", aPlayer.player_id); using (MySqlDataReader reader = SafeExecuteReader(command)) { while (reader.Read()) { String typeAction = reader.GetInt32("command_type") + "|" + reader.GetInt32("command_action"); Double command_count = reader.GetDouble("command_count"); Double weight = 0; if (_commandSourceReputationDictionary.TryGetValue(typeAction, out weight)) { sourceReputation += (weight * command_count); } else { Log.Error("Unable to find source weight for command " + typeAction); } if (_commandTargetReputationDictionary.TryGetValue(typeAction, out weight)) { targetReputation += (weight * command_count); } else { Log.Error("Unable to find target weight for command " + typeAction); } } } } totalReputation = sourceReputation + targetReputation; if (totalReputation >= 0) { totalReputationConstrained = (1000 * totalReputation) / (totalReputation + 1000); } else { totalReputationConstrained = -(1000 * Math.Abs(totalReputation)) / (Math.Abs(totalReputation) + 1000); } using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" REPLACE INTO adkats_player_reputation VALUES ( @player_id, @game_id, @target_rep, @source_rep, @total_rep, @total_rep_co )"; if (aPlayer.player_id <= 0) { Log.Error("Player ID invalid when updating player reputation. Unable to complete."); return; } command.Parameters.AddWithValue("player_id", aPlayer.player_id); if (aPlayer.game_id <= 0) { aPlayer.game_id = _serverInfo.GameID; } command.Parameters.AddWithValue("game_id", aPlayer.game_id); command.Parameters.AddWithValue("target_rep", targetReputation); command.Parameters.AddWithValue("source_rep", sourceReputation); command.Parameters.AddWithValue("total_rep", totalReputation); command.Parameters.AddWithValue("total_rep_co", totalReputationConstrained); Int32 rowsAffected = SafeExecuteNonQuery(command); if (_firstPlayerListComplete && Math.Abs(aPlayer.player_reputation - totalReputationConstrained) > .02) { Log.Debug(() => aPlayer.GetVerboseName() + "'s reputation updated from " + Math.Round(aPlayer.player_reputation, 2) + " to " + Math.Round(totalReputationConstrained, 2), 3); if (aPlayer.player_spawnedOnce || (aPlayer.fbpInfo != null && aPlayer.fbpInfo.TeamID == 0)) { if (!PlayerIsAdmin(aPlayer)) { String message = "Your reputation "; if (totalReputationConstrained > aPlayer.player_reputation) { if (Math.Round(totalReputationConstrained, 2) == 0) { message += "increased from " + Math.Round(aPlayer.player_reputation, 2) + " to " + Math.Round(totalReputationConstrained, 2) + "!"; } else if (totalReputationConstrained > 0) { message += "increased from " + Math.Round(aPlayer.player_reputation, 2) + " to " + Math.Round(totalReputationConstrained, 2) + "! Thanks for your help!"; } else { message += "increased from " + Math.Round(aPlayer.player_reputation, 2) + " to " + Math.Round(totalReputationConstrained, 2) + ", but is still negative."; } } else { if (aPlayer.player_reputation >= 0) { if (totalReputationConstrained < 0) { message += "has gone negative! Be careful, it's now " + Math.Round(totalReputationConstrained, 2); } else { message += "decreased from " + Math.Round(aPlayer.player_reputation, 2) + " to " + Math.Round(totalReputationConstrained, 2); } } else { message += "decreased further from " + Math.Round(aPlayer.player_reputation, 2) + " to " + Math.Round(totalReputationConstrained, 2); } } if (informPlayer) { aPlayer.Say(message); } } } } aPlayer.player_reputation = totalReputationConstrained; } } } catch (Exception e) { Log.HandleException(new AException("Error while updating player reputation.", e)); } } private Boolean SendQuery(String query, Boolean verbose) { if (String.IsNullOrEmpty(query)) { return false; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { //Attempt to execute the query command.CommandText = query; using (MySqlDataReader reader = SafeExecuteReader(command)) { if (verbose) { if (reader.Read()) { Log.Success("Query returned value " + reader.GetValue(0).ToString() + "."); return true; } else { Log.Error("Query returned no results."); return false; } } else { return reader.Read(); } } } } } catch (Exception e) { if (verbose) { Log.HandleException(new AException("Verbose. Error while performing query.", e)); } return false; } } private Boolean SendNonQuery(String desc, String nonQuery, Boolean verbose) { if (String.IsNullOrEmpty(nonQuery)) { return false; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = nonQuery; //Attempt to execute the non query Int32 rowsAffected = SafeExecuteNonQuery(command); if (verbose) { Log.Success("Non-Query success. " + rowsAffected + " rows affected. [" + desc + "]"); } return true; } } } catch (Exception e) { if (verbose) { Log.Error("Non-Query failed. [" + desc + "]: " + e); } return false; } } private void UpdateRecord(ARecord record) { //If record has multiple inner records, update those instead if (record.TargetInnerRecords.Any()) { foreach (ARecord innerRecord in record.TargetInnerRecords) { //Update the inner record with action, numeric, and message, before pushing innerRecord.command_action = record.command_action; innerRecord.command_numeric = record.command_numeric; innerRecord.record_message = record.record_message; //Call inner upload UpdateInnerRecord(innerRecord); } } else { UpdateInnerRecord(record); } } private void UpdateInnerRecord(ARecord record) { Log.Debug(() => "UpdateInnerRecord starting!", 6); //Make sure database connection active if (_databaseConnectionCriticalState) { record.record_exception = new AException("Database not connected."); return; } try { Int32 attempts = 0; Boolean success = false; do { try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { String tablename = (record.isDebug) ? ("`adkats_records_debug`") : ("`adkats_records_main`"); //Set the insert command structure command.CommandText = "UPDATE " + tablename + @" SET `command_action` = @command_action, `command_numeric` = @command_numeric, `record_message` = @record_message, `adkats_read` = 'Y' WHERE `record_id` = @record_id"; //Fill the command command.Parameters.AddWithValue("@record_id", record.record_id); command.Parameters.AddWithValue("@command_numeric", record.command_numeric); //Trim to 500 characters record.record_message = record.record_message.Length <= 500 ? record.record_message : record.record_message.Substring(0, 500); command.Parameters.AddWithValue("@record_message", record.record_message); command.Parameters.AddWithValue("@command_action", record.command_action.command_id); //Attempt to execute the query Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { success = true; } } } } catch (Exception e) { Log.HandleException(new AException("Error while updating record.", e)); success = false; } } while (!success && attempts++ < 5); UpdateRecordEndPointReputations(record); if (success) { Log.Debug(() => record.command_action.command_key + " update for " + record.GetTargetNames() + " by " + record.GetSourceName() + " SUCCESSFUL!", 3); } else { Log.Error(record.command_action.command_key + " update for " + record.GetTargetNames() + " by " + record.GetSourceName() + " FAILED!"); } Log.Debug(() => "UpdateInnerRecord finished!", 6); } catch (Exception e) { Log.HandleException(new AException("Error while updating record", e)); } } private ARecord FetchRecordUpdate(ARecord record) { Log.Debug(() => "FetchRecordUpdate starting!", 6); //Make sure database connection active if (_databaseConnectionCriticalState) { return record; } if (record.record_id < 1) { return record; } try { List reportsToExpire = new List(); var reupload = false; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { String tablename = (record.isDebug) ? ("`adkats_records_debug`") : ("`adkats_records_main`"); String sql = @" SELECT `command_type`, `command_action`, `command_numeric`, `target_name`, `target_id`, `source_name`, `source_id`, `record_message` FROM " + tablename + @" WHERE `record_id` = " + record.record_id; command.CommandText = sql; using (MySqlDataReader reader = SafeExecuteReader(command)) { //Grab the record if (reader.Read()) { var commandNumeric = reader.GetInt32("command_numeric"); if (commandNumeric != record.command_numeric) { // Don't allow command numeric updates if the new number is 0, but we already have a number if (record.command_numeric != 0 && commandNumeric == 0) { // In fact, fix the record on the database side reupload = true; Log.Info("Record " + record.record_id + " had an invalid command numeric. Fixing back to " + record.command_numeric + "."); } else { Log.Info("Record " + record.record_id + " command numeric changed from " + record.command_numeric + " to " + commandNumeric); record.command_numeric = commandNumeric; } } var targetName = reader.GetString("target_name"); if (targetName != record.target_name) { Log.Info("Record " + record.record_id + " target name changed from " + record.target_name + " to " + targetName); record.target_name = targetName; } if (!reader.IsDBNull(4)) { Int64 targetID = reader.GetInt64(4); if (record.target_player == null) { record.target_player = FetchPlayer(false, false, false, null, targetID, null, null, null, null); if (record.target_player == null) { Log.Error("Unable to fetch target player for ID " + targetID + " when fetching record " + record.record_id + " updates."); } else { Log.Info("Record " + record.record_id + " added target player " + record.target_player.GetVerboseName()); } } else if (targetID != record.target_player.player_id) { var newPlayer = FetchPlayer(false, false, false, null, targetID, null, null, null, null); if (newPlayer == null) { Log.Error("Unable to fetch target player change for ID " + targetID + " when fetching record " + record.record_id + " updates."); } else { Log.Info("Record " + record.record_id + " target player changed from " + record.target_player.GetVerboseName() + " to " + newPlayer.GetVerboseName()); record.target_player = newPlayer; } } } var sourceName = reader.GetString("source_name"); if (sourceName != record.source_name) { Log.Info("Record " + record.record_id + " source name changed from " + record.source_name + " to " + sourceName); record.source_name = sourceName; } if (!reader.IsDBNull(6)) { Int64 sourceID = reader.GetInt64(6); if (record.source_player == null) { record.source_player = FetchPlayer(false, false, false, null, sourceID, null, null, null, null); if (record.source_player == null) { Log.Error("Unable to fetch source player for ID " + sourceID + " when fetching record " + record.record_id + " updates."); } else { Log.Info("Record " + record.record_id + " added source player " + record.source_player.GetVerboseName()); } } else if (sourceID != record.source_player.player_id) { var newPlayer = FetchPlayer(false, false, false, null, sourceID, null, null, null, null); if (newPlayer == null) { Log.Error("Unable to fetch source player change for ID " + sourceID + " when fetching record " + record.record_id + " updates."); } else { Log.Info("Record " + record.record_id + " source player changed from " + record.source_player.GetVerboseName() + " to " + newPlayer.GetVerboseName()); record.source_player = newPlayer; } } } var recordMessage = reader.GetString("record_message"); if (recordMessage != record.record_message) { Log.Info("Record " + record.record_id + " message changed from '" + record.record_message + "' to '" + recordMessage + "'"); record.record_message = recordMessage; } Int32 commandTypeInt = reader.GetInt32("command_type"); ACommand commandType; if (!_CommandIDDictionary.TryGetValue(commandTypeInt, out commandType)) { Log.Error("Unable to parse command type " + commandTypeInt + " when fetching record " + record.record_id + " updates."); } if (commandType.command_key != record.command_type.command_key) { Log.Info("Record " + record.record_id + " command type changed from " + record.command_type.command_name + " to " + commandType.command_name); record.command_type = commandType; } Int32 commandActionInt = reader.GetInt32("command_action"); ACommand commandAction; if (!_CommandIDDictionary.TryGetValue(commandActionInt, out commandAction)) { Log.Error("Unable to parse command action " + commandTypeInt + " when fetching record " + record.record_id + " updates."); } if (commandAction.command_key != record.command_action.command_key) { Log.Info("Record " + record.record_id + " command action changed from " + record.command_action.command_name + " to " + commandAction.command_name); record.command_action = commandAction; if (record.target_player != null) { if (record.command_action.command_key == "player_report_confirm") { // Expire all other active reports against the player since this is the one that we acted on reportsToExpire.AddRange(record.target_player.TargetedRecords.Where(aRecord => IsActiveReport(aRecord) && aRecord.record_id != record.record_id)); SendMessageToSource(record, "Your report [" + record.command_numeric + "] has been accepted. Thank you."); OnlineAdminSayMessage("Report [" + record.command_numeric + "] has been accepted."); } else if (record.command_action.command_key == "player_report_deny") { SendMessageToSource(record, "Your report [" + record.command_numeric + "] has been denied."); OnlineAdminSayMessage("Report [" + record.command_numeric + "] has been denied."); } else if (record.command_action.command_key == "player_report_ignore") { OnlineAdminSayMessage("Report [" + record.command_numeric + "] has been ignored by " + record.GetSourceName() + "."); } } } } else { Log.Error("Unable to fetch update for record " + record.record_id + " no matching record found."); } } } } // Need to do this separately because otherwise it's stacked database contexts foreach (var report in reportsToExpire) { ExpireActiveReport(report); } if (reupload) { UpdateRecord(record); } } catch (Exception e) { Log.HandleException(new AException("Error while fetching record update", e)); } Log.Debug(() => "FetchRecordUpdate finished!", 6); return record; } private ARecord FetchRecordByID(Int64 recordID, Boolean debug) { Log.Debug(() => "fetchRecordByID starting!", 6); ARecord record = null; //Make sure database connection active if (_databaseConnectionCriticalState) { return null; } try { //Success fetching record Boolean success = false; using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { String tablename = (debug) ? ("`adkats_records_debug`") : ("`adkats_records_main`"); String sql = @" SELECT `record_id`, `server_id`, `command_type`, `command_action`, `command_numeric`, `target_name`, `target_id`, `source_name`, `source_id`, `record_message`, `record_time` FROM " + tablename + @" WHERE `record_id` = " + recordID; command.CommandText = sql; using (MySqlDataReader reader = SafeExecuteReader(command)) { //Grab the record if (reader.Read()) { success = true; record = new ARecord(); record.record_source = ARecord.Sources.Database; record.record_access = ARecord.AccessMethod.HiddenExternal; record.record_id = reader.GetInt64("record_id"); record.server_id = reader.GetInt64("server_id"); Int32 commandTypeInt = reader.GetInt32("command_type"); if (!_CommandIDDictionary.TryGetValue(commandTypeInt, out record.command_type)) { Log.Error("Unable to parse command type " + commandTypeInt + " when fetching record by ID."); } Int32 commandActionInt = reader.GetInt32("command_action"); if (!_CommandIDDictionary.TryGetValue(commandActionInt, out record.command_action)) { Log.Error("Unable to parse command action " + commandActionInt + " when fetching record by ID."); } record.command_numeric = reader.GetInt32("command_numeric"); record.target_name = reader.GetString("target_name"); if (!reader.IsDBNull(6)) { record.target_player = new APlayer(this) { player_id = reader.GetInt64(6) }; } record.source_name = reader.GetString("source_name"); if (!reader.IsDBNull(8)) { record.source_player = new APlayer(this) { player_id = reader.GetInt64(8) }; } record.record_message = reader.GetString("record_message"); record.record_time = reader.GetDateTime("record_time"); } if (success) { Log.Debug(() => "Record found for ID " + recordID, 5); } else { Log.Debug(() => "No record found for ID " + recordID, 5); } } if (success && record.target_player != null) { long oldID = record.target_player.player_id; record.target_player = FetchPlayer(false, true, false, null, oldID, null, null, null, null); if (record.target_player == null) { Log.Error("Unable to find player ID: " + oldID); return null; } if (!String.IsNullOrEmpty(record.target_player.player_name)) { record.target_name = record.target_player.player_name; } else { record.target_name = "NoNameTarget"; } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching record by ID", e)); } Log.Debug(() => "fetchRecordByID finished!", 6); return record; } private List FetchRecentRecords(Int64? player_id, Int64? command_id, Int64 limit_days, Int64 limit_records, Boolean target_only, Boolean debug) { Log.Debug(() => "FetchRecentRecords starting!", 6); List records = new List(); //Make sure database connection active if (_databaseConnectionCriticalState) { return records; } try { //Success fetching record using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { String tablename = (debug) ? ("`adkats_records_debug`") : ("`adkats_records_main`"); String sql = @" (SELECT `record_id`, `server_id`, `command_type`, `command_action`, `command_numeric`, `target_name`, `target_id`, `source_name`, `source_id`, `record_message`, `record_time` FROM " + tablename + @" WHERE `record_id` = `record_id`"; if (command_id != null && command_id > 0) { sql += @" AND ( `command_type` = @command_id OR `command_action` = @command_id )"; command.Parameters.AddWithValue("@command_id", command_id); } if (player_id != null && player_id > 0) { sql += @" AND ( `target_id` = @player_id " + ((target_only) ? ("") : (" OR `source_id` = @player_id ")) + @" )"; command.Parameters.AddWithValue("@player_id", player_id); } sql += @" AND ( DATE_ADD(`record_time`, INTERVAL @limit_days DAY) > UTC_TIMESTAMP() ) ORDER BY `record_id` DESC LIMIT @limit_records) ORDER BY `record_id` ASC"; command.Parameters.AddWithValue("@limit_days", limit_days); command.Parameters.AddWithValue("@limit_records", limit_records); command.CommandText = sql; using (MySqlDataReader reader = SafeExecuteReader(command)) { //Grab the records while (reader.Read()) { ARecord record = new ARecord(); record.record_source = ARecord.Sources.Database; record.record_access = ARecord.AccessMethod.HiddenExternal; record.record_id = reader.GetInt64("record_id"); record.server_id = reader.GetInt64("server_id"); Int32 commandTypeInt = reader.GetInt32("command_type"); if (!_CommandIDDictionary.TryGetValue(commandTypeInt, out record.command_type)) { Log.Error("Unable to parse command type " + commandTypeInt + " when fetching record."); } Int32 commandActionInt = reader.GetInt32("command_action"); if (!_CommandIDDictionary.TryGetValue(commandActionInt, out record.command_action)) { Log.Error("Unable to parse command action " + commandActionInt + " when fetching record."); } record.command_numeric = reader.GetInt32("command_numeric"); record.target_name = reader.GetString("target_name"); if (!reader.IsDBNull(6)) { Int64 targetID = reader.GetInt64(6); APlayer tPlayer; if ((_PlayerDictionary.TryGetValue(record.target_name, out tPlayer) || _PlayerLeftDictionary.TryGetValue(record.target_name, out tPlayer)) && tPlayer.player_id == targetID) { tPlayer.LastUsage = UtcNow(); Log.Debug(() => "Target player fetched from memory.", 7); } else { tPlayer = FetchPlayer(false, true, false, null, targetID, null, null, null, null); } record.target_player = tPlayer; } record.source_name = reader.GetString("source_name"); if (!reader.IsDBNull(8)) { Int64 targetID = reader.GetInt64(8); APlayer sPlayer; if ((_PlayerDictionary.TryGetValue(record.target_name, out sPlayer) || _PlayerLeftDictionary.TryGetValue(record.target_name, out sPlayer)) && sPlayer.player_id == targetID) { sPlayer.LastUsage = UtcNow(); Log.Debug(() => "Target player fetched from memory.", 7); } else { sPlayer = FetchPlayer(false, true, false, null, targetID, null, null, null, null); } record.source_player = sPlayer; } record.record_message = reader.GetString("record_message"); record.record_time = reader.GetDateTime("record_time"); records.Add(record); } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching recent records", e)); } Log.Debug(() => "FetchRecentRecords finished!", 6); return records; } private List FetchUnreadRecords() { Log.Debug(() => "fetchUnreadRecords starting!", 6); //Create return list List records = new List(); //Make sure database connection active if (_databaseConnectionCriticalState) { return records; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { String sql = @" SELECT `record_id`, `server_id`, `command_type`, `command_action`, `command_numeric`, `target_name`, `target_id`, `source_name`, `source_id`, `record_message`, `record_time` FROM `" + _mySqlSchemaName + @"`.`adkats_records_main` WHERE `adkats_read` = 'N' AND `command_type` NOT IN (72, 73) AND `command_action` NOT IN (72, 73) AND `server_id` = " + _serverInfo.ServerID; command.CommandText = sql; using (MySqlDataReader reader = SafeExecuteReader(command)) { //Grab the record while (reader.Read()) { ARecord record = new ARecord(); record.record_source = ARecord.Sources.Database; record.record_access = ARecord.AccessMethod.HiddenExternal; record.record_id = reader.GetInt64("record_id"); record.server_id = reader.GetInt64("server_id"); Int32 commandTypeInt = reader.GetInt32("command_type"); if (!_CommandIDDictionary.TryGetValue(commandTypeInt, out record.command_type)) { Log.Error("Unable to parse command type " + commandTypeInt + " when fetching record by ID."); } Int32 commandActionInt = reader.GetInt32("command_action"); if (!_CommandIDDictionary.TryGetValue(commandActionInt, out record.command_action)) { Log.Error("Unable to parse command action " + commandActionInt + " when fetching record by ID."); } record.command_numeric = reader.GetInt32("command_numeric"); record.target_name = reader.GetString("target_name"); object value = reader.GetValue(6); Int64 targetIDParse = -1; Log.Debug(() => "id fetched!", 6); if (Int64.TryParse(value.ToString(), out targetIDParse)) { Log.Debug(() => "id parsed! " + targetIDParse, 6); //Check if the player needs to be imported, or if they are already in the server APlayer importedPlayer = FetchPlayer(false, true, false, null, targetIDParse, null, null, null, null); if (importedPlayer == null) { continue; } APlayer currentPlayer = null; if (!String.IsNullOrEmpty(importedPlayer.player_name) && _PlayerDictionary.TryGetValue(importedPlayer.player_name, out currentPlayer)) { currentPlayer.LastUsage = UtcNow(); Log.Debug(() => "External player " + currentPlayer.GetVerboseName() + " is currently in the server, using existing data.", 5); record.target_player = currentPlayer; } else { Log.Debug(() => "External player " + importedPlayer.GetVerboseName() + " is not in the server, fetching from database.", 5); record.target_player = importedPlayer; } record.target_name = record.target_player.player_name; } else { Log.Debug(() => "id parse failed!", 6); } record.source_name = reader.GetString("source_name"); object sourceIDObj = reader.GetValue(8); Int64 sourceIDParse = -1; if (Int64.TryParse(sourceIDObj.ToString(), out sourceIDParse)) { Log.Debug(() => "source id parsed! " + sourceIDParse, 6); //Check if the player needs to be imported, or if they are already in the server APlayer importedPlayer = FetchPlayer(false, true, false, null, sourceIDParse, null, null, null, null); if (importedPlayer == null) { continue; } APlayer currentPlayer = null; if (!String.IsNullOrEmpty(importedPlayer.player_name) && _PlayerDictionary.TryGetValue(importedPlayer.player_name, out currentPlayer)) { Log.Debug(() => "External player " + currentPlayer.GetVerboseName() + " is currently in the server, using existing data.", 5); record.source_player = currentPlayer; } else { Log.Debug(() => "External player " + importedPlayer.GetVerboseName() + " is not in the server, fetching from database.", 5); record.source_player = importedPlayer; } record.target_name = record.target_player.player_name; } record.record_message = reader.GetString("record_message"); record.record_time = reader.GetDateTime("record_time"); records.Add(record); } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching unread records from database.", e)); } Log.Debug(() => "fetchUnreadRecords finished!", 6); return records; } private List FetchExternalOnlinePlayers() { Log.Debug(() => "FetchExternalOnlinePlayers starting!", 6); //Create return list List onlinePlayers = new List(); //Make sure database connection active if (_databaseConnectionCriticalState) { return onlinePlayers; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { String sql = @" SELECT `tbl_server`.`ServerID` AS `server_id`, `tbl_server`.`ServerName` AS `server_name`, `tbl_playerdata`.`PlayerID` AS `player_id`, `tbl_playerdata`.`SoldierName` AS `player_name`, `tbl_playerdata`.`EAGUID` AS `player_guid` FROM `tbl_currentplayers` INNER JOIN `tbl_server` ON `tbl_server`.`ServerID` = `tbl_currentplayers`.`ServerID` INNER JOIN `tbl_playerdata` ON `tbl_currentplayers`.`EA_GUID` = `tbl_playerdata`.`EAGUID` AND `tbl_server`.`GameID` = `tbl_playerdata`.`GameID` WHERE `tbl_currentplayers`.`ServerID` != @current_server_id GROUP BY `tbl_playerdata`.`PlayerID`"; command.CommandText = sql; command.Parameters.AddWithValue("@current_server_id", _serverInfo.ServerID); using (MySqlDataReader reader = SafeExecuteReader(command)) { //Grab the record while (reader.Read()) { APlayer ePlayer = FetchPlayer(false, false, false, null, reader.GetInt64("player_id"), null, null, null, null); if (ePlayer != null) { ePlayer.player_server = new AServer(this) { ServerID = reader.GetInt64("server_id"), ServerName = reader.GetString("server_name") }; onlinePlayers.Add(ePlayer); } } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching external online players.", e)); } Log.Debug(() => "FetchExternalOnlinePlayers finished!", 6); return onlinePlayers; } private APlayer FetchMatchingExternalOnlinePlayer(String searchName) { Log.Debug(() => "FetchMatchingExternalOnlinePlayer starting!", 6); APlayer aPlayer = null; //Make sure database connection active if (_databaseConnectionCriticalState) { return null; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { String sql = @" SELECT `tbl_server`.`ServerID` AS `server_id`, `tbl_server`.`ServerName` AS `server_name`, `tbl_playerdata`.`PlayerID` AS `player_id`, `tbl_playerdata`.`SoldierName` AS `player_name`, `tbl_playerdata`.`EAGUID` AS `player_guid` FROM `tbl_currentplayers` INNER JOIN `tbl_server` ON `tbl_server`.`ServerID` = `tbl_currentplayers`.`ServerID` INNER JOIN `tbl_playerdata` ON `tbl_currentplayers`.`EA_GUID` = `tbl_playerdata`.`EAGUID` AND `tbl_server`.`GameID` = `tbl_playerdata`.`GameID` WHERE `tbl_currentplayers`.`ServerID` != @current_server_id"; command.CommandText = sql; command.Parameters.AddWithValue("@current_server_id", _serverInfo.ServerID); using (MySqlDataReader reader = SafeExecuteReader(command)) { while (reader.Read()) { if (Regex.Match(reader.GetString("player_name"), searchName, RegexOptions.IgnoreCase).Success) { aPlayer = FetchPlayer(false, true, false, null, reader.GetInt64("player_id"), null, null, null, null); if (aPlayer == null) { return null; } aPlayer.player_server = new AServer(this) { ServerID = reader.GetInt64("server_id"), ServerName = reader.GetString("server_name") }; return aPlayer; } } return null; } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching matching external online from database.", e)); } Log.Debug(() => "FetchMatchingExternalOnlinePlayer finished!", 6); return aPlayer; } private void RunPluginOrchestration() { Log.Debug(() => "RunPluginOrchestration starting!", 6); //Make sure database connection active if (_databaseConnectionCriticalState) { return; } try { Log.Debug(() => "Running plugin orchestration", 5); using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT `setting_plugin`, `setting_name`, `setting_value` FROM `adkats_orchestration` WHERE `setting_server` = @server_id"; command.Parameters.AddWithValue("server_id", _serverInfo.ServerID); using (MySqlDataReader reader = SafeExecuteReader(command)) { while (reader.Read()) { SetExternalPluginSetting(reader.GetString("setting_plugin"), reader.GetString("setting_name"), reader.GetString("setting_value")); Threading.Wait(10); } } } } } catch (Exception e) { Log.HandleException(new AException("Error while running plugin orchestration.", e)); } Log.Debug(() => "RunPluginOrchestration finished!", 6); } private Int64 FetchNameBanCount() { Log.Debug(() => "fetchNameBanCount starting!", 7); //Make sure database connection active if (_databaseConnectionCriticalState) { return 0; } if (_NameBanCount >= 0 && (UtcNow() - _lastNameBanCountFetch).TotalSeconds < 30) { return _NameBanCount; } _lastNameBanCountFetch = UtcNow(); try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT COUNT(ban_id) AS `ban_count` FROM `adkats_bans` WHERE `adkats_bans`.`ban_enforceName` = 'Y' AND `ban_status` = 'Active'"; using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { _NameBanCount = reader.GetInt64("ban_count"); return _NameBanCount; } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching number of id bans.", e)); } Log.Debug(() => "fetchNameBanCount finished!", 7); return -1; } private Int64 FetchGUIDBanCount() { Log.Debug(() => "fetchGUIDBanCount starting!", 7); //Make sure database connection active if (_databaseConnectionCriticalState) { return 0; } if (_GUIDBanCount >= 0 && (UtcNow() - _lastGUIDBanCountFetch).TotalSeconds < 30) { return _GUIDBanCount; } _lastGUIDBanCountFetch = UtcNow(); try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT COUNT(ban_id) AS `ban_count` FROM `adkats_bans` WHERE `adkats_bans`.`ban_enforceGUID` = 'Y' AND `ban_status` = 'Active'"; using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { _GUIDBanCount = reader.GetInt64("ban_count"); return _GUIDBanCount; } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching number of GUID bans.", e)); } Log.Debug(() => "fetchGUIDBanCount finished!", 7); return -1; } private Int64 FetchIPBanCount() { Log.Debug(() => "fetchIPBanCount starting!", 7); //Make sure database connection active if (_databaseConnectionCriticalState) { return 0; } if (_IPBanCount >= 0 && (UtcNow() - _lastIPBanCountFetch).TotalSeconds < 30) { return _IPBanCount; } _lastIPBanCountFetch = UtcNow(); try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT COUNT(ban_id) AS `ban_count` FROM `adkats_bans` WHERE `adkats_bans`.`ban_enforceIP` = 'Y' AND `ban_status` = 'Active'"; using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { _IPBanCount = reader.GetInt64("ban_count"); return _IPBanCount; } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching number of IP bans.", e)); } Log.Debug(() => "fetchIPBanCount finished!", 7); return -1; } private void RemoveUser(AUser user) { Log.Debug(() => "removeUser starting!", 6); if (_databaseConnectionCriticalState) { return; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = "DELETE FROM `" + _mySqlSchemaName + "`.`adkats_users` WHERE `user_id` = @user_id"; command.Parameters.AddWithValue("@user_id", user.user_id); Int32 rowsAffected = SafeExecuteNonQuery(command); } } } catch (Exception e) { Log.HandleException(new AException("Error while removing user.", e)); } Log.Debug(() => "removeUser finished!", 6); } private void RemoveRole(ARole aRole) { Log.Debug(() => "removeRole starting!", 6); if (_databaseConnectionCriticalState) { return; } try { //Assign "Default Guest" to all users currently on this role ARole guestRole = null; if (_RoleKeyDictionary.TryGetValue("guest_default", out guestRole)) { foreach (AUser aUser in _userCache.Values) { if (aUser.user_role.role_key == aRole.role_key) { aUser.user_role = guestRole; } UploadUser(aUser); } } else { Log.Error("Could not fetch default guest user role. Unsafe to remove requested user role."); return; } using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = "DELETE FROM `" + _mySqlSchemaName + "`.`adkats_rolecommands` WHERE `role_id` = @role_id"; command.Parameters.AddWithValue("@role_id", aRole.role_id); Int32 rowsAffected = SafeExecuteNonQuery(command); } using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = "DELETE FROM `" + _mySqlSchemaName + "`.`adkats_roles` WHERE `role_id` = @role_id"; command.Parameters.AddWithValue("@role_id", aRole.role_id); Int32 rowsAffected = SafeExecuteNonQuery(command); } } } catch (Exception e) { Log.HandleException(new AException("Error while removing user.", e)); } Log.Debug(() => "removeRole finished!", 6); } private void UploadUser(AUser aUser) { Log.Debug(() => "uploadUser starting!", 6); if (_databaseConnectionCriticalState) { return; } try { Log.Debug(() => "Uploading user: " + aUser.user_name, 5); using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { if (aUser.user_role == null) { ARole aRole = null; if (_RoleKeyDictionary.TryGetValue("guest_default", out aRole)) { aUser.user_role = aRole; } else { Log.Error("Unable to assign default guest role to user " + aUser.user_name + ". Unable to upload user."); return; } } command.CommandText = @" INSERT INTO `adkats_users` ( " + ((aUser.user_id > 0) ? ("`user_id`,") : ("")) + @" `user_name`, `user_email`, `user_phone`, `user_role`, `user_expiration`, `user_notes` ) VALUES ( " + ((aUser.user_id > 0) ? ("@user_id,") : ("")) + @" @user_name, @user_email, @user_phone, @user_role, @user_expiration, @user_notes ) ON DUPLICATE KEY UPDATE `user_name` = @user_name, `user_email` = @user_email, `user_phone` = @user_phone, `user_role` = @user_role, `user_expiration` = @user_expiration, `user_notes` = @user_notes"; if (aUser.user_id > 0) { command.Parameters.AddWithValue("@user_id", aUser.user_id); } command.Parameters.AddWithValue("@user_name", aUser.user_name); command.Parameters.AddWithValue("@user_email", aUser.user_email); command.Parameters.AddWithValue("@user_phone", aUser.user_phone); command.Parameters.AddWithValue("@user_role", aUser.user_role.role_id); command.Parameters.AddWithValue("@user_expiration", aUser.user_expiration); command.Parameters.AddWithValue("@user_notes", aUser.user_notes); //Attempt to execute the query Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { //Set the user's new ID if new if (aUser.user_id < 0) { aUser.user_id = command.LastInsertedId; } Log.Debug(() => "User uploaded to database SUCCESSFULY.", 5); } else { Log.Error("Unable to upload user " + aUser.user_name + " to database."); return; } } //Run command to delete all current soldiers using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_usersoldiers` where `user_id` = " + aUser.user_id; //Attempt to execute the query Int32 rowsAffected = SafeExecuteNonQuery(command); } //Upload/Update the user's soldier list if (aUser.soldierDictionary.Count > 0) { //Refill user with current soldiers foreach (APlayer aPlayer in aUser.soldierDictionary.Values) { using (MySqlCommand command = connection.CreateCommand()) { //Set the insert command structure command.CommandText = @" INSERT INTO `adkats_usersoldiers` ( `user_id`, `player_id` ) VALUES ( @user_id, @player_id ) ON DUPLICATE KEY UPDATE `player_id` = @player_id"; //Set values command.Parameters.AddWithValue("@user_id", aUser.user_id); command.Parameters.AddWithValue("@player_id", aPlayer.player_id); //Attempt to execute the query Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { Log.Debug(() => "Soldier link " + aUser.user_id + "->" + aPlayer.player_id + " uploaded to database SUCCESSFULY.", 5); } else { Log.Error("Unable to upload soldier link for " + aUser.user_name + " to database."); return; } } } } } } catch (Exception e) { Log.HandleException(new AException("Error while updating player access.", e)); } Log.Debug(() => "uploadUser finished!", 6); } private void TryAddUserSoldier(AUser aUser, String soldierName) { try { //Attempt to fetch the soldier if (!String.IsNullOrEmpty(soldierName) && IsSoldierNameValid(soldierName)) { List matchingPlayers; if (FetchMatchingPlayers(soldierName, out matchingPlayers, false)) { if (matchingPlayers.Count > 0) { APlayer exactMatch = null; foreach (APlayer aPlayer in matchingPlayers) { if (aPlayer.player_name == soldierName) { exactMatch = aPlayer; break; } } if (matchingPlayers.Count > 10) { if (exactMatch != null) { exactMatch.LastUsage = UtcNow(); bool playerDuplicate = false; //Make sure the player is not already assigned to another user lock (_userCache) { if (_userCache.Values.Any(innerUser => innerUser.soldierDictionary.ContainsKey(exactMatch.player_id))) { playerDuplicate = true; } } if (!playerDuplicate) { if (aUser.soldierDictionary.ContainsKey(exactMatch.player_id)) { aUser.soldierDictionary.Remove(exactMatch.player_id); } aUser.soldierDictionary.Add(exactMatch.player_id, exactMatch); return; } else { Log.Error("Player " + exactMatch.GetVerboseName() + "(" + _gameIDDictionary[exactMatch.game_id] + ") already assigned to a user."); } } Log.Error("Too many players matched the query, unable to add."); return; } foreach (APlayer matchingPlayer in matchingPlayers) { matchingPlayer.LastUsage = UtcNow(); bool playerDuplicate = false; //Make sure the player is not already assigned to another user lock (_userCache) { if (_userCache.Values.Any(innerUser => innerUser.soldierDictionary.ContainsKey(matchingPlayer.player_id))) { playerDuplicate = true; } } if (!playerDuplicate) { if (aUser.soldierDictionary.ContainsKey(matchingPlayer.player_id)) { aUser.soldierDictionary.Remove(matchingPlayer.player_id); } aUser.soldierDictionary.Add(matchingPlayer.player_id, matchingPlayer); } else { Log.Error("Player " + matchingPlayer.GetVerboseName() + "(" + _gameIDDictionary[matchingPlayer.game_id] + ") already assigned to a user."); } } return; } Log.Error("Players matching '" + soldierName + "' not found in database. Unable to assign to user."); } } else { Log.Error("'" + soldierName + "' is an invalid player name. Unable to assign to user."); } } catch (Exception e) { Log.HandleException(new AException("Error while attempting to add user soldier.", e)); } } private void UploadRole(ARole aRole) { Log.Debug(() => "uploadRole starting!", 6); //Make sure database connection active if (_databaseConnectionCriticalState) { return; } try { Log.Debug(() => "Uploading role: " + aRole.role_name, 5); //Open db connection using (MySqlConnection connection = GetDatabaseConnection()) { //Upload/Update the main role object using (MySqlCommand command = connection.CreateCommand()) { //Set the insert command structure command.CommandText = @" INSERT INTO `adkats_roles` ( `role_key`, `role_name` ) VALUES ( @role_key, @role_name ) ON DUPLICATE KEY UPDATE `role_key` = @role_key, `role_name` = @role_name"; //Set values command.Parameters.AddWithValue("@role_key", aRole.role_key); command.Parameters.AddWithValue("@role_name", aRole.role_name); //Attempt to execute the query Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { //Set the user's new ID if new if (aRole.role_id < 0) { aRole.role_id = command.LastInsertedId; } Log.Debug(() => "Role " + aRole.role_name + " uploaded to database.", 5); } else { Log.Error("Unable to upload role " + aRole.role_name + " to database."); return; } } //Delete all current allowed commands using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_rolecommands` where `role_id` = " + aRole.role_id; //Attempt to execute the query Int32 rowsAffected = SafeExecuteNonQuery(command); } foreach (ACommand aCommand in aRole.RoleAllowedCommands.Values.ToList()) { //Upload the role's allowed commands using (MySqlCommand command = connection.CreateCommand()) { //Set the insert command structure command.CommandText = @" INSERT INTO `adkats_rolecommands` ( `role_id`, `command_id` ) VALUES ( @role_id, @command_id ) ON DUPLICATE KEY UPDATE `role_id` = @role_id, `command_id` = @command_id"; //Set values command.Parameters.AddWithValue("@role_id", aRole.role_id); command.Parameters.AddWithValue("@command_id", aCommand.command_id); //Attempt to execute the query Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { Log.Debug(() => "Role-command " + aRole.role_name + " uploaded to database.", 5); } else { Log.Error("Unable to upload role-command for " + aRole.role_name + "."); return; } } } //Delete all current role groups using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"DELETE FROM `adkats_rolegroups` where `role_id` = " + aRole.role_id; //Attempt to execute the query Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { Log.Debug(() => "Deleted existing database role-group info for " + aRole.role_name + ".", 5); } } foreach (ASpecialGroup aGroup in aRole.RoleSetGroups.Values.ToList()) { //Upload the role's set groups using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" INSERT INTO `adkats_rolegroups` ( `role_id`, `group_key` ) VALUES ( @role_id, @group_key ) ON DUPLICATE KEY UPDATE `role_id` = @role_id, `group_key` = @group_key"; //Set values command.Parameters.AddWithValue("@role_id", aRole.role_id); command.Parameters.AddWithValue("@group_key", aGroup.group_key); //Attempt to execute the query Int32 rowsAffected = SafeExecuteNonQuery(command); if (rowsAffected > 0) { Log.Debug(() => "Role-group " + aGroup.group_key + " for " + aRole.role_name + " uploaded to database.", 5); } else { Log.Error("Unable to upload role-group " + aGroup.group_key + " for " + aRole.role_name + "."); return; } } } } } catch (Exception e) { Log.HandleException(new AException("Error while uploading role.", e)); } Log.Debug(() => "uploadRole finished!", 6); } private void UploadBan(ABan aBan) { Log.Debug(() => "uploadBan starting!", 6); Boolean success = false; //Make sure database connection active if (_databaseConnectionCriticalState) { return; } if (aBan == null) { Log.Error("Ban invalid in uploadBan."); } else { try { //Upload the inner record if needed if (aBan.ban_record.record_id < 0) { if (!UploadRecord(aBan.ban_record)) { return; } } using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" INSERT INTO `" + _mySqlSchemaName + @"`.`adkats_bans` ( `player_id`, `latest_record_id`, `ban_status`, `ban_notes`, `ban_startTime`, `ban_endTime`, `ban_enforceName`, `ban_enforceGUID`, `ban_enforceIP`, `ban_sync` ) VALUES ( @player_id, @latest_record_id, @ban_status, @ban_notes, @ban_startTime, DATE_ADD(@ban_startTime, INTERVAL @ban_durationMinutes MINUTE), @ban_enforceName, @ban_enforceGUID, @ban_enforceIP, @ban_sync ) ON DUPLICATE KEY UPDATE `latest_record_id` = @latest_record_id, `ban_status` = @ban_status, `ban_notes` = @ban_notes, `ban_startTime` = @ban_startTime, `ban_endTime` = DATE_ADD(@ban_startTime, INTERVAL @ban_durationMinutes MINUTE), `ban_enforceName` = @ban_enforceName, `ban_enforceGUID` = @ban_enforceGUID, `ban_enforceIP` = @ban_enforceIP, `ban_sync` = @ban_sync"; command.Parameters.AddWithValue("@player_id", aBan.ban_record.target_player.player_id); command.Parameters.AddWithValue("@latest_record_id", aBan.ban_record.record_id); if (String.IsNullOrEmpty(aBan.ban_status)) { aBan.ban_exception = new AException("Ban status was null or empty when posting."); Log.HandleException(aBan.ban_exception); return; } if (aBan.ban_status != "Active" && aBan.ban_status != "Disabled" && aBan.ban_status != "Expired") { aBan.ban_exception = new AException("Ban status of '" + aBan.ban_status + "' was invalid when posting."); Log.HandleException(aBan.ban_exception); return; } command.Parameters.AddWithValue("@ban_status", aBan.ban_status); if (String.IsNullOrEmpty(aBan.ban_notes)) { aBan.ban_notes = "NoNotes"; } command.Parameters.AddWithValue("@ban_notes", aBan.ban_notes); command.Parameters.AddWithValue("@ban_enforceName", aBan.ban_enforceName ? ('Y') : ('N')); command.Parameters.AddWithValue("@ban_enforceGUID", aBan.ban_enforceGUID ? ('Y') : ('N')); command.Parameters.AddWithValue("@ban_enforceIP", aBan.ban_enforceIP ? ('Y') : ('N')); command.Parameters.AddWithValue("@ban_sync", "*" + _serverInfo.ServerID + "*"); //Handle permaban case if (aBan.ban_record.command_action.command_key.Contains("player_ban_perm")) { command.Parameters.AddWithValue("@ban_durationMinutes", (Int32)UtcNow().AddYears(20).Subtract(UtcNow()).TotalMinutes); } else { command.Parameters.AddWithValue("@ban_durationMinutes", aBan.ban_record.command_numeric); } if (aBan.ban_record.command_action.command_key == "player_ban_perm_future") { command.Parameters.AddWithValue("@ban_startTime", aBan.ban_record.record_time + TimeSpan.FromMinutes(aBan.ban_record.command_numeric)); } else { command.Parameters.AddWithValue("@ban_startTime", aBan.ban_record.record_time); } //Attempt to execute the query if (SafeExecuteNonQuery(command) >= 0) { //Rows affected should be > 0 Log.Debug(() => "Success Uploading Ban on player " + aBan.ban_record.target_player.player_id, 5); success = true; } } if (success) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT `ban_id`, `ban_startTime`, `ban_endTime`, `ban_status` FROM `adkats_bans` WHERE `player_id` = @player_id"; command.Parameters.AddWithValue("@player_id", aBan.ban_record.target_player.player_id); //Attempt to execute the query using (MySqlDataReader reader = SafeExecuteReader(command)) { //Grab the ban ID if (reader.Read()) { aBan.ban_id = reader.GetInt64("ban_id"); aBan.ban_startTime = reader.GetDateTime("ban_startTime"); aBan.ban_endTime = reader.GetDateTime("ban_endTime"); String status = reader.GetString("ban_status"); if (status != aBan.ban_status) { aBan.ban_exception = new AException("Ban status was invalid when confirming ban post. Your database is not in strict mode."); Log.HandleException(aBan.ban_exception); return; } Log.Debug(() => "Ban ID: " + aBan.ban_id, 5); } else { Log.Error("Could not fetch ban information after upload"); } } } } } } catch (Exception e) { Log.HandleException(new AException("Error while uploading new ban.", e)); } } Log.Debug(() => "uploadBan finished!", 6); } private Boolean FetchMatchingPlayers(String playerName, out List resultPlayers, Boolean verbose) { Log.Debug(() => "FetchMatchingPlayers starting!", 6); resultPlayers = new List(); if (String.IsNullOrEmpty(playerName)) { if (verbose) { Log.Error("Player id was blank when fetching matching players."); } return false; } using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT `PlayerID` AS `player_id` FROM `tbl_playerdata` WHERE `SoldierName` LIKE '%" + playerName + "%'"; //Attempt to execute the query using (MySqlDataReader reader = SafeExecuteReader(command)) { //Grab the matching players while (reader.Read()) { APlayer aPlayer = FetchPlayer(false, true, false, null, reader.GetInt64("player_id"), null, null, null, null); if (aPlayer != null) { resultPlayers.Add(aPlayer); } } if (resultPlayers.Count == 0) { if (verbose) { Log.Error("No players found matching '" + playerName + "'"); } return false; } } } } Log.Debug(() => "FetchMatchingPlayers finished!", 6); return true; } private APlayer FetchPlayer(Boolean allowUpdate, Boolean allowOtherGames, Boolean allowNameSubstringSearch, Int32? gameID, Int64 playerID, String playerName, String playerGUID, String playerIP, String playerDiscordID) { Log.Debug(() => "fetchPlayer starting!", 6); //Create return object APlayer aPlayer = null; //Make sure database connection active if (_databaseConnectionCriticalState) { //If AdKats is disconnected from the database, return the player as-is aPlayer = new APlayer(this) { game_id = _serverInfo.GameID, player_name = playerName, player_guid = playerGUID, LastUsage = UtcNow() }; aPlayer.SetIP(playerIP); AssignPlayerRole(aPlayer); Log.Warn(aPlayer.player_name + " " + aPlayer.player_guid + " " + aPlayer.player_ip + " loaded without a database connection!"); return aPlayer; } if (playerID < 0 && String.IsNullOrEmpty(playerName) && String.IsNullOrEmpty(playerGUID) && String.IsNullOrEmpty(playerIP) && String.IsNullOrEmpty(playerDiscordID)) { Log.Error("Attempted to fetch player with no information."); } else { try { if (playerID > 0) { aPlayer = GetFetchedPlayers().FirstOrDefault(dPlayer => dPlayer.player_id == playerID); } if (aPlayer != null) { Log.Debug(() => "Player " + playerID + " successfully fetched from pre-fetch list by ID.", 6); aPlayer.LastUsage = UtcNow(); return aPlayer; } if (!String.IsNullOrEmpty(playerGUID)) { aPlayer = GetFetchedPlayers().FirstOrDefault(dPlayer => dPlayer.player_guid == playerGUID); } if (aPlayer != null) { Log.Debug(() => "Player " + playerID + " successfully fetched from pre-fetch list by GUID.", 6); aPlayer.LastUsage = UtcNow(); return aPlayer; } if (!String.IsNullOrEmpty(playerIP)) { aPlayer = GetFetchedPlayers().FirstOrDefault(dPlayer => dPlayer.player_ip == playerIP); } if (aPlayer != null) { Log.Debug(() => "Player " + playerID + " successfully fetched from pre-fetch list by IP.", 6); aPlayer.LastUsage = UtcNow(); return aPlayer; } if (!String.IsNullOrEmpty(playerName)) { aPlayer = GetFetchedPlayers().FirstOrDefault(dPlayer => dPlayer.player_name == playerName); } if (aPlayer != null) { Log.Debug(() => "Player " + playerID + " successfully fetched from pre-fetch list by Name.", 6); aPlayer.LastUsage = UtcNow(); return aPlayer; } if (!String.IsNullOrEmpty(playerDiscordID)) { aPlayer = GetFetchedPlayers().FirstOrDefault(dPlayer => dPlayer.player_discord_id == playerDiscordID); } if (aPlayer != null) { Log.Debug(() => "Player " + playerID + " successfully fetched from pre-fetch list by Discord ID.", 6); aPlayer.LastUsage = UtcNow(); return aPlayer; } using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { String sql = @" SELECT `tbl_playerdata`.`PlayerID` as `player_id`, `tbl_playerdata`.`SoldierName` as `player_name`, `tbl_playerdata`.`EAGUID` as `player_guid`, `tbl_playerdata`.`PBGUID` as `player_pbguid`, `tbl_playerdata`.`IP_Address` as `player_ip`, `tbl_playerdata`.`DiscordID` as `player_discord_id`, `tbl_playerdata`.`ClanTag` as `player_clantag`, `adkats_battlecries`.`player_battlecry`, `adkats_battlelog_players`.`persona_id` as `player_personaID`, `adkats_battlelog_players`.`user_id` as `player_userID`"; if (_serverInfo.GameID > 0) { sql += ",`GameID` as `game_id` "; } sql += "FROM `" + _mySqlSchemaName + @"`.`tbl_playerdata` LEFT JOIN `adkats_battlecries` ON `tbl_playerdata`.`PlayerID` = `adkats_battlecries`.`player_id` LEFT JOIN `adkats_battlelog_players` ON `tbl_playerdata`.`PlayerID` = `adkats_battlelog_players`.`player_id` "; bool sqlEnder = true; if (playerID >= 0) { sql += " WHERE ( "; sqlEnder = false; sql += " `PlayerID` = " + playerID + " "; } if (!String.IsNullOrEmpty(playerGUID)) { if (sqlEnder) { sql += " WHERE ( "; sqlEnder = false; } else { sql += " OR "; } sql += " `EAGUID` = '" + playerGUID + "' "; } if (String.IsNullOrEmpty(playerGUID) && !String.IsNullOrEmpty(playerName)) { if (sqlEnder) { sql += " WHERE ( "; sqlEnder = false; } else { sql += " OR "; } sql += " `SoldierName` LIKE '" + ((allowNameSubstringSearch) ? ("%" + playerName + "%") : (playerName)) + "' "; } if (String.IsNullOrEmpty(playerGUID) && !String.IsNullOrEmpty(playerIP)) { if (sqlEnder) { sql += " WHERE ( "; sqlEnder = false; } else { sql += " OR "; } sql += " `IP_Address` = '" + playerIP + "' "; } if (String.IsNullOrEmpty(playerGUID) && !String.IsNullOrEmpty(playerDiscordID)) { if (sqlEnder) { sql += " WHERE ( "; sqlEnder = false; } else { sql += " OR "; } sql += " `DiscordID` = '" + playerDiscordID + "' "; } if (!sqlEnder) { sql += " ) "; } if ((_serverInfo.GameID > 0 && !allowOtherGames) || gameID != null) { if (gameID != null) { sql += " AND `GameID` = " + gameID + " "; } else { sql += " AND `GameID` = " + _serverInfo.GameID + " "; } } sql += @" LIMIT 1"; command.CommandText = sql; if (_debugDisplayPlayerFetches) { PrintPreparedCommand(command); } using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { aPlayer = new APlayer(this); //Player ID will never be null aPlayer.player_id = reader.GetInt64("player_id"); if (_serverInfo.GameID > 0) { aPlayer.game_id = reader.GetInt32("game_id"); } if (!reader.IsDBNull(1)) { aPlayer.player_name = reader.GetString("player_name"); } if (!reader.IsDBNull(2)) { aPlayer.player_guid = reader.GetString("player_guid"); } if (!reader.IsDBNull(3)) { aPlayer.player_pbguid = reader.GetString("player_pbguid"); } if (!reader.IsDBNull(4)) { aPlayer.SetIP(reader.GetString("player_ip")); } if (!reader.IsDBNull(5)) { aPlayer.player_discord_id = reader.GetString("player_discord_id"); } if (!reader.IsDBNull(6)) { aPlayer.player_clanTag = reader.GetString("player_clantag"); } if (!reader.IsDBNull(7)) { aPlayer.player_battlecry = reader.GetString("player_battlecry"); } if (!reader.IsDBNull(8)) { aPlayer.player_battlelog_personaID = reader.GetString("player_personaID"); } if (!reader.IsDBNull(9)) { aPlayer.player_battlelog_userID = reader.GetString("player_userID"); } if (!String.IsNullOrEmpty(aPlayer.player_battlelog_personaID) && !String.IsNullOrEmpty(aPlayer.player_battlelog_userID)) { aPlayer.BLInfoStored = true; } } else { var infoString = "No player matching search information. " + allowUpdate + ", " + allowOtherGames + ", " + ((gameID != null) ? (gameID.ToString()) : ("No game ID")) + ", " + playerID + ", " + ((!String.IsNullOrEmpty(playerName)) ? (playerName) : ("No name search")) + ", " + ((!String.IsNullOrEmpty(playerGUID)) ? (playerGUID) : ("No GUID search")) + ", " + ((!String.IsNullOrEmpty(playerIP)) ? (playerIP) : ("No IP search")) + ", " + ((!String.IsNullOrEmpty(playerDiscordID)) ? (playerDiscordID) : ("No Discord ID search")); if (_debugDisplayPlayerFetches) { Log.Info(infoString); } else { Log.Debug(() => infoString, 4); } } } } if (allowUpdate) { if (aPlayer == null) { Log.Debug(() => "Adding player to database.", 5); using (MySqlCommand command = connection.CreateCommand()) { Int32? useableGameID = null; if (gameID != null) { useableGameID = gameID; } else if (_serverInfo.GameID > 0) { useableGameID = (Int32?)_serverInfo.GameID; } //Set the insert command structure if (useableGameID != null) { command.CommandText = @" INSERT INTO `" + _mySqlSchemaName + @"`.`tbl_playerdata` ( `GameID`, `SoldierName`, `EAGUID`, `IP_Address` ) VALUES ( @GameID, @SoldierName, @EAGUID, @IP_Address ) ON DUPLICATE KEY UPDATE `PlayerID` = LAST_INSERT_ID(`PlayerID`), `SoldierName` = @SoldierName, `EAGUID` = @EAGUID, `IP_Address` = @IP_Address"; command.Parameters.AddWithValue("@GameID", _serverInfo.GameID); command.Parameters.AddWithValue("@SoldierName", String.IsNullOrEmpty(playerName) ? null : playerName); command.Parameters.AddWithValue("@EAGUID", String.IsNullOrEmpty(playerGUID) ? null : playerGUID); command.Parameters.AddWithValue("@IP_Address", String.IsNullOrEmpty(playerIP) ? null : playerIP); } else { command.CommandText = @" INSERT INTO `" + _mySqlSchemaName + @"`.`tbl_playerdata` ( `SoldierName`, `EAGUID`, `IP_Address` ) VALUES ( @SoldierName, @EAGUID, @IP_Address ) ON DUPLICATE KEY UPDATE `PlayerID` = LAST_INSERT_ID(`PlayerID`), `SoldierName` = @SoldierName, `EAGUID` = @EAGUID, `IP_Address` = @IP_Address"; command.Parameters.AddWithValue("@SoldierName", String.IsNullOrEmpty(playerName) ? null : playerName); command.Parameters.AddWithValue("@EAGUID", String.IsNullOrEmpty(playerGUID) ? null : playerGUID); command.Parameters.AddWithValue("@IP_Address", String.IsNullOrEmpty(playerIP) ? null : playerIP); } if (_debugDisplayPlayerFetches) { PrintPreparedCommand(command); } //Attempt to execute the query if (SafeExecuteNonQuery(command) > 0) { //Rows affected should be > 0 aPlayer = new APlayer(this) { player_id = command.LastInsertedId, player_name = playerName, player_guid = playerGUID }; aPlayer.SetIP(playerIP); if (useableGameID != null) { aPlayer.game_id = (long)useableGameID; } else { aPlayer.game_id = _serverInfo.GameID; } aPlayer.player_new = true; } else { Log.Error("Unable to add player to database."); return null; } } } //check for name changes if (!String.IsNullOrEmpty(playerName) && !String.IsNullOrEmpty(aPlayer.player_guid) && playerName != aPlayer.player_name) { aPlayer.player_name_previous = aPlayer.player_name; aPlayer.player_name = playerName; ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_changename"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "AdKats", record_message = aPlayer.player_name_previous, record_time = UtcNow() }; QueueRecordForProcessing(record); Log.Debug(() => aPlayer.player_name_previous + " changed their name to " + playerName + ". Updating the database.", 2); if (_ShowPlayerNameChangeAnnouncement) { OnlineAdminSayMessage(aPlayer.player_name_previous + " changed their name to " + playerName); } UpdatePlayer(aPlayer); } } if (aPlayer == null) { return null; } //Assign player role AssignPlayerRole(aPlayer); //Pull player first seen if (aPlayer.player_id > 0) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT FirstSeenOnServer FROM tbl_server_player INNER JOIN tbl_playerstats ON tbl_playerstats.StatsID = tbl_server_player.StatsID WHERE tbl_server_player.PlayerID = @player_id ORDER BY tbl_playerstats.FirstSeenOnServer LIMIT 1"; command.Parameters.AddWithValue("@player_id", aPlayer.player_id); using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { aPlayer.player_firstseen = reader.GetDateTime("FirstSeenOnServer"); } else { aPlayer.player_firstseen = UtcNow(); Log.Debug(() => "No stats found to fetch first seen time.", 5); } } } using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT (Playtime/60.0) as playtime_minutes FROM tbl_server_player INNER JOIN tbl_playerstats ON tbl_playerstats.StatsID = tbl_server_player.StatsID WHERE tbl_server_player.PlayerID = @player_id AND tbl_server_player.Serverid = @server_id ORDER BY Serverid ASC"; command.Parameters.AddWithValue("@player_id", aPlayer.player_id); command.Parameters.AddWithValue("@server_id", _serverInfo.ServerID); using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { aPlayer.player_serverplaytime = TimeSpan.FromMinutes(reader.GetDouble("playtime_minutes")); } else { Log.Debug(() => "No stats found to fetch time on server.", 5); } } } } } if (aPlayer != null && aPlayer.player_id > 0) { aPlayer.LastUsage = UtcNow(); AddFetchedPlayer(aPlayer); } } catch (Exception e) { Log.HandleException(new AException("Error while fetching player.", e)); } } Log.Debug(() => "fetchPlayer finished!", 6); if (aPlayer != null) { aPlayer.LastUsage = UtcNow(); } return aPlayer; } private void AddFetchedPlayer(APlayer aPlayer) { try { lock (_FetchedPlayers) { //Remove all old values List removeIDs = _FetchedPlayers.Values.ToList() .Where(dPlayer => (UtcNow() - dPlayer.LastUsage).TotalMinutes > 120) .Select(dPlayer => dPlayer.player_id).ToList(); foreach (Int64 removeID in removeIDs) { _FetchedPlayers.Remove(removeID); } aPlayer.LastUsage = UtcNow(); _FetchedPlayers[aPlayer.player_id] = aPlayer; } } catch (Exception e) { Log.HandleException(new AException("Error adding new fetched player.", e)); } } private List GetFetchedPlayers() { try { lock (_FetchedPlayers) { return _FetchedPlayers.Values.ToList(); } } catch (Exception e) { Log.HandleException(new AException("Error getting fetched players.", e)); } return new List(); } private APlayer UpdatePlayer(APlayer aPlayer) { Log.Debug(() => "updatePlayer starting!", 6); //Make sure database connection active if (_databaseConnectionCriticalState) { return aPlayer; } if (aPlayer == null || aPlayer.player_id < 0 || (String.IsNullOrEmpty(aPlayer.player_name) && String.IsNullOrEmpty(aPlayer.player_guid) & String.IsNullOrEmpty(aPlayer.player_ip))) { Log.Error("Attempted to update player without required information."); } else { try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { //Set the insert command structure command.CommandText = @" UPDATE IGNORE `tbl_playerdata` SET `SoldierName` = @player_name, `EAGUID` = @player_guid, `ClanTag` = @player_clanTag, `IP_Address` = @player_ip, `DiscordID` = @player_discord_id WHERE `PlayerID` = @player_id"; command.Parameters.AddWithValue("@player_id", aPlayer.player_id); command.Parameters.AddWithValue("@player_name", aPlayer.player_name); command.Parameters.AddWithValue("@player_guid", aPlayer.player_guid); command.Parameters.AddWithValue("@player_clanTag", String.IsNullOrEmpty(aPlayer.player_clanTag) ? "" : aPlayer.player_clanTag); command.Parameters.AddWithValue("@player_ip", String.IsNullOrEmpty(aPlayer.player_ip) ? null : aPlayer.player_ip); command.Parameters.AddWithValue("@player_discord_id", String.IsNullOrEmpty(aPlayer.player_discord_id) ? null : aPlayer.player_discord_id); //Attempt to execute the query if (SafeExecuteNonQuery(command) > 0) { Log.Debug(() => "Update player info success.", 5); } } if (!String.IsNullOrEmpty(aPlayer.player_battlelog_personaID) && !String.IsNullOrEmpty(aPlayer.player_battlelog_personaID) && !aPlayer.BLInfoStored) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" REPLACE INTO `adkats_battlelog_players` ( `player_id`, `persona_id`, `user_id` ) VALUES ( @player_id, @persona_id, @user_id )"; command.Parameters.AddWithValue("@player_id", aPlayer.player_id); command.Parameters.AddWithValue("@persona_id", aPlayer.player_battlelog_personaID); command.Parameters.AddWithValue("@user_id", aPlayer.player_battlelog_userID); Int32 rowsAffected = SafeExecuteNonQuery(command); } } } } catch (Exception e) { Log.HandleException(new AException("Error while updating player.", e)); } } Log.Debug(() => "updatePlayer finished!", 6); return aPlayer; } private void UpdatePopulatorPlayers() { Log.Debug(() => "UpdatePopulatingPlayers starting!", 6); try { //List for current valid populator player IDs List validIDs = new List(); lock (_populatorPlayers) { //Rejection case if (!_PopulatorMonitor) { _populatorPlayers.Clear(); return; } List populatorsPastWeek = GetPopulatingPlayers(TimeSpan.FromDays(7), _PopulatorMinimumPopulationCountPastWeek, _PopulatorPopulatingThisServerOnly); List populatorsPast2Weeks = GetPopulatingPlayers(TimeSpan.FromDays(14), _PopulatorMinimumPopulationCountPast2Weeks, _PopulatorPopulatingThisServerOnly); //Find all populators from the past week foreach (APlayer aPlayer in populatorsPastWeek) { if (!_pluginEnabled) { return; } //If using specified populators only, reject any non-specified populator entries if (_PopulatorUseSpecifiedPopulatorsOnly && !GetMatchingVerboseASPlayersOfGroup("whitelist_populator", aPlayer).Any()) { continue; } //Add the valid ID if (!validIDs.Contains(aPlayer.player_id)) { validIDs.Add(aPlayer.player_id); } //Add the player if (!_populatorPlayers.ContainsKey(aPlayer.player_name)) { if (_firstPlayerListComplete) { Log.Info("Adding " + aPlayer.player_name + " to current populator players."); } } _populatorPlayers[aPlayer.player_name] = aPlayer; } //Find all populators from the past 2 weeks foreach (APlayer aPlayer in populatorsPast2Weeks) { if (!_pluginEnabled) { return; } //If using specified populators only, reject any non-specified populator entries if (_PopulatorUseSpecifiedPopulatorsOnly && !GetMatchingVerboseASPlayersOfGroup("whitelist_populator", aPlayer).Any()) { continue; } //Add the valid ID if (!validIDs.Contains(aPlayer.player_id)) { validIDs.Add(aPlayer.player_id); } //Add the player if (!_populatorPlayers.ContainsKey(aPlayer.player_name)) { if (_firstPlayerListComplete) { Log.Info("Adding " + aPlayer.player_name + " to current populator players."); } } _populatorPlayers[aPlayer.player_name] = aPlayer; } //Remove invalid players foreach (APlayer aPlayer in _populatorPlayers.Values.Where(dPlayer => !validIDs.Contains(dPlayer.player_id)).ToList()) { if (!_pluginEnabled) { return; } if (_firstPlayerListComplete) { Log.Info("Removing " + aPlayer.player_name + " from current populator players."); } _populatorPlayers.Remove(aPlayer.player_name); } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching baserape causing players", e)); } Log.Debug(() => "UpdatePopulatingPlayers finished!", 6); } private List GetPopulatingPlayers(TimeSpan duration, Int32 minPopulations, Boolean thisServerOnly) { Log.Debug(() => "GetPopulatingPlayers starting!", 6); List resultPlayers = new List(); try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { if (thisServerOnly) { command.CommandText = @" SELECT * FROM (SELECT `target_id` AS `player_id`, `target_name` AS `player_name`, COUNT(`record_id`) AS `population_count` FROM `adkats_records_main` WHERE `server_id` = @server_id AND `command_type` = 88 AND DATE_ADD(`record_time`, INTERVAL @duration_minutes MINUTE) > UTC_TIMESTAMP() GROUP BY `target_id` ORDER BY `population_count` DESC, `target_name` ASC) AS InnerResults WHERE `population_count` >= @population_minimum"; command.Parameters.AddWithValue("@server_id", _serverInfo.ServerID); command.Parameters.AddWithValue("@duration_minutes", (Int32)duration.TotalMinutes); command.Parameters.AddWithValue("@population_minimum", minPopulations); } else { command.CommandText = @" SELECT * FROM (SELECT `target_id` AS `player_id`, `target_name` AS `player_name`, COUNT(`record_id`) AS `population_count` FROM `adkats_records_main` WHERE `command_type` = 88 AND DATE_ADD(`record_time`, INTERVAL @duration_minutes MINUTE) > UTC_TIMESTAMP() GROUP BY `target_id` ORDER BY `population_count` DESC, `target_name` ASC) AS InnerResults WHERE `population_count` >= @population_minimum"; command.Parameters.AddWithValue("@duration_minutes", (Int32)duration.TotalMinutes); command.Parameters.AddWithValue("@population_minimum", minPopulations); } //Attempt to execute the query using (MySqlDataReader reader = SafeExecuteReader(command)) { //Grab the matching players while (reader.Read()) { APlayer aPlayer = FetchPlayer(false, true, false, null, reader.GetInt64("player_id"), null, null, null, null); if (aPlayer != null) { resultPlayers.Add(aPlayer); } } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching populating players", e)); } Log.Debug(() => "GetPopulatingPlayers finished!", 6); return resultPlayers; } private ABan FetchBanByID(Int64 ban_id) { Log.Debug(() => "FetchBanByID starting!", 6); ABan aBan = null; //Make sure database connection active if (_databaseConnectionCriticalState) { return null; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { //Build the query command.CommandText = @" SELECT `ban_id`, `player_id`, `latest_record_id`, `ban_status`, `ban_notes`, `ban_startTime`, `ban_endTime`, `ban_enforceName`, `ban_enforceGUID`, `ban_enforceIP`, `ban_sync` FROM `adkats_bans` WHERE `ban_id` = @ban_id"; command.Parameters.AddWithValue("@ban_id", ban_id); using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { //Create the ban object aBan = new ABan { ban_id = reader.GetInt64("ban_id"), ban_status = reader.GetString("ban_status"), ban_notes = reader.GetString("ban_notes"), ban_sync = reader.GetString("ban_sync"), ban_startTime = reader.GetDateTime("ban_startTime"), ban_endTime = reader.GetDateTime("ban_endTime"), ban_enforceName = (reader.GetString("ban_enforceName") == "Y"), ban_enforceGUID = (reader.GetString("ban_enforceGUID") == "Y"), ban_enforceIP = (reader.GetString("ban_enforceIP") == "Y"), ban_record = FetchRecordByID(reader.GetInt64("latest_record_id"), false) }; if (aBan.ban_endTime.Subtract(UtcNow()).TotalSeconds < 0) { aBan.ban_status = "Expired"; UpdateBanStatus(aBan); } } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching ban.", e)); } Log.Debug(() => "FetchBanByID finished!", 6); return aBan; } private void InfoOrRespond(ARecord debugRecord, String message) { if (debugRecord != null) { SendMessageToSource(debugRecord, message); } else { Log.Info(message); } } private Boolean RunAssist(APlayer aPlayer, ARecord realRecord, ARecord debugRecord, Boolean auto) { //Locals var powerPercentageThreshold = 18.0; var roundMinutes = Math.Round(_serverInfo.GetRoundElapsedTime().TotalMinutes, 1); //Team Info Check ATeam team1, team2; String rejectionMessage = "Error"; if (!GetTeamByID(1, out team1)) { if (_roundState == RoundState.Playing) { InfoOrRespond(debugRecord, "Teams not loaded when they should be."); } return false; } if (!GetTeamByID(2, out team2)) { if (_roundState == RoundState.Playing) { InfoOrRespond(debugRecord, "Teams not loaded when they should be."); } return false; } ATeam winningTeam, losingTeam; if (team1.TeamTicketCount > team2.TeamTicketCount) { winningTeam = team1; losingTeam = team2; } else { winningTeam = team2; losingTeam = team1; } ATeam friendlyTeam, enemyTeam; if (aPlayer.fbpInfo.TeamID == team1.TeamID) { friendlyTeam = team1; enemyTeam = team2; } else if (aPlayer.fbpInfo.TeamID == team2.TeamID) { friendlyTeam = team2; enemyTeam = team1; } else { InfoOrRespond(debugRecord, Log.CViolet("Invalid teams when attempting to assist. Team ID was " + aPlayer.fbpInfo.TeamID + ".")); return false; } ATeam mapUpTeam, mapDownTeam; if (team1.GetTicketDifferenceRate() > team2.GetTicketDifferenceRate()) { mapUpTeam = team1; mapDownTeam = team2; } else { mapUpTeam = team2; mapDownTeam = team1; } String recordMessage = "Assist Weak Team [" + winningTeam.TeamTicketCount + ":" + losingTeam.TeamTicketCount + "][" + FormatTimeString(_serverInfo.GetRoundElapsedTime(), 3) + "]"; if (realRecord != null) { realRecord.record_message = recordMessage; } if (!auto) { InfoOrRespond(debugRecord, recordMessage); } Boolean canAssist = true; Boolean ticketBypass = false; Double ticketBypassAmount = (_startingTicketCount > 0 ? (_startingTicketCount / 3.5) : 250); rejectionMessage = ""; var oldFriendlyPower = friendlyTeam.GetTeamPower(); var oldEnemyPower = enemyTeam.GetTeamPower(); var newFriendlyPower = friendlyTeam.GetTeamPower(aPlayer, null); var newEnemyPower = enemyTeam.GetTeamPower(null, aPlayer); if (enemyTeam == mapUpTeam) { powerPercentageThreshold = 0; } var enemyMetro1 = _serverInfo.InfoObject.Map == "XP0_Metro" && _serverInfo.InfoObject.GameMode == "ConquestLarge0" && enemyTeam.TeamID == 1; var debugOldPower = oldEnemyPower; var debugNewPower = newEnemyPower; if (enemyMetro1) { if (roundMinutes < 20 && team1.TeamTicketCount + 500 > team2.TeamTicketCount) { powerPercentageThreshold = 0; } // If this is metro, overstate the power of the lower team slightly // The upper team needs a slight stat boost over normal if (enemyTeam == mapUpTeam) { // If the lower team has the map, overstate its power even more if ((team2.TeamTicketCount + 500 < team1.TeamTicketCount || roundMinutes < 10) && _populationStatus == PopulationState.High) { oldEnemyPower *= 1.35; newEnemyPower *= 1.35; } else { oldEnemyPower *= 1.22; newEnemyPower *= 1.22; } } else if (team1.TeamTicketCount + 500 > team2.TeamTicketCount) { if (roundMinutes <= 10) { oldEnemyPower *= 1.12; newEnemyPower *= 1.12; } else if (_populationStatus == PopulationState.High) { oldEnemyPower *= 1.08; newEnemyPower *= 1.08; } } } var newFriendlyCount = GetPlayerCount(true, true, true, friendlyTeam.TeamID) - 1; var newEnemyCount = GetPlayerCount(true, true, true, enemyTeam.TeamID) + 1; // Weed out bad assumptions // like a team being more powerful without someone on it newFriendlyPower = Math.Min(oldFriendlyPower, newFriendlyPower); // or less powerful with someone on it newEnemyPower = Math.Max(oldEnemyPower, newEnemyPower); // Calculate power differences var newPowerDiff = Math.Abs(newEnemyPower - newFriendlyPower); var oldPowerDiff = Math.Abs(oldEnemyPower - oldFriendlyPower); // Calculate percent differences var newPercDiff = Math.Abs(newFriendlyPower - newEnemyPower) / ((newFriendlyPower + newEnemyPower) / 2.0) * 100.0; var oldPercDiff = Math.Abs(oldFriendlyPower - oldEnemyPower) / ((oldFriendlyPower + oldEnemyPower) / 2.0) * 100.0; Boolean enemyWinning = (aPlayer.fbpInfo.TeamID == losingTeam.TeamID); Boolean enemyHasMoreMap = enemyTeam.GetTicketDifferenceRate() > friendlyTeam.GetTicketDifferenceRate(); if (_serverInfo.GetRoundElapsedTime().TotalMinutes < _minimumAssistMinutes) { canAssist = false; var duration = TimeSpan.FromMinutes(_minimumAssistMinutes - _serverInfo.GetRoundElapsedTime().TotalMinutes); rejectionMessage += "assist off for " + FormatTimeString(duration, 2); } else if (enemyWinning && enemyHasMoreMap) { canAssist = false; rejectionMessage += "winning and strong"; } else if (newEnemyCount - 4 >= newFriendlyCount) { // Hard cap the number of players a team can have over another canAssist = false; rejectionMessage += "too many players"; } else { var enemyMorePowerful = newEnemyPower > newFriendlyPower; var powerDifferenceIncreased = newPowerDiff > oldPowerDiff; var powerDifferencePercOverThreshold = newPercDiff > powerPercentageThreshold; // Check team power if (_previousRoundDuration.TotalSeconds > 0 && _serverInfo.GetRoundElapsedTime().TotalMinutes >= 10 && Math.Abs(winningTeam.TeamTicketCount - losingTeam.TeamTicketCount) > ticketBypassAmount && enemyTeam == losingTeam) { ticketBypass = true; } else { if (// The new team would be absolutely more powerful than the current team enemyMorePowerful && // The differenct in power between the teams would go up powerDifferenceIncreased && // The difference in power would be over the threshold, or the enemy has more map (powerDifferencePercOverThreshold || enemyHasMoreMap)) { canAssist = false; rejectionMessage += "would be too strong"; } // Special rejection for metro 1 if (canAssist && enemyMetro1 && roundMinutes < 15 && (enemyMorePowerful || enemyHasMoreMap)) { canAssist = false; rejectionMessage += "1 would be too strong"; } } } if (!canAssist) { if (realRecord != null) { rejectionMessage = Log.FBold(Log.CViolet(rejectionMessage)); rejectionMessage = realRecord.GetSourceName() + " (" + Math.Round(realRecord.target_player.GetPower(true)) + ") assist to " + enemyTeam.GetTeamIDKey() + " rejected (" + rejectionMessage + ")."; if (!auto) { rejectionMessage += " Queued #" + (_AssistAttemptQueue.Count() + 1) + " for auto-assist."; lock (_AssistAttemptQueue) { _AssistAttemptQueue.Enqueue(realRecord); } AdminSayMessage(Log.CViolet(rejectionMessage)); } } else if (debugRecord != null) { rejectionMessage = debugRecord.GetTargetNames() + " (" + Math.Round(debugRecord.target_player.GetPower(true)) + ") assist to " + enemyTeam.GetTeamIDKey() + " rejected, " + rejectionMessage; if (!auto) { InfoOrRespond(debugRecord, rejectionMessage); } } } else { if (realRecord != null) { SendMessageToSource(realRecord, Log.CViolet("Queuing you to assist the weak team. Thank you.")); var powerDiffString = Math.Round(newPercDiff) + "<" + Math.Round(oldPercDiff); if (newPercDiff > oldPercDiff && newPercDiff <= powerPercentageThreshold) { powerDiffString = "Lenient"; } if (ticketBypass) { powerDiffString = "Bypass"; } AdminSayMessage(Log.CViolet(realRecord.GetTargetNames() + " (" + Math.Round(realRecord.target_player.GetPower(true)) + ") assist to " + enemyTeam.GetTeamIDKey() + " accepted (" + powerDiffString + "), queueing.")); realRecord.command_action = GetCommandByKey("self_assist_unconfirmed"); } else if (debugRecord != null) { SendMessageToSource(debugRecord, Log.CViolet("Assist accepted.")); } } return canAssist; } private Int32 FetchEstimatedEventRoundNumber() { var roundDate = GetEventRoundDateTime(); if (DateTime.Now >= roundDate) { return 0; } var durationTillEvent = roundDate.Subtract(DateTime.Now); var estimate = _roundID + (int)Math.Ceiling((durationTillEvent.TotalMinutes + _serverInfo.GetRoundElapsedTime().TotalMinutes) / FetchAverageRoundMinutes(durationTillEvent.TotalHours < 72)); if (estimate < 1) { estimate = 1; } if (estimate > 1000000) { estimate = 1000000; } return estimate; } private DateTime GetEventRoundDateTime() { return _EventDate.AddHours(_EventHour); } private Double FetchAverageRoundMinutes(Boolean active) { try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { if (active) { //Only include active rounds, this is best for durations when the event is near command.CommandText = @" select avg(round_duration) `AvgRoundDuration` from (select * from (select round_id, min(roundstat_time) round_starttime, max(roundstat_time) round_endtime, timestampdiff(minute, min(roundstat_time), max(roundstat_time)) round_duration from tbl_extendedroundstats where server_id = @ServerID and timestampdiff(minute, `roundstat_time`, utc_timestamp()) < 15080 group by round_id) round_times where round_duration < 100 and round_duration > 5) round_durations"; } else { //Non-active round inclusion is good for estimating long-term command.CommandText = @" SELECT TIMESTAMPDIFF(SECOND, MIN(`roundstart_time`), MAX(`roundstart_time`)) / (REPLACE(COUNT(`round_id`), 0, 1.0)) / 60.0 as `AvgRoundDuration` FROM (SELECT `round_id`, `roundstat_time` AS `roundstart_time` FROM `tbl_extendedroundstats` WHERE `server_id` = @ServerID AND TIMESTAMPDIFF(MINUTE, `roundstat_time`, UTC_TIMESTAMP()) < 15080 GROUP BY `round_id` ORDER BY `roundstat_id` DESC) AS `RoundStartTimes`"; } command.Parameters.AddWithValue("@ServerID", _serverInfo.ServerID); using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { return reader.GetDouble("AvgRoundDuration"); } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching average round duration.", e)); } return 0; } private DateTime FetchFutureRoundDate(Int32 TargetRoundID) { if (_roundID < 1 || TargetRoundID <= 1 || _databaseConnectionCriticalState) { return DateTime.MinValue; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { //The most ham-handed SQL I've ever written command.CommandText = @" SELECT *, (@TargetRound - `CurrentRoundId`) AS `RemainingRounds`, `AvgRoundDuration` * (@TargetRound - `CurrentRoundId`) AS `RemainingMinutes`, DATE_ADD(UTC_TIMESTAMP(), INTERVAL `AvgRoundDuration` * (@TargetRound - `CurrentRoundId`) MINUTE) AS `TargetTime` FROM (SELECT (SELECT MAX(`round_id`) FROM `tbl_extendedroundstats` WHERE `server_id` = @ServerID) AS `CurrentRoundId`, (SELECT TIMESTAMPDIFF(SECOND, MIN(`roundstart_time`), MAX(`roundstart_time`)) / (REPLACE(COUNT(`round_id`), 0, 1.0)) / 60.0 FROM (SELECT `round_id`, `roundstat_time` AS `roundstart_time` FROM `tbl_extendedroundstats` WHERE `server_id` = @ServerID AND TIMESTAMPDIFF(MINUTE, `roundstat_time`, UTC_TIMESTAMP()) < 10080 GROUP BY `round_id` ORDER BY `roundstat_id` DESC) AS `RoundStartTimes`) AS `AvgRoundDuration`) AS `RoundInfo`"; command.Parameters.AddWithValue("@ServerID", _serverInfo.ServerID); command.Parameters.AddWithValue("@TargetRound", TargetRoundID); using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { return reader.GetDateTime("TargetTime"); } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching future round time.", e)); } return DateTime.MinValue; } private List FetchPlayerBans(APlayer player) { Log.Debug(() => "FetchPlayerBans starting!", 6); List aBanList = new List(); //Make sure database connection active if (_databaseConnectionCriticalState) { return aBanList; } if (player == null) { Log.Error("Player null when fetching player bans. Contact ColColonCleaner."); return aBanList; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { //Build the query String query = @" SELECT `adkats_bans`.`ban_id`, `adkats_bans`.`player_id`, `adkats_bans`.`latest_record_id`, `adkats_bans`.`ban_status`, `adkats_bans`.`ban_notes`, `adkats_bans`.`ban_startTime`, `adkats_bans`.`ban_endTime`, `adkats_bans`.`ban_enforceName`, `adkats_bans`.`ban_enforceGUID`, `adkats_bans`.`ban_enforceIP`, `adkats_bans`.`ban_sync` FROM `adkats_bans` INNER JOIN `tbl_playerdata` ON `tbl_playerdata`.`PlayerID` = `adkats_bans`.`player_id` WHERE `adkats_bans`.`ban_status` = 'Active' "; if (_serverInfo.GameID > 0 && player.game_id < 0) { query += " AND `tbl_playerdata`.`GameID` = " + _serverInfo.GameID; } else if (player.game_id > 0) { query += " AND `tbl_playerdata`.`GameID` = " + player.game_id; } else { Log.Error("Unusable game IDs when fetching player bans for " + player.player_name + "."); return aBanList; } query += " AND ("; Boolean started = false; if (!String.IsNullOrEmpty(player.player_name)) { started = true; query += "(`tbl_playerdata`.`SoldierName` = '" + player.player_name + @"' AND `adkats_bans`.`ban_enforceName` = 'Y')"; } if (!String.IsNullOrEmpty(player.player_guid)) { if (started) { query += " OR "; } started = true; query += "(`tbl_playerdata`.`EAGUID` = '" + player.player_guid + "' AND `adkats_bans`.`ban_enforceGUID` = 'Y')"; } if (!String.IsNullOrEmpty(player.player_ip) && player.player_ip != "127.0.0.1") { if (started) { query += " OR "; } started = true; query += "(`tbl_playerdata`.`IP_Address` = '" + player.player_ip + "' AND `adkats_bans`.`ban_enforceIP` = 'Y')"; } if (!started) { Log.HandleException(new AException("No data to fetch ban with. This should never happen.")); return aBanList; } query += ")"; //Assign the query command.CommandText = query; if (_debugDisplayPlayerFetches) { PrintPreparedCommand(command); } using (MySqlDataReader reader = SafeExecuteReader(command)) { while (reader.Read()) { //Create the ban element ABan aBan = new ABan { ban_id = reader.GetInt64("ban_id"), ban_status = reader.GetString("ban_status"), ban_notes = reader.GetString("ban_notes"), ban_sync = reader.GetString("ban_sync"), ban_startTime = reader.GetDateTime("ban_startTime"), ban_endTime = reader.GetDateTime("ban_endTime"), ban_enforceName = (reader.GetString("ban_enforceName") == "Y"), ban_enforceGUID = (reader.GetString("ban_enforceGUID") == "Y"), ban_enforceIP = (reader.GetString("ban_enforceIP") == "Y"), ban_record = FetchRecordByID(reader.GetInt64("latest_record_id"), false) }; if (aBan.ban_endTime.Subtract(UtcNow()).TotalSeconds < 0) { aBan.ban_status = "Expired"; UpdateBanStatus(aBan); } else if (!String.IsNullOrEmpty(player.player_name_previous) && aBan.ban_enforceName && !aBan.ban_enforceGUID && !aBan.ban_enforceIP) { ARecord record = new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_unban"), command_numeric = 0, target_name = player.player_name, target_player = player, source_name = "BanEnforcer", record_message = "Name-Banned player has changed their name. (" + player.player_name_previous + " -> " + player.player_name + ")", record_time = UtcNow() }; QueueRecordForProcessing(record); } else if (_serverInfo.ServerGroup == FetchServerGroup(aBan.ban_record.server_id) && aBan.ban_startTime < UtcNow()) { aBanList.Add(aBan); } } if (aBanList.Count > 1) { Log.Warn("Multiple bans matched player " + player.player_id + ". Linked accounts detected."); } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching player ban.", e)); } Log.Debug(() => "FetchPlayerBans finished!", 6); return aBanList; } private List FetchMatchingBans(String playerSubstring, Int64 searchLimit) { Log.Debug(() => "FetchMatchingBans starting!", 6); List aBanList = new List(); //Make sure database connection active if (_databaseConnectionCriticalState) { return null; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { //Build the query command.CommandText = @" SELECT `ban_id` FROM `tbl_playerdata` INNER JOIN `adkats_bans` ON `PlayerID` = `player_id` INNER JOIN `adkats_records_main` ON `latest_record_id` = `record_id` WHERE `ban_status` = 'Active' AND ( `SoldierName` LIKE @PlayerSubstring OR `target_name` LIKE @PlayerSubstring ) ORDER BY `record_time` DESC LIMIT @searchLimit"; //Add the search value command.Parameters.AddWithValue("@PlayerSubstring", "%" + playerSubstring + "%"); command.Parameters.AddWithValue("@searchLimit", searchLimit); using (MySqlDataReader reader = SafeExecuteReader(command)) { while (reader.Read()) { aBanList.Add(FetchBanByID(reader.GetInt64("ban_id"))); } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching player ban.", e)); } return aBanList; } private void RepopulateProconBanList() { Log.Debug(() => "repopulateProconBanList starting!", 6); Log.Info("Downloading bans from database, please wait. This might take several minutes depending on your ban count!"); //Make sure database connection active if (_databaseConnectionCriticalState) { return; } Double totalBans = 0; Double bansDownloaded = 0; Double bansRepopulated = 0; Boolean earlyExit = false; DateTime startTime = UtcNow(); try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT COUNT(*) AS `ban_count` FROM `adkats_bans` WHERE `ban_status` = 'Active'"; using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { totalBans = reader.GetInt64("ban_count"); } } } if (totalBans < 1) { return; } using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT `ban_id`, `player_id`, `latest_record_id`, `ban_status`, `ban_notes`, `ban_sync`, `ban_startTime`, `ban_endTime`, `ban_enforceName`, `ban_enforceGUID`, `ban_enforceIP` FROM `adkats_bans` WHERE `ban_status` = 'Active'"; List importedBans = new List(); using (MySqlDataReader reader = SafeExecuteReader(command)) { //Loop through all incoming bans while (reader.Read()) { //Break from the loop if the plugin is disabled or the setting is reverted. if (!_pluginEnabled || _UseBanEnforcer) { Log.Warn("You exited the ban download process early, the process was not completed."); earlyExit = true; break; } //Create the ban element ABan aBan = new ABan { ban_id = reader.GetInt64("ban_id"), player_id = reader.GetInt64("player_id"), ban_status = reader.GetString("ban_status"), ban_notes = reader.GetString("ban_notes"), ban_sync = reader.GetString("ban_sync"), ban_startTime = reader.GetDateTime("ban_startTime"), ban_endTime = reader.GetDateTime("ban_endTime"), ban_record = FetchRecordByID(reader.GetInt64("latest_record_id"), false), ban_enforceName = (reader.GetString("ban_enforceName") == "Y"), ban_enforceGUID = (reader.GetString("ban_enforceGUID") == "Y"), ban_enforceIP = (reader.GetString("ban_enforceIP") == "Y") }; if (aBan.ban_status != "Active") { continue; } if (aBan.ban_record == null) { aBan.ban_record = new ARecord { record_source = ARecord.Sources.Automated, isDebug = false, target_player = FetchPlayer(false, true, false, null, aBan.player_id, null, null, null, null), source_name = "AdKats", record_message = "Ban Reason Expunged", record_time = UtcNow() }; aBan.ban_record.target_name = aBan.ban_record.target_player.player_name; } if (aBan.ban_record.target_player == null) { aBan.ban_record.target_player = FetchPlayer(false, true, false, null, aBan.player_id, null, null, null, null); } if (aBan.ban_record.target_player != null) { importedBans.Add(aBan); if (++bansDownloaded % 15 == 0) { Log.Write(Math.Round(100 * bansDownloaded / totalBans, 2) + "% of bans downloaded. AVG " + Math.Round(bansDownloaded / ((UtcNow() - startTime).TotalSeconds), 2) + " downloads/sec."); } } } } if (importedBans.Count > 0) { Log.Info(importedBans.Count + " bans downloaded, beginning repopulation to ban list."); } startTime = UtcNow(); foreach (ABan aBan in importedBans) { //Get the record information long totalBanSeconds = (long)aBan.ban_endTime.Subtract(UtcNow()).TotalSeconds; if (totalBanSeconds > 0) { Log.Debug(() => "Re-ProconBanning: " + aBan.ban_record.GetTargetNames() + " for " + totalBanSeconds + "sec for " + aBan.ban_record.record_message, 4); //Push the id ban if (aBan.ban_enforceName) { Threading.Wait(75); //Permabans and Temp bans longer than 1 year will be defaulted to permaban if (totalBanSeconds > 0 && totalBanSeconds < 31536000) { ExecuteCommand("procon.protected.send", "banList.add", "id", aBan.ban_record.target_player.player_name, "seconds", totalBanSeconds.ToString(), aBan.ban_record.record_message); } else { ExecuteCommand("procon.protected.send", "banList.add", "id", aBan.ban_record.target_player.player_name, "perm", aBan.ban_record.record_message); } } //Push the guid ban if (aBan.ban_enforceGUID) { Threading.Wait(75); //Permabans and Temp bans longer than 1 year will be defaulted to permaban if (totalBanSeconds > 0 && totalBanSeconds < 31536000) { ExecuteCommand("procon.protected.send", "banList.add", "guid", aBan.ban_record.target_player.player_guid, "seconds", totalBanSeconds.ToString(), aBan.ban_record.record_message); } else { ExecuteCommand("procon.protected.send", "banList.add", "guid", aBan.ban_record.target_player.player_guid, "perm", aBan.ban_record.record_message); } } //Push the IP ban if (aBan.ban_enforceIP) { Threading.Wait(75); //Permabans and Temp bans longer than 1 year will be defaulted to permaban if (totalBanSeconds > 0 && totalBanSeconds < 31536000) { ExecuteCommand("procon.protected.send", "banList.add", "ip", aBan.ban_record.target_player.player_ip, "seconds", totalBanSeconds.ToString(), aBan.ban_record.record_message); } else { ExecuteCommand("procon.protected.send", "banList.add", "ip", aBan.ban_record.target_player.player_ip, "perm", aBan.ban_record.record_message); } } } if (++bansRepopulated % 15 == 0) { Log.Write(Math.Round(100 * bansRepopulated / totalBans, 2) + "% of bans repopulated. AVG " + Math.Round(bansRepopulated / ((UtcNow() - startTime).TotalSeconds), 2) + " downloads/sec."); } } ExecuteCommand("procon.protected.send", "banList.save"); ExecuteCommand("procon.protected.send", "banList.list"); if (!earlyExit) { Log.Success("All AdKats Enforced bans repopulated to procon's ban list."); } //Update the last db ban fetch time _lastDbBanFetch = UtcNow(); } } } catch (Exception e) { Log.HandleException(new AException("Error while repopulating procon banlist.", e)); } } private Boolean UpdateBanStatus(ABan aBan) { Log.Debug(() => "updateBanStatus starting!", 6); //Make sure database connection active if (_databaseConnectionCriticalState) { return false; } Boolean success = false; if (aBan == null) { Log.Error("Ban invalid in updateBanStatus."); } else { try { //Conditionally modify the ban_sync for this server if (!aBan.ban_sync.Contains("*" + _serverInfo.ServerID + "*")) { aBan.ban_sync += ("*" + _serverInfo.ServerID + "*"); } using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { String query = @" UPDATE `" + _mySqlSchemaName + @"`.`adkats_bans` SET `ban_sync` = '" + aBan.ban_sync + @"', `ban_status` = '" + aBan.ban_status + @"' WHERE `ban_id` = " + aBan.ban_id; command.CommandText = query; //Attempt to execute the query if (SafeExecuteNonQuery(command) > 0) { success = true; } } } } catch (Exception e) { Log.HandleException(new AException("Error while updating status of ban.", e)); } } Log.Debug(() => "updateBanStatus finished!", 6); return success; } private void ImportBansFromBBM5108() { //Check if tables exist from BF3 Ban Manager if (!ConfirmTable("bm_banlist")) { return; } Log.Info("BF3 Ban Manager tables detected. Checking validity."); //Check if any BBM5108 bans exist in the AdKats Banlist try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT * FROM `" + _mySqlSchemaName + @"`.`adkats_bans` WHERE `adkats_bans`.`ban_notes` = 'BBM5108' LIMIT 1"; using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { Log.Info("BF3 Ban Manager bans already imported, Cancelling import."); return; } } } } } catch (Exception e) { Log.HandleException(new AException("Error while checking for BBM Bans.", e)); return; } Log.Success("Validity confirmed."); Log.Info("Preparing to fetch all BF3 Ban Manager Bans..."); Double totalBans = 0; Double bansImported = 0; Queue inboundBBMBans = new Queue(); DateTime startTime = UtcNow(); try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { Log.Debug(() => "Creating query to import BBM5108", 3); command.CommandText = @" SELECT soldiername, eaguid, ban_length, ban_duration, ban_reason FROM bm_banlist INNER JOIN bm_soldiers ON bm_banlist.soldierID = bm_soldiers.soldierID"; using (MySqlDataReader reader = SafeExecuteReader(command)) { Boolean told = false; while (reader.Read()) { if (!told) { Log.Debug(() => "BBM5108 bans found, grabbing.", 3); told = true; } BBM5108Ban bbmBan = new BBM5108Ban { soldiername = reader.IsDBNull(reader.GetOrdinal("soldiername")) ? null : reader.GetString("soldiername"), eaguid = reader.IsDBNull(reader.GetOrdinal("eaguid")) ? null : reader.GetString("eaguid"), ban_length = reader.GetString("ban_length"), ban_duration = reader.GetDateTime("ban_duration"), ban_reason = reader.IsDBNull(reader.GetOrdinal("ban_reason")) ? null : reader.GetString("ban_reason") }; inboundBBMBans.Enqueue(bbmBan); totalBans++; } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching BBM Bans.", e)); return; } Log.Info(totalBans + " Ban Manager bans fetched, starting import to AdKats Ban Enforcer..."); try { //Loop through all BBMBans in order that they came in while (inboundBBMBans.Count > 0) { //Break from the loop if the plugin is disabled or the setting is reverted. if (!_pluginEnabled || !_UseBanEnforcer) { Log.Error("You exited the ban import process process early, the process was not completed and cannot recover without manual override. Talk to ColColonCleaner."); break; } BBM5108Ban bbmBan = inboundBBMBans.Dequeue(); //Create the record ARecord record = new ARecord(); record.record_time = UtcNow(); //Fetch the player record.target_player = FetchPlayer(true, true, false, null, -1, bbmBan.soldiername, bbmBan.eaguid, null, null); record.record_source = ARecord.Sources.Automated; if (bbmBan.ban_length == "permanent") { Log.Debug(() => "Ban is permanent", 4); record.command_type = GetCommandByKey("player_ban_perm"); record.command_action = GetCommandByKey("player_ban_perm"); record.command_numeric = 0; } else if (bbmBan.ban_length == "seconds") { Log.Debug(() => "Ban is temporary", 4); record.command_type = GetCommandByKey("player_ban_temp"); record.command_action = GetCommandByKey("player_ban_temp"); record.command_numeric = (Int32)(bbmBan.ban_duration - UtcNow()).TotalMinutes; } else { //Ignore all other cases e.g. round bans Log.Debug(() => "Ban type '" + bbmBan.ban_length + "' not usable", 3); continue; } record.source_name = "BanEnforcer"; record.server_id = _serverInfo.ServerID; if (!String.IsNullOrEmpty(record.target_player.player_name)) { record.target_name = record.target_player.player_name; } record.isIRO = false; record.record_message = bbmBan.ban_reason; //Update the ban enforcement depending on available information Boolean nameAvailable = !String.IsNullOrEmpty(record.target_player.player_name); Boolean guidAvailable = !String.IsNullOrEmpty(record.target_player.player_guid); Boolean ipAvailable = !String.IsNullOrEmpty(record.target_player.player_ip); //Create the ban ABan aBan = new ABan { ban_record = record, ban_notes = "BBM5108", ban_enforceName = nameAvailable && (_DefaultEnforceName || (!guidAvailable && !ipAvailable) || !String.IsNullOrEmpty(bbmBan.soldiername)), ban_enforceGUID = guidAvailable && (_DefaultEnforceGUID || (!nameAvailable && !ipAvailable) || !String.IsNullOrEmpty(bbmBan.eaguid)), ban_enforceIP = ipAvailable && _DefaultEnforceIP }; if (!aBan.ban_enforceName && !aBan.ban_enforceGUID && !aBan.ban_enforceIP) { Log.Error("Unable to create ban, no proper player information"); continue; } //Upload the ban Log.Debug(() => "Uploading Ban Manager ban.", 5); UploadBan(aBan); if (++bansImported % 25 == 0) { Log.Write(Math.Round(100 * bansImported / totalBans, 2) + "% of Ban Manager bans uploaded. AVG " + Math.Round(bansImported / ((UtcNow() - startTime).TotalSeconds), 2) + " uploads/sec."); } } } catch (Exception e) { Log.HandleException(new AException("Error while processing imported BBM Bans to AdKats banlist.", e)); return; } if (inboundBBMBans.Count == 0) { Log.Success("All Ban Manager bans imported into AdKats Ban Enforcer!"); } } private Boolean CanPunish(ARecord record, Int32 duration) { Log.Debug(() => "canPunish starting!", 6); if (duration < 1) { Log.Error("CanPunish duration must be positive."); return false; } //TODO: Add check for multiple targets if (record.target_player != null && record.target_player.TargetedRecords.Any(aRecord => aRecord.command_type.command_key == "player_punish" && (UtcNow() - aRecord.record_time).TotalSeconds < duration && aRecord.record_action_executed == true)) { return false; } //Make sure database connection active if (_databaseConnectionCriticalState) { record.record_exception = new AException("Database not connected."); return false; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT `record_time` AS `latest_time` FROM `adkats_records_main` WHERE `adkats_records_main`.`command_type` = " + GetCommandByKey("player_punish").command_id + @" AND `adkats_records_main`.`target_id` = " + record.target_player.player_id + @" AND DATE_ADD(`record_time`, INTERVAL " + duration + @" SECOND) > UTC_TIMESTAMP() ORDER BY `record_time` DESC LIMIT 1"; using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { Log.Debug(() => "can't upload punish", 6); return false; } return true; } } } } catch (Exception e) { Log.HandleException(new AException("Error while checking if player can be punished.", e)); //Assume false if any errors return false; } } private Boolean FetchIROStatus(ARecord record) { Log.Debug(() => "FetchIROStatus starting!", 6); try { //TODO: Add check for multiple targets if (record.target_player != null && record.target_player.TargetedRecords.Any(aRecord => aRecord.command_type.command_key == "player_punish" && (UtcNow() - aRecord.record_time).TotalSeconds < _IROTimeout && aRecord.record_action_executed == true)) { return true; } //Make sure database connection active if (_databaseConnectionCriticalState) { record.record_exception = new AException("Database not connected."); return false; } using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT `record_time` AS `latest_time` FROM `adkats_records_main` INNER JOIN `adkats_commands` ON `adkats_records_main`.`command_type` = `adkats_commands`.`command_id` WHERE `adkats_commands`.`command_key` = 'player_punish' AND `adkats_records_main`.`target_id` = " + record.target_player.player_id + @" AND DATE_ADD(`record_time`, INTERVAL " + _IROTimeout + @" MINUTE) > UTC_TIMESTAMP() ORDER BY `record_time` DESC LIMIT 1"; using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { Log.Debug(() => "Punish is Double counted", 6); return true; } return false; } } } } catch (Exception e) { Log.HandleException(new AException("Error while checking if punish will be IRO.", e)); //Assume false if any errors return false; } } private void RunActionsFromDB() { Log.Debug(() => "runActionsFromDB starting!", 7); //Make sure database connection active if (_databaseConnectionCriticalState || !_firstPlayerListComplete) { return; } try { foreach (ARecord record in FetchUnreadRecords()) { QueueRecordForActionHandling(record); } //Update the last time this was fetched _lastDbActionFetch = UtcNow(); } catch (Exception e) { Log.HandleException(new AException("Error while queueing unread records for action handling.", e)); } } private Int32 FetchPoints(APlayer player, Boolean combineOverride, Boolean update) { Int32 returnVal = player.player_infractionPoints; //Make sure database connection active if (_databaseConnectionCriticalState || (!update && player.player_infractionPoints != Int32.MinValue)) { return (returnVal > 0) ? (returnVal) : (0); } Log.Debug(() => "FetchPoints starting!", 6); try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { if (_CombineServerPunishments || combineOverride) { command.CommandText = @"SELECT `total_points` FROM `" + _mySqlSchemaName + @"`.`adkats_infractions_global` WHERE `player_id` = @player_id"; command.Parameters.AddWithValue("@player_id", player.player_id); } else { command.CommandText = @"SELECT `total_points` FROM `" + _mySqlSchemaName + @"`.`adkats_infractions_server` WHERE `player_id` = @player_id and `server_id` = @server_id"; command.Parameters.AddWithValue("@player_id", player.player_id); command.Parameters.AddWithValue("@server_id", _serverInfo.ServerID); } using (MySqlDataReader reader = SafeExecuteReader(command)) { returnVal = reader.Read() ? reader.GetInt32("total_points") : 0; player.player_infractionPoints = returnVal; } } } } catch (Exception e) { Log.HandleException(new AException("Error while getting infraction points for player.", e)); } Log.Debug(() => "FetchPoints finished!", 6); return (returnVal > 0) ? (returnVal) : (0); } private Double FetchPowerInformation(APlayer aPlayer) { Log.Debug(() => "FetchPowerInformation starting!", 6); try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT `InnerResults`.*, ROUND(`top_count`/REPLACE(`round_count`, 0, 1), 2) AS `top_round_ratio` FROM ( SELECT (SELECT COUNT(`stat_id`) FROM `adkats_statistics` WHERE `server_id` = @server_id AND `target_id` = @target_id AND `stat_time` > DATE_SUB(UTC_TIMESTAMP, INTERVAL 90 DAY) AND ( `stat_type` = 'player_win' OR `stat_type` = 'player_loss' )) AS `round_count`, (SELECT COUNT(`stat_id`) FROM `adkats_statistics` WHERE `server_id` = @server_id AND `target_id` = @target_id AND `stat_time` > DATE_SUB(UTC_TIMESTAMP, INTERVAL 90 DAY) AND `stat_type` = 'player_top') AS `top_count` FROM DUAL ) AS `InnerResults`"; command.Parameters.AddWithValue("@server_id", _serverInfo.ServerID); command.Parameters.AddWithValue("@target_id", aPlayer.player_id); //Execute the query using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { //Update top stats aPlayer.TopStats.RoundCount = reader.GetInt32("round_count"); aPlayer.TopStats.TopCount = reader.GetInt32("top_count"); aPlayer.TopStats.TopRoundRatio = reader.GetDouble("top_round_ratio"); aPlayer.TopStats.Fetched = true; } else { Log.Error("Unable to fetch " + aPlayer.GetVerboseName() + "'s top player information."); } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching top player information", e)); } Log.Debug(() => "FetchPowerInformation finished!", 6); return aPlayer.GetPower(false); } private List>> FetchConversation(Int64 player1_id, Int64 player2_id, Int64 limit_lines, Int64 limit_days) { Log.Debug(() => "FetchConversation starting!", 6); List>> pchat = new List>>(); //Make sure database connection active if (_databaseConnectionCriticalState) { return pchat; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" (SELECT `tbl_chatlog`.`logDate` as `chat_time`, `tbl_chatlog`.`logSoldierName` as `chat_player`, `tbl_chatlog`.`logMessage` as `chat_message` FROM `tbl_chatlog` WHERE ( `tbl_chatlog`.`logPlayerID` = @player1_id OR `tbl_chatlog`.`logPlayerID` = @player2_id ) AND `tbl_chatlog`.`ServerID` = @server_id AND DATE_ADD(`tbl_chatlog`.`logDate`, INTERVAL @limit_days DAY) > UTC_TIMESTAMP() ORDER BY `ID` DESC LIMIT @limit_lines) ORDER BY `chat_time` ASC"; command.Parameters.AddWithValue("@player1_id", player1_id); command.Parameters.AddWithValue("@player2_id", player2_id); command.Parameters.AddWithValue("@limit_lines", limit_lines); command.Parameters.AddWithValue("@limit_days", limit_days); command.Parameters.AddWithValue("@server_id", _serverInfo.ServerID); using (MySqlDataReader reader = SafeExecuteReader(command)) { while (reader.Read()) { pchat.Add(new KeyValuePair>(reader.GetDateTime("chat_time"), new KeyValuePair(reader.GetString("chat_player"), reader.GetString("chat_message")))); } } } } } catch (Exception e) { Log.HandleException(new AException("Error while getting conversation for player.", e)); } Log.Debug(() => "FetchConversation finished!", 6); return pchat; } private List> FetchChat(Int64 player_id, Int64 limit_lines, Int64 limit_days) { Log.Debug(() => "FetchChat starting!", 6); List> pchat = new List>(); //Make sure database connection active if (_databaseConnectionCriticalState) { return pchat; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" (SELECT `tbl_chatlog`.`logDate` as `chat_time`, `tbl_chatlog`.`logMessage` as `chat_message` FROM `tbl_chatlog` WHERE `tbl_chatlog`.`logPlayerID` = @player_id AND `tbl_chatlog`.`ServerID` = @server_id AND DATE_ADD(`tbl_chatlog`.`logDate`, INTERVAL @limit_days DAY) > UTC_TIMESTAMP() ORDER BY `ID` DESC LIMIT @limit_lines) ORDER BY `chat_time` ASC"; command.Parameters.AddWithValue("@player_id", player_id); command.Parameters.AddWithValue("@limit_lines", limit_lines); command.Parameters.AddWithValue("@limit_days", limit_days); command.Parameters.AddWithValue("@server_id", _serverInfo.ServerID); using (MySqlDataReader reader = SafeExecuteReader(command)) { while (reader.Read()) { pchat.Add(new KeyValuePair(reader.GetDateTime("chat_time"), reader.GetString("chat_message"))); } } } } } catch (Exception e) { Log.HandleException(new AException("Error while getting conversation for player.", e)); } Log.Debug(() => "FetchChat finished!", 6); return pchat; } private void FetchCommands() { Log.Debug(() => "fetchCommands starting!", 6); Boolean displayUpdate = false; if (_databaseConnectionCriticalState) { return; } try { lock (_CommandIDDictionary) { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand sqlcommand = connection.CreateCommand()) { const string sql = @" SELECT `command_id`, `command_active`, `command_key`, `command_logging`, `command_name`, `command_text`, `command_playerInteraction`, `command_access` FROM `adkats_commands`"; sqlcommand.CommandText = sql; HashSet validIDs = new HashSet(); using (MySqlDataReader reader = SafeExecuteReader(sqlcommand)) { _CommandKeyDictionary.Clear(); _CommandNameDictionary.Clear(); _CommandTextDictionary.Clear(); while (reader.Read()) { if (!_pluginEnabled) { return; } //ID is the immutable element Int32 commandID = reader.GetInt32("command_id"); ACommand.CommandActive commandActive = (ACommand.CommandActive)Enum.Parse(typeof(ACommand.CommandActive), reader.GetString("command_active")); String commandKey = reader.GetString("command_key"); ACommand.CommandLogging commandLogging = (ACommand.CommandLogging)Enum.Parse(typeof(ACommand.CommandLogging), reader.GetString("command_logging")); String commandName = reader.GetString("command_name"); String commandText = reader.GetString("command_text"); ACommand.CommandAccess commandAccess = (ACommand.CommandAccess)Enum.Parse(typeof(ACommand.CommandAccess), reader.GetString("command_access")); Boolean commandPlayerInteraction = reader.GetBoolean("command_playerInteraction"); validIDs.Add(commandID); ACommand currentCommand; if (_CommandIDDictionary.TryGetValue(commandID, out currentCommand)) { if (!currentCommand.command_active.Equals(commandActive)) { Log.Info(currentCommand.command_key + " active state being changed from " + currentCommand.command_active + " to " + commandActive); currentCommand.command_active = commandActive; displayUpdate = true; } if (currentCommand.command_key != commandKey) { Log.Info(currentCommand.command_key + " command key being changed from " + currentCommand.command_key + " to " + commandKey); currentCommand.command_key = commandKey; displayUpdate = true; } if (!currentCommand.command_logging.Equals((commandLogging))) { Log.Info(currentCommand.command_key + " logging state being changed from " + currentCommand.command_logging + " to " + commandLogging); currentCommand.command_logging = commandLogging; displayUpdate = true; } if (currentCommand.command_name != commandName) { Log.Info(currentCommand.command_key + " command name being changed from " + currentCommand.command_name + " to " + commandName); currentCommand.command_name = commandName; displayUpdate = true; } if (currentCommand.command_text != commandText) { Log.Info(currentCommand.command_key + " command text being changed from " + currentCommand.command_text + " to " + commandText); currentCommand.command_text = commandText; displayUpdate = true; } if (currentCommand.command_playerInteraction != commandPlayerInteraction) { Log.Info(currentCommand.command_key + " player interaction state being changed from " + currentCommand.command_playerInteraction + " to " + commandPlayerInteraction); currentCommand.command_playerInteraction = commandPlayerInteraction; displayUpdate = true; } if (!currentCommand.command_access.Equals(commandAccess)) { Log.Info(currentCommand.command_key + " command access being changed from " + currentCommand.command_access + " to " + commandAccess); currentCommand.command_access = commandAccess; displayUpdate = true; } } else { currentCommand = new ACommand { command_id = commandID, command_active = commandActive, command_key = commandKey, command_logging = commandLogging, command_name = commandName, command_text = commandText, command_playerInteraction = commandPlayerInteraction, command_access = commandAccess }; _CommandIDDictionary.Add(currentCommand.command_id, currentCommand); displayUpdate = true; } _CommandKeyDictionary.Add(currentCommand.command_key, currentCommand); _CommandNameDictionary.Add(currentCommand.command_name, currentCommand); _CommandTextDictionary.Add(currentCommand.command_text, currentCommand); if (!_commandUsageTimes.ContainsKey(currentCommand.command_key)) { _commandUsageTimes[currentCommand.command_key] = UtcNow(); } //Handle mandatory defaults Boolean changed = false; switch (currentCommand.command_key) { case "command_confirm": if (currentCommand.command_active != ACommand.CommandActive.Active) { Log.Warn("Confirm command must be active. Resetting."); currentCommand.command_active = ACommand.CommandActive.Active; changed = true; } if (currentCommand.command_text != "yes") { Log.Warn("Confirm command text must be 'yes'. Resetting."); currentCommand.command_text = "yes"; changed = true; } if (currentCommand.command_access != ACommand.CommandAccess.Any) { Log.Warn("Confirm command access must be 'Any'. Resetting."); currentCommand.command_access = ACommand.CommandAccess.Any; changed = true; } break; case "command_cancel": if (currentCommand.command_active != ACommand.CommandActive.Active) { Log.Warn("Cancel command must be active. Resetting."); currentCommand.command_active = ACommand.CommandActive.Active; changed = true; } if (currentCommand.command_text != "no") { Log.Warn("Cancel command text must be 'no'. Resetting."); currentCommand.command_text = "no"; changed = true; } if (currentCommand.command_access != ACommand.CommandAccess.Any) { Log.Warn("Confirm command access must be 'Any'. Resetting."); currentCommand.command_access = ACommand.CommandAccess.Any; changed = true; } break; case "player_say": if (currentCommand.command_access != ACommand.CommandAccess.AnyHidden) { Log.Info(currentCommand.command_name + " access must be 'AnyHidden'. Resetting."); currentCommand.command_access = ACommand.CommandAccess.AnyHidden; changed = true; } break; case "player_yell": if (currentCommand.command_access != ACommand.CommandAccess.AnyHidden) { Log.Info(currentCommand.command_name + " access must be 'AnyHidden'. Resetting."); currentCommand.command_access = ACommand.CommandAccess.AnyHidden; changed = true; } break; case "player_tell": if (currentCommand.command_access != ACommand.CommandAccess.AnyHidden) { Log.Info(currentCommand.command_name + " access must be 'AnyHidden'. Resetting."); currentCommand.command_access = ACommand.CommandAccess.AnyHidden; changed = true; } break; case "player_find": if (currentCommand.command_access != ACommand.CommandAccess.AnyHidden) { Log.Info(currentCommand.command_name + " access must be 'AnyHidden'. Resetting."); currentCommand.command_access = ACommand.CommandAccess.AnyHidden; changed = true; } break; } if (changed) { QueueCommandForUpload(currentCommand); displayUpdate = true; } } } if (_CommandIDDictionary.Count > 0) { foreach (ACommand remCommand in _CommandIDDictionary.Values.Where(aRole => !validIDs.Contains(aRole.command_id)).ToList()) { Log.Info("Removing command " + remCommand.command_key); _CommandIDDictionary.Remove(remCommand.command_id); } Boolean newCommands = false; if (!_CommandIDDictionary.ContainsKey(1)) { SendNonQuery("Adding command command_confirm", "INSERT INTO `adkats_commands` VALUES(1, 'Active', 'command_confirm', 'Unable', 'Confirm Command', 'yes', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(2)) { SendNonQuery("Adding command command_cancel", "INSERT INTO `adkats_commands` VALUES(2, 'Active', 'command_cancel', 'Unable', 'Cancel Command', 'no', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(3)) { SendNonQuery("Adding command player_kill", "INSERT INTO `adkats_commands` VALUES(3, 'Active', 'player_kill', 'Log', 'Kill Player', 'kill', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(4)) { SendNonQuery("Adding command player_kill_lowpop", "INSERT INTO `adkats_commands` VALUES(4, 'Invisible', 'player_kill_lowpop', 'Log', 'Kill Player (Low Population)', 'lowpopkill', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(5)) { SendNonQuery("Adding command player_kill_repeat", "INSERT INTO `adkats_commands` VALUES(5, 'Invisible', 'player_kill_repeat', 'Log', 'Kill Player (Repeat Kill)', 'repeatkill', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(6)) { SendNonQuery("Adding command player_kick", "INSERT INTO `adkats_commands` VALUES(6, 'Active', 'player_kick', 'Log', 'Kick Player', 'kick', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(7)) { SendNonQuery("Adding command player_ban_temp", "INSERT INTO `adkats_commands` VALUES(7, 'Active', 'player_ban_temp', 'Log', 'Temp-Ban Player', 'tban', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(8)) { SendNonQuery("Adding command player_ban_perm", "INSERT INTO `adkats_commands` VALUES(8, 'Active', 'player_ban_perm', 'Log', 'Permaban Player', 'ban', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(9)) { SendNonQuery("Adding command player_punish", "INSERT INTO `adkats_commands` VALUES(9, 'Active', 'player_punish', 'Mandatory', 'Punish Player', 'punish', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(10)) { SendNonQuery("Adding command player_forgive", "INSERT INTO `adkats_commands` VALUES(10, 'Active', 'player_forgive', 'Mandatory', 'Forgive Player', 'forgive', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(11)) { SendNonQuery("Adding command player_mute", "INSERT INTO `adkats_commands` VALUES(11, 'Active', 'player_mute', 'Log', 'Mute Player', 'mute', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(12)) { SendNonQuery("Adding command player_join", "INSERT INTO `adkats_commands` VALUES(12, 'Active', 'player_join', 'Log', 'Join Player', 'join', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(14)) { SendNonQuery("Adding command player_move", "INSERT INTO `adkats_commands` VALUES(14, 'Active', 'player_move', 'Log', 'On-Death Move Player', 'move', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(15)) { SendNonQuery("Adding command player_fmove", "INSERT INTO `adkats_commands` VALUES(15, 'Active', 'player_fmove', 'Log', 'Force Move Player', 'fmove', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(16)) { SendNonQuery("Adding command self_teamswap", "INSERT INTO `adkats_commands` VALUES(16, 'Active', 'self_teamswap', 'Log', 'Teamswap Self', 'moveme', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(17)) { SendNonQuery("Adding command self_kill", "INSERT INTO `adkats_commands` VALUES(17, 'Active', 'self_kill', 'Log', 'Kill Self', 'killme', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(18)) { SendNonQuery("Adding command player_report", "INSERT INTO `adkats_commands` VALUES(18, 'Active', 'player_report', 'Log', 'Report Player', 'report', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(19)) { SendNonQuery("Adding command player_report_confirm", "INSERT INTO `adkats_commands` VALUES(19, 'Invisible', 'player_report_confirm', 'Log', 'Report Player (Confirmed)', 'confirmreport', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(20)) { SendNonQuery("Adding command player_calladmin", "INSERT INTO `adkats_commands` VALUES(20, 'Active', 'player_calladmin', 'Log', 'Call Admin on Player', 'admin', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(21)) { SendNonQuery("Adding command admin_say", "INSERT INTO `adkats_commands` VALUES(21, 'Active', 'admin_say', 'Log', 'Admin Say', 'say', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(22)) { SendNonQuery("Adding command player_say", "INSERT INTO `adkats_commands` VALUES(22, 'Active', 'player_say', 'Log', 'Player Say', 'psay', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(23)) { SendNonQuery("Adding command admin_yell", "INSERT INTO `adkats_commands` VALUES(23, 'Active', 'admin_yell', 'Log', 'Admin Yell', 'yell', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(24)) { SendNonQuery("Adding command player_yell", "INSERT INTO `adkats_commands` VALUES(24, 'Active', 'player_yell', 'Log', 'Player Yell', 'pyell', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(25)) { SendNonQuery("Adding command admin_tell", "INSERT INTO `adkats_commands` VALUES(25, 'Active', 'admin_tell', 'Log', 'Admin Tell', 'tell', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(26)) { SendNonQuery("Adding command player_tell", "INSERT INTO `adkats_commands` VALUES(26, 'Active', 'player_tell', 'Log', 'Player Tell', 'ptell', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(27)) { SendNonQuery("Adding command self_whatis", "INSERT INTO `adkats_commands` VALUES(27, 'Active', 'self_whatis', 'Unable', 'What Is', 'whatis', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(28)) { SendNonQuery("Adding command self_voip", "INSERT INTO `adkats_commands` VALUES(28, 'Active', 'self_voip', 'Unable', 'VOIP', 'voip', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(29)) { SendNonQuery("Adding command self_rules", "INSERT INTO `adkats_commands` VALUES(29, 'Active', 'self_rules', 'Log', 'Request Rules', 'rules', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(30)) { SendNonQuery("Adding command round_restart", "INSERT INTO `adkats_commands` VALUES(30, 'Active', 'round_restart', 'Log', 'Restart Current Round', 'restart', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(31)) { SendNonQuery("Adding command round_next", "INSERT INTO `adkats_commands` VALUES(31, 'Active', 'round_next', 'Log', 'Run Next Round', 'nextlevel', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(32)) { SendNonQuery("Adding command round_end", "INSERT INTO `adkats_commands` VALUES(32, 'Active', 'round_end', 'Log', 'End Current Round', 'endround', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(33)) { SendNonQuery("Adding command server_nuke", "INSERT INTO `adkats_commands` VALUES(33, 'Active', 'server_nuke', 'Log', 'Server Nuke', 'nuke', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(34)) { SendNonQuery("Adding command server_kickall", "INSERT INTO `adkats_commands` VALUES(34, 'Active', 'server_kickall', 'Log', 'Kick All Guests', 'kickall', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(35)) { SendNonQuery("Adding command adkats_exception", "INSERT INTO `adkats_commands` VALUES(35, 'Invisible', 'adkats_exception', 'Mandatory', 'Logged Exception', 'logexception', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(36)) { SendNonQuery("Adding command banenforcer_enforce", "INSERT INTO `adkats_commands` VALUES(36, 'Invisible', 'banenforcer_enforce', 'Mandatory', 'Enforce Active Ban', 'enforceban', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(37)) { SendNonQuery("Adding command player_unban", "INSERT INTO `adkats_commands` VALUES(37, 'Active', 'player_unban', 'Log', 'Unban Player', 'unban', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(38)) { SendNonQuery("Adding command self_admins", "INSERT INTO `adkats_commands` VALUES(38, 'Active', 'self_admins', 'Log', 'Request Online Admins', 'admins', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(39)) { SendNonQuery("Adding command self_lead", "INSERT INTO `adkats_commands` VALUES(39, 'Active', 'self_lead', 'Log', 'Lead Current Squad', 'lead', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(40)) { SendNonQuery("Adding command admin_accept", "INSERT INTO `adkats_commands` VALUES(40, 'Active', 'admin_accept', 'Log', 'Accept player report', 'accept', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(41)) { SendNonQuery("Adding command admin_deny", "INSERT INTO `adkats_commands` VALUES(41, 'Active', 'admin_deny', 'Log', 'Deny player report', 'deny', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(42)) { SendNonQuery("Adding command player_report_deny", "INSERT INTO `adkats_commands` VALUES(42, 'Invisible', 'player_report_deny', 'Log', 'Report Player (Denied)', 'denyreport', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(43)) { SendNonQuery("Adding command server_swapnuke", "INSERT INTO `adkats_commands` VALUES(43, 'Active', 'server_swapnuke', 'Log', 'SwapNuke Server', 'swapnuke', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(44)) { SendNonQuery("Adding command player_blacklistdisperse", "INSERT INTO `adkats_commands` VALUES(44, 'Active', 'player_blacklistdisperse', 'Log', 'Autobalance Disperse Player', 'disperse', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(45)) { SendNonQuery("Adding command player_whitelistbalance", "INSERT INTO `adkats_commands` VALUES(45, 'Active', 'player_whitelistbalance', 'Log', 'Autobalance Whitelist Player', 'mbwhitelist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(46)) { SendNonQuery("Adding command player_slotreserved", "INSERT INTO `adkats_commands` VALUES(46, 'Active', 'player_slotreserved', 'Log', 'Reserved Slot Player', 'reserved', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(47)) { SendNonQuery("Adding command player_slotspectator", "INSERT INTO `adkats_commands` VALUES(47, 'Active', 'player_slotspectator', 'Log', 'Spectator Slot Player', 'spectator', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(48)) { SendNonQuery("Adding command player_changename", "INSERT INTO `adkats_commands` VALUES(48, 'Invisible', 'player_changename', 'Mandatory', 'Player Changed Name', 'changename', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(49)) { SendNonQuery("Adding command player_changeip", "INSERT INTO `adkats_commands` VALUES(49, 'Invisible', 'player_changeip', 'Mandatory', 'Player Changed IP', 'changeip', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(50)) { SendNonQuery("Adding command player_ban_perm_future", "INSERT INTO `adkats_commands` VALUES(50, 'Active', 'player_ban_perm_future', 'Log', 'Future Permaban Player', 'fban', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(51)) { SendNonQuery("Adding command self_assist", "INSERT INTO `adkats_commands` VALUES(51, 'Active', 'self_assist', 'Log', 'Assist Losing Team', 'assist', FALSE, 'Any')", true); newCommands = true; } SendNonQuery("Updating command 51 player interaction", "UPDATE `adkats_commands` SET `command_playerInteraction`=0 WHERE `command_id`=51", false); if (!_CommandIDDictionary.ContainsKey(52)) { SendNonQuery("Adding command self_uptime", "INSERT INTO `adkats_commands` VALUES(52, 'Active', 'self_uptime', 'Log', 'Request Uptimes', 'uptime', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(53)) { SendNonQuery("Adding command self_contest", "INSERT INTO `adkats_commands` VALUES(53, 'Active', 'self_contest', 'Log', 'Contest Report', 'contest', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(54)) { SendNonQuery("Adding command player_kill_force", "INSERT INTO `adkats_commands` VALUES(54, 'Active', 'player_kill_force', 'Log', 'Kill Player (Force)', 'fkill', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(55)) { SendNonQuery("Adding command player_info", "INSERT INTO `adkats_commands` VALUES(55, 'Active', 'player_info', 'Log', 'Fetch Player Info', 'pinfo', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(56)) { SendNonQuery("Adding command player_dequeue", "INSERT INTO `adkats_commands` VALUES(56, 'Active', 'player_dequeue', 'Log', 'Dequeue Player Action', 'deq', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(57)) { SendNonQuery("Adding command self_help", "INSERT INTO `adkats_commands` VALUES(57, 'Active', 'self_help', 'Log', 'Request Server Commands', 'help', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(58)) { SendNonQuery("Adding command player_find", "INSERT INTO `adkats_commands` VALUES(58, 'Active', 'player_find', 'Log', 'Find Player', 'find', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(59)) { SendNonQuery("Adding command server_afk", "INSERT INTO `adkats_commands` VALUES(59, 'Active', 'server_afk', 'Log', 'Manage AFK Players', 'afk', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(60)) { SendNonQuery("Adding command player_pull", "INSERT INTO `adkats_commands` VALUES(60, 'Active', 'player_pull', 'Log', 'Pull Player', 'pull', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(61)) { SendNonQuery("Adding command admin_ignore", "INSERT INTO `adkats_commands` VALUES(61, 'Active', 'admin_ignore', 'Log', 'Ignore player report', 'ignore', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(62)) { SendNonQuery("Adding command player_report_ignore", "INSERT INTO `adkats_commands` VALUES(62, 'Invisible', 'player_report_ignore', 'Log', 'Report Player (Ignored)', 'ignorereport', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(63)) { SendNonQuery("Adding command player_mark", "INSERT INTO `adkats_commands` VALUES(63, 'Active', 'player_mark', 'Unable', 'Mark Player', 'mark', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(64)) { SendNonQuery("Adding command player_chat", "INSERT INTO `adkats_commands` VALUES(64, 'Active', 'player_chat', 'Log', 'Fetch Player Chat', 'pchat', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(65)) { SendNonQuery("Adding command player_whitelistanticheat", "INSERT INTO `adkats_commands` VALUES(65, 'Active', 'player_whitelistanticheat', 'Log', 'AntiCheat Whitelist Player', 'acwhitelist', TRUE, 'Any')", true); newCommands = true; } if (SendQuery("SELECT command_id FROM adkats_commands WHERE command_key = 'player_whitelisthackerchecker'", false)) { Log.Info("Updating command player_whitelisthackerchecker to new definition player_whitelistanticheat."); SendNonQuery("Updating command player_whitelisthackerchecker command_text to new definition.", "UPDATE adkats_commands SET adkats_commands.command_text = 'acwhitelist' WHERE command_id = 65", true); SendNonQuery("Updating command player_whitelisthackerchecker command_name to new definition.", "UPDATE adkats_commands SET adkats_commands.command_name = 'AntiCheat Whitelist Player' WHERE command_id = 65", true); SendNonQuery("Updating command player_whitelisthackerchecker command_key to new definition.", "UPDATE adkats_commands SET adkats_commands.command_key = 'player_whitelistanticheat' WHERE command_id = 65", true); } if (!_CommandIDDictionary.ContainsKey(66)) { SendNonQuery("Adding command player_lock", "INSERT INTO `adkats_commands` VALUES(66, 'Active', 'player_lock', 'Log', 'Lock Player Commands', 'lock', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(67)) { SendNonQuery("Adding command player_unlock", "INSERT INTO `adkats_commands` VALUES(67, 'Active', 'player_unlock', 'Log', 'Unlock Player Commands', 'unlock', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(68)) { SendNonQuery("Adding command self_rep", "INSERT INTO `adkats_commands` VALUES(68, 'Active', 'self_rep', 'Log', 'Request Server Reputation', 'rep', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(69)) { SendNonQuery("Adding command player_repboost", "INSERT INTO `adkats_commands` VALUES(69, 'Invisible', 'player_repboost', 'Log', 'Boost Player Reputation', 'rboost', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(70)) { SendNonQuery("Adding command player_log", "INSERT INTO `adkats_commands` VALUES(70, 'Active', 'player_log', 'Log', 'Log Player Information', 'log', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(71)) { SendNonQuery("Adding command player_whitelistping", "INSERT INTO `adkats_commands` VALUES(71, 'Active', 'player_whitelistping', 'Log', 'Ping Whitelist Player', 'pwhitelist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(72)) { SendNonQuery("Adding command player_ban_temp_old", "INSERT INTO `adkats_commands` VALUES(72, 'Invisible', 'player_ban_temp_old', 'Log', 'Previous Temp Ban', 'pretban', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(73)) { SendNonQuery("Adding command player_ban_perm_old", "INSERT INTO `adkats_commands` VALUES(73, 'Invisible', 'player_ban_perm_old', 'Log', 'Previous Perm Ban', 'preban', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(74)) { SendNonQuery("Adding command player_pm_send", "INSERT INTO `adkats_commands` VALUES(74, 'Active', 'player_pm_send', 'Unable', 'Player Private Message', 'msg', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(75)) { SendNonQuery("Adding command player_pm_reply", "INSERT INTO `adkats_commands` VALUES(75, 'Active', 'player_pm_reply', 'Unable', 'Player Private Reply', 'r', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(76)) { SendNonQuery("Adding command admin_pm_send", "INSERT INTO `adkats_commands` VALUES(76, 'Active', 'admin_pm_send', 'Unable', 'Admin Private Message', 'adminmsg', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(77)) { SendNonQuery("Adding command player_whitelistaa", "INSERT INTO `adkats_commands` VALUES(77, 'Active', 'player_whitelistaa', 'Log', 'AA Whitelist Player', 'aawhitelist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(78)) { SendNonQuery("Adding command self_surrender", "INSERT INTO `adkats_commands` VALUES(78, 'Active', 'self_surrender', 'Log', 'Vote Surrender', 'surrender', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(79)) { SendNonQuery("Adding command self_votenext", "INSERT INTO `adkats_commands` VALUES(79, 'Active', 'self_votenext', 'Log', 'Vote Next Round', 'votenext', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(80)) { SendNonQuery("Adding command self_reportlist", "INSERT INTO `adkats_commands` VALUES(80, 'Active', 'self_reportlist', 'Log', 'List player reports', 'reportlist', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(81)) { SendNonQuery("Adding command plugin_restart", "INSERT INTO `adkats_commands` VALUES(81, 'Active', 'plugin_restart', 'Log', 'Restart AdKats', 'prestart', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(82)) { SendNonQuery("Adding command server_shutdown", "INSERT INTO `adkats_commands` VALUES(82, 'Active', 'server_shutdown', 'Log', 'Shutdown Server', 'shutdown', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(83)) { SendNonQuery("Adding command self_nosurrender", "INSERT INTO `adkats_commands` VALUES(83, 'Active', 'self_nosurrender', 'Log', 'Vote Against Surrender', 'nosurrender', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(84)) { SendNonQuery("Adding command player_whitelistspambot", "INSERT INTO `adkats_commands` VALUES(84, 'Active', 'player_whitelistspambot', 'Log', 'SpamBot Whitelist Player', 'spamwhitelist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(85)) { SendNonQuery("Adding command player_pm_start", "INSERT INTO `adkats_commands` VALUES(85, 'Invisible', 'player_pm_start', 'Log', 'Player Private Message Start', 'pmstart', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(86)) { SendNonQuery("Adding command player_pm_transmit", "INSERT INTO `adkats_commands` VALUES(86, 'Invisible', 'player_pm_transmit', 'Log', 'Player Private Message Transmit', 'pmtransmit', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(87)) { SendNonQuery("Adding command player_pm_cancel", "INSERT INTO `adkats_commands` VALUES(87, 'Invisible', 'player_pm_cancel', 'Log', 'Player Private Message Cancel', 'pmcancel', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(88)) { SendNonQuery("Adding command player_population_success", "INSERT INTO `adkats_commands` VALUES(88, 'Invisible', 'player_population_success', 'Log', 'Player Successfully Populated Server', 'popsuccess', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(89)) { SendNonQuery("Adding command server_map_detriment", "INSERT INTO `adkats_commands` VALUES(89, 'Invisible', 'server_map_detriment', 'Log', 'Map Detriment Log', 'mapdetriment', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(90)) { SendNonQuery("Adding command server_map_benefit", "INSERT INTO `adkats_commands` VALUES(90, 'Invisible', 'server_map_benefit', 'Log', 'Map Benefit Log', 'mapbenefit', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(91)) { SendNonQuery("Adding command plugin_update", "INSERT INTO `adkats_commands` VALUES(91, 'Active', 'plugin_update', 'Unable', 'Update AdKats', 'pupdate', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(92)) { SendNonQuery("Adding command player_warn", "INSERT INTO `adkats_commands` VALUES(92, 'Active', 'player_warn', 'Log', 'Warn Player', 'warn', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(93)) { SendNonQuery("Adding command server_countdown", "INSERT INTO `adkats_commands` VALUES(93, 'Active', 'server_countdown', 'Log', 'Run Countdown', 'cdown', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(94)) { SendNonQuery("Adding command player_whitelistreport", "INSERT INTO `adkats_commands` VALUES(94, 'Active', 'player_whitelistreport', 'Log', 'Report Whitelist Player', 'rwhitelist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(95)) { SendNonQuery("Adding command player_whitelistreport_remove", "INSERT INTO `adkats_commands` VALUES(95, 'Active', 'player_whitelistreport_remove', 'Log', 'Remove Report Whitelist', 'unrwhitelist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(96)) { SendNonQuery("Adding command player_whitelistspambot_remove", "INSERT INTO `adkats_commands` VALUES(96, 'Active', 'player_whitelistspambot_remove', 'Log', 'Remove SpamBot Whitelist', 'unspamwhitelist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(97)) { SendNonQuery("Adding command player_whitelistaa_remove", "INSERT INTO `adkats_commands` VALUES(97, 'Active', 'player_whitelistaa_remove', 'Log', 'Remove AA Whitelist', 'unaawhitelist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(98)) { SendNonQuery("Adding command player_whitelistping_remove", "INSERT INTO `adkats_commands` VALUES(98, 'Active', 'player_whitelistping_remove', 'Log', 'Remove Ping Whitelist', 'unpwhitelist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(99)) { SendNonQuery("Adding command player_whitelistanticheat_remove", "INSERT INTO `adkats_commands` VALUES(99, 'Active', 'player_whitelistanticheat_remove', 'Log', 'Remove AntiCheat Whitelist', 'unacwhitelist', TRUE, 'Any')", true); newCommands = true; } if (SendQuery("SELECT command_id FROM adkats_commands WHERE command_key = 'player_whitelisthackerchecker_remove'", false)) { Log.Info("Updating command player_whitelisthackerchecker_remove to new definition player_whitelistanticheat_remove."); SendNonQuery("Updating command player_whitelisthackerchecker_remove command_text to new definition.", "UPDATE adkats_commands SET adkats_commands.command_text = 'unacwhitelist' WHERE command_id = 99", true); SendNonQuery("Updating command player_whitelisthackerchecker_remove command_name to new definition.", "UPDATE adkats_commands SET adkats_commands.command_name = 'Remove AntiCheat Whitelist' WHERE command_id = 99", true); SendNonQuery("Updating command player_whitelisthackerchecker_remove command_key to new definition.", "UPDATE adkats_commands SET adkats_commands.command_key = 'player_whitelistanticheat_remove' WHERE command_id = 99", true); } if (!_CommandIDDictionary.ContainsKey(100)) { SendNonQuery("Adding command player_slotspectator_remove", "INSERT INTO `adkats_commands` VALUES(100, 'Active', 'player_slotspectator_remove', 'Log', 'Remove Spectator Slot', 'unspectator', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(101)) { SendNonQuery("Adding command player_slotreserved_remove", "INSERT INTO `adkats_commands` VALUES(101, 'Active', 'player_slotreserved_remove', 'Log', 'Remove Reserved Slot', 'unreserved', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(102)) { SendNonQuery("Adding command player_whitelistbalance_remove", "INSERT INTO `adkats_commands` VALUES(102, 'Active', 'player_whitelistbalance_remove', 'Log', 'Remove Autobalance Whitelist', 'unmbwhitelist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(103)) { SendNonQuery("Adding command player_blacklistdisperse_remove", "INSERT INTO `adkats_commands` VALUES(103, 'Active', 'player_blacklistdisperse_remove', 'Log', 'Remove Autobalance Dispersion', 'undisperse', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(104)) { SendNonQuery("Adding command player_whitelistpopulator", "INSERT INTO `adkats_commands` VALUES(104, 'Active', 'player_whitelistpopulator', 'Log', 'Populator Whitelist Player', 'popwhitelist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(105)) { SendNonQuery("Adding command player_whitelistpopulator_remove", "INSERT INTO `adkats_commands` VALUES(105, 'Active', 'player_whitelistpopulator_remove', 'Log', 'Remove Populator Whitelist', 'unpopwhitelist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(106)) { SendNonQuery("Adding command player_whitelistteamkill", "INSERT INTO `adkats_commands` VALUES(106, 'Active', 'player_whitelistteamkill', 'Log', 'TeamKillTracker Whitelist Player', 'tkwhitelist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(107)) { SendNonQuery("Adding command player_whitelistteamkill_remove", "INSERT INTO `adkats_commands` VALUES(107, 'Active', 'player_whitelistteamkill_remove', 'Log', 'Remove TeamKillTracker Whitelist', 'untkwhitelist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(108)) { SendNonQuery("Adding command self_assist_unconfirmed", "INSERT INTO `adkats_commands` VALUES(108, 'Invisible', 'self_assist_unconfirmed', 'Log', 'Unconfirmed Assist', 'uassist', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(109)) { SendNonQuery("Adding command player_blacklistspectator", "INSERT INTO `adkats_commands` VALUES(109, 'Active', 'player_blacklistspectator', 'Log', 'Spectator Blacklist Player', 'specblacklist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(110)) { SendNonQuery("Adding command player_blacklistspectator_remove", "INSERT INTO `adkats_commands` VALUES(110, 'Active', 'player_blacklistspectator_remove', 'Log', 'Remove Spectator Blacklist', 'unspecblacklist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(111)) { SendNonQuery("Adding command player_blacklistreport", "INSERT INTO `adkats_commands` VALUES(111, 'Active', 'player_blacklistreport', 'Log', 'Report Source Blacklist', 'rblacklist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(112)) { SendNonQuery("Adding command player_blacklistreport_remove", "INSERT INTO `adkats_commands` VALUES(112, 'Active', 'player_blacklistreport_remove', 'Log', 'Remove Report Source Blacklist', 'unrblacklist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(113)) { SendNonQuery("Adding command player_whitelistcommand", "INSERT INTO `adkats_commands` VALUES(113, 'Active', 'player_whitelistcommand', 'Log', 'Command Target Whitelist', 'cwhitelist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(114)) { SendNonQuery("Adding command player_whitelistcommand_remove", "INSERT INTO `adkats_commands` VALUES(114, 'Active', 'player_whitelistcommand_remove', 'Log', 'Remove Command Target Whitelist', 'uncwhitelist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(115)) { SendNonQuery("Adding command player_blacklistautoassist", "INSERT INTO `adkats_commands` VALUES(115, 'Active', 'player_blacklistautoassist', 'Log', 'Auto-Assist Blacklist', 'auablacklist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(116)) { SendNonQuery("Adding command player_blacklistautoassist_remove", "INSERT INTO `adkats_commands` VALUES(116, 'Active', 'player_blacklistautoassist_remove', 'Log', 'Remove Auto-Assist Blacklist', 'unauablacklist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(117)) { SendNonQuery("Adding command player_isadmin", "INSERT INTO `adkats_commands` VALUES(117, 'Active', 'player_isadmin', 'Log', 'Fetch Admin Status', 'isadmin', FALSE, 'AnyHidden')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(118)) { SendNonQuery("Adding command self_feedback", "INSERT INTO `adkats_commands` VALUES(118, 'Active', 'self_feedback', 'Log', 'Give Server Feedback', 'feedback', FALSE, 'AnyHidden')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(119)) { SendNonQuery("Adding command player_loadout", "INSERT INTO `adkats_commands` VALUES(119, 'Active', 'player_loadout', 'Log', 'Fetch Player Loadout', 'loadout', FALSE, 'AnyHidden')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(120)) { SendNonQuery("Adding command player_loadout_force", "INSERT INTO `adkats_commands` VALUES(120, 'Active', 'player_loadout_force', 'Log', 'Force Player Loadout', 'floadout', TRUE, 'AnyHidden')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(121)) { SendNonQuery("Adding command self_battlecry", "INSERT INTO `adkats_commands` VALUES(121, 'Active', 'self_battlecry', 'Log', 'Set Own Battlecry', 'battlecry', FALSE, 'AnyHidden')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(122)) { SendNonQuery("Adding command player_battlecry", "INSERT INTO `adkats_commands` VALUES(122, 'Active', 'player_battlecry', 'Log', 'Set Player Battlecry', 'setbattlecry', TRUE, 'AnyHidden')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(123)) { SendNonQuery("Adding command player_perks", "INSERT INTO `adkats_commands` VALUES(123, 'Active', 'player_perks', 'Log', 'Fetch Player Perks', 'perks', FALSE, 'Any')", true); newCommands = true; } SendNonQuery("Updating command 123 player interaction", "UPDATE `adkats_commands` SET `command_playerInteraction` = 0 WHERE `command_id` = 123", false); if (!_CommandIDDictionary.ContainsKey(124)) { SendNonQuery("Adding command player_ping", "INSERT INTO `adkats_commands` VALUES(124, 'Active', 'player_ping', 'Log', 'Fetch Player Ping', 'ping', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(125)) { SendNonQuery("Adding command player_forceping", "INSERT INTO `adkats_commands` VALUES(125, 'Active', 'player_forceping', 'Log', 'Force Manual Player Ping', 'fping', TRUE, 'AnyHidden')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(126)) { SendNonQuery("Adding command player_debugassist", "INSERT INTO `adkats_commands` VALUES(126, 'Active', 'player_debugassist', 'Log', 'Debug Assist Losing Team', 'debugassist', FALSE, 'AnyHidden')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(127)) { SendNonQuery("Adding command player_changetag", "INSERT INTO `adkats_commands` VALUES(127, 'Invisible', 'player_changetag', 'Mandatory', 'Player Changed Clan Tag', 'changetag', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(128)) { SendNonQuery("Adding command player_discordlink", "INSERT INTO `adkats_commands` VALUES(128, 'Active', 'player_discordlink', 'Log', 'Link Player to Discord Member', 'discordlink', TRUE, 'AnyHidden')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(129)) { SendNonQuery("Adding command player_blacklistallcaps", "INSERT INTO `adkats_commands` VALUES(129, 'Active', 'player_blacklistallcaps', 'Log', 'All-Caps Chat Blacklist', 'allcapsblacklist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(130)) { SendNonQuery("Adding command player_blacklistallcaps_remove", "INSERT INTO `adkats_commands` VALUES(130, 'Active', 'player_blacklistallcaps_remove', 'Log', 'Remove All-Caps Chat Blacklist', 'unallcapsblacklist', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(131)) { SendNonQuery("Adding command poll_trigger", "INSERT INTO `adkats_commands` VALUES(131, 'Active', 'poll_trigger', 'Log', 'Trigger Poll', 'poll', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(132)) { SendNonQuery("Adding command poll_vote", "INSERT INTO `adkats_commands` VALUES(132, 'Active', 'poll_vote', 'Log', 'Vote In Poll', 'vote', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(133)) { SendNonQuery("Adding command poll_cancel", "INSERT INTO `adkats_commands` VALUES(133, 'Active', 'poll_cancel', 'Unable', 'Cancel Active Poll', 'pollcancel', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(134)) { SendNonQuery("Adding command poll_complete", "INSERT INTO `adkats_commands` VALUES(134, 'Active', 'poll_complete', 'Unable', 'Complete Active Poll', 'pollcomplete', FALSE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(135)) { SendNonQuery("Adding command server_nuke_winning", "INSERT INTO `adkats_commands` VALUES(135, 'Active', 'server_nuke_winning', 'Log', 'Server Nuke Winning Team', 'wnuke', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(136)) { SendNonQuery("Adding command player_loadout_ignore", "INSERT INTO `adkats_commands` VALUES(136, 'Active', 'player_loadout_ignore', 'Log', 'Ignore Player Loadout', 'iloadout', TRUE, 'AnyHidden')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(137)) { SendNonQuery("Adding command player_challenge_play", "INSERT INTO `adkats_commands` VALUES(137, 'Active', 'player_challenge_play', 'Log', 'Challenge Playing Status', 'challengeplay', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(138)) { SendNonQuery("Adding command player_challenge_ignore", "INSERT INTO `adkats_commands` VALUES(138, 'Active', 'player_challenge_ignore', 'Log', 'Challenge Ignoring Status', 'challengeignore', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(139)) { SendNonQuery("Adding command self_challenge", "INSERT INTO `adkats_commands` VALUES(139, 'Active', 'self_challenge', 'Log', 'Challenge', 'ch', FALSE, 'Any')", true); newCommands = true; } else if (_CommandIDDictionary[139].command_text == "challenge") { SendNonQuery("Updating command 139 text", "UPDATE `adkats_commands` SET `command_text` = 'ch' WHERE `command_id` = 139", false); } if (!_CommandIDDictionary.ContainsKey(140)) { SendNonQuery("Adding command player_challenge_autokill", "INSERT INTO `adkats_commands` VALUES(140, 'Active', 'player_challenge_autokill', 'Log', 'Challenge AutoKill Status', 'challengeautokill', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(141)) { SendNonQuery("Adding command player_challenge_autokill_remove", "INSERT INTO `adkats_commands` VALUES(141, 'Active', 'player_challenge_autokill_remove', 'Log', 'Remove Challenge AutoKill Status', 'unchallengeautokill', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(142)) { SendNonQuery("Adding command player_challenge_play_remove", "INSERT INTO `adkats_commands` VALUES(142, 'Active', 'player_challenge_play_remove', 'Log', 'Remove Challenge Playing Status', 'unchallengeplay', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(143)) { SendNonQuery("Adding command player_challenge_ignore_remove", "INSERT INTO `adkats_commands` VALUES(143, 'Active', 'player_challenge_ignore_remove', 'Log', 'Remove Challenge Ignoring Status', 'unchallengeignore', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(144)) { SendNonQuery("Adding command player_challenge_complete", "INSERT INTO `adkats_commands` VALUES(144, 'Invisible', 'player_challenge_complete', 'Log', 'Player Completed Challenge', 'challengecomplete', TRUE, 'Any')", true); newCommands = true; } if (!_CommandIDDictionary.ContainsKey(145)) { SendNonQuery("Adding command player_report_expire", "INSERT INTO `adkats_commands` VALUES(145, 'Invisible', 'player_report_expire', 'Log', 'Report Player (Expired)', 'expirereport', TRUE, 'Any')", true); newCommands = true; } if (newCommands) { FetchCommands(); return; } } else { Log.Error("Commands could not be fetched."); } //Update functions for command timeouts UpdateCommandTimeouts(); } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching commands from database.", e)); } if (displayUpdate) { UpdateSettingPage(); } Log.Debug(() => "fetchCommands finished!", 6); } private void FillCommandDescDictionary() { _CommandDescriptionDictionary["command_confirm"] = "Command used to confirm actions. No parameters."; _CommandDescriptionDictionary["command_cancel"] = "Command used to cancel actions. No parameters."; _CommandDescriptionDictionary["player_kill"] = "Kills a player, queues for kill on spawn if dead. Requires a reason."; _CommandDescriptionDictionary["player_kill_lowpop"] = "Invisible command. Assigned when a player_punish is issued during low population."; _CommandDescriptionDictionary["player_kill_repeat"] = "Invisible command. Assigned when a player_kill is issued and player is dead."; _CommandDescriptionDictionary["player_kick"] = "Kicks a player from the server. Requires a reason."; _CommandDescriptionDictionary["player_ban_temp"] = "Temporarily bans a player from the server for the given time. Requires a reason."; _CommandDescriptionDictionary["player_ban_perm"] = "Permanently bans a player from the server. Requires a reason."; _CommandDescriptionDictionary["player_punish"] = "Increases infraction points, then punishes the player. Requires a reason."; _CommandDescriptionDictionary["player_forgive"] = "Decreases infraction points and informs the player. Requires a reason."; _CommandDescriptionDictionary["player_mute"] = "Mutes a player for the current round. Talking will cause punishment. Requires a reason."; _CommandDescriptionDictionary["player_join"] = "Switches you to a players squad if there is room."; _CommandDescriptionDictionary["player_roundwhitelist"] = "DISABLED COMMAND"; _CommandDescriptionDictionary["player_move"] = "When the player dies it queues them to switch teams when a slot is available."; _CommandDescriptionDictionary["player_fmove"] = "Immediately queues the player to switch teams when a slot is available."; _CommandDescriptionDictionary["self_teamswap"] = "Immediately queues you to switch teams when a slot is available."; _CommandDescriptionDictionary["self_kill"] = "Makes you commit suicide and regret your existence."; _CommandDescriptionDictionary["player_report"] = "Reports a player to admins. Requires a reason."; _CommandDescriptionDictionary["player_report_confirm"] = "Invisible command. Assigned when an admin confirms a report."; _CommandDescriptionDictionary["player_calladmin"] = "Calls admin on a player. Requires a reason."; _CommandDescriptionDictionary["admin_say"] = "Sends a chat message to the whole server."; _CommandDescriptionDictionary["player_say"] = "Sends a chat message to a particular player."; _CommandDescriptionDictionary["admin_yell"] = "Sends a yell message to the whole server."; _CommandDescriptionDictionary["player_yell"] = "Sends a yell message to a particular player."; _CommandDescriptionDictionary["admin_tell"] = "Sends both a chat message and yell message to the whole server."; _CommandDescriptionDictionary["player_tell"] = "Sends both a chat message and yell message to a particular player."; _CommandDescriptionDictionary["self_whatis"] = "Tells you what a command or pre-message ID means."; _CommandDescriptionDictionary["self_voip"] = "Tells you what the VOIP address for this server is."; _CommandDescriptionDictionary["self_rules"] = "Tells you what the server rules are."; _CommandDescriptionDictionary["round_restart"] = "Restarts the current round. All players keep their points."; _CommandDescriptionDictionary["round_next"] = "Runs the next round/map in line. All players keep their points."; _CommandDescriptionDictionary["round_end"] = "Ends the current round with a decided winner."; _CommandDescriptionDictionary["server_nuke"] = "Kills all players in the decided subset."; _CommandDescriptionDictionary["server_kickall"] = "Kicks all non-admins from the server."; _CommandDescriptionDictionary["adkats_exception"] = "Invisible command. Issued by AdKats to log exceptions."; _CommandDescriptionDictionary["banenforcer_enforce"] = "Invisible command. Issued by BanEnforcer when a player's ban is enforced."; _CommandDescriptionDictionary["player_unban"] = "Searches for banned players with the given name, and gives the option to unban."; _CommandDescriptionDictionary["self_admins"] = "Tells you the list of online admins."; _CommandDescriptionDictionary["self_lead"] = "Gives the target leader of their current squad. No parameters to target yourself."; _CommandDescriptionDictionary["admin_accept"] = "Accepts the given report ID. Takes no action against the target player."; _CommandDescriptionDictionary["admin_deny"] = "Denys the given report ID. Reduces the reporter's reputation."; _CommandDescriptionDictionary["player_report_deny"] = "Invisible command. Assigned when an admin denies a report."; _CommandDescriptionDictionary["server_swapnuke"] = "Queues all players to switch teams immediately."; _CommandDescriptionDictionary["player_blacklistdisperse"] = "Adds the target player to even dispersion for the server."; _CommandDescriptionDictionary["player_whitelistbalance"] = "Adds the target player to autobalance whitelist for the server."; _CommandDescriptionDictionary["player_slotreserved"] = "Adds the target player to reserved slots for the server."; _CommandDescriptionDictionary["player_slotspectator"] = "Adds the target player to spectator slots for the server."; _CommandDescriptionDictionary["player_changename"] = "Invisible command. Issued when a player changes their name."; _CommandDescriptionDictionary["player_changeip"] = "Invisible command. Issued when a player changes location/IP."; _CommandDescriptionDictionary["player_ban_perm_future"] = "Future-permaban, inverse of a temp-ban. Requires a reason."; _CommandDescriptionDictionary["self_assist"] = "Queues you to assist the losing team."; _CommandDescriptionDictionary["self_uptime"] = "Tells you the uptime/population information for the server."; _CommandDescriptionDictionary["self_contest"] = "Contests any current report against you. Admins must act manually on the report afterward."; _CommandDescriptionDictionary["player_kill_force"] = "Immediately kills the target player, avoids all other player_kill logic."; _CommandDescriptionDictionary["player_info"] = "Returns all known information about the player."; _CommandDescriptionDictionary["player_dequeue"] = "Cancels any queued action on a player."; _CommandDescriptionDictionary["self_help"] = "Tells you all commands your user role can access."; _CommandDescriptionDictionary["player_find"] = "Target a player to fetch their team, position, and current score."; _CommandDescriptionDictionary["server_afk"] = "Calls the AFK Manager logic to remove AFK players from the server."; _CommandDescriptionDictionary["player_pull"] = "Pulls a player to your squad, killing them in the process."; _CommandDescriptionDictionary["admin_ignore"] = "Ignores the given report ID. Takes no action against the target or source player."; _CommandDescriptionDictionary["player_mark"] = "Marks a player for notification if they leave the server."; _CommandDescriptionDictionary["player_chat"] = "Fetches player or conversation chat history."; _CommandDescriptionDictionary["player_whitelistanticheat"] = "Whitelists the target player from AntiCheat, and unbans them if necessary."; _CommandDescriptionDictionary["player_lock"] = "Temporarily locks a player from admin commands."; _CommandDescriptionDictionary["player_unlock"] = "Removes command lock from a player."; _CommandDescriptionDictionary["self_rep"] = "Returns your current server reputation."; _CommandDescriptionDictionary["player_repboost"] = "Invisible command. Boosts player rep for a given reason."; _CommandDescriptionDictionary["player_log"] = "Logs the given information to the player's record."; _CommandDescriptionDictionary["player_whitelistping"] = "Whitelists a player from ping kick."; _CommandDescriptionDictionary["player_ban_temp_old"] = "Invisible command. Set to all disabled temp-bans."; _CommandDescriptionDictionary["player_ban_perm_old"] = "Invisible command. Set to all disabled permabans."; _CommandDescriptionDictionary["player_pm_send"] = "Sends a private message to the targeted player."; _CommandDescriptionDictionary["player_pm_reply"] = "Replies to the current private message."; _CommandDescriptionDictionary["admin_pm_send"] = "Sends a private message to all online admins."; _CommandDescriptionDictionary["player_whitelistaa"] = "Whitelists a player for Admin Assistant status."; _CommandDescriptionDictionary["self_surrender"] = "Votes to end the round with current winning team as winner, then start the next."; _CommandDescriptionDictionary["self_votenext"] = "Votes to end the round with current winning team as winner, then start the next."; _CommandDescriptionDictionary["self_reportlist"] = "Lists the latest unused player reports."; _CommandDescriptionDictionary["plugin_restart"] = "Reboots AdKats."; _CommandDescriptionDictionary["self_nosurrender"] = "Votes against ending the round with surrender."; _CommandDescriptionDictionary["player_whitelistspambot"] = "Whitelists a player from seeing any messages from the SpamBot."; _CommandDescriptionDictionary["plugin_update"] = "Updates AdKats."; _CommandDescriptionDictionary["player_warn"] = "Warns a player. Requires a reason."; _CommandDescriptionDictionary["server_countdown"] = "Sends a visible countdown to all players in the given subset."; _CommandDescriptionDictionary["player_whitelistreport"] = "Whitelists a player from being reported."; _CommandDescriptionDictionary["player_whitelistreport_remove"] = "Removes a player from report whitelist."; _CommandDescriptionDictionary["player_whitelistspambot_remove"] = "Removes a player from SpamBot whitelist."; _CommandDescriptionDictionary["player_whitelistaa_remove"] = "Removes a player from Admin Assistant whitelist."; _CommandDescriptionDictionary["player_whitelistping_remove"] = "Removes a player from Ping whitelist."; _CommandDescriptionDictionary["player_whitelistanticheat_remove"] = "Removes a player from AntiCheat whitelist."; _CommandDescriptionDictionary["player_slotspectator_remove"] = "Removes a player from spectator slot list."; _CommandDescriptionDictionary["player_slotreserved_remove"] = "Removes a player from reserved slot list."; _CommandDescriptionDictionary["player_whitelistbalance_remove"] = "Removes a player from autobalance whitelist."; _CommandDescriptionDictionary["player_blacklistdisperse_remove"] = "Removes a player from autobalance dispersion."; _CommandDescriptionDictionary["player_whitelistpopulator"] = "Whitelists a player to be considered a populator."; _CommandDescriptionDictionary["player_whitelistpopulator_remove"] = "Removes a player from the populator whitelist."; _CommandDescriptionDictionary["player_whitelistteamkill"] = "Whitelists a player from being acted on by TeamKillTracker."; _CommandDescriptionDictionary["player_whitelistteamkill_remove"] = "Removes a player from TeamKillTracker whitelist."; _CommandDescriptionDictionary["player_blacklistspectator"] = "A player under spectator blacklist cannot join as a spectator."; _CommandDescriptionDictionary["player_blacklistspectator_remove"] = "Removes a player from the spectator blacklist."; _CommandDescriptionDictionary["player_blacklistreport"] = "A player under report source blacklist cannot use the report/calladmin commands."; _CommandDescriptionDictionary["player_blacklistreport_remove"] = "Removes a player from the report source blacklist."; _CommandDescriptionDictionary["player_whitelistcommand"] = "A player under command target whitelist cannot be targeted by certain admin commands."; _CommandDescriptionDictionary["player_whitelistcommand_remove"] = "Removes a player from the command target whitelist."; _CommandDescriptionDictionary["player_blacklistautoassist"] = "A player under auto-assist blacklist is automatically @assist'd when baserape starts."; _CommandDescriptionDictionary["player_blacklistautoassist_remove"] = "Removes a player from the auto-assist blacklist."; _CommandDescriptionDictionary["player_isadmin"] = "Fetches a player's admin status."; _CommandDescriptionDictionary["self_feedback"] = "Logs feedback for the server."; _CommandDescriptionDictionary["player_loadout"] = "Returns a player's loadout if AdKatsLRT is installed and integrated."; _CommandDescriptionDictionary["player_loadout_force"] = "If AdKatsLRT is installed the targeted player is elevated to trigger level loadout enforcement."; _CommandDescriptionDictionary["self_battlecry"] = "Sets a new battlecry for your player."; _CommandDescriptionDictionary["player_battlecry"] = "Sets a new battlecry for the given player."; _CommandDescriptionDictionary["player_perks"] = "Displays the active perks a player has, and how long until those perks expire."; _CommandDescriptionDictionary["player_ping"] = "Fetches a player's current ping, either from the server or manually if necessary."; _CommandDescriptionDictionary["player_forceping"] = "Forces AdKats to manually ping a player instead of using the server ping."; _CommandDescriptionDictionary["player_debugassist"] = "Runs a mock assist command on a player to see what the results would be, along with debug information."; _CommandDescriptionDictionary["player_changetag"] = "Invisible command. Issued when a player changes their clan tag."; _CommandDescriptionDictionary["player_discordlink"] = "Links a player with a currently online discord member."; _CommandDescriptionDictionary["player_blacklistallcaps"] = "Adds the target player to all-caps chat blacklist for the server."; _CommandDescriptionDictionary["player_blacklistallcaps_remove"] = "Removes a player from the all-caps chat blacklist."; _CommandDescriptionDictionary["poll_trigger"] = "Starts a poll of the given type."; _CommandDescriptionDictionary["poll_vote"] = "Votes in the currently active poll."; _CommandDescriptionDictionary["poll_cancel"] = "Cancels the current active poll without running its completion action."; _CommandDescriptionDictionary["poll_complete"] = "Completes the current active poll and runs its action."; _CommandDescriptionDictionary["server_nuke_winning"] = "Kills all players on the winning team."; _CommandDescriptionDictionary["player_loadout_ignore"] = "If AdKatsLRT is installed the targeted player is temporarily ignored for loadout enforcement."; _CommandDescriptionDictionary["player_challenge_play"] = "A player under challenge playing status will be automatically enrolled in any active challenge if auto-assign is disabled."; _CommandDescriptionDictionary["player_challenge_ignore"] = "A player under under challenge ignoring status will not be shown any challenge related messages."; _CommandDescriptionDictionary["self_challenge"] = "Personal control command for the challenge system."; _CommandDescriptionDictionary["player_challenge_autokill"] = "A player under challenge autokill status will be automatically slain when a challenge weapon is completed."; _CommandDescriptionDictionary["player_challenge_autokill_remove"] = "Removes a player from challenge autokill status."; _CommandDescriptionDictionary["player_challenge_play_remove"] = "Removes a player from challenge playing status."; _CommandDescriptionDictionary["player_challenge_ignore_remove"] = "Removes a player from challenge ignoring status."; _CommandDescriptionDictionary["player_report_expire"] = "Invisible command. Assigned when a reported player leaves the server without the report being acted on."; } private void FillReadableMapModeDictionaries() { try { ReadableMaps.Clear(); ReadableModes.Clear(); foreach (CMap m in this.GetMapDefines()) { if (!ReadableMaps.ContainsKey(m.FileName)) { ReadableMaps[m.FileName] = m.PublicLevelName; } if (!ReadableModes.ContainsKey(m.PlayList)) { ReadableModes[m.PlayList] = m.GameMode; } } Log.Debug(() => "Readable maps/modes filled", 6); } catch (Exception e) { Log.HandleException(new AException("Error while filling map/mode dictionaries.", e)); } } private String GetCurrentReadableMap() { try { if (_serverInfo != null && _serverInfo.InfoObject != null && !String.IsNullOrEmpty(_serverInfo.InfoObject.Map)) { return GetReadableMap(_serverInfo.InfoObject.Map); } } catch (Exception e) { Log.HandleException(new AException("Error getting current readable map.", e)); } return "Unknown"; } private String GetReadableMap(String mapKey) { try { String map = mapKey; ReadableMaps.TryGetValue(mapKey, out map); return map; } catch (Exception e) { Log.HandleException(new AException("Error getting readable map.", e)); } return "Unknown"; } private String GetCurrentReadableMode() { try { if (_serverInfo != null && _serverInfo.InfoObject != null && !String.IsNullOrEmpty(_serverInfo.InfoObject.GameMode)) { return GetReadableMode(_serverInfo.InfoObject.GameMode); } } catch (Exception e) { Log.HandleException(new AException("Error getting current readable mode.", e)); } return "Unknown"; } private String GetReadableMode(String modeKey) { try { String mode = modeKey; ReadableMaps.TryGetValue(modeKey, out mode); return mode; } catch (Exception e) { Log.HandleException(new AException("Error getting readable mode.", e)); } return "Unknown"; } private void UpdateCommandTimeouts() { _commandTimeoutDictionary["self_rules"] = (plugin => (plugin._ServerRulesList.Count() * plugin._ServerRulesInterval)); _commandTimeoutDictionary["player_punish"] = (plugin => (18)); _commandTimeoutDictionary["player_kick"] = (plugin => (45)); _commandTimeoutDictionary["player_blacklistdisperse"] = (plugin => (30)); _commandTimeoutDictionary["player_ban_temp"] = (plugin => (30)); _commandTimeoutDictionary["player_ban_perm"] = (plugin => (90)); _commandTimeoutDictionary["player_ban_perm_future"] = (plugin => (90)); _commandTimeoutDictionary["player_report"] = (plugin => (10)); _commandTimeoutDictionary["self_kill"] = (plugin => (10 * 60)); } private void FetchRoles() { Log.Debug(() => "fetchRoles starting!", 6); Boolean displayUpdate = false; if (_databaseConnectionCriticalState) { return; } try { lock (_RoleIDDictionary) { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { const string sql = @" SELECT `role_id`, `role_key`, `role_name` FROM `adkats_roles`"; command.CommandText = sql; HashSet validIDs = new HashSet(); using (MySqlDataReader reader = SafeExecuteReader(command)) { _RoleKeyDictionary.Clear(); _RoleNameDictionary.Clear(); while (reader.Read()) { if (!_pluginEnabled) { return; } long roleID = reader.GetInt64("role_id"); string roleKey = reader.GetString("role_key"); string roleName = reader.GetString("role_name"); validIDs.Add(roleID); ARole currentRole; if (_RoleIDDictionary.TryGetValue(roleID, out currentRole)) { if (currentRole.role_key != roleKey) { Log.Info(currentRole.role_key + " role key being changed from " + currentRole.role_key + " to " + roleKey); currentRole.role_key = roleKey; displayUpdate = true; } if (currentRole.role_name != roleName) { Log.Info(currentRole.role_key + " role name being changed from " + currentRole.role_name + " to " + roleName); currentRole.role_name = roleName; displayUpdate = true; } } else { currentRole = new ARole { role_id = roleID, role_key = roleKey, role_name = roleName }; _RoleIDDictionary.Add(currentRole.role_id, currentRole); displayUpdate = true; } _RoleKeyDictionary.Add(currentRole.role_key, currentRole); _RoleNameDictionary.Add(currentRole.role_name, currentRole); } foreach (ARole remRole in _RoleIDDictionary.Values.Where(aRole => !validIDs.Contains(aRole.role_id)).ToList()) { if (!_pluginEnabled) { return; } Log.Info("Removing role " + remRole.role_key); _RoleIDDictionary.Remove(remRole.role_id); displayUpdate = true; } } } using (MySqlCommand command = connection.CreateCommand()) { const string sql = @" SELECT `role_id`, `command_id` FROM `adkats_rolecommands` ORDER BY `role_id` ASC"; command.CommandText = sql; using (MySqlDataReader reader = SafeExecuteReader(command)) { Dictionary> rIDcIDDictionary = new Dictionary>(); while (reader.Read()) { if (!_pluginEnabled) { return; } int roleID = reader.GetInt32("role_id"); long commandID = reader.GetInt64("command_id"); if (!rIDcIDDictionary.ContainsKey(roleID)) { rIDcIDDictionary[roleID] = new HashSet(); } rIDcIDDictionary[roleID].Add(commandID); } foreach (KeyValuePair> currentRoleElement in rIDcIDDictionary) { if (!_pluginEnabled) { return; } ARole aRole; Boolean uploadRequired = false; if (!_RoleIDDictionary.TryGetValue(currentRoleElement.Key, out aRole)) { Log.Warn("Role for ID " + currentRoleElement.Key + " not found in role dictionary when assigning commands."); continue; } foreach (long curCommandID in currentRoleElement.Value) { if (!_pluginEnabled) { return; } ACommand aCommand; if (!_CommandIDDictionary.TryGetValue(curCommandID, out aCommand)) { Log.Warn("Command for ID " + curCommandID + " not found in command dictionary when assigning commands."); uploadRequired = true; continue; } if (!aRole.RoleAllowedCommands.ContainsKey(aCommand.command_key) && aCommand.command_active == ACommand.CommandActive.Active) { //Conditional check for default guest admin commands if (aRole.role_key == "guest_default" && aCommand.command_playerInteraction) { Log.Warn("The guest role cannot have access to admin commands."); uploadRequired = true; continue; } aRole.RoleAllowedCommands[aCommand.command_key] = aCommand; } } KeyValuePair> element = currentRoleElement; foreach (ACommand remCommand in aRole.RoleAllowedCommands.Values.ToList().Where(remCommand => !element.Value.Contains(remCommand.command_id))) { if (!_pluginEnabled) { return; } switch (remCommand.command_key) { case "command_confirm": Log.Error("Confirm command cannot be denied for any role. [R]"); uploadRequired = true; continue; case "command_cancel": Log.Error("Cancel command cannot be denied for any role. [R]"); uploadRequired = true; continue; } Log.Info("Removing command " + remCommand.command_key + " from role " + aRole.role_key); aRole.RoleAllowedCommands.Remove(remCommand.command_key); uploadRequired = true; } //Confirm required commands if (aRole.RoleAllowedCommands.Values.All(aCommand => aCommand.command_key != "command_confirm")) { ACommand confirmCommand = GetCommandByKey("command_confirm"); if (confirmCommand != null) { Log.Error("Confirm command cannot be denied for any role. Reassigning."); aRole.RoleAllowedCommands[confirmCommand.command_key] = confirmCommand; uploadRequired = true; } } if (aRole.RoleAllowedCommands.Values.All(aCommand => aCommand.command_key != "command_cancel")) { ACommand cancelCommand = GetCommandByKey("command_cancel"); if (cancelCommand != null) { Log.Error("Cancel command cannot be denied for any role. Reassigning."); aRole.RoleAllowedCommands[cancelCommand.command_key] = cancelCommand; uploadRequired = true; } } FillConditionalAllowedCommands(aRole); //Calculate role power level aRole.role_powerLevel = aRole.RoleAllowedCommands.Values.Count(aCommand => !aCommand.command_playerInteraction) + (2 * aRole.RoleAllowedCommands.Values.Count(aCommand => aCommand.command_playerInteraction)); if (_CommandIDDictionary.Any() && uploadRequired) { QueueRoleForUpload(aRole); displayUpdate = true; } } } } if (_RoleIDDictionary.Count == 0) { Log.Error("Roles could not be fetched."); } //Fetch role groups using (MySqlCommand command = connection.CreateCommand()) { const string sql = @" SELECT `role_id`, `group_key` FROM `adkats_rolegroups` ORDER BY `role_id` ASC"; command.CommandText = sql; using (MySqlDataReader reader = SafeExecuteReader(command)) { Dictionary> rIDgKeyDictionary = new Dictionary>(); while (reader.Read()) { if (!_pluginEnabled) { return; } int roleID = reader.GetInt32("role_id"); String groupKey = reader.GetString("group_key"); HashSet setGroups; if (!rIDgKeyDictionary.TryGetValue(roleID, out setGroups)) { setGroups = new HashSet(); rIDgKeyDictionary.Add(roleID, setGroups); } setGroups.Add(groupKey); } foreach (KeyValuePair> currentRoleElement in rIDgKeyDictionary) { if (!_pluginEnabled) { return; } ARole aRole; Boolean uploadRequired = false; if (!_RoleIDDictionary.TryGetValue(currentRoleElement.Key, out aRole)) { Log.Warn("Role for ID " + currentRoleElement.Key + " not found in role dictionary when assigning groups."); continue; } foreach (String groupKey in currentRoleElement.Value) { if (!_pluginEnabled) { return; } ASpecialGroup aGroup; if (!_specialPlayerGroupKeyDictionary.TryGetValue(groupKey, out aGroup)) { Log.Warn("Group for key " + groupKey + " not found in group cache when assigning groups."); uploadRequired = true; continue; } if (!aRole.RoleSetGroups.ContainsKey(aGroup.group_key)) { if (aGroup.group_key == "whitelist_adminassistant" && RoleIsAdmin(aRole)) { // This role is not allowed for admins Log.Warn("Removing " + aGroup.group_name + " from " + aRole.role_name + ". That role is an admin role and cannot use " + aGroup.group_name + "."); uploadRequired = true; } else { aRole.RoleSetGroups.Add(aGroup.group_key, aGroup); } } } KeyValuePair> element = currentRoleElement; foreach (ASpecialGroup remGroup in aRole.RoleSetGroups.Values.ToList().Where(remGroup => !element.Value.Contains(remGroup.group_key))) { if (!_pluginEnabled) { return; } Log.Info("Removing group " + remGroup.group_key + " from role " + aRole.role_key); aRole.RoleAllowedCommands.Remove(remGroup.group_key); uploadRequired = true; } FillConditionalAllowedCommands(aRole); if (uploadRequired) { QueueRoleForUpload(aRole); displayUpdate = true; } } } } //Done with users } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching roles from database.", e)); } if (displayUpdate) { UpdateSettingPage(); } Log.Debug(() => "fetchRoles finished!", 6); } private void FillConditionalAllowedCommands(ARole aRole) { //Teamswap Command ACommand teamswapCommand; if (_CommandKeyDictionary.TryGetValue("self_teamswap", out teamswapCommand)) { if (!aRole.ConditionalAllowedCommands.ContainsKey(teamswapCommand.command_key)) { aRole.ConditionalAllowedCommands.Add(teamswapCommand.command_key, new KeyValuePair, ACommand>(TeamSwapFunc, teamswapCommand)); } } else { Log.Error("Unable to find teamswap command when assigning conditional commands."); } //Admins Command ACommand adminsCommand; if (_CommandKeyDictionary.TryGetValue("self_admins", out adminsCommand)) { if (!aRole.ConditionalAllowedCommands.ContainsKey(adminsCommand.command_key)) { aRole.ConditionalAllowedCommands.Add(adminsCommand.command_key, new KeyValuePair, ACommand>(AAPerkFunc, adminsCommand)); } } else { Log.Error("Unable to find teamswap command when assigning conditional commands."); } } private void FetchUserList() { Log.Debug(() => "fetchUserList starting!", 6); if (_databaseConnectionCriticalState) { return; } DateTime start = UtcNow(); try { if (!_firstUserListComplete) { OnlineAdminSayMessage("Fetching user list."); Log.Info("Fetching user list."); } if (!_firstUserListComplete && !SendQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE ( TABLE_SCHEMA = '" + _mySqlSchemaName + "' AND TABLE_NAME = 'adkats_users' AND COLUMN_NAME = 'user_expiration' )", false)) { SendNonQuery("Adding user expiration.", "ALTER TABLE `adkats_users` ADD COLUMN `user_expiration` DATETIME NOT NULL AFTER `user_role`", true); SendNonQuery("Adding initial user expiration values.", "UPDATE `adkats_users` SET `user_expiration` = DATE_ADD(UTC_TIMESTAMP(), INTERVAL 20 YEAR)", true); } if (!_firstUserListComplete && !SendQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE ( TABLE_SCHEMA = '" + _mySqlSchemaName + "' AND TABLE_NAME = 'adkats_users' AND COLUMN_NAME = 'user_notes' )", false)) { SendNonQuery("Adding user notes.", "ALTER TABLE `adkats_users` ADD COLUMN `user_notes` VARCHAR(1000) NOT NULL DEFAULT 'No Notes' AFTER `user_expiration`", true); } start = UtcNow(); using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT `adkats_users`.`user_id`, `adkats_users`.`user_name`, `adkats_users`.`user_email`, `adkats_users`.`user_phone`, `adkats_users`.`user_role`, `adkats_users`.`user_expiration`, `adkats_users`.`user_notes` FROM `adkats_users`"; List validIDs = new List(); using (MySqlDataReader reader = SafeExecuteReader(command)) { lock (_userCache) { while (reader.Read()) { if (!_pluginEnabled) { return; } int userID = reader.GetInt32("user_id"); //0 validIDs.Add(userID); string userName = reader.GetString("user_name"); //1 String userEmail = null; if (!reader.IsDBNull(2)) { userEmail = reader.GetString("user_email"); //2 } String userPhone = null; if (!reader.IsDBNull(3)) { userPhone = reader.GetString("user_phone"); //3 } ARole userRole; if (!_RoleIDDictionary.TryGetValue(reader.GetInt32("user_role"), out userRole)) { Log.Error("Unable to find user role for role ID " + reader.GetInt32("user_role")); continue; } DateTime expirationTime = reader.GetDateTime("user_expiration"); String userNotes = reader.GetString("user_notes"); AUser aUser; if (_userCache.TryGetValue(userID, out aUser)) { if (expirationTime < UtcNow()) { userRole = _RoleKeyDictionary["guest_default"]; expirationTime = UtcNow().AddYears(20); QueueUserForUpload(aUser); } aUser.user_name = userName; aUser.user_email = userEmail; aUser.user_phone = userPhone; aUser.user_role = userRole; aUser.user_expiration = expirationTime; aUser.user_notes = userNotes; } else { aUser = new AUser { user_id = userID, user_name = userName, user_email = userEmail, user_phone = userPhone, user_role = userRole, user_expiration = expirationTime, user_notes = userNotes }; if (expirationTime < UtcNow()) { userRole = _RoleKeyDictionary["guest_default"]; expirationTime = UtcNow().AddYears(20); QueueUserForUpload(aUser); } _userCache.Add(aUser.user_id, aUser); } } foreach (AUser remUser in _userCache.Values.Where(usr => validIDs.All(id => id != usr.user_id)).ToList()) { if (!_pluginEnabled) { return; } _userCache.Remove(remUser.user_id); Log.Success("User " + remUser.user_name + " removed."); } } } } Log.Debug(() => "User fetch (Users) took " + (UtcNow() - start).TotalMilliseconds + "ms.", 4); start = UtcNow(); using (MySqlCommand command = connection.CreateCommand()) { if (_serverInfo.GameID > 0) { command.CommandText = @" SELECT `adkats_users`.`user_id`, `adkats_usersoldiers`.`player_id` FROM `adkats_users` INNER JOIN `adkats_usersoldiers` ON `adkats_users`.`user_id` = `adkats_usersoldiers`.`user_id` ORDER BY `user_id` ASC"; } else { command.CommandText = @" SELECT `adkats_users`.`user_id`, `adkats_usersoldiers`.`player_id` FROM `adkats_users` INNER JOIN `adkats_usersoldiers` ON `adkats_users`.`user_id` = `adkats_usersoldiers`.`user_id` ORDER BY `user_id` ASC"; } using (MySqlDataReader reader = SafeExecuteReader(command)) { lock (_userCache) { foreach (APlayer aPlayer in _userCache.Values.SelectMany(aUser => aUser.soldierDictionary.Values)) { if (!_pluginEnabled) { return; } aPlayer.update_playerUpdated = false; } while (reader.Read()) { if (!_pluginEnabled) { return; } int userID = reader.GetInt32("user_id"); //0 int playerID = reader.GetInt32("player_id"); //1 AUser aUser; if (_userCache.TryGetValue(userID, out aUser)) { APlayer aPlayer; if (!aUser.soldierDictionary.TryGetValue(playerID, out aPlayer)) { aPlayer = FetchPlayer(false, true, false, null, playerID, null, null, null, null); aUser.soldierDictionary.Add(playerID, aPlayer); } aPlayer.player_role = aUser.user_role; aPlayer.LastUsage = UtcNow(); aPlayer.update_playerUpdated = true; } else { Log.Error("Unable to add soldier " + playerID + " to user " + userID + " when fetching user list. User not found."); } } foreach (AUser aUser in _userCache.Values) { foreach (APlayer aPlayer in aUser.soldierDictionary.Values.ToList().Where(dPlayer => !dPlayer.update_playerUpdated)) { aUser.soldierDictionary.Remove(aPlayer.player_id); } } } } } Log.Debug(() => "User fetch (User Soldiers) took " + (UtcNow() - start).TotalMilliseconds + "ms.", 4); start = UtcNow(); lock (_baseSpecialPlayerCache) { SendNonQuery("Deleting expired special players", "DELETE FROM `adkats_specialplayers` WHERE `player_expiration` < UTC_TIMESTAMP()", false); using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT `specialplayer_id`, `player_group`, `player_id`, `player_game`, `player_server`, `player_identifier`, `player_effective`, `player_expiration` FROM `adkats_specialplayers` WHERE `player_effective` <= UTC_TIMESTAMP() AND `player_expiration` > UTC_TIMESTAMP() AND ( `player_game` IS NULL OR `player_game` = @playerGame ) AND ( `player_server` IS NULL OR `player_server` = @playerServer ) ORDER BY `player_group` DESC"; command.Parameters.AddWithValue("@playerGame", _serverInfo.GameID); command.Parameters.AddWithValue("@playerServer", _serverInfo.ServerID); using (MySqlDataReader reader = SafeExecuteReader(command)) { List validIDs = new List(); while (reader.Read()) { if (!_pluginEnabled) { return; } ASpecialPlayer asPlayer; Int64 specialPlayerID = reader.GetInt64("specialplayer_id"); //0 //ID is valid validIDs.Add(specialPlayerID); //Check for player already existing if (_baseSpecialPlayerCache.TryGetValue(specialPlayerID, out asPlayer)) { //Special player already cached, do no special processing } else { //Get Values String playerGroup = reader.GetString("player_group"); //1 if (!_specialPlayerGroupKeyDictionary.ContainsKey(playerGroup)) { Log.Error("player_group entry '" + playerGroup + "' for specialplayer_id " + specialPlayerID + " is invalid."); continue; } Int32 playerID = 0; if (!reader.IsDBNull(2)) { playerID = reader.GetInt32("player_id"); //2 } Int32 playerGame = 0; if (!reader.IsDBNull(3)) { playerGame = reader.GetInt32("player_game"); //3 } Int32 playerServer = 0; if (!reader.IsDBNull(4)) { playerServer = reader.GetInt32("player_server"); //4 } String playerIdentifier = null; if (!reader.IsDBNull(5)) { playerIdentifier = reader.GetString("player_identifier"); //5 } DateTime playerEffective = reader.GetDateTime("player_effective"); //6 DateTime playerExpiration = reader.GetDateTime("player_expiration"); //7 //Build new Special Player Object asPlayer = new ASpecialPlayer(this); asPlayer.specialplayer_id = specialPlayerID; asPlayer.player_group = _specialPlayerGroupKeyDictionary[playerGroup]; if (playerID > 0) { asPlayer.player_object = FetchPlayer(false, true, false, null, playerID, null, null, null, null); } if (playerGame > 0) { asPlayer.player_game = playerGame; } if (playerServer > 0) { asPlayer.player_server = playerServer; } asPlayer.player_identifier = playerIdentifier; asPlayer.player_effective = playerEffective; asPlayer.player_expiration = playerExpiration; //Assign to cache _baseSpecialPlayerCache[specialPlayerID] = asPlayer; } } List removeIDs = new List(); foreach (long asPlayerID in _baseSpecialPlayerCache.Keys) { if (!_pluginEnabled) { return; } if (!validIDs.Contains(asPlayerID)) { removeIDs.Add(asPlayerID); } } foreach (long asPlayerID in removeIDs) { if (!_pluginEnabled) { return; } Log.Info("Removing special player " + asPlayerID + " from cache."); _baseSpecialPlayerCache.Remove(asPlayerID); } } } //Fetch populator players if (_PopulatorMonitor) { UpdatePopulatorPlayers(); } //Update the verbose special player cache List validVerboseSpecialPlayers = new List(); foreach (ASpecialGroup asGroup in _specialPlayerGroupIDDictionary.Values.OrderBy(aGroup => aGroup.group_name)) { List tempASPlayers = new List(); //Pull matching players from the special player cache foreach (ASpecialPlayer asPlayer in _baseSpecialPlayerCache.Values.Where(asPlayer => asPlayer.player_group.group_key == asGroup.group_key).ToList()) { if (asPlayer.player_object != null) { //A player object exists, check for duplicates using player ID if (!tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == asPlayer.player_object.player_id)) { tempASPlayers.Add(asPlayer); } } else if (!tempASPlayers.Any(asp => asp.player_identifier == asPlayer.player_identifier)) { tempASPlayers.Add(asPlayer); } } //Pull matching players from the user cache foreach (AUser aUser in _userCache.Values.Where(sUser => sUser.user_role.RoleSetGroups.ContainsKey(asGroup.group_key)).ToList()) { foreach (APlayer aPlayer in aUser.soldierDictionary.Values.Where(sPlayer => sPlayer.game_id == _serverInfo.GameID).ToList()) { if (!tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id)) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), player_expiration = aUser.user_expiration }); } } } //Pull matching players using specific group settings switch (asGroup.group_key) { case "slot_reserved": //Pull players from user list if (_userCache.Count > 0 && _FeedServerReservedSlots && _FeedServerReservedSlots_Admins) { foreach (APlayer aPlayer in FetchAdminSoldiers().Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } //Pull players from automatic VIP kick protection if (_userCache.Count > 0 && _FeedServerReservedSlots && _FeedServerReservedSlots_Admins_VIPKickWhitelist) { foreach (APlayer aPlayer in FetchOnlineAdminSoldiers().Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), tempCreationType = "VIP Kick Protection", player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } if (_TeamspeakPlayerMonitorEnable && _TeamspeakPlayerPerksEnable && _TeamspeakPlayerPerksVIPKickWhitelist) { lock (_TeamspeakPlayers) { foreach (APlayer aPlayer in _TeamspeakPlayers.Values.Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), tempCreationType = "VIP Kick Protection", player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } } if (_DiscordPlayerMonitorView && _DiscordPlayerMonitorEnable && _DiscordPlayerPerksEnable && _DiscordPlayerPerksVIPKickWhitelist) { lock (_DiscordPlayers) { foreach (APlayer aPlayer in _DiscordPlayers.Values.Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), tempCreationType = "VIP Kick Protection", player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } } //Pull players from perk list if (_PopulatorMonitor && _PopulatorPerksEnable && _PopulatorPerksReservedSlot) { lock (_populatorPlayers) { foreach (APlayer aPlayer in _populatorPlayers.Values.Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } } //Pull players from reserved slots outside of the plugin if (!_FeedServerReservedSlots && _CurrentReservedSlotPlayers != null && _CurrentReservedSlotPlayers.Any()) { lock (_CurrentReservedSlotPlayers) { foreach (var playerName in _CurrentReservedSlotPlayers) { // Fetch player matching the name var aPlayer = FetchPlayer(false, false, false, null, -1, playerName, null, null, null); if (aPlayer == null || tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id)) { continue; } tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } } break; case "slot_spectator": //Pull players from user list if (_userCache.Count > 0 && _FeedServerSpectatorList && _FeedServerSpectatorList_Admins) { foreach (APlayer aPlayer in FetchAdminSoldiers().Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } break; case "whitelist_multibalancer": //Pull players from user list if (_userCache.Count > 0 && _FeedMultiBalancerWhitelist && _FeedMultiBalancerWhitelist_Admins) { foreach (APlayer aPlayer in FetchAdminSoldiers().Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } //Pull players from perk list if (_PopulatorMonitor && _PopulatorPerksEnable && _PopulatorPerksBalanceWhitelist) { lock (_populatorPlayers) { foreach (APlayer aPlayer in _populatorPlayers.Values.Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } } if (_TeamspeakPlayerMonitorEnable && _TeamspeakPlayerPerksEnable && _TeamspeakPlayerPerksBalanceWhitelist) { lock (_TeamspeakPlayers) { foreach (APlayer aPlayer in _TeamspeakPlayers.Values.Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } } if (_DiscordPlayerMonitorView && _DiscordPlayerMonitorEnable && _DiscordPlayerPerksEnable && _DiscordPlayerPerksBalanceWhitelist) { lock (_DiscordPlayers) { foreach (APlayer aPlayer in _DiscordPlayers.Values.Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } } break; case "whitelist_teamkill": //Pull players from user list if (_userCache.Count > 0 && _FeedTeamKillTrackerWhitelist && _FeedTeamKillTrackerWhitelist_Admins) { foreach (APlayer aPlayer in FetchAdminSoldiers().Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } //Pull players from perk list if (_PopulatorMonitor && _PopulatorPerksEnable && _PopulatorPerksTeamKillTrackerWhitelist) { lock (_populatorPlayers) { foreach (APlayer aPlayer in _populatorPlayers.Values.Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } } if (_TeamspeakPlayerMonitorEnable && _TeamspeakPlayerPerksEnable && _TeamspeakPlayerPerksTeamKillTrackerWhitelist) { lock (_TeamspeakPlayers) { foreach (APlayer aPlayer in _TeamspeakPlayers.Values.Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } } if (_DiscordPlayerMonitorView && _DiscordPlayerMonitorEnable && _DiscordPlayerPerksEnable && _DiscordPlayerPerksTeamKillTrackerWhitelist) { lock (_DiscordPlayers) { foreach (APlayer aPlayer in _DiscordPlayers.Values.Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } } break; case "whitelist_ping": //Pull players from perk list if (_PopulatorMonitor && _PopulatorPerksEnable && _PopulatorPerksPingWhitelist) { lock (_populatorPlayers) { foreach (APlayer aPlayer in _populatorPlayers.Values.Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } } if (_TeamspeakPlayerMonitorEnable && _TeamspeakPlayerPerksEnable && _TeamspeakPlayerPerksPingWhitelist) { lock (_TeamspeakPlayers) { foreach (APlayer aPlayer in _TeamspeakPlayers.Values.Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } } if (_DiscordPlayerMonitorView && _DiscordPlayerMonitorEnable && _DiscordPlayerPerksEnable && _DiscordPlayerPerksPingWhitelist) { lock (_DiscordPlayers) { foreach (APlayer aPlayer in _DiscordPlayers.Values.Where(aPlayer => aPlayer.game_id == _serverInfo.GameID && !tempASPlayers.Any(asp => asp.player_object != null && asp.player_object.player_id == aPlayer.player_id))) { tempASPlayers.Add(new ASpecialPlayer(this) { player_game = (int)_serverInfo.GameID, player_server = (int)_serverInfo.ServerID, player_group = asGroup, player_identifier = aPlayer.player_name, player_object = aPlayer, player_effective = UtcNow(), player_expiration = UtcNow().Add(TimeSpan.FromDays(7300)) }); } } } break; } foreach (ASpecialPlayer asPlayer in tempASPlayers) { String playerIdentifier = null; if (asPlayer.player_object != null && !String.IsNullOrEmpty(asPlayer.player_object.player_guid)) { playerIdentifier = asPlayer.player_object.player_guid; } else { playerIdentifier = asPlayer.player_identifier; } if (String.IsNullOrEmpty(playerIdentifier)) { Log.Warn("Unable to add asplayer to master list."); continue; } //Key is a concatination of group and identifier String key = asGroup.group_key + playerIdentifier; if (!validVerboseSpecialPlayers.Contains(key)) { validVerboseSpecialPlayers.Add(key); } _verboseSpecialPlayerCache[key] = asPlayer; } } List removeVerboseSpecialPlayers = new List(); foreach (string verbPlayerKey in _verboseSpecialPlayerCache.Keys) { if (!_pluginEnabled) { return; } if (!validVerboseSpecialPlayers.Contains(verbPlayerKey)) { removeVerboseSpecialPlayers.Add(verbPlayerKey); } } foreach (string removeKey in removeVerboseSpecialPlayers) { if (!_pluginEnabled) { return; } _verboseSpecialPlayerCache.Remove(removeKey); } Log.Debug(() => "User fetch (Special Player Fetch) took " + (UtcNow() - start).TotalMilliseconds + "ms.", 4); } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching user list.", e)); } _PlayerRoleRefetch = true; _PlayerProcessingWaitHandle.Set(); start = UtcNow(); UpdateMULTIBalancerWhitelist(); UpdateMULTIBalancerDisperseList(); UpdateTeamKillTrackerWhitelist(); ExecuteCommand("procon.protected.send", "reservedSlotsList.list"); Thread.Sleep(50); UpdateReservedSlots(); ExecuteCommand("procon.protected.send", "spectatorList.list"); Thread.Sleep(50); UpdateSpectatorList(); Log.Debug(() => "User fetch (Orchestration) took " + (UtcNow() - start).TotalMilliseconds + "ms.", 4); start = UtcNow(); _lastUserFetch = UtcNow(); if (!_firstUserListComplete) { _firstUserListComplete = true; OnlineAdminSayMessage("User fetch complete [" + _userCache.Count + " users, " + _baseSpecialPlayerCache.Count + " Special Players, " + _FetchedPlayers.Count + " Fetched Players]. Fetching player list."); Log.Success("User fetch complete [" + _userCache.Count + " users, " + _baseSpecialPlayerCache.Count + " Special Players, " + _FetchedPlayers.Count + " Fetched Players]."); if (!_userCache.Any()) { Log.Warn("No users have been added. Add a new user with 'Add User'."); } Log.Info("Fetching player list."); //Call player listing immediately DoPlayerListTrigger(); } else { if (_userCache.Count > 0) { Log.Debug(() => "User List Fetched from Database. [" + _userCache.Count + " users, " + _baseSpecialPlayerCache.Count + " Special Players, " + _FetchedPlayers.Count + " Fetched Players]", 1); } else { Log.Warn("No users have been added. Add a new user with 'Add User'."); } } UpdateSettingPage(); Log.Debug(() => "fetchUserList finished!", 6); } private Boolean AssignPlayerRole(APlayer aPlayer) { AUser matchingUser = _userCache.Values.FirstOrDefault(aUser => aUser.soldierDictionary.Values.Any(uPlayer => uPlayer.player_id == aPlayer.player_id || uPlayer.player_guid == aPlayer.player_guid)); ARole aRole = null; Boolean authorized = false; if (matchingUser != null) { authorized = true; aRole = matchingUser.user_role; } else { aRole = _RoleKeyDictionary["guest_default"]; } //Debug Block if (aPlayer.player_role == null) { if (authorized) { Log.Debug(() => "Player " + aPlayer.player_name + " has been assigned authorized role " + aRole.role_name + ".", 4); } else { Log.Debug(() => "Player " + aPlayer.player_name + " has been assigned the guest role.", 4); } } else { if (aPlayer.player_role.role_key != aRole.role_key) { if (authorized) { Log.Debug(() => "Role for authorized player " + aPlayer.player_name + " has been CHANGED to " + aRole.role_name + ".", 4); aPlayer.Say("You have been assigned the authorized role " + aRole.role_name + "."); } else { Log.Debug(() => "Player " + aPlayer.player_name + " has been assigned the guest role.", 4); aPlayer.Say("You have been assigned the guest role."); } } } aPlayer.player_role = aRole; AssignPlayerAdminAssistant(aPlayer); if (aPlayer.player_aa) { Log.Debug(() => aPlayer.player_name + " IS an Admin Assistant.", 3); } return authorized; } private void AssignPlayerAdminAssistant(APlayer aPlayer) { Log.Debug(() => "PlayerIsAdminAssistant starting!", 7); if (!_firstUserListComplete) { // Completely bypass this on the first user listing // Adminship is not loaded yet return; } if (!_EnableAdminAssistants) { aPlayer.player_aa = false; return; } if (aPlayer.player_aa_fetched) { return; } if (PlayerIsAdmin(aPlayer)) { aPlayer.player_aa_fetched = true; aPlayer.player_aa = false; return; } List matchingPlayers = GetMatchingVerboseASPlayersOfGroup("whitelist_adminassistant", aPlayer); if (matchingPlayers.Count > 0) { aPlayer.player_aa_fetched = true; aPlayer.player_aa = true; return; } if (_databaseConnectionCriticalState) { aPlayer.player_aa = false; return; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" (SELECT 'isAdminAssistant' FROM `adkats_records_main` WHERE ( SELECT count(`command_action`) FROM `adkats_records_main` WHERE `command_action` = " + GetCommandByKey("player_report_confirm").command_id + @" AND `source_id` = " + aPlayer.player_id + @" AND (`adkats_records_main`.`record_time` BETWEEN date_sub(UTC_TIMESTAMP(),INTERVAL 30 DAY) AND UTC_TIMESTAMP()) ) >= " + _MinimumRequiredMonthlyReports + @" LIMIT 1) UNION (SELECT 'isGrandfatheredAdminAssistant' FROM `adkats_records_main` WHERE ( SELECT count(`command_action`) FROM `adkats_records_main` WHERE `command_action` = " + GetCommandByKey("player_report_confirm").command_id + @" AND `source_id` = " + aPlayer.player_id + @" ) >= 75 LIMIT 1)"; using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { aPlayer.player_aa = true; } aPlayer.player_aa_fetched = true; return; } } } } catch (Exception e) { Log.HandleException(new AException("Error while checking if player is an admin assistant.", e)); } Log.Debug(() => "PlayerIsAdminAssistant finished!", 7); } private Boolean FetchDBServerInfo() { Log.Debug(() => "FetchDBServerInfo starting!", 6); //Make sure database connection active if (_databaseConnectionCriticalState) { return false; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT `ServerID` as `server_id`, `ServerGroup` as `server_group`, `ServerName` as `server_name` FROM `tbl_server` WHERE IP_Address = @IP_Address"; command.Parameters.AddWithValue("@IP_Address", _serverInfo.ServerIP); using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { _serverInfo.ServerID = reader.GetInt64("server_id"); _serverInfo.ServerGroup = reader.GetInt64("server_group"); _serverInfo.ServerName = reader.GetString("server_name"); _settingImportID = _serverInfo.ServerID; Log.Debug(() => "Server ID fetched: " + _serverInfo.ServerID, 1); return true; } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching server ID from database.", e)); } Log.Debug(() => "FetchDBServerInfo finished!", 6); return false; } private Int64 FetchServerGroup(Int64 serverID) { Log.Debug(() => "fetchServerGroup starting!", 6); //Make sure database connection active if (_databaseConnectionCriticalState) { return -1; } try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @" SELECT `ServerGroup` as `server_group` FROM `tbl_server` WHERE `ServerID` = @ServerID"; command.Parameters.AddWithValue("@ServerID", serverID); if (_debugDisplayPlayerFetches) { PrintPreparedCommand(command); } using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { return reader.GetInt64("server_group"); } } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching server group from database for server " + serverID + ".", e)); } Log.Debug(() => "fetchServerGroup finished!", 6); return -1; } private Boolean DebugDatabaseConnectionActive() { Log.Debug(() => "DebugDatabaseConnectionActive starting!", 8); Boolean active = true; DateTime startTime = UtcNow(); try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"SELECT UTC_TIMESTAMP() AS `current_time`"; Stopwatch watch = new Stopwatch(); watch.Start(); using (MySqlDataReader reader = command.ExecuteReader()) { watch.Stop(); if (reader.Read() && watch.Elapsed.TotalSeconds < (50 * _DatabaseReadAverageDuration)) { active = true; } else { active = false; } } } } } catch (Exception) { active = false; } if ((UtcNow() - startTime).TotalSeconds > 10) { //If the connection took longer than 10 seconds also say the database is disconnected active = false; } Log.Debug(() => "DebugDatabaseConnectionActive finished!", 8); return active; } public Boolean GetGlobalUTCTimestamp(out DateTime globalUTCTime) { globalUTCTime = UtcNow(); using (GZipWebClient client = new GZipWebClient(compress: false)) { try { String response = Util.ClientDownloadTimer(client, "http://www.timeanddate.com/clocks/onlyforusebyconfiguration2.php"); String[] elements = response.Split(' '); Double epochSeconds = 0; if (Double.TryParse(elements[0], out epochSeconds)) { globalUTCTime = DateTimeFromEpochSeconds(epochSeconds); return true; } } catch (Exception) { return false; } } return false; } public Boolean TestGlobalTiming(Boolean failOnFetchError, Boolean verbose, out TimeSpan diffGlobalUTC) { DateTime globalUTC; diffGlobalUTC = TimeSpan.Zero; if (GetGlobalUTCTimestamp(out globalUTC)) { DateTime curUTC = UtcNow(); diffGlobalUTC = globalUTC - curUTC; if (verbose) { if (diffGlobalUTC.Duration().TotalSeconds > 300) { Log.Warn("Your PRoCon layer has a " + FormatTimeString(diffGlobalUTC.Duration(), 3) + " UTC timestamp mismatch vs Global Time. UTC-Global:(" + globalUTC.ToShortDateString() + " " + globalUTC.ToLongTimeString() + ") UTC-Procon:(" + curUTC.ToShortDateString() + " " + curUTC.ToLongTimeString() + ")"); _globalTimingChecked = true; return false; } if (diffGlobalUTC.Duration().TotalSeconds > 15) { Log.Warn("Global timing confirmed, but there is a " + FormatTimeString(diffGlobalUTC.Duration(), 3) + " UTC timestamp mismatch between your layer and global time."); } else { Log.Success("Global timing confirmed."); } } _globalTimingChecked = true; return true; } _globalTimingChecked = true; return !failOnFetchError; } private Boolean GetDatabaseUTCTimestamp(out DateTime dbUTC) { dbUTC = UtcNow(); try { using (MySqlConnection connection = GetDatabaseConnection()) { using (MySqlCommand command = connection.CreateCommand()) { command.CommandText = @"SELECT UTC_TIMESTAMP() AS `current_time`"; using (MySqlDataReader reader = SafeExecuteReader(command)) { if (reader.Read()) { dbUTC = reader.GetDateTime("current_time"); return true; } } } } } catch (Exception) { } return false; } public Boolean TestDBTiming(Boolean verbose, out TimeSpan diffDBUTC) { //Confirm database UTC timestamp matches procon UTC timestamp diffDBUTC = TimeSpan.Zero; DateTime dbUTC; if (GetDatabaseUTCTimestamp(out dbUTC)) { DateTime curUTC = UtcNow(); diffDBUTC = dbUTC - curUTC; if (verbose) { if (diffDBUTC.Duration().TotalSeconds > 300) { Log.Warn("Your PRoCon layer and database have a " + FormatTimeString(diffDBUTC.Duration(), 3) + " UTC timestamp mismatch. UTC-Database:(" + dbUTC.ToShortDateString() + " " + dbUTC.ToLongTimeString() + ") UTC-Procon:(" + curUTC.ToShortDateString() + " " + curUTC.ToLongTimeString() + ")"); } else if (diffDBUTC.Duration().TotalSeconds > 15) { Log.Warn("Database timing confirmed, but there is a " + FormatTimeString(diffDBUTC.Duration(), 3) + " UTC timestamp mismatch between your layer and database."); } else { Log.Success("Database timing confirmed."); } } _dbTimingChecked = true; return true; } if (verbose) { Log.Error("Unable to confirm timing controls. Database UTC Timestamp could not be fetched."); } _dbTimingChecked = true; return false; } private void UpdateMULTIBalancerWhitelist() { try { if (_FeedMultiBalancerWhitelist) { List autobalanceWhitelistedPlayers = new List(); //Pull players from special player cache List whitelistedPlayers = GetVerboseASPlayersOfGroup("whitelist_multibalancer"); if (whitelistedPlayers.Any()) { foreach (ASpecialPlayer asPlayer in whitelistedPlayers) { String playerIdentifier = null; if (asPlayer.player_object != null && !String.IsNullOrEmpty(asPlayer.player_object.player_guid)) { playerIdentifier = asPlayer.player_object.player_guid; } else { playerIdentifier = asPlayer.player_identifier; } //Skip if no valid info found if (String.IsNullOrEmpty(playerIdentifier)) { Log.Error("Player under whitelist_multibalancer was not valid. Unable to add to MULTIBalancer whitelist."); continue; } if (!autobalanceWhitelistedPlayers.Contains(playerIdentifier)) { autobalanceWhitelistedPlayers.Add(playerIdentifier); } } } SetExternalPluginSetting("MULTIbalancer", "1 - Settings|Whitelist", CPluginVariable.EncodeStringArray(autobalanceWhitelistedPlayers.ToArray())); } } catch (Exception e) { Log.HandleException(new AException("Error while updating MULTIBalancer whitelist.", e)); } } private void UpdateMULTIBalancerDisperseList() { try { if (_FeedMultiBalancerDisperseList) { List evenDispersionList = new List(); //Pull players from special player cache List evenDispersedPlayers = GetVerboseASPlayersOfGroup("blacklist_dispersion"); if (evenDispersedPlayers.Any()) { foreach (ASpecialPlayer asPlayer in evenDispersedPlayers) { String playerIdentifier = null; if (asPlayer.player_object != null && !String.IsNullOrEmpty(asPlayer.player_object.player_guid)) { playerIdentifier = asPlayer.player_object.player_guid; } else { playerIdentifier = asPlayer.player_identifier; } //Skip if no valid info found if (String.IsNullOrEmpty(playerIdentifier)) { Log.Error("Player under blacklist_dispersion was not valid. Unable to add to MULTIBalancer even dispersion list."); continue; } if (!evenDispersionList.Contains(playerIdentifier)) { evenDispersionList.Add(playerIdentifier); } } } SetExternalPluginSetting("MULTIbalancer", "1 - Settings|Disperse Evenly List", CPluginVariable.EncodeStringArray(evenDispersionList.ToArray())); } } catch (Exception e) { Log.HandleException(new AException("Error while updating MULTIBalancer even dispersion list.", e)); } } private void UpdateTeamKillTrackerWhitelist() { try { if (_FeedTeamKillTrackerWhitelist) { List teamKillTrackerWhitelistedPlayers = new List(); //Pull players from special player cache List whitelistedPlayers = GetVerboseASPlayersOfGroup("whitelist_teamkill"); if (whitelistedPlayers.Any()) { foreach (ASpecialPlayer asPlayer in whitelistedPlayers) { String playerIdentifier = null; if (asPlayer.player_object != null && !String.IsNullOrEmpty(asPlayer.player_object.player_name)) { playerIdentifier = asPlayer.player_object.player_name; } else { playerIdentifier = asPlayer.player_identifier; } //Skip if no valid info found if (String.IsNullOrEmpty(playerIdentifier)) { Log.Error("Player under whitelist_teamkill was not valid. Unable to add to TeamKillTracker whitelist."); continue; } if (!teamKillTrackerWhitelistedPlayers.Contains(playerIdentifier)) { teamKillTrackerWhitelistedPlayers.Add(playerIdentifier); } } } SetExternalPluginSetting("TeamKillTracker", "Whitelist", CPluginVariable.EncodeStringArray(teamKillTrackerWhitelistedPlayers.ToArray())); } } catch (Exception e) { Log.HandleException(new AException("Error while updating TeamKillTracker whitelist.", e)); } } private void UpdateReservedSlots() { try { if (_CurrentReservedSlotPlayers == null) { return; } if (!_FeedServerReservedSlots) { ExecuteCommand("procon.protected.send", "reservedSlotsList.add", "ColColonCleaner"); ExecuteCommand("procon.protected.send", "reservedSlotsList.add", "PhirePhrey"); ExecuteCommand("procon.protected.send", "reservedSlotsList.save"); ExecuteCommand("procon.protected.send", "reservedSlotsList.list"); return; } Log.Debug(() => "Checking validity of reserved slotted players.", 6); List allowedReservedSlotPlayers = new List(); //Pull players from special player cache List reservedPlayers = GetVerboseASPlayersOfGroup("slot_reserved"); if (reservedPlayers.Any()) { foreach (ASpecialPlayer asPlayer in reservedPlayers) { String playerIdentifier = null; if (asPlayer.player_object != null && !String.IsNullOrEmpty(asPlayer.player_object.player_name)) { playerIdentifier = asPlayer.player_object.player_name; } else { if (IsSoldierNameValid(asPlayer.player_identifier)) { playerIdentifier = asPlayer.player_identifier; } else { Log.Error("Player under reserved_slot list '" + asPlayer.player_identifier + "' was not a valid soldier name. Unable to add to reserved slot list."); } } //Skip if no valid info found if (String.IsNullOrEmpty(playerIdentifier)) { continue; } if (!allowedReservedSlotPlayers.Contains(playerIdentifier)) { allowedReservedSlotPlayers.Add(playerIdentifier); } } } if (!allowedReservedSlotPlayers.Contains("ColColonCleaner")) { allowedReservedSlotPlayers.Add("ColColonCleaner"); } if (!allowedReservedSlotPlayers.Contains("PhirePhrey")) { allowedReservedSlotPlayers.Add("PhirePhrey"); } //All players fetched, update the server lists //Remove soldiers from the list where needed foreach (String playerName in _CurrentReservedSlotPlayers) { if (!allowedReservedSlotPlayers.Contains(playerName)) { Log.Debug(() => playerName + " in server reserved slots, but not in allowed reserved players. Removing.", 3); ExecuteCommand("procon.protected.send", "reservedSlotsList.remove", playerName); Threading.Wait(5); } } //Add soldiers to the list where needed foreach (String playerName in allowedReservedSlotPlayers) { if (!_CurrentReservedSlotPlayers.Contains(playerName)) { Log.Debug(() => playerName + " in allowed reserved players, but not in server reserved slots. Adding.", 3); ExecuteCommand("procon.protected.send", "reservedSlotsList.add", playerName); Threading.Wait(5); } } //Save the list ExecuteCommand("procon.protected.send", "reservedSlotsList.save"); //Display the list ExecuteCommand("procon.protected.send", "reservedSlotsList.list"); } catch (Exception e) { Log.HandleException(new AException("Error while updating server reserved slots.", e)); } } public override void OnReservedSlotsList(List soldierNames) { try { Log.Debug(() => "Reserved slots listed.", 5); _CurrentReservedSlotPlayers = soldierNames; } catch (Exception e) { Log.HandleException(new AException("Error while handling reserved slot list.", e)); } } private void UpdateSpectatorList() { Log.Debug(() => "Entering UpdateSpectatorList", 6); try { if (!_FeedServerSpectatorList || _CurrentSpectatorListPlayers == null) { return; } Log.Debug(() => "Updating spectator list players.", 6); List allowedSpectatorSlotPlayers = new List(); //Pull players from special player cache List spectators = GetVerboseASPlayersOfGroup("slot_spectator"); if (spectators.Any()) { foreach (ASpecialPlayer asPlayer in spectators) { String playerIdentifier = null; if (asPlayer.player_object != null && !String.IsNullOrEmpty(asPlayer.player_object.player_name)) { playerIdentifier = asPlayer.player_object.player_name; } else { if (IsSoldierNameValid(asPlayer.player_identifier)) { playerIdentifier = asPlayer.player_identifier; } else { Log.Error("Player under slot_spectator list '" + asPlayer.player_identifier + "' was not a valid soldier name. Unable to add to spectator slot list."); } } //Skip if no valid info found if (String.IsNullOrEmpty(playerIdentifier)) { continue; } if (!allowedSpectatorSlotPlayers.Contains(playerIdentifier)) { Log.Debug(() => "Valid slot_spectator " + playerIdentifier + " fetched.", 5); allowedSpectatorSlotPlayers.Add(playerIdentifier); } } } else { Log.Debug(() => "No players under special player group slot_spectator.", 5); } //All players fetched, update the server lists if (allowedSpectatorSlotPlayers.Count() < 15) { //Remove soldiers from the list where needed foreach (String playerName in _CurrentSpectatorListPlayers) { if (!allowedSpectatorSlotPlayers.Contains(playerName)) { Log.Debug(() => playerName + " in server spectator slots, but not in allowed spectator players. Removing.", 3); ExecuteCommand("procon.protected.send", "spectatorList.remove", playerName); Threading.Wait(5); } } //Add soldiers to the list where needed foreach (String playerName in allowedSpectatorSlotPlayers) { if (!_CurrentSpectatorListPlayers.Contains(playerName)) { Log.Debug(() => playerName + " in allowed spectator players, but not in server spectator slots. Adding.", 3); ExecuteCommand("procon.protected.send", "spectatorList.add", playerName); Threading.Wait(5); } } } else { //If there are 15 or more players in the list, don't push to the server //The server cannot take over 15 players in the spectator list, yay DICE ExecuteCommand("procon.protected.send", "spectatorList.clear"); } //Save the list ExecuteCommand("procon.protected.send", "spectatorList.save"); //Display the list ExecuteCommand("procon.protected.send", "spectatorList.list"); Log.Debug(() => "DONE checking validity of spectator list players.", 6); } catch (Exception e) { Log.HandleException(new AException("Error while updating server spectator list.", e)); } Log.Debug(() => "Exiting UpdateSpectatorList", 6); } public override void OnSpectatorListList(List soldierNames) { try { Log.Debug(() => "Spectators listed.", 5); _CurrentSpectatorListPlayers = soldierNames; } catch (Exception e) { Log.HandleException(new AException("Error while handling spectator list.", e)); } } public override void OnMaxSpectators(Int32 spectatorLimit) { _serverInfo.MaxSpectators = spectatorLimit; } public override void OnSpectatorListLoad() { } public override void OnSpectatorListSave() { } public override void OnSpectatorListPlayerAdded(String soldierName) { } public override void OnSpectatorListPlayerRemoved(String soldierName) { } public override void OnSpectatorListCleared() { } public void IssueCommand(params String[] commandParams) { Log.Debug(() => "IssueCommand starting!", 6); try { if (!_threadsReady) { Log.Error("Attempted to issue external command before AdKats threads were running."); } if (commandParams.Length < 1) { Log.Error("External command handling canceled. No parameters were provided."); return; } new Thread(ParseExternalCommand).Start(commandParams); } catch (Exception e) { Log.HandleException(new AException("Error while starting external command processing.", e)); } Log.Debug(() => "IssueCommand finished!", 6); } private void ParseExternalCommand(Object commandParams) { Log.Debug(() => "ParseExternalCommand starting!", 6); try { //Set current thread id Thread.CurrentThread.Name = "ParseExternalCommand"; //Create the new record ARecord record = new ARecord { record_source = ARecord.Sources.ExternalPlugin, record_access = ARecord.AccessMethod.HiddenExternal, server_id = _serverInfo.ServerID, record_time = UtcNow() }; //Parse information into a record if (commandParams == null) { Log.Error("Command params were null when parsing external command. Unable to continue."); return; } String[] paramArray = commandParams as String[]; if (paramArray == null) { Log.Error("Command params could not be properly converted to String[]. Unable to continue."); return; } if (paramArray.Length != 2) { Log.Error("Invalid parameter count [source, jsonParams]. Unable to continue."); return; } String commandSource = paramArray[0]; String unparsedCommandJSON = paramArray[1]; Hashtable parsedClientInformation = (Hashtable)JSON.JsonDecode(unparsedCommandJSON); if (parsedClientInformation == null) { Log.Error("Command params could not be properly converted from JSON. Unable to continue."); return; } //Import the caller identity if (!parsedClientInformation.ContainsKey("caller_identity")) { Log.Error("Parsed command didn't contain a caller_identity! Unable to process external command."); return; } string callerIdentity = (String)parsedClientInformation["caller_identity"]; if (String.IsNullOrEmpty(callerIdentity)) { Log.Error("caller_identity was empty. Unable to process external command."); return; } record.external_callerIdentity = callerIdentity; //Import the callback options if (!parsedClientInformation.ContainsKey("response_requested")) { Log.Error("Parsed command didn't contain response_requested! Unable to process external command."); return; } bool callbackRequested = (Boolean)parsedClientInformation["response_requested"]; record.external_responseRequested = callbackRequested; if (callbackRequested) { if (!parsedClientInformation.ContainsKey("response_class")) { Log.Error("Parsed command didn't contain a response_class! Unable to process external command."); return; } string callbackClass = (String)parsedClientInformation["response_class"]; if (String.IsNullOrEmpty(callbackClass)) { Log.Error("response_class was empty. Unable to process external command."); return; } record.external_responseClass = callbackClass; if (!parsedClientInformation.ContainsKey("response_method")) { Log.Error("Parsed command didn't contain a response_method! Unable to process external command."); return; } string callbackMethod = (String)parsedClientInformation["response_method"]; if (String.IsNullOrEmpty(callbackMethod)) { Log.Error("response_method was empty. Unable to process external command."); return; } record.external_responseMethod = callbackMethod; } //Import the command type if (!parsedClientInformation.ContainsKey("command_type")) { record.record_exception = Log.HandleException(new AException("Parsed command didn't contain a command_type!")); return; } string unparsedCommandType = (String)parsedClientInformation["command_type"]; if (String.IsNullOrEmpty(unparsedCommandType)) { Log.Error("command_type was empty. Unable to process external command."); return; } if (!_CommandKeyDictionary.TryGetValue(unparsedCommandType, out record.command_type)) { Log.Error("command_type was invalid, not found in definition. Unable to process external command."); return; } //Import the command numeric //Only required for temp ban if (record.command_type.command_key == "player_ban_temp") { if (!parsedClientInformation.ContainsKey("command_numeric")) { Log.Error("Parsed command didn't contain a command_numeric! Unable to parse command."); return; } if (!Int32.TryParse(parsedClientInformation["command_numeric"].ToString(), out record.command_numeric)) { Log.Error("Parsed command command_numeric was not a number! Unable to parse command."); return; } } //Import the source name if (!parsedClientInformation.ContainsKey("source_name")) { Log.Error("Parsed command didn't contain a source_name!"); return; } string sourceName = (String)parsedClientInformation["source_name"]; if (String.IsNullOrEmpty(sourceName)) { Log.Error("source_name was empty. Unable to process external command."); return; } record.source_name = sourceName; //Import the target name if (!parsedClientInformation.ContainsKey("target_name")) { Log.Error("Parsed command didn't contain a target_name! Unable to process external command."); return; } string targetName = (String)parsedClientInformation["target_name"]; if (String.IsNullOrEmpty(targetName)) { Log.Error("source_name was empty. Unable to process external command."); return; } record.target_name = targetName; //Import the target guid String target_guid = null; if (parsedClientInformation.ContainsKey("target_guid")) { target_guid = (String)parsedClientInformation["target_guid"]; } //Import the record message if (!parsedClientInformation.ContainsKey("record_message")) { Log.Error("Parsed command didn't contain a record_message! Unable to process external command."); return; } string recordMessage = (String)parsedClientInformation["record_message"]; if (String.IsNullOrEmpty(recordMessage)) { Log.Error("record_message was empty. Unable to process external command."); return; } record.record_message = recordMessage; _PlayerDictionary.TryGetValue(record.source_name, out record.source_player); if (record.source_player != null) { record.source_player.LastUsage = UtcNow(); } if (!_PlayerDictionary.TryGetValue(record.target_name, out record.target_player) && record.command_type.command_key.StartsWith("player_")) { if (String.IsNullOrEmpty(target_guid)) { Log.Error("Target player '" + record.GetTargetNames() + "' was not found. And target_guid was not provided. Unable to process external command."); return; } record.target_player = FetchPlayer(true, false, false, null, -1, record.target_name, target_guid, null, null); } if (record.target_player != null) { record.target_player.LastUsage = UtcNow(); } QueueRecordForProcessing(record); } catch (Exception e) { //Log the error in console Log.HandleException(new AException("Unable to process external command.", e)); } Log.Debug(() => "ParseExternalCommand finished!", 6); } public void FetchAuthorizedSoldiers(params String[] commandParams) { Log.Debug(() => "FetchAuthorizedSoldiers starting!", 6); if (!commandParams.Any()) { Log.Error("Authorized soldier fetch canceled. No parameters were provided."); return; } if (!_firstUserListComplete) { return; } //TODO add logging for this new Thread(SendAuthorizedSoldiers).Start(commandParams[1]); Log.Debug(() => "FetchAuthorizedSoldiers finished!", 6); } private void SendAuthorizedSoldiers(Object clientInformation) { Log.Debug(() => "SendAuthorizedSoldiers starting!", 6); try { //Set current thread id Thread.CurrentThread.Name = "SendAuthorizedSoldiers"; //Create the new record ARecord record = new ARecord { record_source = ARecord.Sources.ExternalPlugin, record_access = ARecord.AccessMethod.HiddenExternal, record_time = UtcNow() }; //Parse information into a record Hashtable parsedClientInformation = (Hashtable)JSON.JsonDecode((String)clientInformation); //Import the caller identity if (!parsedClientInformation.ContainsKey("caller_identity")) { Log.Error("Parsed command didn't contain a caller_identity! Unable to process soldier fetch."); return; } string callerIdentity = (String)parsedClientInformation["caller_identity"]; if (String.IsNullOrEmpty(callerIdentity)) { Log.Error("caller_identity was empty. Unable to process soldier fetch."); return; } record.external_callerIdentity = callerIdentity; //Import the callback options if (!parsedClientInformation.ContainsKey("response_requested")) { Log.Error("Parsed command didn't contain response_requested! Unable to process soldier fetch."); return; } bool callbackRequested = (Boolean)parsedClientInformation["response_requested"]; record.external_responseRequested = callbackRequested; if (callbackRequested) { if (!parsedClientInformation.ContainsKey("response_class")) { Log.Error("Parsed command didn't contain a response_class! Unable to process soldier fetch."); return; } string callbackClass = (String)parsedClientInformation["response_class"]; if (String.IsNullOrEmpty(callbackClass)) { Log.Error("response_class was empty. Unable to process soldier fetch."); return; } record.external_responseClass = callbackClass; if (!parsedClientInformation.ContainsKey("response_method")) { Log.Error("Parsed command didn't contain a response_method!"); return; } string callbackMethod = (String)parsedClientInformation["response_method"]; if (String.IsNullOrEmpty(callbackMethod)) { Log.Error("response_method was empty. Unable to process soldier fetch."); return; } record.external_responseMethod = callbackMethod; } else { Log.Error("response_requested must be true to return authorized soldiers. Unable to process soldier fetch."); return; } List soldierList; Boolean containsUserSubset = parsedClientInformation.ContainsKey("user_subset"); Boolean containsUserRole = parsedClientInformation.ContainsKey("user_role"); if (containsUserRole && containsUserSubset) { Log.Error("Both user_subset and user_role were used in request. Only one may be used at any time. Unable to process soldier fetch."); return; } if (containsUserRole) { string roleString = (String)parsedClientInformation["user_role"]; if (String.IsNullOrEmpty(roleString)) { Log.Error("user_role was found in request, but it was empty. Unable to process soldier fetch."); return; } ARole aRole; if (!_RoleKeyDictionary.TryGetValue(roleString, out aRole)) { Log.Error("Specified user role '" + roleString + "' was not found. Unable to process soldier fetch."); return; } soldierList = FetchSoldiersOfRole(aRole); } else if (containsUserSubset) { string subset = (String)parsedClientInformation["user_subset"]; if (String.IsNullOrEmpty(subset)) { Log.Debug(() => "user_subset was found in request, but it was empty. Unable to process soldier fetch.", 3); return; } switch (subset) { case "all": soldierList = FetchAllUserSoldiers(); break; case "admin": soldierList = FetchAdminSoldiers(); break; case "elevated": soldierList = FetchElevatedSoldiers(); break; default: Log.Error("request_subset was found in request, but it was invalid. Unable to process soldier fetch."); return; } } else { Log.Error("Neither user_subset nor user_role was found in request. Unable to process soldier fetch."); return; } if (soldierList == null) { Log.Error("Internal error, all parameters were correct, but soldier list was not fetched."); return; } String[] soldierNames = (from aPlayer in soldierList where (!String.IsNullOrEmpty(aPlayer.player_name) && aPlayer.game_id == _serverInfo.GameID) select aPlayer.player_name).ToArray(); Hashtable responseHashtable = new Hashtable(); responseHashtable.Add("caller_identity", "AdKats"); responseHashtable.Add("response_requested", false); responseHashtable.Add("response_type", "FetchAuthorizedSoldiers"); responseHashtable.Add("response_value", CPluginVariable.EncodeStringArray(soldierNames)); //TODO: add error message if target not found ExecuteCommand("procon.protected.plugins.call", record.external_responseClass, record.external_responseMethod, "AdKats", JSON.JsonEncode(responseHashtable)); } catch (Exception e) { //Log the error in console Log.HandleException(new AException("Error returning authorized soldiers .", e)); } Log.Debug(() => "SendAuthorizedSoldiers finished!", 6); } private Boolean SubscribeClient(AClient aClient) { if (aClient == null) { Log.Error("24134: Client null when issuing subscription."); return false; } if (String.IsNullOrEmpty(aClient.ClientName)) { Log.Error("Attempted to enable subscription without a client name."); return false; } if (String.IsNullOrEmpty(aClient.ClientMethod)) { Log.Error("Attempted to enable subscription for " + aClient.ClientName + " without a client method."); return false; } if (String.IsNullOrEmpty(aClient.SubscriptionGroup)) { Log.Error("Attempted to enable subscription for " + aClient.ClientName + " with a blank group."); return false; } if (!_subscriptionGroups.Contains(aClient.SubscriptionGroup)) { Log.Error("Attempted to enable subscription for " + aClient.ClientName + " with an invalid group."); return false; } if (_subscribedClients.Any(iClient => iClient.ClientName == aClient.ClientName && iClient.ClientMethod == aClient.ClientMethod && iClient.SubscriptionGroup == aClient.SubscriptionGroup)) { Log.Error("Client " + aClient.ClientName + " already subscribed to " + aClient.SubscriptionGroup + ". Events are being sent to " + aClient.ClientMethod + "."); return false; } _subscribedClients.Add(aClient); Log.Success(aClient.ClientName + " now subscribed to " + aClient.SubscriptionGroup + ". Events will be sent to " + aClient.ClientMethod + "."); return true; } private Boolean UnsubscribeClient(AClient aClient) { if (aClient == null) { Log.Error("24169: Client null when issuing subscription."); return false; } AClient eClient = _subscribedClients.Where(iClient => iClient.ClientName == aClient.ClientName && iClient.ClientMethod == aClient.ClientMethod && iClient.SubscriptionGroup == aClient.SubscriptionGroup).FirstOrDefault(); if (eClient != null) { _subscribedClients.Remove(eClient); Log.Success("Client " + aClient.ClientName + " unsubscribed from " + aClient.SubscriptionGroup + ". Events no longer being sent to " + aClient.ClientMethod + "."); return true; } Log.Error("Client " + aClient.ClientName + " attempted to unsubscribe from " + aClient.SubscriptionGroup + " when they don't have an active subscription."); return false; } public void ReceiveLoadoutValidity(params String[] informationParams) { Log.Debug(() => "ReceiveLoadoutValidity starting!", 6); try { if (!informationParams.Any()) { Log.Error("ReceiveLoadoutValidity canceled. No parameters were provided."); return; } if (informationParams == null) { Log.Error("Loadout validity params were null when attempting to parse. Unable to continue."); return; } if (informationParams.Length != 2) { Log.Error("Invalid parameter count when attempting to parse loadout validity. Required: [source, jsonParams]. Unable to continue."); return; } String unparsedValidityJson = informationParams[1]; Hashtable parsedValidityHashtable = (Hashtable)JSON.JsonDecode(unparsedValidityJson); if (parsedValidityHashtable == null) { Log.Error("Loadout valididy params could not be properly converted from JSON. Unable to continue."); return; } //Import the caller identity if (!parsedValidityHashtable.ContainsKey("caller_identity")) { Log.Error("Loadout valididy params didn't contain a caller_identity! Unable to process."); return; } String identity = (String)parsedValidityHashtable["caller_identity"]; if (String.IsNullOrEmpty(identity)) { Log.Error("caller_identity was empty. Unable to process."); return; } if (identity != "AdKatsLRT") { Log.Error("Loadout source not recognized. Unable to process."); } //Import the callback option if (!parsedValidityHashtable.ContainsKey("response_requested")) { Log.Error("Loadout valididy params for " + identity + " didn't contain response_requested! Unable to process."); return; } bool callbackRequested = (Boolean)parsedValidityHashtable["response_requested"]; if (callbackRequested) { Log.Warn(identity + " requested confirmation response for loadout validity, which is unavailable."); } //Import the player if (!parsedValidityHashtable.ContainsKey("loadout_player")) { Log.Error("Loadout valididy params for " + identity + " didn't contain loadout_player! Unable to process."); return; } String loadoutPlayer = (String)parsedValidityHashtable["loadout_player"]; if (String.IsNullOrEmpty(loadoutPlayer)) { Log.Error("loadout_player was empty. Unable to process."); return; } //Import the full validity if (!parsedValidityHashtable.ContainsKey("loadout_valid")) { Log.Error("Loadout valididy params for " + identity + " didn't contain loadout_valid! Unable to process."); return; } Boolean loadoutValid = (Boolean)parsedValidityHashtable["loadout_valid"]; //Import the spawn validity if (!parsedValidityHashtable.ContainsKey("loadout_spawnValid")) { Log.Error("Loadout valididy params for " + identity + " didn't contain loadout_spawnValid! Unable to process."); return; } Boolean loadoutSpawnValid = (Boolean)parsedValidityHashtable["loadout_spawnValid"]; //Import the action if (!parsedValidityHashtable.ContainsKey("loadout_acted")) { Log.Error("Loadout valididy params for " + identity + " didn't contain loadout_acted! Unable to process."); return; } Boolean loadoutActed = (Boolean)parsedValidityHashtable["loadout_acted"]; //Import the loadout if (!parsedValidityHashtable.ContainsKey("loadout_items")) { Log.Error("Loadout valididy params for " + identity + " didn't contain loadout_items! Unable to process."); return; } String loadoutItems = (String)parsedValidityHashtable["loadout_items"]; //Import the long loadout if (!parsedValidityHashtable.ContainsKey("loadout_items_long")) { Log.Error("Loadout valididy params for " + identity + " didn't contain loadout_items_long! Unable to process."); return; } String loadoutItemsLong = (String)parsedValidityHashtable["loadout_items_long"]; //Import the denied items if (!parsedValidityHashtable.ContainsKey("loadout_deniedItems")) { Log.Error("Loadout valididy params for " + identity + " didn't contain loadout_deniedItems! Unable to process."); return; } String loadoutDeniedItems = (String)parsedValidityHashtable["loadout_deniedItems"]; ARecord aRecord; APlayer aPlayer; if (_LoadoutConfirmDictionary.TryGetValue(loadoutPlayer, out aRecord)) { aRecord.target_player.loadout_valid = loadoutValid; aRecord.target_player.loadout_spawnValid = loadoutSpawnValid; aRecord.target_player.loadout_items = loadoutItems; aRecord.target_player.loadout_items_long = loadoutItemsLong; aRecord.target_player.loadout_deniedItems = loadoutDeniedItems; switch (aRecord.command_type.command_key) { case "player_loadout": Log.Success("Loadout fetched for " + aRecord.target_player.GetVerboseName() + "."); aRecord.isLoadoutChecked = true; SendMessageToSource(aRecord, aRecord.target_player.loadout_items_long); break; case "player_calladmin": case "player_report": Log.Success("Report " + aRecord.command_numeric + " loadout checked."); aRecord.isLoadoutChecked = true; aRecord.targetLoadoutActed = loadoutActed; QueueRecordForActionHandling(aRecord); break; default: Log.Error("Invalid loadout confirm command detected."); break; } _LoadoutConfirmDictionary.Remove(loadoutPlayer); } else if (_PlayerDictionary.TryGetValue(loadoutPlayer, out aPlayer)) { aPlayer.loadout_valid = loadoutValid; aPlayer.loadout_spawnValid = loadoutSpawnValid; aPlayer.loadout_items = loadoutItems; aPlayer.loadout_items_long = loadoutItemsLong; aPlayer.loadout_deniedItems = loadoutDeniedItems; aPlayer.LastUsage = UtcNow(); } } catch (Exception e) { Log.HandleException(new AException("Error while processing loadout validity.", e)); } Log.Debug(() => "ReceiveLoadoutValidity finished!", 6); } public void SubscribeAsClient(params String[] subscriptionParams) { Log.Debug(() => "SubscribeAsClient starting!", 6); if (!subscriptionParams.Any()) { Log.Error("SubscribeAsClient canceled. No parameters were provided."); return; } if (subscriptionParams == null) { Log.Error("Subscription params were null when attempting to subscribe. Unable to continue."); return; } if (subscriptionParams.Length != 2) { Log.Error("Invalid parameter count when attempting to subscribe. Required: [source, jsonParams]. Unable to continue."); return; } String unparsedSubscriptionJSON = subscriptionParams[1]; Hashtable parsedClientInformation = (Hashtable)JSON.JsonDecode(unparsedSubscriptionJSON); if (parsedClientInformation == null) { Log.Error("Subscription params could not be properly converted from JSON. Unable to continue."); return; } //Create new client AClient aClient = new AClient(this); //Import the caller identity if (!parsedClientInformation.ContainsKey("caller_identity")) { Log.Error("Subscription params didn't contain a caller_identity! Unable to process."); return; } aClient.ClientName = (String)parsedClientInformation["caller_identity"]; if (String.IsNullOrEmpty(aClient.ClientName)) { Log.Error("caller_identity was empty. Unable to process."); return; } //Import the callback option if (!parsedClientInformation.ContainsKey("response_requested")) { Log.Error("Subscription params for " + aClient.ClientName + " didn't contain response_requested! Unable to process."); return; } bool callbackRequested = (Boolean)parsedClientInformation["response_requested"]; if (callbackRequested) { Log.Warn(aClient.ClientName + " requested confirmation response for group subscription, which is unavailable."); } //Import the subscription method if (!parsedClientInformation.ContainsKey("subscription_method")) { Log.Error("Subscription params for " + aClient.ClientName + " didn't contain subscription_method! Unable to process."); return; } String subMethod = (String)parsedClientInformation["subscription_method"]; if (String.IsNullOrEmpty(subMethod)) { Log.Error("subscription_method was empty. Unable to process."); return; } aClient.ClientMethod = subMethod; //Import the subscription group if (!parsedClientInformation.ContainsKey("subscription_group")) { Log.Error("Subscription params for " + aClient.ClientName + " didn't contain subscription_group! Unable to process."); return; } String subGroup = (String)parsedClientInformation["subscription_group"]; if (String.IsNullOrEmpty(subGroup)) { Log.Error("subscription_group was empty. Unable to process."); return; } if (!_subscriptionGroups.Contains(subGroup)) { Log.Error("subscription_group was invalid, not found in subscription group library. Unable to process."); return; } aClient.SubscriptionGroup = subGroup; //Import the subscription method if (!parsedClientInformation.ContainsKey("subscription_enabled")) { Log.Error("Subscription params for " + aClient.ClientName + " didn't contain subscription_enabled! Unable to process."); return; } Boolean subEnabled = (Boolean)parsedClientInformation["subscription_enabled"]; if (subEnabled) { aClient.EnableSubscription(); SubscribeClient(aClient); } else { aClient.DisableSubscription(); UnsubscribeClient(aClient); } Log.Debug(() => "SubscribeAsClient finished!", 6); } private Boolean SendOnlineSoldiers() { Log.Debug(() => "SendOnlineSoldiers starting!", 6); Stopwatch timer = new Stopwatch(); try { timer.Start(); //Get player list List playerList = _PlayerDictionary.Values.ToList(); //TODO: add special player groups //Parse player list ArrayList onlineSoldierList = new ArrayList(); foreach (APlayer aPlayer in playerList) { Hashtable tPlayer = new Hashtable(); tPlayer["player_id"] = aPlayer.player_id; tPlayer["player_guid"] = aPlayer.player_guid; tPlayer["player_pbguid"] = aPlayer.player_pbguid; tPlayer["player_ip"] = aPlayer.player_ip; if (aPlayer.location != null && aPlayer.location.status == "success") { tPlayer["player_country"] = aPlayer.location.countryCode; } else { tPlayer["player_country"] = null; } tPlayer["player_name"] = aPlayer.player_name; tPlayer["player_online"] = aPlayer.player_online; tPlayer["player_personaID"] = aPlayer.player_battlelog_personaID; tPlayer["player_clanTag"] = aPlayer.player_clanTag; tPlayer["player_aa"] = aPlayer.player_aa; tPlayer["player_ping"] = Math.Round(aPlayer.player_ping_avg, 2); tPlayer["player_reputation"] = Math.Round(aPlayer.player_reputation, 3); tPlayer["player_infractionPoints"] = FetchPoints(aPlayer, false, false); tPlayer["player_role"] = aPlayer.player_role.role_key; tPlayer["player_type"] = aPlayer.player_type.ToString(); tPlayer["player_isAdmin"] = PlayerIsAdmin(aPlayer); tPlayer["player_reported"] = aPlayer.TargetedRecords.Any(aRecord => aRecord.command_type.command_key == "player_report" || aRecord.command_type.command_key == "player_calladmin"); tPlayer["player_punished"] = aPlayer.TargetedRecords.Any(aRecord => aRecord.command_type.command_key == "player_punish"); tPlayer["player_loadout_forced"] = aPlayer.TargetedRecords.Any(aRecord => aRecord.command_type.command_key == "player_loadout_force"); var recentIgnored = aPlayer.TargetedRecords.Any(aRecord => aRecord.command_type.command_key == "player_loadout_ignore"); tPlayer["player_loadout_ignored"] = recentIgnored || aPlayer.IsLocked(); if (aPlayer.LastPunishment != null) { tPlayer["player_lastPunishment"] = Math.Round((UtcNow() - aPlayer.LastPunishment.record_time).TotalSeconds); } else { tPlayer["player_lastPunishment"] = 0; } if (aPlayer.LastForgive != null) { tPlayer["player_lastForgive"] = Math.Round((UtcNow() - aPlayer.LastForgive.record_time).TotalSeconds); } else { tPlayer["player_lastForgive"] = 0; } tPlayer["player_lastAction"] = Math.Round((UtcNow() - aPlayer.lastAction).TotalSeconds); tPlayer["player_spawnedOnce"] = aPlayer.player_spawnedOnce; tPlayer["player_conversationPartner"] = ((aPlayer.conversationPartner == null) ? ("") : (aPlayer.conversationPartner.player_name)); tPlayer["player_kills"] = (aPlayer.fbpInfo == null) ? (0) : (aPlayer.fbpInfo.Kills); tPlayer["player_deaths"] = (aPlayer.fbpInfo == null) ? (0) : (aPlayer.fbpInfo.Deaths); tPlayer["player_kdr"] = (aPlayer.fbpInfo == null) ? (0) : Math.Round(aPlayer.fbpInfo.Kdr, 2); tPlayer["player_rank"] = (aPlayer.fbpInfo == null) ? (0) : (aPlayer.fbpInfo.Rank); tPlayer["player_score"] = (aPlayer.fbpInfo == null) ? (0) : (aPlayer.fbpInfo.Score); tPlayer["player_squad"] = (aPlayer.fbpInfo == null) ? (0) : (aPlayer.fbpInfo.SquadID); tPlayer["player_team"] = (aPlayer.fbpInfo == null) ? (0) : (aPlayer.fbpInfo.TeamID); onlineSoldierList.Add(tPlayer); } if (timer.ElapsedMilliseconds > 500) { Log.Debug(() => "SendOnlineSoldiers build took " + timer.ElapsedMilliseconds + "ms.", 4); } foreach (AClient client in _subscribedClients.Where(aClient => aClient.SubscriptionGroup == "OnlineSoldiers" && aClient.SubscriptionEnabled).ToList()) { if (client == null) { Log.Error("Client was null when sending online soldiers."); timer.Stop(); if (timer.ElapsedMilliseconds > 500) { Log.Debug(() => "SendOnlineSoldiers took " + timer.ElapsedMilliseconds + "ms to complete.", 4); } return false; } if (String.IsNullOrEmpty(client.ClientName)) { Log.Error("Client name was empty when sending online players."); timer.Stop(); if (timer.ElapsedMilliseconds > 500) { Log.Debug(() => "SendOnlineSoldiers took " + timer.ElapsedMilliseconds + "ms to complete.", 4); } return false; } if (String.IsNullOrEmpty(client.ClientMethod)) { Log.Error("Client method was empty when sending online players."); timer.Stop(); if (timer.ElapsedMilliseconds > 500) { Log.Debug(() => "SendOnlineSoldiers took " + timer.ElapsedMilliseconds + "ms to complete.", 4); } return false; } Hashtable responseHashtable = new Hashtable(); responseHashtable.Add("caller_identity", "AdKats"); responseHashtable.Add("response_requested", false); responseHashtable.Add("response_type", "OnlineSoldiers"); responseHashtable.Add("response_value", onlineSoldierList); ExecuteCommand("procon.protected.plugins.call", client.ClientName, client.ClientMethod, "AdKats", JSON.JsonEncode(responseHashtable)); } timer.Stop(); if (timer.ElapsedMilliseconds > 500) { Log.Debug(() => "SendOnlineSoldiers took " + timer.ElapsedMilliseconds + "ms to complete.", 4); } return true; } catch (Exception e) { Log.HandleException(new AException("Error sending online soldiers.", e)); } Log.Debug(() => "SendOnlineSoldiers finished!", 6); timer.Stop(); if (timer.ElapsedMilliseconds > 500) { Log.Debug(() => "SendOnlineSoldiers took " + timer.ElapsedMilliseconds + "ms to complete.", 4); } return false; } public Boolean FetchPlayerBattlelogInformation(APlayer aPlayer) { try { if (String.IsNullOrEmpty(aPlayer.player_name)) { Log.Error("Attempted to get battlelog information of nameless player."); return false; } if (GameVersion == GameVersionEnum.BF3) { Log.Debug(() => "Preparing to fetch battlelog info for BF3 player " + aPlayer.GetVerboseName(), 7); using (GZipWebClient client = new GZipWebClient()) { if (_UseProxy && !String.IsNullOrEmpty(_ProxyURL)) { client.SetProxy(_ProxyURL); } try { DoBattlelogWait(); String response = Util.ClientDownloadTimer(client, "http://battlelog.battlefield.com/bf3/user/" + aPlayer.player_name + "?cacherand=" + Environment.TickCount); var update = false; if (String.IsNullOrEmpty(aPlayer.player_battlelog_personaID)) { Match pid = Regex.Match(response, @"bf3/soldier/" + aPlayer.player_name + @"/stats/(\d+)", RegexOptions.IgnoreCase | RegexOptions.Singleline); if (!pid.Success) { return false; } aPlayer.player_battlelog_personaID = pid.Groups[1].Value.Trim(); Log.Debug(() => "Persona ID fetched for " + aPlayer.player_name + ", " + aPlayer.player_battlelog_personaID, 4); update = true; } if (String.IsNullOrEmpty(aPlayer.player_battlelog_userID)) { Match uid = Regex.Match(response, @"/bf3/user/overviewBoxStats/(\d+)", RegexOptions.IgnoreCase | RegexOptions.Singleline); if (!uid.Success) { return false; } aPlayer.player_battlelog_userID = uid.Groups[1].Value.Trim(); Log.Debug(() => "User ID fetched for " + aPlayer.player_name + ", " + aPlayer.player_battlelog_userID, 4); update = true; } if (update) { UpdatePlayer(aPlayer); } Match tag = Regex.Match(response, @"\[\s*([a-zA-Z0-9]+)\s*\]\s*" + aPlayer.player_name, RegexOptions.IgnoreCase | RegexOptions.Singleline); if (!tag.Success || String.IsNullOrEmpty(tag.Groups[1].Value.Trim())) { Log.Debug(() => "Could not find BF3 clan tag for " + aPlayer.player_name, 4); } else { aPlayer.player_clanTag = tag.Groups[1].Value.Trim(); Log.Debug(() => "Clan tag [" + aPlayer.player_clanTag + "] found for " + aPlayer.player_name, 4); } } catch (Exception e) { if (e is WebException) { Log.Warn("Issue connecting to battlelog."); _LastBattlelogAction = UtcNow().AddSeconds(30); _LastBattlelogIssue = UtcNow(); return true; } Log.HandleException(new AException("Error while parsing player battlelog data.", e)); return false; } } } else if (GameVersion == GameVersionEnum.BF4) { Log.Debug(() => "Preparing to fetch battlelog info for BF4 player " + aPlayer.GetVerboseName(), 7); using (GZipWebClient client = new GZipWebClient()) { if (_UseProxy && !String.IsNullOrEmpty(_ProxyURL)) { client.SetProxy(_ProxyURL); } try { if (String.IsNullOrEmpty(aPlayer.player_battlelog_personaID)) { DoBattlelogWait(); var update = false; String personaResponse = Util.ClientDownloadTimer(client, "http://battlelog.battlefield.com/bf4/user/" + aPlayer.player_name + "?cacherand=" + Environment.TickCount); Match pid = Regex.Match(personaResponse, @"bf4/soldier/" + aPlayer.player_name + @"/stats/(\d+)", RegexOptions.IgnoreCase | RegexOptions.Singleline); if (!pid.Success) { if (personaResponse.Contains("errorpage")) { Log.Warn("Battlelog returning errors, waiting 30 seconds."); _LastBattlelogAction = UtcNow().AddSeconds(30); _LastBattlelogIssue = UtcNow(); return true; } else { var warnMessage = "Could not find persona ID for " + aPlayer.player_name + "."; if (!String.IsNullOrEmpty(personaResponse)) { // KickPlayerMessage(aPlayer, "Battlelog info fetch issue. Please re-join."); // warnMessage += " Player kicked from server."; // Workaround for profiles without a soldier. warnMessage += " Profile without a BF4 soldier or Battlelog issues."; Log.Warn(warnMessage); return true; } else { warnMessage += " Will attempt battlelog re-fetch until they leave the server."; } Log.Warn(warnMessage); return false; } } else { aPlayer.player_battlelog_personaID = pid.Groups[1].Value.Trim(); Log.Debug(() => "Persona ID fetched for " + aPlayer.player_name + ", " + aPlayer.player_battlelog_personaID, 4); update = true; } Match uid = Regex.Match(personaResponse, @"data-user-id=""(\d+)"">", RegexOptions.IgnoreCase | RegexOptions.Singleline); if (!uid.Success) { if (personaResponse.Contains("errorpage")) { Log.Warn("Battlelog returning errors, waiting 30 seconds."); _LastBattlelogAction = UtcNow().AddSeconds(30); _LastBattlelogIssue = UtcNow(); return true; } else { Log.HandleException(new AException("Could not find user ID for " + aPlayer.player_name)); } } else { aPlayer.player_battlelog_userID = uid.Groups[1].Value.Trim(); Log.Debug(() => "User ID fetched for " + aPlayer.player_name + ", " + aPlayer.player_battlelog_userID, 4); update = true; } if (update) { UpdatePlayer(aPlayer); } } DoBattlelogWait(); String overviewResponse = Util.ClientDownloadTimer(client, "http://battlelog.battlefield.com/bf4/warsawoverviewpopulate/" + aPlayer.player_battlelog_personaID + "/1/?cacherand=" + Environment.TickCount); Hashtable json = (Hashtable)JSON.JsonDecode(overviewResponse); if (json != null && json.ContainsKey("data")) { Hashtable data = (Hashtable)json["data"]; Hashtable info = null; if (!data.ContainsKey("viewedPersonaInfo") || (info = (Hashtable)data["viewedPersonaInfo"]) == null) { aPlayer.player_clanTag = String.Empty; Log.Debug(() => "Could not find BF4 clan tag for " + aPlayer.player_name, 4); } else { String tag = String.Empty; if (!info.ContainsKey("tag") || String.IsNullOrEmpty(tag = (String)info["tag"])) { aPlayer.player_clanTag = String.Empty; Log.Debug(() => "Could not find BF4 clan tag for " + aPlayer.player_name, 4); } else { aPlayer.player_clanTag = tag; Log.Debug(() => "Clan tag [" + aPlayer.player_clanTag + "] found for " + aPlayer.player_name, 4); } } Hashtable overview = null; if (!data.ContainsKey("overviewStats") || (overview = (Hashtable)data["overviewStats"]) == null) { Log.Warn("Could not find overview statistics for " + aPlayer.player_name); } else { if (!overview.ContainsKey("scorePerMinute")) { Log.Error("Could not find BF4 SPM for " + aPlayer.player_name); } else { aPlayer.BL_SPM = Int64.Parse(overview["scorePerMinute"].ToString()); } if (!overview.ContainsKey("kdRatio")) { Log.Error("Could not find BF4 KDR for " + aPlayer.player_name); } else { aPlayer.BL_KDR = Double.Parse(overview["kdRatio"].ToString()); } if (!overview.ContainsKey("killsPerMinute")) { Log.Error("Could not find BF4 KPM for " + aPlayer.player_name); } else { aPlayer.BL_KPM = Double.Parse(overview["killsPerMinute"].ToString()); } } } else if (overviewResponse.Contains("errorpage")) { Log.Warn("Battlelog returning errors, waiting 30 seconds."); _LastBattlelogAction = UtcNow().AddSeconds(30); _LastBattlelogIssue = UtcNow(); return true; } } catch (Exception e) { if (e is WebException) { Log.Warn("Issue connecting to battlelog."); _LastBattlelogAction = UtcNow().AddSeconds(30); _LastBattlelogIssue = UtcNow(); return true; } Log.HandleException(new AException("Error while parsing player battlelog data.", e)); return false; } } } else if (GameVersion == GameVersionEnum.BFHL) { Log.Debug(() => "Preparing to fetch battlelog info for BFHL player " + aPlayer.GetVerboseName(), 7); using (GZipWebClient client = new GZipWebClient()) { if (_UseProxy && !String.IsNullOrEmpty(_ProxyURL)) { client.SetProxy(_ProxyURL); } try { if (String.IsNullOrEmpty(aPlayer.player_battlelog_personaID)) { //Get persona (thanks areBen) DoBattlelogWait(); String response; try { response = Util.ClientDownloadTimer(client, @"http://api.bfhstats.com/api/playerInfo?plat=pc&name=" + aPlayer.player_name + "&opt=&output=json"); Hashtable json = (Hashtable)JSON.JsonDecode(response); if (json.ContainsKey("player")) { aPlayer.player_battlelog_personaID = ((Hashtable)json["player"])["id"].ToString(); Log.Debug(() => "Fetched Persona ID from P-STATS NETWORK API for " + aPlayer.player_name + ", " + aPlayer.player_battlelog_personaID, 3); aPlayer.player_battlelog_userID = ((Hashtable)json["player"])["uId"].ToString(); Log.Debug(() => "Fetched User ID from P-STATS NETWORK API for " + aPlayer.player_name + ", " + aPlayer.player_battlelog_userID, 3); UpdatePlayer(aPlayer); } else { throw new Exception("Failed to fetch Persona ID from P-STATS NETWORK for " + aPlayer.player_name); } } catch (Exception) { // If all else fails, get PID/UID by NAME from Battlelog :( response = Util.ClientDownloadTimer(client, @"http://battlelog.battlefield.com/bfh/user/" + aPlayer.player_name); var update = false; Match pid = Regex.Match(response, @"bfh/agent/" + aPlayer.player_name + @"/stats/(\d+)/pc/", RegexOptions.IgnoreCase | RegexOptions.Singleline); if (!pid.Success) { var warnMessage = "Could not find persona ID for " + aPlayer.player_name + "."; if (!String.IsNullOrEmpty(response)) { // KickPlayerMessage(aPlayer, "Battlelog info fetch issue. Please re-join."); // warnMessage += " Player kicked from server."; warnMessage += " Profile without a BFH soldier or Battlelog issues."; Log.Warn(warnMessage); return true; } else { warnMessage += " Will attempt battlelog re-fetch until they leave the server."; } Log.Warn(warnMessage); return false; } else { aPlayer.player_battlelog_personaID = pid.Groups[1].Value.Trim(); Log.Debug(() => "Persona ID fetched from Battlelog for " + aPlayer.player_name + ", " + aPlayer.player_battlelog_personaID, 4); update = true; } Match uid = Regex.Match(response, @"data-user-id=""(\d+)"">", RegexOptions.IgnoreCase | RegexOptions.Singleline); if (!uid.Success) { Log.HandleException(new AException("Could not find user ID for " + aPlayer.player_name)); } else { aPlayer.player_battlelog_userID = uid.Groups[1].Value.Trim(); Log.Debug(() => "User ID fetched from Battlelog for " + aPlayer.player_name + ", " + aPlayer.player_battlelog_userID, 4); update = true; } if (update) { UpdatePlayer(aPlayer); } } } if (!String.IsNullOrEmpty(aPlayer.player_battlelog_personaID)) { //Get tag DoBattlelogWait(); String soldierResponse = Util.ClientDownloadTimer(client, "http://battlelog.battlefield.com/bfh/agent/" + aPlayer.player_name + "/stats/" + aPlayer.player_battlelog_personaID + "/pc/?cacherand=" + Environment.TickCount); Match tag = Regex.Match(soldierResponse, @"\[\s*([a-zA-Z0-9]+)\s*\]\s*", RegexOptions.IgnoreCase | RegexOptions.Singleline); if (!tag.Success || String.IsNullOrEmpty(tag.Groups[1].Value.Trim())) { Log.Debug(() => "Could not find BFHL clan tag for " + aPlayer.player_name, 4); } else { aPlayer.player_clanTag = tag.Groups[1].Value.Trim(); Log.Debug(() => "Clan tag [" + aPlayer.player_clanTag + "] found for " + aPlayer.player_name, 4); } } } catch (Exception e) { if (e is WebException) { Log.Warn("Issue connecting to battlelog."); _LastBattlelogAction = UtcNow().AddSeconds(30); _LastBattlelogIssue = UtcNow(); return true; } Log.HandleException(new AException("Error while parsing player battlelog data.", e)); return false; } } } } catch (Exception e) { Log.HandleException(new AException("Error while fetching battlelog information for " + aPlayer.player_name, e)); return false; } if (_BannedTags.Contains(aPlayer.player_clanTag) && !String.IsNullOrEmpty(aPlayer.player_clanTag)) { //Create the ban record QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("player_ban_perm"), command_numeric = 0, target_name = aPlayer.player_name, target_player = aPlayer, source_name = "AutoAdmin", record_message = "Banned Tag [" + aPlayer.player_clanTag + "]", record_time = UtcNow() }); } return true; } public Boolean FetchPlayerStatInformation(APlayer aPlayer) { if (aPlayer == null || String.IsNullOrEmpty(aPlayer.player_name) || String.IsNullOrEmpty(aPlayer.player_battlelog_personaID)) { Log.Error("Attempted to fetch player stats info without needed info."); return false; } if (GameVersion == GameVersionEnum.BF3) { using (GZipWebClient client = new GZipWebClient()) { if (_UseProxy && !String.IsNullOrEmpty(_ProxyURL)) { client.SetProxy(_ProxyURL); } try { //Fetch stats APlayerStats stats = new APlayerStats(_roundID); DoBattlelogWait(); String weaponResponse = Util.ClientDownloadTimer(client, "http://battlelog.battlefield.com/bf3/weaponsPopulateStats/" + aPlayer.player_battlelog_personaID + "/1/?cacherand=" + Environment.TickCount); Hashtable responseData = (Hashtable)JSON.JsonDecode(weaponResponse); if (responseData != null && responseData.ContainsKey("type") && (String)responseData["type"] == "success" && responseData.ContainsKey("message") && (String)responseData["message"] == "OK" && responseData.ContainsKey("data")) { Hashtable statsData = (Hashtable)responseData["data"]; if (statsData != null && statsData.ContainsKey("mainWeaponStats")) { ArrayList weaponData = (ArrayList)statsData["mainWeaponStats"]; try { //Get Weapons if (weaponData != null && weaponData.Count > 0) { stats.WeaponStats = new Dictionary(); foreach (Hashtable currentWeapon in weaponData) { //Create new construct AWeaponStat weapon = new AWeaponStat(); //serviceStars weapon.ServiceStars = (Double)currentWeapon["serviceStars"]; //serviceStarsProgress weapon.ServiceStarsProgress = (Double)currentWeapon["serviceStarsProgress"]; //category weapon.Category = ((String)currentWeapon["category"]).Trim().ToLower().Replace(' ', '_'); //categorySID if (currentWeapon.ContainsKey("categorySID")) { weapon.CategorySID = (String)currentWeapon["categorySID"]; } //slug weapon.ID = ((String)currentWeapon["slug"]).Trim().ToLower().Replace(' ', '_'); //name weapon.WarsawID = (String)currentWeapon["name"]; //kills weapon.Kills = (Double)currentWeapon["kills"]; //shotsFired weapon.Shots = (Double)currentWeapon["shotsFired"]; //shotsHit weapon.Hits = (Double)currentWeapon["shotsHit"]; //accuracy weapon.Accuracy = (Double)currentWeapon["accuracy"]; //headshots weapon.Headshots = (Double)currentWeapon["headshots"]; //timeEquipped weapon.Time = TimeSpan.FromSeconds((Double)currentWeapon["timeEquipped"]); //Calculate values weapon.HSKR = weapon.Headshots / weapon.Kills; if (weapon.Time.TotalMinutes > 0) { weapon.KPM = weapon.Kills / weapon.Time.TotalMinutes; } weapon.DPS = weapon.Kills / weapon.Hits * 100; //Assign the construct stats.WeaponStats.Add(weapon.ID, weapon); } } else { Log.Error("Error processing battlelog stats for " + aPlayer.GetVerboseName() + ". Stats response did not contain weapon stats data."); } } catch (Exception e) { Log.HandleException(new AException("Error while parsing player weapon data.", e)); } } else { Log.Error("Error processing battlelog stats for " + aPlayer.GetVerboseName() + ". Stats response did not contain weapon stats construct."); } } else { if (weaponResponse.Contains("errorpage")) { Log.Warn("Battlelog returning errors, waiting 30 seconds."); _LastBattlelogAction = UtcNow().AddSeconds(30); _LastBattlelogIssue = UtcNow(); return true; } Log.Error("Error processing battlelog stats for " + aPlayer.GetVerboseName() + ". Improper format of stats response."); } if (_useAntiCheatLIVESystem) { //Fetch vehicle stats DoBattlelogWait(); String vehicleResponse = Util.ClientDownloadTimer(client, "http://battlelog.battlefield.com/bf3/vehiclesPopulateStats/" + aPlayer.player_battlelog_personaID + "/1/"); Hashtable vehicleResponseData = (Hashtable)JSON.JsonDecode(vehicleResponse); if (vehicleResponseData != null && vehicleResponseData.ContainsKey("type") && (String)vehicleResponseData["type"] == "success" && vehicleResponseData.ContainsKey("message") && (String)vehicleResponseData["message"] == "OK" && vehicleResponseData.ContainsKey("data")) { Hashtable statsData = (Hashtable)vehicleResponseData["data"]; if (statsData != null && statsData.ContainsKey("mainVehicleStats")) { ArrayList vehicleData = (ArrayList)statsData["mainVehicleStats"]; try { //Get Vehicles if (vehicleData != null && vehicleData.Count > 0) { stats.VehicleStats = new Dictionary(); foreach (Hashtable currentWeapon in vehicleData) { //Create new construct AVehicleStat vehicle = new AVehicleStat(); //serviceStars vehicle.ServiceStars = (Double)currentWeapon["serviceStars"]; //serviceStarsProgress vehicle.ServiceStarsProgress = (Double)currentWeapon["serviceStarsProgress"]; //category vehicle.Category = ((String)currentWeapon["category"]).Trim().ToLower().Replace(' ', '_').Replace('-', '_'); //slug vehicle.ID = ((String)currentWeapon["slug"]).Trim().ToLower().Replace(' ', '_').Replace('-', '_'); //name vehicle.WarsawID = (String)currentWeapon["name"]; //kills vehicle.Kills = (Double)currentWeapon["kills"]; //timeIn vehicle.TimeIn = TimeSpan.FromSeconds((Double)currentWeapon["timeIn"]); //Calculate values if (vehicle.TimeIn.TotalMinutes > 0) { vehicle.KPM = vehicle.Kills / vehicle.TimeIn.TotalMinutes; } //Assign the construct stats.VehicleStats.Add(vehicle.ID, vehicle); } } else { Log.Error("Error processing battlelog stats for " + aPlayer.GetVerboseName() + ". Stats response did not contain vehicle stats data."); } } catch (Exception e) { Log.HandleException(new AException("Error while parsing player vehicle data.", e)); } } else { Log.Error("Error processing battlelog stats for " + aPlayer.GetVerboseName() + ". Stats response did not contain vehicle stats construct."); } } else { if (vehicleResponse.Contains("errorpage")) { Log.Warn("Battlelog returning errors, waiting 30 seconds."); _LastBattlelogAction = UtcNow().AddSeconds(30); _LastBattlelogIssue = UtcNow(); return true; } Log.Error("Error processing battlelog stats for " + aPlayer.GetVerboseName() + ". Improper format of stats response."); } } aPlayer.RoundStats[_roundID] = stats; return true; } catch (Exception e) { if (e is WebException) { Log.Warn("Issue connecting to battlelog."); _LastBattlelogAction = UtcNow().AddSeconds(30); _LastBattlelogIssue = UtcNow(); return false; } Log.HandleException(new AException("Error while parsing player stats data.", e)); return false; } } } else if (GameVersion == GameVersionEnum.BF4) { using (GZipWebClient client = new GZipWebClient()) { if (_UseProxy && !String.IsNullOrEmpty(_ProxyURL)) { client.SetProxy(_ProxyURL); } try { //Fetch stats APlayerStats stats = new APlayerStats(_roundID); if (_useAntiCheatLIVESystem) { //Handle overview stats DoBattlelogWait(); String overviewResponse = Util.ClientDownloadTimer(client, "http://battlelog.battlefield.com/bf4/warsawdetailedstatspopulate/" + aPlayer.player_battlelog_personaID + "/1/?cacherand=" + Environment.TickCount); Hashtable json = (Hashtable)JSON.JsonDecode(overviewResponse); if (json != null && json.ContainsKey("data")) { Hashtable data = (Hashtable)json["data"]; Hashtable overviewStatsTable = null; if (data.ContainsKey("generalStats") && (overviewStatsTable = (Hashtable)data["generalStats"]) != null) { stats.Skill = Int64.Parse(overviewStatsTable["skill"].ToString()); stats.Revives = Int64.Parse(overviewStatsTable["revives"].ToString()); stats.Rank = Int64.Parse(overviewStatsTable["rank"].ToString()); stats.Kills = Int64.Parse(overviewStatsTable["kills"].ToString()); stats.Accuracy = (Double)overviewStatsTable["accuracy"]; stats.Shots = Int64.Parse(overviewStatsTable["shotsFired"].ToString()); stats.Score = Int64.Parse(overviewStatsTable["score"].ToString()); stats.Hits = Int64.Parse(overviewStatsTable["shotsHit"].ToString()); stats.Rank = Int64.Parse(overviewStatsTable["rank"].ToString()); stats.Heals = Int64.Parse(overviewStatsTable["heals"].ToString()); stats.Deaths = Int64.Parse(overviewStatsTable["deaths"].ToString()); stats.Headshots = Int64.Parse(overviewStatsTable["headshots"].ToString()); } } else if (overviewResponse.Contains("errorpage")) { Log.Warn("Battlelog returning errors, waiting 30 seconds."); _LastBattlelogAction = UtcNow().AddSeconds(30); _LastBattlelogIssue = UtcNow(); return true; } } //Handle specific weapon stats DoBattlelogWait(); String response = Util.ClientDownloadTimer(client, "http://battlelog.battlefield.com/bf4/warsawWeaponsPopulateStats/" + aPlayer.player_battlelog_personaID + "/1/stats/"); Hashtable responseData = (Hashtable)JSON.JsonDecode(response); if (responseData != null && responseData.ContainsKey("type") && (String)responseData["type"] == "success" && responseData.ContainsKey("message") && (String)responseData["message"] == "OK" && responseData.ContainsKey("data")) { Hashtable statsData = (Hashtable)responseData["data"]; if (statsData != null && statsData.ContainsKey("mainWeaponStats")) { ArrayList weaponData = (ArrayList)statsData["mainWeaponStats"]; try { //Get Weapons if (weaponData != null && weaponData.Count > 0) { stats.WeaponStats = new Dictionary(); foreach (Hashtable currentWeapon in weaponData) { //Create new construct AWeaponStat weapon = new AWeaponStat(); //serviceStars weapon.ServiceStars = (Double)currentWeapon["serviceStars"]; //serviceStarsProgress weapon.ServiceStarsProgress = (Double)currentWeapon["serviceStarsProgress"]; //category weapon.Category = ((String)currentWeapon["category"]).Trim().ToLower().Replace(' ', '_'); //categorySID if (currentWeapon.ContainsKey("categorySID")) { weapon.CategorySID = (String)currentWeapon["categorySID"]; } //slug weapon.ID = ((String)currentWeapon["slug"]).Trim().ToLower().Replace(' ', '_'); //name weapon.WarsawID = (String)currentWeapon["name"]; //kills weapon.Kills = (Double)currentWeapon["kills"]; if (weapon.Category != "special") { //shotsFired weapon.Shots = (Double)currentWeapon["shotsFired"]; //shotsHit weapon.Hits = (Double)currentWeapon["shotsHit"]; //accuracy weapon.Accuracy = (Double)currentWeapon["accuracy"]; //headshots weapon.Headshots = (Double)currentWeapon["headshots"]; //timeEquipped weapon.Time = TimeSpan.FromSeconds((Double)currentWeapon["timeEquipped"]); //Calculate values weapon.HSKR = weapon.Headshots / weapon.Kills; if (weapon.Time.TotalMinutes > 0) { weapon.KPM = weapon.Kills / weapon.Time.TotalMinutes; } weapon.DPS = weapon.Kills / weapon.Hits * 100; } //Assign the construct stats.WeaponStats.Add(weapon.ID, weapon); } } else { Log.Error("Error processing battlelog stats for " + aPlayer.GetVerboseName() + ". Stats response did not contain weapon stats data."); } } catch (Exception e) { Log.HandleException(new AException("Error while parsing player weapon data.", e)); } } else { Log.Error("Error processing battlelog stats for " + aPlayer.GetVerboseName() + ". Stats response did not contain weapon stats construct."); } } else { if (response.Contains("errorpage")) { Log.Warn("Battlelog returning errors, waiting 30 seconds."); _LastBattlelogAction = UtcNow().AddSeconds(30); _LastBattlelogIssue = UtcNow(); return true; } Log.Error("Error processing battlelog stats for " + aPlayer.GetVerboseName() + ". Improper format of stats response."); } if (_useAntiCheatLIVESystem) { //Fetch vehicle stats DoBattlelogWait(); String vehicleResponse = Util.ClientDownloadTimer(client, "http://battlelog.battlefield.com/bf4/en/warsawvehiclesPopulateStats/" + aPlayer.player_battlelog_personaID + "/1/stats/"); Hashtable vehicleResponseData = (Hashtable)JSON.JsonDecode(vehicleResponse); if (vehicleResponseData != null && vehicleResponseData.ContainsKey("type") && (String)vehicleResponseData["type"] == "success" && vehicleResponseData.ContainsKey("message") && (String)vehicleResponseData["message"] == "OK" && vehicleResponseData.ContainsKey("data")) { Hashtable statsData = (Hashtable)vehicleResponseData["data"]; if (statsData != null && statsData.ContainsKey("mainVehicleStats")) { ArrayList vehicleData = (ArrayList)statsData["mainVehicleStats"]; try { //Get Vehicles if (vehicleData != null && vehicleData.Count > 0) { stats.VehicleStats = new Dictionary(); foreach (Hashtable currentWeapon in vehicleData) { //Create new construct AVehicleStat vehicle = new AVehicleStat(); //serviceStars vehicle.ServiceStars = (Double)currentWeapon["serviceStars"]; //serviceStarsProgress vehicle.ServiceStarsProgress = (Double)currentWeapon["serviceStarsProgress"]; //category vehicle.Category = ((String)currentWeapon["category"]).Trim().ToLower().Replace(' ', '_').Replace('-', '_'); //slug vehicle.ID = ((String)currentWeapon["slug"]).Trim().ToLower().Replace(' ', '_').Replace('-', '_'); //name vehicle.WarsawID = (String)currentWeapon["name"]; //kills vehicle.Kills = (Double)currentWeapon["kills"]; //timeIn vehicle.TimeIn = TimeSpan.FromSeconds((Double)currentWeapon["timeIn"]); //Calculate values if (vehicle.TimeIn.TotalMinutes > 0) { vehicle.KPM = vehicle.Kills / vehicle.TimeIn.TotalMinutes; } //Assign the construct stats.VehicleStats.Add(vehicle.ID, vehicle); } } else { Log.Error("Error processing battlelog stats for " + aPlayer.GetVerboseName() + ". Stats response did not contain vehicle stats data."); } } catch (Exception e) { Log.HandleException(new AException("Error while parsing player vehicle data.", e)); } } else { Log.Error("Error processing battlelog stats for " + aPlayer.GetVerboseName() + ". Stats response did not contain vehicle stats construct."); } } else { if (vehicleResponse.Contains("errorpage")) { Log.Warn("Battlelog returning errors, waiting 30 seconds."); _LastBattlelogAction = UtcNow().AddSeconds(30); _LastBattlelogIssue = UtcNow(); return false; } Log.Error("Error processing battlelog stats for " + aPlayer.GetVerboseName() + ". Improper format of stats response."); } } aPlayer.RoundStats[_roundID] = stats; return true; } catch (Exception e) { if (e is WebException) { Log.Warn("Issue connecting to battlelog."); _LastBattlelogAction = UtcNow().AddSeconds(30); _LastBattlelogIssue = UtcNow(); return false; } Log.HandleException(new AException("Error while parsing player stats data.", e)); return false; } } } else if (GameVersion == GameVersionEnum.BFHL) { using (GZipWebClient client = new GZipWebClient()) { if (_UseProxy && !String.IsNullOrEmpty(_ProxyURL)) { client.SetProxy(_ProxyURL); } try { //Fetch stats APlayerStats stats = new APlayerStats(_roundID); DoBattlelogWait(); String weaponResponse = Util.ClientDownloadTimer(client, "http://battlelog.battlefield.com/bfh/BFHWeaponsPopulateStats/" + aPlayer.player_battlelog_personaID + "/1/stats/?cacherand=" + Environment.TickCount); Hashtable responseData = (Hashtable)JSON.JsonDecode(weaponResponse); if (responseData != null && responseData.ContainsKey("type") && (String)responseData["type"] == "success" && responseData.ContainsKey("message") && (String)responseData["message"] == "OK" && responseData.ContainsKey("data")) { Hashtable statsData = (Hashtable)responseData["data"]; if (statsData != null && statsData.ContainsKey("mainWeaponStats")) { ArrayList weaponData = (ArrayList)statsData["mainWeaponStats"]; try { //Get Weapons if (weaponData != null && weaponData.Count > 0) { stats.WeaponStats = new Dictionary(); foreach (Hashtable currentWeapon in weaponData) { //Create new construct AWeaponStat weapon = new AWeaponStat(); //serviceStars weapon.ServiceStars = (Double)currentWeapon["serviceStars"]; //serviceStarsProgress weapon.ServiceStarsProgress = (Double)currentWeapon["serviceStarsProgress"]; //category weapon.Category = ((String)currentWeapon["category"]).Trim().ToLower().Replace(' ', '_'); //categorySID if (currentWeapon.ContainsKey("categorySID")) { weapon.CategorySID = (String)currentWeapon["categorySID"]; } //slug weapon.ID = ((String)currentWeapon["slug"]).Trim().ToLower().Replace(' ', '_'); //name weapon.WarsawID = (String)currentWeapon["name"]; //kills weapon.Kills = (Double)currentWeapon["kills"]; //shotsFired weapon.Shots = (Double)currentWeapon["shotsFired"]; //shotsHit weapon.Hits = (Double)currentWeapon["shotsHit"]; //accuracy weapon.Accuracy = (Double)currentWeapon["accuracy"]; //headshots weapon.Headshots = (Double)currentWeapon["headshots"]; //timeEquipped weapon.Time = TimeSpan.FromSeconds((Double)currentWeapon["timeEquipped"]); //Calculate values weapon.HSKR = weapon.Headshots / weapon.Kills; if (weapon.Time.TotalMinutes > 0) { weapon.KPM = weapon.Kills / weapon.Time.TotalMinutes; } weapon.DPS = weapon.Kills / weapon.Hits * 100; //Assign the construct stats.WeaponStats.Add(weapon.ID, weapon); } } else { Log.Error("Error processing battlelog stats for " + aPlayer.GetVerboseName() + ". Stats response did not contain weapon stats data."); } } catch (Exception e) { Log.HandleException(new AException("Error while parsing player weapon data.", e)); } } else { Log.Error("Error processing battlelog stats for " + aPlayer.GetVerboseName() + ". Stats response did not contain weapon stats construct."); } } else { if (weaponResponse.Contains("errorpage")) { Log.Warn("Battlelog returning errors, waiting 30 seconds."); _LastBattlelogAction = UtcNow().AddSeconds(30); _LastBattlelogIssue = UtcNow(); return false; } Log.Error("Error processing battlelog stats for " + aPlayer.GetVerboseName() + ". Improper format of stats response."); } aPlayer.RoundStats[_roundID] = stats; return true; } catch (Exception e) { if (e is WebException) { Log.Warn("Issue connecting to battlelog."); _LastBattlelogAction = UtcNow().AddSeconds(30); _LastBattlelogIssue = UtcNow(); return false; } Log.HandleException(new AException("Error while parsing player stats data.", e)); return false; } } } return false; } private Boolean PopulateCommandReputationDictionaries() { try { Dictionary sourceDic = new Dictionary(); Dictionary targetDic = new Dictionary(); ArrayList repDefs = FetchAdKatsReputationDefinitions(); if (repDefs == null || repDefs.Count == 0) { return false; } foreach (Hashtable repWeapon in repDefs) { sourceDic[(String)repWeapon["command_typeaction"]] = (double)repWeapon["source_weight"]; targetDic[(String)repWeapon["command_typeaction"]] = (double)repWeapon["target_weight"]; } _commandSourceReputationDictionary = sourceDic; _commandTargetReputationDictionary = targetDic; return true; } catch (Exception e) { Log.HandleException(new AException("Error while populating command reputation cache", e)); } return false; } private ArrayList FetchAdKatsReputationDefinitions() { Log.Debug(() => "Entering FetchAdKatsReputationDefinitions", 7); ArrayList repTable = null; using (GZipWebClient client = new GZipWebClient(compress: false)) { String repInfo; Log.Debug(() => "Fetching reputation definitions...", 2); try { repInfo = Util.ClientDownloadTimer(client, "https://raw.githubusercontent.com/AdKats/AdKats/master/adkatsreputationstats.json" + "?cacherand=" + Environment.TickCount); Log.Debug(() => "Reputation definitions fetched.", 1); } catch (Exception) { try { repInfo = Util.ClientDownloadTimer(client, "https://api.myrcon.net/plugins/adkats/reputation" + "?cacherand=" + Environment.TickCount); Log.Debug(() => "Reputation definitions fetched from backup location.", 1); } catch (Exception) { return null; } } try { repTable = (ArrayList)JSON.JsonDecode(repInfo); } catch (Exception e) { Log.HandleException(new AException("Error while parsing reputation definitions.", e)); } } Log.Debug(() => "Exiting FetchAdKatsReputationDefinitions", 7); return repTable; } private Boolean PopulateSpecialGroupDictionaries() { Log.Debug(() => "Entering PopulateSpecialGroupsDictionary", 7); try { List groupList = FetchASpecialGroupDefinitions(); if (groupList == null || groupList.Count == 0) { return false; } lock (_specialPlayerGroupKeyDictionary) { lock (_specialPlayerGroupIDDictionary) { _specialPlayerGroupIDDictionary.Clear(); _specialPlayerGroupKeyDictionary.Clear(); foreach (ASpecialGroup group in groupList) { _specialPlayerGroupIDDictionary[group.group_id] = group; _specialPlayerGroupKeyDictionary[group.group_key] = group; } } } return true; } catch (Exception e) { Log.HandleException(new AException("Exception while populating special group cache", e)); } Log.Debug(() => "Exiting PopulateSpecialGroupsDictionary", 7); return false; } private List FetchASpecialGroupDefinitions() { Log.Debug(() => "Entering FetchASpecialGroupDefinitions", 7); List SpecialGroupsList = null; using (GZipWebClient client = new GZipWebClient(compress: false)) { String groupInfo; Log.Debug(() => "Fetching special group definitions...", 2); try { groupInfo = Util.ClientDownloadTimer(client, "https://raw.githubusercontent.com/AdKats/AdKats/master/adkatsspecialgroups.json" + "?cacherand=" + Environment.TickCount); Log.Debug(() => "Special group definitions fetched.", 1); } catch (Exception) { try { groupInfo = Util.ClientDownloadTimer(client, "https://api.myrcon.net/plugins/adkats/specialgroups" + "?cacherand=" + Environment.TickCount); Log.Debug(() => "Special group definitions fetched from backup location.", 1); } catch (Exception) { return null; } } try { Hashtable groupsTable = (Hashtable)JSON.JsonDecode(groupInfo); ArrayList GroupsList = (ArrayList)groupsTable["SpecialGroups"]; if (GroupsList == null || GroupsList.Count == 0) { return null; } SpecialGroupsList = new List(); foreach (Hashtable groupHash in GroupsList) { ASpecialGroup update = new ASpecialGroup(); //update_id update.group_id = Convert.ToInt32(groupHash["group_id"]); //group_key Object group_key = groupHash["group_key"]; if (group_key == null) { Log.Error("AdKats special group entry group_key was not found."); continue; } update.group_key = (String)group_key; //group_name Object group_name = groupHash["group_name"]; if (group_name == null) { Log.Error("AdKats special group entry group_name was not found."); continue; } update.group_name = (String)group_name; //Add SpecialGroupsList.Add(update); } } catch (Exception e) { Log.HandleException(new AException("Error while parsing special group definitions.", e)); return null; } } Log.Debug(() => "Exiting FetchASpecialGroupDefinitions", 7); return SpecialGroupsList; } private void RunSQLUpdates(Boolean async) { Log.Debug(() => "Entering RunSQLUpdates", 7); if (Threading.IsAlive("SQLUpdater")) { return; } if (async) { Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "SQLUpdater"; Thread.Sleep(TimeSpan.FromMilliseconds(250)); RunSQLUpdates(); Threading.StopWatchdog(); }))); } else { RunSQLUpdates(); } Log.Debug(() => "Exiting RunSQLUpdates", 7); } private void RunSQLUpdates() { try { if (_databaseConnectionCriticalState) { return; } Int64 currentVersionInt = ConvertVersionInt(PluginVersion); foreach (ASQLUpdate update in FetchSQLUpdates()) { if (!_pluginEnabled) { break; } if (update == null) { Log.Error("SQL update was null. Skipping."); continue; } try { //Check for valid version if (!String.IsNullOrEmpty(update.version_minimum) && currentVersionInt < ConvertVersionInt(update.version_minimum)) { Log.Debug(() => "Cancelling SQL update '" + update.update_id + "'. Version too early for update.", 5); continue; } if (!String.IsNullOrEmpty(update.version_maximum) && currentVersionInt > ConvertVersionInt(update.version_maximum)) { Log.Debug(() => "Cancelling SQL update '" + update.update_id + "'. Version too late for update.", 5); continue; } //Check for valid initial conditions Boolean invalid = false; foreach (String icheckSQL in update.update_checks) { if (!_pluginEnabled) { break; } String checkSQL = icheckSQL.Replace("%DATABASENAME%", _mySqlSchemaName); if (SendQuery(checkSQL, false)) { if (!update.update_checks_hasResults) { //Has results, when it shouldn't invalid = true; break; } } else { if (update.update_checks_hasResults) { //Doesn't have results, when it should invalid = true; break; } } } if (invalid) { Log.Debug(() => "Cancelling SQL update '" + update.update_id + "', it does not apply to this database.", 5); continue; } //Run the updates Int32 executeIndex = 0; Boolean failed = false; foreach (String iexecuteSQL in update.update_execute) { if (!_pluginEnabled) { break; } String executeSQL = iexecuteSQL.Replace("%DATABASENAME%", _mySqlSchemaName); if (!SendNonQuery("Executing SQL Update '" + update.update_id + "' (" + update.message_name + ")" + executeIndex++, executeSQL, false) && update.update_execute_requiresModRows) { failed = true; break; } } if (failed) { Log.Error("Cancelling SQL update '" + update.update_id + "'. Update failed execution (" + update.message_failure + "), running failure clause(s). "); Int32 failureIndex = 0; foreach (String failureSQL in update.update_failure) { SendNonQuery("Running SQL Update '" + update.update_id + "' Failure Clause " + failureIndex++, failureSQL, false); } continue; } Log.Success("SQL Update '" + update.update_id + "' completed execution (" + update.message_success + ")."); Int32 successIndex = 0; foreach (String successSQL in update.update_success) { if (!_pluginEnabled) { break; } SendNonQuery("Running SQL Update '" + update.update_id + "' Success Clause " + successIndex++, successSQL, false); } } catch (Exception e) { Log.HandleException(new AException("Error while running SQL update '" + update.update_id + "'.", e)); } } } catch (Exception e) { Log.HandleException(new AException("Error while processing SQL updates.", e)); } } private List FetchSQLUpdates() { Log.Debug(() => "Entering FetchSQLUpdates", 7); List SQLUpdates = new List(); using (GZipWebClient client = new GZipWebClient(compress: false)) { try { String updateInfo; try { updateInfo = Util.ClientDownloadTimer(client, "https://raw.githubusercontent.com/AdKats/AdKats/master/adkatsupdates.json" + "?cacherand=" + Environment.TickCount); Log.Debug(() => "SQL updates fetched.", 1); } catch (Exception) { try { updateInfo = Util.ClientDownloadTimer(client, "https://api.myrcon.net/plugins/adkats/sqlupdates" + "?cacherand=" + Environment.TickCount); Log.Debug(() => "SQL updates fetched from backup location.", 1); } catch (Exception) { Log.Error("Unable to download SQL updates."); return SQLUpdates; } } Hashtable updateTable = (Hashtable)JSON.JsonDecode(updateInfo); ArrayList SQLUpdateList = (ArrayList)updateTable["SQLUpdates"]; if (SQLUpdateList != null && SQLUpdateList.Count > 0) { Log.Debug(() => "SQL updates found. Parsing...", 5); foreach (Hashtable updateHash in SQLUpdateList) { ASQLUpdate update = new ASQLUpdate(); //update_id update.update_id = (String)updateHash["update_id"]; if (String.IsNullOrEmpty(update.update_id)) { Log.Error("SQL update update_id was not found or empty."); continue; } Log.Debug(() => "Parsing SQL Update '" + update.update_id + "'", 5); //version_minimum update.version_minimum = (String)updateHash["version_minimum"]; Log.Debug(() => "SQL update '" + update.update_id + "' version_minimum: " + update.version_minimum, 5); //version_maximum update.version_maximum = (String)updateHash["version_maximum"]; Log.Debug(() => "SQL update '" + update.update_id + "' version_maximum: " + update.version_maximum, 5); //message_name update.message_name = (String)updateHash["message_name"]; if (String.IsNullOrEmpty(update.message_name)) { Log.Error("SQL update '" + update.update_id + "' message_name was not found or empty."); continue; } Log.Debug(() => "SQL update '" + update.update_id + "' message_name: " + update.message_name, 5); //message_success update.message_success = (String)updateHash["message_success"]; if (String.IsNullOrEmpty(update.message_success)) { Log.Error("SQL update '" + update.update_id + "' message_success was not found or empty."); continue; } Log.Debug(() => "SQL update '" + update.update_id + "' message_success: " + update.message_success, 5); //message_failure update.message_failure = (String)updateHash["message_failure"]; if (String.IsNullOrEmpty(update.message_failure)) { Log.Error("SQL update '" + update.update_id + "' message_failure was not found or empty."); continue; } Log.Debug(() => "SQL update '" + update.update_id + "' message_failure: " + update.message_failure, 5); //update_checks_hasResults Object update_checks_hasResults = updateHash["update_checks_hasResults"]; if (update_checks_hasResults == null) { Log.Error("SQL update '" + update.update_id + "' update_checks_hasResults was not found."); continue; } update.update_checks_hasResults = (Boolean)update_checks_hasResults; Log.Debug(() => "SQL update '" + update.update_id + "' update_checks_hasResults: " + update.update_checks_hasResults, 5); //update_checks ArrayList update_checks = (ArrayList)updateHash["update_checks"]; if (update_checks == null) { Log.Error("SQL update '" + update.update_id + "' update_checks was not found."); continue; } foreach (String line in update_checks) { update.update_checks.Add(line); } Log.Debug(() => "SQL update '" + update.update_id + "' update_checks: " + update.update_checks.Count, 5); //update_execute_requiresModRows Object update_execute_requiresModRows = updateHash["update_execute_requiresModRows"]; if (update_execute_requiresModRows == null) { Log.Error("SQL update '" + update.update_id + "' update_execute_requiresModRows was not found."); continue; } update.update_execute_requiresModRows = (Boolean)update_execute_requiresModRows; Log.Debug(() => "SQL update '" + update.update_id + "' update_execute_requiresModRows: " + update.update_execute_requiresModRows, 5); //update_execute ArrayList update_execute = (ArrayList)updateHash["update_execute"]; if (update_execute == null) { Log.Error("SQL update '" + update.update_id + "' update_execute was not found."); continue; } foreach (String line in update_execute) { update.update_execute.Add(line); } Log.Debug(() => "SQL update '" + update.update_id + "' update_execute: " + update.update_execute.Count, 5); //update_success ArrayList update_success = (ArrayList)updateHash["update_success"]; if (update_success == null) { Log.Error("SQL update '" + update.update_id + "' update_success was not found."); continue; } foreach (String line in update_success) { update.update_success.Add(line); } Log.Debug(() => "SQL update '" + update.update_id + "' update_success: " + update.update_success.Count, 5); //update_failure ArrayList update_failure = (ArrayList)updateHash["update_failure"]; if (update_failure == null) { Log.Error("SQL update '" + update.update_id + "' update_failure was not found."); continue; } foreach (String line in update_failure) { update.update_failure.Add(line); } Log.Debug(() => "SQL update '" + update.update_id + "' update_failure: " + update.update_failure.Count, 5); //Add SQLUpdates.Add(update); } } else { Log.Debug(() => "No SQL updates found.", 5); } } catch (Exception) { Log.Error("Unable to process SQL updates."); } } Log.Debug(() => "Exiting FetchSQLUpdates", 7); return SQLUpdates; } private void PushThreadDebug(Int64 ticks, String thread, Int32 threadid, Int32 line, String element) { try { Log.Debug(() => ticks + " " + thread + " " + threadid + " " + line + " " + element, 8); } catch (Exception e) { Log.HandleException(new AException("error pushing thread debug", e)); } } private String ReplacePlayerInformation(String originalString, APlayer aPlayer) { String processedString = ""; if (String.IsNullOrEmpty(originalString)) { return processedString; } //Create new instance of original string processedString += originalString; if (aPlayer == null) { return processedString; } if (aPlayer.player_id > 0) { processedString = processedString.Replace("%player_id%", aPlayer.player_id.ToString()); } if (!String.IsNullOrEmpty(aPlayer.player_name)) { processedString = processedString.Replace("%player_name%", aPlayer.player_name); } if (!String.IsNullOrEmpty(aPlayer.player_guid)) { processedString = processedString.Replace("%player_guid%", aPlayer.player_guid); } if (!String.IsNullOrEmpty(aPlayer.player_pbguid)) { processedString = processedString.Replace("%player_pbguid%", aPlayer.player_pbguid); } if (!String.IsNullOrEmpty(aPlayer.player_ip)) { processedString = processedString.Replace("%player_ip%", aPlayer.player_ip); } return processedString; } public Boolean UserIsAdmin(AUser aUser) { return aUser != null && RoleIsAdmin(aUser.user_role); } public Boolean PlayerIsAdmin(APlayer aPlayer) { return aPlayer != null && aPlayer.player_role != null && RoleIsAdmin(aPlayer.player_role); } public Boolean PlayerIsExternal(APlayer aPlayer) { return aPlayer.player_server.ServerID != _serverInfo.ServerID; } public Boolean RoleIsAdmin(ARole aRole) { try { if (aRole == null) { Log.Error("role null in RoleIsAdmin"); return false; } if (aRole.RoleAllowedCommands.Values.Any(command => command.command_playerInteraction)) { return true; } } catch (Exception e) { Log.HandleException(new AException("Error fetching role admin status.", e)); } return false; } public Boolean PlayerIsWinning(APlayer aPlayer) { try { if (aPlayer.fbpInfo == null) { return false; } //Team Info Check ATeam team1, team2; if (!GetTeamByID(1, out team1)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } return false; } if (!GetTeamByID(2, out team2)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } return false; } ATeam winningTeam, losingTeam; if (team1.TeamTicketCount > team2.TeamTicketCount) { winningTeam = team1; losingTeam = team2; } else { winningTeam = team2; losingTeam = team1; } return aPlayer.fbpInfo.TeamID == winningTeam.TeamID; } catch (Exception e) { Log.HandleException(new AException("Error fetching player winning status.", e)); } return false; } public String GetChatCommandByKey(String commandKey) { try { var command = GetCommandByKey(commandKey); if (command != null) { return "!" + command.command_text; } } catch (Exception e) { Log.HandleException(new AException("Error fetching chat command by key.", e)); } return "ERROR48610"; } public ACommand GetCommandByKey(String commandKey) { ACommand command = null; try { if (String.IsNullOrEmpty(commandKey)) { Log.HandleException(new AException("commandKey was null when fetching command")); return command; } if (!_CommandKeyDictionary.TryGetValue(commandKey, out command)) { Threading.Wait(1000); if (!_CommandKeyDictionary.TryGetValue(commandKey, out command)) { Log.HandleException(new AException("Unable to get command for key '" + commandKey + "'")); } } } catch (Exception e) { Log.HandleException(new AException("Error fetching command by key.", e)); } return command; } public String GetPlayerTeamKey(APlayer aPlayer) { String teamKey = "UKN"; if (aPlayer == null || aPlayer.fbpInfo == null) { return teamKey; } ATeam aTeam; if (GetTeamByID(aPlayer.fbpInfo.TeamID, out aTeam)) { return aTeam.TeamKey; } return teamKey; } public String GetPlayerTeamName(APlayer aPlayer) { String teamName = "Unknown"; if (aPlayer == null || aPlayer.fbpInfo == null) { return teamName; } ATeam aTeam; if (GetTeamByID(aPlayer.fbpInfo.TeamID, out aTeam)) { return aTeam.TeamName; } return teamName; } public Boolean GetTeamByID(Int32 teamID, out ATeam aTeam) { aTeam = null; if (_teamDictionary.TryGetValue(teamID, out aTeam)) { return true; } if (_roundState == RoundState.Playing) { Log.HandleException(new AException("Team not found for ID " + teamID + " in dictionary of " + _teamDictionary.Count + " teams.")); } return false; } public String ExtractString(String s, String tag) { if (String.IsNullOrEmpty(s) || String.IsNullOrEmpty(tag)) { Log.Error("Unable to extract string '" + s + "'. Invalid inputs."); return null; } String startTag = "<" + tag + ">"; Int32 startIndex = s.IndexOf(startTag, StringComparison.Ordinal) + startTag.Length; if (startIndex == -1) { Log.Error("Unable to extract string '" + s + "'. Starting tag not found."); return null; } Int32 endIndex = s.IndexOf("", startIndex, StringComparison.Ordinal); if (startIndex == -1) { Log.Error("Unable to extract string '" + s + "'. Ending tag not found."); return null; } return s.Substring(startIndex, endIndex - startIndex); } public Boolean IsSoldierNameValid(String soldierName) { try { Log.Debug(() => "Checking player '" + soldierName + "' for validity.", 7); if (String.IsNullOrEmpty(soldierName)) { Log.Debug(() => "Soldier Name empty or null.", 5); return false; } if (soldierName.Length > 16) { Log.Debug(() => "Soldier Name '" + soldierName + "' too long, maximum length is 16 characters.", 5); return false; } return true; } catch (Exception) { //Soldier id caused exception in the regex, definitely not valid Log.Error("Soldier Name '" + soldierName + "' contained invalid characters."); return false; } } public TimeSpan NowDuration(DateTime diff) { return (UtcNow() - diff).Duration(); } public String FormatNowDuration(DateTime diff, Int32 maxComponents) { return FormatTimeString(NowDuration(diff), maxComponents); } public String FormatTimeString(TimeSpan timeSpan, Int32 maxComponents) { Log.Debug(() => "Entering formatTimeString", 7); String timeString = null; if (maxComponents < 1) { return timeString; } try { String formattedTime = (timeSpan.TotalMilliseconds >= 0) ? ("") : ("-"); Double secondSubset = Math.Abs(timeSpan.TotalSeconds); if (secondSubset < 1) { return "0s"; } Double minuteSubset = (secondSubset / 60); Double hourSubset = (minuteSubset / 60); Double daySubset = (hourSubset / 24); Double weekSubset = (daySubset / 7); Double monthSubset = (weekSubset / 4); Double yearSubset = (monthSubset / 12); int years = (Int32)yearSubset; Int32 months = (Int32)monthSubset % 12; Int32 weeks = (Int32)weekSubset % 4; Int32 days = (Int32)daySubset % 7; Int32 hours = (Int32)hourSubset % 24; Int32 minutes = (Int32)minuteSubset % 60; Int32 seconds = (Int32)secondSubset % 60; Int32 usedComponents = 0; if (years > 0 && usedComponents < maxComponents) { usedComponents++; formattedTime += years + "y"; } if (months > 0 && usedComponents < maxComponents) { usedComponents++; formattedTime += months + "M"; } if (weeks > 0 && usedComponents < maxComponents) { usedComponents++; formattedTime += weeks + "w"; } if (days > 0 && usedComponents < maxComponents) { usedComponents++; formattedTime += days + "d"; } if (hours > 0 && usedComponents < maxComponents) { usedComponents++; formattedTime += hours + "h"; } if (minutes > 0 && usedComponents < maxComponents) { usedComponents++; formattedTime += minutes + "m"; } if (seconds > 0 && usedComponents < maxComponents) { usedComponents++; formattedTime += seconds + "s"; } timeString = formattedTime; } catch (Exception e) { Log.HandleException(new AException("Error while formatting time String.", e)); } if (String.IsNullOrEmpty(timeString)) { timeString = "0s"; } Log.Debug(() => "Exiting formatTimeString", 7); return timeString; } private void RemovePlayerFromDictionary(String playerName, Boolean lockDictionary) { Log.Debug(() => "Entering removePlayerFromDictionary", 7); try { //If the player is currently in the player list, remove them if (!String.IsNullOrEmpty(playerName)) { if (_PlayerDictionary.ContainsKey(playerName)) { Log.Debug(() => "Removing " + playerName + " from current player list.", 4); if (lockDictionary) { lock (_PlayerDictionary) { _PlayerDictionary.Remove(playerName); } } else { _PlayerDictionary.Remove(playerName); } } } } catch (Exception e) { Log.HandleException(new AException("Error while removing player from player dictionary.", e)); } Log.Debug(() => "Exiting removePlayerFromDictionary", 7); } public CPlayerInfo BuildCPlayerInfo(String playerName, String playerGUID) { Log.Debug(() => "Entering ", 7); CPlayerInfo playerInfo = null; try { IList lstParameters = new List(); IList lstVariables = new List(); lstParameters.Add("id"); lstVariables.Add(playerName); lstParameters.Add("guid"); lstVariables.Add(playerGUID); playerInfo = new CPlayerInfo(lstParameters, lstVariables); } catch (Exception e) { Log.HandleException(new AException("Error while creating CPlayerInfo object.", e)); } Log.Debug(() => "Exiting ", 7); return playerInfo; } private TimeSpan GetRemainingBanTime(ABan aBan) { return aBan.ban_endTime.Subtract(UtcNow()); } private String GetPingLimitStatus() { Int32 finalTrigger = 0; Int32 baseTrigger = 0; Int32 modifier = 0; Int32 hour = 0; String population = "Unknown"; if (_pluginEnabled && _firstPlayerListComplete) { if (_serverInfo != null && GetPlayerCount() >= _serverInfo.InfoObject.MaxPlayerCount - 1) { baseTrigger = (Int32)_pingEnforcerFullTriggerMS; hour = DateTime.Now.Hour; modifier = _pingEnforcerFullTimeModifier[DateTime.Now.Hour]; finalTrigger = baseTrigger + modifier; population = "Full"; } else if (_populationStatus == PopulationState.High) { baseTrigger = (Int32)_pingEnforcerHighTriggerMS; hour = DateTime.Now.Hour; modifier = _pingEnforcerHighTimeModifier[DateTime.Now.Hour]; finalTrigger = baseTrigger + modifier; population = "High"; } else if (_populationStatus == PopulationState.Medium) { baseTrigger = (Int32)_pingEnforcerMedTriggerMS; hour = DateTime.Now.Hour; modifier = _pingEnforcerMedTimeModifier[DateTime.Now.Hour]; finalTrigger = baseTrigger + modifier; population = "Medium"; } else if (_populationStatus == PopulationState.Low) { baseTrigger = (Int32)_pingEnforcerLowTriggerMS; hour = DateTime.Now.Hour; modifier = _pingEnforcerLowTimeModifier[DateTime.Now.Hour]; finalTrigger = baseTrigger + modifier; population = "Low"; } } return finalTrigger + "ms = " + baseTrigger + "ms [Pop: " + population + "] " + ((modifier >= 0) ? ("add ") : ("remove ")) + Math.Abs(modifier) + "ms [Hour: " + hour + "]"; } private Double GetPingLimit() { Double currentTriggerMS = 1000; if (_pluginEnabled && _firstPlayerListComplete) { if (_serverInfo != null && GetPlayerCount() >= _serverInfo.InfoObject.MaxPlayerCount - 1) { currentTriggerMS = _pingEnforcerFullTriggerMS + _pingEnforcerFullTimeModifier[DateTime.Now.Hour]; } else if (_populationStatus == PopulationState.High) { currentTriggerMS = _pingEnforcerHighTriggerMS + _pingEnforcerHighTimeModifier[DateTime.Now.Hour]; } else if (_populationStatus == PopulationState.Medium) { currentTriggerMS = _pingEnforcerMedTriggerMS + _pingEnforcerMedTimeModifier[DateTime.Now.Hour]; } else if (_populationStatus == PopulationState.Low) { currentTriggerMS = _pingEnforcerLowTriggerMS + _pingEnforcerLowTimeModifier[DateTime.Now.Hour]; } } return currentTriggerMS; } public DateTime UtcNow() { return DateTime.UtcNow + _dbTimingOffset; } public void KickPlayerMessage(APlayer player, String message) { var kickDuration = 0; if (GameVersion == GameVersionEnum.BF4) { kickDuration = 30; if (player.player_spawnedOnce) { kickDuration = 6; } } KickPlayerMessage(player.player_name, message, kickDuration); } public void KickPlayerMessage(String playerName, String message, Int32 kickDuration) { ExecuteCommand("procon.protected.send", "admin.killPlayer", playerName); if (kickDuration > 0) { // Cannot just kick the player, they don't see the kick message Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "KickPlayerMessage"; var startTime = UtcNow(); while (NowDuration(startTime).TotalSeconds < kickDuration) { PlayerTellMessage(playerName, "KICKED for " + message, false, 1); Thread.Sleep(500); } ExecuteCommand("procon.protected.send", "admin.kickPlayer", playerName, message); Threading.StopWatchdog(); }))); } else { ExecuteCommand("procon.protected.send", "admin.kickPlayer", playerName, message); } } public void BanKickPlayerMessage(APlayer aPlayer, String message) { Int32 kickDuration = 0; if (GameVersion == GameVersionEnum.BF4 && _BanEnforcerBF4LenientKick) { kickDuration = 30; if (aPlayer.player_spawnedOnce) { kickDuration = 6; } if (aPlayer.BanEnforceCount > 2) { kickDuration = 0; } } BanKickPlayerMessage(aPlayer, message, kickDuration); } public void BanKickPlayerMessage(APlayer aPlayer, String message, Int32 kickDuration) { ExecuteCommand("procon.protected.send", "admin.killPlayer", aPlayer.player_name); if (kickDuration > 0) { var wasSpawned = aPlayer.player_spawnedOnce; // Cannot just ban the player, they don't see the ban message Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "BanKickPlayerMessage"; var startTime = UtcNow(); while (NowDuration(startTime).TotalSeconds < kickDuration && // If the player was active, or if the player is new and not spawned, continue spamming (wasSpawned || (!aPlayer.player_spawnedOnce && !aPlayer.player_chatOnce))) { PlayerTellMessage(aPlayer.player_name, "BANNED for " + message, false, 1); Thread.Sleep(500); } ExecuteCommand("procon.protected.send", "admin.kickPlayer", aPlayer.player_name, message); Threading.StopWatchdog(); }))); } else { ExecuteCommand("procon.protected.send", "admin.kickPlayer", aPlayer.player_name, message); } } // HELPERS // Credit Jon Skeet public static DateTime GetNextWeekday(DateTime start, DayOfWeek day) { // The (... + 7) % 7 ensures we end up with a value in the range [0, 6] int daysToAdd = (((int)day - (int)start.DayOfWeek + 7) % 7); return start.AddDays(daysToAdd); } public static String GetRandom32BitHashCode() { String randomString = ""; Random random = new Random(); for (Int32 i = 0; i < 32; i++) { randomString += Convert.ToChar(Convert.ToInt32(Math.Floor(91 * random.NextDouble()))).ToString(CultureInfo.InvariantCulture); } return Encode(randomString); } public static String Encode(String str) { byte[] encbuff = Encoding.UTF8.GetBytes(str); return Convert.ToBase64String(encbuff); } public static String Decode(String str) { byte[] decbuff = Convert.FromBase64String(str.Replace(" ", "+")); return Encoding.UTF8.GetString(decbuff); } public static String EncodeStringArray(String[] strValue) { StringBuilder encodedString = new StringBuilder(); for (Int32 i = 0; i < strValue.Length; i++) { if (i > 0) { encodedString.Append("|"); //strReturn += "|"; } encodedString.Append(Encode(strValue[i])); //strReturn += Encode(strValue[i]); } return encodedString.ToString(); } public byte[] GetBytes(String str) { byte[] bytes = new byte[str.Length * sizeof(char)]; Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length); return bytes; } public String GetString(byte[] bytes) { char[] chars = new char[bytes.Length / sizeof(char)]; Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length); return new String(chars); } // Credit ata/community public String MakeAlphanumeric(String input) { if (String.IsNullOrEmpty(input)) { return input; } var arr = input.ToCharArray(); arr = Array.FindAll(arr, (c => (char.IsLetterOrDigit(c) || char.IsWhiteSpace(c) || c == '-' || c == '_'))); return new string(arr); } //Calling this method will make the settings window refresh with new data public void UpdateSettingPage() { SetExternalPluginSetting("AdKats", "UpdateSettings", "Update"); } //Calls setVariable with the given parameters public void SetExternalPluginSetting(String pluginName, String settingName, String settingValue) { if (String.IsNullOrEmpty(pluginName) || String.IsNullOrEmpty(settingName) || settingValue == null) { Log.Error("Required inputs null or empty in setExternalPluginSetting"); return; } ExecuteCommand("procon.protected.plugins.setVariable", pluginName, settingName, settingValue); } private Int32 GetPlayerCount() { return GetPlayerCount(true, true, true, null); } private Int32 GetPlayerCount(Boolean excludeCommanders, Boolean excludeSpectators, Boolean excludeSeeders, Int32? TeamID) { try { var players = _PlayerDictionary.Values.ToList().Where(aPlayer => aPlayer != null); if (excludeCommanders) { players = players.Where(aPlayer => aPlayer.player_type != PlayerType.CommanderMobile && aPlayer.player_type != PlayerType.CommanderPC); } if (excludeSpectators) { players = players.Where(aPlayer => aPlayer.player_type != PlayerType.Spectator); } if (excludeSeeders) { players = players.Where(aPlayer => NowDuration(aPlayer.lastAction).TotalMinutes < 20); } if (TeamID != null) { players = players.Where(aPlayer => aPlayer.fbpInfo != null && aPlayer.fbpInfo.TeamID == TeamID); } return players.Count(); } catch (Exception e) { Log.HandleException(new AException("Error while fetching player count.", e)); } return 0; } public class ThreadManager { private Logger Log; public ThreadManager(Logger log) { Log = log; } private readonly Dictionary _watchdogThreads = new Dictionary(); private EventWaitHandle _masterWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); public Int32 Count() { return _watchdogThreads.Count(); } public void Init() { _masterWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); } public void Set() { _masterWaitHandle.Set(); } public void Reset() { _masterWaitHandle.Reset(); } public Boolean Wait(Int32 duration) { return _masterWaitHandle.WaitOne(duration); } public Boolean Wait(TimeSpan duration) { return _masterWaitHandle.WaitOne(duration); } public void StartWatchdog(Thread aThread) { try { aThread.Start(); lock (_watchdogThreads) { if (!_watchdogThreads.ContainsKey(aThread.ManagedThreadId)) { _watchdogThreads.Add(aThread.ManagedThreadId, aThread); _masterWaitHandle.WaitOne(100); } } } catch (Exception e) { Log.HandleException(new AException("Error logging thread start.", e)); } } public void StopWatchdog() { try { lock (_watchdogThreads) { _watchdogThreads.Remove(Thread.CurrentThread.ManagedThreadId); } } catch (Exception e) { Log.HandleException(new AException("Error logging thread exit.", e)); } } public void Prune() { try { lock (_watchdogThreads) { var threads = _watchdogThreads.ToList(); foreach (Int32 deadThreadID in threads.Where(threadPair => threadPair.Value == null || !threadPair.Value.IsAlive) .Select(threadPair => threadPair.Value == null ? threadPair.Key : threadPair.Value.ManagedThreadId)) { _watchdogThreads.Remove(deadThreadID); } } } catch (Exception e) { Log.HandleException(new AException("Error pruning watchdog threads.", e)); } } public void Monitor() { try { lock (_watchdogThreads) { if (_watchdogThreads.Count() >= 20) { String aliveThreads = ""; foreach (Thread value in _watchdogThreads.Values.ToList()) { aliveThreads = aliveThreads + (value.Name + "[" + value.ManagedThreadId + "] "); } Log.Warn("Thread warning: " + aliveThreads); } } } catch (Exception e) { Log.HandleException(new AException("Error monitoring watchdog thread count.", e)); } } public Boolean IsAlive(String threadName) { try { lock (_watchdogThreads) { return _watchdogThreads.Values.ToList().Any(aThread => aThread != null && aThread.IsAlive && aThread.Name == threadName); } } catch (Exception e) { Log.HandleException(new AException("Error checking for matching alive thread.", e)); } return false; } public void MonitorShutdown() { try { //Check to make sure all threads have completed and stopped Int32 attempts = 0; Boolean alive = false; do { attempts++; Thread.Sleep(500); alive = false; String aliveThreads = ""; lock (_watchdogThreads) { foreach (Int32 deadThreadID in _watchdogThreads.Values.ToList().Where(thread => !thread.IsAlive).Select(thread => thread.ManagedThreadId).ToList()) { _watchdogThreads.Remove(deadThreadID); } foreach (Thread aliveThread in _watchdogThreads.Values.ToList()) { alive = true; aliveThreads += (aliveThread.Name + "[" + aliveThread.ManagedThreadId + "] "); } } if (aliveThreads.Length > 0) { if (attempts > 20) { Log.Warn("Threads still exiting: " + aliveThreads); } else { Log.Debug(() => "Threads still exiting: " + aliveThreads, 2); } } } while (alive); } catch (Exception e) { Log.HandleException(new AException("Error while monitoring shutdown.", e)); } } } public class GZipWebClient : WebClient { private String ua; private bool compress; public GZipWebClient(String ua = "Mozilla/5.0 (compatible; PRoCon 1; AdKats)", bool compress = true) { this.ua = ua; this.compress = compress; base.Headers["User-Agent"] = ua; } public string GZipDownloadString(string address) { return this.GZipDownloadString(new Uri(address)); } public string GZipDownloadString(Uri address) { base.Headers[HttpRequestHeader.UserAgent] = ua; if (compress == false) return base.DownloadString(address); base.Headers[HttpRequestHeader.AcceptEncoding] = "gzip"; var stream = this.OpenRead(address); if (stream == null) return ""; var contentEncoding = ResponseHeaders[HttpResponseHeader.ContentEncoding]; base.Headers.Remove(HttpRequestHeader.AcceptEncoding); Stream decompressedStream = null; StreamReader reader = null; if (!string.IsNullOrEmpty(contentEncoding) && contentEncoding.ToLower().Contains("gzip")) { decompressedStream = new GZipStream(stream, CompressionMode.Decompress); reader = new StreamReader(decompressedStream); } else { reader = new StreamReader(stream); } var data = reader.ReadToEnd(); reader.Close(); decompressedStream?.Close(); stream.Close(); return data; } public void SetProxy(String proxyURL) { if (!String.IsNullOrEmpty(proxyURL)) { Uri uri = new Uri(proxyURL); this.Proxy = new WebProxy(proxyURL, true); if (!String.IsNullOrEmpty(uri.UserInfo)) { string[] parameters = uri.UserInfo.Split(':'); if (parameters.Length < 2) { return; } this.Proxy.Credentials = new NetworkCredential(parameters[0], parameters[1]); } } } } public class Utilities { private Logger Log; public Utilities(Logger log) { Log = log; } public String ClientDownloadTimer(GZipWebClient wClient, String url) { Log.Debug(() => "Preparing to download from " + GetDomainName(url), 7); Stopwatch timer = new Stopwatch(); timer.Start(); String returnString = wClient.GZipDownloadString(url); timer.Stop(); Log.Debug(() => "Downloaded from " + GetDomainName(url) + " in " + timer.ElapsedMilliseconds + "ms", 7); return returnString; } public string GetDomainName(string url) { string domain = new Uri(url).DnsSafeHost.ToLower(); var tokens = domain.Split('.'); if (tokens.Length > 2) { //Add only second level exceptions to the < 3 rule here string[] exceptions = { "info", "firm", "name", "com", "biz", "gen", "ltd", "web", "net", "pro", "org" }; var validTokens = 2 + ((tokens[tokens.Length - 2].Length < 3 || exceptions.Contains(tokens[tokens.Length - 2])) ? 1 : 0); domain = string.Join(".", tokens, tokens.Length - validTokens, validTokens); } return domain; } //Credit to Imisnew2, grabbed from TS3Sync public Double PercentMatch(String s, String t) { if (String.IsNullOrEmpty(s) || String.IsNullOrEmpty(t)) { return 0.0; } Double max; Double min; Int32 distance; if (s.Length >= t.Length) { max = s.Length; min = t.Length; distance = LevenshteinDistance(s, t); } else { max = t.Length; min = s.Length; distance = LevenshteinDistance(t, s); } double percent = (max - distance) / max; double maxPossMatch = min / max; return (percent / maxPossMatch) * 100; } //Credit to Micovery and PapaCharlie9 for modified Levenshtein Distance algorithm public Int32 LevenshteinDistance(String s, String t) { s = s.ToLower(); t = t.ToLower(); Int32 n = s.Length; Int32 m = t.Length; int[,] d = new Int32[n + 1, m + 1]; if (n == 0) { return m; } if (m == 0) { return n; } for (Int32 i = 0; i <= n; d[i, 0] = i++) { ; } for (Int32 j = 0; j <= m; d[0, j] = j++) { ; } for (Int32 i = 1; i <= n; i++) { for (Int32 j = 1; j <= m; j++) { d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 0), d[i - 1, j - 1] + ((t[j - 1] == s[i - 1]) ? 0 : 1)); } } return d[n, m]; } //parses single word or number parameters out of a String until param count is reached public String[] ParseParameters(String message, Int32 maxParamCount) { //create list for parameters List parameters = new List(); if (message.Length > 0) { //Add all single word/number parameters String[] paramSplit = message.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); Int32 maxLoop = (paramSplit.Length < maxParamCount) ? (paramSplit.Length) : (maxParamCount); for (Int32 i = 0; i < maxLoop - 1; i++) { Log.Debug(() => "Param " + i + ": " + paramSplit[i], 6); parameters.Add(paramSplit[i]); message = message.TrimStart(paramSplit[i].ToCharArray()).Trim(); } //Add final multi-word parameter parameters.Add(message); } Log.Debug(() => "Num params: " + parameters.Count, 6); return parameters.ToArray(); } } public String GenerateKickReason(ARecord record) { String sourceNameString = "[" + record.source_name + "]"; //Create the full message String fullMessage = record.record_message + " " + sourceNameString; //Trim the kick message if necessary Int32 cutLength = fullMessage.Length - 80; if (cutLength > 0) { String cutReason = record.record_message.Substring(0, record.record_message.Length - cutLength); fullMessage = cutReason + " " + sourceNameString; } return fullMessage; } public String GenerateBanReason(ABan aBan) { String banDurationString; //If ban time > 1000 days just say perm TimeSpan remainingTime = GetRemainingBanTime(aBan); if (remainingTime.TotalDays > 1000) { banDurationString = "[perm]"; } else { banDurationString = "[" + FormatTimeString(remainingTime, 2) + "]"; } String sourceNameString = "[" + aBan.ban_record.source_name + "]"; String banAppendString = ((_UseBanAppend) ? ("[" + _BanAppend + "]") : ("")); //Create the full message String fullMessage = aBan.ban_record.record_message + " " + banDurationString + sourceNameString + banAppendString; //Trim the kick message if necessary Int32 cutLength = fullMessage.Length - 80; if (cutLength > 0) { String cutReason = aBan.ban_record.record_message.Substring(0, aBan.ban_record.record_message.Length - cutLength); fullMessage = cutReason + " " + banDurationString + sourceNameString + banAppendString; } return fullMessage; } public void UpdateOtherPlugins(String dllPath) { //Other plugins //1 - MULTIBalancer - With ColColonCleaner balance mods if (false && _UseExperimentalTools && GameVersion == GameVersionEnum.BF4) { String externalPluginSource; using (GZipWebClient client = new GZipWebClient(compress: false)) { try { externalPluginSource = Util.ClientDownloadTimer(client, "https://raw.githubusercontent.com/ColColonCleaner/multi-balancer/master/MULTIbalancer.cs" + "?cacherand=" + Environment.TickCount); } catch (Exception) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Unable to install/update MULTIBalancer."); } Log.Error("Unable to install/update MULTIBalancer."); _pluginUpdateCaller = null; Threading.StopWatchdog(); return; } } if (String.IsNullOrEmpty(externalPluginSource)) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Downloaded MULTIBalancer source was empty. Unable to install/update MULTIBalancer."); } Log.Error("Downloaded MULTIBalancer source was empty. Unable to install/update MULTIBalancer."); _pluginUpdateCaller = null; Threading.StopWatchdog(); return; } String externalPluginFileName = "MULTIbalancer.cs"; String externalPluginPath = Path.Combine(dllPath.Trim(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }), externalPluginFileName); CompilerResults externalPluginCompileResults = CompilePluginSource(externalPluginSource); if (externalPluginCompileResults.Errors.HasErrors) { foreach (CompilerError errComp in externalPluginCompileResults.Errors) { if (String.Compare(errComp.ErrorNumber, "CS0016", StringComparison.Ordinal) != 0 && errComp.IsWarning == false) { Log.Error(String.Format("\t^1{0} (Line: {1}, C: {2}) {3}: {4}", new object[] { externalPluginFileName, errComp.Line, errComp.Column, errComp.ErrorNumber, errComp.ErrorText })); } } if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Updated MULTIBalancer source could not compile. Unable to install/update MULTIBalancer."); } Log.Error("Updated MULTIBalancer source could not compile. Unable to install/update MULTIBalancer."); _pluginUpdateCaller = null; Threading.StopWatchdog(); return; } Int64 patchedPluginSizeKb = 0; Boolean externalPluginFileWriteFailed = false; Int32 externalPluginWriteAttempts = 0; do { using (FileStream stream = File.Open(externalPluginPath, FileMode.Create)) { if (!stream.CanWrite) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Cannot write updates to MULTIBalancer source file. Unable to install/update MULTIBalancer."); } Log.Error("Cannot write updates to MULTIBalancer source file. Unable to install/update MULTIBalancer."); _pluginUpdateCaller = null; Threading.StopWatchdog(); return; } Byte[] info = new UTF8Encoding(true).GetBytes(externalPluginSource); stream.Write(info, 0, info.Length); } patchedPluginSizeKb = new FileInfo(externalPluginPath).Length / 1024; //There is no way the valid plugin can be less than 1 Kb if (patchedPluginSizeKb < 1) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Write failure on MULTIBalancer update. Attempting write again."); } Log.Error("Write failure on MULTIBalancer update. Attempting write again."); externalPluginFileWriteFailed = true; } else { externalPluginFileWriteFailed = false; } if (++externalPluginWriteAttempts > 5) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Constant failure to write MULTIBalancer update to file. Unable to install/update MULTIBalancer."); } Log.Error("Constant failure to write MULTIBalancer update to file. Unable to install/update MULTIBalancer."); _pluginUpdateCaller = null; Threading.StopWatchdog(); return; } } while (externalPluginFileWriteFailed); if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "MULTIBalancer installed/updated. Plugin size " + patchedPluginSizeKb + "KB"); } Log.Success("MULTIBalancer installed/updated. Plugin size " + patchedPluginSizeKb + "KB"); } } public void UpdateExtensions(String dllPath) { //Extensions //1 - AdKatsLRT - Private Extension - Token Required if (!String.IsNullOrEmpty(_AdKatsLRTExtensionToken)) { String extensionSource; using (GZipWebClient client = new GZipWebClient(compress: false)) { try { extensionSource = Util.ClientDownloadTimer(client, "https://raw.githubusercontent.com/AdKats/AdKats-LRT/master/AdKatsLRT.cs?token=" + _AdKatsLRTExtensionToken + "&cacherand=" + Environment.TickCount); } catch (Exception) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Unable to install/update AdKatsLRT Extension. Connection error, or invalid token."); } Log.Error("Unable to install/update AdKatsLRT Extension. Connection error, or invalid token."); _pluginUpdateCaller = null; Threading.StopWatchdog(); return; } } if (String.IsNullOrEmpty(extensionSource)) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Downloaded AdKatsLRT Extension source was empty. Unable to install/update AdKatsLRT Extension."); } Log.Error("Downloaded AdKatsLRT Extension source was empty. Unable to install/update AdKatsLRT Extension."); _pluginUpdateCaller = null; Threading.StopWatchdog(); return; } String extensionFileName = "AdKatsLRT.cs"; String extensionPath = Path.Combine(dllPath.Trim(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }), extensionFileName); CompilerResults extensionCompileResults = CompilePluginSource(extensionSource); if (extensionCompileResults.Errors.HasErrors) { foreach (CompilerError errComp in extensionCompileResults.Errors) { if (String.Compare(errComp.ErrorNumber, "CS0016", StringComparison.Ordinal) != 0 && errComp.IsWarning == false) { Log.Error(String.Format("\t^1{0} (Line: {1}, C: {2}) {3}: {4}", new object[] { extensionFileName, errComp.Line, errComp.Column, errComp.ErrorNumber, errComp.ErrorText })); } } if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Updated AdKatsLRT Extension source could not compile. Unable to install/update AdKatsLRT Extension."); } Log.Error("Updated AdKatsLRT Extension source could not compile. Unable to install/update AdKatsLRT Extension."); _pluginUpdateCaller = null; Threading.StopWatchdog(); return; } Int64 patchedExtensionSizeKb = 0; Boolean extensionFileWriteFailed = false; Int32 extensionWriteAttempts = 0; do { using (FileStream stream = File.Open(extensionPath, FileMode.Create)) { if (!stream.CanWrite) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Cannot write updates to AdKatsLRT Extension source file. Unable to install/update AdKatsLRT Extension."); } Log.Error("Cannot write updates to AdKatsLRT Extension source file. Unable to install/update AdKatsLRT Extension."); _pluginUpdateCaller = null; Threading.StopWatchdog(); return; } Byte[] info = new UTF8Encoding(true).GetBytes(extensionSource); stream.Write(info, 0, info.Length); } patchedExtensionSizeKb = new FileInfo(extensionPath).Length / 1024; //There is no way the valid extension can be less than 1 Kb if (patchedExtensionSizeKb < 1) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Write failure on AdKatsLRT Extension update. Attempting write again."); } Log.Error("Write failure on AdKatsLRT Extension update. Attempting write again."); extensionFileWriteFailed = true; } else { extensionFileWriteFailed = false; } if (++extensionWriteAttempts > 5) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Constant failure to write AdKatsLRT Extension update to file. Unable to install/update AdKatsLRT Extension."); } Log.Error("Constant failure to write AdKatsLRT Extension update to file. Unable to install/update AdKatsLRT Extension."); _pluginUpdateCaller = null; Threading.StopWatchdog(); return; } } while (extensionFileWriteFailed); if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "AdKatsLRT Extension installed/updated. Extension size " + patchedExtensionSizeKb + "KB"); } Log.Success("AdKatsLRT Extension installed/updated. Extension size " + patchedExtensionSizeKb + "KB"); } } public void CheckForPluginUpdates(Boolean manual) { try { if ((_pluginVersionStatus == VersionStatus.OutdatedBuild && !_automaticUpdatesDisabled && !_pluginUpdatePatched) || _pluginVersionStatus == VersionStatus.TestBuild || (!String.IsNullOrEmpty(_AdKatsLRTExtensionToken)) || manual) { if (Threading.IsAlive("PluginUpdater")) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Update already in progress."); } _pluginUpdateCaller = null; Threading.StopWatchdog(); return; } Thread pluginUpdater = new Thread(new ThreadStart(delegate { try { Thread.CurrentThread.Name = "PluginUpdater"; _pluginUpdateProgress = "Started"; String dllPath = Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName; //Other plugins UpdateOtherPlugins(dllPath); //AdKats Extensions UpdateExtensions(dllPath); if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Preparing to download plugin update."); } if (_pluginVersionStatus == VersionStatus.OutdatedBuild) { Log.Info("Preparing to download plugin update to version " + _latestPluginVersion); } String pluginSource = null; using (GZipWebClient client = new GZipWebClient(compress: false)) { try { string stableURL = "https://raw.githubusercontent.com/AdKats/AdKats/master/AdKats.cs" + "?cacherand=" + Environment.TickCount; string testURL = "https://raw.githubusercontent.com/AdKats/AdKats/test/AdKats.cs" + "?cacherand=" + Environment.TickCount; if (_pluginVersionStatus == VersionStatus.OutdatedBuild) { pluginSource = Util.ClientDownloadTimer(client, stableURL); } else { pluginSource = Util.ClientDownloadTimer(client, testURL); } } catch (Exception) { try { string stableURL = "https://api.myrcon.net/plugins/adkats/branch/master" + "?cacherand=" + Environment.TickCount; string testURL = "https://api.myrcon.net/plugins/adkats/branch/test" + "?cacherand=" + Environment.TickCount; if (_pluginVersionStatus == VersionStatus.OutdatedBuild) { pluginSource = Util.ClientDownloadTimer(client, stableURL); } else { pluginSource = Util.ClientDownloadTimer(client, testURL); } } catch (Exception) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Unable to download plugin update."); } if (_pluginVersionStatus == VersionStatus.OutdatedBuild) { Log.Error("Unable to download plugin update to version " + _latestPluginVersion); } _pluginUpdateCaller = null; Threading.StopWatchdog(); return; } } } if (String.IsNullOrEmpty(pluginSource)) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Downloaded plugin source was empty. Cannot update."); } if (_pluginVersionStatus == VersionStatus.OutdatedBuild) { Log.Error("Downloaded plugin source was empty. Cannot update to version " + _latestPluginVersion); } _pluginUpdateCaller = null; Threading.StopWatchdog(); return; } _pluginUpdateProgress = "Downloaded"; if (_pluginVersionStatus == VersionStatus.OutdatedBuild) { Log.Success("Updated plugin source downloaded."); Log.Info("Preparing test compile on updated plugin source."); } String pluginFileName = "AdKats.cs"; String pluginPath = null; if (Environment.OSVersion.Platform.ToString().ToLower().StartsWith("unix")) { pluginPath = Path.Combine(dllPath, pluginFileName); } else { pluginPath = Path.Combine(dllPath.Trim(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }), pluginFileName); } CompilerResults compileResults = CompilePluginSource(pluginSource); if (compileResults.Errors.HasErrors) { foreach (CompilerError errComp in compileResults.Errors) { if (String.Compare(errComp.ErrorNumber, "CS0016", StringComparison.Ordinal) != 0 && errComp.IsWarning == false) { Log.Error(String.Format("\t^1{0} (Line: {1}, C: {2}) {3}: {4}", new object[] { pluginFileName, errComp.Line, errComp.Column, errComp.ErrorNumber, errComp.ErrorText })); } } if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Updated plugin source could not compile. Cannot update."); } if (_pluginVersionStatus == VersionStatus.OutdatedBuild) { Log.Error("Updated plugin source could not compile. Cannot update to version " + _latestPluginVersion); } _pluginUpdateCaller = null; Threading.StopWatchdog(); return; } if (_pluginVersionStatus == VersionStatus.OutdatedBuild) { Log.Success("Plugin update compiled successfully."); } _pluginUpdateProgress = "Compiled"; if (_pluginVersionStatus == VersionStatus.OutdatedBuild) { Log.Info("Preparing to update source file on disk."); } Int64 originalSizeKb = new FileInfo(pluginPath).Length / 1024; Int64 patchedSizeKB = 0; Boolean fileWriteFailed = false; Int32 attempts = 0; do { using (FileStream stream = File.Open(pluginPath, FileMode.Create)) { if (!stream.CanWrite) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Cannot write updates to source file. Cannot update."); } Log.Error("Cannot write updates to source file. Cannot update."); _pluginUpdateCaller = null; Threading.StopWatchdog(); return; } Byte[] info = new UTF8Encoding(true).GetBytes(pluginSource); stream.Write(info, 0, info.Length); } patchedSizeKB = new FileInfo(pluginPath).Length / 1024; //There is no way the valid plugin can be less than 1 Kb if (patchedSizeKB < 1) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Write failure on plugin update. Attempting write again."); } Log.Error("Write failure on plugin update. Attempting write again."); Thread.Sleep(500); fileWriteFailed = true; } else { fileWriteFailed = false; } if (++attempts > 5) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Constant failure to write plugin update to file. Cannot update."); } Log.Error("Constant failure to write plugin update to file. Cannot update."); _pluginUpdateCaller = null; Threading.StopWatchdog(); return; } } while (fileWriteFailed); String patchedVersion = ExtractString(pluginSource, "version_code"); if (!String.IsNullOrEmpty(patchedVersion)) { Int64 patchedVersionInt = ConvertVersionInt(patchedVersion); if (patchedVersionInt >= _currentPluginVersionInt) { //Patched version is newer than current version if (patchedVersionInt > _pluginPatchedVersionInt && _pluginUpdatePatched) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Previous update " + _pluginPatchedVersion + " overwritten by newer patch " + patchedVersion + ", restart procon to run this version. Plugin size " + patchedSizeKB + "KB"); } //Patched version is newer than an already patched version Log.Success("Previous update " + _pluginPatchedVersion + " overwritten by newer patch " + patchedVersion + ", restart procon to run this version. Plugin size " + patchedSizeKB + "KB"); if (_UseExperimentalTools && !EventActive()) { if (NowDuration(_proconStartTime).TotalMinutes < 3) { Threading.Wait(1000); Environment.Exit(2232); } else { // Tell the layer to reboot at round end Log.Warn("Procon will be shut down on the next level load."); _LevelLoadShutdown = true; } } } else if (!_pluginUpdatePatched && patchedVersionInt > _currentPluginVersionInt) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Plugin updated to version " + patchedVersion + ", restart procon to run this version. Plugin size " + patchedSizeKB + "KB"); } //User not notified of patch yet Log.Success("Plugin updated to version " + patchedVersion + ", restart procon to run this version. Plugin size " + patchedSizeKB + "KB"); Log.Success("Updated plugin file located at: " + pluginPath); if (_UseExperimentalTools && !EventActive()) { if (NowDuration(_proconStartTime).TotalMinutes < 3) { Threading.Wait(1000); Environment.Exit(2232); } else { // Tell the layer to reboot at round end Log.Warn("Procon will be shut down on the next level load."); _LevelLoadShutdown = true; } } } else { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Plugin updated to same version, " + patchedVersion + ". Plugin size " + patchedSizeKB + "KB"); } } } else if (!_pluginUpdatePatched) { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Plugin reverted to previous version " + patchedVersion + ", restart procon to run this version. Plugin size " + patchedSizeKB + "KB"); } //Patched version is older than current version Log.Warn("Plugin reverted to previous version " + patchedVersion + ", restart procon to run this version. Plugin size " + patchedSizeKB + "KB"); } _pluginPatchedVersion = patchedVersion; _pluginPatchedVersionInt = patchedVersionInt; } else { if (_pluginUpdateCaller != null) { SendMessageToSource(_pluginUpdateCaller, "Plugin update patched, but its version could not be extracted. Plugin size " + patchedSizeKB + "KB"); } Log.Warn("Plugin update patched, but its version could not be extracted. Plugin size " + patchedSizeKB + "KB"); } _pluginUpdateProgress = "Patched"; _pluginUpdatePatched = true; } catch (Exception e) { Log.HandleException(new AException("Error while running update thread.", e)); } _pluginUpdateCaller = null; Threading.StopWatchdog(); })); Threading.StartWatchdog(pluginUpdater); } } catch (Exception e) { Log.HandleException(new AException("Error while updating plugin source to latest version", e)); } } public void ProconChatWrite(String msg) { msg = msg.Replace(Environment.NewLine, " "); ExecuteCommand("procon.protected.chat.write", Log.COrange("AdKats") + " > " + msg); } public void PrintPreparedCommand(MySqlCommand cmd) { String query = cmd.Parameters.Cast() .Aggregate(cmd.CommandText, (current, p) => current.Replace(p.ParameterName, (p.Value != null) ? ("\"" + p.Value.ToString() + "\"") : ("NULL"))); Log.Write(query); } public static DateTime GetEpochTime() { return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); } public static DateTime GetLocalEpochTime() { return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Local); } public DateTime DateTimeFromEpochSeconds(Double epochSeconds) { return GetEpochTime().AddSeconds(epochSeconds); } public class Logger { private readonly AdKats _plugin; public Int32 DebugLevel { get; set; } public Boolean VerboseErrors { get; set; } public Logger(AdKats plugin) { _plugin = plugin; } private void WriteConsole(String msg) { _plugin.ExecuteCommand("procon.protected.pluginconsole.write", "[^b" + _plugin.GetType().Name + "^n] " + msg); } private void WriteChat(String msg) { _plugin.ExecuteCommand("procon.protected.chat.write", COrange(_plugin.GetType().Name) + " > " + msg); } public void Debug(Func messageFunc, Int32 level) { try { if (DebugLevel >= level) { if (DebugLevel >= 8) { WriteConsole("[" + level + "-" + new StackFrame(1).GetMethod().Name + "-" + ((String.IsNullOrEmpty(Thread.CurrentThread.Name)) ? ("Main") : (Thread.CurrentThread.Name)) + "-" + Thread.CurrentThread.ManagedThreadId + "] " + messageFunc()); } else { WriteConsole(messageFunc()); } } } catch (Exception e) { WriteConsole("Error writing debug message. " + e.ToString()); } } public void Write(String msg) { WriteConsole(msg); } public void Info(String msg) { WriteConsole("^b^0INFO^n^0: " + msg); } public void Warn(String msg) { WriteConsole("^b^3WARNING^n^0: " + msg); } public void Error(String msg) { if (VerboseErrors) { //Opening WriteConsole("^b^1ERROR-" +//Plugin version Int32.Parse(_plugin.GetPluginVersion().Replace(".", "")) + "-" +//Method name new StackFrame(1).GetMethod().Name + "-" +//Thread ((String.IsNullOrEmpty(Thread.CurrentThread.Name)) ? ("Main") : (Thread.CurrentThread.Name)) + Thread.CurrentThread.ManagedThreadId +//Closing "^n^0: " +//Error Message "[" + msg + "]"); } else { //Opening WriteConsole("^b^1ERROR-" +//Plugin version Int32.Parse(_plugin.GetPluginVersion().Replace(".", "")) +//Closing "^n^0: " +//Error Message "[" + msg + "]"); } } public void Success(String msg) { WriteConsole("^b^2SUCCESS^n^0: " + msg); } public String Exception(String msg, Exception e, Int32 level) { //Opening string exceptionMessage = "^b^8EXCEPTION-" +//Plugin version Int32.Parse(_plugin.GetPluginVersion().Replace(".", "")); if (_plugin._firstPlayerListComplete) { exceptionMessage += "-A" + Math.Round(_plugin.NowDuration(_plugin._AdKatsRunningTime).TotalHours, 2); } else { exceptionMessage += "-P" + Math.Round(_plugin.NowDuration(_plugin._proconStartTime).TotalHours, 2); } if (e != null) { exceptionMessage += "-"; Int64 impericalLineNumber = 0; Int64 parsedLineNumber = 0; StackTrace stack = new StackTrace(e, true); if (stack.FrameCount > 0) { impericalLineNumber = stack.GetFrame(0).GetFileLineNumber(); } Int64.TryParse(e.ToString().Split(' ').Last(), out parsedLineNumber); if (impericalLineNumber != 0) { exceptionMessage += impericalLineNumber; } else if (parsedLineNumber != 0) { exceptionMessage += parsedLineNumber; } else { exceptionMessage += "D"; } } exceptionMessage += "-" +//Method name new StackFrame(level + 1).GetMethod().Name + "-" +//Thread ((String.IsNullOrEmpty(Thread.CurrentThread.Name)) ? ("Main") : (Thread.CurrentThread.Name)) + Thread.CurrentThread.ManagedThreadId +//Closing "^n^0: " +//Message "[" + msg + "]" +//Exception string ((e != null) ? ("[" + e + "]") : ("")); WriteConsole(exceptionMessage); return exceptionMessage; } public AException HandleException(AException aException) { //If it's null or AdKats isn't enabled, just return if (aException == null) { Error("Attempted to handle exception when none was given."); return null; } if (!_plugin._pluginEnabled) { return aException; } //Check if the exception attributes to the database if (aException.InternalException != null && (aException.InternalException is TimeoutException || aException.InternalException.ToString().Contains("Unable to connect to any of the specified MySQL hosts") || aException.InternalException.ToString().Contains("Reading from the stream has failed.") || aException.InternalException.ToString().Contains("Too many connections") || aException.InternalException.ToString().Contains("Timeout expired") || aException.InternalException.ToString().Contains("An existing connection was forcibly closed by the remote host") || aException.InternalException.ToString().Contains("Unable to read data") || aException.InternalException.ToString().Contains("Lock wait timeout exceeded"))) { _plugin.HandleDatabaseConnectionInteruption(); } else if (aException.InternalException != null && aException.InternalException is MySqlException && (((MySqlException)aException.InternalException).Number == 1205 || ((MySqlException)aException.InternalException).Number == 1213)) { //Deadlock related. Do nothing. } else { var exceptionString = Exception(aException.Message, aException.InternalException, 1); //Create the Exception record ARecord record = new ARecord { record_source = ARecord.Sources.Automated, isDebug = true, server_id = _plugin._serverInfo.ServerID, command_type = _plugin.GetCommandByKey("adkats_exception"), command_numeric = Int32.Parse(_plugin.GetPluginVersion().Replace(".", "")), target_name = "AdKats", target_player = null, source_name = "AdKats", record_message = FClear(exceptionString), record_time = _plugin.UtcNow() }; //Process the record _plugin.QueueRecordForProcessing(record); } return aException; } public void Chat(String msg) { msg = msg.Replace(Environment.NewLine, " "); WriteChat(msg); } public String FClear(String msg) { return msg.Replace("^b", "") .Replace("^n", "") .Replace("^i", "") .Replace("^0", "") .Replace("^1", "") .Replace("^2", "") .Replace("^3", "") .Replace("^4", "") .Replace("^5", "") .Replace("^6", "") .Replace("^7", "") .Replace("^8", "") .Replace("^9", ""); } public String FBold(String msg) { return "^b" + msg + "^n"; } public String FItalic(String msg) { return "^i" + msg + "^n"; } public String CMaroon(String msg) { return "^1" + msg + "^0"; } public String CGreen(String msg) { return "^2" + msg + "^0"; } public String COrange(String msg) { return "^3" + msg + "^0"; } public String CBlue(String msg) { return "^4" + msg + "^0"; } public String CBlueLight(String msg) { return "^5" + msg + "^0"; } public String CViolet(String msg) { return "^6" + msg + "^0"; } public String CPink(String msg) { return "^7" + msg + "^0"; } public String CRed(String msg) { return "^8" + msg + "^0"; } public String CGrey(String msg) { return "^9" + msg + "^0"; } } public MySqlDataReader SafeExecuteReader(MySqlCommand command) { Stopwatch watch = new Stopwatch(); watch.Start(); try { MySqlDataReader reader = command.ExecuteReader(); watch.Stop(); if (watch.Elapsed.TotalSeconds > 10 && watch.Elapsed.TotalSeconds > (50 * _DatabaseReadAverageDuration) && _firstPlayerListComplete) { HandleDatabaseConnectionInteruption(); } if (_DatabaseReaderDurations.Count < 25000) { lock (_DatabaseReaderDurations) { _DatabaseReaderDurations.Add(watch.Elapsed.TotalSeconds); _DatabaseReadAverageDuration = _DatabaseReaderDurations.Average(); } } return reader; } catch (Exception e) { try { //If the failure was due to deadlock, wait a short duration and issue again if (e.ToString().ToLower().Contains("deadlock")) { Thread.Sleep(250); //If any further errors thrown, just throw them watch.Reset(); watch.Start(); MySqlDataReader reader = command.ExecuteReader(); watch.Stop(); if (watch.Elapsed.TotalSeconds > 10 && watch.Elapsed.TotalSeconds > (50 * _DatabaseReadAverageDuration) && _firstPlayerListComplete) { HandleDatabaseConnectionInteruption(); } if (_DatabaseReaderDurations.Count < 25000) { lock (_DatabaseReaderDurations) { _DatabaseReaderDurations.Add(watch.Elapsed.TotalSeconds); _DatabaseReadAverageDuration = _DatabaseReaderDurations.Average(); } } return reader; } throw e; } catch (Exception e2) { e = e2; if (e2.GetType() == typeof(TimeoutException) || e2.ToString().Contains("Unable to connect to any of the specified MySQL hosts") || e2.ToString().Contains("Reading from the stream has failed.") || e2.ToString().Contains("Too many connections") || e2.ToString().Contains("Timeout expired") || e2.ToString().Contains("An existing connection was forcibly closed by the remote host") || e2.ToString().Contains("Unable to read data") || e2.ToString().Contains("Lock wait timeout exceeded")) { Log.Info("Average Read: " + Math.Round(_DatabaseReadAverageDuration, 3) + "s " + _DatabaseReaderDurations.Count + " | Average Write: " + Math.Round(_DatabaseWriteAverageDuration, 3) + "s " + _DatabaseNonQueryDurations.Count); PrintPreparedCommand(command); } } throw e; } } public Int32 SafeExecuteNonQuery(MySqlCommand command) { Stopwatch watch = new Stopwatch(); watch.Start(); try { int modified = command.ExecuteNonQuery(); watch.Stop(); if (watch.Elapsed.TotalSeconds > 10 && watch.Elapsed.TotalSeconds > (50 * _DatabaseWriteAverageDuration) && _firstPlayerListComplete) { HandleDatabaseConnectionInteruption(); } if (_DatabaseNonQueryDurations.Count < 25000) { lock (_DatabaseNonQueryDurations) { _DatabaseNonQueryDurations.Add(watch.Elapsed.TotalSeconds); _DatabaseWriteAverageDuration = _DatabaseNonQueryDurations.Average(); } } return modified; } catch (Exception e) { try { //If the failure was due to deadlock, wait a short duration and issue again if (e.ToString().ToLower().Contains("deadlock")) { Thread.Sleep(250); //If any further errors thrown, just throw them watch.Reset(); watch.Start(); int modified = command.ExecuteNonQuery(); watch.Stop(); if (watch.Elapsed.TotalSeconds > 10 && watch.Elapsed.TotalSeconds > (50 * _DatabaseWriteAverageDuration) && _firstPlayerListComplete) { HandleDatabaseConnectionInteruption(); } if (_DatabaseNonQueryDurations.Count < 25000) { lock (_DatabaseNonQueryDurations) { _DatabaseNonQueryDurations.Add(watch.Elapsed.TotalSeconds); _DatabaseWriteAverageDuration = _DatabaseNonQueryDurations.Average(); } } return modified; } throw e; } catch (Exception e2) { e = e2; if (e2.GetType() == typeof(TimeoutException) || e2.ToString().Contains("Unable to connect to any of the specified MySQL hosts") || e2.ToString().Contains("Reading from the stream has failed.") || e2.ToString().Contains("Too many connections") || e2.ToString().Contains("Timeout expired") || e2.ToString().Contains("An existing connection was forcibly closed by the remote host") || e2.ToString().Contains("Unable to read data") || e2.ToString().Contains("Lock wait timeout exceeded")) { Log.Info("Average Read: " + Math.Round(_DatabaseReadAverageDuration, 3) + "s " + _DatabaseReaderDurations.Count + " | Average Write: " + Math.Round(_DatabaseWriteAverageDuration, 3) + "s " + _DatabaseNonQueryDurations.Count); PrintPreparedCommand(command); } } PrintPreparedCommand(command); throw e; } } public void HandleDatabaseConnectionInteruption() { //Only handle these errors if all threads are already functioning normally if (_firstPlayerListComplete) { if (_databaseTimeouts == 0) { _lastDatabaseTimeout = UtcNow(); } ++_databaseTimeouts; if (_databaseTimeouts >= 5) { Log.Warn("Database connection issue detected. Trigger " + _databaseTimeouts + "/" + DatabaseTimeoutThreshold + "."); } //Check for critical state (timeouts > threshold, and last issue less than 1 minute ago) if ((UtcNow() - _lastDatabaseTimeout).TotalSeconds < 60) { if (_databaseTimeouts >= DatabaseTimeoutThreshold) { try { //If the handler is already alive, return if (_DisconnectHandlingThread != null && _DisconnectHandlingThread.IsAlive) { Log.Debug(() => "Attempted to start disconnect handling thread when it was already running.", 2); return; } //Create a new thread to handle the disconnect orchestration _DisconnectHandlingThread = new Thread(new ThreadStart(delegate { try { Thread.CurrentThread.Name = "DisconnectHandling"; //Log the time of critical disconnect DateTime disconnectTime = DateTime.Now; Stopwatch disconnectTimer = new Stopwatch(); disconnectTimer.Start(); //Immediately disable Stat Logger Log.Error("Database connection in critical failure state. Disabling Stat Logger and putting AdKats in Backup Mode."); _databaseConnectionCriticalState = true; ExecuteCommand("procon.protected.plugins.enable", "CChatGUIDStatsLoggerBF3", "False"); ExecuteCommand("procon.protected.plugins.enable", "CChatGUIDStatsLogger", "False"); //Set resolved Boolean restored = false; //Enter loop to check for database reconnection do { //If someone manually disables AdKats, exit everything if (!_pluginEnabled) { Threading.StopWatchdog(); return; } //Wait 15 seconds to retry Threading.Wait(15000); //Check if the connection has been restored restored = DebugDatabaseConnectionActive(); if (!restored) { _databaseSuccess = 0; //Inform the user database still not connectable Log.Error("Database still not accessible. (" + FormatTimeString(disconnectTimer.Elapsed, 3) + " since critical disconnect at " + disconnectTime.ToShortTimeString() + ".)"); } else { _databaseSuccess++; Log.Info("Database connection appears restored, but waiting " + (DatabaseSuccessThreshold - _databaseSuccess) + " more successful connections to restore normal operation."); } } while (_databaseSuccess < DatabaseSuccessThreshold); //Connection has been restored, inform the user disconnectTimer.Stop(); Log.Success("Database connection restored, re-enabling Stat Logger and returning AdKats to Normal Mode."); //Reset timeout counts _databaseSuccess = 0; _databaseTimeouts = 0; //re-enable AdKats and Stat Logger _databaseConnectionCriticalState = false; ExecuteCommand("procon.protected.plugins.enable", "CChatGUIDStatsLoggerBF3", "True"); ExecuteCommand("procon.protected.plugins.enable", "CChatGUIDStatsLogger", "True"); //Clear the player dinctionary, causing all players to be fetched from the database again lock (_PlayerDictionary) { _PlayerDictionary.Clear(); } //Create the Exception record ARecord record = new ARecord { record_source = ARecord.Sources.Automated, isDebug = true, server_id = _serverInfo.ServerID, command_type = GetCommandByKey("adkats_exception"), command_numeric = 0, target_name = "Database", target_player = null, source_name = "AdKats", record_message = "Critical Database Disconnect Handled (" + String.Format("{0:0.00}", disconnectTimer.Elapsed.TotalMinutes) + " minutes). AdKats on server " + _serverInfo.ServerID + " functioning normally again.", record_time = UtcNow() }; //Process the record QueueRecordForProcessing(record); } catch (Exception) { Log.Error("Error handling database disconnect."); } Log.Success("Exiting Critical Disconnect Handler."); Threading.StopWatchdog(); })); //Start the thread Threading.StartWatchdog(_DisconnectHandlingThread); } catch (Exception) { Log.Error("Error while initializing disconnect handling thread."); } } } else { //Reset the current timout count _databaseTimeouts = 0; } _lastDatabaseTimeout = UtcNow(); } else { Log.Debug(() => "Attempted to handle database timeout when threads not running.", 2); } } private Int32 getNukeCount(Int32 teamID) { if (!_nukesThisRound.ContainsKey(teamID)) { _nukesThisRound[teamID] = 0; } return _nukesThisRound[teamID]; } private void incNukeCount(Int32 teamID) { _nukesThisRound[teamID] = getNukeCount(teamID) + 1; } public void StartRoundTimer() { if (_pluginEnabled && _threadsReady) { try { //If the thread is still alive, inform the user and return if (_RoundTimerThread != null && _RoundTimerThread.IsAlive) { Log.Error("Tried to enable a round timer while one was still active."); return; } _RoundTimerThread = new Thread(new ThreadStart(delegate { try { Thread.CurrentThread.Name = "RoundTimer"; Log.Debug(() => "starting round timer", 2); Threading.Wait(5000); int maxRoundTimeSeconds = (Int32)(_maxRoundTimeMinutes * 60); for (Int32 secondsRemaining = maxRoundTimeSeconds; secondsRemaining > 0; secondsRemaining--) { if (_roundState != RoundState.Playing || !_pluginEnabled || !_threadsReady) { return; } if (secondsRemaining == maxRoundTimeSeconds - 60 && secondsRemaining > 60) { AdminTellMessage("Round will end automatically in ~" + (Int32)(secondsRemaining / 60.0) + " minutes."); Log.Debug(() => "Round will end automatically in ~" + (Int32)(secondsRemaining / 60.0) + " minutes.", 3); } else if (secondsRemaining == (maxRoundTimeSeconds / 2) && secondsRemaining > 60) { AdminTellMessage("Round will end automatically in ~" + (Int32)(secondsRemaining / 60.0) + " minutes."); Log.Debug(() => "Round will end automatically in ~" + (Int32)(secondsRemaining / 60.0) + " minutes.", 3); } else if (secondsRemaining == 30) { AdminTellMessage("Round ends in 30 seconds. (Current winning team will win)"); Log.Debug(() => "Round ends in 30 seconds. (Current winning team will win)", 3); } else if (secondsRemaining == 20) { AdminTellMessage("Round ends in 20 seconds. (Current winning team will win)"); Log.Debug(() => "Round ends in 20 seconds. (Current winning team will win)", 3); } else if (secondsRemaining <= 10) { AdminSayMessage("Round ends in..." + secondsRemaining); Log.Debug(() => "Round ends in..." + secondsRemaining, 3); } //Sleep for 1 second Threading.Wait(1000); } ATeam team1, team2; if (!GetTeamByID(1, out team1)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } Threading.StopWatchdog(); return; } if (!GetTeamByID(2, out team2)) { if (_roundState == RoundState.Playing) { Log.Error("Teams not loaded when they should be."); } Threading.StopWatchdog(); return; } if (team1.TeamTicketCount < team2.TeamTicketCount) { ExecuteCommand("procon.protected.send", "mapList.endRound", "2"); Log.Debug(() => "Ended Round (2)", 4); } else { ExecuteCommand("procon.protected.send", "mapList.endRound", "1"); Log.Debug(() => "Ended Round (1)", 4); } } catch (Exception e) { Log.HandleException(new AException("Error in round timer thread.", e)); } Log.Debug(() => "Exiting round timer.", 2); Threading.StopWatchdog(); })); //Start the thread Threading.StartWatchdog(_RoundTimerThread); } catch (Exception e) { Log.HandleException(new AException("Error starting round timer thread.", e)); } } } private void DoBattlelogWait() { try { lock (_battlelogLocker) { var now = UtcNow(); var timeSinceLast = (now - _LastBattlelogAction); var requiredWait = _BattlelogWaitDuration; // Preliminary wait increase when battlelog disconnect is detected if (NowDuration(_LastBattlelogIssue).TotalMinutes < 3) { Threading.Wait(TimeSpan.FromSeconds(20)); } //Wait between battlelog actions if (timeSinceLast < requiredWait) { var remainingWait = requiredWait - timeSinceLast; Log.Debug(() => "Waiting " + ((int)remainingWait.TotalMilliseconds) + "ms to query battlelog.", 6); Threading.Wait(remainingWait); } now = UtcNow(); lock (_BattlelogActionTimes) { _BattlelogActionTimes.Enqueue(now); } _LastBattlelogAction = UtcNow(); } } catch (Exception e) { Log.HandleException(new AException("Error performing battlelog wait.", e)); Threading.Wait(_BattlelogWaitDuration); } } private void DoServerInfoTrigger() { _LastServerInfoTrigger = UtcNow(); ExecuteCommand("procon.protected.send", "serverInfo"); } private void DoPlayerListTrigger() { lock (_PlayerListTriggerTimes) { while (_PlayerListTriggerTimes.Any() && NowDuration(_PlayerListTriggerTimes.Peek()).TotalMinutes > 7.5) { _PlayerListTriggerTimes.Dequeue(); } _LastPlayerListTrigger = UtcNow(); _PlayerListTriggerTimes.Enqueue(_LastPlayerListTrigger); ExecuteCommand("procon.protected.send", "admin.listPlayers", "all"); } } private Double getPlayerListTriggerRate() { lock (_PlayerListTriggerTimes) { while (_PlayerListTriggerTimes.Any() && NowDuration(_PlayerListTriggerTimes.Peek()).TotalMinutes > 7.5) { _PlayerListTriggerTimes.Dequeue(); } return _PlayerListTriggerTimes.Count() / NowDuration(_PlayerListTriggerTimes.Min()).TotalMinutes; } } private void DoPlayerListReceive() { lock (_PlayerListReceiveTimes) { while (_PlayerListReceiveTimes.Any() && NowDuration(_PlayerListReceiveTimes.Peek()).TotalMinutes > 7.5) { _PlayerListReceiveTimes.Dequeue(); } _LastPlayerListReceive = UtcNow(); _PlayerListReceiveTimes.Enqueue(_LastPlayerListReceive); } } private Double getPlayerListReceiveRate() { lock (_PlayerListReceiveTimes) { while (_PlayerListReceiveTimes.Any() && NowDuration(_PlayerListReceiveTimes.Peek()).TotalMinutes > 7.5) { _PlayerListReceiveTimes.Dequeue(); } return _PlayerListReceiveTimes.Count() / NowDuration(_PlayerListReceiveTimes.Min()).TotalMinutes; } } private void DoPlayerListAccept() { lock (_PlayerListAcceptTimes) { while (_PlayerListAcceptTimes.Any() && NowDuration(_PlayerListAcceptTimes.Peek()).TotalMinutes > 7.5) { _PlayerListAcceptTimes.Dequeue(); } _LastPlayerListAccept = UtcNow(); _PlayerListAcceptTimes.Enqueue(_LastPlayerListAccept); } } private Double getPlayerListAcceptRate() { lock (_PlayerListAcceptTimes) { while (_PlayerListAcceptTimes.Any() && NowDuration(_PlayerListAcceptTimes.Peek()).TotalMinutes > 7.5) { _PlayerListAcceptTimes.Dequeue(); } return _PlayerListAcceptTimes.Count() / NowDuration(_PlayerListAcceptTimes.Min()).TotalMinutes; } } private void DoPlayerListProcessed() { lock (_PlayerListProcessedTimes) { while (_PlayerListProcessedTimes.Any() && NowDuration(_PlayerListProcessedTimes.Peek()).TotalMinutes > 7.5) { _PlayerListProcessedTimes.Dequeue(); } _LastPlayerListProcessed = UtcNow(); _PlayerListProcessedTimes.Enqueue(_LastPlayerListProcessed); } } private Double getPlayerListProcessedRate() { lock (_PlayerListProcessedTimes) { while (_PlayerListProcessedTimes.Any() && NowDuration(_PlayerListProcessedTimes.Peek()).TotalMinutes > 7.5) { _PlayerListProcessedTimes.Dequeue(); } return _PlayerListProcessedTimes.Count() / NowDuration(_PlayerListProcessedTimes.Min()).TotalMinutes; } } private void DoIPAPIWait() { try { lock (_IPAPILocker) { var now = UtcNow(); var timeSinceLast = (now - _LastBattlelogAction); var requiredWait = _IPAPIWaitDuration; //Wait between battlelog actions if (timeSinceLast < requiredWait) { var remainingWait = requiredWait - timeSinceLast; Log.Debug(() => "Waiting " + ((int)remainingWait.TotalMilliseconds) + "ms to query IPAPI.", 6); Threading.Wait(remainingWait); } _LastIPAPIAction = UtcNow(); } } catch (Exception e) { Log.HandleException(new AException("Error performing IPAPI wait.", e)); Threading.Wait(_BattlelogWaitDuration); } } private void DoGoogleWait() { if ((UtcNow() - _LastGoogleAction) < _GoogleWaitDuration) { Thread.Sleep(_GoogleWaitDuration - (UtcNow() - _LastGoogleAction)); } _LastGoogleAction = UtcNow(); } //Credit Patrick McDonald public static string TrimStart(string target, string trimString) { string result = target; while (result.StartsWith(trimString)) { result = result.Substring(trimString.Length); } return result; } public static string TrimEnd(string target, string trimString) { string result = target; while (result.EndsWith(trimString)) { result = result.Substring(0, result.Length - trimString.Length); } return result; } public Int64 ConvertVersionInt(String version) { try { String[] versionSplit = version.Split('.'); Int64 major, minor, patch, hotfix; if (versionSplit.Length == 4 && Int64.TryParse(versionSplit[0], out major) && Int64.TryParse(versionSplit[1], out minor) && Int64.TryParse(versionSplit[2], out patch) && Int64.TryParse(versionSplit[3], out hotfix)) { return (major * 1000000000) + (minor * 1000000) + (patch * 1000) + (hotfix); } } catch (Exception e) { Log.HandleException(new AException("Error converting version number.", e)); } return 0; } public Int32 GetStringUpperPercentage(String input) { Int32 upperCount = 0; Int32 totalCount = 0; if (String.IsNullOrEmpty(input)) { return 0; } try { foreach (var character in input.ToCharArray()) { if (char.IsLetter(character)) { totalCount++; if (char.IsUpper(character)) { upperCount++; } } } if (totalCount == 0) { return 0; } return (Int32)Math.Ceiling((Double)upperCount / (Double)totalCount * 100.0); } catch (Exception e) { Log.HandleException(new AException("Error getting string upper percentage. " + input.Length + "," + upperCount + "," + totalCount, e)); } return 0; } private void FetchIPLocation(APlayer aPlayer) { if (String.IsNullOrEmpty(aPlayer.player_ip) || (aPlayer.location != null && aPlayer.location.status == "success" && aPlayer.player_ip == aPlayer.location.IP)) { return; } IPAPILocation loc = new IPAPILocation(aPlayer.player_ip); using (GZipWebClient client = new GZipWebClient(compress: false)) { try { Hashtable response = null; String URL = "http://ip-api.com/json/" + aPlayer.player_ip + "?cacherand=" + Environment.TickCount; try { DoIPAPIWait(); response = (Hashtable)JSON.JsonDecode(Util.ClientDownloadTimer(client, URL)); } catch (Exception e) { if (NowDuration(_lastIPAPIError).TotalMinutes > 5) { Log.Debug(() => "ip-api failed to respond with player location information, your layer may be IP banned. (" + e.Message + ")", 4); _lastIPAPIError = UtcNow(); } return; } loc.status = (String)response["status"]; if (loc.status == "fail") { loc.message = (String)response["message"]; aPlayer.location = loc; return; } if (loc.status == "success") { loc.country = (String)response["country"]; loc.countryCode = (String)response["countryCode"]; loc.region = (String)response["region"]; loc.regionName = (String)response["regionName"]; loc.city = (String)response["city"]; loc.zip = (String)response["zip"]; loc.lat = (Double)response["lat"]; loc.lon = (Double)response["lon"]; loc.timezone = (String)response["timezone"]; loc.isp = (String)response["isp"]; loc.org = (String)response["org"]; loc.query = (String)response["query"]; aPlayer.location = loc; } } catch (Exception e) { Log.HandleException(new AException("Error while parsing IP response information.", e)); } } } public CompilerResults CompilePluginSource(String pluginSource) { String procon_path = Directory.GetParent(Application.ExecutablePath).FullName; String pluginDirectory = Path.Combine(procon_path, Path.Combine("Plugins", GameVersion.ToString())); Dictionary providerOptions = new Dictionary(); providerOptions.Add("CompilerVersion", "v3.5"); CSharpCodeProvider cSharpCodeProvider = new CSharpCodeProvider(providerOptions); CompilerParameters compilerParameters = new CompilerParameters(); compilerParameters.ReferencedAssemblies.Add("System.dll"); compilerParameters.ReferencedAssemblies.Add("System.Core.dll"); compilerParameters.ReferencedAssemblies.Add("System.Data.dll"); compilerParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll"); compilerParameters.ReferencedAssemblies.Add("System.Xml.dll"); compilerParameters.ReferencedAssemblies.Add("MySql.Data.dll"); compilerParameters.ReferencedAssemblies.Add("PRoCon.Core.dll"); compilerParameters.GenerateInMemory = true; compilerParameters.IncludeDebugInformation = false; compilerParameters.TempFiles = new TempFileCollection(pluginDirectory); return cSharpCodeProvider.CompileAssemblyFromSource(compilerParameters, pluginSource); } public String ProcessEventServerName(String serverName, Boolean testActive, Boolean testConcrete) { var eventDate = GetEventRoundDateTime(); if (((_CurrentEventRoundNumber == 999999 && eventDate < DateTime.Now) || _CurrentEventRoundNumber < _roundID) && !EventActive()) { serverName = serverName.Replace("%EventDateDuration%", "TBD") .Replace("%EventDateTime%", "TBD") .Replace("%EventDate%", "TBD") .Replace("%EventRound%", "TBD") .Replace("%RemainingRounds%", "TBD") .Replace("%s%", "s") .Replace("%S%", "S"); } else { if (serverName.Contains("%EventDateDuration%")) { serverName = serverName.Replace("%EventDateDuration%", FormatTimeString(eventDate - DateTime.Now, 2)); } if (serverName.Contains("%EventDateTime%")) { serverName = serverName.Replace("%EventDateTime%", eventDate.ToShortDateString() + " " + eventDate.ToShortTimeString()); } if (serverName.Contains("%EventDate%")) { serverName = serverName.Replace("%EventDate%", eventDate.ToShortDateString()); } if (serverName.Contains("%CurrentRound%")) { serverName = serverName.Replace("%CurrentRound%", String.Format("{0:n0}", _roundID)); } if (serverName.Contains("%EventRound%")) { if (_CurrentEventRoundNumber != 999999) { serverName = serverName.Replace("%EventRound%", String.Format("{0:n0}", _CurrentEventRoundNumber)); } else { serverName = serverName.Replace("%EventRound%", String.Format("{0:n0}", FetchEstimatedEventRoundNumber())); } } if (serverName.Contains("%RemainingRounds%")) { var remainingRounds = 0; if (testConcrete) { remainingRounds = 3; serverName = serverName.Replace("%RemainingRounds%", String.Format("{0:n0}", Math.Max(remainingRounds, 0))); serverName = serverName.Replace("%s%", remainingRounds > 1 ? "s" : ""); serverName = serverName.Replace("%S%", remainingRounds > 1 ? "S" : ""); } else if (_CurrentEventRoundNumber != 999999) { remainingRounds = _CurrentEventRoundNumber - _roundID; serverName = serverName.Replace("%RemainingRounds%", String.Format("{0:n0}", Math.Max(remainingRounds, 0))); serverName = serverName.Replace("%s%", remainingRounds > 1 ? "s" : ""); serverName = serverName.Replace("%S%", remainingRounds > 1 ? "S" : ""); } else { remainingRounds = FetchEstimatedEventRoundNumber() - _roundID; serverName = serverName.Replace("%RemainingRounds%", String.Format("{0:n0}", Math.Max(remainingRounds, 0))); serverName = serverName.Replace("%s%", remainingRounds > 1 ? "s" : ""); serverName = serverName.Replace("%S%", remainingRounds > 1 ? "S" : ""); } } } if (testActive) { serverName += " AUTO-PRIMARIES ONLY!"; } serverName = serverName.Trim(); Int32 cutLength = serverName.Length - 62; if (cutLength > 0) { serverName = serverName.Substring(0, serverName.Length - cutLength); } return serverName; } public class ABan { //Current exception state of the ban public DateTime ban_endTime; public Boolean ban_enforceGUID = true; public Boolean ban_enforceIP = false; public Boolean ban_enforceName = false; public AException ban_exception = null; public Int64 ban_id = -1; public String ban_notes = null; public ARecord ban_record = null; public DateTime ban_startTime; public String ban_status = "Active"; public String ban_sync = null; public Int64 player_id = -1; //startTime and endTime are not set by AdKats, they are set in the database. //startTime and endTime will be valid only when bans are fetched from the database } public class ACommand { //Active option public enum CommandActive { Active, Disabled, Invisible } //Logging option public enum CommandLogging { Log, Ignore, Mandatory, Unable } public enum CommandAccess { Any, AnyHidden, AnyVisible, GlobalVisible, TeamVisible, SquadVisible } public CommandActive command_active = CommandActive.Active; public CommandLogging command_logging = CommandLogging.Log; public CommandAccess command_access = CommandAccess.Any; public Int64 command_id = -1; public String command_key = null; public String command_name = null; public Boolean command_playerInteraction = true; public String command_text = null; public override string ToString() { return command_name ?? "Unknown Command"; } } public class AException { public Exception InternalException = null; public String Message = String.Empty; public String Method = String.Empty; // This constructor MUST be executed inside the method where the error was triggered // otherwise the most recent item in the stack frame will not be correct public AException(String message, Exception internalException) { Method = new StackFrame(1).GetMethod().Name; Message = message; InternalException = internalException; } public AException(String message) { Method = new StackFrame(1).GetMethod().Name; Message = message; } //Override toString public override String ToString() { return "[" + Method + "][" + Message + "]" + ((InternalException != null) ? (": " + InternalException) : ("")); } } public class AChallengeManager { private AdKats _plugin; // Settings public Boolean Enabled; public Boolean AutoPlay = true; public Boolean EnableServerRoundRules; public Boolean RandomPlayerRoundRules; public Int32 MinimumPlayers = 0; public Int32 CommandLockTimeoutHours = 24; public enum ChallengeState { Init, Loaded, Playing, Ended } // Runtime public Boolean Loading { get; private set; } public Boolean Loaded { get; private set; } private Boolean TriggerLoad; public List Definitions; public List Rules; public List Rewards; public List Entries; private Int32 LoadedRoundID; // Round Rule public CRule RoundRule; private ChallengeState ChallengeRoundState = ChallengeState.Init; public List CompletedRoundEntries; // Timings private DateTime _LastDBReadAll = DateTime.UtcNow - TimeSpan.FromMinutes(30); public AChallengeManager(AdKats plugin) { _plugin = plugin; try { Definitions = new List(); Rules = new List(); Entries = new List(); CompletedRoundEntries = new List(); Rewards = new List(); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while creating challenge manager.", e)); } } public void SetEnabled(Boolean enable) { try { if (Enabled && !enable) { _plugin.Log.Success("Disabling challenge manager."); CancelActiveRoundRule(); ChallengeRoundState = ChallengeState.Init; Enabled = enable; } else if (!Enabled && enable) { _plugin.Log.Success("Enabling challenge manager."); if (Loaded) { Enabled = enable; if (_plugin._roundID <= 1) { _plugin.Log.Error("Round ID was invalid when starting challenge manager."); } else { OnRoundLoaded(_plugin._roundID); if (_plugin._roundState == RoundState.Playing) { // We're already playing. Trigger playing state. OnRoundPlaying(_plugin._roundID); } } } else { Enabled = enable; TriggerLoad = true; } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while reading all challenge manager DB info.", e)); } } public String GetDefinitionEnum(Boolean includeNone) { try { if (!Definitions.Any()) { _plugin.Log.Error("Attempted to get definition enum with no definitions added."); return null; } var rng = new Random(Environment.TickCount); var enumString = String.Empty; foreach (var defName in Definitions.OrderBy(def => def.Name) .Select(def => def.Name)) { if (String.IsNullOrEmpty(enumString)) { enumString += "enum.ChallengeDefinitionEnum" + (includeNone ? "None" : "") + "_" + rng.Next(100000, 999999) + "(" + (includeNone ? "None|" : ""); } else { enumString += "|"; } enumString += defName; } enumString += ")"; return enumString; } catch (Exception e) { Loading = false; _plugin.Log.HandleException(new AException("Error while generating definition enum.", e)); } return null; } public void HandleRead(MySqlConnection con, Boolean bypass) { try { var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { if (_plugin.NowDuration(_LastDBReadAll).TotalMinutes > 5.0 || bypass) { if (Loading) { return; } Loading = true; // -- LOAD -- DBReadDefinitions(con); DBReadRules(con); DBReadEntries(con); DBReadRewards(con); if (TriggerLoad) { if (_plugin._roundID <= 1) { _plugin.Log.Error("Round ID was invalid when starting challenge manager."); } else { OnRoundLoaded(_plugin._roundID); if (_plugin._roundState == RoundState.Playing) { // We're already playing. Trigger playing state. OnRoundPlaying(_plugin._roundID); } } TriggerLoad = false; } // -- END LOAD -- Loading = false; Loaded = true; _LastDBReadAll = _plugin.UtcNow(); _plugin.UpdateSettingPage(); } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while reading all challenge manager DB info.", e)); Loading = false; } } public void DBReadDefinitions(MySqlConnection con) { try { var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" SELECT `ID`, `Name`, `CreateTime`, `ModifyTime` FROM `adkats_challenge_definition` ORDER BY `ID` ASC"; var defReads = new List(); var defPushes = new List(); using (MySqlDataReader reader = _plugin.SafeExecuteReader(command)) { lock (Definitions) { var validIDs = new List(); while (reader.Read()) { var readID = reader.GetInt32("ID"); var def = Definitions.FirstOrDefault(dDef => dDef.ID == readID); if (def == null) { def = new CDefinition(_plugin, this, false); def.ID = readID; Definitions.Add(def); } def.Name = reader.GetString("Name"); var sanitizedName = def.Name.Replace("|", ""); if (String.IsNullOrEmpty(sanitizedName)) { _plugin.Log.Error("Challenge definition " + readID + " contained invalid name characters."); var rng = new Random(Environment.TickCount); // Assume we won't hit duplicates with 1 million (welp) def.Name = "CHG-" + def.ID + "-" + rng.Next(1000000); defPushes.Add(def); } else if (def.Name != sanitizedName) { _plugin.Log.Error("Challenge definition " + readID + " contained invalid name characters."); def.Name = sanitizedName; defPushes.Add(def); } def.CreateTime = reader.GetDateTime("CreateTime"); def.ModifyTime = reader.GetDateTime("ModifyTime"); defReads.Add(def); validIDs.Add(readID); } // Remove definitions as necessary foreach (var def in Definitions.Where(dDef => !validIDs.Contains(dDef.ID)).ToList()) { _plugin.Log.Info("Removing definition " + def.ID + " from challenge manager. Definition was deleted from database."); Definitions.Remove(def); } } } // These must be executed afterward, otherwise it would cause overlapping readers on the same connection foreach (var def in defPushes) { def.DBPush(localConnection); } foreach (var def in defReads) { def.DBReadDetails(localConnection); } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error reading in definitions for challenge manager.", e)); } } public List GetDefinitions() { var defs = new List(); try { lock (Definitions) { defs.AddRange(Definitions.OrderBy(def => def.ID).ToList()); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting list of definitions.", e)); } return defs; } public CDefinition GetDefinition(Int64 defID) { try { lock (Definitions) { if (!Definitions.Any()) { return null; } return Definitions.FirstOrDefault(def => def.ID == defID); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting definition from manager.", e)); } return null; } protected void DeleteDefinition(Int64 defID) { try { lock (Definitions) { CDefinition def = Definitions.FirstOrDefault(dDef => dDef.ID == defID); if (def == null) { _plugin.Log.Error("No definition exists with ID " + defID + "."); return; } Definitions.Remove(def); // Reload everything. Deleting a definition is huge. HandleRead(null, true); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while deleting definition from manager.", e)); } } public void CreateDefinition(String defName) { try { lock (Definitions) { var newDef = new CDefinition(_plugin, this, true) { Name = defName }; if (newDef == null) { _plugin.Log.Error("Definition was null when adding to the challenge manager."); return; } // Check if a definition exists with this ID if (Definitions.Any(dDef => dDef.ID == newDef.ID)) { _plugin.Log.Error("Definition with ID " + newDef.ID + " already exists."); return; } // Check if a definition exists with this name if (Definitions.Any(dDef => dDef.Name == newDef.Name)) { _plugin.Log.Error("Definition called " + newDef.Name + " already exists."); return; } if (newDef.Phantom) { // Try to push it to the database newDef.DBPush(null); } if (newDef.ID <= 0) { _plugin.Log.Error("Defintion had invalid ID when adding to the challenge manager."); return; } Definitions.Add(newDef); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while adding definition to manager.", e)); } } public void DBReadRules(MySqlConnection con) { try { if (_plugin._serverInfo == null || _plugin._serverInfo.ServerID <= 0) { _plugin.Log.Error("Unable to read challenge rules. Server info invalid."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" SELECT `ID`, `ServerID`, `DefID`, `Enabled`, `Name`, `Tier`, `CompletionType`, `RoundCount`, `DurationMinutes`, `DeathCount`, `CreateTime`, `ModifyTime`, `RoundLastUsedTime`, `PersonalLastUsedTime` FROM `adkats_challenge_rule` WHERE `ServerID` = @ServerID ORDER BY `DefID` ASC, `ID` ASC"; command.Parameters.AddWithValue("@ServerID", _plugin._serverInfo.ServerID); var ruleDeletes = new List(); var rulePushes = new List(); using (MySqlDataReader reader = _plugin.SafeExecuteReader(command)) { lock (Rules) { var validIDs = new List(); while (reader.Read()) { var add = false; var readID = reader.GetInt32("ID"); var rule = Rules.FirstOrDefault(dRule => dRule.ID == readID); if (rule == null) { rule = new CRule(_plugin, this, false); rule.ID = readID; add = true; } var serverID = reader.GetInt64("ServerID"); if (_plugin._serverInfo.ServerID != serverID) { _plugin.Log.Error("Invalid server ID when reading challenge rule " + rule.ID + "."); ruleDeletes.Add(rule); continue; } rule.ServerID = serverID; var definition = GetDefinition(reader.GetInt64("DefID")); if (definition == null) { _plugin.Log.Error("Invalid definition value when reading challenge rule " + rule.ID + "."); ruleDeletes.Add(rule); continue; } rule.Definition = definition; rule.Enabled = reader.GetBoolean("Enabled"); rule.Name = reader.GetString("Name"); var sanitizedName = rule.Name.Replace("|", ""); if (String.IsNullOrEmpty(sanitizedName)) { _plugin.Log.Error("Challenge rule " + rule.ID + " contained invalid name characters."); var rng = new Random(Environment.TickCount); // Assume we won't hit duplicates with 1 million (welp) rule.Name = "CHG-" + rule.ID + "-" + rng.Next(1000000); rulePushes.Add(rule); } else if (rule.Name != sanitizedName) { _plugin.Log.Error("Challenge rule " + rule.ID + " contained invalid name characters."); rule.Name = sanitizedName; rulePushes.Add(rule); } rule.Tier = reader.GetInt32("Tier"); rule.Completion = (CRule.CompletionType)Enum.Parse(typeof(CRule.CompletionType), reader.GetString("CompletionType")); rule.RoundCount = reader.GetInt32("RoundCount"); rule.DurationMinutes = reader.GetInt32("DurationMinutes"); rule.DeathCount = reader.GetInt32("DeathCount"); rule.CreateTime = reader.GetDateTime("CreateTime"); rule.ModifyTime = reader.GetDateTime("ModifyTime"); rule.RoundLastUsedTime = reader.GetDateTime("RoundLastUsedTime"); rule.PersonalLastUsedTime = reader.GetDateTime("PersonalLastUsedTime"); validIDs.Add(readID); if (add) { Rules.Add(rule); } } // Remove rules as necessary foreach (var rule in Rules.Where(dRule => !validIDs.Contains(dRule.ID)).ToList()) { _plugin.Log.Info("Removing rule " + rule.ID + " from challenge manager. Rule was deleted from database."); Rules.Remove(rule); } } } // These must be executed afterward, otherwise it would cause overlapping readers on the same connection foreach (var rule in ruleDeletes) { rule.DBDelete(localConnection); } foreach (var rule in rulePushes) { rule.DBPush(localConnection); } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error reading in rules for challenge manager.", e)); } } public List GetRules() { var rules = new List(); try { lock (Rules) { rules.AddRange(Rules.OrderBy(rule => rule.ID).ToList()); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting list of rules.", e)); } return rules; } public CRule GetRule(Int64 ruleID) { try { lock (Rules) { if (!Rules.Any()) { return null; } return Rules.FirstOrDefault(rule => rule.ID == ruleID); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting rule from manager.", e)); } return null; } protected void DeleteRule(Int64 ruleID) { try { lock (Rules) { CRule rule = Rules.FirstOrDefault(dRule => dRule.ID == ruleID); if (rule == null) { _plugin.Log.Error("No rule exists with ID " + ruleID + "."); return; } Rules.Remove(rule); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while deleting rule from manager.", e)); } } public void CreateRule(String definitionName) { try { lock (Rules) { var rule = new CRule(_plugin, this, true); if (String.IsNullOrEmpty(definitionName)) { _plugin.Log.Error("Definition name was empty when creating challenge rule."); return; } var definitions = GetDefinitions(); if (!definitions.Any()) { _plugin.Log.Error("No definitions available when creating challenge rule."); return; } var matchingDefinition = definitions.FirstOrDefault(def => def.Name == definitionName); if (matchingDefinition == null) { _plugin.Log.Error("No matching definition when creating challenge rule."); return; } rule.Definition = matchingDefinition; var rules = GetRules(); Int64 oneLouder = 1; if (rules.Any()) { oneLouder += rules.Select(dRule => dRule.ID).Max(); } rule.Name = rule.Definition.Name + " Rule " + oneLouder; Rules.Add(rule); rule.DBPush(null); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while creating definition detail in manager.", e)); } } protected void DeleteReward(Int64 rewardID) { try { lock (Rewards) { CReward reward = Rewards.FirstOrDefault(dReward => dReward.ID == rewardID); if (reward == null) { _plugin.Log.Error("No reward exists with ID " + rewardID + "."); return; } Rewards.Remove(reward); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while deleting reward from manager.", e)); } } public void DBReadEntries(MySqlConnection con) { try { var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { var startTime = _plugin.UtcNow(); using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" SELECT `ace`.`ID`, `ace`.`PlayerID`, `ace`.`RuleID`, `ace`.`Completed`, `ace`.`Failed`, `ace`.`Canceled`, `ace`.`StartRound`, `ace`.`StartTime`, `ace`.`CompleteTime` FROM `adkats_challenge_entry` `ace` JOIN `adkats_challenge_rule` `acr` ON `ace`.`RuleID` = `acr`.`ID` WHERE `acr`.`ServerID` = @ServerID AND `ace`.`Completed` = 0 AND `ace`.`Canceled` = 0 AND `ace`.`Failed` = 0 ORDER BY `PlayerID` ASC, `RuleID` ASC, `ID` ASC"; var entryChecks = new List(); command.Parameters.AddWithValue("@ServerID", _plugin._serverInfo.ServerID); using (MySqlDataReader reader = _plugin.SafeExecuteReader(command)) { lock (Entries) { while (reader.Read()) { var readID = reader.GetInt64("ID"); if (Entries.Any(dEntry => dEntry.ID == readID)) { // This entry is already loaded. Ignore it. continue; } var playerID = reader.GetInt64("PlayerID"); var ruleID = reader.GetInt64("RuleID"); var startRound = reader.GetInt32("StartRound"); var entry = new CEntry(_plugin, this, _plugin.FetchPlayer(false, false, false, null, playerID, null, null, null, null), GetRule(ruleID), startRound, false); entry.ID = readID; if (entry.Player == null) { _plugin.Log.Error("Unable to fetch player for Entry " + entry.ID + " and player " + playerID + ". Unable to read."); continue; } if (entry.Rule == null) { _plugin.Log.Error("Unable to fetch rule for Entry " + entry.ID + " and rule " + ruleID + ". Unable to read."); continue; } entry.Completed = reader.GetBoolean("Completed"); entry.Failed = reader.GetBoolean("Failed"); entry.Canceled = reader.GetBoolean("Canceled"); entry.StartTime = reader.GetDateTime("StartTime"); entry.CompleteTime = reader.GetDateTime("CompleteTime"); entryChecks.Add(entry); Entries.Add(entry); } } } // These must be executed afterward, otherwise it would cause overlapping readers on the same connection foreach (var entry in entryChecks.Where(ent => !ent.Completed && !ent.Canceled && !ent.Failed)) { // Check to see if the entry has failed entry.CheckFailure(); // Read in the details and refresh progress entry.DBReadDetails(localConnection); if (entry.Progress == null) { entry.RefreshProgress(null); } } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error reading in entries for challenge manager.", e)); } } public List GetEntries() { var entries = new List(); try { lock (Entries) { entries.AddRange(Entries.OrderBy(entry => entry.ID).ToList()); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting list of entries.", e)); } return entries; } public List GetEntriesForPlayer(APlayer player) { var entries = new List(); if (player.player_id <= 0) { _plugin.Log.Error("Unable to get entries for player. Player did not have a valid ID."); return entries; } try { lock (Entries) { entries.AddRange(Entries.Where(entry => entry.Player.player_id == player.player_id) .OrderBy(entry => entry.ID) .ToList()); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting list of entries for player.", e)); } return entries; } public void AssignActiveEntryForPlayer(APlayer player) { try { if (player.ActiveChallenge != null || _plugin.EventActive()) { return; } // Order by ID so we can cancel older entries as needed var activeEntries = GetEntriesForPlayer(player).Where(entry => !entry.Canceled && !entry.Completed && !entry.Failed) .OrderBy(entry => entry.ID) .ToList(); var cancelling = false; while (activeEntries.Count() > 1) { var first = activeEntries.First(); // There is more than one active entry. Cancel this oldest one. first.DoCancel(); // Remove the entry. activeEntries.Remove(first); // Repeat as needed. cancelling = true; } if (cancelling && !activeEntries.Any()) { _plugin.Log.Error("We cancelled too many things!!!!"); } // Check for ignoring status if (_plugin.GetMatchingVerboseASPlayersOfGroup("challenge_ignore", player).Any()) { // They are ignoring challenges but have an active challenge. Cancel it. var matching = activeEntries.FirstOrDefault(); if (matching != null) { matching.DoCancel(); } return; } player.ActiveChallenge = activeEntries.FirstOrDefault(); // Secondary check in case we're doing random assignment of challenges if (player.ActiveChallenge == null && EnableServerRoundRules && RandomPlayerRoundRules && AutoPlay && RoundRule == null && ChallengeRoundState == ChallengeState.Playing) { // Need to choose a random round rule for the player CreateAndAssignRandomRoundEntry(player, true); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while assigning active challenge entry for player.", e)); } } public void AddCompletedEntryForRound(CEntry entry) { try { if (entry == null || entry.ID <= 0 || !entry.Details.Any()) { _plugin.Log.Error("Tried to add completed entry when it was invalid."); return; } lock (CompletedRoundEntries) { if (CompletedRoundEntries.Contains(entry)) { _plugin.Log.Error("Tried to add completed entry " + entry.ID + " for round, but it was already added."); return; } if (!entry.Completed) { _plugin.Log.Error("Tried to add completed entry " + entry.ID + " for round, but it wasn't completed."); return; } CompletedRoundEntries.Add(entry); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while adding completed round entry.", e)); } } public List GetCompletedRoundEntries() { try { lock (CompletedRoundEntries) { return CompletedRoundEntries.ToList(); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting completed round entries.", e)); } return new List(); } public CEntry GetCompletedRoundEntryForPlayer(APlayer player) { try { if (RoundRule == null) { return null; } lock (CompletedRoundEntries) { return CompletedRoundEntries.FirstOrDefault(entry => entry.Player.player_id == player.player_id && entry.Rule == RoundRule); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting completed round entries for player.", e)); } return null; } public void CreateAndAssignRandomRoundEntry(APlayer player, Boolean verbose) { try { var roundRules = GetRules().Where(rule => rule.Enabled && rule.Tier == 1 && rule.Completion == CRule.CompletionType.Rounds && rule.RoundCount == 1 && rule.Definition.GetDetails().Any()); if (!roundRules.Any()) { if (verbose) { player.Say("No active round challenges found."); } return; } // Assign a random rule from the available list var rng = new Random(Environment.TickCount); CreateAndAssignEntry(player, roundRules.OrderBy(rule => rng.Next()).First(), verbose); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error creating and assigning random round entry.", e)); } } public void CreateAndAssignRandomEntry(APlayer player, Int32 tier, Boolean verbose) { try { if (tier < 0) { tier = 0; } if (tier > 10) { tier = 10; } var rules = GetRules().Where(rule => rule.Enabled && rule.Definition.GetDetails().Any()) .OrderBy(rule => rule.Tier) .ThenBy(rule => rule.Name).ToList(); if (tier != 0) { rules = rules.Where(rule => rule.Tier == tier).ToList(); } if (!rules.Any()) { if (verbose) { player.Say("No matching challenges found."); } return; } // Assign a random rule from the available list var rng = new Random(Environment.TickCount); CreateAndAssignEntry(player, rules.OrderBy(rule => rng.Next()).First(), verbose); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error creating and assigning random entry.", e)); } } public void CreateAndAssignEntry(APlayer player, CRule rule, Boolean verbose) { try { if (player == null || player.player_id <= 0) { _plugin.Log.Error("Player was invalid when creating entry."); return; } // Check for ignoring status if (_plugin.GetMatchingVerboseASPlayersOfGroup("challenge_ignore", player).Any()) { _plugin.Log.Error("Player was ignoring challenges when trying to assign entry."); return; } if (rule == null || rule.ID <= 0 || rule.ServerID != _plugin._serverInfo.ServerID || rule.Phantom) { _plugin.Log.Error("Rule was invalid when creating entry."); return; } if (!rule.Enabled) { _plugin.Log.Error("Rule " + rule.ToString() + " was not enabled when creating entry."); return; } if (ChallengeRoundState != ChallengeState.Playing) { if (ChallengeRoundState == ChallengeState.Init) { _plugin.Log.Error("Challenge system not initialized when assigning entry."); return; } player.Say("Round not started. Please spawn to start the round before starting challenges."); return; } if (LoadedRoundID <= 1) { _plugin.Log.Error("Loaded round ID was invalid when assigning entry."); return; } lock (Entries) { // Cancel any existing entries for the player if (player.ActiveChallenge != null) { player.ActiveChallenge.DoCancel(); } // Create the new entry var newEntry = new CEntry(_plugin, this, player, rule, LoadedRoundID, true); newEntry.DBPush(null); if (newEntry.Phantom) { _plugin.Log.Error("Unable to create challenge entry for " + player.GetVerboseName() + ", could not upload to database."); return; } if (verbose) { var commandText = _plugin.GetChatCommandByKey("self_challenge"); player.Say(_plugin.Log.CPink("Now playing " + rule.Name + " challenge. For more info use " + commandText)); if (_plugin.GetPlayerCount() < MinimumPlayers) { player.Say("Challenges do not gain progress until " + MinimumPlayers + " active players."); } } newEntry.RefreshProgress(null); Entries.Add(newEntry); player.ActiveChallenge = newEntry; } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while creating and assigning challenge entry in manager.", e)); } } public void AssignRoundChallengeIfKillValid(AKill kill) { try { if (RoundRule == null) { // We don't have a round rule. Nothing to do here. return; } if (kill == null || kill.killer == null || kill.killer.player_id <= 0) { _plugin.Log.Error("Kill was invalid when assigning round challenge."); return; } var player = kill.killer; // Check ignoring case if (_plugin.GetMatchingVerboseASPlayersOfGroup("challenge_ignore", player).Any()) { return; } // Check whitelisting case if (!AutoPlay && !_plugin.GetMatchingVerboseASPlayersOfGroup("challenge_play", player).Any()) { return; } // Check to see if they should have an active challenge already assigned if (player.ActiveChallenge == null) { AssignActiveEntryForPlayer(player); } if (player.ActiveChallenge == null) { // Only create the entry if the current kill is valid for the round rule if (RoundRule.KillValid(kill) && // Make sure they haven't completed the round challenge already GetCompletedRoundEntryForPlayer(player) == null) { CreateAndAssignEntry(player, RoundRule, true); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while assigning challenge if kill valid.", e)); } } public List GetRewards() { var rewards = new List(); try { lock (Rewards) { rewards.AddRange(Rewards.OrderBy(reward => reward.ID).ToList()); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting list of rewards.", e)); } return rewards; } public CReward GetReward(Int64 rewardID) { try { lock (Rewards) { if (!Rewards.Any()) { return null; } return Rewards.FirstOrDefault(dReward => dReward.ID == rewardID); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting reward from manager.", e)); } return null; } public void CreateReward(Int32 tier) { try { lock (Rewards) { var newReward = new CReward(_plugin, this, true) { Tier = tier }; if (newReward == null) { _plugin.Log.Error("Reward was null when adding to the challenge manager."); return; } // Check if a reward exists with this tier and reward type if (Rewards.Any(dReward => dReward.Tier == newReward.Tier && dReward.Reward == newReward.Reward)) { _plugin.Log.Error("Reward with tier " + newReward.Tier + " and reward " + newReward.Reward.ToString() + " already exists."); return; } if (newReward.Phantom) { // Try to push it to the database newReward.DBPush(null); } if (newReward.ID <= 0) { _plugin.Log.Error("Reward had invalid ID when adding to the challenge manager."); return; } Rewards.Add(newReward); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while adding reward to manager.", e)); } } public void DBReadRewards(MySqlConnection con) { try { var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" SELECT `ID`, `ServerID`, `Tier`, `Reward`, `Enabled`, `DurationMinutes`, `CreateTime`, `ModifyTime` FROM `adkats_challenge_reward` WHERE `ServerID` = @ServerID ORDER BY `ID` ASC"; command.Parameters.AddWithValue("@ServerID", _plugin._serverInfo.ServerID); using (MySqlDataReader reader = _plugin.SafeExecuteReader(command)) { lock (Rewards) { var validIDs = new List(); while (reader.Read()) { var readID = reader.GetInt32("ID"); var reward = Rewards.FirstOrDefault(dReward => dReward.ID == readID); if (reward == null) { reward = new CReward(_plugin, this, false); reward.ID = readID; Rewards.Add(reward); } reward.ServerID = reader.GetInt64("ServerID"); if (reward.ServerID != _plugin._serverInfo.ServerID) { _plugin.Log.Error("CReward " + this.ToString() + " was loaded, but belongs to another server."); } reward.Tier = reader.GetInt32("Tier"); reward.Reward = (CReward.RewardType)Enum.Parse(typeof(CReward.RewardType), reader.GetString("Reward")); reward.Enabled = reader.GetBoolean("Enabled"); reward.DurationMinutes = reader.GetInt32("DurationMinutes"); reward.CreateTime = reader.GetDateTime("CreateTime"); reward.ModifyTime = reader.GetDateTime("ModifyTime"); validIDs.Add(readID); } // Remove definitions as necessary foreach (var reward in Rewards.Where(dReward => !validIDs.Contains(dReward.ID)).ToList()) { _plugin.Log.Info("Removing reward " + reward.ID + " from challenge manager. Reward was deleted from database."); Rewards.Remove(reward); } } } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error reading in rewards for challenge manager.", e)); } } public void CancelActiveRoundRule() { try { if (RoundRule != null) { // Remove the current rule RoundRule = null; } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while cancelling active round rule entries.", e)); } } public void FailActiveRoundRule() { try { if (RoundRule != null) { // Get the active challenges being played for the current round rule. foreach (var activeEntry in _plugin._PlayerDictionary.Values.ToList() .Where(dPlayer => dPlayer.ActiveChallenge != null && dPlayer.ActiveChallenge.Rule == RoundRule) .Select(dPlayer => dPlayer.ActiveChallenge)) { // Cancel all active entries for this rule, since it's being changed. activeEntry.DoFail(); } // Remove the current rule RoundRule = null; } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while failing active round rule entries.", e)); } } public String GetChallengeInfo(APlayer aPlayer, Boolean description) { try { if (!Enabled) { return "Challenge manager not enabled."; } if (_plugin.EventActive()) { return "Challenges are not enabled during server events."; } if (aPlayer == null || aPlayer.player_id <= 0) { _plugin.Log.Error("Tried to get challenge info for an invalid player."); return "ERROR758"; } // Get active entries for the current player. var commandText = _plugin.GetChatCommandByKey("self_challenge"); AssignActiveEntryForPlayer(aPlayer); var completedRoundEntry = GetCompletedRoundEntryForPlayer(aPlayer); if (aPlayer.ActiveChallenge == null) { // They aren't playing a challenge yet. // Add them to the current round challenge automatically if one is running. if (RoundRule == null) { // No round challenge. Tell them how they can join a challenge. return "No round challenge active. Choose a new challenge with " + commandText + " list"; } // We have a round rule, good, but make sure they haven't completed the round challenge already if (completedRoundEntry == null) { CreateAndAssignEntry(aPlayer, RoundRule, false); } } var completedRoundEntryString = ""; if (completedRoundEntry != null) { completedRoundEntryString += "COMPLETED " + completedRoundEntry.Rule.Name.ToUpper() + " ROUND CHALLENGE" + Environment.NewLine; } if (aPlayer.ActiveChallenge == null) { return completedRoundEntryString + "Choose a new challenge with " + commandText + " list"; } // The player is available and they have entries. var challenge = aPlayer.ActiveChallenge; var completionTimeString = "Challenge Ends: "; if (challenge.Rule.Completion == CRule.CompletionType.Rounds) { // Completion round is the same round if RoundCount = 1 var completionRoundID = challenge.StartRound + challenge.Rule.RoundCount; var remainingRounds = completionRoundID - LoadedRoundID; if (remainingRounds == 1) { completionTimeString += "End of this round."; } else if (remainingRounds == 2) { completionTimeString += "End of next round."; } else { completionTimeString += remainingRounds + "rounds from now."; } } else if (challenge.Rule.Completion == CRule.CompletionType.Duration) { var completionTime = challenge.StartTime + TimeSpan.FromMinutes(challenge.Rule.DurationMinutes); var completionDuration = _plugin.NowDuration(completionTime); if (_plugin.UtcNow() > completionTime) { completionTimeString += "In a few seconds."; } else { completionTimeString += "In " + Math.Round(completionDuration.TotalMinutes, 1) + "m (" + _plugin.FormatTimeString(completionDuration, 3) + ")"; } } else if (challenge.Rule.Completion == CRule.CompletionType.Deaths) { var deaths = challenge.Progress.Deaths.Count(); var deathsRemaining = challenge.Rule.DeathCount - deaths; if (deathsRemaining <= 0) { completionTimeString += "ERROR17592"; } else { completionTimeString += "If you get " + deathsRemaining + " " + (deaths > 0 ? "more " : "") + "deaths."; } } else { completionTimeString += "ERROR28217"; } completionTimeString += Environment.NewLine; var rewardString = ""; var matchingRewards = GetRewards().Where(dReward => dReward.Tier == challenge.Rule.Tier && dReward.Enabled && dReward.Reward != CReward.RewardType.None); if (matchingRewards.Any()) { var rewardStrings = matchingRewards.OrderBy(dReward => dReward.Reward.ToString()) .Select(dReward => dReward.getDescriptionString(aPlayer)) .Distinct(); rewardString = String.Join(", ", rewardStrings.ToArray()); } var info = ""; if (description) { info += aPlayer.GetVerboseName() + " " + challenge.Rule.Name.ToUpper() + " CHALLENGE" + Environment.NewLine; info += completionTimeString; if (!String.IsNullOrEmpty(rewardString)) { info += "Rewards: " + rewardString + Environment.NewLine; } info += challenge.Rule.RuleInfo() + Environment.NewLine; info += "To see your progress type: " + commandText + " p" + Environment.NewLine; } else { info += "Status: " + Math.Round(challenge.Progress.CompletionPercentage) + "% | " + challenge.Progress.TotalCompletedKills + " Kills | " + challenge.Progress.TotalRequiredKills + " Required" + Environment.NewLine; info += completionTimeString; if (!String.IsNullOrEmpty(rewardString)) { info += "Rewards: " + rewardString + Environment.NewLine; } info += challenge.Progress.ToString(); } if (_plugin.GetPlayerCount() < MinimumPlayers) { info += "Challenges do not gain progress until " + MinimumPlayers + " active players."; } return info; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting challenge info.", e)); } return "ERROR264"; } public void OnRoundEnded(Int32 roundID) { try { if (!Enabled) { return; } // Confirm we are in valid state to end. if (ChallengeRoundState != ChallengeState.Playing) { // We're not, get out of here. _plugin.Log.Warn("Ended challenge round when not in playing state. State was " + ChallengeRoundState.ToString() + "."); } // This needs to be done async to prevent AdKats from blocking the main procon event thread _plugin.Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "ChallengeRoundEnd"; // Wait for any round-end messages to fire _plugin.Threading.Wait(TimeSpan.FromSeconds(15)); SyncOnRoundEnded(roundID); _plugin.Threading.StopWatchdog(); }))); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while running round end logic.", e)); } } private void SyncOnRoundEnded(Int32 roundID) { try { if (!Enabled) { return; } List completedPersonalChallenges; List completedRoundRuleChallenges; var notIgnoringPlayers = _plugin.GetOnlinePlayersWithoutGroup("challenge_ignore"); if (RoundRule != null) { completedRoundRuleChallenges = GetCompletedRoundEntries().Where(entry => entry.Rule == RoundRule).ToList(); completedPersonalChallenges = GetCompletedRoundEntries().Where(entry => entry.Rule != RoundRule).ToList(); // Congrats to the winners var completedRoundRuleEntries = GetCompletedRoundEntries().Where(entry => entry.Rule == RoundRule); var playerS = completedRoundRuleEntries.Count() != 1 ? "s" : ""; var endedMessage = RoundRule.Name + " Round Challenge Ended! " + completedRoundRuleEntries.Count() + " player" + playerS + " completed it!"; _plugin.ProconChatWrite(_plugin.Log.CPink(endedMessage)); var roundMessage = "Round Winners: " + String.Join(", ", completedRoundRuleEntries.Select(entry => entry.Player.GetVerboseName()).ToArray()); if (completedRoundRuleEntries.Any()) { _plugin.ProconChatWrite(_plugin.Log.CPink(roundMessage)); } foreach (var aPlayer in notIgnoringPlayers) { _plugin.PlayerSayMessage(aPlayer.player_name, endedMessage, false, 1); if (completedRoundRuleEntries.Any()) { _plugin.PlayerSayMessage(aPlayer.player_name, roundMessage, false, 1); } } } else { completedPersonalChallenges = GetCompletedRoundEntries(); } if (completedPersonalChallenges.Any()) { _plugin.Threading.Wait(TimeSpan.FromSeconds(3)); var personalMessage = "Personal Winners: " + String.Join(Environment.NewLine, completedPersonalChallenges.Select(entry => entry.Player.GetVerboseName() + " [" + entry.Rule.Name + "]").ToArray()); _plugin.ProconChatWrite(_plugin.Log.CPink(personalMessage)); foreach (var aPlayer in notIgnoringPlayers) { _plugin.PlayerSayMessage(aPlayer.player_name, personalMessage, false, 1); } } // Trigger the state change ChallengeRoundState = ChallengeState.Ended; // Fail the active round rule if applicable FailActiveRoundRule(); // Fail challenges as necessary var roundEntries = GetEntries().Where(entry => !entry.Completed && !entry.Failed && !entry.Canceled); foreach (var entry in roundEntries) { entry.CheckFailure(); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while running round end sync logic.", e)); } } public void OnRoundLoaded(Int32 roundID) { try { if (!Enabled) { return; } if (ChallengeRoundState == ChallengeState.Playing) { // We are still in playing state, this is likely due to a server crash or other oddity. Run the end trigger to change the state. _plugin.Log.Warn("Challenge manager was in Playing state when loading round. Was expecting Ended. Fixing this state."); SyncOnRoundEnded(roundID); } // Confirm we are in valid state to load. if (ChallengeRoundState != ChallengeState.Ended && ChallengeRoundState != ChallengeState.Init) { // We're not, get out of here. _plugin.Log.Warn("Attempted to load challenge when not in ended or initializing state."); return; } lock (CompletedRoundEntries) { // Clear all of the completed round entries. We're starting a new list. CompletedRoundEntries.Clear(); } // Do not start a server-wide round rule during an event if (EnableServerRoundRules && !RandomPlayerRoundRules && !_plugin.EventActive()) { // Make sure we don't have an active rule at this time. if (RoundRule == null) { // We want to have server-wide round rules. Grab ones which are valid for that. var roundRules = GetRules().Where(rule => rule.Enabled && rule.Tier == 1 && rule.Completion == CRule.CompletionType.Rounds && rule.RoundCount == 1 && rule.Definition.GetDetails().Any()); if (roundRules.Any()) { // Randomize the list and pick the first unused one var rng = new Random(Environment.TickCount); var chosenRule = roundRules.Where(rule => rule.RoundLastUsedTime.Equals(AdKats.GetEpochTime())).OrderBy(rule => rng.Next()).FirstOrDefault(); if (chosenRule == null) { // None are unused, pick the one which has the longest duration since used chosenRule = roundRules.OrderBy(rule => rule.RoundLastUsedTime).FirstOrDefault(); } if (chosenRule != null) { chosenRule.RoundLastUsedTime = _plugin.UtcNow(); chosenRule.DBPush(null); RoundRule = chosenRule; if (ChallengeRoundState == ChallengeState.Ended) { // Delay announcing the new challenge for a few seconds _plugin.Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "ChallengeRoundRuleAnnounce"; Thread.Sleep(TimeSpan.FromSeconds(25)); var startMessage = RoundRule.Name + " Challenge Starting! Type " + _plugin.GetChatCommandByKey("self_challenge") + " for more info."; _plugin.ProconChatWrite(_plugin.Log.FBold(_plugin.Log.CPink(startMessage))); // Only tell players about the new challenge if they don't already have a challenge assigned foreach (var player in _plugin.GetOnlinePlayersWithoutGroup("challenge_ignore").Where(player => player.ActiveChallenge == null)) { _plugin.PlayerTellMessage(player.player_name, startMessage, false, 1); } _plugin.Threading.StopWatchdog(); }))); } } else { _plugin.Log.Error("Unable to select server-wide round rule."); } } else { _plugin.Log.Warn("Server-wide round rules enabled but none available. They must be enabled, tier 1, completion type Rounds, and have duration of 1 round."); } } else { _plugin.Log.Error("Unable to start new round rule. Rule " + RoundRule.ToString() + " is already running."); } } LoadedRoundID = roundID; ChallengeRoundState = ChallengeState.Loaded; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while running round load logic.", e)); } } public void OnRoundPlaying(Int32 roundID) { try { if (!Enabled) { return; } // Confirm we are in valid state to play. if (ChallengeRoundState != ChallengeState.Loaded) { // We're not, get out of here. _plugin.Log.Warn("Attempted to start playing challenge round when not in loaded state."); return; } if (LoadedRoundID != roundID) { _plugin.Log.Error("Attempted to start challenge playing with invalid round ID " + roundID + ", original round loaded was " + LoadedRoundID); return; } ChallengeRoundState = ChallengeState.Playing; if (EnableServerRoundRules) { var playerList = _plugin.GetOnlinePlayersWithoutGroup("challenge_ignore").Where(player => player.ActiveChallenge == null); if (RoundRule != null) { var startMessage = RoundRule.Name + " Challenge Starting! Type " + _plugin.GetChatCommandByKey("self_challenge") + " for more info."; _plugin.ProconChatWrite(_plugin.Log.FBold(_plugin.Log.CPink(startMessage))); // Only tell players about the new challenge if they don't already have a challenge assigned foreach (var player in playerList) { _plugin.PlayerTellMessage(player.player_name, startMessage, false, 1); } } else if (RandomPlayerRoundRules) { foreach (var player in playerList) { AssignActiveEntryForPlayer(player); } } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while running round play logic.", e)); } } public void RunRoundChallenge(Int32 RuleID) { try { if (Enabled) { if (Loading) { _plugin.Threading.Wait(5000); } if (!Loaded) { HandleRead(null, true); if (!Loaded) { _plugin.Log.Error("Unable to run next challenge. Manager could not complete loading."); return; } } if (!EnableServerRoundRules) { _plugin.Log.Error("Server-wide round rules not enabled. Unable to start rule."); return; } if (RandomPlayerRoundRules) { _plugin.Log.Error("Random player round rules are being used instead of one rule for everyone."); return; } var chosenRule = GetRules().FirstOrDefault(rule => rule.ID == RuleID); if (chosenRule == null) { // They entered an invalid value _plugin.Log.Error("Invalid rule ID entered. Unable to start rule."); return; } if (!chosenRule.Enabled) { // They entered an invalid value _plugin.Log.Error("Rule " + chosenRule.ToString() + " is not enabled. Unable to start rule."); return; } if (chosenRule.Tier != 1) { // They entered an invalid value _plugin.Log.Error("Rule " + chosenRule.ToString() + " is not a tier 1 rule. Unable to start rule."); return; } if (chosenRule.Completion != CRule.CompletionType.Rounds || chosenRule.RoundCount != 1) { // Only 1 round long, round based completion rules can be used. _plugin.Log.Error("Rule " + chosenRule.ToString() + " isn't round completion or doesn't have a round count of 1, unable to start rule."); return; } if (_plugin.EventActive()) { // Challenges are not allowed during events _plugin.Log.Error("Server-wide challenges cannot activate during events."); return; } CancelActiveRoundRule(); chosenRule.RoundLastUsedTime = _plugin.UtcNow(); chosenRule.DBPush(null); RoundRule = chosenRule; var startMessage = RoundRule.Name + " Challenge Starting! Type " + _plugin.GetChatCommandByKey("self_challenge") + " for more info."; _plugin.ProconChatWrite(_plugin.Log.FBold(_plugin.Log.CPink(startMessage))); // Only tell players about the new challenge if they don't already have a challenge assigned foreach (var player in _plugin.GetOnlinePlayersWithoutGroup("challenge_ignore").Where(player => player.ActiveChallenge == null)) { _plugin.PlayerTellMessage(player.player_name, startMessage, false, 1); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while running next challenge.", e)); } } public class CDefinition { private AdKats _plugin; public static String DetailTypeEnumString = "enum.ChallengeTemplateDetailType(None|Weapon|Damage)"; public static String DetailDamageEnumString = String.Empty; public Boolean Phantom; private AChallengeManager Manager; public Int64 ID; public String Name; public DateTime CreateTime; public DateTime ModifyTime; private List Details; public CDefinition(AdKats plugin, AChallengeManager manager, Boolean phantom) { _plugin = plugin; Manager = manager; Phantom = phantom; CreateTime = _plugin.UtcNow(); ModifyTime = _plugin.UtcNow(); Details = new List(); //Fill the damage type setting enum string, if it's not already if (DetailDamageEnumString == String.Empty) { Random random = new Random(Environment.TickCount); foreach (CDefinitionDetail.DetailDamage damageType in Enum.GetValues(typeof(CDefinitionDetail.DetailDamage)) .Cast() .OrderBy(type => type.ToString())) { if (String.IsNullOrEmpty(DetailDamageEnumString)) { DetailDamageEnumString += "enum.DetailDamageTypeEnum_" + random.Next(100000, 999999) + "("; } else { DetailDamageEnumString += "|"; } DetailDamageEnumString += damageType; } DetailDamageEnumString += ")"; } } public void SetNameByString(String newName) { try { if (String.IsNullOrEmpty(newName)) { _plugin.Log.Error("Definition name was empty when setting by string."); return; } var sanitizedName = _plugin.MakeAlphanumeric(newName); if (String.IsNullOrEmpty(sanitizedName)) { _plugin.Log.Error("Definition name was empty when setting by string."); return; } if (Name == sanitizedName) { _plugin.Log.Error("Definition name was the same when setting by string."); return; } // Check to see if the name can be parsed as an int32 // This is actually an issue with the procon setting display framework // For some reason if the string is numeric, it tries to parse it as a number if (Regex.IsMatch(sanitizedName, @"^\d+$")) { // String is numeric, try to parse it. Int32 parsed; if (!Int32.TryParse(sanitizedName, out parsed)) { // Can't parse it. Make it not numeric. sanitizedName += "X"; } } // Check if a definition exists with this name if (Manager.GetDefinitions().Any(dDef => dDef.Name == sanitizedName)) { _plugin.Log.Error("Definition called " + sanitizedName + " already exists."); return; } Name = sanitizedName; ModifyTime = _plugin.UtcNow(); // Push to the database. DBPush(null); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while setting definition name by string.", e)); } } public void SortDetails(MySqlConnection connection) { try { if (ID <= 0 || Phantom) { _plugin.Log.Error("CDefinition was invalid when sorting."); return; } var localConnection = connection; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { lock (Details) { var currentID = 1; foreach (var detail in Details.OrderBy(dDetail => dDetail.DetailID)) { // Take the current ordering by detail ID and create a sequential list detail.SetDetailID(localConnection, currentID++); if (detail.Phantom) { detail.DBPush(connection); } } } } finally { if (connection == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while sorting definition details.", e)); } } public List GetDetails() { var details = new List(); try { lock (Details) { details.AddRange(Details.OrderBy(det => det.DetailID).ToList()); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting list of details.", e)); } return details; } public CDefinitionDetail GetDetail(Int64 detailID) { try { lock (Details) { if (!Details.Any()) { return null; } return Details.FirstOrDefault(detail => detail.DetailID == detailID); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting detail by detail ID.", e)); } return null; } protected void DeleteDetail(Int64 detailID) { try { lock (Details) { var matchingDetail = Details.FirstOrDefault(detail => detail.DetailID == detailID); if (matchingDetail != null) { Details.Remove(matchingDetail); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while deleting detail from definition.", e)); } } public void CreateDetail(String type, String value) { try { lock (Details) { // Set the ID to 99999. This will be changed during the detail sort. var detail = new CDefinitionDetail(_plugin, this, 99999, true) { KillCount = 1 }; // Find the detail type if (type == CDefinitionDetail.DetailType.Damage.ToString()) { detail.Type = CDefinitionDetail.DetailType.Damage; detail.WeaponCount = 1; try { detail.Damage = (CDefinitionDetail.DetailDamage)Enum.Parse(typeof(CDefinitionDetail.DetailDamage), value); } catch (Exception e) { _plugin.Log.Error("Unable to create Damage detail with damage type " + value + "."); return; } if (Details.Any(dDetail => dDetail.Type == CDefinitionDetail.DetailType.Damage && dDetail.Damage == detail.Damage)) { _plugin.Log.Error("Detail with damage " + detail.Damage.ToString() + " already exists."); return; } // Check to see if any of the internal damage types are already added var existingDamageTypes = new List(); foreach (var damageTypeList in Details.Select(dDetail => dDetail.GetDamageTypes())) { existingDamageTypes.AddRange(damageTypeList); } foreach (var damageType in existingDamageTypes) { if (detail.GetDamageTypes().Contains(damageType)) { _plugin.Log.Error("Detail with internal damage " + damageType + " already exists."); return; } } } else if (type == CDefinitionDetail.DetailType.Weapon.ToString()) { detail.Type = CDefinitionDetail.DetailType.Weapon; var weaponCode = _plugin.WeaponDictionary.GetWeaponCodeByShortName(value.Split('\\')[1]); // Confirm the weapon is valid if (String.IsNullOrEmpty(weaponCode)) { _plugin.Log.Error("Unable to create Weapon detail with weapon name " + value + ". No matching weapon code exists."); return; } if (_plugin.WeaponDictionary.GetDamageTypeByWeaponCode(weaponCode) == DamageTypes.None) { _plugin.Log.Error("Unable to create Weapon detail with weapon code " + weaponCode + ". No valid matching damage type exists."); return; } if (Details.Any(dDetail => dDetail.Type == CDefinitionDetail.DetailType.Weapon && dDetail.Weapon == weaponCode)) { _plugin.Log.Error("Detail with weapon " + value + " already exists."); return; } detail.Weapon = weaponCode; } else { _plugin.Log.Error("Invalid detail type " + type + " when creating definition detail."); return; } Details.Add(detail); // Sorting details will automatically push newly created details SortDetails(null); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while creating definition detail in manager.", e)); } } public void DBPush(MySqlConnection connection) { try { if (connection == null) { connection = _plugin.GetDatabaseConnection(); } if (Phantom) { DBCreate(connection, true); } else { DBUpdate(connection, true); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBPush for CDefinition.", e)); } } private void DBCreate(MySqlConnection con, Boolean includeDetails) { try { var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" INSERT INTO `adkats_challenge_definition` ( `Name`, `CreateTime`, `ModifyTime` ) VALUES ( @Name, @CreateTime, @ModifyTime )"; command.Parameters.AddWithValue("@Name", Name); command.Parameters.AddWithValue("@CreateTime", CreateTime); command.Parameters.AddWithValue("@ModifyTime", ModifyTime); if (_plugin.SafeExecuteNonQuery(command) > 0) { ID = command.LastInsertedId; // This record is no longer phantom Phantom = false; if (includeDetails) { DBPushDetails(localConnection); }; } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBCreate for CDefinition.", e)); } } public void DBPushDetails(MySqlConnection con) { try { if (ID <= 0) { _plugin.Log.Error("ID " + ID + " was invalid when updating details for CDefinition."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { foreach (var detail in Details) { detail.DBPush(localConnection); } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBUpdateDetails for CDefinition.", e)); } } private void DBUpdate(MySqlConnection con, Boolean includeDetails) { try { var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" UPDATE `adkats_challenge_definition` SET `Name` = @Name, `ModifyTime` = @ModifyTime WHERE `ID` = @ID"; command.Parameters.AddWithValue("@ID", ID); command.Parameters.AddWithValue("@Name", Name); command.Parameters.AddWithValue("@ModifyTime", ModifyTime); if (_plugin.SafeExecuteNonQuery(command) > 0) { if (includeDetails) { DBPushDetails(con); } } else { _plugin.Log.Error("Failed to update CDefinition " + ID + " in database."); } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBUpdate for CDefinition.", e)); } } public void DBRead(MySqlConnection con) { try { if (ID <= 0) { _plugin.Log.Error("ID " + ID + " was invalid when reading CDefinition."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" SELECT `ID`, `Name`, `CreateTime`, `ModifyTime` FROM `adkats_challenge_definition` WHERE `ID` = @ID"; command.Parameters.AddWithValue("@ID", ID); var push = false; using (MySqlDataReader reader = _plugin.SafeExecuteReader(command)) { if (reader.Read()) { Name = reader.GetString("Name"); var sanitizedName = Name.Replace("|", ""); if (String.IsNullOrEmpty(sanitizedName)) { _plugin.Log.Error("Challenge definition " + ID + " contained invalid name characters."); var rng = new Random(Environment.TickCount); // Assume we won't hit duplicates with 1 million (welp) Name = "CHG-" + ID + "-" + rng.Next(1000000); push = true; } else if (Name != sanitizedName) { _plugin.Log.Error("Challenge definition " + ID + " contained invalid name characters."); Name = sanitizedName; push = true; } CreateTime = reader.GetDateTime("CreateTime"); ModifyTime = reader.GetDateTime("ModifyTime"); } else { _plugin.Log.Error("Unable to find matching CDefinition for ID " + ID); } } if (push) { DBPush(localConnection); } } DBReadDetails(localConnection); } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBRead for CDefinition.", e)); } } public void DBReadDetails(MySqlConnection con) { try { if (ID <= 0) { _plugin.Log.Error("ID " + ID + " was invalid when reading CDefinition Details."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" SELECT `DefID`, `DetailID`, `Type`, `Damage`, `WeaponCount`, `Weapon`, `KillCount`, `CreateTime`, `ModifyTime` FROM `adkats_challenge_definition_detail` WHERE `DefID` = @DefID"; command.Parameters.AddWithValue("@DefID", ID); var deleteDetails = new List(); using (MySqlDataReader reader = _plugin.SafeExecuteReader(command)) { lock (Details) { // Clear all existing details, we are fetching them all from the DB Details.Clear(); while (reader.Read()) { var add = false; var upload = false; var detailID = reader.GetInt32("DetailID"); CDefinitionDetail detail = Details.FirstOrDefault(dDetail => dDetail.DetailID == detailID); if (detail == null) { detail = new CDefinitionDetail(_plugin, this, detailID, false); add = true; } detail.Type = (CDefinitionDetail.DetailType)Enum.Parse(typeof(CDefinitionDetail.DetailType), reader.GetString("Type")); // Damage section if (!reader.IsDBNull(3)) { detail.Damage = (CDefinitionDetail.DetailDamage)Enum.Parse(typeof(CDefinitionDetail.DetailDamage), reader.GetString("Damage")); // Make sure we aren't loading in duplicate damage types if (detail.Type == CDefinitionDetail.DetailType.Damage && Details.Any(dDetail => dDetail.Type == CDefinitionDetail.DetailType.Damage && dDetail.Damage == detail.Damage)) { _plugin.Log.Error("Detail with damage " + detail.Damage.ToString() + " already exists."); deleteDetails.Add(detail); continue; } detail.WeaponCount = reader.GetInt32("WeaponCount"); if (detail.Type == CDefinitionDetail.DetailType.Damage && detail.WeaponCount < 1) { _plugin.Log.Error("Challenge detail " + this.ID + ":" + detail.DetailID + " had an invalid weapon count. Changing to 1."); detail.WeaponCount = 1; upload = true; } } // Weapon section if (!reader.IsDBNull(5)) { detail.Weapon = reader.GetString("Weapon"); // Make sure we aren't loading in duplicate weapon codes if (detail.Type == CDefinitionDetail.DetailType.Weapon && Details.Any(dDetail => dDetail.Type == CDefinitionDetail.DetailType.Weapon && dDetail.Weapon == detail.Weapon)) { _plugin.Log.Error("Detail with weapon " + detail.Weapon + " already exists."); deleteDetails.Add(detail); continue; } } detail.KillCount = reader.GetInt32("KillCount"); if (detail.KillCount < 1) { _plugin.Log.Error("Challenge detail " + this.ID + ":" + detail.DetailID + " had an invalid kill count. Changing to 1."); detail.KillCount = 1; upload = true; } detail.CreateTime = reader.GetDateTime("CreateTime"); detail.ModifyTime = reader.GetDateTime("ModifyTime"); if (upload) { detail.DBPush(localConnection); } if (add) { Details.Add(detail); } } // No need to clean up details, they are purged during every read. } } if (deleteDetails.Any()) { foreach (var detail in deleteDetails) { detail.DBDelete(localConnection); } SortDetails(localConnection); } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBReadDetails for CDefinition.", e)); } } public void DBDelete(MySqlConnection con) { try { var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_challenge_definition` WHERE `ID` = @ID;"; command.Parameters.AddWithValue("@ID", ID); if (_plugin.SafeExecuteNonQuery(command) > 0) { // SUCCESS // Clear all the details, they are removed automatically from the database _plugin.Log.Info("Deleted challenge definition " + ID + " with " + Details.Count() + " details."); Details.Clear(); Manager.DeleteDefinition(ID); } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBDelete for CDefinition.", e)); } } public class CDefinitionDetail { public enum DetailType { None, Weapon, Damage } public enum DetailDamage { None, Melee, Handgun, Assault_Rifle, Carbine, LMG, SMG, DMR, DMR_And_Sniper, Sniper_Rifle, Shotgun, Explosive } private AdKats _plugin; public Boolean Phantom; public CDefinition Definition { get; private set; } public Int64 DetailID { get; private set; } public DetailType Type = DetailType.None; public DetailDamage Damage = DetailDamage.None; public Int32 WeaponCount; public String Weapon; public Int32 KillCount; public DateTime CreateTime; public DateTime ModifyTime; public CDefinitionDetail(AdKats plugin, CDefinition definition, Int64 startingID, Boolean phantom) { _plugin = plugin; Definition = definition; DetailID = startingID; Phantom = phantom; CreateTime = _plugin.UtcNow(); ModifyTime = _plugin.UtcNow(); } public void DBPush(MySqlConnection con) { try { if (con == null) { con = _plugin.GetDatabaseConnection(); } if (Phantom) { DBCreate(con); } else { DBUpdate(con); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBPush for CDefinitionDetail.", e)); } } private void DBCreate(MySqlConnection con) { try { if (Definition == null || Definition.ID <= 0 || DetailID <= 0) { _plugin.Log.Error("CDefinitionDetail was invalid when creating."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" INSERT INTO `adkats_challenge_definition_detail` ( `DefID`, `DetailID`, `Type`, `Damage`, `WeaponCount`, `Weapon`, `KillCount`, `CreateTime`, `ModifyTime` ) VALUES ( @DefID, @DetailID, @Type, @Damage, @WeaponCount, @Weapon, @KillCount, @CreateTime, @ModifyTime ) ON DUPLICATE KEY UPDATE `Type` = @Type AND `Damage` = @Damage AND `WeaponCount` = @WeaponCount AND `Weapon` = @Weapon AND `KillCount` = @KillCount AND `CreateTime` = @CreateTime AND `ModifyTime` = @ModifyTime"; command.Parameters.AddWithValue("@DefID", Definition.ID); command.Parameters.AddWithValue("@DetailID", DetailID); command.Parameters.AddWithValue("@Type", Type.ToString()); command.Parameters.AddWithValue("@Damage", Damage.ToString()); command.Parameters.AddWithValue("@WeaponCount", WeaponCount); command.Parameters.AddWithValue("@Weapon", Weapon); command.Parameters.AddWithValue("@KillCount", KillCount); command.Parameters.AddWithValue("@CreateTime", CreateTime); command.Parameters.AddWithValue("@ModifyTime", ModifyTime); if (_plugin.SafeExecuteNonQuery(command) > 0) { // This record is no longer phantom Phantom = false; } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBCreate for CDefinitionDetail.", e)); } } private void DBUpdate(MySqlConnection con) { try { if (Definition == null || Definition.ID <= 0 || DetailID <= 0 || Phantom) { _plugin.Log.Error("CDefinitionDetail was invalid when updating."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" UPDATE `adkats_challenge_definition_detail` SET `Type` = @Type, `Damage` = @Damage, `WeaponCount` = @WeaponCount, `Weapon` = @Weapon, `KillCount` = @KillCount, `CreateTime` = @CreateTime, `ModifyTime` = @ModifyTime WHERE `DefID` = @DefID AND `DetailID` = @DetailID"; command.Parameters.AddWithValue("@DefID", Definition.ID); command.Parameters.AddWithValue("@DetailID", DetailID); command.Parameters.AddWithValue("@Type", Type.ToString()); command.Parameters.AddWithValue("@Damage", Damage.ToString()); command.Parameters.AddWithValue("@WeaponCount", WeaponCount); command.Parameters.AddWithValue("@Weapon", Weapon); command.Parameters.AddWithValue("@KillCount", KillCount); command.Parameters.AddWithValue("@CreateTime", CreateTime); command.Parameters.AddWithValue("@ModifyTime", ModifyTime); if (_plugin.SafeExecuteNonQuery(command) <= 0) { _plugin.Log.Error("Failed to update CDefinitionDetail " + Definition.ID + ":" + DetailID + " in database."); } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBUpdate for CDefinitionDetail.", e)); } } public void DBRead(MySqlConnection con) { try { if (Definition == null || Definition.ID <= 0 || DetailID <= 0 || Phantom) { _plugin.Log.Error("CDefinitionDetail was invalid when reading."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" SELECT `Type`, `Damage`, `WeaponCount`, `Weapon`, `KillCount`, `CreateTime`, `ModifyTime` FROM `adkats_challenge_definition_detail` WHERE `DefID` = @DefID AND `DetailID` = @DetailID"; command.Parameters.AddWithValue("@DefID", Definition.ID); command.Parameters.AddWithValue("@DetailID", DetailID); var push = false; var delete = false; using (MySqlDataReader reader = _plugin.SafeExecuteReader(command)) { if (reader.Read()) { Type = (DetailType)Enum.Parse(typeof(DetailType), reader.GetString("Type")); // Damage section if (!reader.IsDBNull(1)) { Damage = (DetailDamage)Enum.Parse(typeof(DetailDamage), reader.GetString("Damage")); // Make sure we aren't loading in duplicate damage types if (Type == DetailType.Damage && Definition.GetDetails().Any(dDetail => dDetail.Type == DetailType.Damage && dDetail.Damage == Damage)) { _plugin.Log.Error("Detail with damage " + Damage.ToString() + " already exists."); delete = true; } WeaponCount = reader.GetInt32("WeaponCount"); if (Type == DetailType.Damage && WeaponCount < 1) { _plugin.Log.Error("Challenge detail " + Definition.ID + ":" + DetailID + " had an invalid weapon count. Changing to 1."); WeaponCount = 1; push = true; } } // Weapon section if (!reader.IsDBNull(3)) { Weapon = reader.GetString("Weapon"); // Make sure we aren't loading in duplicate weapon codes if (Type == DetailType.Weapon && Definition.GetDetails().Any(dDetail => dDetail.Type == DetailType.Weapon && dDetail.Weapon == Weapon)) { _plugin.Log.Error("Detail with weapon " + Weapon + " already exists."); delete = true; } } KillCount = reader.GetInt32("KillCount"); if (KillCount < 1) { _plugin.Log.Error("Challenge detail " + Definition.ID + ":" + DetailID + " had an invalid kill count. Changing to 1."); KillCount = 1; push = true; } CreateTime = reader.GetDateTime("CreateTime"); ModifyTime = reader.GetDateTime("ModifyTime"); } else { _plugin.Log.Error("Unable to find matching CDefinitionDetail for " + Definition.ID + ":" + DetailID + "."); } } if (delete) { DBDelete(localConnection); Definition.DeleteDetail(DetailID); } else if (push) { DBPush(localConnection); } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBRead for CDefinitionDetail.", e)); } } public void DBDelete(MySqlConnection con) { try { if (Definition == null || Definition.ID <= 0 || DetailID <= 0 || Phantom) { _plugin.Log.Error("CDefinitionDetail was invalid when deleting."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_challenge_definition_detail` WHERE `DefID` = @DefID AND `DetailID` = @DetailID"; command.Parameters.AddWithValue("@DefID", Definition.ID); command.Parameters.AddWithValue("@DetailID", DetailID); if (_plugin.SafeExecuteNonQuery(command) > 0) { // SUCCESS _plugin.Log.Info("Deleted CDefinitionDetail " + Definition.ID + ":" + DetailID + "."); Definition.DeleteDetail(DetailID); } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBDelete for CDefinitionDetail.", e)); } } public void SetDetailID(MySqlConnection con, Int64 newDetailID) { try { if (newDetailID <= 0) { _plugin.Log.Error("newDetailID was invalid when changing ID."); return; } if (DetailID == newDetailID) { _plugin.Log.Info("Detail IDs were the same when changing ID."); return; } if (Phantom) { // No database link to update, simply set the new ID DetailID = newDetailID; return; } if (Definition == null || Definition.ID <= 0 || DetailID <= 0) { _plugin.Log.Error("CDefinitionDetail was invalid when changing ID."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" UPDATE IGNORE `adkats_challenge_definition_detail` SET `DetailID` = @NewDetailID WHERE `DefID` = @DefID AND `DetailID` = @OldDetailID"; command.Parameters.AddWithValue("@DefID", Definition.ID); command.Parameters.AddWithValue("@OldDetailID", DetailID); command.Parameters.AddWithValue("@NewDetailID", newDetailID); if (_plugin.SafeExecuteNonQuery(command) > 0) { DetailID = newDetailID; } else { _plugin.Log.Error("Error changing CDefinitionDetail " + Definition.ID + ":" + DetailID + " to ID " + newDetailID + "."); } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error updating ID for CDefinitionDetail.", e)); } } public void SetDamageTypeByString(String damageType) { try { // Confirm this is a damage type detail if (Type != DetailType.Damage) { _plugin.Log.Error("Tried to change damage type of detail when it wasn't a damage detail."); return; } if (String.IsNullOrEmpty(damageType)) { _plugin.Log.Error("Damage type was empty when setting damage type by string."); return; } var newDamage = (CDefinitionDetail.DetailDamage)Enum.Parse(typeof(CDefinitionDetail.DetailDamage), damageType); if (newDamage == Damage) { _plugin.Log.Info("Old detail damage type and new damage type were the same when setting damage type by string."); return; } Damage = newDamage; ModifyTime = _plugin.UtcNow(); DBPush(null); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error updating damage type by string for CDefinitionDetail.", e)); } } public void SetWeaponCountByString(String weaponCount) { try { // Confirm this is a damage type detail if (Type != DetailType.Damage) { _plugin.Log.Error("Tried to change weapon count of detail when it wasn't a damage detail."); return; } if (String.IsNullOrEmpty(weaponCount)) { _plugin.Log.Error("Weapon count was empty when setting weapon count by string."); return; } var newWeaponCount = Int32.Parse(weaponCount); if (newWeaponCount < 1) { _plugin.Log.Error("Weapon count cannot be less than 1."); newWeaponCount = 1; } if (newWeaponCount != WeaponCount) { WeaponCount = newWeaponCount; ModifyTime = _plugin.UtcNow(); DBPush(null); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error updating weapon count by string for CDefinitionDetail.", e)); } } public void SetWeaponNameByString(String weaponName) { try { // Confirm this is a weapon type detail if (Type != DetailType.Weapon) { _plugin.Log.Error("Tried to change weapon name of detail when it wasn't a weapon detail."); return; } if (String.IsNullOrEmpty(weaponName)) { _plugin.Log.Error("Weapon name was empty when setting weapon name by string."); return; } // Get the weapon code for this name var weaponSplit = weaponName.Split('\\'); if (weaponSplit.Count() != 2) { _plugin.Log.Error("Challenge weapon name '" + weaponName + "' was invalid. Unable to assign."); return; } var newWeapon = _plugin.WeaponDictionary.GetWeaponCodeByShortName(weaponSplit[1]); if (String.IsNullOrEmpty(newWeapon)) { _plugin.Log.Error("Error getting weapon code when setting weapon name by string."); return; } if (newWeapon != Weapon) { Weapon = newWeapon; ModifyTime = _plugin.UtcNow(); DBPush(null); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error updating weapon name by string for CDefinitionDetail.", e)); } } public void SetKillCountByString(String killCount) { try { if (String.IsNullOrEmpty(killCount)) { _plugin.Log.Error("Kill count was empty when setting kill count by string."); return; } var newKillCount = Int32.Parse(killCount); if (newKillCount < 1) { _plugin.Log.Error("Kill count cannot be less than 1."); newKillCount = 1; } if (newKillCount != KillCount) { KillCount = newKillCount; ModifyTime = _plugin.UtcNow(); DBPush(null); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error updating kill count by string for CDefinitionDetail.", e)); } } public List GetDamageTypes() { try { // Maps detail damage to procon damage types List damageTypes = new List(); if (Type == DetailType.Damage) { switch (Damage) { case DetailDamage.None: damageTypes.Add(DamageTypes.None); break; case DetailDamage.Melee: damageTypes.Add(DamageTypes.Melee); break; case DetailDamage.Handgun: damageTypes.Add(DamageTypes.Handgun); break; case DetailDamage.Assault_Rifle: damageTypes.Add(DamageTypes.AssaultRifle); break; case DetailDamage.Carbine: damageTypes.Add(DamageTypes.Carbine); break; case DetailDamage.LMG: damageTypes.Add(DamageTypes.LMG); break; case DetailDamage.SMG: damageTypes.Add(DamageTypes.SMG); break; case DetailDamage.DMR: damageTypes.Add(DamageTypes.DMR); break; case DetailDamage.DMR_And_Sniper: damageTypes.Add(DamageTypes.DMR); damageTypes.Add(DamageTypes.SniperRifle); break; case DetailDamage.Sniper_Rifle: damageTypes.Add(DamageTypes.SniperRifle); break; case DetailDamage.Shotgun: damageTypes.Add(DamageTypes.Shotgun); break; case DetailDamage.Explosive: damageTypes.Add(DamageTypes.Explosive); damageTypes.Add(DamageTypes.ProjectileExplosive); break; default: _plugin.Log.Info("Invalid detail damage when getting damage types."); break; } } if (!damageTypes.Any()) { damageTypes.Add(DamageTypes.None); } return damageTypes; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error getting damage types for CDefinitionDetail.", e)); } return null; } public override string ToString() { try { if (_plugin == null || Type == DetailType.None) { return "INVALID"; } var detailName = Type.ToString() + " - "; if (Type == DetailType.Damage) { detailName += Damage.ToString().Replace("_", " "); } else if (Type == DetailType.Weapon) { detailName += _plugin.WeaponDictionary.GetShortWeaponNameByCode(Weapon); } return detailName; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error stringifying definition detail.", e)); } return "ERROR"; } } } public class CRule { public enum CompletionType { None, Rounds, Duration, Deaths } public static String CompletionTypeEnumString = "enum.ChallengeRuleCompletionType(None|Rounds|Duration|Deaths)"; private AdKats _plugin; public Boolean Phantom; private AChallengeManager Manager; public Int64 ID; public Int64 ServerID; public CDefinition Definition; public Boolean Enabled; public String Name; public Int32 Tier; public CompletionType Completion = CompletionType.None; public Int32 RoundCount; public Int32 DurationMinutes; public Int32 DeathCount; public DateTime CreateTime; public DateTime ModifyTime; public DateTime RoundLastUsedTime; public DateTime PersonalLastUsedTime; public CRule(AdKats plugin, AChallengeManager manager, Boolean phantom) { _plugin = plugin; Manager = manager; Phantom = phantom; CreateTime = _plugin.UtcNow(); ModifyTime = _plugin.UtcNow(); RoundLastUsedTime = AdKats.GetEpochTime(); PersonalLastUsedTime = AdKats.GetEpochTime(); Tier = 1; RoundCount = 1; DurationMinutes = 60; DeathCount = 1; } public void DBPush(MySqlConnection con) { try { if (con == null) { con = _plugin.GetDatabaseConnection(); } if (Phantom) { DBCreate(con); } else { DBUpdate(con); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBPush for CDefinitionDetail.", e)); } } private void DBCreate(MySqlConnection con) { try { if (Definition == null || Definition.ID <= 0) { _plugin.Log.Error("CRule was invalid when creating."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" INSERT INTO `adkats_challenge_rule` ( `ServerID`, `DefID`, `Enabled`, `Name`, `Tier`, `CompletionType`, `RoundCount`, `DurationMinutes`, `DeathCount`, `CreateTime`, `ModifyTime`, `RoundLastUsedTime`, `PersonalLastUsedTime` ) VALUES ( @ServerID, @DefID, @Enabled, @Name, @Tier, @CompletionType, @RoundCount, @DurationMinutes, @DeathCount, @CreateTime, @ModifyTime, @RoundLastUsedTime, @PersonalLastUsedTime )"; if (ServerID <= 0) { // This rule doesn't have an associated server ID, and we're creating it. // Assign this server's ID ServerID = _plugin._serverInfo.ServerID; } command.Parameters.AddWithValue("@ServerID", ServerID); command.Parameters.AddWithValue("@DefID", Definition.ID); command.Parameters.AddWithValue("@Enabled", Enabled); command.Parameters.AddWithValue("@Name", Name); command.Parameters.AddWithValue("@Tier", Tier); command.Parameters.AddWithValue("@CompletionType", Completion.ToString()); command.Parameters.AddWithValue("@RoundCount", RoundCount); command.Parameters.AddWithValue("@DurationMinutes", DurationMinutes); command.Parameters.AddWithValue("@DeathCount", DeathCount); command.Parameters.AddWithValue("@CreateTime", CreateTime); command.Parameters.AddWithValue("@ModifyTime", ModifyTime); command.Parameters.AddWithValue("@RoundLastUsedTime", RoundLastUsedTime); command.Parameters.AddWithValue("@PersonalLastUsedTime", PersonalLastUsedTime); if (_plugin.SafeExecuteNonQuery(command) > 0) { ID = command.LastInsertedId; // This record is no longer phantom Phantom = false; } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBCreate for CRule.", e)); } } private void DBUpdate(MySqlConnection con) { try { if (Definition == null || Definition.ID <= 0 || Phantom || ID <= 0) { _plugin.Log.Error("CRule was invalid when updating."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" UPDATE `adkats_challenge_rule` SET `DefID` = @DefID, `Enabled` = @Enabled, `Name` = @Name, `Tier` = @Tier, `CompletionType` = @CompletionType, `RoundCount` = @RoundCount, `DurationMinutes` = @DurationMinutes, `DeathCount` = @DeathCount, `ModifyTime` = @ModifyTime, `RoundLastUsedTime` = @RoundLastUsedTime, `PersonalLastUsedTime` = @PersonalLastUsedTime WHERE `ID` = @ID"; command.Parameters.AddWithValue("@ID", ID); command.Parameters.AddWithValue("@DefID", Definition.ID); command.Parameters.AddWithValue("@Enabled", Enabled); command.Parameters.AddWithValue("@Name", Name); command.Parameters.AddWithValue("@Tier", Tier); command.Parameters.AddWithValue("@CompletionType", Completion.ToString()); command.Parameters.AddWithValue("@RoundCount", RoundCount); command.Parameters.AddWithValue("@DurationMinutes", DurationMinutes); command.Parameters.AddWithValue("@DeathCount", DeathCount); command.Parameters.AddWithValue("@ModifyTime", ModifyTime); command.Parameters.AddWithValue("@RoundLastUsedTime", RoundLastUsedTime); command.Parameters.AddWithValue("@PersonalLastUsedTime", PersonalLastUsedTime); if (_plugin.SafeExecuteNonQuery(command) <= 0) { _plugin.Log.Error("Failed to update CRule " + ID + " in database."); } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBUpdate for CRule.", e)); } } public void DBRead(MySqlConnection con) { try { if (Definition == null || Definition.ID <= 0 || Phantom || ID <= 0) { _plugin.Log.Error("CRule was invalid when reading."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" SELECT `ID`, `ServerID`, `DefID`, `Enabled`, `Name`, `Tier`, `CompletionType`, `RoundCount`, `DurationMinutes`, `DeathCount`, `CreateTime`, `ModifyTime`, `RoundLastUsedTime`, `PersonalLastUsedTime` FROM `adkats_challenge_rule` WHERE `ID` = @ID"; command.Parameters.AddWithValue("@ID", ID); var push = false; var delete = false; using (MySqlDataReader reader = _plugin.SafeExecuteReader(command)) { if (reader.Read()) { var serverID = reader.GetInt64("ServerID"); if (_plugin._serverInfo.ServerID != serverID) { _plugin.Log.Error("Invalid server ID when reading challenge rule " + ID + "."); delete = true; } ServerID = serverID; var definition = Manager.GetDefinition(reader.GetInt64("DefID")); if (definition == null) { _plugin.Log.Error("Invalid definition value when reading challenge rule " + ID + "."); delete = true; } Definition = definition; Enabled = reader.GetBoolean("Enabled"); Name = reader.GetString("Name"); var sanitizedName = Name.Replace("|", ""); if (String.IsNullOrEmpty(sanitizedName)) { _plugin.Log.Error("Challenge rule " + ID + " contained invalid name characters."); var rng = new Random(Environment.TickCount); // Assume we won't hit duplicates with 1 million (welp) Name = "CHG-" + ID + "-" + rng.Next(1000000); push = true; } else if (Name != sanitizedName) { _plugin.Log.Error("Challenge rule " + ID + " contained invalid name characters."); Name = sanitizedName; push = true; } Tier = reader.GetInt32("Tier"); if (Tier < 1) { _plugin.Log.Error("Rule tier cannot be less than 1."); Tier = 1; push = true; } if (Tier > 10) { _plugin.Log.Error("Rule tier cannot be greter than 10."); Tier = 10; push = true; } Completion = (CompletionType)Enum.Parse(typeof(CompletionType), reader.GetString("CompletionType")); RoundCount = reader.GetInt32("RoundCount"); DurationMinutes = reader.GetInt32("DurationMinutes"); DeathCount = reader.GetInt32("DeathCount"); CreateTime = reader.GetDateTime("CreateTime"); ModifyTime = reader.GetDateTime("ModifyTime"); RoundLastUsedTime = reader.GetDateTime("RoundLastUsedTime"); PersonalLastUsedTime = reader.GetDateTime("PersonalLastUsedTime"); } else { _plugin.Log.Error("Unable to find matching CRule for " + ID + "."); return; } } if (delete) { DBDelete(localConnection); } else if (push) { DBPush(localConnection); } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBRead for CRule.", e)); } } public void DBDelete(MySqlConnection con) { try { if (Definition == null || Definition.ID <= 0 || Phantom || ID <= 0) { _plugin.Log.Error("CRule was invalid when reading."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_challenge_rule` WHERE `ID` = @ID"; command.Parameters.AddWithValue("@ID", ID); if (_plugin.SafeExecuteNonQuery(command) > 0) { // SUCCESS _plugin.Log.Info("Deleted CRule " + ID + "."); Manager.DeleteRule(ID); } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBDelete for CRule.", e)); } } public void SetEnabledByString(String enabled) { try { if (String.IsNullOrEmpty(enabled)) { _plugin.Log.Error("Enabled was empty when setting by string."); return; } var newEnabled = Boolean.Parse(enabled); if (newEnabled != Enabled) { if (newEnabled && !Definition.GetDetails().Any()) { _plugin.Log.Error("Cannot enable rule " + Name + ", associated definition " + Definition.Name + " has no damages/weapons added."); return; } Enabled = newEnabled; ModifyTime = _plugin.UtcNow(); DBPush(null); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error updating enabled state by string for CRule.", e)); } } public void SetNameByString(String newName) { try { if (String.IsNullOrEmpty(newName)) { _plugin.Log.Error("Rule name was empty when setting by string."); return; } var sanitizedName = newName.Replace("|", ""); if (String.IsNullOrEmpty(sanitizedName)) { _plugin.Log.Error("Rule name was empty when setting by string."); return; } if (Name == sanitizedName) { _plugin.Log.Error("Rule name was the same when setting by string."); return; } // Check to see if the name can be parsed as an int32 // This is actually an issue with the procon setting display framework // For some reason if the string is numeric, it tries to parse it as a number if (Regex.IsMatch(sanitizedName, @"^\d+$")) { // String is numeric, try to parse it. Int32 parsed; if (!Int32.TryParse(sanitizedName, out parsed)) { // Can't parse it. Make it not numeric. sanitizedName += "X"; } } // Check if a definition exists with this name if (Manager.GetRules().Any(dRule => dRule.Name == sanitizedName)) { _plugin.Log.Error("Rule called " + sanitizedName + " already exists."); return; } Name = sanitizedName; ModifyTime = _plugin.UtcNow(); // Push to the database. DBPush(null); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while setting rule name by string.", e)); } } public void SetTierByString(String tier) { try { if (String.IsNullOrEmpty(tier)) { _plugin.Log.Error("Rule tier was empty when setting by string."); return; } var newTier = Int32.Parse(tier); if (newTier < 1) { _plugin.Log.Error("Rule tier cannot be less than 1."); newTier = 1; } if (newTier > 10) { _plugin.Log.Error("Rule tier cannot be greter than 10."); newTier = 10; } if (newTier == Tier) { _plugin.Log.Info("Old tier and new tier were the same when setting by string."); return; } Tier = newTier; ModifyTime = _plugin.UtcNow(); DBPush(null); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error updating tier by string for CRule.", e)); } } public void SetDefinitionByString(String definitionName) { try { if (String.IsNullOrEmpty(definitionName)) { _plugin.Log.Error("Definition name was empty when setting by string."); return; } var definitions = Manager.GetDefinitions(); if (!definitions.Any()) { _plugin.Log.Error("No definitions available when setting by string."); return; } var matchingDefinition = definitions.FirstOrDefault(def => def.Name == definitionName); if (matchingDefinition == null) { _plugin.Log.Error("No matching definition when setting by string."); return; } if (matchingDefinition.ID == Definition.ID) { _plugin.Log.Info("Old definition and new definition were the same when setting by string."); return; } Definition = matchingDefinition; ModifyTime = _plugin.UtcNow(); DBPush(null); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error updating definition by string for CRule.", e)); } } public void SetCompletionTypeByString(String completionType) { try { if (String.IsNullOrEmpty(completionType)) { _plugin.Log.Error("Completion type was empty when setting by string."); return; } var newCompletionType = (CompletionType)Enum.Parse(typeof(CompletionType), completionType); if (newCompletionType == Completion) { _plugin.Log.Info("Old completion type and new completion type were the same when setting by string."); return; } Completion = newCompletionType; ModifyTime = _plugin.UtcNow(); DBPush(null); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error updating completion type by string for CRule.", e)); } } public void SetRoundCountByString(String roundCount) { try { if (Completion != CompletionType.Rounds) { _plugin.Log.Error("Rule type was not ROUNDS when trying to set rounds duration by string."); return; } if (String.IsNullOrEmpty(roundCount)) { _plugin.Log.Error("Round count was empty when setting by string."); return; } var newRoundCount = Int32.Parse(roundCount); if (newRoundCount < 1) { _plugin.Log.Error("Round based rule duration cannot be less than 1 round."); newRoundCount = 1; } if (newRoundCount != RoundCount) { RoundCount = newRoundCount; ModifyTime = _plugin.UtcNow(); DBPush(null); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error updating round duration by string for CRule.", e)); } } public void SetDurationMinutesByString(String durationMinutes) { try { if (Completion != CompletionType.Duration) { _plugin.Log.Error("Rule type was not DURATION when trying to set minute duration by string."); return; } if (String.IsNullOrEmpty(durationMinutes)) { _plugin.Log.Error("Duration minutes was empty when setting by string."); return; } var newDurationMinutes = Int32.Parse(durationMinutes); if (newDurationMinutes < 1) { _plugin.Log.Error("Minute based rule duration cannot be less than 1 minute."); newDurationMinutes = 1; } if (newDurationMinutes != DurationMinutes) { DurationMinutes = newDurationMinutes; ModifyTime = _plugin.UtcNow(); DBPush(null); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error updating minute duration by string for CRule.", e)); } } public void SetDeathCountByString(String deathCount) { try { if (Completion != CompletionType.Deaths) { _plugin.Log.Error("Rule type was not DEATHS when trying to set rounds duration by string."); return; } if (String.IsNullOrEmpty(deathCount)) { _plugin.Log.Error("Death count was empty when setting by string."); return; } var newDeathCount = Int32.Parse(deathCount); if (newDeathCount < 1) { _plugin.Log.Error("Death based rule duration cannot be less than 1 death."); newDeathCount = 1; } if (newDeathCount != DeathCount) { DeathCount = newDeathCount; ModifyTime = _plugin.UtcNow(); DBPush(null); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error updating death duration by string for CRule.", e)); } } public String RuleInfo() { String info = ""; var details = Definition.GetDetails(); var damages = details.Where(det => det.Type == CDefinition.CDefinitionDetail.DetailType.Damage); if (damages.Any()) { info += "Weapon Types: "; foreach (var detail in damages) { var weaponS = detail.WeaponCount != 1 ? "s" : ""; var killS = detail.KillCount != 1 ? "s" : ""; info += "[" + detail.Damage.ToString().Replace("_", " ") + "/" + detail.WeaponCount + " Weapon" + weaponS + "/" + detail.KillCount + " Kill" + killS + "]" + Environment.NewLine; } info += Environment.NewLine; } var weapons = details.Where(det => det.Type == CDefinition.CDefinitionDetail.DetailType.Weapon); if (weapons.Any()) { info += "Weapons: "; foreach (var detail in weapons) { var killS = detail.KillCount != 1 ? "s" : ""; info += "[" + _plugin.WeaponDictionary.GetShortWeaponNameByCode(detail.Weapon) + "/" + detail.KillCount + " Kill" + killS + "] " + Environment.NewLine; } } return info; } public Boolean KillValid(AKill aKill) { try { if (ID <= 0 || Definition == null || Definition.ID <= 0) { _plugin.Log.Error("Rule was invalid when checking for valid kill."); return false; } // Check for invalid kill if (aKill == null || aKill.killer == null || String.IsNullOrEmpty(aKill.weaponCode) || aKill.victim == null) { _plugin.Log.Error("Kill was invalid when checking for valid kill."); return false; } // Silently cancel on teamkills if (aKill.IsTeamkill) { return false; } // Default to the kill being invalid var details = Definition.GetDetails(); if (!details.Any()) { _plugin.Log.Error("Tried to add kill to rule " + ID + ", definition " + Definition.ID + ", when that definition had no damages/weapons."); return false; } foreach (var detail in details) { if (detail.KillCount <= 0) { _plugin.Log.Error("Challenge definition detail " + detail.Definition.ID + "|" + detail.DetailID + " had a non-positive kill count."); continue; } switch (detail.Type) { case CDefinition.CDefinitionDetail.DetailType.None: _plugin.Log.Error("Challenge definition damage detail " + detail.Definition.ID + "|" + detail.DetailID + " had a NONE rule type."); break; case CDefinition.CDefinitionDetail.DetailType.Damage: // Check for matching damage if (detail.WeaponCount <= 0) { _plugin.Log.Error("Challenge definition damage detail " + detail.Definition.ID + "|" + detail.DetailID + "|" + detail.Damage.ToString() + " had non-positive weapon count."); break; } if (!detail.GetDamageTypes().Contains(aKill.weaponDamage)) { break; } return true; case CDefinition.CDefinitionDetail.DetailType.Weapon: // Check for matching weapon if (detail.Weapon != aKill.weaponCode) { break; } return true; } } return false; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while checking whether challenge kill was valid.", e)); } return false; } public override string ToString() { return ID + " (Tier " + Tier + " / " + Name + ")"; } } public class CEntry { private AdKats _plugin; public Boolean Phantom; private AChallengeManager Manager; public Int64 ID; public APlayer Player; public CRule Rule; public Boolean Completed; public Boolean Failed; public Boolean Canceled; public Int32 StartRound; public DateTime StartTime; public DateTime CompleteTime; public List Details; public EntryProgress Progress; public String LastCompletedWeapon; public Boolean AutoKillTold; public Boolean Died; public Boolean kAllowed; public CEntry(AdKats plugin, AChallengeManager manager, APlayer player, CRule rule, Int32 startingRound, Boolean phantom) { _plugin = plugin; Manager = manager; Player = player; Rule = rule; Phantom = phantom; StartRound = startingRound; if (StartRound <= 1) { throw new ArgumentException("Error creating CEntry. Starting round number was invalid."); } StartTime = _plugin.UtcNow(); CompleteTime = AdKats.GetEpochTime(); Details = new List(); } public List GetDetails() { var details = new List(); try { lock (Details) { details.AddRange(Details.OrderBy(det => det.DetailID).ToList()); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting list of details.", e)); } return details; } public CEntryDetail GetDetail(Int64 detailID) { try { lock (Details) { if (!Details.Any()) { return null; } return Details.FirstOrDefault(detail => detail.DetailID == detailID); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting detail by detail ID.", e)); } return null; } public void CheckFailure() { try { // If this entry is already closed, don't process it if (!Completed && !Failed && !Canceled) { // The entry is active // Check for round duration failure if (Rule.Completion == CRule.CompletionType.Rounds) { // Completion round is the same round if RoundCount = 1 var completionRound = StartRound + Rule.RoundCount - 1; if (Manager.LoadedRoundID > completionRound || (Manager.ChallengeRoundState == ChallengeState.Ended && Manager.LoadedRoundID == completionRound)) { // This entry is overtime. Fail it. DoFail(); return; } } else if (Rule.Completion == CRule.CompletionType.Duration) { var completionTime = StartTime + TimeSpan.FromMinutes(Rule.DurationMinutes); // If the current round is if (_plugin.UtcNow() > completionTime) { // This entry is overtime. Fail it. DoFail(); return; } } else if (Rule.Completion == CRule.CompletionType.Deaths) { // Ignore death count based checks, those are handled on the death event itself } else { _plugin.Log.Error("Unable to do validation on entry " + ID + ", completion type is invalid."); } // Automatically cancel challenges that haven't been updated in 10 days. if (Details.Any()) { DateTime lastModifyTime = Details.Max(detail => detail.Kill.TimeStamp); TimeSpan durationSinceModified = _plugin.NowDuration(lastModifyTime); if (durationSinceModified > TimeSpan.FromDays(10)) { DoCancel(); } } else { TimeSpan durationSinceStarted = _plugin.NowDuration(StartTime); if (durationSinceStarted > TimeSpan.FromDays(10)) { DoCancel(); } } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing Validate for CEntry.", e)); } } public void DoFail() { try { if (ID <= 0 || Canceled || Completed || Failed || Player == null || Player.player_id <= 0) { _plugin.Log.Error("Attempted to fail a challenge entry when it was invalid."); return; } // We're valid to fail. Continue. // Get progress. var percentage = "0%"; // Only load the progress if details exist, otherwise the percentage is always 0 if (Details.Any()) { if (Progress == null) { RefreshProgress(null); } percentage = Math.Round(Progress.CompletionPercentage) + "%"; } _plugin.PlayerSayMessage(Player.player_name, _plugin.Log.CPink(Rule.Name + " challenge FAILED at " + percentage + " complete."), Manager.ChallengeRoundState == ChallengeState.Playing, 1); Failed = true; CompleteTime = _plugin.UtcNow(); DBPush(null); Player.ActiveChallenge = null; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing Fail for CEntry.", e)); } } public void DoComplete() { try { if (ID <= 0 || Canceled || Completed || Failed || Player == null || Player.player_id <= 0) { _plugin.Log.Error("Attempted to fail a challenge entry when it was invalid."); return; } // We're valid to fail. Continue. // Get progress. var percentage = "0%"; // Only load the progress if details exist, otherwise the percentage is always 0 if (Details.Any()) { if (Progress == null) { RefreshProgress(null); } percentage = Math.Round(Progress.CompletionPercentage) + "%"; } // Validate that they actually completed it. if (Progress.CompletionPercentage < 99.999) { _plugin.Log.Error("Attempted to complete challenge without 100% completion."); return; } // Process rewards. var rewardMessage = ""; var matchingRewards = Manager.GetRewards().Where(dReward => dReward.Tier == Rule.Tier && dReward.Enabled && dReward.Reward != CReward.RewardType.None); var givenRewards = new List(); foreach (var reward in matchingRewards) { Int32 existingMinutes = 0; List existingPlayers = new List(); var descriptionString = reward.getDescriptionString(Player); switch (reward.Reward) { case CReward.RewardType.ReservedSlot: existingPlayers = _plugin.GetMatchingASPlayersOfGroup("slot_reserved", Player); if (existingPlayers.Any()) { existingMinutes = (Int32)_plugin.NowDuration(existingPlayers.First().player_expiration).TotalMinutes; } _plugin.QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _plugin._serverInfo.ServerID, command_type = _plugin.GetCommandByKey("player_slotreserved"), command_numeric = existingMinutes + reward.DurationMinutes, target_name = Player.player_name, target_player = Player, source_name = "ChallengeManager", record_message = Player.GetVerboseName() + " completed tier " + reward.Tier + " challenge, assigning " + descriptionString + ".", record_time = _plugin.UtcNow() }); givenRewards.Add(descriptionString); break; case CReward.RewardType.SpectatorSlot: existingPlayers = _plugin.GetMatchingASPlayersOfGroup("slot_spectator", Player); if (existingPlayers.Any()) { existingMinutes = (Int32)_plugin.NowDuration(existingPlayers.First().player_expiration).TotalMinutes; } _plugin.QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _plugin._serverInfo.ServerID, command_type = _plugin.GetCommandByKey("player_slotspectator"), command_numeric = existingMinutes + reward.DurationMinutes, target_name = Player.player_name, target_player = Player, source_name = "ChallengeManager", record_message = Player.GetVerboseName() + " completed tier " + reward.Tier + " challenge, assigning " + descriptionString + ".", record_time = _plugin.UtcNow() }); givenRewards.Add(descriptionString); break; case CReward.RewardType.BalanceWhitelist: existingPlayers = _plugin.GetMatchingASPlayersOfGroup("whitelist_multibalancer", Player); if (existingPlayers.Any()) { existingMinutes = (Int32)_plugin.NowDuration(existingPlayers.First().player_expiration).TotalMinutes; } _plugin.QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _plugin._serverInfo.ServerID, command_type = _plugin.GetCommandByKey("player_whitelistbalance"), command_numeric = existingMinutes + reward.DurationMinutes, target_name = Player.player_name, target_player = Player, source_name = "ChallengeManager", record_message = Player.GetVerboseName() + " completed tier " + reward.Tier + " challenge, assigning " + descriptionString + ".", record_time = _plugin.UtcNow() }); givenRewards.Add(descriptionString); break; case CReward.RewardType.PingWhitelist: existingPlayers = _plugin.GetMatchingASPlayersOfGroup("whitelist_ping", Player); if (existingPlayers.Any()) { existingMinutes = (Int32)_plugin.NowDuration(existingPlayers.First().player_expiration).TotalMinutes; } _plugin.QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _plugin._serverInfo.ServerID, command_type = _plugin.GetCommandByKey("player_whitelistping"), command_numeric = existingMinutes + reward.DurationMinutes, target_name = Player.player_name, target_player = Player, source_name = "ChallengeManager", record_message = Player.GetVerboseName() + " completed tier " + reward.Tier + " challenge, assigning " + descriptionString + ".", record_time = _plugin.UtcNow() }); givenRewards.Add(descriptionString); break; case CReward.RewardType.TeamKillTrackerWhitelist: existingPlayers = _plugin.GetMatchingASPlayersOfGroup("whitelist_teamkill", Player); if (existingPlayers.Any()) { existingMinutes = (Int32)_plugin.NowDuration(existingPlayers.First().player_expiration).TotalMinutes; } _plugin.QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _plugin._serverInfo.ServerID, command_type = _plugin.GetCommandByKey("player_whitelistteamkill"), command_numeric = existingMinutes + reward.DurationMinutes, target_name = Player.player_name, target_player = Player, source_name = "ChallengeManager", record_message = Player.GetVerboseName() + " completed tier " + reward.Tier + " challenge, assigning " + descriptionString + ".", record_time = _plugin.UtcNow() }); givenRewards.Add(descriptionString); break; case CReward.RewardType.CommandLock: var lockString = "command lock"; if (_plugin._UseExperimentalTools) { lockString = "rule breaking allowed"; } var time = _plugin.UtcNow(); var lockCommand = _plugin.GetCommandByKey("player_lock"); var recentLock = _plugin.FetchRecentRecords(Player.player_id, lockCommand.command_id, 1000, 1, true, false).FirstOrDefault(); if (recentLock != null && recentLock.source_name == "ChallengeManager") { var durationSinceLast = _plugin.NowDuration(recentLock.record_time); if (durationSinceLast.TotalHours < Manager.CommandLockTimeoutHours) { Player.Say("Unable to award '" + lockString + "' until it's active again."); continue; } } _plugin.QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _plugin._serverInfo.ServerID, command_type = lockCommand, command_numeric = reward.DurationMinutes, target_name = Player.player_name, target_player = Player, source_name = "ChallengeManager", record_message = Player.GetVerboseName() + " completed tier " + reward.Tier + " challenge, assigning " + descriptionString + ".", record_time = time }); var end = time.AddMinutes(reward.DurationMinutes); _plugin.Threading.StartWatchdog(new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "CommandLockMonitor"; Thread.Sleep(TimeSpan.FromSeconds(3)); _plugin.AdminSayMessage(Player.GetVerboseName() + " " + lockString + " ACTIVE!"); while (true) { if (!_plugin._pluginEnabled) { break; } if (_plugin.UtcNow() > end) { _plugin.AdminSayMessage(Player.GetVerboseName() + " " + lockString + " ENDED!"); break; } var duration = _plugin.NowDuration(end); var durationSec = (Int32)duration.TotalSeconds; // Send the message every 30 seconds if (durationSec % 30 == 0) { _plugin.AdminSayMessage(Player.GetVerboseName() + " " + lockString + ", " + _plugin.FormatTimeString(duration, 2) + " remaining!"); } _plugin.Threading.Wait(1000); } _plugin.Threading.StopWatchdog(); }))); givenRewards.Add(descriptionString); break; } } if (givenRewards.Any()) { rewardMessage = String.Join(", ", givenRewards.ToArray()); } var spacer = "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"; var finishing = Player.GetVerboseName() + " finished tier " + Rule.Tier + " challenge"; var gratz = Rule.Name + "! Congrats!"; if (givenRewards.Any()) { rewardMessage = "Reward: " + rewardMessage; } _plugin.ProconChatWrite(_plugin.Log.FBold(_plugin.Log.CPink(finishing + " " + gratz))); foreach (var player in _plugin.GetOnlinePlayersWithoutGroup("challenge_ignore")) { _plugin.PlayerSayMessage(player.player_name, spacer, false, 1); _plugin.PlayerSayMessage(player.player_name, finishing, false, 1); _plugin.PlayerSayMessage(player.player_name, gratz, false, 1); if (givenRewards.Any()) { _plugin.PlayerSayMessage(player.player_name, rewardMessage, false, 1); } _plugin.PlayerSayMessage(player.player_name, spacer, false, 1); } if (givenRewards.Any()) { Player.Say("Use " + _plugin.GetChatCommandByKey("player_perks") + " to see your updated perks."); } Completed = true; CompleteTime = _plugin.UtcNow(); DBPush(null); Manager.AddCompletedEntryForRound(this); _plugin.QueueRecordForProcessing(new ARecord { record_source = ARecord.Sources.Automated, server_id = _plugin._serverInfo.ServerID, command_type = _plugin.GetCommandByKey("player_challenge_complete"), command_numeric = Rule.Tier, target_name = Player.player_name, target_player = Player, source_name = "ChallengeManager", record_message = "Completed Tier " + Rule.Tier + " Challenge [" + Rule.Name + "]", record_time = _plugin.UtcNow() }); Player.ActiveChallenge = null; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing Fail for CEntry.", e)); } } public void DoCancel() { try { if (ID <= 0 || Canceled || Completed || Failed || Player == null || Player.player_id <= 0) { _plugin.Log.Error("Attempted to cancel a challenge entry when it was invalid."); return; } // We're valid to cancel. Continue. // Get progress. var percentage = "0%"; // Only load the progress if details exist, otherwise the percentage is always 0 if (Details.Any()) { if (Progress == null) { RefreshProgress(null); } percentage = Math.Round(Progress.CompletionPercentage) + "%"; } Player.Say(Rule.Name + " challenge CANCELLED at " + percentage + " complete."); Canceled = true; CompleteTime = _plugin.UtcNow(); DBPush(null); Player.ActiveChallenge = null; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing Cancel for CEntry.", e)); } } public void DBPush(MySqlConnection connection) { try { if (connection == null) { connection = _plugin.GetDatabaseConnection(); } if (Phantom) { DBCreate(connection, true); } else { DBUpdate(connection, true); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBPush for CEntry.", e)); } } private void DBCreate(MySqlConnection con, Boolean includeDetails) { try { var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" INSERT INTO `adkats_challenge_entry` ( `PlayerID`, `RuleID`, `Completed`, `Failed`, `Canceled`, `StartRound`, `StartTime`, `CompleteTime` ) VALUES ( @PlayerID, @RuleID, @Completed, @Failed, @Canceled, @StartRound, @StartTime, @CompleteTime )"; command.Parameters.AddWithValue("@PlayerID", Player.player_id); command.Parameters.AddWithValue("@RuleID", Rule.ID); command.Parameters.AddWithValue("@Completed", Completed); command.Parameters.AddWithValue("@Failed", Failed); command.Parameters.AddWithValue("@Canceled", Canceled); command.Parameters.AddWithValue("@StartRound", StartRound); command.Parameters.AddWithValue("@StartTime", StartTime); command.Parameters.AddWithValue("@CompleteTime", CompleteTime); if (_plugin.SafeExecuteNonQuery(command) > 0) { ID = command.LastInsertedId; // This record is no longer phantom Phantom = false; } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBCreate for CEntry.", e)); } } private void DBUpdate(MySqlConnection con, Boolean includeDetails) { try { if (ID <= 0) { _plugin.Log.Error("ID " + ID + " was invalid when updating CEntry."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" UPDATE `adkats_challenge_entry` SET `Completed` = @Completed, `Failed` = @Failed, `Canceled` = @Canceled, `CompleteTime` = @CompleteTime WHERE `ID` = @ID"; command.Parameters.AddWithValue("@ID", ID); command.Parameters.AddWithValue("@Completed", Completed); command.Parameters.AddWithValue("@Failed", Failed); command.Parameters.AddWithValue("@Canceled", Canceled); command.Parameters.AddWithValue("@CompleteTime", CompleteTime); if (_plugin.SafeExecuteNonQuery(command) <= 0) { _plugin.Log.Error("Failed to update CEntry " + ID + " in database."); } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBUpdate for CEntry.", e)); } } public void DBRead(MySqlConnection con) { try { if (ID <= 0) { _plugin.Log.Error("ID " + ID + " was invalid when reading CEntry."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" SELECT `ID`, `PlayerID`, `RuleID`, `Completed`, `Failed`, `Canceled`, `StartRound`, `StartTime`, `CompleteTime` FROM `adkats_challenge_entry` WHERE `ID` = @`ID`"; command.Parameters.AddWithValue("@ID", ID); using (MySqlDataReader reader = _plugin.SafeExecuteReader(command)) { if (reader.Read()) { var playerID = reader.GetInt64("PlayerID"); if (Player != null) { // We have a player already if (Player.player_id != playerID) { _plugin.Log.Error("WHAT. Entry " + ID + " changed player on the database. This should never happen."); } } else { Player = _plugin.FetchPlayer(false, false, false, null, playerID, null, null, null, null); } if (Player == null) { _plugin.Log.Error("Unable to fetch player for Entry " + ID + " and player " + playerID + ". Unable to read."); return; } var ruleID = reader.GetInt64("RuleID"); if (Rule != null) { // We have a rule already if (Rule.ID != ruleID) { _plugin.Log.Error("NOOOO. Entry changed rule on the database. This should never happen."); } } else { Rule = Manager.GetRule(ruleID); } if (Rule == null) { _plugin.Log.Error("Unable to fetch rule for Entry " + ID + " and rule " + ruleID + ". Unable to read."); return; } Completed = reader.GetBoolean("Completed"); Failed = reader.GetBoolean("Failed"); Canceled = reader.GetBoolean("Canceled"); StartRound = reader.GetInt32("StartRound"); StartTime = reader.GetDateTime("StartTime"); CompleteTime = reader.GetDateTime("CompleteTime"); RefreshProgress(null); } else { _plugin.Log.Error("Unable to find matching CEntry for ID " + ID); } } } DBReadDetails(localConnection); } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBRead for CEntry.", e)); } } public void DBReadDetails(MySqlConnection con) { try { if (ID <= 0) { _plugin.Log.Error("ID " + ID + " was invalid when reading CEntry details."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" SELECT `DetailID`, `VictimID`, `Weapon`, `RoundID`, `DetailTime` FROM `adkats_challenge_entry_detail` WHERE `EntryID` = @EntryID ORDER BY `DetailID` ASC"; command.Parameters.AddWithValue("@EntryID", ID); using (MySqlDataReader reader = _plugin.SafeExecuteReader(command)) { lock (Details) { var changed = false; // Clear all existing details, we are fetching them all from the DB while (reader.Read()) { var detailID = reader.GetInt32("DetailID"); if (detailID <= 0) { _plugin.Log.Error("Entry detail " + ID + ":" + detailID + " was invalid, unable to load."); continue; } if (GetDetails().Any(dDetail => dDetail.DetailID == detailID)) { // This single message will be VERY spammy once the manager starts being used. Remove after testing. _plugin.Log.Error("Entry detail " + ID + ":" + detailID + " already loaded. Skipping."); continue; } var detailKiller = Player; if (detailKiller == null) { _plugin.Log.Error("Killer was invalid when loading entry detail " + ID + ":" + detailID + ", unable to load."); continue; } var victimID = reader.GetInt32("VictimID"); var detailVictim = _plugin.FetchPlayer(false, false, false, null, victimID, null, null, null, null); if (detailVictim == null) { _plugin.Log.Error("Victim " + victimID + " was invalid when loading entry detail " + ID + ":" + detailID + ", unable to load."); continue; } var detailWeaponCode = reader.GetString("Weapon"); var detailWeaponDamage = _plugin.WeaponDictionary.GetDamageTypeByWeaponCode(detailWeaponCode); // Use damage validation to make sure the weapon is real if (detailWeaponDamage == DamageTypes.None && detailWeaponCode != "Death") { _plugin.Log.Error("Weapon " + detailWeaponCode + " was invalid when loading entry detail " + ID + ":" + detailID + ", unable to load."); continue; } var detailRoundID = reader.GetInt32("RoundID"); if (detailRoundID <= 0) { _plugin.Log.Error("Round ID " + detailRoundID + " was invalid when loading entry detail " + ID + ":" + detailID + ", unable to load."); continue; } var detailTime = reader.GetDateTime("DetailTime"); // Build the kill object var kill = new AKill(_plugin) { killer = detailKiller, weaponCode = detailWeaponCode, weaponDamage = detailWeaponDamage, victim = detailVictim, RoundID = detailRoundID, UTCTimeStamp = detailTime, TimeStamp = detailTime.ToLocalTime() }; Details.Add(new CEntryDetail(_plugin, this, detailID, kill, false)); changed = true; } if (changed) { RefreshProgress(null); } } } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBReadDetails for CEntry.", e)); } } public Boolean AddKill(AKill aKill) { try { if (!Manager.Enabled || !Rule.Enabled || Completed || Failed || Canceled) { _plugin.Log.Info("Entry " + ID + " was invalid when adding kill."); return false; } // Check for invalid entry if (aKill.killer.player_id != Player.player_id) { _plugin.Log.Info("Kill player " + aKill.killer.GetVerboseName() + " did not match entry player " + Player.GetVerboseName() + "."); return false; } // Check for invalid rule if (Rule == null) { _plugin.Log.Warn("Entry " + ID + " rule was null when trying to add kill: " + aKill.ToString()); return false; } // Check for invalid kill if (!Rule.KillValid(aKill)) { return false; } lock (Details) { // Create the new detail ID as one more than the current max. var detailID = Details.Select(dDetail => dDetail.DetailID).DefaultIfEmpty(0).Max() + 1; if (detailID <= 0) { _plugin.Log.Error("Entry detail " + ID + ":" + detailID + " was invalid. Generated detail ID was not valid. Unable to add kill."); return false; } if (GetDetails().Any(dDetail => dDetail.DetailID == detailID)) { _plugin.Log.Error("Entry detail " + ID + ":" + detailID + " already existed when adding kill. Unable to add kill."); return false; } if (aKill.killer == null || aKill.killer.player_id <= 0) { _plugin.Log.Error("Killer was invalid when adding kill to entry detail " + ID + ":" + detailID + ". Unable to add kill."); return false; } if (aKill.victim == null || aKill.victim.player_id <= 0) { _plugin.Log.Error("Victim was invalid when adding kill to entry detail " + ID + ":" + detailID + ". Unable to add kill."); return false; } // Use damage validation to make sure the weapon is real if (aKill.weaponDamage == DamageTypes.None) { _plugin.Log.Error("Weapon " + aKill.weaponCode + " was invalid when adding kill to entry detail " + ID + ":" + detailID + ". Unable to add kill."); return false; } if (aKill.RoundID <= 0) { _plugin.Log.Error("Round ID " + aKill.RoundID + " was invalid when adding kill to entry detail " + ID + ":" + detailID + ". Unable to add kill."); return false; } // We're good so far. Now make sure the kill is meaningful. // Meaningful being adding it to an available damage/weapon bucket if (!RefreshProgress(aKill)) { // Kill was not meaningful return false; } var newDetail = new CEntryDetail(_plugin, this, detailID, aKill, true); newDetail.DBPush(null); // Make sure the upload was successful before adding if (newDetail.Phantom) { _plugin.Log.Error("Challenge entry detail " + ID + ":" + detailID + " could not upload. Unable to add kill."); return false; } // Everything is validated. Add the kill. Details.Add(newDetail); if (Progress.CompletionPercentage >= 99.999) { DoComplete(); return true; } Progress.SendStatusForKill(aKill); return true; } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while adding kill to challenge entry.", e)); } return false; } public void AddDeath(AKill inputKill) { try { if (!Manager.Enabled || !Rule.Enabled || Completed || Failed || Canceled) { _plugin.Log.Info("Entry " + ID + " was invalid when adding death."); return; } // Check for invalid entry if (inputKill.victim == null || inputKill.victim.player_id != Player.player_id) { _plugin.Log.Info("Victim player " + inputKill.victim.GetVerboseName() + " did not match entry player " + Player.GetVerboseName() + "."); return; } // Check for invalid rule if (Rule == null) { _plugin.Log.Warn("Entry " + ID + " rule was null when trying to add death: " + inputKill.ToString()); return; } if (Died) { return; } lock (Details) { // Create the new detail ID as one more than the current max. var detailID = Details.Select(dDetail => dDetail.DetailID).DefaultIfEmpty(0).Max() + 1; if (detailID <= 0) { _plugin.Log.Error("Entry detail " + ID + ":" + detailID + " was invalid. Generated detail ID was not valid. Unable to add death."); return; } if (GetDetails().Any(dDetail => dDetail.DetailID == detailID)) { _plugin.Log.Error("Entry detail " + ID + ":" + detailID + " already existed when adding death. Unable to add death."); return; } if (inputKill.killer == null) { // The killer player was null. This is likely an admin kill. return; } if (inputKill.killer == inputKill.victim || inputKill.IsSuicide) { // They suicided. return; } if (inputKill.RoundID <= 0) { _plugin.Log.Error("Round ID " + inputKill.RoundID + " was invalid when adding death to entry detail " + ID + ":" + detailID + ". Unable to add death."); return; } // Need to make a new kill object // Change the kill code to Death var death = new AKill(_plugin) { killer = inputKill.killer, weaponCode = "Death", weaponDamage = DamageTypes.None, victim = inputKill.victim, IsHeadshot = inputKill.IsHeadshot, IsSuicide = inputKill.IsSuicide, IsTeamkill = inputKill.IsTeamkill, RoundID = inputKill.RoundID, TimeStamp = inputKill.TimeStamp, UTCTimeStamp = inputKill.UTCTimeStamp }; var newDetail = new CEntryDetail(_plugin, this, detailID, death, true); newDetail.DBPush(null); // Make sure the upload was successful before adding if (newDetail.Phantom) { _plugin.Log.Error("Challenge entry detail " + ID + ":" + detailID + " could not upload. Unable to add death."); return; } // Everything is validated. Add the death. Details.Add(newDetail); RefreshProgress(null); // We only care about responding to the death if this is a death or time based challenge var commandText = _plugin.GetChatCommandByKey("self_challenge"); if (Rule.Completion == CRule.CompletionType.Deaths) { var deaths = Progress.Deaths.Count(); var deathsRemaining = Rule.DeathCount - deaths; if (Progress.Deaths.Count() >= Rule.DeathCount) { DoFail(); return; } var deathS = deathsRemaining != 1 ? "s" : ""; _plugin.PlayerSayMessage(Player.player_name, commandText + " You have " + deathsRemaining + " death" + deathS + " remaining.", false, 1); Died = true; } else if (Rule.Completion == CRule.CompletionType.Duration) { var completionTime = StartTime + TimeSpan.FromMinutes(Rule.DurationMinutes); var completionDuration = _plugin.NowDuration(completionTime); _plugin.PlayerSayMessage(Player.player_name, commandText + " You have " + _plugin.FormatTimeString(completionDuration, 3) + " remaining.", false, 1); Died = true; } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while adding death to challenge entry.", e)); } } public void AddSpawn(APlayer player) { try { if (!Manager.Enabled || !Rule.Enabled || Completed || Failed || Canceled) { _plugin.Log.Info("Entry " + ID + " was invalid when adding spawn."); return; } // Check for invalid entry if (player == null || player.player_id != Player.player_id) { _plugin.Log.Info("Spawn player " + player.GetVerboseName() + " did not match entry player " + Player.GetVerboseName() + "."); return; } // Check for invalid rule if (Rule == null) { _plugin.Log.Warn("Entry " + ID + " rule was null when trying to add spawn."); return; } // Reset the flag so they are allowed to accept deaths again. Died = false; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while adding spawn to challenge entry.", e)); } } public Boolean RefreshProgress(AKill includeKill) { try { var killMeaningful = true; if (ID <= 0 || Rule == null || Rule.ID <= 0 || Rule.Definition == null || Rule.Definition.ID <= 0) { _plugin.Log.Error("Entry was invalid when refreshing progress."); return false; } var details = Rule.Definition.GetDetails(); if (!details.Any()) { _plugin.Log.Error("Cannot refresh progress, no damage types added to definition " + Rule.Definition.ID + "."); return false; } // Build the necessary buckets // Damage buckets var damageDetails = details.Where(detail => detail.Type == CDefinition.CDefinitionDetail.DetailType.Damage && detail.Damage != CDefinition.CDefinitionDetail.DetailDamage.None && detail.KillCount > 0).ToList(); var damageBuckets = new List(); foreach (var detail in damageDetails) { var damageBucket = new DamageBucket(_plugin) { Damage = detail.Damage, WeaponCount = detail.WeaponCount, MaxKillsPerWeapon = detail.KillCount }; var weaponCodes = new List(); foreach (var damageType in detail.GetDamageTypes()) { weaponCodes.AddRange(_plugin.WeaponDictionary.GetWeaponCodesOfDamageType(damageType)); } foreach (var weaponCode in weaponCodes) { damageBucket.Weapons[weaponCode] = new WeaponBucket(_plugin) { WeaponCode = weaponCode, MaxKills = detail.KillCount }; } if (!damageBuckets.Contains(damageBucket)) { damageBuckets.Add(damageBucket); } } // Weapon buckets var weaponDetails = details.Where(detail => detail.Type == CDefinition.CDefinitionDetail.DetailType.Weapon && !String.IsNullOrEmpty(detail.Weapon) && detail.KillCount > 0).ToList(); var weaponBuckets = new List(); foreach (var detail in weaponDetails) { var weaponBucket = new WeaponBucket(_plugin) { WeaponCode = detail.Weapon, MaxKills = detail.KillCount }; if (!weaponBuckets.Contains(weaponBucket)) { weaponBuckets.Add(weaponBucket); } } var deathBucket = new List(); var detailKills = GetDetails().Select(dDetail => dDetail.Kill).ToList(); if (includeKill != null) { detailKills.Add(includeKill); } foreach (var kill in detailKills.OrderBy(dKill => dKill.TimeStamp)) { if (kill.weaponCode == "Death" && kill.weaponDamage == DamageTypes.None) { deathBucket.Add(kill); continue; } // See if it matches a specific weapon requirement if (weaponBuckets.Any()) { var matchingWeaponBucket = weaponBuckets.FirstOrDefault(bucket => bucket.WeaponCode == kill.weaponCode); if (matchingWeaponBucket != null) { // It does. See if we can assign the kill to the damage type. if (matchingWeaponBucket.AddKill(kill)) { // Kill added, get out continue; } } } // Couldn't assign the kill to a specific weapon requirement // Check if the current rule has a need for this kill's damage type // A weapon can thankfully only belong to one damage type DamageBucket matchingDamageBucket = null; foreach (var bucket in damageBuckets) { if (bucket.Weapons.ContainsKey(kill.weaponCode)) { matchingDamageBucket = bucket; break; } } if (matchingDamageBucket != null) { // It does. See if we can assign the kill to the damage type. if (matchingDamageBucket.Weapons[kill.weaponCode].AddKill(kill)) { // Kill added, get out continue; } } // Unable to assign the kill. Either we don't have a slot for it, or all the slots for it are full.. if (includeKill != null && includeKill == kill) { // We are testing a kill and it couldn't be added anywhere killMeaningful = false; } } Progress = new EntryProgress(_plugin, this, damageBuckets, weaponBuckets, deathBucket); if (includeKill != null) { return killMeaningful; } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting challenge rule completion status.", e)); } return false; } public override string ToString() { if (Player == null) { return "PlayerInvalid"; } if (Rule == null) { return "RuleInvalid"; } if (Progress == null) { RefreshProgress(null); } return Rule.Name + " - " + Player.GetVerboseName() + " - " + Math.Round(Progress.CompletionPercentage) + "% - " + Progress.TotalCompletedKills + " Kills - " + Progress.TotalRequiredKills + " Required"; } public class EntryProgress { private AdKats _plugin; private CEntry Entry; private List DamageBuckets; private List WeaponBuckets; public List Deaths; public Int32 TotalRequiredKills { get; private set; } public Int32 TotalCompletedKills { get; private set; } public Double CompletionPercentage { get; private set; } public EntryProgress(AdKats plugin, CEntry entry, List damageBuckets, List weaponBuckets, List deaths) { _plugin = plugin; try { Entry = entry; DamageBuckets = damageBuckets; if (DamageBuckets == null) { DamageBuckets = new List(); } WeaponBuckets = weaponBuckets; if (WeaponBuckets == null) { WeaponBuckets = new List(); } Deaths = deaths; if (Deaths == null) { Deaths = new List(); } // Total for all required kills TotalRequiredKills = DamageBuckets.Sum(bucket => bucket.WeaponCount * bucket.MaxKillsPerWeapon) + WeaponBuckets.Sum(bucket => bucket.MaxKills); // Total for all completed kills TotalCompletedKills = damageBuckets.Sum(bucket => bucket.GetWeapons().Sum(weapon => weapon.Kills.Count())) + weaponBuckets.Sum(weapon => weapon.Kills.Count()); // Total completion percentage CompletionPercentage = Math.Max(Math.Min(100 * (Double)TotalCompletedKills / (Double)TotalRequiredKills, 100), 0); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error creating entry status.", e)); } } public void SendStatusForKill(AKill kill) { if (kill.weaponDamage == DamageTypes.None) { _plugin.Log.Error("Damage type for current kill couldn't be found, or was None."); } try { var requiredKills = 0; var completedKills = 0; WeaponBucket weaponBucket = WeaponBuckets.FirstOrDefault(bucket => bucket.WeaponCode == kill.weaponCode); if (weaponBucket != null) { requiredKills += weaponBucket.MaxKills; completedKills += weaponBucket.Kills.Count(); } WeaponBucket damageWeaponBucket = null; foreach (var damageBucket in DamageBuckets) { if (damageBucket.Weapons.ContainsKey(kill.weaponCode)) { damageWeaponBucket = damageBucket.Weapons[kill.weaponCode]; requiredKills += damageWeaponBucket.MaxKills; completedKills += damageWeaponBucket.Kills.Count(); break; } } var weaponCompletionPercentage = Math.Max(Math.Min(100 * (Double)completedKills / (Double)requiredKills, 100), 0); String weaponName = _plugin.WeaponDictionary.GetShortWeaponNameByCode(kill.weaponCode); String completion = ""; var respond = true; var completeFlag = false; if (weaponCompletionPercentage >= 99.999) { completion = "COMPLETED!"; if (CompletionPercentage < 99.999) { completion += " Try another weapon!"; completeFlag = true; } if (Entry.LastCompletedWeapon == kill.weaponCode) { respond = false; } Entry.LastCompletedWeapon = kill.weaponCode; } if (!respond) { return; } var commandText = _plugin.GetChatCommandByKey("self_challenge"); var completionSayMessage = commandText + " " + weaponName + " [" + completedKills + "/" + requiredKills + "][" + Math.Round(CompletionPercentage) + "%] " + completion; if (!String.IsNullOrEmpty(completion)) { // We are complete, mark the message in pink completionSayMessage = _plugin.Log.CPink(completionSayMessage); kill.killer.Say(completionSayMessage); } else { _plugin.PlayerSayMessage(kill.killer.player_name, completionSayMessage, false, 1); } if (completeFlag) { _plugin.PlayerYellMessage(kill.killer.player_name, completion, false, 1); if (_plugin.GetMatchingVerboseASPlayersOfGroup("challenge_autokill", kill.killer).Any()) { _plugin.Threading.Wait(250); _plugin.ExecuteCommand("procon.protected.send", "admin.killPlayer", kill.killer.player_name); kill.killer.Say(_plugin.Log.CPink("Killed automatically. To disable type " + commandText + " autokill")); } else { if (!Entry.AutoKillTold) { kill.killer.Say("Manual admin kill: " + commandText + " k | Auto admin kill: " + commandText + " autokill"); Entry.AutoKillTold = true; } Entry.kAllowed = true; } } return; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error getting status for kill.", e)); } } public override string ToString() { try { var status = ""; if (Entry.Rule == null) { return "ERROR4"; } if (!DamageBuckets.Any() && !WeaponBuckets.Any()) { status += "No damage or weapons defined."; return status; } foreach (var weapon in WeaponBuckets) { status += weapon.ToString() + Environment.NewLine; } foreach (var damage in DamageBuckets) { status += damage.ToString() + Environment.NewLine; } return status; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error getting entry status string.", e)); } return "ERROR5"; } } public class DamageBucket { private AdKats _plugin; public CDefinition.CDefinitionDetail.DetailDamage Damage; public Int32 WeaponCount; public Int32 MaxKillsPerWeapon; public Dictionary Weapons; public DamageBucket(AdKats plugin) { _plugin = plugin; Damage = CDefinition.CDefinitionDetail.DetailDamage.None; Weapons = new Dictionary(); } public List GetWeapons() { var buckets = new List(); try { // Add buckets until the weapon count is reached or we run out of buckets foreach (var bucket in Weapons.Values.Where(dBucket => dBucket.Kills.Count() > 0) .OrderByDescending(dBucket => dBucket.Kills.Count())) { if (buckets.Count() >= WeaponCount) { break; } buckets.Add(bucket); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting damage bucket weapons.", e)); } return buckets; } public override string ToString() { try { var requiredKills = WeaponCount * MaxKillsPerWeapon; var weaponBuckets = GetWeapons(); var completedKills = weaponBuckets.Sum(weapon => weapon.Kills.Count()); var completionPercentage = Math.Max(Math.Min(100 * (Double)completedKills / (Double)requiredKills, 100), 0); var status = "Type " + Damage.ToString().Replace("_", " ") + " [" + completedKills + "/" + requiredKills + "][" + Math.Round(completionPercentage) + "%]:"; if (weaponBuckets.Any()) { foreach (var weaponString in weaponBuckets.Select(bucket => bucket.ToString())) { status += Environment.NewLine + "-- " + weaponString; } } else { status += Environment.NewLine + "No weapons used yet."; } return status; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting damage bucket string.", e)); } return String.Empty; } } public class WeaponBucket { private AdKats _plugin; public Boolean Completed; public String WeaponCode; public List Kills; public Int32 MaxKills; public WeaponBucket(AdKats plugin) { _plugin = plugin; Kills = new List(); } public Boolean AddKill(AKill kill) { try { if (Kills.Count() < MaxKills) { Kills.Add(kill); return true; } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while adding kill to weapon bucket.", e)); } return false; } public override string ToString() { try { var completedKills = Kills.Count(); var completionPercentage = Math.Max(Math.Min(100 * (Double)completedKills / (Double)MaxKills, 100), 0); var weaponName = _plugin.WeaponDictionary.GetShortWeaponNameByCode(WeaponCode); return weaponName + " [" + completedKills + "/" + MaxKills + "][" + Math.Round(completionPercentage) + "%]"; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting weapon bucket string.", e)); } return String.Empty; } } public class CEntryDetail { private AdKats _plugin; public Boolean Phantom; // None of these should be changed after creation public CEntry Entry { get; private set; } public Int64 DetailID { get; private set; } public AKill Kill { get; private set; } public CEntryDetail(AdKats plugin, CEntry entry, Int64 detailID, AKill kill, Boolean phantom) { _plugin = plugin; Entry = entry; DetailID = detailID; Kill = kill; Phantom = phantom; } // Done public void DBPush(MySqlConnection con) { try { var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" INSERT INTO `adkats_challenge_entry_detail` ( `EntryID`, `DetailID`, `VictimID`, `Weapon`, `RoundID`, `DetailTime` ) VALUES ( @EntryID, @DetailID, @VictimID, @Weapon, @RoundID, @DetailTime )"; command.Parameters.AddWithValue("@EntryID", Entry.ID); command.Parameters.AddWithValue("@DetailID", DetailID); command.Parameters.AddWithValue("@VictimID", Kill.victim.player_id); command.Parameters.AddWithValue("@Weapon", Kill.weaponCode); command.Parameters.AddWithValue("@RoundID", Kill.RoundID); command.Parameters.AddWithValue("@DetailTime", Kill.UTCTimeStamp); if (_plugin.SafeExecuteNonQuery(command) > 0) { // This record is no longer phantom Phantom = false; } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBPush for CEntryDetail.", e)); } } public void DBRead(MySqlConnection con) { try { if (Entry == null || Entry.ID <= 0 || Entry.Player == null || DetailID <= 0) { _plugin.Log.Error("Challenge entry detail was invalid when reading."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" SELECT `VictimID`, `Weapon`, `RoundID`, `DetailTime` FROM `adkats_challenge_entry_detail` WHERE `EntryID` = @EntryID AND `DetailID` = @DetailID"; command.Parameters.AddWithValue("@EntryID", Entry.ID); command.Parameters.AddWithValue("@DetailID", DetailID); using (MySqlDataReader reader = _plugin.SafeExecuteReader(command)) { if (reader.Read()) { var victimID = reader.GetInt32("VictimID"); var detailVictim = _plugin.FetchPlayer(false, false, false, null, victimID, null, null, null, null); if (detailVictim == null) { _plugin.Log.Error("Victim " + victimID + " was invalid when loading entry detail " + Entry.ID + ":" + DetailID + ", unable to load."); return; } var detailWeaponCode = reader.GetString("Weapon"); var detailWeaponDamage = _plugin.WeaponDictionary.GetDamageTypeByWeaponCode(detailWeaponCode); // Use damage validation to make sure the weapon is real if (detailWeaponDamage == DamageTypes.None && detailWeaponCode != "Death") { _plugin.Log.Error("Weapon " + detailWeaponCode + " was invalid when loading entry detail " + Entry.ID + ":" + DetailID + ", unable to load."); return; } var detailRoundID = reader.GetInt32("RoundID"); if (detailRoundID <= 0) { _plugin.Log.Error("Round ID " + detailRoundID + " was invalid when loading entry detail " + Entry.ID + ":" + DetailID + ", unable to load."); return; } var detailTime = reader.GetDateTime("DetailTime"); // Build the kill object var kill = new AKill(_plugin) { killer = Entry.Player, weaponCode = detailWeaponCode, weaponDamage = detailWeaponDamage, victim = detailVictim, RoundID = detailRoundID, UTCTimeStamp = detailTime, TimeStamp = detailTime.ToLocalTime() }; } else { _plugin.Log.Error("Unable to find matching CEntryDetail " + Entry.ID + ":" + DetailID + "."); } } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBRead for CEntryDetail.", e)); } } } } public class CReward { public enum RewardType { None, ReservedSlot, SpectatorSlot, BalanceWhitelist, PingWhitelist, TeamKillTrackerWhitelist, CommandLock } public static String RewardTypeEnumString = "enum.ChallengeRewardType(None|ReservedSlot|SpectatorSlot|BalanceWhitelist|PingWhitelist|TeamKillTrackerWhitelist|CommandLock)"; private AdKats _plugin; public Boolean Phantom; private AChallengeManager Manager; public Int64 ID; public Int64 ServerID; public Int32 Tier; public RewardType Reward = RewardType.None; public Boolean Enabled; public Int32 DurationMinutes; public DateTime CreateTime; public DateTime ModifyTime; public String getDescriptionString(APlayer player) { if (Reward == RewardType.None) { return ""; } var rewardString = "+" + getDurationString(); switch (Reward) { case CReward.RewardType.ReservedSlot: rewardString += " reserved slot"; break; case CReward.RewardType.SpectatorSlot: rewardString += " spectator slot"; break; case CReward.RewardType.BalanceWhitelist: rewardString += " autobalance whitelist"; break; case CReward.RewardType.PingWhitelist: rewardString += " ping whitelist"; break; case CReward.RewardType.TeamKillTrackerWhitelist: rewardString += " teamkill whitelist"; break; case CReward.RewardType.CommandLock: var lockString = "command lock"; if (_plugin._UseExperimentalTools) { lockString = "rule breaking allowed"; } rewardString += " " + lockString; if (player != null) { var lockCommand = _plugin.GetCommandByKey("player_lock"); var recentLock = _plugin.FetchRecentRecords(player.player_id, lockCommand.command_id, 1000, 1, true, false).FirstOrDefault(); if (recentLock != null && recentLock.source_name == "ChallengeManager") { var durationSinceLast = _plugin.NowDuration(recentLock.record_time); if (durationSinceLast.TotalHours < Manager.CommandLockTimeoutHours) { var durationTillActive = _plugin.NowDuration(recentLock.record_time.AddHours(Manager.CommandLockTimeoutHours)); rewardString += " (" + _plugin.FormatTimeString(durationTillActive, 2) + " timeout)"; } } } break; } return rewardString; } public String getDurationString() { return _plugin.FormatTimeString(TimeSpan.FromMinutes(DurationMinutes), 2); } public CReward(AdKats plugin, AChallengeManager manager, Boolean phantom) { _plugin = plugin; Manager = manager; Phantom = phantom; CreateTime = _plugin.UtcNow(); ModifyTime = _plugin.UtcNow(); Tier = 1; DurationMinutes = 1440; Reward = RewardType.None; } public void DBPush(MySqlConnection con) { try { if (con == null) { con = _plugin.GetDatabaseConnection(); } if (Phantom) { DBCreate(con); } else { DBUpdate(con); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBPush for CReward.", e)); } } private void DBCreate(MySqlConnection con) { try { var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" INSERT INTO `adkats_challenge_reward` ( `ServerID`, `Tier`, `Reward`, `Enabled`, `DurationMinutes`, `CreateTime`, `ModifyTime` ) VALUES ( @ServerID, @Tier, @Reward, @Enabled, @DurationMinutes, @CreateTime, @ModifyTime )"; if (ServerID <= 0) { // This rule doesn't have an associated server ID, and we're creating it. // Assign this server's ID ServerID = _plugin._serverInfo.ServerID; } command.Parameters.AddWithValue("@ServerID", ServerID); command.Parameters.AddWithValue("@Tier", Tier); command.Parameters.AddWithValue("@Reward", Reward.ToString()); command.Parameters.AddWithValue("@Enabled", Enabled); command.Parameters.AddWithValue("@DurationMinutes", DurationMinutes); command.Parameters.AddWithValue("@CreateTime", CreateTime); command.Parameters.AddWithValue("@ModifyTime", ModifyTime); if (_plugin.SafeExecuteNonQuery(command) > 0) { ID = command.LastInsertedId; // This record is no longer phantom Phantom = false; } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBCreate for CReward.", e)); } } private void DBUpdate(MySqlConnection con) { try { if (ID <= 0 || Phantom) { _plugin.Log.Error("CReward was invalid when updating."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" UPDATE `adkats_challenge_reward` SET `ServerID` = @ServerID, `Tier` = @Tier, `Reward` = @Reward, `Enabled` = @Enabled, `DurationMinutes` = @DurationMinutes, `ModifyTime` = @ModifyTime WHERE `ID` = @ID"; command.Parameters.AddWithValue("@ID", ID); command.Parameters.AddWithValue("@ServerID", ServerID); command.Parameters.AddWithValue("@Tier", Tier); command.Parameters.AddWithValue("@Reward", Reward.ToString()); command.Parameters.AddWithValue("@Enabled", Enabled); command.Parameters.AddWithValue("@DurationMinutes", DurationMinutes); command.Parameters.AddWithValue("@ModifyTime", ModifyTime); if (_plugin.SafeExecuteNonQuery(command) <= 0) { _plugin.Log.Error("Failed to update CReward " + this.ToString() + " in database."); } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBUpdate for CReward.", e)); } } public void DBRead(MySqlConnection con) { try { if (ID <= 0 || Phantom) { _plugin.Log.Error("CReward was invalid when reading."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" SELECT `ServerID`, `Tier`, `Reward`, `Enabled`, `DurationMinutes`, `CreateTime`, `ModifyTime` FROM `adkats_challenge_reward` WHERE `ID` = @ID"; command.Parameters.AddWithValue("@ID", ID); var push = false; var delete = false; using (MySqlDataReader reader = _plugin.SafeExecuteReader(command)) { if (reader.Read()) { ServerID = reader.GetInt64("ServerID"); if (ServerID != _plugin._serverInfo.ServerID) { _plugin.Log.Error("CReward " + this.ToString() + " was loaded, but belongs to another server."); } Tier = reader.GetInt32("Tier"); Reward = (RewardType)Enum.Parse(typeof(RewardType), reader.GetString("Reward")); Enabled = reader.GetBoolean("Enabled"); DurationMinutes = reader.GetInt32("DurationMinutes"); CreateTime = reader.GetDateTime("CreateTime"); ModifyTime = reader.GetDateTime("ModifyTime"); } else { _plugin.Log.Error("Unable to find matching CReward for ID " + ID + "."); delete = true; } } if (delete) { DBDelete(localConnection); } else if (push) { DBPush(localConnection); } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBRead for CReward.", e)); } } public void DBDelete(MySqlConnection con) { try { if (ServerID <= 0 || Tier < 1 || Tier > 10 || Phantom) { _plugin.Log.Error("CReward was invalid when deleting."); return; } var localConnection = con; if (localConnection == null) { localConnection = _plugin.GetDatabaseConnection(); } try { using (MySqlCommand command = localConnection.CreateCommand()) { command.CommandText = @" DELETE FROM `adkats_challenge_reward` WHERE `ID` = @ID"; command.Parameters.AddWithValue("@ID", ID); if (_plugin.SafeExecuteNonQuery(command) > 0) { // SUCCESS _plugin.Log.Info("Deleted CReward " + this.ToString() + "."); Manager.DeleteReward(ID); } } } finally { if (con == null && localConnection != null) { localConnection.Dispose(); } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error performing DBDelete for CReward.", e)); } } public void SetEnabledByString(String enabled) { try { if (String.IsNullOrEmpty(enabled)) { _plugin.Log.Error("Enabled was empty when setting by string."); return; } var newEnabled = Boolean.Parse(enabled); if (newEnabled != Enabled) { Enabled = newEnabled; ModifyTime = _plugin.UtcNow(); DBPush(null); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error updating enabled state by string for CReward.", e)); } } public void SetTierByString(String tier) { try { if (String.IsNullOrEmpty(tier)) { _plugin.Log.Error("Rule tier was empty when setting by string."); return; } var newTier = Int32.Parse(tier); if (newTier < 1) { _plugin.Log.Error("Rule tier cannot be less than 1."); newTier = 1; } if (newTier > 10) { _plugin.Log.Error("Rule tier cannot be greter than 10."); newTier = 10; } if (newTier == Tier) { _plugin.Log.Info("Old tier and new tier were the same when setting by string."); return; } Tier = newTier; ModifyTime = _plugin.UtcNow(); DBPush(null); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error updating tier by string for CReward.", e)); } } public void SetRewardTypeByString(String rewardType) { try { if (String.IsNullOrEmpty(rewardType)) { _plugin.Log.Error("Reward type was empty when setting by string."); return; } RewardType newReward = RewardType.None; try { newReward = (RewardType)Enum.Parse(typeof(RewardType), rewardType); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while parsing reward type by string.", e)); } if (Reward == newReward) { _plugin.Log.Error("Old reward type and new reward type were the same when setting by string."); return; } if (Manager.GetRewards().Any(reward => reward.Reward == newReward && reward.Tier == Tier)) { _plugin.Log.Error("Tier " + Tier + " already has a " + newReward.ToString() + " reward."); return; } Reward = newReward; ModifyTime = _plugin.UtcNow(); DBPush(null); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error updating reward type by string for CReward.", e)); } } public void SetDurationMinutesByString(String durationMinutes) { try { if (String.IsNullOrEmpty(durationMinutes)) { _plugin.Log.Error("Duration minutes was empty when setting by string."); return; } var newDurationMinutes = Int32.Parse(durationMinutes); if (newDurationMinutes < 5) { _plugin.Log.Error("Duration of reward cannot be less than 5 minutes."); newDurationMinutes = 5; } if (newDurationMinutes != DurationMinutes) { DurationMinutes = newDurationMinutes; ModifyTime = _plugin.UtcNow(); DBPush(null); } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error updating minute duration by string for CRule.", e)); } } public override string ToString() { return ID + " (" + ServerID + "/" + Tier + "/" + Reward.ToString() + ")"; } } } public class AEventOption { public enum MapCode { UNKNOWN, RESET, MET, LOC }; public static readonly Dictionary MapNames = new Dictionary { {MapCode.MET, "Operation Metro"}, {MapCode.LOC, "Operation Locker"} }; public enum ModeCode { UNKNOWN, RESET, T100, T200, T300, T400, R200, R300, R400, C500, C1000, C2000, F9, F6, F3, D500, HD500, D750, D1000 }; public static readonly Dictionary ModeNames = new Dictionary { {ModeCode.T100, "TDM 100"}, {ModeCode.T200, "TDM 200"}, {ModeCode.T300, "TDM 300"}, {ModeCode.T400, "TDM 400"}, {ModeCode.R200, "Rush 200"}, {ModeCode.R300, "Rush 300"}, {ModeCode.R400, "Rush 400"}, {ModeCode.C500, "Conquest 500"}, {ModeCode.C1000, "Conquest 1000"}, {ModeCode.C2000, "Conquest 2000"}, {ModeCode.F9, "CTF 9"}, {ModeCode.F6, "CTF 6"}, {ModeCode.F3, "CTF 3"}, {ModeCode.D500, "Domination 500"}, {ModeCode.HD500, "HC Domination 500"}, {ModeCode.D750, "Domination 750"}, {ModeCode.D1000, "Domination 1000"} }; public enum RuleCode { UNKNOWN, ENDEVENT, AW, KO, NE, GO, AO, ARO, LMGO, PO, SO, EO, MLO, DO, BKO, BSO, RTO, HO, NH, CAI, TR }; public static readonly Dictionary RuleNames = new Dictionary { {RuleCode.AW, "All Weapons"}, {RuleCode.KO, "Knives Only"}, {RuleCode.NE, "No Explosives"}, {RuleCode.GO, "Grenades Only"}, {RuleCode.AO, "Automatics Only"}, {RuleCode.ARO, "Assault Rifles Only"}, {RuleCode.LMGO, "LMGs Only"}, {RuleCode.PO, "Pistols Only"}, {RuleCode.SO, "Shotguns Only"}, {RuleCode.EO, "Explosives Only"}, {RuleCode.MLO, "Mares Leg Only"}, {RuleCode.DO, "Defibs Only"}, {RuleCode.BKO, "Bow And Knives Only"}, {RuleCode.BSO, "Bolt Sniper Only"}, {RuleCode.RTO, "Repair Tool Only"}, {RuleCode.HO, "Headshots Only"}, {RuleCode.NH, "No Headshots"}, {RuleCode.CAI, "Cowboys and Indians"}, {RuleCode.TR, "Troll Rules"}, {RuleCode.ENDEVENT, "End The Event"} }; public MapCode Map; public ModeCode Mode; public RuleCode Rule; public static AEventOption Default() { return new AEventOption() { Map = MapNames.FirstOrDefault().Key, Mode = ModeNames.FirstOrDefault().Key, Rule = RuleNames.FirstOrDefault().Key }; } public String getDisplay() { return MapNames[Map] + "/" + ModeNames[Mode] + "/" + RuleNames[Rule]; } public String getCode() { return Map + "-" + Mode + "-" + Rule; } public static RuleCode RuleFromDisplay(String ruleDisplay) { if (!RuleNames.Any(rule => rule.Value == ruleDisplay)) { return RuleNames.FirstOrDefault().Key; } return RuleNames.FirstOrDefault(rule => rule.Value == ruleDisplay).Key; } public static ModeCode ModeFromDisplay(String modeDisplay) { if (!ModeNames.Any(mode => mode.Value == modeDisplay)) { return ModeNames.FirstOrDefault().Key; } return ModeNames.FirstOrDefault(mode => mode.Value == modeDisplay).Key; } public static MapCode MapFromDisplay(String mapDisplay) { if (!MapNames.Any(map => map.Value == mapDisplay)) { return MapNames.FirstOrDefault().Key; } return MapNames.FirstOrDefault(map => map.Value == mapDisplay).Key; } public static AEventOption FromDisplay(String display) { if (!display.Contains('/')) { return Default(); } var split = display.Split('/'); if (split.Length != 3 || !MapNames.Any(map => map.Value == split[0]) || !ModeNames.Any(mode => mode.Value == split[1]) || !RuleNames.Any(rule => rule.Value == split[2])) { return Default(); } return new AEventOption() { Map = MapNames.FirstOrDefault(map => map.Value == split[0]).Key, Mode = ModeNames.FirstOrDefault(mode => mode.Value == split[1]).Key, Rule = RuleNames.FirstOrDefault(rule => rule.Value == split[2]).Key }; } public static AEventOption FromCode(String code) { if (!code.Contains('-')) { return Default(); } var split = code.Split('-'); if (split.Length != 3) { return Default(); } var parsedMap = (MapCode)Enum.Parse(typeof(MapCode), split[0]); var parsedMode = (ModeCode)Enum.Parse(typeof(ModeCode), split[1]); var parsedRule = (RuleCode)Enum.Parse(typeof(RuleCode), split[2]); if (!MapNames.ContainsKey(parsedMap) || !ModeNames.ContainsKey(parsedMode) || !RuleNames.ContainsKey(parsedRule)) { return Default(); } return new AEventOption() { Map = parsedMap, Mode = parsedMode, Rule = parsedRule }; } } public class APoll { private AdKats Plugin; public String ID; public String Title; public Dictionary> Options; public Boolean Completed; public Boolean Canceled; public DateTime StartTime; private Boolean _FirstPrint; public DateTime PrintTime; public Dictionary Votes; public APoll(AdKats plugin) { Plugin = plugin; StartTime = Plugin.UtcNow(); PrintTime = Plugin.UtcNow().AddMinutes(-10); Options = new Dictionary>(); Votes = new Dictionary(); } public void AddOption(String option, Boolean singular) { Int32 optionNumber = 1; while (Options.ContainsKey(optionNumber)) { optionNumber++; } Options[optionNumber] = new KeyValuePair(option, singular); } public Boolean AddVote(APlayer voter, Int32 vote) { if (!Options.ContainsKey(vote)) { voter.Say("Vote " + vote + " is not valid. Options are " + Options.Keys.Min() + "-" + Options.Keys.Max() + "."); return false; } Int32 currentVote = -1; if (Votes.ContainsKey(voter)) { currentVote = Votes[voter]; } if (currentVote == vote) { voter.Say("You already voted for '" + Options[vote].Key + "'"); return false; } if (currentVote > 0) { voter.Say("You changed your vote from '" + Options[currentVote].Key + "' to '" + Options[vote].Key + "'"); } else { voter.Say("Vote added for '" + Options[vote].Key + "'."); } Votes[voter] = vote; return true; } public void PrintPoll(Boolean printWinning) { List optionStrings = new List(); foreach (var option in Options) { optionStrings.Add("!" + option.Key + " " + option.Value.Key + " [" + Votes.Count(vote => vote.Value == option.Key) + "]"); } List optionLines = new List(); String currentLine = String.Empty; foreach (var option in optionStrings) { if (currentLine == String.Empty) { currentLine = option; } else { currentLine += " | " + option; optionLines.Add(currentLine); currentLine = String.Empty; } } if (currentLine != String.Empty) { optionLines.Add(currentLine + " |"); } PrintTime = Plugin.UtcNow(); if (!_FirstPrint) { Plugin.AdminTellMessage(Title); _FirstPrint = true; } else { Plugin.AdminSayMessage(Title); if (printWinning) { GetWinningOption("leading", true); } } foreach (var line in optionLines) { Plugin.AdminSayMessage(line); } } public String GetWinningOption(String printType, Boolean yell) { if (!Votes.Any()) { // If nobody has voted yet, use the first option return Options.Values.FirstOrDefault().Key; } String winnerString = null; List exclude = new List(); do { var votes = Votes.Values.ToList(); var results = votes.Where(vote => !exclude.Contains(vote)) .GroupBy(vote => vote) .Select(group => new { Option = group.Key, Count = group.Count() }) .OrderByDescending(group => group.Count); var winner = results.First(); var winnerOption = Options[winner.Option]; winnerString = winnerOption.Key; if (winnerOption.Value) { // This result can only win if it's greater than all other options combined var sumOtherVotes = results.Where(result => result.Option != winner.Option).Sum(result => result.Count); if (winner.Count < sumOtherVotes) { winnerString = null; } } if (printType != null && winnerString != null) { if (printType == "won") { String wonMessage = "'" + winnerString + "' won with " + winner.Count + " votes!"; if (yell) { Plugin.AdminYellMessage(wonMessage, true, 0); } else { Plugin.AdminSayMessage(wonMessage); } } else if (printType == "leading") { String leadingMessage = "'" + winnerString + "' is winning! (" + winner.Count + " votes)"; if (yell) { Plugin.AdminYellMessage(leadingMessage, true, 2); } else { Plugin.AdminSayMessage(leadingMessage); } } } } while (winnerString == null); return winnerString; } public void Reset() { Options.Clear(); Votes.Clear(); Completed = false; Canceled = false; StartTime = Plugin.UtcNow(); _FirstPrint = false; PrintTime = Plugin.UtcNow().AddMinutes(-10); } } public class AKill { public AdKats _plugin; public String weaponCode; public DamageTypes weaponDamage = DamageTypes.None; public APlayer killer; public APlayer victim; public CPlayerInfo killerCPI; public CPlayerInfo victimCPI; public Boolean IsSuicide; public Boolean IsHeadshot; public Boolean IsTeamkill; public DateTime TimeStamp; public DateTime UTCTimeStamp; public Int64 RoundID; public AKill(AdKats plugin) { _plugin = plugin; } public override string ToString() { // Default values in case any are null; String killerString = killer != null ? killer.GetVerboseName() : "UnknownKiller"; String methodString = "UnknownMethod"; if (!String.IsNullOrEmpty(weaponCode)) { methodString = _plugin.WeaponDictionary.GetShortWeaponNameByCode(weaponCode); } String victimString = victim != null ? victim.GetVerboseName() : "UnknownVictim"; return killerString + " [" + methodString + "] " + victimString; } } public class AMove { public APlayer Player; public ASquad Squad; } public class ASquad { public static readonly Dictionary Names = new Dictionary() { {0, "None"}, {1, "Alpha"}, {2, "Bravo"}, {3, "Charlie"}, {4, "Delta"}, {5, "Echo"}, {6, "Foxtrot"}, {7, "Golf"}, {8, "Hotel"}, {9, "India"}, {10, "Juliet"}, {11, "Kilo"}, {12, "Lima"}, {13, "Mike"}, {14, "November"}, {15, "Oscar"}, {16, "Papa"} }; public AdKats Plugin; public Int32 TeamID; public Int32 SquadID; public List Players; public ASquad(AdKats plugin) { Plugin = plugin; Players = new List(); } public String GetName() { if (!Names.ContainsKey(SquadID)) { Plugin.Log.Error("Invalid squad ID " + SquadID + ", unable to get squad name."); return SquadID.ToString(); } return Names[SquadID]; } public Double GetPower() { return Players.Sum(aPlayer => aPlayer.GetPower(true)); } public override String ToString() { return TeamID + ":" + GetName() + ":" + Math.Round(GetPower()) + " / " + String.Join(" | ", Players.OrderBy(member => member.player_name).Select(member => member.player_name).ToArray()); } } public class APlayer { public CPunkbusterInfo PBPlayerInfo = null; public List LiveKills = null; public List TargetedRecords = null; public String player_clanTag = null; public CPlayerInfo fbpInfo = null; public Int32 BanEnforceCount = 0; public Int32 backup_kills = 0; public Int32 backup_deaths = 0; public Int32 backup_score = 0; public Int64 game_id = -1; public DateTime lastAction = DateTime.UtcNow; public DateTime lastKill = DateTime.UtcNow; public DateTime lastDeath = DateTime.UtcNow; public DateTime lastSpawn = DateTime.UtcNow; public DateTime lastSwitchMessage = DateTime.UtcNow; public DateTime LastUsage = DateTime.UtcNow; public Boolean player_aa = false; public Boolean player_aa_fetched = false; public Boolean player_aa_told = false; public String player_guid = null; public Int64 player_id = -1; public String player_ip { get; private set; } public String player_discord_id; public DateTime VoipJoinTime = DateTime.UtcNow - TimeSpan.FromMinutes(15); public DiscordManager.DiscordMember DiscordObject; public TeamSpeakClientViewer.TeamspeakClient TSClientObject; public String player_name = null; public String player_name_previous = null; public String player_battlecry = null; public Boolean player_online = true; public String player_pbguid = null; public ARole player_role = null; public String player_slot = null; public Double player_reputation = 0; public DateTime player_firstseen = DateTime.UtcNow; public DateTime JoinTime = DateTime.UtcNow; public AServer player_server = null; public TimeSpan player_serverplaytime = TimeSpan.FromSeconds(0); public Boolean player_spawnedOnce = false; public Boolean player_chatOnce = false; public Boolean player_spawnedRound = false; public PlayerType player_type = PlayerType.Player; public Boolean BLInfoStored = false; public String player_battlelog_personaID = null; public String player_battlelog_userID = null; private Boolean player_locked; private DateTime player_locked_start = DateTime.UtcNow; private TimeSpan player_locked_duration = TimeSpan.Zero; private String player_locked_source; public Int32 player_infractionPoints = Int32.MinValue; public ARecord LastPunishment = null; public ARecord LastForgive = null; public ATeam RequiredTeam = null; public Int32 RequiredSquad = -1; public readonly Queue> player_pings; public Boolean player_pings_full { get; private set; } public Double player_ping_avg { get; private set; } public Double player_ping { get; private set; } public DateTime player_ping_time { get; private set; } public Boolean player_ping_added { get; private set; } public Boolean player_ping_manual = false; public APlayer conversationPartner = null; public Int32 AllCapsMessages = 0; public List TeamMoves; public Int32 ActiveSession = 1; public Dictionary RoundStats; public Int64 BL_SPM; public Double BL_KDR; public Double BL_KPM; public ATopStats TopStats; public IPAPILocation location = null; public Boolean update_playerUpdated = true; public Boolean player_new = false; public AChallengeManager.CEntry ActiveChallenge = null; public Boolean loadout_valid = true; public Boolean loadout_spawnValid = true; public String loadout_items = "Loadout not fetched yet."; public String loadout_items_long = "Loadout not fetched yet."; public String loadout_deniedItems = "No denied items."; public String PrintThreadID = String.Empty; private readonly AdKats Plugin; public APlayer(AdKats plugin) { Plugin = plugin; RoundStats = new Dictionary(); TopStats = new ATopStats(); LiveKills = new List(); player_pings = new Queue>(); TargetedRecords = new List(); LastUsage = DateTime.UtcNow; TeamMoves = new List(); PrintThreadID = ""; ActiveSession = 1; } public void Say(String message) { Plugin.PlayerSayMessage(player_name, message); } public void Say(String message, Boolean displayProconChat, Int32 spamCount) { Plugin.PlayerSayMessage(player_name, message, displayProconChat, spamCount); } public void Yell(String message) { Plugin.PlayerYellMessage(player_name, message); } public void Yell(String message, Boolean displayProconChat, Int32 spamCount) { Plugin.PlayerYellMessage(player_name, message, displayProconChat, spamCount); } public void Tell(String message) { Plugin.PlayerTellMessage(player_name, message); } public void Tell(String message, Boolean displayProconChat, Int32 spamCount) { Plugin.PlayerTellMessage(player_name, message, displayProconChat, spamCount); } public String GetTeamKey() { String key = "Unknown"; if (fbpInfo != null) { try { key = Plugin._teamDictionary[fbpInfo.TeamID].TeamKey; } catch (Exception) { key = fbpInfo.TeamID.ToString(); } } return key; } private Double maxScore = 30000.0; private Double maxKills = 200.0; private Double maxKd = 4.0; public Double GetPower(Boolean includeMods) { return GetPower(true, includeMods, includeMods); } public Double GetPower(Boolean includeBase, Boolean includeActive, Boolean includeSaved) { // Base power is 7-32 Double basePower = min7(TopStats.RoundCount >= 3 && TopStats.TopCount > 0 ? Math.Pow(TopStats.TopRoundRatio + 1, 5) : 1.0); // Cap top power at 30 if the player is new if (TopStats.RoundCount < 20) { basePower = Math.Min(basePower, 30.0); } // If their base power is calculated low, use their battlelog stats instead var blPower = (BL_KDR * BL_SPM * BL_KPM) / 2500.0 * 20; if (basePower < 15 && blPower > 0) { // Don't allow the calculation to be less than their base power, or greater than 20 basePower = Math.Min(Math.Max(blPower, basePower), 20); } Double savedPower = TopStats.TempTopPower; if (fbpInfo == null) { if (!includeSaved) { return basePower; } return Math.Max(basePower, savedPower / 3.0); } // Active power is 7-ActiveInfluence Double killPower = min7(Math.Min(fbpInfo.Kills, maxKills) / maxKills * Plugin._TeamPowerActiveInfluence); Double kdPower = min7(Math.Min(fbpInfo.Kills / Math.Max(fbpInfo.Deaths, 1.0), maxKd) / maxKd * Plugin._TeamPowerActiveInfluence); Double scorePower = min7(Math.Min(fbpInfo.Score, maxScore) / maxScore * Plugin._TeamPowerActiveInfluence); Double activePower = (killPower + kdPower + scorePower) / 3.0; // Take whichever power level is greatest TopStats.TempTopPower = Math.Max(Math.Max(basePower, savedPower), activePower); var returnPower = min7(0); if (includeBase) { returnPower = Math.Max(returnPower, basePower); } if (includeActive) { returnPower = Math.Max(returnPower, activePower); } if (includeSaved) { returnPower = Math.Max(returnPower, savedPower / 3.0); } return returnPower; } private Double min7(Double val) { return Math.Max(val, 7.0); } public void SetIP(String ip) { this.player_ip = (String.IsNullOrEmpty(ip) ? (null) : (ip)); } public String GetVerboseName() { return ((String.IsNullOrEmpty(player_clanTag)) ? ("") : ("[" + player_clanTag + "]")) + player_name; } public TimeSpan GetIdleTime() { return Plugin.UtcNow() - lastAction; } public void ClearPingEntries() { player_pings.Clear(); player_pings_full = false; player_ping = 0; player_ping_avg = 0; } public void AddPingEntry(Double newPingValue) { //Get rounded time (floor) DateTime newPingTime = Plugin.UtcNow(); newPingTime = newPingTime.AddTicks(-(newPingTime.Ticks % TimeSpan.TicksPerSecond)); if (!player_ping_added) { player_ping_avg = newPingValue; player_ping = newPingValue; player_ping_time = newPingTime; player_pings.Enqueue(new KeyValuePair(newPingValue, newPingTime)); player_ping_added = true; return; } //Linear Interpolation DateTime oldPingTime = player_ping_time; Double oldPingValue = player_ping; Double interTimeOldSeconds = 0; Double interTimeNewSeconds = (newPingTime - oldPingTime).TotalSeconds; Double m = (newPingValue - oldPingValue) / (interTimeNewSeconds); Double b = oldPingValue; for (Int32 sec = (Int32)interTimeOldSeconds; sec < interTimeNewSeconds; sec++) { DateTime subPingTime = oldPingTime.AddSeconds(sec); Double subPingValue = (m * sec) + b; player_pings.Enqueue(new KeyValuePair(subPingValue, subPingTime)); } //Remove old values Boolean removed = false; do { removed = false; if (player_pings.Any() && (Plugin.UtcNow() - player_pings.Peek().Value).TotalSeconds > Plugin._pingMovingAverageDurationSeconds) { player_pings.Dequeue(); player_pings_full = true; removed = true; } } while (removed); //Set instance vars player_ping = newPingValue; player_ping_time = newPingTime; player_ping_avg = player_pings.Sum(pingEntry => pingEntry.Key) / player_pings.Count; } public Boolean IsLocked() { if (player_locked && player_locked_start + player_locked_duration < Plugin.UtcNow()) { //Unlock the player player_locked = false; } return player_locked; } public Boolean Lock(String locker, TimeSpan duration) { if (IsLocked()) { return false; } if (String.IsNullOrEmpty(locker)) { Plugin.Log.Error("Attempted to lock player with empty locker."); return false; } if (duration == null || duration == TimeSpan.Zero) { Plugin.Log.Error("Attempted to lock player with invalid duration."); return false; } player_locked = true; player_locked_duration = duration; player_locked_start = Plugin.UtcNow(); player_locked_source = locker; return true; } public void Unlock() { player_locked = false; } public TimeSpan GetLockRemaining() { if (IsLocked()) { return (player_locked_start + player_locked_duration) - Plugin.UtcNow(); } return TimeSpan.Zero; } public String GetLockSource() { if (IsLocked()) { return player_locked_source; } return null; } } public class AClient { public String ClientName; public String ClientMethod; public String SubscriptionGroup; public Boolean SubscriptionEnabled; public DateTime SubscriptionTime { get; private set; } private readonly AdKats Plugin; public AClient(AdKats plugin) { Plugin = plugin; } public void EnableSubscription() { SubscriptionEnabled = true; SubscriptionTime = Plugin.UtcNow(); } public void DisableSubscription() { SubscriptionEnabled = false; SubscriptionTime = Plugin.UtcNow(); } } public class AServer { public Int64 ServerID; public Int64 ServerGroup; public String ServerIP; public String ServerName; public String ServerType = "UNKNOWN"; public Int64 GameID = -1; public Boolean CommanderEnabled; public Boolean FairFightEnabled; public Boolean ForceReloadWholeMags; public Boolean HitIndicatorEnabled; public String GamePatchVersion = "UNKNOWN"; public Int32 MaxSpectators = -1; public CServerInfo InfoObject { get; private set; } private DateTime infoObjectTime = DateTime.UtcNow; private List MapList; private Int32 MapIndex; private Int32 NextMapIndex; private readonly AdKats Plugin; public AServer(AdKats plugin) { Plugin = plugin; } public void SetInfoObject(CServerInfo infoObject) { InfoObject = infoObject; ServerName = infoObject.ServerName; infoObjectTime = Plugin.UtcNow(); } public TimeSpan GetRoundElapsedTime() { if (InfoObject == null || Plugin._roundState != RoundState.Playing) { return TimeSpan.Zero; } return TimeSpan.FromSeconds(InfoObject.RoundTime) + (Plugin.UtcNow() - infoObjectTime); } public void SetMapList(List MapList) { if (this.MapList == null) { this.MapList = MapList; } else { lock (this.MapList) { this.MapList = MapList; } } } public List GetMapList() { List ret; if (this.MapList == null) { this.MapList = new List(); } lock (this.MapList) { ret = MapList; } return ret; } public void SetMapListIndicies(Int32 current, Int32 next) { this.MapIndex = current; this.NextMapIndex = next; } public MaplistEntry GetNextMap() { if (MapList == null) { return null; } return MapList.FirstOrDefault(entry => entry.Index == this.NextMapIndex); } public MaplistEntry GetMap() { if (MapList == null) { return null; } return MapList.FirstOrDefault(entry => entry.Index == this.MapIndex); } } public class ATopStats { public Boolean Fetched; public Int32 RoundCount; public Int32 TopCount; public Double TopRoundRatio; public Double TempTopPower; } public class APlayerStats { public Int64 RoundID; public Int64 Rank; public Int64 Skill; public Int64 Kills; public Int64 Headshots; public Int64 Deaths; public Int64 Shots; public Int64 Hits; public Int64 Score; public Double Accuracy; public Int64 Revives; public Int64 Heals; public Dictionary WeaponStats = null; public Dictionary VehicleStats = null; public CPlayerInfo LiveStats = null; public APlayerStats(Int64 roundID) { RoundID = roundID; } } public class ARecord { //Source of this record public enum Sources { Default, Automated, ExternalPlugin, InGame, Settings, ServerCommand, Database, HTTP } //Access method of this record public enum AccessMethod { HiddenInternal, HiddenExternal, HiddenGlobal, HiddenTeam, HiddenSquad, PublicExternal, PublicGlobal, PublicTeam, PublicSquad } //Command attributes for the record public ACommand command_action = null; public Int32 command_numeric = 0; public ACommand command_type = null; //All messages sent through this record via sendMessageToSource or other means public List debugMessages; //Settings for External Plugin commands public String external_callerIdentity = null; public String external_responseClass = null; public String external_responseMethod = null; public Boolean external_responseRequested; //Internal processing public Boolean isAliveChecked; public Boolean isLoadoutChecked; public Boolean targetLoadoutActed = false; public Boolean isContested; public Boolean isDebug; public Boolean isIRO; public Boolean record_action_executed; public AException record_exception = null; //record data public Int64 record_id = -1; public String record_message = null; public Sources record_source = Sources.Default; public DateTime record_time = DateTime.UtcNow; public DateTime record_time_update = DateTime.UtcNow; public Int64 server_id = -1; public String source_name = null; public APlayer source_player = null; public Int32 SourceSession = 0; public String target_name = null; public APlayer target_player = null; public Int32 TargetSession = 0; public DateTime record_creationTime; public AccessMethod record_access = AccessMethod.HiddenInternal; public Boolean record_held; public Boolean record_orchestrate; public List TargetNamesLocal; public List TargetPlayersLocal; public List TargetInnerRecords; //Default Constructor public ARecord() { debugMessages = new List(); TargetNamesLocal = new List(); TargetPlayersLocal = new List(); TargetInnerRecords = new List(); record_creationTime = DateTime.UtcNow; } public String GetSourceName() { String source = ""; if (source_player != null) { source = ((source_player.player_online) ? ("") : ("(OFFLINE)")) + source_player.GetVerboseName(); } else if (String.IsNullOrEmpty(source_name)) { source = "NOSOURCE"; } else { source = source_name; } return source; } public String GetTargetNames() { String targets = ""; if (TargetPlayersLocal.Any()) { foreach (APlayer aPlayer in TargetPlayersLocal) { targets += ((aPlayer.player_online) ? ("") : ("(OFFLINE)")) + aPlayer.GetVerboseName() + ", "; } } else if (target_player != null) { targets = ((target_player.player_online) ? ("") : ("(OFFLINE)")) + target_player.GetVerboseName(); } else { targets = ((String.IsNullOrEmpty(target_name)) ? ("NoNameTarget") : (target_name)); } return targets.Trim().TrimEnd(','); } } public class AStatistic { public enum StatisticType { map_benefit, map_detriment, player_win, player_loss, player_top, round_quality, battlelog_requestfreq, ping_over50, ping_over100, ping_over150 } public Int64 stat_id; public Int64 server_id; public Int64 round_id; public StatisticType stat_type; public String target_name; public APlayer target_player; public Double stat_value; public String stat_comment; public DateTime stat_time; } public class AChatMessage { public enum ChatSubset { Global, Team, Squad } public String Speaker; public String Message; public String OriginalMessage; public ChatSubset Subset; public Boolean Hidden; public Int32 SubsetTeamID; public Int32 SubsetSquadID; public DateTime Timestamp; } public class ARole { public Dictionary, ACommand>> ConditionalAllowedCommands = null; public Dictionary RoleAllowedCommands = null; public Dictionary RoleSetGroups = null; public CPrivileges RoleProconPrivileges = null; public Int64 role_id = -1; public String role_key = null; public String role_name = null; public Int64 role_powerLevel = 0; public ARole() { RoleAllowedCommands = new Dictionary(); RoleSetGroups = new Dictionary(); ConditionalAllowedCommands = new Dictionary, ACommand>>(); } } public class ASpecialGroup { public Int64 group_id; public String group_key; public String group_name; } public class ASpecialPlayer { private readonly AdKats Plugin; public Int64 specialplayer_id; public Int32? player_game = null; public ASpecialGroup player_group = null; public String player_identifier = null; public APlayer player_object = null; public Int32? player_server = null; public DateTime player_effective; public DateTime player_expiration; public String tempCreationType; public ASpecialPlayer(AdKats plugin) { Plugin = plugin; } public Boolean IsMatchingPlayer(APlayer aPlayer) { try { if (player_group != null && aPlayer != null && player_object != null && (player_object.player_id == aPlayer.player_id || player_identifier == aPlayer.player_name || player_identifier == aPlayer.player_guid || player_identifier == aPlayer.player_ip)) { return true; } } catch (Exception e) { Plugin.Log.HandleException(new AException("Error checking APlayer match with ASPlayer.", e)); } return false; } public Boolean IsMatchingPlayerOfGroup(APlayer aPlayer, String specialPlayerGroup) { try { if (player_group != null && player_group.group_key == specialPlayerGroup && aPlayer != null && player_object != null && (player_object.player_id == aPlayer.player_id || player_identifier == aPlayer.player_name || player_identifier == aPlayer.player_guid || player_identifier == aPlayer.player_ip)) { return true; } } catch (Exception e) { Plugin.Log.HandleException(new AException("Error checking APlayer/Group match with ASPlayer.", e)); } return false; } } public class ATeam { private readonly AdKats Plugin; private readonly Queue> TeamTicketCounts; public Boolean TeamTicketCountsFull { get; private set; } private Double TeamTicketDifferenceRate; public Int32 TeamTicketCount { get; private set; } public DateTime TeamTicketsTime { get; private set; } public Boolean TeamTicketsAdded { get; private set; } //Ticket Adjustments private Int32 TeamTicketAdjustment; private readonly Queue> TeamAdjustedTicketCounts; public Int32 TeamAdjustedTicketCount { get; private set; } public DateTime TeamAdjustedTicketsTime { get; private set; } public Boolean TeamAdjustedTicketsAdded { get; private set; } private Double TeamAdjustedTicketDifferenceRate; private readonly Queue> TeamAdjustedTicketDifferenceRates; public Double TeamAdjustedTicketAccellerationRate { get; private set; } //Score private readonly Queue> TeamTotalScores; public Boolean TeamTotalScoresFull { get; private set; } public Double TeamScoreDifferenceRate { get; private set; } public Double TeamTotalScore { get; private set; } public DateTime TeamTotalScoresTime { get; private set; } public Boolean TeamTotalScoresAdded { get; private set; } public ATeam(AdKats plugin, Int32 teamID, String teamKey, String teamName, String teamDesc) { Plugin = plugin; TeamID = teamID; TeamKey = teamKey; TeamName = teamName; TeamDesc = teamDesc; TeamTotalScores = new Queue>(); TeamTotalScoresFull = false; TeamTicketCounts = new Queue>(); TeamTicketCountsFull = false; TeamAdjustedTicketCounts = new Queue>(); TeamAdjustedTicketDifferenceRates = new Queue>(); } public Int32 TeamID { get; private set; } public String TeamKey { get; private set; } public String TeamName { get; private set; } public String TeamDesc { get; private set; } public String GetTeamIDKey() { return TeamID + "/" + TeamKey; } //Live Vars public Boolean Populated { get; private set; } public Int32 TeamPlayerCount { get; set; } public Double GetTicketDifferenceRate() { return TeamTicketDifferenceRate >= 0 ? TeamTicketDifferenceRate : TeamAdjustedTicketDifferenceRate; } public Double GetRawTicketDifferenceRate() { return TeamTicketDifferenceRate; } public void UpdatePlayerCount(Int32 playerCount) { Populated = true; TeamPlayerCount = playerCount; } public void IncrementTeamTicketAdjustment() { Interlocked.Increment(ref TeamTicketAdjustment); } public void UpdateTicketCount(Double newTicketCount) { try { UpdateAdjustedTicketCount(newTicketCount); //Get rounded time (floor) DateTime newTicketTime = Plugin.UtcNow(); newTicketTime = newTicketTime.AddTicks(-(newTicketTime.Ticks % TimeSpan.TicksPerSecond)); if (!TeamTicketsAdded) { TeamTicketDifferenceRate = 0; TeamTicketCount = (Int32)newTicketCount; TeamTicketsTime = newTicketTime; TeamTicketCounts.Enqueue(new KeyValuePair(newTicketCount, newTicketTime)); TeamTicketsAdded = true; return; } //Interpolation DateTime oldTicketTime = TeamTicketsTime; Double oldTicketValue = TeamTicketCount; Double interTimeOldSeconds = 0; Double interTimeNewSeconds = (newTicketTime - oldTicketTime).TotalSeconds; Double m = (newTicketCount - oldTicketValue) / (interTimeNewSeconds); Double b = oldTicketValue; for (Int32 sec = (Int32)interTimeOldSeconds; sec < interTimeNewSeconds; sec++) { DateTime subTicketTime = oldTicketTime.AddSeconds(sec); Double subTicketValue = (m * sec) + b; TeamTicketCounts.Enqueue(new KeyValuePair(subTicketValue, subTicketTime)); } //Remove old values (more than 90 seconds ago) Boolean removed = false; do { removed = false; if (TeamTicketCounts.Any() && (Plugin.UtcNow() - TeamTicketCounts.Peek().Value).TotalSeconds > 90) { TeamTicketCounts.Dequeue(); TeamTicketCountsFull = true; removed = true; } } while (removed); //Set instance vars TeamTicketCount = (Int32)newTicketCount; TeamTicketsTime = newTicketTime; List values = TeamTicketCounts.Select(pair => pair.Key).ToList(); List differences = new List(); for (int i = 0; i < values.Count - 1; i++) { differences.Add(values[i + 1] - values[i]); } differences.Sort(); //Convert to tickets/min TeamTicketDifferenceRate = (differences.Sum() / differences.Count) * 60; if (Double.IsNaN(TeamTicketDifferenceRate)) { TeamTicketDifferenceRate = 0; } } catch (Exception e) { Plugin.Log.HandleException(new AException("Error while updating team ticket count.", e)); } } private void UpdateAdjustedTicketCount(Double newRealTicketCount) { try { //Calculate adjusted ticket count Double newAdjustedTicketCount = newRealTicketCount + TeamTicketAdjustment; //Get rounded time (floor) DateTime newAdjustedTicketTime = Plugin.UtcNow(); newAdjustedTicketTime = newAdjustedTicketTime.AddTicks(-(newAdjustedTicketTime.Ticks % TimeSpan.TicksPerSecond)); if (!TeamAdjustedTicketsAdded) { TeamAdjustedTicketDifferenceRate = 0; TeamAdjustedTicketAccellerationRate = 0; TeamAdjustedTicketCount = (Int32)newAdjustedTicketCount; TeamAdjustedTicketsTime = newAdjustedTicketTime; TeamAdjustedTicketCounts.Enqueue(new KeyValuePair(newAdjustedTicketCount, newAdjustedTicketTime)); TeamAdjustedTicketsAdded = true; return; } //Remove old values (more than 90 seconds ago) Boolean removed = false; do { removed = false; if (TeamAdjustedTicketCounts.Any() && (Plugin.UtcNow() - TeamAdjustedTicketCounts.Peek().Value).TotalSeconds > 90) { TeamAdjustedTicketCounts.Dequeue(); removed = true; } if (TeamAdjustedTicketDifferenceRates.Any() && (Plugin.UtcNow() - TeamAdjustedTicketDifferenceRates.Peek().Value).TotalSeconds > 90) { TeamAdjustedTicketDifferenceRates.Dequeue(); removed = true; } } while (removed); Double oldAdjustedDifferenceRate = TeamAdjustedTicketDifferenceRate; //Interpolation DateTime oldTicketTime = TeamAdjustedTicketsTime; Double oldTicketValue = TeamAdjustedTicketCount; Double interTimeOldSeconds = 0; Double interTimeNewSeconds = (newAdjustedTicketTime - oldTicketTime).TotalSeconds; Double m = (newAdjustedTicketCount - oldTicketValue) / (interTimeNewSeconds); Double b = oldTicketValue; for (Int32 sec = (Int32)interTimeOldSeconds; sec < interTimeNewSeconds; sec++) { //Calculate time this datapoint occured DateTime subTicketTime = oldTicketTime.AddSeconds(sec); //Caclulate and enqueue the new adjusted ticket count Double subTicketValue = (m * sec) + b; TeamAdjustedTicketCounts.Enqueue(new KeyValuePair(subTicketValue, subTicketTime)); //Calculate and enqueue the new adjusted ticket difference rate List ticketValues = TeamAdjustedTicketCounts.Select(pair => pair.Key).ToList(); List ticketDifferences = new List(); for (int i = 0; i < ticketValues.Count - 1; i++) { ticketDifferences.Add(ticketValues[i + 1] - ticketValues[i]); } ticketDifferences.Sort(); //Convert to tickets/min TeamAdjustedTicketDifferenceRate = (ticketDifferences.Sum() / ticketDifferences.Count) * 60; if (Double.IsNaN(TeamAdjustedTicketDifferenceRate) || TeamAdjustedTicketDifferenceRate > 0) { TeamAdjustedTicketDifferenceRate = 0; } TeamAdjustedTicketDifferenceRates.Enqueue(new KeyValuePair(TeamAdjustedTicketDifferenceRate, subTicketTime)); //Calculate new ticket acceleration List accelerationValues = TeamAdjustedTicketDifferenceRates.Select(pair => pair.Key).ToList(); List accelerationDifferences = new List(); for (int i = 0; i < accelerationValues.Count - 1; i++) { accelerationDifferences.Add(accelerationValues[i + 1] - accelerationValues[i]); } accelerationDifferences.Sort(); //Convert to tickets/min/min TeamAdjustedTicketAccellerationRate = (accelerationDifferences.Sum() / accelerationDifferences.Count) * 60; if (Double.IsNaN(TeamAdjustedTicketAccellerationRate)) { TeamAdjustedTicketAccellerationRate = 0; } } //Set instance vars TeamAdjustedTicketCount = (Int32)newAdjustedTicketCount; TeamAdjustedTicketsTime = newAdjustedTicketTime; } catch (Exception e) { Plugin.Log.HandleException(new AException("Error while updating team adjusted ticket count.", e)); } } public void UpdateTotalScore(Double newTotalScore) { try { //Get rounded time (floor) DateTime newScoreTime = Plugin.UtcNow(); newScoreTime = newScoreTime.AddTicks(-(newScoreTime.Ticks % TimeSpan.TicksPerSecond)); if (!TeamTotalScoresAdded) { TeamScoreDifferenceRate = 0; TeamTotalScore = newTotalScore; TeamTotalScoresTime = newScoreTime; TeamTotalScores.Enqueue(new KeyValuePair(newTotalScore, newScoreTime)); TeamTotalScoresAdded = true; return; } //Interpolation DateTime oldScoreTime = TeamTotalScoresTime; Double oldScoreValue = TeamTotalScore; Double interTimeOldSeconds = 0; Double interTimeNewSeconds = (newScoreTime - oldScoreTime).TotalSeconds; Double m = (newTotalScore - oldScoreValue) / (interTimeNewSeconds); Double b = oldScoreValue; for (Int32 sec = (Int32)interTimeOldSeconds; sec < interTimeNewSeconds; sec++) { DateTime subScoreTime = oldScoreTime.AddSeconds(sec); Double subScoreValue = (m * sec) + b; TeamTotalScores.Enqueue(new KeyValuePair(subScoreValue, subScoreTime)); } //Remove old values (more than 60 seconds ago) Boolean removed = false; do { removed = false; if (TeamTotalScores.Any() && (Plugin.UtcNow() - TeamTotalScores.Peek().Value).TotalSeconds > 60) { TeamTotalScores.Dequeue(); TeamTotalScoresFull = true; removed = true; } } while (removed); //Set instance vars TeamTotalScore = newTotalScore; TeamTotalScoresTime = newScoreTime; List values = TeamTotalScores.Select(pair => pair.Key).ToList(); List differences = new List(); for (int i = 0; i < values.Count - 1; i++) { differences.Add(values[i + 1] - values[i]); } differences.Sort(); //Convert to tickets/min TeamScoreDifferenceRate = (differences.Sum() / differences.Count) * 60; if (Double.IsNaN(TeamScoreDifferenceRate)) { TeamScoreDifferenceRate = 0; } } catch (Exception e) { Plugin.Log.HandleException(new AException("Error while updating team ticket total score.", e)); } } public Double GetTeamPower() { return GetTeamPower(null, null, true); } public Double GetTeamPower(Boolean useModifiers) { return GetTeamPower(null, null, useModifiers); } public Double GetTeamPower(APlayer ignorePlayer, APlayer includePlayer) { return GetTeamPower(ignorePlayer, includePlayer, true); } public Double GetTeamPower(APlayer ignorePlayer, APlayer includePlayer, Boolean useModifiers) { try { if (!Plugin._PlayerDictionary.Any()) { return 0; } List teamPlayers = Plugin._PlayerDictionary.Values.ToList() .Where(aPlayer => // Player is a live soldier in game, not a spectator/commander/etc. aPlayer.player_type == PlayerType.Player && // Player is not a server seeder Plugin.NowDuration(aPlayer.lastAction).TotalMinutes < 20 && // Player is not the one we decided to ignore, if any (ignorePlayer == null || aPlayer.player_id != ignorePlayer.player_id) && ( // Player is required to be on this team (aPlayer.RequiredTeam != null && aPlayer.RequiredTeam.TeamID == TeamID) || // Player is actually on this team, and not required to be anywhere specifc (aPlayer.RequiredTeam == null && aPlayer.fbpInfo.TeamID == TeamID) || // Player is queued to move to this team ((TeamID == 1 ? Plugin._Team2MoveQueue : Plugin._Team1MoveQueue).Any(pObject => pObject.GUID == aPlayer.player_guid)) )).ToList(); if (includePlayer != null && !teamPlayers.Contains(includePlayer)) { teamPlayers.Add(includePlayer); } var roundMinutes = 0.0; if (Plugin._roundState == RoundState.Playing) { roundMinutes = Plugin._serverInfo.GetRoundElapsedTime().TotalMinutes; } var teamTopPlayers = teamPlayers.Where(aPlayer => aPlayer.GetPower(true, roundMinutes >= 5.0, true) > 1); var topPowerSum = teamTopPlayers.Select(aPlayer => aPlayer.GetPower(true, roundMinutes >= 5.0, true)).Sum(); var totalPower = Math.Round(topPowerSum); return totalPower; } catch (Exception e) { Plugin.Log.HandleException(new AException("Error while fetching team power.", e)); } return 0; } public void Reset() { try { TeamTicketCount = 0; TeamAdjustedTicketCount = 0; TeamTicketCounts.Clear(); TeamAdjustedTicketCounts.Clear(); TeamAdjustedTicketDifferenceRates.Clear(); TeamTicketDifferenceRate = 0; TeamAdjustedTicketDifferenceRate = 0; TeamAdjustedTicketAccellerationRate = 0; TeamTicketsAdded = false; TeamAdjustedTicketsAdded = false; TeamTicketAdjustment = 0; TeamTotalScore = 0; TeamTotalScores.Clear(); TeamScoreDifferenceRate = 0; TeamTotalScoresAdded = false; TeamTotalScoresFull = false; TeamTicketCountsFull = false; } catch (Exception e) { Plugin.Log.HandleException(new AException("Error while resetting team.", e)); } } } public class AUser { //No reference to player table made here, plain String id access public Dictionary soldierDictionary = null; public String user_email = null; public Int64 user_id = -1; public String user_name = null; public String user_phone = null; public ARole user_role = null; public DateTime user_expiration; public String user_notes = ""; public AUser() { soldierDictionary = new Dictionary(); } } public class AWeaponStat { //serviceStars public Double ServiceStars = 0; //serviceStarsProgress public Double ServiceStarsProgress = 0; //category public String Category; //categorySID public String CategorySID; //slug public String ID; //name public String WarsawID; //shotsFired public Double Shots = 0; //shotsHit public Double Hits = 0; //accuracy public Double Accuracy = 0; //headshots public Double Headshots = 0; //kills public Double Kills = 0; //timeEquipped public TimeSpan Time = TimeSpan.FromSeconds(0); //Calculated Values public Double HSKR = 0; public Double KPM = 0; public Double DPS = 0; } public class AVehicleStat { //serviceStars public Double ServiceStars = 0; //serviceStarsProgress public Double ServiceStarsProgress = 0; //slug public String ID; //name public String WarsawID; //kills public Double Kills = 0; //timeIn public TimeSpan TimeIn = TimeSpan.FromSeconds(0); //category public String Category; //destroyXinY public Double DestroyXInY; //Calculated Values public Double KPM = 0; } public class ASQLUpdate { public String update_id; public String version_minimum; public String version_maximum; public String message_name; public String message_success; public String message_failure; public Boolean update_checks_hasResults = true; public List update_checks; public Boolean update_execute_requiresModRows; public List update_execute; public List update_success; public List update_failure; public ASQLUpdate() { update_checks = new List(); update_execute = new List(); update_success = new List(); update_failure = new List(); } } internal enum AssessmentTypes { none, black, white, watch } public class BBM5108Ban { public DateTime ban_duration; public String ban_length = null; public String ban_reason = null; public String eaguid = null; public String soldiername = null; } public class PushBulletHandler { public enum Target { Private, Channel } public AdKats Plugin; public String AccessToken; public Target DefaultTarget = Target.Private; public String DefaultChannelTag; public PushBulletHandler(AdKats plugin) { Plugin = plugin; } public void PushReport(ARecord record) { if (record.target_player == null) { Plugin.SendMessageToSource(record, "Unable to send report email. No target player found."); return; } Plugin.Log.Debug(() => "Sending PushBullet report [" + record.command_numeric + "] on " + record.GetTargetNames(), 3); String title = record.GetTargetNames() + " reported in [" + Plugin.GameVersion + "] " + Plugin._serverInfo.ServerName.Substring(0, Math.Min(15, Plugin._serverInfo.ServerName.Length - 1)); StringBuilder bb = new StringBuilder(); bb.Append("AdKats player report [" + record.command_numeric + "]"); bb.AppendLine(); bb.AppendLine(); bb.Append(record.GetSourceName() + " reported " + record.GetTargetNames() + " for " + record.record_message); bb.AppendLine(); bb.AppendLine(); bb.Append(Plugin._serverInfo.ServerName); PushDefault(title, bb.ToString()); } public void PushDefault(String title, String body) { switch (DefaultTarget) { case Target.Private: PushPrivate(title, body); break; case Target.Channel: PushChannel(title, body, DefaultChannelTag); break; default: Plugin.Log.Error("Pushbullet configured with invalid target."); break; } } public void PushPrivate(String title, String body) { WebResponse response = null; try { if (String.IsNullOrEmpty(AccessToken)) { Plugin.Log.Error("PushBullet token empty! Unable to push private note."); return; } if (String.IsNullOrEmpty(title)) { Plugin.Log.Error("PushBullet note title empty! Unable to push private note."); return; } if (String.IsNullOrEmpty(body)) { Plugin.Log.Error("PushBullet note body empty! Unable to push private note."); return; } WebRequest request = WebRequest.Create("https://api.pushbullet.com/v2/pushes"); request.Method = "POST"; request.Headers.Add("Access-Token", AccessToken); request.ContentType = "application/json"; String jsonBody = JSON.JsonEncode(new Hashtable { {"active", true}, {"type", "note"}, {"sender_name", "AdKats-" + Plugin._serverInfo.ServerID}, {"title", title}, {"body", body} }); byte[] byteArray = Encoding.UTF8.GetBytes(jsonBody); request.ContentLength = byteArray.Length; Stream requestStream = request.GetRequestStream(); requestStream.Write(byteArray, 0, byteArray.Length); requestStream.Close(); } catch (WebException e) { response = e.Response; Plugin.Log.Info("RESPONSE: " + new StreamReader(response.GetResponseStream()).ReadToEnd()); Plugin.Log.HandleException(new AException("Error sending private PushBullet note.", e)); } } public void PushChannel(String title, String body, String channelTag) { WebResponse response = null; try { if (String.IsNullOrEmpty(AccessToken)) { Plugin.Log.Error("PushBullet token empty! Unable to push channel note."); return; } if (String.IsNullOrEmpty(channelTag)) { Plugin.Log.Error("PushBullet channel tag empty! Unable to push channel note."); return; } if (String.IsNullOrEmpty(title)) { Plugin.Log.Error("PushBullet note title empty! Unable to push channel note."); return; } if (String.IsNullOrEmpty(body)) { Plugin.Log.Error("PushBullet note body empty! Unable to push channel note."); return; } WebRequest request = WebRequest.Create("https://api.pushbullet.com/v2/pushes"); request.Method = "POST"; request.Headers.Add("Access-Token", AccessToken); request.ContentType = "application/json"; String jsonBody = JSON.JsonEncode(new Hashtable { {"active", true}, {"type", "note"}, {"sender_name", "AdKats-" + Plugin._serverInfo.ServerID}, {"channel_tag", channelTag}, {"title", title}, {"body", body} }); byte[] byteArray = Encoding.UTF8.GetBytes(jsonBody); request.ContentLength = byteArray.Length; Stream requestStream = request.GetRequestStream(); requestStream.Write(byteArray, 0, byteArray.Length); requestStream.Close(); } catch (WebException e) { response = e.Response; Plugin.Log.Info("RESPONSE: " + new StreamReader(response.GetResponseStream()).ReadToEnd()); Plugin.Log.HandleException(new AException("Error sending private PushBullet note.", e)); } } } public class AWeaponDictionary { public AdKats _plugin; public readonly Dictionary Weapons = new Dictionary(); public String AllDamageTypeEnumString = ""; public String InfantryDamageTypeEnumString = ""; public String AllWeaponNameEnumString = ""; public String InfantryWeaponNameEnumString = ""; public AWeaponDictionary(AdKats plugin) { _plugin = plugin; try { //Fill the damage type setting enum string Random random = new Random(Environment.TickCount); InfantryDamageTypeEnumString = String.Empty; foreach (DamageTypes damageType in Enum.GetValues(typeof(DamageTypes)) .Cast() .Where(type => type != DamageTypes.Nonlethal && type != DamageTypes.Suicide && type != DamageTypes.VehicleAir && type != DamageTypes.VehicleHeavy && type != DamageTypes.VehicleLight && type != DamageTypes.VehiclePersonal && type != DamageTypes.VehicleStationary && type != DamageTypes.VehicleTransport && type != DamageTypes.VehicleWater) .OrderBy(type => type.ToString())) { if (String.IsNullOrEmpty(InfantryDamageTypeEnumString)) { InfantryDamageTypeEnumString += "enum.InfantryDamageTypeEnum_" + random.Next(100000, 999999) + "("; } else { InfantryDamageTypeEnumString += "|"; } InfantryDamageTypeEnumString += damageType; } InfantryDamageTypeEnumString += ")"; AllDamageTypeEnumString = String.Empty; foreach (DamageTypes damageType in Enum.GetValues(typeof(DamageTypes)) .Cast() .Where(type => type != DamageTypes.Nonlethal && type != DamageTypes.Suicide) .OrderBy(type => type.ToString())) { if (String.IsNullOrEmpty(AllDamageTypeEnumString)) { AllDamageTypeEnumString += "enum.AllDamageTypeEnum_" + random.Next(100000, 999999) + "("; } else { AllDamageTypeEnumString += "|"; } AllDamageTypeEnumString += damageType; } AllDamageTypeEnumString += ")"; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while creating weapon dictionary.", e)); } } public Boolean PopulateDictionaries() { try { // Populate the weapon types foreach (Weapon weapon in _plugin.GetWeaponDefines()) { if (weapon != null) { // Valid weapon, add damage type to the dictionary AWeapon dWeapon = null; if (!Weapons.TryGetValue(weapon.Name, out dWeapon)) { dWeapon = new AWeapon() { Game = _plugin.GameVersion, Code = weapon.Name }; Weapons[dWeapon.Code] = dWeapon; } dWeapon.Damage = weapon.Damage; // Fixing invalid weapon damages if (dWeapon.Code == "dlSHTR") { // This is the phantom bow. Change it to sniper rifle damage type. dWeapon.Damage = DamageTypes.SniperRifle; } if (dWeapon.Code == "U_SR338") { // This is a DMR incorrectly categorized as sniper damage dWeapon.Damage = DamageTypes.DMR; } if (dWeapon.Code == "U_BallisticShield") { // This is the shield incorrectly categorized as impact damage. dWeapon.Damage = DamageTypes.Melee; } if (dWeapon.Code.ToLower() == "roadkill") { // ToLower needed because of different values between bf3/bf4 dWeapon.Damage = DamageTypes.Impact; } } } Hashtable weaponNames = FetchAWeaponNames(); if (weaponNames == null) { return false; } Hashtable gameWeaponNames = (Hashtable)weaponNames[_plugin.GameVersion.ToString()]; if (gameWeaponNames == null) { _plugin.Log.Error("Weapons for " + _plugin.GameVersion + " not found in weapon name library."); return false; } foreach (DictionaryEntry currentWeapon in gameWeaponNames) { //Create new construct String weaponCode = (String)currentWeapon.Key; String shortName = (String)((Hashtable)currentWeapon.Value)["readable_short"]; String longName = (String)((Hashtable)currentWeapon.Value)["readable_long"]; AWeapon dWeapon = null; if (!Weapons.TryGetValue(weaponCode, out dWeapon)) { dWeapon = new AWeapon() { Game = _plugin.GameVersion, Code = weaponCode }; Weapons[dWeapon.Code] = dWeapon; } //Add the weapon names dWeapon.Code = weaponCode; dWeapon.ShortName = shortName; dWeapon.LongName = longName; } //Fill the weapon name enum string Random random = new Random(Environment.TickCount); InfantryWeaponNameEnumString = String.Empty; foreach (var weaponName in Weapons.Values.Where(weapon => !String.IsNullOrEmpty(weapon.ShortName) && weapon.Damage != DamageTypes.None && weapon.Damage != DamageTypes.Nonlethal && weapon.Damage != DamageTypes.Suicide && weapon.Damage != DamageTypes.VehicleAir && weapon.Damage != DamageTypes.VehicleHeavy && weapon.Damage != DamageTypes.VehicleLight && weapon.Damage != DamageTypes.VehiclePersonal && weapon.Damage != DamageTypes.VehicleStationary && weapon.Damage != DamageTypes.VehicleTransport && weapon.Damage != DamageTypes.VehicleWater) .OrderBy(weapon => weapon.Damage) .ThenBy(weapon => weapon.ShortName) .Select(weapon => weapon.Damage.ToString() + "\\" + weapon.ShortName) .Distinct()) { if (String.IsNullOrEmpty(InfantryWeaponNameEnumString)) { InfantryWeaponNameEnumString += "enum.InfantryWeaponNameEnum_" + random.Next(100000, 999999) + "(None|"; } else { InfantryWeaponNameEnumString += "|"; } InfantryWeaponNameEnumString += weaponName; } InfantryWeaponNameEnumString += ")"; AllWeaponNameEnumString = String.Empty; foreach (var weaponName in Weapons.Values.Where(weapon => !String.IsNullOrEmpty(weapon.ShortName) && weapon.Damage != DamageTypes.None && weapon.Damage != DamageTypes.Nonlethal && weapon.Damage != DamageTypes.Suicide) .OrderBy(weapon => weapon.Damage) .ThenBy(weapon => weapon.ShortName) .Select(weapon => weapon.Damage.ToString() + "\\" + weapon.ShortName) .Distinct()) { if (String.IsNullOrEmpty(AllWeaponNameEnumString)) { AllWeaponNameEnumString += "enum.AllWeaponNameEnum_" + random.Next(100000, 999999) + "(None|"; } else { AllWeaponNameEnumString += "|"; } AllWeaponNameEnumString += weaponName; } AllWeaponNameEnumString += ")"; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while populating weapon name cache", e)); } return true; } private Hashtable FetchAWeaponNames() { _plugin.Log.Debug(() => "Entering FetchAWeaponNames", 7); Hashtable weaponNames = null; using (GZipWebClient client = new GZipWebClient(compress: false)) { String downloadString; _plugin.Log.Debug(() => "Fetching weapon names...", 2); try { downloadString = _plugin.Util.ClientDownloadTimer(client, "https://raw.githubusercontent.com/AdKats/AdKats/master/adkatsweaponnames.json" + "?cacherand=" + Environment.TickCount); _plugin.Log.Debug(() => "Weapon names fetched.", 1); } catch (Exception) { try { downloadString = _plugin.Util.ClientDownloadTimer(client, "https://api.myrcon.net/plugins/adkats/weaponnames" + "?cacherand=" + Environment.TickCount); _plugin.Log.Debug(() => "Weapon names fetched from backup location.", 1); } catch (Exception) { return null; } } try { weaponNames = (Hashtable)JSON.JsonDecode(downloadString); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while parsing reputation definitions.", e)); } } _plugin.Log.Debug(() => "Exiting FetchAWeaponNames", 7); return weaponNames; } public List GetWeaponCodesOfDamageType(DamageTypes damage) { try { if (damage == DamageTypes.None) { _plugin.Log.HandleException(new AException("Damage type was None when fetching weapons of damage type.")); return new List(); } return Weapons.Values.Where(weapon => weapon.Damage == damage).Select(weapon => weapon.Code).ToList(); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting damage type.", e)); } return new List(); } public DamageTypes GetDamageTypeByWeaponCode(String weaponCode) { try { if (String.IsNullOrEmpty(weaponCode)) { _plugin.Log.HandleException(new AException("weaponCode was empty/null when fetching weapon damage type.")); return DamageTypes.None; } var weapon = Weapons.Values.FirstOrDefault(dWeapon => dWeapon.Code == weaponCode); if (weapon == null) { _plugin.Log.HandleException(new AException("No weapon defined for code " + weaponCode + " when fetching damage type. Is your DEF file updated?")); return DamageTypes.None; } return weapon.Damage; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting damage type for weapon code.", e)); } return DamageTypes.None; } public String GetWeaponCodeByShortName(String weaponShortName) { try { if (!String.IsNullOrEmpty(weaponShortName)) { foreach (var weapon in Weapons.Values) { if (weapon != null && weapon.ShortName == weaponShortName) { return weapon.Code; } } } _plugin.Log.HandleException(new AException("Unable to get weapon CODE for short NAME '" + weaponShortName + "', in " + Weapons.Count() + " weapons.")); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting weapon code for short name.", e)); } return null; } public String GetShortWeaponNameByCode(String weaponCode) { try { AWeapon weaponName = null; if (String.IsNullOrEmpty(weaponCode)) { _plugin.Log.HandleException(new AException("weaponCode was null when fetching weapon name")); return null; } Weapons.TryGetValue(weaponCode, out weaponName); if (weaponName == null) { _plugin.Log.HandleException(new AException("Unable to get weapon short NAME for CODE '" + weaponCode + "', in " + Weapons.Count() + " weapons.")); return weaponCode; } return weaponName.ShortName; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting short weapon name for code.", e)); } return null; } public String GetLongWeaponNameByCode(String weaponCode) { try { AWeapon weaponName = null; if (String.IsNullOrEmpty(weaponCode)) { _plugin.Log.HandleException(new AException("weaponCode was null when fetching weapon name")); return null; } Weapons.TryGetValue(weaponCode, out weaponName); if (weaponName == null) { _plugin.Log.HandleException(new AException("Unable to get weapon long NAME for CODE '" + weaponCode + "', in " + Weapons.Count() + " weapons.")); return weaponCode; } return weaponName.ShortName; } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while getting long weapon name for code.", e)); } return null; } public class AWeapon { public GameVersionEnum Game; public DamageTypes Damage = DamageTypes.None; public String Code; public String ShortName; public String LongName; } } public class EmailHandler { private readonly Queue _EmailProcessingQueue = new Queue(); public readonly EventWaitHandle _EmailProcessingWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); public String CustomHTMLAddition; public AdKats Plugin; public List RecipientEmails = new List(); public String SMTPPassword = "paqwjboqkbfywapu"; public Int32 SMTPPort = 587; public String SMTPServer = "smtp.gmail.com"; public String SMTPUser = "adkatsbattlefield@gmail.com"; public String SenderEmail = "adkatsbattlefield@gmail.com"; public Boolean UseSSL = true; private Thread _EmailProcessingThread; public EmailHandler(AdKats plugin) { Plugin = plugin; switch (Plugin.GameVersion) { case GameVersionEnum.BF3: CustomHTMLAddition = @"
BF3 Battlelog Profile

BF3Stats Profile

AntiCheat, INC. Search

I-Stats Search

TeamDes Search

Hedix Search
"; break; case GameVersionEnum.BF4: CustomHTMLAddition = @"
BF4 Battlelog Profile

BF4Stats Profile

AntiCheat, INC. Search
"; break; default: CustomHTMLAddition = ""; break; } } public void SendReport(ARecord record) { try { if (Plugin.FetchOnlineAdminSoldiers().Any() && false) { Plugin.Log.Warn("Online admins detected, report email aborted."); return; } if (record.target_player == null) { Plugin.SendMessageToSource(record, "Unable to send report email. No target player found."); return; } //Create a new thread to handle keep-alive //This thread will remain running for the duration the layer is online Thread emailSendingThread = new Thread(new ThreadStart(delegate { try { Thread.CurrentThread.Name = "EmailSending"; String subject = String.Empty; String body = String.Empty; StringBuilder sb = new StringBuilder(); if (String.IsNullOrEmpty(Plugin._serverInfo.ServerName)) { //Unable to send report email, server id unknown return; } subject = record.GetTargetNames() + " reported in [" + Plugin.GameVersion + "] " + Plugin._serverInfo.ServerName; sb.Append("

AdKats " + Plugin.GameVersion + " Player Report [" + record.command_numeric + "]

"); sb.Append("

" + Plugin._serverInfo.ServerName + "

"); sb.Append("

" + DateTime.Now + " ProCon Time

"); sb.Append("

" + record.GetSourceName() + " has reported " + record.GetTargetNames() + " for " + record.record_message + "

"); sb.Append("

"); CPlayerInfo playerInfo = record.target_player.fbpInfo; int numReports = Plugin._PlayerReports.Count(aRecord => aRecord.target_player.player_id == record.target_player.player_id && aRecord.TargetSession == aRecord.target_player.ActiveSession); sb.Append("Reported " + numReports + " times during their current session.
"); sb.Append("Has " + Plugin.FetchPoints(record.target_player, false, true) + " infraction points.
"); sb.Append("Score: " + playerInfo.Score + "
"); sb.Append("Kills: " + playerInfo.Kills + "
"); sb.Append("Deaths: " + playerInfo.Deaths + "
"); sb.Append("Kdr: " + playerInfo.Kdr + "
"); sb.Append("Ping: " + playerInfo.Ping + "
"); sb.Append("

"); sb.Append("

"); sb.Append("SoldierName: " + playerInfo.SoldierName + "
"); sb.Append("EA GUID: " + playerInfo.GUID + "
"); if (record.target_player.PBPlayerInfo != null) { sb.Append("PB GUID: " + record.target_player.PBPlayerInfo.GUID + "
"); sb.Append("IP: " + record.target_player.PBPlayerInfo.Ip.Split(':')[0] + "
"); sb.Append("Country: " + record.target_player.PBPlayerInfo.PlayerCountry + "
"); } String processedCustomHTML = Plugin.ReplacePlayerInformation(CustomHTMLAddition, record.target_player); processedCustomHTML = processedCustomHTML.Replace("%map_name%", Plugin.GetCurrentReadableMap()); processedCustomHTML = processedCustomHTML.Replace("%mode_name%", Plugin.GetCurrentReadableMode()); sb.Append(processedCustomHTML); sb.Append("

"); if (record.target_player != null) { sb.Append(""); sb.Append(@""); sb.Append(""); if (record.source_player != null) { foreach (KeyValuePair> chatLine in Plugin.FetchConversation(record.source_player.player_id, record.target_player.player_id, 30, 7)) { sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); } } else { foreach (KeyValuePair chatLine in Plugin.FetchChat(record.target_player.player_id, 30, 7)) { sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); } } sb.Append(""); sb.Append("
TimePlayerMessage
" + chatLine.Key.ToShortDateString() + " " + chatLine.Key.ToShortTimeString() + "" + chatLine.Value.Key + "" + chatLine.Value.Value + "
" + chatLine.Key.ToShortDateString() + " " + chatLine.Key.ToShortTimeString() + "" + record.GetTargetNames() + "" + chatLine.Value + "
"); } body = sb.ToString(); EmailWrite(subject, body); } catch (Exception e) { Plugin.Log.HandleException(new AException("Error in email sending thread.", e)); } Plugin.Threading.StopWatchdog(); })); //Start the thread Plugin.Threading.StartWatchdog(emailSendingThread); } catch (Exception e) { Plugin.Log.HandleException(new AException("Error when sending email.", e)); } } private void EmailWrite(String subject, String body) { try { MailMessage email = new MailMessage(); email.From = new MailAddress(SenderEmail, "AdKats Report System"); Boolean someAdded = false; lock (Plugin._userCache) { foreach (AUser aUser in Plugin._userCache.Values) { //Check for not null and default values if (Plugin.UserIsAdmin(aUser) && !String.IsNullOrEmpty(aUser.user_email)) { if (Regex.IsMatch(aUser.user_email, @"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$")) { email.Bcc.Add(new MailAddress(aUser.user_email)); someAdded = true; } else { Plugin.Log.Error("Error in user email address: " + aUser.user_email); } } } foreach (String extraEmail in RecipientEmails) { if (String.IsNullOrEmpty(extraEmail.Trim())) { continue; } if (Regex.IsMatch(extraEmail, @"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$")) { email.Bcc.Add(new MailAddress(extraEmail)); someAdded = true; } else { Plugin.Log.Error("Error in extra email address: " + extraEmail); } } } if (!someAdded) { Plugin.Log.Error("Unable to send email. No users with emails have access to player interaction commands."); return; } email.Subject = subject; email.Body = body; email.IsBodyHtml = true; email.BodyEncoding = Encoding.UTF8; QueueEmailForSending(email); } catch (Exception e) { Plugin.Log.Error("Error while sending email: " + e); } } private void QueueEmailForSending(MailMessage email) { Plugin.Log.Debug(() => "Entering QueueEmailForSending", 7); try { if (Plugin._pluginEnabled) { Plugin.Log.Debug(() => "Preparing to queue email for processing", 6); lock (_EmailProcessingQueue) { _EmailProcessingQueue.Enqueue(email); Plugin.Log.Debug(() => "Email queued for processing", 6); //Start the processing thread if not already running if (_EmailProcessingThread == null || !_EmailProcessingThread.IsAlive) { _EmailProcessingThread = new Thread(EmailProcessingThreadLoop) { IsBackground = true }; Plugin.Threading.StartWatchdog(_EmailProcessingThread); } _EmailProcessingWaitHandle.Set(); } } } catch (Exception e) { Plugin.Log.HandleException(new AException("Error while queueing email for processing.", e)); } Plugin.Log.Debug(() => "Exiting QueueEmailForSending", 7); } public void EmailProcessingThreadLoop() { try { Plugin.Log.Debug(() => "Starting Email Handling Thread", 1); Thread.CurrentThread.Name = "EmailProcessing"; DateTime loopStart = Plugin.UtcNow(); while (true) { try { Plugin.Log.Debug(() => "Entering Email Handling Thread Loop", 7); if (!Plugin._pluginEnabled) { Plugin.Log.Debug(() => "Detected AdKats not enabled. Exiting thread " + Thread.CurrentThread.Name, 6); break; } //Get all unprocessed inbound emails Queue inboundEmailMessages = new Queue(); if (_EmailProcessingQueue.Any()) { Plugin.Log.Debug(() => "Preparing to lock inbound mail queue to retrive new mail", 7); lock (_EmailProcessingQueue) { Plugin.Log.Debug(() => "Inbound mail found. Grabbing.", 6); //Grab all mail in the queue inboundEmailMessages = new Queue(_EmailProcessingQueue.ToArray()); //Clear the queue for next run _EmailProcessingQueue.Clear(); } } else { Plugin.Log.Debug(() => "No inbound mail. Waiting for Input.", 6); //Wait for input if ((Plugin.UtcNow() - loopStart).TotalMilliseconds > 1000) { Plugin.Log.Debug(() => "Warning. " + Thread.CurrentThread.Name + " thread processing completed in " + ((int)((Plugin.UtcNow() - loopStart).TotalMilliseconds)) + "ms", 4); } _EmailProcessingWaitHandle.Reset(); _EmailProcessingWaitHandle.WaitOne(TimeSpan.FromSeconds(5)); loopStart = Plugin.UtcNow(); continue; } //Loop through all mails in order that they came in while (inboundEmailMessages.Any()) { if (!Plugin._pluginEnabled) { break; } Plugin.Log.Debug(() => "begin reading mail", 6); MailMessage message = inboundEmailMessages.Dequeue(); if (Plugin.Log.DebugLevel >= 5) { Plugin.Log.Write("server: " + SMTPServer); Plugin.Log.Write("port: " + SMTPPort); Plugin.Log.Write("user/pass: " + ((!String.IsNullOrEmpty(SMTPUser) && !String.IsNullOrEmpty(SMTPPassword)) ? "OK" : "INVALID")); Plugin.Log.Write("details sender: " + message.Sender); Plugin.Log.Write("details from: " + message.From); Plugin.Log.Write("details to: " + message.To); Plugin.Log.Write("details cc: " + message.CC); Plugin.Log.Write("details bcc: " + message.Bcc); Plugin.Log.Write("details subject: " + message.Subject); Plugin.Log.Write("details body: " + message.Body); } //Dequeue the first/next mail SmtpClient smtp = new SmtpClient(SMTPServer, SMTPPort) { EnableSsl = UseSSL, Timeout = 10000, DeliveryMethod = SmtpDeliveryMethod.Network, UseDefaultCredentials = false, Credentials = new NetworkCredential(SMTPUser, SMTPPassword) }; smtp.SendCompleted += new SendCompletedEventHandler(smtp_SendCompleted); Plugin.Log.Debug(() => "Sending notification email. Please wait.", 1); smtp.Send(message); Plugin.Log.Debug(() => "A notification email has been sent.", 1); if (inboundEmailMessages.Any()) { //Wait 5 seconds between loops Plugin.Threading.Wait(5000); } } } catch (Exception e) { if (e is ThreadAbortException) { Plugin.Log.HandleException(new AException("mail processing thread aborted. Exiting.")); break; } Plugin.Log.HandleException(new AException("Error occured in mail processing thread. skipping loop.", e)); } } Plugin.Log.Debug(() => "Ending mail Processing Thread", 1); Plugin.Threading.StopWatchdog(); } catch (Exception e) { Plugin.Log.HandleException(new AException("Error occured in mail processing thread.", e)); } } private void smtp_SendCompleted(object sender, AsyncCompletedEventArgs e) { if (e.Cancelled == true || e.Error != null) { Plugin.Log.HandleException(new AException("Error occured in mail processing. Sending Canceled.", e.Error)); } } } public class IPAPILocation { public String IP; public String city; public String country; public String countryCode; public String isp; public Double lat; public Double lon; public String message; public String org; public String query; public String region; public String regionName; public String status; public String timezone; public String zip; public IPAPILocation(String ip) { if (String.IsNullOrEmpty(ip)) { throw new NoNullAllowedException("Location IP must not be null."); } IP = ip; } } internal static class SHA1 { private static System.Security.Cryptography.SHA1 HASHER = System.Security.Cryptography.SHA1.Create(); public static string Data(byte[] data) { StringBuilder stringifyHash = new StringBuilder(); byte[] hash = SHA1.HASHER.ComputeHash(data); for (int x = 0; x < hash.Length; x++) { stringifyHash.Append(hash[x].ToString("x2")); } return stringifyHash.ToString(); } public static string String(string data) { return SHA1.Data(Encoding.UTF8.GetBytes(data)); } } public class StatLibrary { private readonly AdKats Plugin; public Dictionary Weapons; public StatLibrary(AdKats plugin) { Plugin = plugin; } public Boolean PopulateWeaponStats() { try { //Get Weapons Hashtable statTable = FetchWeaponDefinitions(); Hashtable gameTable = (Hashtable)statTable[Plugin.GameVersion.ToString()]; if (gameTable != null && gameTable.Count > 0) { Dictionary tempWeapons = new Dictionary(); foreach (String currentCategory in gameTable.Keys) { Hashtable categoryTable = (Hashtable)gameTable[currentCategory]; foreach (String currentWeapon in categoryTable.Keys) { Hashtable weaponTable = (Hashtable)categoryTable[currentWeapon]; StatLibraryWeapon weapon = new StatLibraryWeapon { ID = currentWeapon, Category = currentCategory, DamageMax = (Double)weaponTable["max"], DamageMin = (Double)weaponTable["min"] }; tempWeapons.Add(weapon.ID, weapon); } } if (tempWeapons.Count > 0) { Weapons = tempWeapons; return true; } } else { Plugin.Log.Error("Unable to find current game in weapon stats library."); } } catch (Exception e) { Plugin.Log.HandleException(new AException("Error while fetching weapon stats for " + Plugin.GameVersion, e)); } return false; } private Hashtable FetchWeaponDefinitions() { Hashtable statTable = null; using (GZipWebClient client = new GZipWebClient(compress: false)) { String weaponInfo; Plugin.Log.Debug(() => "Fetching weapon statistic definitions...", 2); try { weaponInfo = Plugin.Util.ClientDownloadTimer(client, "https://raw.githubusercontent.com/AdKats/AdKats/master/adkatsblweaponstats.json" + "?cacherand=" + Environment.TickCount); Plugin.Log.Debug(() => "Weapon statistic definitions fetched.", 1); } catch (Exception) { try { weaponInfo = Plugin.Util.ClientDownloadTimer(client, "https://api.myrcon.net/plugins/adkats/weapons" + "?cacherand=" + Environment.TickCount); Plugin.Log.Debug(() => "Weapon statistic definitions fetched from backup location.", 1); } catch (Exception) { return null; } } try { statTable = (Hashtable)JSON.JsonDecode(weaponInfo); } catch (Exception e) { Plugin.Log.HandleException(new AException("Error while parsing weapon statistic definitions.", e)); return null; } } return statTable; } } public class StatLibraryWeapon { public String Category = null; public Double DamageMax = -1; public Double DamageMin = -1; public String ID = null; } public List Shuffle(List list) { RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider(); int n = list.Count; while (n > 1) { byte[] box = new byte[1]; do provider.GetBytes(box); while (!(box[0] < n * (Byte.MaxValue / n))); int k = (box[0] % n); n--; T value = list[k]; list[k] = list[n]; list[n] = value; } return list; } public class DiscordManager { //Plugin public AdKats _plugin; //Settings public Boolean Enabled; public String ServerID; public String[] ChannelNames = { }; public Boolean DebugMembers; public Boolean DebugService; public VoipJoinDisplayType JoinDisplay = VoipJoinDisplayType.Disabled; public String JoinMessage = "%playerusername% joined discord! Welcome!"; private TimeSpan _UpdateDuration = TimeSpan.FromSeconds(29); public String URL; //Vars public String ServerName; public String InstantInvite; private readonly Dictionary Channels = new Dictionary(); private readonly Dictionary Members = new Dictionary(); public DateTime LastUpdate = DateTime.UtcNow - TimeSpan.FromSeconds(30); public Int32 ConnectionIssueCount = 0; public DiscordManager(AdKats plugin) { _plugin = plugin; RunDiscordManagerMainThread(); } public void Enable() { Enabled = true; UpdateDiscordServerInfo(); } public void Disable() { Enabled = false; Channels.Clear(); Members.Clear(); } public List GetChannels() { return Channels.Values.ToList(); } public List GetMembers(Boolean onlyActive, Boolean onlyVoice, Boolean onlyChannels) { try { if (!Members.Any()) { return new List(); } var resultMembers = Members.Values.Where(aMember => !aMember.Bot); if (onlyActive) { resultMembers = resultMembers.Where(aMember => aMember.Status == "online"); } if (onlyVoice) { resultMembers = resultMembers.Where(aMember => aMember.Channel != null); } if (onlyChannels) { resultMembers = resultMembers.Where(aMember => aMember.Channel != null && (!ChannelNames.Any() || String.IsNullOrEmpty(ChannelNames.FirstOrDefault()) || ChannelNames.Contains(aMember.Channel.Name))); } return resultMembers.ToList(); } catch (Exception) { return new List(); } } private void RunDiscordManagerMainThread() { var discordMainThread = new Thread(new ThreadStart(delegate { Thread.CurrentThread.Name = "DiscordManagerMainThread"; Thread.Sleep(TimeSpan.FromMilliseconds(250)); // Main Thread Loop while (true) { try { if (Enabled) { // Update the server information at interval if (_plugin.NowDuration(LastUpdate) > _UpdateDuration) { if (DebugService) { _plugin.Log.Info("Ready to update discord server info."); } var results = UpdateDiscordServerInfo(); if (DebugService) { _plugin.Log.Info("Discord server info updated. Success: " + results); } } } // Sleep until the next execution is needed Thread.Sleep(TimeSpan.FromSeconds(30)); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error in discord manager main thread loop. Skipping current loop.", e)); Thread.Sleep(TimeSpan.FromSeconds(30)); } } })); discordMainThread.Start(); } private Boolean UpdateDiscordServerInfo() { _plugin.Log.Debug(() => "Entering UpdateDiscordServerInfo", 7); var success = false; try { _plugin.Log.Debug(() => "Preparing to fetch discord server information.", 7); //Get the widget URL var widgetURL = GetWidgetURL(); //Attempt to fetch and parse if (!String.IsNullOrEmpty(widgetURL)) { using (GZipWebClient client = new GZipWebClient(compress: false)) { try { List clientInfo = new List(); String clientResponse = _plugin.Util.ClientDownloadTimer(client, widgetURL); Hashtable responseJSON = null; try { // Remove surrogate codepoint values as raw text var tester = new Regex(@"[\\][u][d][8-9a-f][0-9a-f][0-9a-f]", RegexOptions.IgnoreCase); clientResponse = tester.Replace(clientResponse, String.Empty); responseJSON = (Hashtable)JSON.JsonDecode(clientResponse); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error processing JSON", e)); return false; } if (!responseJSON.ContainsKey("id") || !responseJSON.ContainsKey("name") || !responseJSON.ContainsKey("instant_invite") || !responseJSON.ContainsKey("channels") || !responseJSON.ContainsKey("members")) { _plugin.Log.Warn("Discord JSON did not contain required elements."); clientResponse = clientResponse.Length <= 500 ? clientResponse : clientResponse.Substring(0, 500); _plugin.Log.Warn(clientResponse); return false; } if (DebugService) { _plugin.Log.Warn("Debug printing the discord client response."); clientResponse = clientResponse.Length <= 500 ? clientResponse : clientResponse.Substring(0, 500); _plugin.Log.Warn(clientResponse); } // Globals ServerID = (String)responseJSON["id"]; ServerName = (String)responseJSON["name"]; InstantInvite = (String)responseJSON["instant_invite"]; // Channels ArrayList responseChannels = (ArrayList)responseJSON["channels"]; List validChannels = new List(); foreach (Hashtable channel in responseChannels) { DiscordChannel builtChannel = null; String ID = (String)channel["id"]; validChannels.Add(ID); if (!Channels.TryGetValue(ID, out builtChannel)) { builtChannel = new DiscordChannel() { ID = ID }; Channels[ID] = builtChannel; } builtChannel.Name = (String)channel["name"]; builtChannel.Position = Int32.Parse(channel["position"].ToString()); } //Remove all old channels List removeChannelIDs = Channels.Keys.Where(ID => !validChannels.Contains(ID)).ToList(); foreach (String removeID in removeChannelIDs) { Channels.Remove(removeID); } // Members ArrayList responseMembers = (ArrayList)responseJSON["members"]; List validMembers = new List(); foreach (Hashtable member in responseMembers) { DiscordMember builtMember = null; String ID = (String)member["id"]; if (!String.IsNullOrEmpty(ID)) { validMembers.Add(ID); if (!Members.TryGetValue(ID, out builtMember)) { builtMember = new DiscordMember(); builtMember.ID = ID; builtMember.Name = (String)member["username"]; Members[builtMember.ID] = builtMember; } //username if (member.ContainsKey("username")) { builtMember.Name = (String)member["username"]; } // Player Object if (!builtMember.PlayerTested && _plugin._threadsReady && _plugin._firstPlayerListComplete && !_plugin._databaseConnectionCriticalState) { builtMember.PlayerTested = true; builtMember.PlayerObject = _plugin.FetchPlayer(false, false, false, null, -1, null, null, null, builtMember.ID); // Do not accept memory-only players, only those with real IDs if (builtMember.PlayerObject != null && builtMember.PlayerObject.player_id <= 0) { builtMember.PlayerObject = null; } if (builtMember.PlayerObject != null && DebugMembers) { _plugin.Log.Info("Discord member " + builtMember.Name + " loaded with link to " + builtMember.PlayerObject.GetVerboseName()); } } // Update their last usage time so they aren't purged from memory if (builtMember.PlayerObject != null) { builtMember.PlayerObject.LastUsage = _plugin.UtcNow(); } //nick if (member.ContainsKey("nick")) { // Replace their username with their nickname, since that's what client's see builtMember.Name = (String)member["nick"]; } //status if (member.ContainsKey("status")) { builtMember.Status = (String)member["status"]; } //bot if (member.ContainsKey("bot")) { builtMember.Bot = (Boolean)member["bot"]; } else { builtMember.Bot = false; } //channel_id if (member.ContainsKey("channel_id")) { Channels.TryGetValue((String)member["channel_id"], out builtMember.Channel); } else { builtMember.Channel = null; } //game if (member.ContainsKey("game")) { DiscordGame builtGame = null; Hashtable responseGame = (Hashtable)member["game"]; if (responseGame.ContainsKey("name")) { builtGame = new DiscordGame(); builtGame.Name = (String)responseGame["name"]; } builtMember.Game = builtGame; } else { builtMember.Game = null; } //mute if (member.ContainsKey("mute")) { builtMember.Mute = (Boolean)member["mute"]; } else { builtMember.Mute = false; } //self_mute if (member.ContainsKey("self_mute")) { builtMember.SelfMute = (Boolean)member["self_mute"]; } else { builtMember.SelfMute = false; } //suppress if (member.ContainsKey("suppress")) { builtMember.Suppress = (Boolean)member["suppress"]; } else { builtMember.Suppress = false; } //deaf if (member.ContainsKey("deaf")) { builtMember.Deaf = (Boolean)member["deaf"]; } else { builtMember.Deaf = false; } //self_deaf if (member.ContainsKey("self_deaf")) { builtMember.SelfDeaf = (Boolean)member["self_deaf"]; } else { builtMember.SelfDeaf = false; } //avatar_url if (member.ContainsKey("avatar_url")) { builtMember.AvatarURL = (String)member["avatar_url"]; } else { builtMember.AvatarURL = null; } //avatar if (member.ContainsKey("avatar")) { builtMember.Avatar = (String)member["avatar"]; } else { builtMember.Avatar = null; } //discriminator if (member.ContainsKey("discriminator")) { builtMember.Discriminator = (String)member["discriminator"]; } else { builtMember.Discriminator = null; } } } //Remove all old channels List removeMemberIDs = Members.Keys.ToList().Where(ID => !String.IsNullOrEmpty(ID) && !validMembers.Contains(ID)).ToList(); foreach (String removeID in removeMemberIDs) { Members.Remove(removeID); } LastUpdate = _plugin.UtcNow(); success = true; ConnectionIssueCount = 0; } catch (Exception e) { if (e is WebException) { if (++ConnectionIssueCount > 3) { _plugin.Log.Warn("Issue connecting to discord widget URL (" + ConnectionIssueCount + "): " + widgetURL); } } else { _plugin.Log.HandleException(new AException("Error while parsing discord widget data.", e)); } } } } } catch (Exception e) { _plugin.Log.HandleException(new AException("Error while updating discord server information.", e)); } _plugin.Log.Debug(() => "Exiting UpdateDiscordServerInfo", 7); return success; } public String GetWidgetURL() { if (String.IsNullOrEmpty(ServerID)) { _plugin.Log.Error("Cannot get discord widget URL, no server ID provided."); return null; } return "https://discordapp.com/api/servers/" + ServerID + "/widget.json"; } public class DiscordMember { public String ID; public String Name; public String Nick; public String Status; public DiscordChannel Channel; public DiscordGame Game; public Boolean Bot; public Boolean Mute; public Boolean SelfMute; public Boolean Suppress; public Boolean Deaf; public Boolean SelfDeaf; public String AvatarURL; public String Avatar; public String Discriminator; public Boolean PlayerTested; public APlayer PlayerObject; } public class DiscordGame { public String Name; } public class DiscordChannel { public String ID; public String Name; public Int32 Position; } // Thanks to jbrunink for this code snippet public void PostReport(ARecord record, String type, String sourceInfo, String targetInfo) { if (record.target_player == null) { _plugin.SendMessageToSource(record, "Unable to send report. No target player found."); return; } var debugString = "Sending Discord report [" + record.command_numeric + "] on " + record.GetTargetNames(); _plugin.Log.Debug(() => debugString, 3); if (_plugin._UseExperimentalTools) { _plugin.Log.Info(debugString); } String blockOpener = "```" + Environment.NewLine; String blockCloser = "```"; StringBuilder bb = new StringBuilder(); bb.Append(blockOpener); bb.Append(_plugin.GameVersion + " " + type + " [" + record.command_numeric + "]" + Environment.NewLine); bb.Append("Source: " + sourceInfo + Environment.NewLine); bb.Append("Target: " + targetInfo + Environment.NewLine); bb.Append("Reason: " + record.record_message); bb.Append(Environment.NewLine); bb.Append(record.GetTargetNames() + " rank(" + record.target_player.fbpInfo.Rank + "), score(" + record.target_player.fbpInfo.Score + "), kills(" + record.target_player.fbpInfo.Kills + "), deaths(" + record.target_player.fbpInfo.Deaths + "), k/d(" + Math.Round(record.target_player.fbpInfo.Kdr, 1) + ")" + Environment.NewLine); bb.Append(blockCloser); String body = bb.ToString(); PostToDiscord(body); } public void PostToDiscord(String body) { try { if (String.IsNullOrEmpty(URL)) { _plugin.Log.Error("Discord WebHook URL empty! Unable to post report."); return; } if (String.IsNullOrEmpty(body)) { _plugin.Log.Error("Discord note body empty! Unable to post report."); return; } if (String.IsNullOrEmpty(_plugin._shortServerName)) { _plugin.Log.Error("The 'Short Server Name' setting must be filled in before posting discord reports."); return; } WebRequest request = WebRequest.Create(URL); request.Method = "POST"; request.ContentType = "application/json"; String jsonBody = JSON.JsonEncode(new Hashtable { {"avatar_url", "https://avatars1.githubusercontent.com/u/9680130"}, {"username", "AdKats (" + _plugin._shortServerName + ")"}, {"content", body} }); byte[] byteArray = Encoding.UTF8.GetBytes(jsonBody); request.ContentLength = byteArray.Length; Stream requestStream = request.GetRequestStream(); requestStream.Write(byteArray, 0, byteArray.Length); requestStream.Close(); } catch (WebException e) { WebResponse response = e.Response; _plugin.Log.Info("RESPONSE: " + new StreamReader(response.GetResponseStream()).ReadToEnd()); _plugin.Log.HandleException(new AException("Web error posting to Discord WebHook.", e)); } catch (Exception e) { _plugin.Log.HandleException(new AException("Error posting to Discord WebHook.", e)); } } } //Directly pulled from TeamSpeak3Sync and adapted into inline library public class TeamSpeakClientViewer { public TeamSpeakClientViewer(AdKats plugin) { _plugin = plugin; try { Thread mThreadMain = new Thread(EntryMain); Thread mThreadSynchronize = new Thread(EntrySynchronization); mThreadMain.Start(); mThreadSynchronize.Start(); } catch (Exception e) { ConsoleWrite("^8A fatal error occurred on load! Procon must be restarted for TSCV to work correctly."); ConsoleWrite("^8^bMessage:^n^0 " + e.Message); ConsoleWrite("^8^bStack Trace:^n^0 " + e.StackTrace); } Ts3QueryNickname = "TeamSpeakClientViewer"; Ts3SubChannelNames = new String[] { }; } public Boolean Enabled() { return _mEnabled; } public void Enable() { ConsoleWrite("^2^bTSCV credit to Teamspeak3Sync by Imisnew2^n"); ConsoleWrite("[Enabled] ^2^bRequesting TSCV start...^n"); AddToActionQueue(Commands.ClientEnabled); } public void Disable() { ConsoleWrite("[Disabled] ^8^bRequesting TSCV stop...^n"); AddToActionQueue(Commands.ClientDisabled); if (_mTsReconnecting) { _mTsReconnEvent.Set(); } } public List GetPlayersOnTs() { return _mClientTsInfo.ToList(); } private readonly AdKats _plugin; private Boolean _mEnabled; public String Ts3ServerIp { get; set; } public UInt16 Ts3ServerPort { get; set; } public UInt16 Ts3QueryPort { get; set; } public String Ts3QueryUsername { get; set; } public String Ts3QueryPassword { get; set; } public String Ts3QueryNickname { get; set; } public String Ts3MainChannelName { get; set; } public String[] Ts3SubChannelNames { get; set; } public Boolean DebugClients { get; set; } private Object _teamspeakLocker = new Object(); public VoipJoinDisplayType JoinDisplay = VoipJoinDisplayType.Disabled; public String JoinDisplayMessage = "%playerusername% joined teamspeak! Welcome!"; public Int32 UpdateIntervalSeconds = 30; private const Int32 SynDelayQueriesAmount = 1000; private const Int32 ErrReconnectOnErrorAttempts = 20; private const Int32 ErrReconnectOnErrorInterval = 30000; public enum Commands { ClientEnabled, ClientDisabled, UpdateTsClientInfo, } public enum Queries { OpenConnectionEstablish, OpenConnectionLogin, OpenConnectionUse, OpenConnectionMain, OpenConnectionNickname, TsInfoClientList, TsInfoChannelList, TsInfoClientInfo } private readonly Mutex _mActionMutex = new Mutex(); private readonly Semaphore _mActionSemaphore = new Semaphore(0, Int32.MaxValue); private Queue _mActions = new Queue(); private readonly TeamspeakConnection _mTsConnection = new TeamspeakConnection(); private TeamspeakResponse _mTsResponse = new TeamspeakResponse("error id=0 msg=ok"); private Boolean _mTsReconnecting; private DateTime _mTsPrevSendTime = DateTime.Now; private readonly AutoResetEvent _mTsReconnEvent = new AutoResetEvent(false); private List _mClientTsInfo = new List(); private readonly TeamspeakChannel _mMainChannel = new TeamspeakChannel(); private readonly List _mPickupChannels = new List(); private ActionEvent _mCurrentAction; public class TeamspeakConnection { private static readonly TeamspeakResponse TsrOk = new TeamspeakResponse("error id=0 msg=ok"); private static readonly TeamspeakResponse TsrOpenErr1 = new TeamspeakResponse("error id=-1 msg=The\\sconnection\\swas\\sreopened\\swhen\\sthe\\sprevious\\sconnection\\swas\\sstill\\sopen."); private static readonly TeamspeakResponse TsrOpenErr2 = new TeamspeakResponse("error id=-2 msg=Invalid\\sIP\\sAddress."); private static readonly TeamspeakResponse TsrOpenErr3 = new TeamspeakResponse("error id=-3 msg=Invalid\\sPort."); private static readonly TeamspeakResponse TsrOpenErr4 = new TeamspeakResponse("error id=-4 msg=An\\serror\\soccurred\\swhen\\strying\\sto\\sestablish\\sa\\sconnection."); private static readonly TeamspeakResponse TsrSendErr1 = new TeamspeakResponse("error id=-5 msg=The\\sconnection\\swas\\sclosed\\swhen\\sa\\squery\\swas\\stried\\sto\\sbe\\ssent."); private static readonly TeamspeakResponse TsrSendErr2 = new TeamspeakResponse("error id=-6 msg=The\\squery\\sto\\sbe\\ssent\\swas\\snull."); private static readonly TeamspeakResponse TsrSendErr3 = new TeamspeakResponse("error id=-7 msg=An\\serror\\soccurred\\swhen\\sthe\\squery\\swas\\ssent."); private static readonly TeamspeakResponse TsrSendErr4 = new TeamspeakResponse("error id=-8 msg=An\\serror\\soccurred\\swhen\\sthe\\sresponse\\swas\\sreceived."); public Socket Socket; public TeamspeakConnection() { Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) { SendTimeout = 5000, ReceiveTimeout = 5000 }; } public TeamspeakResponse Open(String ip, UInt16 port) { if (Socket.Connected) { return TsrOpenErr1; } if (String.IsNullOrEmpty(ip)) { return TsrOpenErr2; } if (port == 0) { return TsrOpenErr3; } String rBuffer = String.Empty; Byte[] sBuffer = new Byte[2048]; try { Socket.Connect(ip, port); Thread.Sleep(1000); Int32 size = Socket.Receive(sBuffer, sBuffer.Length, SocketFlags.None); rBuffer += Encoding.Default.GetString(sBuffer, 0, size); if (!rBuffer.Contains("TS3")) { throw new Exception(); } } catch (Exception) { Close(); return TsrOpenErr4; } OnDataReceived(rBuffer); return rBuffer.Contains("error id=") ? new TeamspeakResponse(rBuffer) : TsrOk; } public TeamspeakResponse Close() { Socket.Close(); Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) { SendTimeout = 5000, ReceiveTimeout = 5000 }; return TsrOk; } public TeamspeakResponse Send(TeamspeakQuery query) { if (!Socket.Connected) { return TsrSendErr1; } if (query == null) { return TsrSendErr2; } String rBuffer = null; Byte[] sBuffer = null; try { rBuffer = query.RawQuery(); sBuffer = Encoding.Default.GetBytes(rBuffer); Socket.Send(sBuffer, rBuffer.Length, SocketFlags.None); } catch (Exception) { Close(); return TsrSendErr3; } OnDataSent(rBuffer); rBuffer = String.Empty; sBuffer = new Byte[65536]; DateTime start = DateTime.Now; while (!rBuffer.Contains("error id=") || !rBuffer.EndsWith("\n\r")) { try { Int32 size = Socket.Receive(sBuffer, sBuffer.Length, SocketFlags.None); rBuffer += Encoding.Default.GetString(sBuffer, 0, size); if ((DateTime.Now - start).TotalMilliseconds > 5500) { break; } } catch (Exception) { Close(); return TsrSendErr4; } } OnDataReceived(rBuffer); return new TeamspeakResponse(rBuffer); } public delegate void DataHandler(String data); public event DataHandler DataSent; public event DataHandler DataReceived; private void OnDataSent(String data) { if (DataSent != null) { DataSent(data.Trim()); } } private void OnDataReceived(String data) { if (DataReceived != null) { DataReceived(data.Trim()); } } } public class TeamspeakResponse { private String _tsRaw; private TeamspeakResponseGroup _tsError; private List _tsSections; public String RawResponse { get { return _tsRaw; } } public String Id { get { return _tsError["id"]; } } public String Message { get { return _tsError["msg"]; } } public String ExtraMessage { get { return _tsError["extra_msg"]; } } public Boolean HasSections { get { return _tsSections.Count != 0; } } public ReadOnlyCollection Sections { get { return _tsSections.AsReadOnly(); } } public TeamspeakResponse(String rawResponse) { Parse(rawResponse); } private void Parse(string raw) { _tsRaw = raw.Replace("\n", @"\n").Replace("\r", @"\r"); _tsError = new TeamspeakResponseGroup("empty"); _tsSections = new List(); foreach (String section in raw.Replace("\n\r", "\n").Split('\n')) { if (section.Contains("error id=")) { _tsError = new TeamspeakResponseGroup(section.Trim()); } else if (!String.IsNullOrEmpty(section.Trim())) { _tsSections.Add(new TeamspeakResponseSection(section.Trim())); } } } } public class TeamspeakResponseSection { private String _tsRaw; private List _tsGroups = new List(); public String RawSection { get { return _tsRaw; } } public Boolean HasGroups { get { return _tsGroups.Count != 0; } } public ReadOnlyCollection Groups { get { return _tsGroups.AsReadOnly(); } } public TeamspeakResponseSection(String rawSection) { Parse(rawSection); } private void Parse(String raw) { _tsRaw = raw; _tsGroups = new List(); foreach (String group in raw.Split('|')) { _tsGroups.Add(new TeamspeakResponseGroup(group.Trim())); } } } public class TeamspeakResponseGroup { private String _tsRaw; private Dictionary _tsPairs = new Dictionary(); public String RawGroup { get { return _tsRaw; } } public String this[String key] { get { return (_tsPairs.ContainsKey(key)) ? _tsPairs[key] : null; } } public TeamspeakResponseGroup(String rawGroup) { Parse(rawGroup); } private void Parse(String raw) { _tsRaw = raw; _tsPairs = new Dictionary(); foreach (string element in raw.Split(' ')) { if (element.Contains("=")) { String[] pair = element.Split('='); if (_tsPairs.ContainsKey(pair[0])) { _tsPairs[pair[0]] = TeamspeakHelper.ts_UnescapeString(pair[1]); } else { _tsPairs.Add(pair[0], TeamspeakHelper.ts_UnescapeString(pair[1])); } } } } } public class TeamspeakQuery { private readonly String tsCommand; private readonly Dictionary tsParameters; private readonly List tsOptions; public String Command { get { return tsCommand; } } public TeamspeakQuery(String command) { tsCommand = command; tsParameters = new Dictionary(); tsOptions = new List(); } public void AddParameter(String key, String value) { String tKey = key.Trim(); String tValue = value.Trim(); if (!String.IsNullOrEmpty(tKey) && !String.IsNullOrEmpty(tValue)) { if (!tsParameters.ContainsKey(tKey)) { tsParameters.Add(TeamspeakHelper.ts_EscapeString(tKey), TeamspeakHelper.ts_EscapeString(tValue)); } } } public void AddOption(String option) { String tOption = option.Trim(); if (!String.IsNullOrEmpty(tOption)) { tsOptions.Add(TeamspeakHelper.ts_EscapeString(tOption)); } } public void RemoveParameter(String key) { String tKey = key.Trim(); if (!String.IsNullOrEmpty(tKey)) { tsParameters.Remove(tKey); } } public void RemoveOption(String option) { String tOption = option.Trim(); if (!String.IsNullOrEmpty(tOption)) { tsOptions.Remove(tOption); } } public String RawQuery() { StringBuilder rawQuery = new StringBuilder(); rawQuery.Append(tsCommand); foreach (KeyValuePair p in tsParameters) { rawQuery.AppendFormat(" {0}={1}", p.Key, p.Value); } foreach (String o in tsOptions) { rawQuery.AppendFormat(" -{0}", o); } rawQuery.Append("\n"); return rawQuery.ToString(); } public static TeamspeakQuery BuildLoginQuery(String username, String password) { TeamspeakQuery tsLogin = new TeamspeakQuery("login"); tsLogin.AddParameter("client_login_name", username); tsLogin.AddParameter("client_login_password", password); return tsLogin; } public static TeamspeakQuery BuildChangeNicknameQuery(String newNickname) { TeamspeakQuery tsClientUpdate = new TeamspeakQuery("clientupdate"); tsClientUpdate.AddParameter("client_nickname", newNickname); return tsClientUpdate; } public static TeamspeakQuery BuildServerListQuery() { return new TeamspeakQuery("serverlist"); } public static TeamspeakQuery BuildUseVIdQuery(Int32 virtualId) { TeamspeakQuery tsUse = new TeamspeakQuery("use"); tsUse.AddParameter("sid", virtualId.ToString(CultureInfo.InvariantCulture)); return tsUse; } public static TeamspeakQuery BuildUsePortQuery(Int32 port) { TeamspeakQuery tsUse = new TeamspeakQuery("use"); tsUse.AddParameter("port", port.ToString(CultureInfo.InvariantCulture)); return tsUse; } public static TeamspeakQuery BuildChannelListQuery() { return new TeamspeakQuery("channellist"); } public static TeamspeakQuery BuildChannelFindQuery(String channelName) { TeamspeakQuery tsChannelFind = new TeamspeakQuery("channelfind"); tsChannelFind.AddParameter("pattern", channelName); return tsChannelFind; } public static TeamspeakQuery BuildChannelInfoQuery(Int32 channelId) { TeamspeakQuery tsChannelInfo = new TeamspeakQuery("channelinfo"); tsChannelInfo.AddParameter("cid", channelId.ToString(CultureInfo.InvariantCulture)); return tsChannelInfo; } public static TeamspeakQuery BuildClientListQuery() { return new TeamspeakQuery("clientlist"); } public static TeamspeakQuery BuildClientFindQuery(String clientName) { TeamspeakQuery tsClientFind = new TeamspeakQuery("clientfind"); tsClientFind.AddParameter("pattern", clientName); return tsClientFind; } public static TeamspeakQuery BuildClientInfoQuery(Int32 clientId) { TeamspeakQuery tsClientInfo = new TeamspeakQuery("clientinfo"); tsClientInfo.AddParameter("clid", clientId.ToString(CultureInfo.InvariantCulture)); return tsClientInfo; } public static TeamspeakQuery BuildClientMoveQuery(Int32 clientId, Int32 channelId) { TeamspeakQuery tsClientMove = new TeamspeakQuery("clientmove"); tsClientMove.AddParameter("clid", clientId.ToString(CultureInfo.InvariantCulture)); tsClientMove.AddParameter("cid", channelId.ToString(CultureInfo.InvariantCulture)); return tsClientMove; } } public class TeamspeakServer { public String TsName = null; //virtualserver_name public Int32? TsId = null; //virtualserver_id public Int32? TsPort = null; //virtualserver_port public Int32? TsMachineId = null; //virtualserver_machine_id public String TsStatus = null; //virtualserver_status public Int32? TsUpTime = null; //virtualserver_uptime public Int32? TsClientsOnline = null; //virtualserver_clientsonline public Int32? TsQueryClientsOnline = null; //virtualserver_queryclientsonline public Int32? TsQueryMaxClients = null; //virtualserver_maxclients public Boolean? TsAutoStart = null; //virtualserver_autostart public TeamspeakServer() { } public TeamspeakServer(TeamspeakResponseGroup serverInfo) { SetBasicData(serverInfo); } public void SetBasicData(TeamspeakResponseGroup serverInfo) { String value; Int32 iValue; Boolean bValue; TsName = serverInfo["virtualserver_name"]; if ((value = serverInfo["virtualserver_id"]) != null) { if (Int32.TryParse(value, out iValue)) { TsId = iValue; } else { TsId = null; } } else { TsId = null; } if ((value = serverInfo["virtualserver_port"]) != null) { if (Int32.TryParse(value, out iValue)) { TsPort = iValue; } else { TsPort = null; } } else { TsPort = null; } if ((value = serverInfo["virtualserver_machine_id"]) != null) { if (Int32.TryParse(value, out iValue)) { TsMachineId = iValue; } else { TsMachineId = null; } } else { TsMachineId = null; } TsStatus = serverInfo["virtualserver_status"]; if ((value = serverInfo["virtualserver_uptime"]) != null) { if (Int32.TryParse(value, out iValue)) { TsUpTime = iValue; } else { TsUpTime = null; } } else { TsUpTime = null; } if ((value = serverInfo["virtualserver_clientsonline"]) != null) { if (Int32.TryParse(value, out iValue)) { TsClientsOnline = iValue; } else { TsClientsOnline = null; } } else { TsClientsOnline = null; } if ((value = serverInfo["virtualserver_queryclientsonline"]) != null) { if (Int32.TryParse(value, out iValue)) { TsQueryClientsOnline = iValue; } else { TsQueryClientsOnline = null; } } else { TsQueryClientsOnline = null; } if ((value = serverInfo["virtualserver_maxclients"]) != null) { if (Int32.TryParse(value, out iValue)) { TsQueryMaxClients = iValue; } else { TsQueryMaxClients = null; } } else { TsQueryMaxClients = null; } if ((value = serverInfo["virtualserver_autostart"]) != null) { if (Boolean.TryParse(value, out bValue)) { TsAutoStart = bValue; } else { TsAutoStart = null; } } else { TsAutoStart = null; } } } public class TeamspeakChannel { public String TsName = null; //channel_name public Int32? TsId = null; //cid public Int32? MedPId = null; //pid public Int32? MedOrder = null; //channel_order public Int32? MedTotalClients = null; //total_clients public Int32? MedPowerNeededToSub = null; //channel_needed_subscribe_power public String AdvTopic = null; //channel_topic public String AdvDescription = null; //channel_description public String AdvPassword = null; //channel_password public String AdvFilepath = null; //channel_filepath public String AdvPhoneticName = null; //channel_name_phonetic public Int32? AdvCodec = null; //channel_codec public Int32? AdvCodecQuality = null; //channel_codec_quality public Int32? AdvCodecLatencyFactor = null; //channel_codec_latency_factor public Int32? AdvMaxClients = null; //channel_maxclients public Int32? AdvMaxFamilyClients = null; //channel_maxfamilyclients public Int32? AdvNeededTalkPower = null; //channel_needed_talk_power public Int32? AdvIconId = null; //channel_icon_id public Boolean? AdvFlagPermanent = null; //channel_flag_permanent public Boolean? AdvFlagSemiPermanent = null; //channel_flag_semi_permanent public Boolean? AdvFlagDefault = null; //channel_flag_default public Boolean? AdvFlagPassword = null; //channel_flag_password public Boolean? AdvFlagMaxClientsUnlimited = null; //channel_flag_maxclients_unlimited public Boolean? AdvFlagMaxFamilyClientsUnlimited = null; //channel_flag_maxfamilyclients_unlimited public Boolean? AdvFlagMaxFamilyClientsInherited = null; //channel_flag_maxfamilyclients_inherited public Boolean? AdvForcedSilence = null; //channel_forced_silence public TeamspeakChannel() { } public TeamspeakChannel(TeamspeakResponseGroup channelInfo) { SetBasicData(channelInfo); SetMediumData(channelInfo); SetAdvancedData(channelInfo); } public void SetBasicData(TeamspeakResponseGroup channelInfo) { String value; Int32 iValue; TsName = channelInfo["channel_name"]; if ((value = channelInfo["cid"]) != null) { if (Int32.TryParse(value, out iValue)) { TsId = iValue; } else { TsId = null; } } else { TsId = null; } } public void SetMediumData(TeamspeakResponseGroup channelInfo) { String value; Int32 iValue; if ((value = channelInfo["pid"]) != null) { if (Int32.TryParse(value, out iValue)) { MedPId = iValue; } else { MedPId = null; } } else { MedPId = null; } if ((value = channelInfo["channel_order"]) != null) { if (Int32.TryParse(value, out iValue)) { MedOrder = iValue; } else { MedOrder = null; } } else { MedOrder = null; } if ((value = channelInfo["total_clients"]) != null) { if (Int32.TryParse(value, out iValue)) { MedTotalClients = iValue; } else { MedTotalClients = null; } } else { MedTotalClients = null; } if ((value = channelInfo["channel_needed_subscribe_power"]) != null) { if (Int32.TryParse(value, out iValue)) { MedPowerNeededToSub = iValue; } else { MedPowerNeededToSub = null; } } else { MedPowerNeededToSub = null; } } public void SetAdvancedData(TeamspeakResponseGroup channelInfo) { String value; Int32 iValue; Boolean bValue; AdvTopic = channelInfo["channel_topic"]; AdvDescription = channelInfo["channel_description"]; AdvPassword = channelInfo["channel_password"]; AdvFilepath = channelInfo["channel_filepath"]; AdvPhoneticName = channelInfo["channel_name_phonetic"]; if ((value = channelInfo["channel_codec"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvCodec = iValue; } else { AdvCodec = null; } } else { AdvCodec = null; } if ((value = channelInfo["channel_codec_quality"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvCodecQuality = iValue; } else { AdvCodecQuality = null; } } else { AdvCodecQuality = null; } if ((value = channelInfo["channel_codec_latency_factor"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvCodecLatencyFactor = iValue; } else { AdvCodecLatencyFactor = null; } } else { AdvCodecLatencyFactor = null; } if ((value = channelInfo["channel_maxclients"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvMaxClients = iValue; } else { AdvMaxClients = null; } } else { AdvMaxClients = null; } if ((value = channelInfo["channel_maxfamilyclients"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvMaxFamilyClients = iValue; } else { AdvMaxFamilyClients = null; } } else { AdvMaxFamilyClients = null; } if ((value = channelInfo["channel_needed_talk_power"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvNeededTalkPower = iValue; } else { AdvNeededTalkPower = null; } } else { AdvNeededTalkPower = null; } if ((value = channelInfo["channel_icon_id"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvIconId = iValue; } else { AdvIconId = null; } } else { AdvIconId = null; } if ((value = channelInfo["channel_flag_permanent"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvFlagPermanent = bValue; } else { AdvFlagPermanent = null; } } else { AdvFlagPermanent = null; } if ((value = channelInfo["channel_flag_semi_permanent"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvFlagSemiPermanent = bValue; } else { AdvFlagSemiPermanent = null; } } else { AdvFlagSemiPermanent = null; } if ((value = channelInfo["channel_flag_default"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvFlagDefault = bValue; } else { AdvFlagDefault = null; } } else { AdvFlagDefault = null; } if ((value = channelInfo["channel_flag_password"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvFlagPassword = bValue; } else { AdvFlagPassword = null; } } else { AdvFlagPassword = null; } if ((value = channelInfo["channel_flag_maxclients_unlimited"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvFlagMaxClientsUnlimited = bValue; } else { AdvFlagMaxClientsUnlimited = null; } } else { AdvFlagMaxClientsUnlimited = null; } if ((value = channelInfo["channel_flag_maxfamilyclients_unlimited"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvFlagMaxFamilyClientsUnlimited = bValue; } else { AdvFlagMaxFamilyClientsUnlimited = null; } } else { AdvFlagMaxFamilyClientsUnlimited = null; } if ((value = channelInfo["channel_flag_maxfamilyclients_inherited"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvFlagMaxFamilyClientsInherited = bValue; } else { AdvFlagMaxFamilyClientsInherited = null; } } else { AdvFlagMaxFamilyClientsInherited = null; } if ((value = channelInfo["channel_forced_silence"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvForcedSilence = bValue; } else { AdvForcedSilence = null; } } else { AdvForcedSilence = null; } } } public class TeamspeakClient { public String TsName = null; //client_nickname public Int32? TsId = null; //clid public Int32? MedDatabaseId = null; //client_database_id public Int32? MedChannelId = null; //cid public String MedChannelName = null; public Int32? MedType = null; //client_type public String AdvLoginName = null; //client_login_name public String AdvUniqueId = null; //client_unique_identifier public String AdvIpAddress = null; //connection_client_ip public String AdvVersion = null; //client_version public String AdvPlatform = null; //client_platform public String AdvDescription = null; //client_description public String AdvCountry = null; //client_country public String AdvMetaData = null; //client_meta_data public Int32? AdvChannelGroupId = null; //client_channel_group_id public Int32? AdvServerGroupId = null; //client_servergroups public Boolean? AdvIsChannelCommander = null; //client_is_channel_commander public String AdvDefaultChannel = null; //client_default_channel public Int32? AdvConnectionTime = null; //connection_connected_time public Int32? AdvIdleTime = null; //client_idle_time public Int32? AdvCreationTime = null; //client_created public Int32? AdvLastConnected = null; //client_lastconnected public Int32? AdvTotalConnections = null; //client_totalconnections public Boolean? AdvInputMuted = null; //client_input_muted public Boolean? AdvOutputMuted = null; //client_output_muted public Boolean? AdvOutputMutedOnly = null; //client_outputonly_muted public Boolean? AdvInputHardware = null; //client_input_hardware public Boolean? AdvOutputHardware = null; //client_output_hardware public Boolean? AdvIsRecording = null; //client_is_recording public String AdvFlagAvatar = null; //client_flag_avatar public String AdvAwayMessage = null; //client_away_message public String AdvTalkMessage = null; //client_talk_request_msg public String AdvPhoneticNick = null; //client_nickname_phonetic public String AdvDefaultToken = null; //client_default_token public String AdvBase64Hash = null; //client_base64HashClientUID public Int32? AdvTalkPower = null; //client_talk_power public Int32? AdvQueryViewPower = null; //client_needed_serverquery_view_power public Int32? AdvUnreadMessages = null; //client_unread_messages public Int32? AdvIconId = null; //client_icon_id public Boolean? AdvIsAway = null; //client_away public Boolean? AdvTalkRequest = null; //client_talk_request public Boolean? AdvIsTalker = null; //client_is_talker public Boolean? AdvIsPriority = null; //client_is_priority_speaker public Int32? AdvBytesUpMonth = null; //client_month_bytes_uploaded public Int32? AdvBytesDownMonth = null; //client_month_bytes_downloaded public Int32? AdvBytesUpTotal = null; //client_total_bytes_uploaded public Int32? AdvBytesDownTotal = null; //client_total_bytes_downloaded public Int32? AdvFileBandwidthSent = null; //connection_filetransfer_bandwidth_sent public Int32? AdvFileBandwidthRec = null; //connection_filetransfer_bandwidth_received public Int32? AdvPacketsTotalSent = null; //connection_packets_sent_total public Int32? AdvPacketsTotalRec = null; //connection_packets_received_total public Int32? AdvBytesTotalSent = null; //connection_bytes_sent_total public Int32? AdvBytesTotalRec = null; //connection_bytes_received_total public Int32? AdvBndwdthSecondSent = null; //connection_bandwidth_sent_last_second_total public Int32? AdvBndwdthSecondRec = null; //connection_bandwidth_received_last_second_total public Int32? AdvBndwdthMinuteSent = null; //connection_bandwidth_sent_last_minute_total public Int32? AdvBndwdthMinuteRec = null; //connection_bandwidth_received_last_minute_total public TeamspeakClient() { } public TeamspeakClient(TeamspeakResponseGroup clientInfo) { SetBasicData(clientInfo); SetMediumData(clientInfo); SetAdvancedData(clientInfo); } public void SetBasicData(TeamspeakResponseGroup clientInfo) { String value; Int32 iValue; TsName = clientInfo["client_nickname"]; if ((value = clientInfo["clid"]) != null) { if (Int32.TryParse(value, out iValue)) { TsId = iValue; } else { TsId = null; } } else { TsId = null; } } public void SetMediumData(TeamspeakResponseGroup clientInfo) { String value; Int32 iValue; if ((value = clientInfo["client_database_id"]) != null) { if (Int32.TryParse(value, out iValue)) { MedDatabaseId = iValue; } else { MedDatabaseId = null; } } else { MedDatabaseId = null; } if ((value = clientInfo["cid"]) != null) { if (Int32.TryParse(value, out iValue)) { MedChannelId = iValue; } else { MedChannelId = null; } } else { MedChannelId = null; } if ((value = clientInfo["client_type"]) != null) { if (Int32.TryParse(value, out iValue)) { MedType = iValue; } else { MedType = null; } } else { MedType = null; } } public void SetAdvancedData(TeamspeakResponseGroup clientInfo) { String value; Int32 iValue; Boolean bValue; AdvLoginName = clientInfo["client_login_name"]; AdvUniqueId = clientInfo["client_unique_identifier"]; AdvIpAddress = clientInfo["connection_client_ip"]; AdvVersion = clientInfo["client_version"]; AdvPlatform = clientInfo["client_platform"]; AdvDescription = clientInfo["client_description"]; AdvCountry = clientInfo["client_country"]; AdvMetaData = clientInfo["client_meta_data"]; if ((value = clientInfo["client_channel_group_id"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvChannelGroupId = iValue; } else { AdvChannelGroupId = null; } } else { AdvChannelGroupId = null; } if ((value = clientInfo["client_servergroups"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvServerGroupId = iValue; } else { AdvServerGroupId = null; } } else { AdvServerGroupId = null; } if ((value = clientInfo["client_is_channel_commander"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvIsChannelCommander = bValue; } else { AdvIsChannelCommander = null; } } else { AdvIsChannelCommander = null; } AdvDefaultChannel = clientInfo["client_default_channel"]; if ((value = clientInfo["connection_connected_time"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvConnectionTime = iValue; } else { AdvConnectionTime = null; } } else { AdvConnectionTime = null; } if ((value = clientInfo["client_idle_time"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvIdleTime = iValue; } else { AdvIdleTime = null; } } else { AdvIdleTime = null; } if ((value = clientInfo["client_created"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvCreationTime = iValue; } else { AdvCreationTime = null; } } else { AdvCreationTime = null; } if ((value = clientInfo["client_lastconnected"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvLastConnected = iValue; } else { AdvLastConnected = null; } } else { AdvLastConnected = null; } if ((value = clientInfo["client_totalconnections"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvTotalConnections = iValue; } else { AdvTotalConnections = null; } } else { AdvTotalConnections = null; } if ((value = clientInfo["client_input_muted"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvInputMuted = bValue; } else { AdvInputMuted = null; } } else { AdvInputMuted = null; } if ((value = clientInfo["client_output_muted"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvOutputMuted = bValue; } else { AdvOutputMuted = null; } } else { AdvOutputMuted = null; } if ((value = clientInfo["client_outputonly_muted"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvOutputMutedOnly = bValue; } else { AdvOutputMutedOnly = null; } } else { AdvOutputMutedOnly = null; } if ((value = clientInfo["client_input_hardware"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvInputHardware = bValue; } else { AdvInputHardware = null; } } else { AdvInputHardware = null; } if ((value = clientInfo["client_output_hardware"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvOutputHardware = bValue; } else { AdvOutputHardware = null; } } else { AdvOutputHardware = null; } if ((value = clientInfo["client_is_recording"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvIsRecording = bValue; } else { AdvIsRecording = null; } } else { AdvIsRecording = null; } AdvFlagAvatar = clientInfo["client_flag_avatar"]; AdvAwayMessage = clientInfo["client_away_message"]; AdvTalkMessage = clientInfo["client_talke_request_msg"]; AdvPhoneticNick = clientInfo["client_nickname_phonetic"]; AdvDefaultToken = clientInfo["client_default_token"]; AdvBase64Hash = clientInfo["client_base64HashClientUID"]; if ((value = clientInfo["client_talk_power"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvTalkPower = iValue; } else { AdvTalkPower = null; } } else { AdvTalkPower = null; } if ((value = clientInfo["client_needed_serverquery_view_power"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvQueryViewPower = iValue; } else { AdvQueryViewPower = null; } } else { AdvQueryViewPower = null; } if ((value = clientInfo["client_unread_messages"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvUnreadMessages = iValue; } else { AdvUnreadMessages = null; } } else { AdvUnreadMessages = null; } if ((value = clientInfo["client_icon_id"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvIconId = iValue; } else { AdvIconId = null; } } else { AdvIconId = null; } if ((value = clientInfo["client_away"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvIsAway = bValue; } else { AdvIsAway = null; } } else { AdvIsAway = null; } if ((value = clientInfo["client_talk_request"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvTalkRequest = bValue; } else { AdvTalkRequest = null; } } else { AdvTalkRequest = null; } if ((value = clientInfo["client_is_talker"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvIsTalker = bValue; } else { AdvIsTalker = null; } } else { AdvIsTalker = null; } if ((value = clientInfo["client_is_priority_speaker"]) != null) { if (Boolean.TryParse(value, out bValue)) { AdvIsPriority = bValue; } else { AdvIsPriority = null; } } else { AdvIsPriority = null; } if ((value = clientInfo["client_month_bytes_uploaded"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvBytesUpMonth = iValue; } else { AdvBytesUpMonth = null; } } else { AdvBytesUpMonth = null; } if ((value = clientInfo["client_month_bytes_downloaded"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvBytesDownMonth = iValue; } else { AdvBytesDownMonth = null; } } else { AdvBytesDownMonth = null; } if ((value = clientInfo["client_total_bytes_uploaded"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvBytesUpTotal = iValue; } else { AdvBytesUpTotal = null; } } else { AdvBytesUpTotal = null; } if ((value = clientInfo["client_total_bytes_downloaded"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvBytesDownTotal = iValue; } else { AdvBytesDownTotal = null; } } else { AdvBytesDownTotal = null; } if ((value = clientInfo["connection_filetransfer_bandwidth_sent"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvFileBandwidthSent = iValue; } else { AdvFileBandwidthSent = null; } } else { AdvFileBandwidthSent = null; } if ((value = clientInfo["connection_filetransfer_bandwidth_received"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvFileBandwidthRec = iValue; } else { AdvFileBandwidthRec = null; } } else { AdvFileBandwidthRec = null; } if ((value = clientInfo["connection_packets_sent_total"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvPacketsTotalSent = iValue; } else { AdvUnreadMessages = null; } } else { AdvUnreadMessages = null; } if ((value = clientInfo["connection_packets_received_total"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvPacketsTotalSent = iValue; } else { AdvPacketsTotalSent = null; } } else { AdvPacketsTotalSent = null; } if ((value = clientInfo["connection_bytes_sent_total"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvBytesTotalSent = iValue; } else { AdvBytesTotalSent = null; } } else { AdvBytesTotalSent = null; } if ((value = clientInfo["connection_bytes_received_total"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvBytesTotalRec = iValue; } else { AdvBytesTotalRec = null; } } else { AdvBytesTotalRec = null; } if ((value = clientInfo["connection_bandwidth_sent_last_second_total"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvBndwdthSecondSent = iValue; } else { AdvBndwdthSecondSent = null; } } else { AdvBndwdthSecondSent = null; } if ((value = clientInfo["connection_bandwidth_received_last_second_total"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvBndwdthSecondRec = iValue; } else { AdvBndwdthSecondRec = null; } } else { AdvBndwdthSecondRec = null; } if ((value = clientInfo["connection_bandwidth_sent_last_minute_total"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvBndwdthMinuteSent = iValue; } else { AdvBndwdthMinuteSent = null; } } else { AdvBndwdthMinuteSent = null; } if ((value = clientInfo["connection_bandwidth_received_last_minute_total"]) != null) { if (Int32.TryParse(value, out iValue)) { AdvBndwdthMinuteRec = iValue; } else { AdvBndwdthMinuteRec = null; } } else { AdvBndwdthMinuteRec = null; } } } private static class TeamspeakHelper { public static String ts_EscapeString(String text) { String escaped = text.Replace("\\", @"\\"); escaped = escaped.Replace("/", @"\/"); escaped = escaped.Replace(" ", @"\s"); escaped = escaped.Replace("|", @"\p"); escaped = escaped.Replace("\a", @"\a"); escaped = escaped.Replace("\b", @"\b"); escaped = escaped.Replace("\f", @"\f"); escaped = escaped.Replace("\n", @"\n"); escaped = escaped.Replace("\r", @"\r"); escaped = escaped.Replace("\t", @"\t"); escaped = escaped.Replace("\v", @"\v"); return escaped; } public static String ts_UnescapeString(String text) { String unescaped = text.Replace(@"\\", "\\"); unescaped = unescaped.Replace(@"\/", "/"); unescaped = unescaped.Replace(@"\s", " "); unescaped = unescaped.Replace(@"\p", "|"); unescaped = unescaped.Replace(@"\a", "\a"); unescaped = unescaped.Replace(@"\b", "\b"); unescaped = unescaped.Replace(@"\f", "\f"); unescaped = unescaped.Replace(@"\n", "\n"); unescaped = unescaped.Replace(@"\r", "\r"); unescaped = unescaped.Replace(@"\t", "\t"); unescaped = unescaped.Replace(@"\v", "\v"); return unescaped; } } public class ActionEvent { private readonly Commands command = 0; private Int32 _argsIndex; private readonly List args = new List(); public Commands Command { get { return command; } } public Object Argument { get { return args[(_argsIndex == args.Count) ? (_argsIndex = 1) - 1 : _argsIndex++]; } } public ActionEvent(Commands command, Object[] args) { this.command = command; foreach (Object arg in args) { this.args.Add(arg); } } } private void SetPluginState(Boolean state) { if (state) { Enable(); } else { Disable(); } } private void ConsoleWrite(String message, params Object[] args) { _plugin.ExecuteCommand("procon.protected.pluginconsole.write", String.Format("[TSCV] " + message, args)); } private void DebugWrite(Boolean debug, String message, params Object[] args) { if (debug) { ConsoleWrite(message, args); } } private void DataSent(String data) { } private void DataReceived(String data) { } private void EntryMain() { _mTsConnection.DataSent += DataSent; _mTsConnection.DataReceived += DataReceived; while (true) { _mActionSemaphore.WaitOne(); _mActionMutex.WaitOne(); _mCurrentAction = _mActions.Dequeue(); _mActionMutex.ReleaseMutex(); // If we are disabled, and the incoming command can't change that, skip processing if (!_mEnabled && _mCurrentAction.Command != Commands.ClientEnabled && _mCurrentAction.Command != Commands.ClientDisabled) { continue; } try { switch (_mCurrentAction.Command) { case Commands.ClientEnabled: PerformOpenConnection(); break; case Commands.ClientDisabled: PerformCloseConnection(); break; case Commands.UpdateTsClientInfo: UpdateTsInfo(); break; } } catch (Exception e) { ConsoleWrite("^8A fatal error occurred during processing a command!"); ConsoleWrite("^8^bMessage:^n^0 " + e.Message); ConsoleWrite("^8^bStack Trace:^n^0 " + e.StackTrace); SetPluginState(false); } } } private void EntrySynchronization() { while (true) { if (_mEnabled && !_mTsReconnecting) { AddToActionQueue(Commands.UpdateTsClientInfo); } Thread.Sleep(UpdateIntervalSeconds * 1000); } } private void PerformOpenConnection() { for (int secondsSlept = 0; secondsSlept < 10 && Ts3ServerIp == "Teamspeak Ip"; secondsSlept++) { Thread.Sleep(1000); } ConsoleWrite("[Connection] Establishing a connection to a Teamspeak 3 Server."); _mTsResponse = _mTsConnection.Open(Ts3ServerIp, Ts3QueryPort); if (!PerformResponseHandling(Queries.OpenConnectionEstablish)) { return; } ConsoleWrite("[Connection] ^2Established a connection to {0}:{1}.", Ts3ServerIp, Ts3QueryPort); ConsoleWrite("[Connection] Attempting to login as a Server Query Client."); SendTeamspeakQuery(TeamspeakQuery.BuildLoginQuery(Ts3QueryUsername, Ts3QueryPassword)); if (!PerformResponseHandling(Queries.OpenConnectionLogin)) { return; } ConsoleWrite("[Connection] ^2Logged in as {0}.", Ts3QueryUsername); ConsoleWrite("[Connection] Attempting to select the correct virtual server."); SendTeamspeakQuery(TeamspeakQuery.BuildUsePortQuery(Ts3ServerPort)); if (!PerformResponseHandling(Queries.OpenConnectionUse)) { return; } ConsoleWrite("[Connection] ^2Selected the virtual server using port {0}.", Ts3ServerPort); ConsoleWrite("[Connection] Attempting to find the main channel."); SendTeamspeakQuery(TeamspeakQuery.BuildChannelFindQuery(Ts3MainChannelName)); if (!PerformResponseHandling(Queries.OpenConnectionMain)) { return; } _mMainChannel.SetBasicData(_mTsResponse.Sections[0].Groups[0]); ConsoleWrite("[Connection] ^2Found the channel named {0}.", _mMainChannel.TsName); ConsoleWrite("[Connection] Attempting to alter the Server Query Client's name."); SendTeamspeakQuery(TeamspeakQuery.BuildChangeNicknameQuery(Ts3QueryNickname)); if (!PerformResponseHandling(Queries.OpenConnectionNickname)) { return; } if (_mTsResponse.Id != "513") { ConsoleWrite("[Connection] ^2Changed the Server Query Client's name to {0}.", Ts3QueryNickname); } _mTsResponse = new TeamspeakResponse("error id=0 msg=ok"); ConsoleWrite("[Connection] Attempting to find existing pickup, team, and squad channels."); SendTeamspeakQuery(TeamspeakQuery.BuildChannelListQuery()); List tsChannels = new List(); foreach (TeamspeakResponseSection tsResponseSection in _mTsResponse.Sections) { foreach (TeamspeakResponseGroup tsResponseGroup in tsResponseSection.Groups) { tsChannels.Add(new TeamspeakChannel(tsResponseGroup)); } } foreach (TeamspeakChannel tsChannel in tsChannels) { foreach (String tsName in Ts3SubChannelNames) { if (tsChannel.TsName == tsName) { _mPickupChannels.Add(tsChannel); ConsoleWrite("[Connection] ^2Found ^bPickup^n Channel: {0} ({1}).", tsChannel.TsName, tsChannel.TsId); break; } } } ConsoleWrite("[Connection] TSCV started."); _mEnabled = true; } private void PerformCloseConnection() { ConsoleWrite("[Closing] Shutting down TSCV."); _mTsConnection.Close(); ConsoleWrite("[Closing] Cleaning up resources."); _mClientTsInfo.Clear(); _mPickupChannels.Clear(); _mTsResponse = new TeamspeakResponse("error id=0 msg=ok"); _mCurrentAction = null; ConsoleWrite("[Closing] TSCV stopped."); _mEnabled = false; } private Boolean PerformResponseHandling(Queries queryCode) { if (_mTsResponse.Id == "0") { return true; } switch (_mTsResponse.Id) { case "-1": // Socket was open and we tried to re-establish a connection. case "-5": // Socket was closed and we tried to send a query. case "-6": // The query we tried to send was null. ConsoleWrite("[Error] ^3Minor Error:"); ConsoleWrite("[Error] ^3{0}: {1}", _mTsResponse.Id, _mTsResponse.Message); return true; case "-2": // Invalid IP Address. case "-3": // Invalid Port. case "-4": // Error occurred when trying to establish a connection. ConsoleWrite("[Error] ^8Fatal Error:"); ConsoleWrite("[Error] ^8An error occurred during establishing a connection to the Teamspeak 3 Server."); ConsoleWrite("[Error] ^8Make sure your ^b\"Server Ip\"^n and ^b\"Query Port\"^n are correct."); ConsoleWrite("[Error] ^8{0}: {1}", _mTsResponse.Id, _mTsResponse.Message); if (!_mTsReconnecting && PerformReconnect()) { return true; } SetPluginState(false); return false; case "-7": // Error occurred during sending the query. case "-8": // Error occurred during receiving the response. ConsoleWrite("[Error] ^8Fatal Error:"); ConsoleWrite("[Error] ^8An error occurred during sending and receiving data to the Teamspeak 3 Server."); ConsoleWrite("[Error] ^8{0}: {1}", _mTsResponse.Id, _mTsResponse.Message); if (!_mTsReconnecting && PerformReconnect()) { break; } SetPluginState(false); return false; case "3329": // You are temp banned from the server for flooding. case "3331": // You are temp banned from the server for 'x' seconds. ConsoleWrite("[Error] ^8Fatal Error:"); ConsoleWrite("[Error] ^8You were temporarily banned from the Teamspeak 3 Server for flooding."); ConsoleWrite("[Error] ^8Make sure your ^bProcon's Ip^n is in your ^bTeamspeak 3 Server's Whitelist^n."); ConsoleWrite("[Error] ^8{0}: {1} ({2})", _mTsResponse.Id, _mTsResponse.Message, _mTsResponse.ExtraMessage); SetPluginState(false); return false; } switch (queryCode) { case Queries.OpenConnectionEstablish: ConsoleWrite("[Error] ^8Fatal Error:"); ConsoleWrite("[Error] ^8An error occurred during establishing a connection to the Teamspeak 3 Server."); ConsoleWrite("[Error] ^8Make sure your ^b\"Server Ip\"^n and ^b\"Query Port\"^n are correct."); ConsoleWrite("[Error] ^8{0}: {1}", _mTsResponse.Id, _mTsResponse.Message); SetPluginState(false); return false; case Queries.OpenConnectionLogin: ConsoleWrite("[Error] ^8Fatal Error:"); ConsoleWrite("[Error] ^8An error occurred during logging into the Teamspeak 3 Server."); ConsoleWrite("[Error] ^8Make sure your ^b\"Query Username\"^n and ^b\"Query Password\"^n are correct."); ConsoleWrite("[Error] ^8{0}: {1}", _mTsResponse.Id, _mTsResponse.Message); SetPluginState(false); return false; case Queries.OpenConnectionUse: ConsoleWrite("[Error] ^8Fatal Error:"); ConsoleWrite("[Error] ^8An error occurred during finding the virtual server."); ConsoleWrite("[Error] ^8Make sure your ^b\"Server Port\"^n is correct."); ConsoleWrite("[Error] ^8{0}: {1}", _mTsResponse.Id, _mTsResponse.Message); SetPluginState(false); return false; case Queries.OpenConnectionMain: ConsoleWrite("[Error] ^8Fatal Error:"); ConsoleWrite("[Error] ^8An error occurred during finding the main channel."); ConsoleWrite("[Error] ^8Make sure your ^b\"Main Channel Name\"^n is correct and that the channel exists in the Teamspeak 3 Server."); ConsoleWrite("[Error] ^8{0}: {1}", _mTsResponse.Id, _mTsResponse.Message); SetPluginState(false); return false; case Queries.OpenConnectionNickname: ConsoleWrite("[Error] ^3Minor Error:"); ConsoleWrite("[Error] ^3An error occurred during changing the server query nickname."); ConsoleWrite("[Error] ^3Make sure your ^b\"Query Nickname\"^n is not already in use."); ConsoleWrite("[Error] ^3{0}: {1}", _mTsResponse.Id, _mTsResponse.Message); return true; case Queries.TsInfoClientList: ConsoleWrite("[Error] ^3Minor Error - Update Teamspeak Information:"); ConsoleWrite("[Error] ^3An error occurred during obtaining the Teamspeak Client List."); ConsoleWrite("[Error] ^3{0}: {1}", _mTsResponse.Id, _mTsResponse.Message); return false; case Queries.TsInfoChannelList: ConsoleWrite("[Error] ^3Minor Error - Update Teamspeak Information:"); ConsoleWrite("[Error] ^3An error occurred during obtaining the Teamspeak Channel List."); ConsoleWrite("[Error] ^3{0}: {1}", _mTsResponse.Id, _mTsResponse.Message); return false; case Queries.TsInfoClientInfo: ConsoleWrite("[Error] ^3Minor Error - Update Teamspeak Information:"); ConsoleWrite("[Error] ^3An error occurred during obtaining an Advanced Client Information."); ConsoleWrite("[Error] ^3{0}: {1}", _mTsResponse.Id, _mTsResponse.Message); return true; } return true; } private Boolean PerformReconnect() { ConsoleWrite("[Reconnect] Attempting to establish a new connection to the Teamspeak 3 Server."); _mTsReconnecting = true; for (int attempt = 1; attempt <= ErrReconnectOnErrorAttempts; attempt++) { if (attempt != 1) { _mTsReconnEvent.WaitOne(ErrReconnectOnErrorInterval); } _mActionMutex.WaitOne(); ActionEvent tAction = (_mActions.Count == 0) ? null : _mActions.Peek(); _mActionMutex.ReleaseMutex(); if (tAction == null || tAction.Command != Commands.ClientDisabled) { _mTsConnection.Close(); PerformOpenConnection(); if (_mTsResponse.Id == "0") { _mTsReconnecting = false; return true; } ConsoleWrite("[Reconnect] Failed {0}.", (attempt < ErrReconnectOnErrorAttempts) ? ("attempt " + attempt + " out of " + ErrReconnectOnErrorAttempts) : ("the last attempt.")); } else { attempt = ErrReconnectOnErrorAttempts + 1; } } _mTsReconnecting = false; return false; } private void UpdateTsInfo() { List clientInfo = new List(); SendTeamspeakQuery(TeamspeakQuery.BuildClientListQuery()); if (!PerformResponseHandling(Queries.TsInfoClientList)) { return; } foreach (TeamspeakResponseSection sec in _mTsResponse.Sections) { foreach (TeamspeakResponseGroup grp in sec.Groups) { clientInfo.Add(new TeamspeakClient(grp)); } } List channelInfo = new List(); SendTeamspeakQuery(TeamspeakQuery.BuildChannelListQuery()); if (!PerformResponseHandling(Queries.TsInfoChannelList)) { return; } foreach (TeamspeakResponseSection sec in _mTsResponse.Sections) { foreach (TeamspeakResponseGroup grp in sec.Groups) { channelInfo.Add(new TeamspeakChannel(grp)); } } for (int i = 0; i < clientInfo.Count; i++) { Boolean inChannel = false; if (clientInfo[i].MedChannelId == _mMainChannel.TsId) { inChannel = true; } foreach (TeamspeakChannel pickupChannel in _mPickupChannels) { if (clientInfo[i].MedChannelId == pickupChannel.TsId) { inChannel = true; } } if (!inChannel) { clientInfo.RemoveAt(i--); } } for (int i = 0; i < clientInfo.Count; i++) { int? tsId = clientInfo[i].TsId; if (tsId != null) { SendTeamspeakQuery(TeamspeakQuery.BuildClientInfoQuery(tsId.Value)); } if (!PerformResponseHandling(Queries.TsInfoClientInfo)) { return; } if (_mTsResponse.Id != "0") { continue; } if (!_mTsResponse.HasSections || !_mTsResponse.Sections[0].HasGroups) { continue; } clientInfo[i].SetAdvancedData(_mTsResponse.Sections[0].Groups[0]); } for (int i = 0; i < clientInfo.Count; i++) { if (clientInfo[i].AdvIpAddress == null) { clientInfo.RemoveAt(i--); } } _mClientTsInfo = clientInfo; //Log.Debug(() => DbgClients, "[Clients] Result of Teamspeak Client Update:"); //foreach (TeamspeakClient tsClient in _mClientTsInfo) //Log.Debug(() => DbgClients, "- TS Client [Ip: {0}, Channel: {1}, Name: {2}]", tsClient.AdvIpAddress, tsClient.MedChannelId, tsClient.TsName); } private void AddToActionQueue(Commands command, params Object[] arguments) { _mActionMutex.WaitOne(); if (command == Commands.ClientEnabled || command == Commands.ClientDisabled) { Queue tNew = new Queue(); while (_mActions.Count > 0 && (_mActions.Peek().Command == Commands.ClientEnabled || _mActions.Peek().Command == Commands.ClientDisabled)) { tNew.Enqueue(_mActions.Dequeue()); } Boolean tRelease = tNew.Count == 0; tNew.Clear(); tNew.Enqueue(new ActionEvent(command, arguments)); while (_mActions.Count > 0) { tNew.Enqueue(_mActions.Dequeue()); } _mActions = tNew; if (tRelease) { _mActionSemaphore.Release(); } } else { _mActions.Enqueue(new ActionEvent(command, arguments)); _mActionSemaphore.Release(); } _mActionMutex.ReleaseMutex(); } private void SendTeamspeakQuery(TeamspeakQuery query) { TimeSpan delay = TimeSpan.FromMilliseconds(SynDelayQueriesAmount); TimeSpan delta = DateTime.Now - _mTsPrevSendTime; if (delta <= delay) { Thread.Sleep(delay - delta); } _mTsResponse = _mTsConnection.Send(query); _mTsPrevSendTime = DateTime.Now; } } public void ExecuteCommandWithDelay(Int32 milliseconds, params string[] words) { Thread.Sleep(milliseconds); ExecuteCommand(words); } } }