Пример #1
0
    IEnumerator GetModuleInformation(BombComponent bombComponent, ModuleTweak moduleTweak = null)
    {
        int          moduleID   = -1;
        KMBombModule bombModule = bombComponent.GetComponent <KMBombModule>();
        string       moduleType = Modes.GetModuleID(bombComponent);

        displayNames[moduleType] = bombComponent.GetModuleDisplayName();

        if (bombModule != null)
        {
            // Try to find a module ID from a field
            System.Reflection.FieldInfo idField = ReflectedTypes.GetModuleIDNumber(bombModule, out Component targetComponent);
            if (idField != null)
            {
                // Find the module ID from reflection
                float startTime = Time.time;
                yield return(new WaitUntil(() =>
                {
                    moduleID = (int)idField.GetValue(targetComponent);
                    return moduleID != 0 || Time.time - startTime > 30;                     // Check to see if the field has been initialized with an ID or fail out after 30 seconds.
                }));
            }

            // From the object name.
            string prefix = bombModule.ModuleDisplayName + " #";
            if (moduleID == -1 && bombModule.gameObject.name.StartsWith(prefix) && !int.TryParse(bombModule.gameObject.name.Substring(prefix.Length), out moduleID))
            {
                moduleID = -1;
            }
        }

        // These component types shouldn't try to get the ID from the logger property. Used below.
        var blacklistedComponents = new[]
        {
            ComponentTypeEnum.Empty,
            ComponentTypeEnum.Mod,
            ComponentTypeEnum.NeedyMod,
            ComponentTypeEnum.Timer,
        };

        // From the logger property of vanilla components
        string loggerName = bombComponent.GetValue <object>("logger")?.GetValue <object>("Logger")?.GetValue <string>("Name");

        if (moduleID == -1 && !blacklistedComponents.Contains(bombComponent.ComponentType) && loggerName != null && !int.TryParse(loggerName.Substring(loggerName.IndexOf('#') + 1), out moduleID))
        {
            moduleID = -1;
        }

        // From logging implemented by Tweaks
        if (moduleTweak is ModuleLogging moduleLogging)
        {
            moduleID = moduleLogging.moduleID;
        }

        if (moduleID != -1)
        {
            if (!ids.ContainsKey(moduleType))
            {
                ids[moduleType] = new List <int>();
            }
            ids[moduleType].Add(moduleID);
            componentIDs[bombComponent] = moduleID;
        }

        // Find the index and position of the module's anchor
        var allAnchors = Bomb.Faces.SelectMany(face => face.Anchors).ToList();

        if (allAnchors.Count != 0)         // Prevents .First() from being a problem later if there was somehow no anchors.
        {
            Transform moduleAnchor = allAnchors.OrderBy(anchor => (anchor.position - bombComponent.transform.position).magnitude).First();
            int       index        = allAnchors.IndexOf(moduleAnchor);

            modules[index] = moduleID != -1 ? $"{moduleType} {moduleID}" : $"{moduleType} -";
            var position = Quaternion.Euler(-Bomb.transform.rotation.eulerAngles) * ((moduleAnchor.position - Bomb.transform.position) / Bomb.Scale);
            anchors[index] = new decimal[] { Math.Round((decimal)position.x, 3), Math.Round((decimal)position.z, 3) };               // Round using a decimal to make the JSON a bit cleaner.
        }

        modulesUnactivated--;
        if (modulesUnactivated == 0)
        {
            Tweaks.LogJSON("LFABombInfo", bombLogInfo);
        }
    }
