Пример #1
0
        /*
         * Perform topological sort on mods to ensure order loading order is consistent with dependencies
         */
        private static bool GetCataloguedMods(ModManager __instance, ref IEnumerable <Mod> __result)
        {
            var modsById =
                (Dictionary <string, Mod>) typeof(ModManager).Property("_cataloguedMods").GetValue(__instance);
            var mods   = modsById.Values;
            var outdeg = new Dictionary <string, int>();
            var revDep = new Dictionary <string, List <string> >();

            foreach (var m in mods)
            {
                foreach (var d in m.Dependencies.Select(d => d.ModId))
                {
                    outdeg.Compute(m.Id, (_, x) => x + 1);
                    revDep.ComputeIfAbsent(d, _ => new List <string>()).Add(m.Id);
                }
            }

            var freeMods = mods.Where(m => !outdeg.TryGetValue(m.Id, out var mo) || mo == 0)
                           .Select(m => m.Id)
                           .ToList();

            freeMods.Reverse(); // Reverse to compensate for removing from the end, to match original order, when no dependencies are present
            var ret = new List <Mod>();

            while (freeMods.Count > 0)
            {
                var m = freeMods.Pop();
                if (modsById.TryGetValue(m, out var mod))
                {
                    ret.Add(mod);
                }

                if (revDep.TryGetValue(m, out var rd))
                {
                    freeMods.AddRange(
                        from d in rd
                        where-- outdeg[d] == 0
                        select d
                        );
                }
            }

            foreach (var m in outdeg.Where(m => m.Value > 0).Select(m => m.Key))
            {
                NoonUtility.Log($"[GreatWork] Cyclic dependency detected for mod {m}, loading them in some order", 2,
                                VerbosityLevel.Essential);
                ret.Add(modsById[m]);
            }

            __result = ret;
            return(false);
        }
Пример #2
0
 // stuff to do right away
 void Awake()
 {
     speedMultiplier = Config.Bind("Config", "Speed Multiplier", 1.0f, new ConfigDescription("Speed multiplier for fast mode.\nBe Reasonable! A too high value will make the game completely unplayable.\nA Multiplier of 10 results in an overall speed of 30 seconds per second.", new AcceptableValueRange <float>(1f, 100f)));
     NoonUtility.Log("Configuration Loaded. Speed Multiplier set to: " + speedMultiplier.Value.ToString(), 0, VerbosityLevel.Essential);
     if (speedMultiplier.Value < 1f)
     {
         NoonUtility.Log("Speed Multiplier was less than one, which is not yet supported. Defaulting to 1f.", 0, VerbosityLevel.Essential);
         speedMultiplier.Value = 1f;
     }
     HarmonyInstance = new Harmony("justastranger.fastmodemultiplier");
     HarmonyInstance.PatchAll();
     NoonUtility.Log("Harmony Patches Applied", 0, VerbosityLevel.Essential);
 }
Пример #3
0
        public void RegisterGlobal(Assembly assembly)
        {
            foreach (var type in assembly.GetTypes())
            {
                if (type.GetCustomAttribute(typeof(GwEventHandler)) != null)
                {
                    NoonUtility.Log("Registering " + type);
                    Register(type, null);
                }
            }

            NoonUtility.Log("Event registry for assembly " + assembly.GetName().Name + " complete");
        }
