Exemple #1
0
    public void UpdateSettingWarnings() => MainThreadQueue.Enqueue(() =>
    {
        if (SettingWarning == null)
        {
            return;
        }

        bool demandSettingChanged = DemandBasedSettingCache != settings.DemandBasedModLoading;
        bool demandModsDisabled   = DemandBasedLoading.DisabledModsCount >= 50;

        bool warningsEnabled = CurrentState == KMGameInfo.State.Setup && modConfig.SuccessfulRead;

        CaseGeneratorWarning.SetActive(warningsEnabled && CaseGeneratorSettingCache != settings.CaseGenerator);

        DBMLWarning.SetActive(warningsEnabled && (demandSettingChanged || demandModsDisabled));
        DBMLWarning.Traverse <Text>("WarningText").text = $"The { (demandSettingChanged ? "change to the setting \"DemandBasedModLoading\"" : "") + (demandSettingChanged && demandModsDisabled ? " and " : "") + (demandModsDisabled ? $"{DemandBasedLoading.DisabledModsCount} mod{(DemandBasedLoading.DisabledModsCount == 1 ? "" : "s")} that were automatically disabled" : "") } will only take effect once you enter the Mod Manager. <i>Press \"F2\" to do that automatically!</i>";

        HoldablesWarning.SetActive(warningsEnabled && HoldablesSettingCache.Count != settings.Holdables.Count || HoldablesSettingCache.Except(settings.Holdables).Any());

        SettingWarningEnabled = new[] { CaseGeneratorWarning, DBMLWarning, HoldablesWarning }.Any(warning => warning.activeSelf);
    });