Пример #2
0
    public void Awake()
    {
        Bomb           = GetComponent <Bomb>();
        holdable       = Bomb.GetComponentInChildren <FloatingHoldable>();
        timerComponent = Bomb.GetTimer();
        widgetManager  = Bomb.WidgetManager;

        holdable.OnLetGo += () => BombStatus.Instance.currentBomb = null;

        Color modeColor = ModeColors[Tweaks.CurrentMode];

        BombStatus.Instance.TimerPrefab.color    = modeColor;
        timerComponent.text.color                = modeColor;
        timerComponent.StrikeIndicator.RedColour = modeColor;

        if (Tweaks.CurrentMode == Mode.Zen)
        {
            ZenModeTimePenalty = Mathf.Abs(Modes.settings.ZenModeTimePenalty);
            ZenModeTimerRate   = -timerComponent.GetRate();
            timerComponent.SetRateModifier(ZenModeTimerRate);
            Modes.initialTime = timerComponent.TimeRemaining;

            //This was in the original code to make sure the bomb didn't explode on the first strike
            Bomb.NumStrikesToLose++;
        }

        realTimeStart = Time.unscaledTime;
        BombEvents.OnBombDetonated += OnDetonate;
        BombEvents.OnBombSolved    += OnSolve;

        foreach (BombComponent component in Bomb.BombComponents)
        {
            Dictionary <string, object> makeEventInfo(string type)
            {
                Dictionary <string, object> eventInfo = new Dictionary <string, object>()
                {
                    { "type", type },
                    { "moduleID", Modes.GetModuleID(component) },
                    { "bombTime", CurrentTimer },
                    { "realTime", Time.unscaledTime - realTimeStart },
                };

                if (componentIDs.TryGetValue(component, out int loggingID))
                {
                    eventInfo["loggingID"] = loggingID;
                }

                return(eventInfo);
            }

            component.OnPass += delegate
            {
                BombStatus.Instance.UpdateSolves();

                var eventInfo = makeEventInfo("PASS");
                if (Tweaks.CurrentMode == Mode.Time)
                {
                    if (
                        !Modes.settings.ComponentValues.TryGetValue(Modes.GetModuleID(component), out double ComponentValue) &&
                        !Modes.DefaultComponentValues.TryGetValue(Modes.GetModuleID(component), out ComponentValue)
                        )
                    {
                        ComponentValue = 10;
                    }

                    if (
                        !Modes.settings.TotalModulesMultiplier.TryGetValue(Modes.GetModuleID(component), out double totalModulesMultiplier) &&
                        !Modes.DefaultTotalModulesMultiplier.TryGetValue(Modes.GetModuleID(component), out totalModulesMultiplier)
                        )
                    {
                        totalModulesMultiplier = 0;
                    }

                    var   modules         = Bomb.GetSolvableComponentCount();
                    var   points          = ComponentValue + modules * totalModulesMultiplier;
                    float finalMultiplier = Mathf.Min(Modes.Multiplier, Modes.settings.TimeModeMaxMultiplier);
                    float time            = (float)(points * finalMultiplier * Modes.settings.TimeModePointMultiplier);
                    float finalTime       = Math.Max(Modes.settings.TimeModeMinimumTimeGained, time);

                    // Show the alert
                    string alertText = "";
                    if (Math.Round(totalModulesMultiplier, 3) != 0)
                    {
                        alertText += $"{ComponentValue} + {totalModulesMultiplier:0.###} <size=36>x</size> {modules} mods = {points:0}\n";
                    }

                    string multiplierText = Math.Round(Modes.settings.TimeModePointMultiplier, 3) == 1 ? "" : $"<size=36>x</size> {Modes.settings.TimeModePointMultiplier:0.###} ";
                    alertText += $"{points:0} points <size=36>x</size> {finalMultiplier:0.#} {multiplierText}= {(time > 0 ? "+" : "")}{time.FormatTime()}\n";

                    if (time < Modes.settings.TimeModeMinimumTimeGained)
                    {
                        alertText += $"Min Time Added = {(finalTime > 0 ? "+" : "")}{finalTime.FormatTime()}\n";
                    }

                    eventInfo["timeMode"] = alertText.TrimEnd('\n').Replace("<size=36>x</size>", "×");                     // Build the logging information for time mode.

                    alertText += component.GetModuleDisplayName();

                    AddAlert(alertText.Replace(' ', ' '), Color.green);                     // Replace all spaces with nbsp since we don't want the line to wrap.

                    CurrentTimer += finalTime;

                    Modes.Multiplier += Modes.settings.TimeModeSolveBonus;
                    BombStatus.Instance.UpdateMultiplier();
                }

                Tweaks.LogJSON("LFAEvent", eventInfo);

                return(false);
            };

            var OnStrike = component.OnStrike;
            component.OnStrike = (BombComponent source) =>
            {
                var eventInfo = makeEventInfo("STRIKE");
                if (Tweaks.CurrentMode == Mode.Time)
                {
                    float multiplier      = Modes.Multiplier - Modes.settings.TimeModeMultiplierStrikePenalty;
                    float finalMultiplier = Math.Max(multiplier, Modes.settings.TimeModeMinMultiplier);

                    // Show the alert
                    string alertText = $"TIME LOST = {Modes.settings.TimeModeTimerStrikePenalty:0.###} <size=36>x</size> {CurrentTimer.FormatTime()} = {(CurrentTimer * Modes.settings.TimeModeTimerStrikePenalty).FormatTime()}\n";
                    alertText += $"MULTIPIER = {Modes.Multiplier:0.#} - {Modes.settings.TimeModeMultiplierStrikePenalty:0.#} = {multiplier:0.#}\n";

                    if (multiplier < Modes.settings.TimeModeMinMultiplier)
                    {
                        alertText += $"REDUCED TO MIN = {finalMultiplier}\n";
                    }

                    eventInfo["timeMode"] = alertText.TrimEnd('\n').Replace("<size=36>x</size>", "×");                     // Build the logging information for time mode.

                    alertText += component.GetModuleDisplayName();
                    AddAlert(alertText.Replace(' ', ' '), Color.red);

                    Modes.Multiplier = finalMultiplier;
                    BombStatus.Instance.UpdateMultiplier();
                    if (CurrentTimer < (Modes.settings.TimeModeMinimumTimeLost / Modes.settings.TimeModeTimerStrikePenalty))
                    {
                        CurrentTimer -= Modes.settings.TimeModeMinimumTimeLost;
                    }
                    else
                    {
                        CurrentTimer -= CurrentTimer * Modes.settings.TimeModeTimerStrikePenalty;
                    }

                    // We can safely set the number of strikes to -1 since it's going to be increased by the game after us.
                    Bomb.NumStrikes = -1;
                }

                OnStrike(source);

                // These mode modifications need to happen after the game handles the strike since they change the timer rate.
                if (Tweaks.CurrentMode == Mode.Zen)
                {
                    Bomb.NumStrikesToLose++;

                    ZenModeTimerRate = Mathf.Max(ZenModeTimerRate - Mathf.Abs(Modes.settings.ZenModeTimerSpeedUp), -Mathf.Abs(Modes.settings.ZenModeTimerMaxSpeed));
                    timerComponent.SetRateModifier(ZenModeTimerRate);

                    CurrentTimer       += ZenModeTimePenalty * 60;
                    ZenModeTimePenalty += Mathf.Abs(Modes.settings.ZenModeTimePenaltyIncrease);
                }

                if (Tweaks.CurrentMode == Mode.Steady)
                {
                    timerComponent.SetRateModifier(1);
                    CurrentTimer -= Modes.settings.SteadyModeFixedPenalty * 60 - Modes.settings.SteadyModePercentPenalty * BombStartingTimer;
                }

                BombStatus.Instance.UpdateStrikes();

                Tweaks.LogJSON("LFAEvent", eventInfo);

                return(false);
            };
        }

        var moduleTweaks = new Dictionary <string, Func <BombComponent, ModuleTweak> >()
        {
            { "Emoji Math", bombComponent => new EmojiMathLogging(bombComponent) },
            { "Probing", bombComponent => new ProbingLogging(bombComponent) },
            { "SeaShells", bombComponent => new SeaShellsLogging(bombComponent) },
            { "WordScrambleModule", bombComponent => new WordScramblePatch(bombComponent) },
            { "Color Decoding", bombComponent => new ColorDecodingTweak(bombComponent) },
            { "TurnTheKeyAdvanced", bombComponent => new TTKSTweak(bombComponent) },

            { "Wires", bombComponent => new WiresLogging(bombComponent) },
            { "Keypad", bombComponent => new KeypadLogging(bombComponent) }
        };

        modules     = new string[Bomb.Faces.Sum(face => face.Anchors.Count)];
        anchors     = new decimal[Bomb.Faces.Sum(face => face.Anchors.Count)][];
        bombLogInfo = new Dictionary <string, object>()
        {
            { "serial", JsonConvert.DeserializeObject <Dictionary <string, string> >(Bomb.WidgetManager.GetWidgetQueryResponses(KMBombInfo.QUERYKEY_GET_SERIAL_NUMBER, null)[0])["serial"] },
            { "displayNames", displayNames },
            { "ids", ids },
            { "anchors", anchors },
            { "modules", modules },
            { "timestamp", DateTime.Now.ToString("O") }
        };

        modulesUnactivated = Bomb.BombComponents.Count;
        foreach (BombComponent component in Bomb.BombComponents)
        {
            KMBombModule bombModule = component.GetComponent <KMBombModule>();
            if (bombModule != null && (bombModule.ModuleType == "TurnTheKey" || Tweaks.settings.ModuleTweaks))
            {
                switch (bombModule.ModuleType)
                {
                // TTK is our favorite Zen mode compatible module
                // Of course, everything here is repurposed from Twitch Plays.
                case "TurnTheKey":
                    new TTKComponentSolver(component, bombModule, Tweaks.CurrentMode.Equals(Mode.Zen) ? Modes.initialTime : timerComponent.TimeRemaining);
                    break;

                // Correct some mispositioned objects in older modules
                case "ForeignExchangeRates":
                case "resistors":
                case "CryptModule":
                case "LEDEnc":
                    // This fixes the position of the module itself (but keeps the status light in its original location, which fixes it)
                    component.transform.Find("Model").transform.localPosition = new Vector3(0.004f, 0, 0);
                    break;

                case "Listening":
                    // This fixes the Y-coordinate of the position of the status light
                    component.transform.Find("StatusLight").transform.localPosition = new Vector3(-0.0761f, 0.01986f, 0.075f);
                    break;

                case "TwoBits":
                case "errorCodes":
                case "primeEncryption":
                case "memorableButtons":
                case "babaIsWho":
                case "colorfulDials":
                case "scalarDials":
                    // This fixes the position of the status light
                    component.GetComponentInChildren <StatusLightParent>().transform.localPosition = new Vector3(0.075167f, 0.01986f, 0.076057f);
                    break;

                case "tWords":
                case "moon":
                case "sun":
                case "jewelVault":
                    // This fixes the scale of the light components
                    component.GetComponentInChildren <Light>().range *= component.transform.lossyScale.x;
                    break;
                }

                // This fixes the position of the highlight
                switch (bombModule.ModuleType)
                {
                case "babaIsWho":
                case "needlesslyComplicatedButton":
                    component.GetComponent <Selectable>().Highlight.transform.localPosition = Vector3.zero;
                    break;
                }
            }

            ModuleTweak moduleTweak = null;
            string      moduleType  = bombModule != null ? bombModule.ModuleType : component.ComponentType.ToString();
            if (moduleTweaks.ContainsKey(moduleType) && (moduleType != "WordScrambleModule" || Tweaks.settings.ModuleTweaks))
            {
                moduleTweak = moduleTweaks[moduleType](component);
            }

            component.StartCoroutine(GetModuleInformation(component, moduleTweak));

            if (component.ComponentType == ComponentTypeEnum.Mod || component.ComponentType == ComponentTypeEnum.NeedyMod)
            {
                ReflectedTypes.FindModeBoolean(component);
            }
            else if (Tweaks.settings.ModuleTweaks)
            {
                switch (component.ComponentType)
                {
                case ComponentTypeEnum.Keypad:
                    Tweaks.FixKeypadButtons(((KeypadComponent)component).buttons);
                    break;

                case ComponentTypeEnum.Simon:
                    Tweaks.FixKeypadButtons(((SimonComponent)component).buttons);
                    break;

                case ComponentTypeEnum.Password:
                    Tweaks.FixKeypadButtons(component.GetComponentsInChildren <KeypadButton>());
                    break;

                case ComponentTypeEnum.NeedyVentGas:
                    Tweaks.FixKeypadButtons(((NeedyVentComponent)component).YesButton, ((NeedyVentComponent)component).NoButton);
                    break;
                }
            }
        }
    }