Пример #4
0
 static bool Prefix(Heart __instance)
 {
     try
     {
         float          usualInterval     = (float)AccessTools.Field(typeof(Heart), "BEAT_INTERVAL_SECONDS").GetValue(__instance);
         float          timerBetweenBeats = (float)AccessTools.Field(typeof(Heart), "timerBetweenBeats").GetValue(__instance);
         GameSpeedState gameSpeedState    = (GameSpeedState)AccessTools.Field(typeof(Heart), "gameSpeedState").GetValue(__instance);
         if (fastmodemultiplier.speedMultiplier.Value < 1f)
         {
             NoonUtility.Log("Speed Multiplier was less than one, which is not yet supported. Defaulting to 1f.", 0, VerbosityLevel.Essential);
             fastmodemultiplier.speedMultiplier.Value = 1f;
         }
         timerBetweenBeats += Time.deltaTime;
         AccessTools.Field(typeof(Heart), "timerBetweenBeats").SetValue(__instance, timerBetweenBeats);
         if (timerBetweenBeats > 0.05f)
         {
             timerBetweenBeats -= 0.05f;
             AccessTools.Field(typeof(Heart), "timerBetweenBeats").SetValue(__instance, timerBetweenBeats);
             if (gameSpeedState.GetEffectiveGameSpeed() == GameSpeed.Fast)
             {
                 // NoonUtility.Log("Beat for: " + (0.15f * fastmodemultiplier.speedMultiplier.Value).ToString(), 0, VerbosityLevel.Essential);
                 // __instance.Beat(0.15f);
                 __instance.Beat(0.15f * fastmodemultiplier.speedMultiplier.Value);
             }
             else if (gameSpeedState.GetEffectiveGameSpeed() == GameSpeed.Normal)
             {
                 __instance.Beat(0.05f);
             }
             else if (gameSpeedState.GetEffectiveGameSpeed() == GameSpeed.Paused)
             {
                 __instance.Beat(0f);
             }
             else
             {
                 NoonUtility.Log("Unknown game speed state: " + gameSpeedState.GetEffectiveGameSpeed(), 0, VerbosityLevel.Trivia);
             }
         }
     }
     catch (Exception e)
     {
         NoonUtility.LogException(e);
         throw;
     }
     return(false);
 }
Пример #5
0
        private static Sprite GetSprite(string folder, string file)
        {
            NoonUtility.Log("Loading " + folder + file);
            // Check if a local image exists; if it does, load it first
            string localPath = Application.streamingAssetsPath + "/" + folder + file + ".png";

            if (File.Exists(localPath))
            {
                var fileData = File.ReadAllBytes(localPath);
                var texture  = new Texture2D(2, 2);
                texture.LoadImage(fileData);
                return(Sprite.Create(
                           texture, new Rect(0.0f, 0.0f, texture.width, texture.height), new Vector2(0.5f, 0.5f)));
            }

            // Try to load the image from the packed resources next, and show the placeholder if not found
            Sprite sprite = Resources.Load <Sprite>(folder + file);

            return(sprite != null ? sprite : Resources.Load <Sprite>(folder + PLACEHOLDER_IMAGE_NAME));
        }
        private static void ExportSpriteFolderToFileSystem(string sourceFolder, string ext)
        {
            string exportFolder = Path.Combine(ExportDir, sourceFolder);

            Directory.CreateDirectory(exportFolder);
            var encounteredNames = new HashSet <string>();

            foreach (var asset in Resources.LoadAll <Sprite>(sourceFolder))
            {
                NoonUtility.Log("Asset: " + asset.name);
                if (encounteredNames.Contains(asset.name))
                {
                    NoonUtility.Log("Encountered before!");
                    continue;
                }

                encounteredNames.Add(asset.name);
                string exportPath = Path.Combine(exportFolder, asset.name + "." + ext);
                File.WriteAllBytes(exportPath, GetSpriteAsPng(asset));
                Resources.UnloadAsset(asset);
            }
        }
Пример #7
0
        public void Register(Type t, object instance = default)
        {
            foreach (var mt in t.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public |
                                            BindingFlags.NonPublic))
            {
                var sub = (SubscribeEvent)mt.GetCustomAttribute(typeof(SubscribeEvent));
                if (sub != null)
                {
                    var filters = Enumerable.Empty <EventFilter>();
                    var param   = mt.GetParameters();
                    if (param.Length != 1)
                    {
                        NoonUtility.Log($"Event handlers should only exactly one parameter. {mt} has {param.Length}", 2,
                                        VerbosityLevel.Significants);
                        continue;
                    }

                    var et   = param[0].ParameterType;
                    var link = (EventLink)et.GetCustomAttribute(typeof(EventLink));
                    if (link != null)
                    {
                        et = link.ForEvent;
                    }

                    if (!typeof(Event).IsAssignableFrom(et))
                    {
                        NoonUtility.Log($"Parameter of {mt} is of type {et}, which is not an event", 2,
                                        VerbosityLevel.Significants);
                        continue;
                    }

                    if (!_methods.ContainsKey(et))
                    {
                        _methods[et] = new List <Tuple <MethodInfo, object, IEnumerable <EventFilter> > >();
                    }
                    _methods[et].Add(new Tuple <MethodInfo, object, IEnumerable <EventFilter> >(mt, instance, filters));
                }
            }
        }