Exemple #2
0
    public void Awake()
    {
        Instance = this;

        MainThreadQueue.Initialize();

        GameInfo            = GetComponent <KMGameInfo>();
        SettingWarning      = gameObject.Traverse("UI", "SettingWarning");
        AdvantageousWarning = gameObject.Traverse("UI", "AdvantageousWarning");
        Tips.TipMessage     = gameObject.Traverse("UI", "TipMessage");
        BetterCasePicker.BombCaseGenerator = GetComponentInChildren <BombCaseGenerator>();
        DemandBasedLoading.LoadingScreen   = gameObject.Traverse <CanvasGroup>("UI", "LoadingModules");

        CaseGeneratorWarning = MakeSettingWarning("CaseGenerator");
        DBMLWarning          = MakeSettingWarning("DemandBasedModLoading");
        HoldablesWarning     = MakeSettingWarning("Holdables");

        modConfig = new ModConfig <TweakSettings>("TweakSettings", OnReadError);
        UpdateSettings();
        StartCoroutine(Modes.LoadDefaultSettings());

        DemandBasedLoading.EverLoadedModules = !settings.DemandBasedModLoading;
        DemandBasedSettingCache = settings.DemandBasedModLoading;

        HoldablesSettingCache = settings.Holdables;

        bool changeFadeTime = settings.FadeTime >= 0;

        FreeplayDevice.MAX_SECONDS_TO_SOLVE = float.MaxValue;
        FreeplayDevice.MIN_MODULE_COUNT     = 1;

        if (settings.EnableModsOnlyKey)
        {
            var lastFreeplaySettings = FreeplaySettings.CreateDefaultFreeplaySettings();
            lastFreeplaySettings.OnlyMods = true;
            ProgressionManager.Instance.RecordLastFreeplaySettings(lastFreeplaySettings);
        }

        UpdateSettingWarnings();
        AdvantageousWarning.SetActive(false);

        // Setup API/properties other mods to interact with
        GameObject infoObject = new GameObject("Tweaks_Info", typeof(TweaksProperties));

        infoObject.transform.parent = gameObject.transform;

        TweaksAPI.Setup();

        // Watch the TweakSettings file for Time Mode state being changed in the office.
        FileSystemWatcher watcher = new FileSystemWatcher(Path.Combine(Application.persistentDataPath, "Modsettings"), "TweakSettings.json")
        {
            NotifyFilter = NotifyFilters.LastWrite
        };

        watcher.Changed += (object source, FileSystemEventArgs e) =>
        {
            if (ModConfig <TweakSettings> .SerializeSettings(userSettings) == ModConfig <TweakSettings> .SerializeSettings(modConfig.Read()))
            {
                return;
            }

            UpdateSettings();
            UpdateSettingWarnings();

            MainThreadQueue.Enqueue(() => StartCoroutine(ModifyFreeplayDevice(false)));
        };

        // Setup the leaderboard controller to block the leaderboard submission requests.
        LeaderboardController.Install();

        GetTweaks();

        // Create a fake case with a bunch of anchors to trick the game when using CaseGenerator.
        TweaksCaseGeneratorCase = new GameObject("TweaksCaseGenerator");
        TweaksCaseGeneratorCase.transform.SetParent(transform);
        var kmBomb = TweaksCaseGeneratorCase.AddComponent <KMBomb>();

        kmBomb.IsHoldable      = false;
        kmBomb.WidgetAreas     = new List <GameObject>();
        kmBomb.visualTransform = transform;
        kmBomb.Faces           = new List <KMBombFace>();

        TweaksCaseGeneratorCase.AddComponent <ModBomb>();

        var kmBombFace = TweaksCaseGeneratorCase.AddComponent <KMBombFace>();

        kmBombFace.Anchors = new List <Transform>();
        kmBomb.Faces.Add(kmBombFace);

        for (int i = 0; i <= 9001; i++)
        {
            kmBombFace.Anchors.Add(transform);
        }

        // Handle scene changes
        UnityEngine.SceneManagement.SceneManager.sceneLoaded += (Scene scene, LoadSceneMode _) =>
        {
            UpdateSettings();
            UpdateSettingWarnings();

            Modes.settings = Modes.modConfig.Read();
            Modes.modConfig.Write(Modes.settings);

            if ((scene.name == "mainScene" || scene.name == "gameplayScene") && changeFadeTime)
            {
                SceneManager.Instance.RapidFadeInTime = settings.FadeTime;
            }

            switch (scene.name)
            {
            case "mainScene":
                if (changeFadeTime)
                {
                    SceneManager.Instance.SetupState.FadeInTime          =
                        SceneManager.Instance.SetupState.FadeOutTime     =
                            SceneManager.Instance.UnlockState.FadeInTime = settings.FadeTime;
                }

                break;

            case "gameplayLoadingScene":
                var gameplayLoadingManager = FindObjectOfType <GameplayLoadingManager>();
                if (settings.InstantSkip)
                {
                    gameplayLoadingManager.MinTotalLoadTime = 0;
                }
                if (changeFadeTime)
                {
                    gameplayLoadingManager.FadeInTime      =
                        gameplayLoadingManager.FadeOutTime = settings.FadeTime;
                }

                ReflectedTypes.UpdateTypes();

                ReflectedTypes.CurrencyAPIEndpointField?.SetValue(null, settings.FixFER ? "http://api.exchangeratesapi.io" : "http://api.fixer.io");

                if (
                    AdvantageousFeaturesEnabled &&
                    GameplayState.MissionToLoad != Assets.Scripts.Missions.FreeplayMissionGenerator.FREEPLAY_MISSION_ID &&
                    GameplayState.MissionToLoad != ModMission.CUSTOM_MISSION_ID
                    )
                {
                    StartCoroutine(ShowAdvantageousWarning());
                }

                break;

            case "gameplayScene":
                if (changeFadeTime)
                {
                    SceneManager.Instance.GameplayState.FadeInTime      =
                        SceneManager.Instance.GameplayState.FadeOutTime = settings.FadeTime;
                }

                break;
            }
        };

        // Handle state changes
        GameInfo.OnStateChange += (KMGameInfo.State state) =>
        {
            OnStateChanged(CurrentState, state);

            // Transitioning away from another state
            if (state == KMGameInfo.State.Transitioning)
            {
                if (CurrentState == KMGameInfo.State.Setup)
                {
                    DemandBasedLoading.DisabledModsCount = 0;
                }

                if (CurrentState != KMGameInfo.State.Gameplay)
                {
                    DemandBasedLoading.HandleTransitioning();
                }
            }

            CurrentState = state;
            watcher.EnableRaisingEvents = state == KMGameInfo.State.Setup;

            if (state == KMGameInfo.State.Gameplay)
            {
                if (AdvantageousFeaturesEnabled)
                {
                    LeaderboardController.DisableLeaderboards();
                }

                TwitchPlaysActiveCache = TwitchPlaysActive;
                CurrentModeCache       = CurrentMode;

                BombStatus.Instance.widgetsActivated = false;
                BombStatus.Instance.HUD.SetActive(settings.BombHUD);
                BombStatus.Instance.ConfidencePrefab.gameObject.SetActive(CurrentMode != Mode.Zen);
                BombStatus.Instance.StrikesPrefab.color = CurrentMode == Mode.Time ? Color.yellow : Color.red;

                Modes.Multiplier = Modes.settings.TimeModeStartingMultiplier;
                BombStatus.Instance.UpdateMultiplier();
                bombWrappers.Clear();
                StartCoroutine(CheckForBombs());

                if (GameplayState.BombSeedToUse == -1)
                {
                    GameplayState.BombSeedToUse = settings.MissionSeed;
                }
            }
            else if (state == KMGameInfo.State.Setup)
            {
                if (ReflectedTypes.LoadedModsField.GetValue(ModManager.Instance) is Dictionary <string, Mod> loadedMods)
                {
                    Mod tweaksMod = loadedMods.Values.FirstOrDefault(mod => mod.ModID == "Tweaks");
                    if (tweaksMod != null && CaseGeneratorSettingCache != settings.CaseGenerator)
                    {
                        if (settings.CaseGenerator)
                        {
                            tweaksMod.ModObjects.Add(TweaksCaseGeneratorCase);
                        }
                        else
                        {
                            tweaksMod.ModObjects.Remove(TweaksCaseGeneratorCase);
                        }

                        CaseGeneratorSettingCache = settings.CaseGenerator;
                    }
                }

                StartCoroutine(Tips.ShowTip());
                StartCoroutine(ModifyFreeplayDevice(true));
                StartCoroutine(ModifyHoldables());
                GetComponentInChildren <ModSelectorExtension>().FindAPI();
                TweaksAPI.SetTPProperties(!TwitchPlaysActive);

                Patching.EnsurePatch("LogfileViewerHotkey", typeof(LogfileUploaderPatch));

                GameplayState.BombSeedToUse = -1;

                UpdateSettingWarnings();

                UpdateBombCreator();
            }
            else if (state == KMGameInfo.State.Transitioning)
            {
                // Because the settings are checked on a scene change and there is no scene change from exiting the gameplay room,
                // we need to update the settings here in case the user changed their HideTOC settings.
                UpdateSettings();

                bool modified       = false;
                var  ModMissionToCs = ModManager.Instance.ModMissionToCs;
                foreach (var metaData in ModMissionToCs)
                {
                    modified |= ModToCMetaData.Add(metaData);
                }

                var unloadedMods = (Dictionary <string, Mod>)ReflectedTypes.UnloadedModsField.GetValue(ModManager.Instance);
                if (unloadedMods != null)
                {
                    foreach (var unloadedMod in unloadedMods.Values)
                    {
                        var tocs = (List <ModTableOfContentsMetaData>)ReflectedTypes.TocsField.GetValue(unloadedMod);
                        if (tocs != null)
                        {
                            foreach (var metaData in tocs)
                            {
                                modified |= ModToCMetaData.Remove(metaData);
                            }
                        }
                    }
                }

                var newToCs = ModToCMetaData.Where(metaData => !settings.HideTOC.Any(pattern => Localization.GetLocalizedString(metaData.DisplayNameTerm).Like(pattern)));
                modified |= newToCs.Count() != ModMissionToCs.Count || !newToCs.All(ModMissionToCs.Contains);
                ModMissionToCs.Clear();
                ModMissionToCs.AddRange(newToCs);

                if (modified)
                {
                    SetupState.LastBombBinderTOCIndex = 0;
                    SetupState.LastBombBinderTOCPage  = 0;
                }
            }
        };
    }
 void UpdateSettingWarning() => MainThreadQueue.Enqueue(() => SettingWarning.SetActive(CurrentState == KMGameInfo.State.Setup && CaseGeneratorSettingCache != settings.CaseGenerator));
    public void Awake()
    {
        MainThreadQueue.Initialize();

        GameInfo       = GetComponent <KMGameInfo>();
        SettingWarning = transform.Find("UI").Find("SettingWarning").gameObject;
        BetterCasePicker.BombCaseGenerator = GetComponentInChildren <BombCaseGenerator>();

        modConfig = new ModConfig <TweakSettings>("TweakSettings");
        UpdateSettings();

        bool changeFadeTime = settings.FadeTime >= 0;

        FreeplayDevice.MAX_SECONDS_TO_SOLVE = float.MaxValue;
        FreeplayDevice.MIN_MODULE_COUNT     = 1;

        if (settings.EnableModsOnlyKey)
        {
            var lastFreeplaySettings = FreeplaySettings.CreateDefaultFreeplaySettings();
            lastFreeplaySettings.OnlyMods = true;
            ProgressionManager.Instance.RecordLastFreeplaySettings(lastFreeplaySettings);
        }

        UpdateSettingWarning();

        // Setup API/properties other mods to interact with
        GameObject infoObject = new GameObject("Tweaks_Info", typeof(TweaksProperties));

        infoObject.transform.parent = gameObject.transform;

        // Watch the TweakSettings file for Time Mode state being changed in the office.
        FileSystemWatcher watcher = new FileSystemWatcher(Path.Combine(Application.persistentDataPath, "Modsettings"), "TweakSettings.json")
        {
            NotifyFilter = NotifyFilters.LastWrite
        };

        watcher.Changed += (object source, FileSystemEventArgs e) =>
        {
            if (modConfig.SerializeSettings(settings) == modConfig.SerializeSettings(modConfig.Settings))
            {
                return;
            }

            UpdateSettings();
            UpdateSettingWarning();

            MainThreadQueue.Enqueue(() => StartCoroutine(ModifyFreeplayDevice(false)));
        };

        // Setup our "service" to block the leaderboard submission requests
        ReflectedTypes.InstanceField.SetValue(null, new SteamFilterService());

        // Create a fake case with a bunch of anchors to trick the game when using CaseGenerator.
        TweaksCaseGeneratorCase = new GameObject("TweaksCaseGenerator");
        TweaksCaseGeneratorCase.transform.SetParent(transform);
        var kmBomb = TweaksCaseGeneratorCase.AddComponent <KMBomb>();

        kmBomb.IsHoldable      = false;
        kmBomb.WidgetAreas     = new List <GameObject>();
        kmBomb.visualTransform = transform;
        kmBomb.Faces           = new List <KMBombFace>();

        TweaksCaseGeneratorCase.AddComponent <ModBomb>();

        var kmBombFace = TweaksCaseGeneratorCase.AddComponent <KMBombFace>();

        kmBombFace.Anchors = new List <Transform>();
        kmBomb.Faces.Add(kmBombFace);

        for (int i = 0; i <= 9001; i++)
        {
            kmBombFace.Anchors.Add(transform);
        }

        // Handle scene changes
        UnityEngine.SceneManagement.SceneManager.sceneLoaded += (Scene scene, LoadSceneMode _) =>
        {
            UpdateSettings();
            UpdateSettingWarning();

            Modes.settings           = Modes.modConfig.Settings;
            Modes.modConfig.Settings = Modes.settings;

            if ((scene.name == "mainScene" || scene.name == "gameplayScene") && changeFadeTime)
            {
                SceneManager.Instance.RapidFadeInTime = settings.FadeTime;
            }

            switch (scene.name)
            {
            case "mainScene":
                if (changeFadeTime)
                {
                    SceneManager.Instance.SetupState.FadeInTime          =
                        SceneManager.Instance.SetupState.FadeOutTime     =
                            SceneManager.Instance.UnlockState.FadeInTime = settings.FadeTime;
                }

                break;

            case "gameplayLoadingScene":
                var gameplayLoadingManager = FindObjectOfType <GameplayLoadingManager>();
                if (settings.InstantSkip)
                {
                    gameplayLoadingManager.MinTotalLoadTime = 0;
                }
                if (changeFadeTime)
                {
                    gameplayLoadingManager.FadeInTime      =
                        gameplayLoadingManager.FadeOutTime = settings.FadeTime;
                }

                ReflectedTypes.UpdateTypes();

                ReflectedTypes.CurrencyAPIEndpointField?.SetValue(null, settings.FixFER ? "http://api.exchangeratesapi.io" : "http://api.fixer.io");

                break;

            case "gameplayScene":
                if (changeFadeTime)
                {
                    SceneManager.Instance.GameplayState.FadeInTime      =
                        SceneManager.Instance.GameplayState.FadeOutTime = settings.FadeTime;
                }

                break;
            }
        };

        // Handle state changes
        GameInfo.OnStateChange += (KMGameInfo.State state) =>
        {
            CurrentState = state;
            watcher.EnableRaisingEvents = state == KMGameInfo.State.Setup;

            if (state == KMGameInfo.State.Gameplay)
            {
                bool disableRecords = settings.BombHUD || settings.ShowEdgework || CurrentMode != Mode.Normal || settings.MissionSeed != -1;

                Assets.Scripts.Stats.StatsManager.Instance.DisableStatChanges        =
                    Assets.Scripts.Records.RecordManager.Instance.DisableBestRecords = disableRecords;
                if (disableRecords)
                {
                    SteamFilterService.TargetMissionID = GameplayState.MissionToLoad;
                }

                BetterCasePicker.HandleCaseGeneration();

                BombStatus.Instance.widgetsActivated = false;
                BombStatus.Instance.HUD.SetActive(settings.BombHUD);
                BombStatus.Instance.Edgework.SetActive(settings.ShowEdgework);
                BombStatus.Instance.ConfidencePrefab.gameObject.SetActive(CurrentMode != Mode.Zen);
                BombStatus.Instance.StrikesPrefab.color = CurrentMode == Mode.Time ? Color.yellow : Color.red;

                Modes.Multiplier = Modes.settings.TimeModeStartingMultiplier;
                BombStatus.Instance.UpdateMultiplier();
                bombWrappers = new BombWrapper[] { };
                StartCoroutine(CheckForBombs());
                if (settings.SkipGameplayDelay)
                {
                    StartCoroutine(SkipGameplayDelay());
                }

                if (GameplayState.BombSeedToUse == -1)
                {
                    GameplayState.BombSeedToUse = settings.MissionSeed;
                }
            }
            else if (state == KMGameInfo.State.Setup)
            {
                if (ReflectedTypes.LoadedModsField.GetValue(ModManager.Instance) is Dictionary <string, Mod> loadedMods)
                {
                    Mod tweaksMod = loadedMods.Values.FirstOrDefault(mod => mod.ModID == "Tweaks");
                    if (tweaksMod != null)
                    {
                        if (CaseGeneratorSettingCache != settings.CaseGenerator)
                        {
                            if (settings.CaseGenerator)
                            {
                                tweaksMod.ModObjects.Add(TweaksCaseGeneratorCase);
                            }
                            else
                            {
                                tweaksMod.ModObjects.Remove(TweaksCaseGeneratorCase);
                            }

                            CaseGeneratorSettingCache = settings.CaseGenerator;
                            UpdateSettingWarning();
                        }
                    }
                }

                StartCoroutine(ModifyFreeplayDevice(true));
                GetComponentInChildren <ModSelectorExtension>().FindAPI();

                GameplayState.BombSeedToUse = -1;
            }
            else if (state == KMGameInfo.State.Transitioning)
            {
                // Because the settings are checked on a scene change and there is no scene change from exiting the gameplay room,
                // we need to update the settings here in case the user changed their HideTOC settings.
                UpdateSettings();

                bool modified       = false;
                var  ModMissionToCs = ModManager.Instance.ModMissionToCs;
                foreach (var metaData in ModMissionToCs)
                {
                    modified |= ModToCMetaData.Add(metaData);
                }

                var unloadedMods = (Dictionary <string, Mod>)ReflectedTypes.UnloadedModsField.GetValue(ModManager.Instance);
                if (unloadedMods != null)
                {
                    foreach (var unloadedMod in unloadedMods.Values)
                    {
                        var tocs = (List <ModTableOfContentsMetaData>)ReflectedTypes.TocsField.GetValue(unloadedMod);
                        if (tocs != null)
                        {
                            foreach (var metaData in tocs)
                            {
                                modified |= ModToCMetaData.Remove(metaData);
                            }
                        }
                    }
                }

                var newToCs = ModToCMetaData.Where(metaData => !settings.HideTOC.Any(pattern => Localization.GetLocalizedString(metaData.DisplayNameTerm).Like(pattern)));
                modified |= (newToCs.Count() != ModMissionToCs.Count || !newToCs.All(ModMissionToCs.Contains));
                ModMissionToCs.Clear();
                ModMissionToCs.AddRange(newToCs);

                if (modified)
                {
                    SetupState.LastBombBinderTOCIndex = 0;
                    SetupState.LastBombBinderTOCPage  = 0;
                }
            }
        };
    }
    private void OutputThreadMethod(TextWriter output)
    {
        Stopwatch stopWatch = new Stopwatch();

        stopWatch.Start();
        _messageDelay = 0;

        while (ThreadAlive)
        {
            try
            {
                Thread.Sleep(25);
                IRCCommand command;
                if (stopWatch.ElapsedMilliseconds <= _messageDelay && _state == IRCConnectionState.Connected)
                {
                    continue;
                }
                lock (_sendQueue)
                {
                    if (_sendQueue.Count == 0)
                    {
                        continue;
                    }
                    command = _sendQueue.Dequeue();
                }

                if (command.CommandIsColor() &&
                    CurrentColor.Equals(command.GetColor(), StringComparison.InvariantCultureIgnoreCase))
                {
                    continue;
                }

                output.WriteLine(command.Command);
                output.Flush();

                stopWatch.Reset();
                stopWatch.Start();

                _messageDelay  = _isModerator ? MessageDelayMod : MessageDelayUser;
                _messageDelay += (command.CommandIsColor() && _isModerator) ? 700 : 0;
            }
            catch
            {
                AddTextToHoldable("[IRC:Disconnect] Connection failed.");
                _state = IRCConnectionState.Disconnected;
            }
        }
        MainThreadQueue.Enqueue(() => TwitchGame.EnableDisableInput());

        try
        {
            if (_state == IRCConnectionState.Disconnecting)
            {
                IRCCommand setColor = new IRCCommand($"PRIVMSG #{_settings.channelName} :.color {ColorOnDisconnect}");
                if (setColor.CommandIsColor())
                {
                    AddTextToHoldable("[IRC:Disconnect] Color {0} was requested, setting it now.", ColorOnDisconnect);
                    while (stopWatch.ElapsedMilliseconds < _messageDelay)
                    {
                        Thread.Sleep(25);
                    }
                    output.WriteLine(setColor.Command);
                    output.Flush();

                    stopWatch.Reset();
                    stopWatch.Start();
                    while (stopWatch.ElapsedMilliseconds < 1200)
                    {
                        Thread.Sleep(25);
                    }
                }
                _state = IRCConnectionState.Disconnected;
                AddTextToHoldable("[IRC:Disconnect] Disconnected from chat IRC.");
            }

            lock (_sendQueue)
                _sendQueue.Clear();
        }
        catch
        {
            _state = IRCConnectionState.Disconnected;
        }
        MainThreadQueue.Enqueue(() =>
        {
            if (!gameObject.activeInHierarchy)
            {
                AddTextToHoldable("[IRC:Disconnect] Twitch Plays disabled.");
            }
        });
    }
    private void InputThreadMethod(TextReader input, NetworkStream networkStream)
    {
        bool      pingTimeoutTest = false;    // Keeps track of if we are currently in a ping timeout test.
        Stopwatch stopwatch       = new Stopwatch();

        try
        {
            stopwatch.Start();
            while (ThreadAlive)
            {
                if (_state == IRCConnectionState.Connected)
                {
                    // If the server hasn't sent any data for 6 minutes then begin a ping timeout test.
                    if (stopwatch.ElapsedMilliseconds > 360000 && !pingTimeoutTest)
                    {
                        pingTimeoutTest = true;
                        stopwatch.Reset();
                        stopwatch.Start();
                        SendCommand("PING");
                        MainThreadQueue.Enqueue(() => ConnectionAlert.SetActive(true));
                    }
                    else if (pingTimeoutTest)
                    {
                        alertText.text = $"The bot might be disconnected from the server. Timing out in {(10 - stopwatch.ElapsedMilliseconds / 1000f).ToString("N1")}";
                        alertProgressBar.localScale = new Vector3(1 - stopwatch.ElapsedMilliseconds / 10000f, 1, 1);

                        if (stopwatch.ElapsedMilliseconds > 10000)                         // Timeout if there hasn't been a response to the ping for 10s.
                        {
                            AddTextToHoldable("[IRC:Connect] Connection timed out.");
                            stopwatch.Reset();
                            _state = IRCConnectionState.Disconnected;
                            continue;
                        }
                    }
                }

                if (!networkStream.DataAvailable)
                {
                    Thread.Sleep(25);
                    continue;
                }

                pingTimeoutTest = false;
                MainThreadQueue.Enqueue(() => ConnectionAlert.SetActive(false));
                stopwatch.Reset();
                stopwatch.Start();
                string buffer = input.ReadLine();
                MainThreadQueue.Enqueue(() =>
                {
                    foreach (ActionMap action in Actions)
                    {
                        if (action.TryMatch(buffer))
                        {
                            break;
                        }
                    }
                });
            }
        }
        catch
        {
            AddTextToHoldable("[IRC:Disconnect] Connection failed.");
            _state = IRCConnectionState.Disconnected;
        }
    }