Пример #3
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;
                }
            }
        };
    }
Пример #4
0
    IEnumerator GetModuleInformation(BombComponent bombComponent)
    {
        int          moduleID   = -1;
        KMBombModule bombModule = bombComponent.GetComponent <KMBombModule>();
        string       moduleType = bombModule != null ? bombModule.ModuleType : bombComponent.ComponentType.ToString();

        displayNames[moduleType] = bombComponent.GetModuleDisplayName();

        if (bombModule != null)
        {
            // Try to find a module ID from a field
            System.Reflection.FieldInfo idField = ReflectedTypes.GetModuleIDNumber(bombModule, out Component targetComponent);

            if (idField != null)
            {
                // Find the module ID from reflection
                float startTime = Time.time;
                yield return(new WaitUntil(() =>
                {
                    moduleID = (int)idField.GetValue(targetComponent);
                    return moduleID != 0 || Time.time - startTime > 30;                     // Check to see if the field has been initialized with an ID or fail out after 30 seconds.
                }));
            }

            // From the object name.
            string prefix = bombModule.ModuleDisplayName + " #";
            if (bombModule.gameObject.name.StartsWith(prefix) && !int.TryParse(bombModule.gameObject.name.Substring(prefix.Length), out moduleID))
            {
                moduleID = -1;
            }
        }

        // From the logger property of vanilla components
        string loggerName = bombComponent.GetValue <object>("logger")?.GetValue <object>("Logger")?.GetValue <string>("Name");

        if (loggerName != null && !int.TryParse(loggerName.Substring(loggerName.IndexOf('#') + 1), out moduleID))
        {
            moduleID = -1;
        }

        // TODO: Handle logging implemented by Tweaks

        if (moduleID != -1)
        {
            if (!ids.ContainsKey(moduleType))
            {
                ids[moduleType] = new List <int>();
            }
            ids[moduleType].Add(moduleID);
        }
        else
        {
            Tweaks.Log(bombComponent.GetModuleDisplayName(), "has no module id.");
        }

        // Find anchor index
        int index = 0;

        foreach (BombFace face in Bomb.Faces)
        {
            foreach (Transform anchor in face.Anchors)
            {
                if ((anchor.position - bombComponent.transform.position).magnitude < 0.05)
                {
                    modules[index] = moduleID != -1 ? $"{moduleType} {moduleID}" : $"{moduleType} -";

                    break;
                }

                index++;
            }
        }

        modulesUnactivated--;
        if (modulesUnactivated == 0)
        {
            string[] chunks = JsonConvert.SerializeObject(bombLogInfo).ChunkBy(250).ToArray();
            Tweaks.Log("LFABombInfo", chunks.Length + "\n" + chunks.Join("\n"));
        }
    }