Пример #8
0
 static void Postfix(SpeedControlUI __instance)
 {
     try
     {
         GameSpeedState uiShowsGameSpeed = (GameSpeedState)AccessTools.Field(typeof(SpeedControlUI), "uiShowsGameSpeed").GetValue(__instance);
         if (uiShowsGameSpeed.GetEffectiveGameSpeed() == GameSpeed.Fast)
         {
             // GameSpeed is already fast, so that means we need to make it faster!
             fastmodemultiplier.speedMultiplier.Value += 1f;
             // NoonUtility.Log("GetEffectiveGameSpeed() == GameSpeed.Fast, multiplier value set to: " + fastmodemultiplier.speedMultiplier.Value);
         }
         else if (uiShowsGameSpeed.GetEffectiveGameSpeed() == GameSpeed.Normal)
         {
             // decrease speed multiplier to baseline
             fastmodemultiplier.speedMultiplier.Value = 1f;
             // NoonUtility.Log("Set Normal Speed Multiplier");
         }
     }
     catch (Exception e)
     {
         NoonUtility.LogException(e);
     }
 }
        public IList <RecipeExecutionCommand> GetActualRecipesToExecute(Recipe recipe)
        {
            IList <RecipeExecutionCommand> recipeExecutionCommands = new List <RecipeExecutionCommand>()
            {
                new RecipeExecutionCommand(recipe, null)
            };

            if (recipe.AlternativeRecipes.Count == 0)
            {
                return(recipeExecutionCommands);
            }


            foreach (var ar in recipe.AlternativeRecipes)
            {
                int diceResult = dice.Rolld100(recipe);
                if (diceResult > ar.Chance)
                {
                    NoonUtility.Log(recipe.Id + " says: " + "Dice result " + diceResult + ", against chance " +
                                    ar.Chance +
                                    " for alternative recipe " + ar.Id +
                                    "; will try to execute next alternative recipe");
                }
                else
                {
                    // ReSharper disable once PossibleNullReferenceException
                    Recipe candidateRecipe = (compendium as Compendium).GetRecipeById(ar.Id);

                    if (!candidateRecipe.RequirementsSatisfiedBy(aspectsToConsider))
                    {
                        NoonUtility.Log(recipe.Id + " says: couldn't satisfy requirements for " + ar.Id, 5);
                        continue;
                    }

                    if (currentCharacter.HasExhaustedRecipe(candidateRecipe))
                    {
                        NoonUtility.Log(recipe.Id + " says: already exhausted " + ar.Id, 5);
                        continue;
                    }

                    if (ar.Additional)
                    {
                        var command = new Frangiclave.Patches.Assets.Core.Commands.RecipeExecutionCommand(
                            candidateRecipe, ar.Expulsion)
                        {
                            SendAway = ar.Remote
                        };
                        recipeExecutionCommands.Add(command);
                        NoonUtility.Log(recipe.Id + " says: Found additional recipe " + ar.Id +
                                        " to execute - adding it to execution list and looking for more");
                    }
                    else
                    {
                        IList <RecipeExecutionCommand> recursiveRange = GetActualRecipesToExecute(candidateRecipe);

                        string logMessage = recipe.Id + " says: reached the bottom of the execution list: returning ";
                        logMessage =
                            recursiveRange.Aggregate(logMessage, (current, r) => current + r.Recipe.Id + "; ");
                        NoonUtility.Log(logMessage);

                        return(recursiveRange);
                    }
                }
            }

            return(recipeExecutionCommands);
        }
