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); } }
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; } } } }
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; } } }; }
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")); } }
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); } } }
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; } } }; }