Example #1
0
    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.");
        }
    }
Example #2
0
    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);
            }
        }
    }
Example #3
0
    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;
        }
    }
Example #4
0
    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;
    }
Example #5
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"));
        }
    }
Example #6
0
    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"));
        }
    }