Пример #10
0
        public static void BetterOverwriteOrAdd(Hashtable valuesTable, object key, object value)
        {
            if (valuesTable == null || key == null || !valuesTable.ContainsKey(key))
            {
                NoonUtility.Log(new NoonLogMessage("Couldn't validate valuesTable containing key!"));
                throw new Exception("ValuesTable didn't have the key!");
            }

            // Note: null data and empty data are not the same thing.
            if (value == null)
            {
                return;
            }

            object oldValue = valuesTable[key];

            switch (oldValue)
            {
            case null:
                valuesTable.Add(key, value);
                return;

            case int _ when value is string && int.TryParse(value.ToString(), out int _):
                value = int.Parse(value.ToString());
                break;

            case double _ when value is string && double.TryParse(value.ToString(), out double _):
                value = double.Parse(value.ToString());
                break;
            }

            // We want to support the cases of replacing strings with numbers and numbers with strings.
            // We also want to support the cases of replacing string-XTriggers with an ArrayList of actual XTrigger data.
            // Otherwise, it may be best to notify the modder that something went wrong.
            // In the worst case, we'll need to patch this function with another exception-case.
            if (oldValue.GetType() != value.GetType() &&
                !(oldValue is string && (value is int || value is double || value is ArrayList)) &&
                !((oldValue is int || oldValue is double) && value is string)
                )
            {
                string message = "";
                message += "Tried to overwrite old value '" + oldValue + "' of type '" + oldValue.GetType().FullName
                           + "' in key '" + key + "' with new value '"
                           + value + "' of type " + value.GetType().FullName;
                NoonUtility.Log(new NoonLogMessage(message));

                throw new Exception("Tried to overwrite-or-add with a type other than the old type!");
            }

            // Here's where the magic happens.
            switch (value)
            {
            case ArrayList list:
                if (key.ToString().EndsWith("$prepend"))
                {
                    // Prepend new value to old list
                    list.AddRange((ArrayList)oldValue);
                    valuesTable[key] = list;
                }
                else if (key.ToString().EndsWith("$append") ||
                         key.ToString().EndsWith("$remove") ||
                         key.ToString().Contains("$"))
                {
                    // Append new value to old list
                    ((ArrayList)oldValue).AddRange(list);
                    valuesTable[key] = oldValue;
                }
                else
                {
                    valuesTable[key] = value;
                }

                break;

            case EntityData ed:
                if (oldValue is EntityData oldEd)
                {
                    if (key.ToString().EndsWith("$add") ||
                        key.ToString().Contains("$"))
                    {
                        // Recursive call
                        foreach (string key2 in ed.ValuesTable.Keys)
                        {
                            oldEd.OverwriteOrAdd(key2, ed.GetEntityDataFromValueTable(key2));
                        }

                        valuesTable[key] = oldEd;
                    }
                    else
                    {
                        // Pure replace
                        valuesTable[key] = ed;
                    }
                }
                else
                {
                    // Value and OldValue are the same type, Value is an EntityData, but OldValue isn't.
                    // By all rights, this is unreachable code.
                    throw new Exception("WHAT THE ACTUAL HELL JUST HAPPENED?!?!?");
                }

                break;

            case int v when(oldValue is int old && (key.ToString().EndsWith("$plus") || key.ToString().EndsWith("$minus"))):
                // x + a + b = x + (a+b); x - a - b = x - (a+b)
                // The same effect is achieved by adding b to a.
                valuesTable[key] = old + v;

                break;

            case double v when(oldValue is double old && (key.ToString().EndsWith("$plus") || key.ToString().EndsWith("$minus"))):
                // x + a + b = x + (a+b); x - a - b = x - (a+b)
                // The same effect is achieved by adding b to a.
                valuesTable[key] = old + v;

                break;

            default:
                // "Whether [the] modder wants [it] or not, default syntax means replacing, and it should stay that way for the sake of compatibility"
                // - Chelnoque, untitled one
                valuesTable[key] = value;
                break;
            }
        }