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