public static IEnumerator LoadDefaultSettings() { UnityWebRequest www = UnityWebRequest.Get("https://spreadsheets.google.com/feeds/list/16lz2mCqRWxq__qnamgvlD0XwTuva4jIDW1VPWX49hzM/1/public/values?alt=json"); yield return(www.SendWebRequest()); if (!www.isNetworkError && !www.isHttpError) { bool missingWarning = false; foreach (var entry in JObject.Parse(www.downloadHandler.text)["feed"]["entry"]) { bool score = !string.IsNullOrEmpty(entry["gsx$resolvedscore"]["$t"]?.Value <string>().Trim()); bool multiplier = !string.IsNullOrEmpty(entry["gsx$resolvedbosspointspermodule"]["$t"]?.Value <string>().Trim()); if (entry["gsx$moduleid"] == null) { if ((score || multiplier) && !missingWarning) { missingWarning = true; Tweaks.Log("An entry on the spreadsheet is missing it's module ID. You should contact the spreadsheet maintainers about this."); } continue; } string moduleID = entry["gsx$moduleid"].Value <string>("$t"); if (string.IsNullOrEmpty(moduleID) || moduleID == "ModuleID") { continue; } if (score) { DefaultComponentValues[moduleID] = entry["gsx$resolvedscore"].Value <double>("$t"); } if (multiplier) { DefaultTotalModulesMultiplier[moduleID] = entry["gsx$resolvedbosspointspermodule"].Value <double>("$t"); } } } else { Tweaks.Log("Failed to load the default time mode values."); } }
private static IEnumerator InstantiateComponents(Bomb bomb) { yield return(new WaitUntil(() => modsLoading == 0 && bombQueue.Count != 0 && bombQueue.Peek() == bomb)); var bombGenerator = SceneManager.Instance.GameplayState.GetValue <BombGenerator>("bombGenerator"); bombGenerator.SetValue("bomb", bomb); var logger = bombGenerator.GetValue <ILog>("logger"); // Enable logging again ((log4net.Repository.Hierarchy.Logger)logger.Logger).Level = null; var validBombFaces = new List <BombFace>(bomb.Faces.Where(face => face.ComponentSpawnPoints.Count != 0)); bombGenerator.SetValue("validBombFaces", validBombFaces); List <KMBombInfo> knownBombInfos = new List <KMBombInfo>(); foreach (KMBombInfo item in UnityEngine.Object.FindObjectsOfType <KMBombInfo>()) { knownBombInfos.Add(item); } var bombInfo = allBombInfo[bomb]; var timerFace = bombInfo.TimerFace; var setting = bombInfo.Settings; bombInfo.EnableOriginal = true; bombGenerator.SetValue("rand", bombInfo.Rand); // Bring back the real Random object. UnityEngine.Random.InitState(bomb.Seed); // Loading AudioClips triggers RNG calls so we need to reset the RNG to before that happened. // Emulate logging messages logger.InfoFormat("Generating bomb with seed {0}", bomb.Seed); logger.InfoFormat("Generator settings: {0}", setting.ToString()); foreach (var component in bombInfo.Components) { logger.InfoFormat("Selected {0} ({1})", Modes.GetModuleID(component), component); } var requiresTimer = bombInfo.Components.Where(component => component.RequiresTimerVisibility).Select(Modes.GetModuleID).ToArray(); var anyFace = bombInfo.Components.Where(component => !component.RequiresTimerVisibility).Select(Modes.GetModuleID).ToArray(); logger.DebugFormat("Bomb component list: RequiresTimerVisibility [{0}], AnyFace: [{1}]", string.Join(", ", requiresTimer), string.Join(", ", anyFace)); logger.DebugFormat("Instantiating RequiresTimerVisibility components on {0}", timerFace); // Spawn components bool loggedRemaining = false; foreach (var bombComponent in bombInfo.Components.OrderByDescending(component => component.RequiresTimerVisibility)) { BombFace face = null; if (bombComponent.RequiresTimerVisibility && timerFace.ComponentSpawnPoints.Count != 0) { face = timerFace; } else if (!loggedRemaining) { logger.Debug("Instantiating remaining components on any valid face."); loggedRemaining = true; } if (face == null && validBombFaces.Count != 0) { face = validBombFaces[bombInfo.Rand.Next(0, validBombFaces.Count)]; } if (face == null) { Tweaks.Log("No valid faces remain to instantiate:", bombComponent.name); continue; } bombGenerator.CallMethod("InstantiateComponent", face, bombComponent, setting); } logger.Debug("Filling remaining spaces with empty components."); while (validBombFaces.Count > 0) { bombGenerator.CallMethod("InstantiateComponent", validBombFaces[0], bombGenerator.emptyComponentPrefab, setting); } // We need to re-Init() the bomb face selectables so that the components get their correct X and Y positions for Gamepad support. foreach (Selectable selectable in bomb.Faces.Select(face => face.GetComponent <Selectable>())) { selectable.Init(); } logger.Debug("Generating Widgets"); // To ensure that the widgets get placed in the right position, we need to temporarily revert the bomb's size. bomb.visualTransform.localScale = Vector3.one; WidgetGenerator generator = bombGenerator.GetComponent <WidgetGenerator>(); generator.SetValue("zones", bombInfo.WidgetZones); generator.GenerateWidgets(bomb.WidgetManager, setting.OptionalWidgetCount); bomb.visualTransform.localScale = new Vector3(bomb.Scale, bomb.Scale, bomb.Scale); bombInfo.Loaded = true; HookUpMultipleBombs(bomb, knownBombInfos); bombQueue.Dequeue(); if (BombsLoaded) { SceneManager.Instance.GameplayState.Bombs.AddRange(allBombInfo.Keys); allBombInfo.Clear(); // This fixes the bomb not getting picked up correctly if clicked on before loading is finished. var holdable = KTInputManager.Instance.SelectableManager.GetCurrentFloatingHoldable(); if (holdable) { holdable.Defocus(false, false); } } // Solve any fake modules foreach (BombComponent component in bomb.BombComponents) { if (component.GetComponent <FakeModule>() != null) { component.IsSolved = true; component.Bomb.OnPass(component); } } }
private static IEnumerator LoadModule(BombComponent fakeModule, BombInfo bombInfo) { // To preserve the order that the BombComponents were picked in, insert a null value and keep track of it's index to be replaced later. int componentIndex = bombInfo.Components.Count; bombInfo.Components.Add(null); modsLoading++; Time.timeScale = 0; yield return(null); totalModules = modsLoading; UpdateLoadingScreen(); yield return(null); string SteamID = fakeModule.gameObject.name.Replace("(Clone)", ""); string ModuleID = fakeModule.GetComponent <KMBombModule>()?.ModuleType ?? fakeModule.GetComponent <KMNeedyModule>()?.ModuleType; if (modLoading.ContainsKey(SteamID)) { yield return(new WaitUntil(() => !modLoading[SteamID])); } if (!manuallyLoadedMods.TryGetValue(SteamID, out Mod mod)) { var modPath = Path.Combine(modWorkshopPath, SteamID); if (!Directory.Exists(modPath)) { yield break; } modLoading[SteamID] = true; mod = Mod.LoadMod(modPath, Assets.Scripts.Mods.ModInfo.ModSourceEnum.Local); foreach (string fileName in mod.GetAssetBundlePaths()) { var bundleRequest = AssetBundle.LoadFromFileAsync(fileName); yield return(bundleRequest); var mainBundle = bundleRequest.assetBundle; if (mainBundle != null) { try { mod.LoadBundle(mainBundle); } catch (Exception ex) { Debug.LogErrorFormat("Load of mod \"{0}\" failed: \n{1}\n{2}", mod.ModID, ex.Message, ex.StackTrace); } loadedObjects[SteamID] = mainBundle.LoadAllAssets <UnityEngine.Object>(); mainBundle.Unload(false); } } mod.CallMethod("RemoveMissions"); mod.CallMethod("RemoveSoundOverrides"); manuallyLoadedMods[SteamID] = mod; loadedMods[modPath] = mod; modLoading[SteamID] = false; } loadOrder.Remove(SteamID); loadOrder.Add(SteamID); if (mod == null) { yield break; } List <string> moduleIDs = new List <string>(); BombComponent realModule = null; foreach (KMBombModule kmbombModule in mod.GetModObjects <KMBombModule>()) { string moduleType = kmbombModule.ModuleType; if (moduleType == ModuleID) { realModule = kmbombModule.GetComponent <BombComponent>(); } moduleIDs.Add(moduleType); } foreach (KMNeedyModule kmneedyModule in mod.GetModObjects <KMNeedyModule>()) { string moduleType = kmneedyModule.ModuleType; if (moduleType == ModuleID) { realModule = kmneedyModule.GetComponent <BombComponent>(); } moduleIDs.Add(moduleType); } if (realModule != null) { foreach (ModService original in mod.GetModObjects <ModService>()) { GameObject gameObject = UnityEngine.Object.Instantiate(original).gameObject; gameObject.transform.parent = ModManager.Instance.transform; mod.AddServiceObject(gameObject); } bombInfo.Components[componentIndex] = realModule.GetComponent <BombComponent>(); } else { Tweaks.Log($"Unable to get the real module for {ModuleID} ({SteamID}). IDs found: {moduleIDs.Select(id => $"\"{id}\"").Join(", ")}. This shouldn't happen, please contact the developer of Tweaks."); LeaderboardController.DisableLeaderboards(); bombInfo.Components[componentIndex] = fakeModule; } modsLoading--; UpdateLoadingScreen(); if (modsLoading == 0) { Time.timeScale = 1; } }
static IEnumerator GetModules() { var steamDirectory = SteamDirectory; var repositoryBackup = Path.Combine(Application.persistentDataPath, "RepositoryBackup.json"); UnityWebRequest request = UnityWebRequest.Get("https://ktane.timwi.de/json/raw"); yield return(request.SendWebRequest()); string repositoryJSON = null; if (request.isNetworkError) { Tweaks.Log("Unable to load the repository:", request.error); if (File.Exists(repositoryBackup)) { repositoryJSON = File.ReadAllText(repositoryBackup); } else { Tweaks.Log("Could not find a repository backup."); } } else { repositoryJSON = request.downloadHandler.text; } if (repositoryJSON == null) { Tweaks.Log("Could not get module information."); } else if (steamDirectory == null) { Tweaks.Log("Unable to find Steam!"); } else { // Save a backup of the repository File.WriteAllText(repositoryBackup, repositoryJSON); var disabledMods = ModSettingsManager.Instance.ModSettings.DisabledModPaths.ToList(); modWorkshopPath = Path.GetFullPath(new[] { steamDirectory, "steamapps", "workshop", "content", "341800" }.Aggregate(Path.Combine)); var fakeModuleParent = new GameObject("FakeModuleParent"); fakeModuleParent.transform.parent = Tweaks.Instance.transform; fakeModuleParent.SetActive(false); var loadedBombComponents = ModManager.Instance.GetValue <Dictionary <string, BombComponent> >("loadedBombComponents"); var json = JsonConvert.DeserializeObject <WebsiteJSON>(repositoryJSON); var cantLoad = new List <string>(); foreach (KtaneModule module in json.KtaneModules) { // Don't load anything that: // Doesn't have a Steam ID. // Isn't a module. // Is on the user's exclude list. if ( module.SteamID == null || !(module.Type == "Regular" || module.Type == "Needy") || Tweaks.settings.DemandBasedModsExcludeList.Any(name => module.Name.Like(name)) ) { continue; } var modPath = Path.Combine(modWorkshopPath, module.SteamID); if (!Directory.Exists(modPath)) { cantLoad.Add($"{module.ModuleID} ({module.SteamID})"); continue; } // Disable mods we are going to load on demand if (!disabledMods.Contains(modPath)) { disabledMods.Add(modPath); DisabledModsCount++; } if (loadedBombComponents.ContainsKey(module.ModuleID)) { continue; } GameObject fakeModule = new GameObject("FakeModule"); fakeModule.transform.parent = fakeModuleParent.transform; if (module.Type == "Regular") { var fakeBombComponent = fakeModule.AddComponent <ModBombComponent>(); var bombModule = fakeModule.AddComponent <KMBombModule>(); fakeBombComponent.SetValue("module", bombModule); fakeBombComponent.enabled = false; fakeBombComponent.ComponentType = ComponentTypeEnum.Mod; bombModule.ModuleType = module.ModuleID; bombModule.ModuleDisplayName = module.Name; } else { var fakeNeedyComponent = fakeModule.AddComponent <ModNeedyComponent>(); var needyModule = fakeModule.AddComponent <KMNeedyModule>(); fakeNeedyComponent.SetValue("module", needyModule); fakeNeedyComponent.enabled = false; fakeNeedyComponent.ComponentType = ComponentTypeEnum.NeedyMod; needyModule.ModuleType = module.ModuleID; needyModule.ModuleDisplayName = module.Name; } fakeModule.gameObject.name = module.SteamID; fakeModule.AddComponent <FakeModule>(); fakeModule.AddComponent <Selectable>(); fakeModule.AddComponent <ModSource>().ModName = "Tweaks"; loadedBombComponents[module.ModuleID] = fakeModule.GetComponent <BombComponent>(); fakedModules.Add(module.ModuleID); } if (cantLoad.Count > 0) { Tweaks.Log($"Can't load: {cantLoad.Join(", ")}".ChunkBy(250).Join("\n")); } ModSettingsManager.Instance.ModSettings.DisabledModPaths = disabledMods.ToArray(); ModSettingsManager.Instance.SaveModSettings(); } Time.timeScale = 1; }
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")); } }
private static IEnumerator InstantiateComponents(Bomb bomb) { yield return(new WaitUntil(() => modsLoading == 0)); var bombGenerator = SceneManager.Instance.GameplayState.GetValue <BombGenerator>("bombGenerator"); bombGenerator.SetValue("bomb", bomb); var validBombFaces = new List <BombFace>(bomb.Faces.Where(face => face.ComponentSpawnPoints.Count != 0)); bombGenerator.SetValue("validBombFaces", validBombFaces); List <KMBombInfo> knownBombInfos = new List <KMBombInfo>(); foreach (KMBombInfo item in UnityEngine.Object.FindObjectsOfType <KMBombInfo>()) { knownBombInfos.Add(item); } var bombInfo = allBombInfo[bomb]; var timerFace = bombInfo.TimerFace; var setting = bombInfo.Settings; bombInfo.EnableOriginal = true; bombGenerator.SetValue("rand", bombInfo.Rand); // Bring back the real Random object. UnityEngine.Random.InitState(bomb.Seed); // Loading AudioClips triggers RNG calls so we need to reset the RNG to before that happened. foreach (var bombComponent in bombInfo.Components.OrderByDescending(component => component.RequiresTimerVisibility)) { BombFace face = null; if (bombComponent.RequiresTimerVisibility && timerFace.ComponentSpawnPoints.Count != 0) { face = timerFace; } if (face == null && validBombFaces.Count != 0) { face = validBombFaces[bombInfo.Rand.Next(0, validBombFaces.Count)]; } if (face == null) { Tweaks.Log("No valid faces remain to instantiate:", bombComponent.name); continue; } bombGenerator.CallMethod("InstantiateComponent", face, bombComponent, setting); } while (validBombFaces.Count > 0) { bombGenerator.CallMethod("InstantiateComponent", validBombFaces[0], bombGenerator.emptyComponentPrefab, setting); } // To ensure that the widgets get placed in the right position, we need to temporarily revert the bomb's size. bomb.visualTransform.localScale = Vector3.one; WidgetGenerator generator = bombGenerator.GetComponent <WidgetGenerator>(); generator.SetValue("zones", bombInfo.WidgetZones); generator.GenerateWidgets(bomb.WidgetManager, setting.OptionalWidgetCount); bomb.visualTransform.localScale = new Vector3(bomb.Scale, bomb.Scale, bomb.Scale); bombInfo.Loaded = true; HookUpMultipleBombs(bomb, knownBombInfos); if (BombsLoaded) { SceneManager.Instance.GameplayState.Bombs.AddRange(allBombInfo.Keys); allBombInfo.Clear(); // This fixes the bomb not getting picked up correctly if clicked on before loading is finished. var holdable = KTInputManager.Instance.SelectableManager.GetCurrentFloatingHoldable(); if (holdable) { holdable.Defocus(false, false); } } // Solve any fake modules foreach (BombComponent component in bomb.BombComponents) { if (component.GetComponent <FakeModule>() != null) { component.IsSolved = true; component.Bomb.OnPass(component); } } }
IEnumerator GetModuleInformation(BombComponent bombComponent, ModuleTweak moduleTweak = null) { 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 (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); } // 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) { string[] chunks = JsonConvert.SerializeObject(bombLogInfo).ChunkBy(250).ToArray(); Tweaks.Log("LFABombInfo", chunks.Length + "\n" + chunks.Join("\n")); } }