Пример #5
0
    public BombWrapper(Bomb bomb)
    {
        Bomb           = bomb;
        holdable       = bomb.GetComponentInChildren <FloatingHoldable>();
        timerComponent = bomb.GetTimer();
        widgetManager  = bomb.WidgetManager;

        Color modeColor = ModeColors[Tweaks.CurrentMode];

        BombStatus.Instance.TimerPrefab.color    = modeColor;
        timerComponent.text.color                = modeColor;
        timerComponent.StrikeIndicator.RedColour = modeColor;

        if (Tweaks.CurrentMode == Mode.Zen)
        {
            ZenModeTimePenalty = Mathf.Abs(Modes.settings.ZenModeTimePenalty);
            ZenModeTimerRate   = -timerComponent.GetRate();
            timerComponent.SetRateModifier(ZenModeTimerRate);
            Modes.initialTime = timerComponent.TimeRemaining;

            //This was in the original code to make sure the bomb didn't explode on the first strike
            bomb.NumStrikesToLose++;
        }

        foreach (BombComponent component in Bomb.BombComponents)
        {
            component.OnPass += delegate
            {
                BombStatus.Instance.UpdateSolves();

                if (Tweaks.CurrentMode == Mode.Time)
                {
                    if (!Modes.settings.ComponentValues.TryGetValue(Modes.GetModuleID(component), out double ComponentValue))
                    {
                        ComponentValue = 6;
                    }

                    Modes.settings.TotalModulesMultiplier.TryGetValue(Modes.GetModuleID(component), out double totalModulesMultiplier);

                    float time = (float)(Mathf.Min(Modes.Multiplier, Modes.settings.TimeModeMaxMultiplier) * (ComponentValue + Bomb.BombComponents.Count * totalModulesMultiplier));

                    CurrentTimer += Math.Max(Modes.settings.TimeModeMinimumTimeGained, time);

                    Modes.Multiplier += Modes.settings.TimeModeSolveBonus;
                    BombStatus.Instance.UpdateMultiplier();
                }

                return(false);
            };

            var OnStrike = component.OnStrike;
            component.OnStrike = (BombComponent source) =>
            {
                if (Tweaks.CurrentMode == Mode.Time)
                {
                    Modes.Multiplier = Math.Max(Modes.Multiplier - Modes.settings.TimeModeMultiplierStrikePenalty, Modes.settings.TimeModeMinMultiplier);
                    BombStatus.Instance.UpdateMultiplier();
                    if (CurrentTimer < (Modes.settings.TimeModeMinimumTimeLost / Modes.settings.TimeModeTimerStrikePenalty))
                    {
                        CurrentTimer -= Modes.settings.TimeModeMinimumTimeLost;
                    }
                    else
                    {
                        CurrentTimer -= CurrentTimer * Modes.settings.TimeModeTimerStrikePenalty;
                    }

                    // We can safely set the number of strikes to -1 since it's going to be increased by the game after us.
                    Bomb.NumStrikes = -1;
                }

                OnStrike(source);

                // These mode modifications need to happen after the game handles the strike since they change the timer rate.
                if (Tweaks.CurrentMode == Mode.Zen)
                {
                    bomb.NumStrikesToLose++;

                    ZenModeTimerRate = Mathf.Max(ZenModeTimerRate - Mathf.Abs(Modes.settings.ZenModeTimerSpeedUp), -Mathf.Abs(Modes.settings.ZenModeTimerMaxSpeed));
                    timerComponent.SetRateModifier(ZenModeTimerRate);

                    CurrentTimer       += ZenModeTimePenalty * 60;
                    ZenModeTimePenalty += Mathf.Abs(Modes.settings.ZenModeTimePenaltyIncrease);
                }

                if (Tweaks.CurrentMode == Mode.Steady)
                {
                    timerComponent.SetRateModifier(1);
                    CurrentTimer -= Modes.settings.SteadyModeFixedPenalty * 60 - Modes.settings.SteadyModePercentPenalty * bombStartingTimer;
                }

                BombStatus.Instance.UpdateStrikes();

                return(false);
            };
        }

        var moduleTweaks = new Dictionary <string, Func <BombComponent, ModuleTweak> >()
        {
            { "Emoji Math", bombComponent => new EmojiMathLogging(bombComponent) },
            { "Probing", bombComponent => new ProbingLogging(bombComponent) },
            { "SeaShells", bombComponent => new SeaShellsLogging(bombComponent) },
            { "switchModule", bombComponent => new SwitchesLogging(bombComponent) },
            { "WordScrambleModule", bombComponent => new WordScramblePatch(bombComponent) },

            { "Wires", bombComponent => new WiresLogging(bombComponent) },
            { "Keypad", bombComponent => new KeypadLogging(bombComponent) },
        };

        modules     = new string[bomb.Faces.Sum(face => face.Anchors.Count)];
        bombLogInfo = new Dictionary <string, object>()
        {
            { "serial", JsonConvert.DeserializeObject <Dictionary <string, string> >(bomb.WidgetManager.GetWidgetQueryResponses(KMBombInfo.QUERYKEY_GET_SERIAL_NUMBER, null)[0])["serial"] },
            { "displayNames", displayNames },
            { "ids", ids },
            { "case", bomb.gameObject.name.Replace("(Clone)", "") },
            { "modules", modules }
        };

        modulesUnactivated = bomb.BombComponents.Count;
        foreach (BombComponent component in bomb.BombComponents)
        {
            component.StartCoroutine(GetModuleInformation(component));

            KMBombModule bombModule = component.GetComponent <KMBombModule>();
            if (bombModule != null)
            {
                switch (bombModule.ModuleType)
                {
                //TTK is our favorite Zen mode compatible module
                //Of course, everything here is repurposed from Twitch Plays.
                case "TurnTheKey":
                    new TTKComponentSolver(bombModule, bomb, Tweaks.CurrentMode.Equals(Mode.Zen) ? Modes.initialTime : timerComponent.TimeRemaining);
                    break;

                // Correct some mispositioned objects in older modules
                case "ForeignExchangeRates":
                case "resistors":
                case "CryptModule":
                case "LEDEnc":
                    // This fixes the position of the module itself (but keeps the status light in its original location, which fixes it)
                    component.transform.Find("Model").transform.localPosition = new Vector3(0.004f, 0, 0);
                    break;

                case "Listening":
                    // This fixes the Y-coordinate of the position of the status light
                    component.transform.Find("StatusLight").transform.localPosition = new Vector3(-0.0761f, 0.01986f, 0.075f);
                    break;

                case "TwoBits":
                case "errorCodes":
                    // This fixes the position of the status light
                    component.GetComponentInChildren <StatusLightParent>().transform.localPosition = new Vector3(0.075167f, 0.01986f, 0.076057f);
                    break;
                }
            }

            string moduleType = bombModule != null ? bombModule.ModuleType : component.ComponentType.ToString();
            if (moduleTweaks.ContainsKey(moduleType))
            {
                moduleTweaks[moduleType](component);
            }

            if (component.ComponentType == ComponentTypeEnum.Mod)
            {
                ReflectedTypes.FindModeBoolean(component);
            }
        }
    }
Пример #6
0
    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;
                }
            }
        };
    }