/// <summary> /// Replaces all instructions between the last AND and the RET immediately after /// it with the specified method call. It should return bool and has the bits5 of A /// and B on the stack (in the OR case it also has the values from the previous /// bit compares). /// </summary> /// <param name="method">The method to transpile.</param> /// <param name="replacement">The method to call instead.</param> /// <returns>true if the method was transpiled, or false if the references were not /// found.</returns> private static bool ReplaceAnd(List <CodeInstruction> method, MethodBase replacement) { int n = method.Count; bool transpiled = false; for (int i = n - 1; i > 0; i--) { var instr = method[i]; // LAST and if (instr.opcode == OpCodes.And) { int and = ++i; // Find the next RET or branch after it for (; i < n && !transpiled; i++) { var ni = method[i]; var opcode = ni.opcode; // Ignore branches that branch to the immediately following statement if (opcode == OpCodes.Ret || ((opcode == OpCodes.Br || opcode == OpCodes.Br_S) && (!(ni.operand is Label lbl) || i >= n || !method[i + 1].labels.Contains(lbl)))) { // Replace the AND with static tag compare instr.opcode = OpCodes.Call; instr.operand = replacement; // Remove everything in between if (i > and) { method.RemoveRange(and, i - and); } transpiled = true; } } break; } } if (!transpiled) { PUtil.LogWarning("Unable to transpile method {0}".F(replacement.Name)); } return(transpiled); }
/// <summary> /// Creates an options entry if an attribute is a valid IOptionSpec or /// DynamicOptionAttribute. /// </summary> /// <param name="attribute">The attribute to parse.</param> /// <param name="prop">The property to inspect.</param> /// <param name="depth">The current depth of iteration to avoid infinite loops.</param> /// <returns>The OptionsEntry created from the attribute, or null if none was.</returns> private static IOptionsEntry TryCreateEntry(Attribute attribute, PropertyInfo prop, int depth) { IOptionsEntry result = null; if (prop == null) { throw new ArgumentNullException(nameof(prop)); } if (attribute is IOptionSpec spec) { if (string.IsNullOrEmpty(spec.Title)) { spec = HandleDefaults(spec, prop); } // Attempt to find a class that will represent it var type = prop.PropertyType; result = FindOptionClass(spec, prop); // See if it has entries that can themselves be added, ignore // value types and avoid infinite recursion if (result == null && !type.IsValueType && depth < 16 && type != prop.DeclaringType) { result = CompositeOptionsEntry.Create(spec, prop, depth); } } else if (attribute is DynamicOptionAttribute doa && typeof(IOptionsEntry).IsAssignableFrom(doa.Handler)) { try { result = Activator.CreateInstance(doa.Handler) as IOptionsEntry; } catch (TargetInvocationException e) { PUtil.LogError("Unable to create option handler for property " + prop.Name + ":"); PUtil.LogException(e.GetBaseException() ?? e); } catch (MissingMethodException) { PUtil.LogWarning("Unable to create option handler for property " + prop.Name + ", it must have a public default constructor"); } } return(result); }
/// <summary> /// Tries to move a Duplicant to a more sensible location when they are about to fall. /// </summary> /// <param name="instance">The fall monitor to update if successful.</param> /// <param name="navigator">The Duplicant to check.</param> /// <param name="layer">The location history of the Duplicant.</param> /// <returns>true if the Duplicant was successfully moved away from entombment, or /// false otherwise.</returns> private static bool TryEscapeFalling(LocationHistoryTransitionLayer layer, Navigator navigator, FallMonitor.Instance instance, ref bool flipEmote) { bool moved = false; for (int i = 0; i < LocationHistoryTransitionLayer.TRACK_CELLS; i++) { int last = layer.VisitedCells[i], above = Grid.CellAbove(last); #if DEBUG PUtil.LogDebug("{0} is falling, trying to move to {1:D}".F(navigator. gameObject?.name, last)); #endif if (Grid.IsValidCell(last) && IsValidNavCell(navigator, last)) { ForceMoveTo(instance, last, navigator, ref flipEmote); break; } } return(moved); }
/// <summary> /// Attempts to also patch the Decor Reimagined implementation of DecorProvider. /// Refresh. /// </summary> /// <param name="harmony">The Harmony instance to use for patching.</param> internal static void ApplyPatch(Harmony harmony) { var patchMethod = new HarmonyMethod(typeof(DecorProviderRefreshFix), nameof( TranspileRefresh)); var targetMethod = PPatchTools.GetTypeSafe( "ReimaginationTeam.DecorRework.DecorSplatNew", "DecorReimagined")?. GetMethodSafe("RefreshDecor", false, PPatchTools.AnyArguments); if (targetMethod != null) { PUtil.LogDebug("Patching Decor Reimagined for DecorProvider.RefreshDecor"); harmony.Patch(targetMethod, transpiler: patchMethod); } PUtil.LogDebug("Patching DecorProvider.Refresh"); harmony.Patch(typeof(DecorProvider).GetMethodSafe(nameof(DecorProvider.Refresh), false, PPatchTools.AnyArguments), transpiler: patchMethod); harmony.Patch(typeof(RoomProber), nameof(RoomProber.Sim1000ms), prefix: new HarmonyMethod(typeof(DecorProviderRefreshFix), nameof(PrefixRoomProbe))); ROOMS_PENDING.Clear(); }
public static void OnLoad() { StartLogging(); PUtil.InitLibrary(false); PUtil.RegisterPostload(CompatabilityPatches.DoPatches); POptions.RegisterOptions(typeof(CarbonOption)); Traverse.Create <OilFloaterConfig>().Field <float>("KG_ORE_EATEN_PER_CYCLE").Value = 40f; Traverse.Create <OilFloaterConfig>().Field <float>("CALORIES_PER_KG_OF_ORE").Value = OilFloaterTuning.STANDARD_CALORIES_PER_CYCLE / 40f; Traverse.Create <OilFloaterHighTempConfig>().Field <float>("KG_ORE_EATEN_PER_CYCLE").Value = 40f; Traverse.Create <OilFloaterHighTempConfig>().Field <float>("CALORIES_PER_KG_OF_ORE").Value = OilFloaterTuning.STANDARD_CALORIES_PER_CYCLE / 40f; // Add Coalplant crop type TUNING.CROPS.CROP_TYPES.Add( new Crop.CropVal("Carbon", CoalPlantConfig.LIFECYCLE, (int)CoalPlantConfig.COAL_PRODUCED)); var RESONANT_NUM_SEEDS = ResonantPlantConfig.COAL_PRODUCED_TOTAL / ResonantPlantConfig.COAL_PER_SEED; TUNING.CROPS.CROP_TYPES.Add( new Crop.CropVal(ResonantPlantConfig.SEED_ID, ResonantPlantConfig.LIFECYCLE, (int)RESONANT_NUM_SEEDS)); }
/// <summary> /// Tries to move a Duplicant to a more sensible location when entombed or falling. /// </summary> /// <param name="layer">The location history of the Duplicant.</param> /// <param name="navigator">The Duplicant to check.</param> /// <param name="instance">The fall monitor to update if successful.</param> /// <returns>true if the Duplicant was successfully moved away, or false otherwise.</returns> private static bool TryEscape(LocationHistoryTransitionLayer layer, Navigator navigator, FallMonitor.Instance instance, ref bool flipEmote) { bool moved = false; for (int i = 0; i < LocationHistoryTransitionLayer.TRACK_CELLS; i++) { int last = layer.VisitedCells[i]; if (Grid.IsValidCell(last) && IsValidNavCell(navigator, last)) { PUtil.LogDebug("{0} is in trouble, trying to escape to {1:D}".F(navigator. gameObject?.name, last)); ForceMoveTo(instance, last, navigator, ref flipEmote); // Prevents a loop back and forth between two cells in the history layer.Reset(); break; } } return(moved); }
/// <summary> /// Transpiles Render to disable the actual Graphics.DrawMesh call. /// </summary> internal static TranspiledMethod Transpiler(TranspiledMethod instructions) { var drawMesh = typeof(Graphics).GetMethodSafe(nameof(Graphics.DrawMesh), true, typeof(Mesh), typeof(Vector3), typeof(Quaternion), typeof(Material), typeof(int), typeof(Camera), typeof(int), typeof(MaterialPropertyBlock)); var newMethod = instructions; if (drawMesh != null) { newMethod = PPatchTools.RemoveMethodCall(instructions, drawMesh); } else { PUtil.LogWarning("Unable to patch FallingWater.Render"); } foreach (var instr in newMethod) { yield return(instr); } }
/// <summary> /// Avoids stacking up queues by waiting for the async path probe. Game updates almost /// all handlers that use pathfinding (including BrainScheduler) in a LateUpdate call, /// so ensure it ends in time. /// </summary> internal void EndJob() { var jobManager = AsyncJobManager.Instance; if (jobManager != null && running) { var now = Stopwatch.StartNew(); if (onPathDone.WaitAndMeasure(FastTrackMod.MAX_TIMEOUT)) { Metrics.DebugMetrics.LogPathProbe(now.ElapsedTicks, totalRuntime); } else { PUtil.LogWarning("Path probing did not complete within the timeout!"); } jobCount = 0; totalRuntime = 0L; running = false; } }
/// <summary> /// Pan the camera's view from its current matrix when the activity starts to a new /// matrix so that the view bounds will contain (if possible, intersect if not /// possible) the new bounds in the camera layers' coordinate system. /// </summary> /// <param name="panToBounds">The bounds to pan the view to.</param> /// <param name="duration">The amount of time that the animation should take.</param> /// <returns> /// The newly scheduled activity, if the duration is greater than 0; else null. /// </returns> /// <remarks> /// If the duration is 0 then the view will be transformed immediately, and null will /// be returned. Else a new PTransformActivity will get returned that is set to /// animate the camera’s view matrix to the new bounds. /// </remarks> public virtual PTransformActivity AnimateViewToPanToBounds(RectangleF panToBounds, long duration) { SizeF delta = PUtil.DeltaRequiredToContain(ViewBounds, panToBounds); if (delta.Width != 0 || delta.Height != 0) { if (duration == 0) { TranslateViewBy(-delta.Width, -delta.Height); } else { PMatrix m = ViewMatrix; m.TranslateBy(-delta.Width, -delta.Height); return(AnimateViewToMatrix(m, duration)); } } return(null); }
/// <summary> /// Checks for compatibility and applies fast fetch manager updates only if Efficient /// Supply is not enabled. /// </summary> /// <param name="harmony">The Harmony instance to use for patching.</param> private static void CheckFetchCompat(Harmony harmony) { if (PPatchTools.GetTypeSafe("PeterHan.EfficientFetch.EfficientFetchManager") == null) { PathPatches.AsyncBrainGroupUpdater.AllowFastListSwap = true; harmony.Patch(typeof(FetchManager.FetchablesByPrefabId), nameof(FetchManager.FetchablesByPrefabId.UpdatePickups), prefix: new HarmonyMethod(typeof(GamePatches.FetchManagerFastUpdate), nameof(GamePatches.FetchManagerFastUpdate.BeforeUpdatePickups))); #if DEBUG PUtil.LogDebug("Patched FetchManager for fast pickup updates"); #endif } else { PUtil.LogWarning("Disabling fast pickup updates: Efficient Supply active"); PathPatches.AsyncBrainGroupUpdater.AllowFastListSwap = false; } }
/// <summary> /// Transpiles AddTechItem to remove an Add call that duplicates every item, as it was /// already added to the constructor. /// </summary> internal static TranspiledMethod Transpiler(TranspiledMethod instructions) { var target = typeof(ResourceSet <TechItem>).GetMethodSafe(nameof( ResourceSet <TechItem> .Add), false, typeof(TechItem)); foreach (var instr in instructions) { if (target != null && instr.Is(OpCodes.Callvirt, target)) { // Original method was 1 arg to 1 arg and result was ignored, so just rip // it out instr.opcode = OpCodes.Nop; instr.operand = null; #if DEBUG PUtil.LogDebug("Patched TechItems.AddTechItem"); #endif } yield return(instr); } }
public override void Process(uint when, object _) { if (patches.TryGetValue(when, out PrivateRunList atTime) && atTime != null && atTime.Count > 0) { string stage = RunAt.ToString(when); #if DEBUG PRegistry.LogPatchDebug("Executing {0:D} handler(s) from {1} for stage {2}".F( atTime.Count, Assembly.GetExecutingAssembly().GetNameSafe() ?? "?", stage)); #endif foreach (var patch in atTime) { try { patch.Run(harmony); } catch (TargetInvocationException e) { // Use the inner exception PUtil.LogError("Error running patches for stage " + stage + ":"); PUtil.LogException(e.GetBaseException()); } }
/// <summary> /// Writes a mod's settings to its configuration file. /// </summary> /// <param name="settings">The settings to write.</param> /// <param name="path">The path to the settings file.</param> /// <param name="indent">true to indent the output, or false to leave it in one line.</param> internal static void WriteSettings(object settings, string path, bool indent = false) { if (settings != null) { try { // SharedConfigLocation Directory.CreateDirectory(Path.GetDirectoryName(path)); using (var jw = new JsonTextWriter(File.CreateText(path))) { var serializer = new JsonSerializer { MaxDepth = MAX_SERIALIZATION_DEPTH }; serializer.Formatting = indent ? Formatting.Indented : Formatting.None; // Serialize from stream avoids creating file text in memory serializer.Serialize(jw, settings); } } catch (UnauthorizedAccessException e) { // Options cannot be set PUtil.LogExcWarn(e); } }
/// <summary> /// Create a new dynamic options entry with no backing property. All handling of this /// entry must be done by the user options class. /// </summary> /// <param name="handler">The handler for that option.</param> /// <returns>A dynamic options entry for that type.</returns> internal static DynamicOptionsEntry Create(object handler) { string category = null; if (handler != null) { // Retrieve the category from the user handler var getter = handler.GetType().GetPropertySafe <string>(nameof(IDynamicOption. Category), false)?.GetGetMethod(); if (getter != null) { try { category = getter.Invoke(handler, null)?.ToString(); } catch (TargetInvocationException e) { PUtil.LogExcWarn(e); } } } return(new DynamicOptionsEntry("Dynamic", category, handler)); }
public override void OnLoad(Harmony harmony) { base.OnLoad(harmony); PUtil.InitLibrary(); new PPatchManager(harmony).RegisterPatchClass(typeof(PipPlantOverlayPatches)); LocString.CreateLocStringKeys(typeof(PipPlantOverlayStrings.INPUT_BINDINGS)); PipPlantOverlayTests.SymmetricalRadius = false; OpenOverlay = new PActionManager().CreateAction(PipPlantOverlayStrings. OVERLAY_ACTION, PipPlantOverlayStrings.INPUT_BINDINGS.ROOT.PIPPLANT); new PLocalization().Register(); // If possible, make farming status items appear properly in pip plant mode var overlayBitsField = typeof(StatusItem).GetFieldSafe("overlayBitfieldMap", true); if (overlayBitsField != null && overlayBitsField.GetValue(null) is IDictionary <HashedString, StatusItemOverlays> overlayBits) { overlayBits.Add(PipPlantOverlay.ID, StatusItemOverlays.Farming); } new PVersionCheck().Register(this, new SteamVersionChecker()); }
public override void OnLoad(Harmony harmony) { PUtil.InitLibrary(); Options = new POptions(); Settings = POptions.ReadSettings <ModSettings>(); if (Settings == null) { Settings = new ModSettings(); POptions.WriteSettings(Settings); } Options.RegisterOptions(this, typeof(ModSettings)); ModAssets.LoadAssets(); Registry = RomenHRegistry.Init(); base.OnLoad(harmony); }
/// <summary> /// Copies data from the file system and source zip file to the new combined backup /// zip file. /// </summary> /// <param name="src">The source mod data with no configs.</param> /// <param name="dst">The temporary file destination.</param> /// <param name="toCopy">The files to copy from the source.</param> /// <param name="toAdd">The files to add from the current mod directory.</param> private void CopyFiles(ZipFile src, ZipFile dst, ISet <string> toCopy, IDictionary <string, string> toAdd) { foreach (var entry in src) { #if DEBUG PUtil.LogDebug("ConfigBackupUtility add existing file " + entry.FileName); #endif dst.AddEntry(entry.FileName, (name, stream) => src[name].Extract(stream)); } foreach (var pair in toAdd) { // Avoid capturing the wrong pair value string target = pair.Value, entryName = pair.Key; if (!dst.ContainsEntry(entryName)) { dst.AddEntry(entryName, (name, os) => CopyFromModFolder(name, target, os)); } } }
// misc bookkeeping public static void OnLoad() { StartLogging(); AddDiseaseName(SlimeLethalSickness.ID, DUPLICANTS.DISEASES.SLIMESICKNESS.NAME + " (lethal)"); AddDiseaseName(SlimeCoughSickness.ID, DUPLICANTS.DISEASES.SLIMESICKNESS.NAME + " (cough)"); AddDiseaseName(FoodPoisonVomiting.ID, DUPLICANTS.DISEASES.FOODSICKNESS.NAME + " (vomiting)"); SkipNotifications.Skip(SlimeLethalSickness.ID); SkipNotifications.Skip(SlimeCoughSickness.ID); SkipNotifications.Skip(FoodPoisonVomiting.ID); ImaginationLoader.Init(typeof(DiseasesPatch)); PUtil.RegisterPostload(CompatPatch.CompatPatches); BuildingsPatch.uvlight = PLightShape.Register("SkyLib.LightShape.FixedSemi", BuildingsPatch.SemicircleLight); }
public static void OnLoad(string path) { PUtil.InitLibrary(); POptions.RegisterOptions(typeof(ModUpdateInfo)); LocString.CreateLocStringKeys(typeof(ModUpdateDateStrings.UI)); PLocalization.Register(); // Try to read the backup config first string backupPath = ExtensionMethods.BackupConfigPath; if (File.Exists(backupPath)) { try { // Copy and overwrite our config if possible File.Copy(backupPath, ExtensionMethods.ConfigPath, true); File.Delete(backupPath); PUtil.LogDebug("Restored configuration settings after self-update"); } catch (IOException) { PUtil.LogWarning("Unable to restore configuration for Mod Updater"); } }
/// <summary> /// Creates a resource category header for critters. /// </summary> /// <param name="resList">The parent category screen for this header.</param> /// <param name="prefab">The prefab to use for creating the headers.</param> /// <param name="type">The critter type to create.</param> /// <returns>The heading for that critter type.</returns> public static ResourceCategoryHeader Create(ResourceCategoryScreen resList, GameObject prefab, CritterType type) { var tag = GameTags.BagableCreature; string typeStr = type.GetDescription(); // Create a heading for Critter (Type) PUtil.LogDebug("Creating Critter ({0}) category".F(typeStr)); var gameObject = Util.KInstantiateUI(prefab, resList.CategoryContainer.gameObject, false); gameObject.name = "CategoryHeader_{0}_{1}".F(tag.Name, type.ToString()); var header = gameObject.GetComponent <ResourceCategoryHeader>(); header.SetTag(tag, GameUtil.MeasureUnit.quantity); // Tag it with a wild/tame tag header.gameObject.AddComponent <CritterResourceInfo>().CritterType = type; header.elements.LabelText.SetText("{0} ({1})".F(tag.ProperName(), typeStr)); return(header); }
/// <summary> /// Common transpiled target method for each use of PopFXManager.SpawnFX. /// </summary> private static PopFX SpawnFXShort(PopFXManager instance, Sprite icon, string text, Transform targetTransform, float lifetime, bool track_target, object source) { PopFX popup = null; bool show = true; try { // Parameter count cannot be reduced - in order to conform with Klei method show = ToastControlPopups.ShowPopup(source, text); } catch (Exception e) { // Sometimes this gets executed on a background thread and unhandled exceptions // cause a CTD PUtil.LogException(e); } if (show) { popup = instance.SpawnFX(icon, text, targetTransform, lifetime, track_target); } return(popup); }
/// <summary> /// Toggles empty storage on the object. /// </summary> /// <param name="cell">The cell this building occupies.</param> /// <param name="item">The item to toggle.</param> /// <param name="enable">true to schedule for emptying, or false to disable it.</param> /// <returns>true if changes were made, or false otherwise.</returns> private bool ToggleEmptyStorage(int cell, GameObject item, bool enable) { var daw = item.GetComponentSafe <DropAllWorkable>(); bool changed = false; if (daw != null) { if ((Traverse.Create(daw).GetField <Chore>("chore") != null) != enable) { daw.DropAll(); #if DEBUG var xy = Grid.CellToXY(cell); PUtil.LogDebug("Empty storage {3} @({0:D},{1:D}) = {2}".F(xy.X, xy.Y, enable, item.GetProperName())); #endif changed = true; } } return(changed); }
/// <summary> /// Loads all codex entries for all mods registered. /// </summary> /// <param name="lockKey">Key for shared data lock.</param> /// <param name="tableKey">Key for shared data table.</param> /// <param name="category">The codex category under which these data entries should be loaded.</param> /// <returns>The list of entries that were loaded.</returns> private static IList <CodexEntry> LoadEntries(string lockKey, string tableKey, string category) { var entries = new List <CodexEntry>(32); lock (PSharedData.GetLock(lockKey)) { var table = PSharedData.GetData <IList <string> >(tableKey); if (table != null) { foreach (string dir in table) { #if DEBUG PUtil.LogDebug("Loaded codex entries from directory: {0}".F(dir)); #endif LoadFromDirectory(entries, dir, category); } } } return(entries); }
/// <summary> /// Applied before UpdatePickups runs. /// </summary> internal static bool Prefix(FetchManager.FetchablesByPrefabId __instance, Navigator worker_navigator, GameObject worker_go, Dictionary <int, int> ___cellCosts) { var inst = EfficientFetchManager.Instance; bool cont = true; if (inst != null && options.MinimumAmountPercent > 0) { try { inst.UpdatePickups(__instance, worker_navigator, worker_go, ___cellCosts); cont = false; } catch (Exception e) { // Crashing will bring down simdll with no stack trace PUtil.LogException(e); } } return(cont); }
public static void OnLoad(string path) { PUtil.InitLibrary(); if (DebugNotIncludedOptions.Instance?.DetailedBacktrace ?? true) { DebugLogger.InstallExceptionLogger(); } POptions.RegisterOptions(typeof(DebugNotIncludedOptions)); if (DebugNotIncludedOptions.Instance?.LogAsserts ?? true) { LogAllFailedAsserts(); } // Patch the exception logger for state machines var logException = typeof(DebugUtil).GetMethodSafe("LogException", true, PPatchTools.AnyArguments); if (logException != null) { ModDebugRegistry.Instance.DebugInstance.Patch(logException, prefix: new HarmonyMethod(typeof(DebugLogger), nameof(DebugLogger.LogException))); } foreach (var mod in Global.Instance.modManager?.mods) { if (mod.label.install_path == path) { ThisMod = mod; break; } } // Default UI debug key is ALT+U UIDebugAction = PAction.Register("DebugNotIncluded.UIDebugAction", DebugNotIncludedStrings.KEY_SNAPSHOT, new PKeyBinding(KKeyCode.U, Modifier.Alt)); if (ThisMod == null) { DebugLogger.LogWarning("Unable to determine KMod instance!"); } // Must postload the mods dialog to come out after aki's mods, ony's mods, PLib // options, and so forth PUtil.RegisterPostload(PostloadHandler); }
/// <summary> /// Overridden. Do auto-panning even when the mouse is not moving. /// </summary> /// <param name="sender">The source of the drag event.</param> /// <param name="e">A PInputEventArgs that contains the event data.</param> protected override void OnDragActivityStep(object sender, PInputEventArgs e) { base.OnDragActivityStep(sender, e); if (!autopan) { return; } PCamera c = e.Camera; RectangleFx b = c.Bounds; PointFx l = e.GetPositionRelativeTo(c); PUtil.OutCode outcode = PUtil.RectangleOutCode(l, b); SizeFx delta = SizeFx.Empty; if ((outcode & PUtil.OutCode.Top) != 0) { delta.Height = ValidatePanningDelta(-1.0f - (0.5f * Math.Abs(l.Y - b.Y))); } else if ((outcode & PUtil.OutCode.Bottom) != 0) { delta.Height = ValidatePanningDelta(1.0f + (0.5f * Math.Abs(l.Y - (b.Y + b.Height)))); } if ((outcode & PUtil.OutCode.Right) != 0) { delta.Width = ValidatePanningDelta(1.0f + (0.5f * Math.Abs(l.X - (b.X + b.Width)))); } else if ((outcode & PUtil.OutCode.Left) != 0) { delta.Width = ValidatePanningDelta(-1.0f - (0.5f * Math.Abs(l.X - b.X))); } delta = c.LocalToView(delta); if (delta.Width != 0 || delta.Height != 0) { c.TranslateViewBy(delta.Width, delta.Height); } }
/// <summary> /// Transpiles OnPrefabInit to rotate the offset table before building it. /// </summary> internal static TranspiledMethod Transpiler(TranspiledMethod method, MethodBase __originalMethod) { var target = typeof(OffsetGroups).GetMethodSafe(nameof(OffsetGroups. BuildReachabilityTable), true, typeof(CellOffset[]), typeof(CellOffset[][]), typeof(CellOffset[])); var replacement = typeof(StockBugsPatches).GetMethodSafe(nameof(StockBugsPatches. RotateAndBuild), true, typeof(CellOffset[]), typeof(CellOffset[][]), typeof(CellOffset[]), typeof(KMonoBehaviour)); bool patched = false; string sig = __originalMethod.DeclaringType.FullName + "." + __originalMethod.Name; if (target != null && replacement != null) { foreach (var instr in method) { if (instr.Is(OpCodes.Call, target)) { yield return(new CodeInstruction(OpCodes.Ldarg_0)); instr.operand = replacement; #if DEBUG PUtil.LogDebug("Patched " + sig); #endif patched = true; } yield return(instr); } } else { foreach (var instr in method) { yield return(instr); } } if (!patched) { PUtil.LogWarning("Unable to patch " + sig); } }
/// <summary> /// Transpiles StopAnim to throw away the event handle properly. /// </summary> internal static TranspiledMethod Transpiler(TranspiledMethod instructions) { MethodInfo target = null, setData = null; target = typeof(KCompactedVector <IndirectionData>).GetMethodSafe(nameof( KCompactedVector <IndirectionData> .Free), false, typeof(HandleVector <int> . Handle)); setData = typeof(KCompactedVector <IndirectionData>).GetMethodSafe(nameof( KCompactedVector <IndirectionData> .SetData), false, typeof(HandleVector <int> . Handle), typeof(IndirectionData)); if (target != null && setData != null) { foreach (var instr in instructions) { if (instr.Is(OpCodes.Callvirt, setData)) { // Remove "data" yield return(new CodeInstruction(OpCodes.Pop)); // Call Free yield return(new CodeInstruction(OpCodes.Callvirt, target)); // Pop the retval instr.opcode = OpCodes.Pop; instr.operand = null; #if DEBUG PUtil.LogDebug("Patched AnimEventManager.StopAnim"); #endif } yield return(instr); } } else { PUtil.LogWarning("Unable to patch AnimEventManager.StopAnim"); foreach (var instr in instructions) { yield return(instr); } } }
/// <summary> /// Invoked when the manual config button is pressed. /// </summary> private void OnManualConfig(GameObject _) { string uri = null; try { uri = new Uri(Path.GetDirectoryName(path)).AbsoluteUri; } catch (UriFormatException e) { PUtil.LogWarning("Unable to convert parent of " + path + " to a URI:"); PUtil.LogExcWarn(e); } if (!string.IsNullOrEmpty(uri)) { // Open the config folder, opening the file itself might start an unknown // editor which could execute the json somehow... WriteOptions(); CloseDialog(); PUtil.LogDebug("Opening config folder: " + uri); Application.OpenURL(uri); CheckForRestart(); } }
/// <summary> /// Applied after OnButcherComplete runs. /// </summary> internal static void Postfix(Butcherable __instance) { var obj = __instance.gameObject; bool natural = false; if (obj != null) { var smi = obj.GetSMI <AgeMonitor.Instance>(); if (smi != null) { natural = smi.CyclesUntilDeath < (1.0f / Constants.SECONDS_PER_CYCLE); } #if DEBUG PUtil.LogDebug("Critter died: " + (natural ? "old age" : "prematurely")); #endif if (!natural) { AchievementStateComponent.OnCritterKilled(); } } }