/// <summary>
        /// Checks to see if another mod is installed, based on a provided assembly name.
        /// </summary>
        /// <param name="assemblyName">Name of the mod assembly</param>
        /// <param name="enabledOnly">True if the mod needs to be enabled for the purposes of this check; false if it doesn't matter</param>
        /// <returns>True if the mod is installed (and, if enabledOnly is true, is also enabled), false otherwise</returns>
        internal bool IsModInstalled(string assemblyName, bool enabledOnly = false)
        {
            // Convert assembly name to lower case.
            string assemblyNameLower = assemblyName.ToLower();

            // Iterate through the full list of plugins.
            foreach (PluginManager.PluginInfo plugin in PluginManager.instance.GetPluginsInfo())
            {
                foreach (Assembly assembly in plugin.GetAssemblies())
                {
                    if (assembly.GetName().Name.ToLower().Equals(assemblyNameLower))
                    {
                        Debugging.Message("found mod assembly " + assemblyName);
                        if (enabledOnly)
                        {
                            return(plugin.isEnabled);
                        }
                        else
                        {
                            return(true);
                        }
                    }
                }
            }

            // If we've made it here, then we haven't found a matching assembly.
            return(false);
        }
        public static void DieRev(object instance, uint citizenID, ref Citizen data)
        {
            string message = "Die reverse Harmony patch wasn't applied";

            Debugging.Message(message);
            throw new NotImplementedException(message);
        }
Beispiel #3
0
        /// <summary>
        /// Attaches an event hook to options panel visibility, to create/destroy our options panel as appropriate.
        /// Destroying when not visible saves UI overhead and performance impacts, especially with so many UITextFields.
        /// </summary>
        public static void OptionsEventHook()
        {
            // Get options panel instance.
            gameOptionsPanel = UIView.library.Get <UIPanel>("OptionsPanel");

            if (gameOptionsPanel == null)
            {
                Debugging.Message("couldn't find OptionsPanel");
            }
            else
            {
                // Simple event hook to create/destroy GameObject based on appropriate visibility.
                gameOptionsPanel.eventVisibilityChanged += (control, isVisible) =>
                {
                    // Create/destroy based on visible.
                    if (isVisible)
                    {
                        Create();
                    }
                    else
                    {
                        Close();
                    }
                };

                // Recreate panel on system locale change.
                LocaleManager.eventLocaleChanged += LocaleChanged;
            }
        }
Beispiel #4
0
        /// <summary>
        /// Adds death options tab to tabstrip.
        /// </summary>
        /// <param name="tabStrip">Tab strip to add to</param>
        /// <param name="tabIndex">Index number of tab</param>
        public DeathOptions(UITabstrip tabStrip, int tabIndex)
        {
            // Add tab.
            UIPanel deathTab = PanelUtils.AddTab(tabStrip, "Death", tabIndex, true);

            // Percentage of corpses requiring transport.  % of bodies requiring transport is more intuitive to user than % of vanishing corpses, so we invert the value.
            UISlider vanishingStiffs = PanelUtils.AddSliderWithValue(deathTab, "% of dead bodies requiring deathcare transportation\r\n(Game default 67%, mod default 50%)", 0, 100, 1, 100 - DataStore.autoDeadRemovalChance, (value) => { });

            // Reset to saved button.
            UIButton vanishingStiffReset = PanelUtils.CreateButton(deathTab, "Reset to saved");

            vanishingStiffReset.eventClicked += (control, clickEvent) =>
            {
                // Retrieve saved value from datastore - inverted value (see above).
                vanishingStiffs.value = 100 - DataStore.autoDeadRemovalChance;
            };

            // Turn off autolayout to fit next button to the right at the same y-value and increase button Y-value to clear slider.
            //deathTab.autoLayout = false;
            vanishingStiffReset.relativePosition = new Vector3(vanishingStiffReset.relativePosition.x, vanishingStiffReset.relativePosition.y + 30);

            // Save settings button.
            UIButton vanishingStiffsSave = PanelUtils.CreateButton(deathTab, "Save and apply");

            vanishingStiffsSave.relativePosition = PanelUtils.PositionRightOf(vanishingStiffReset);
            vanishingStiffsSave.eventClicked    += (control, clickEvent) =>
            {
                // Update mod settings - inverted value (see above).
                DataStore.autoDeadRemovalChance = 100 - (int)vanishingStiffs.value;
                Debugging.Message("autoDeadRemovalChance set to: " + DataStore.autoDeadRemovalChance);

                // Update WG configuration file.
                PanelUtils.SaveXML();
            };
        }
        /// <summary>
        /// Adds speed options tab to tabstrip.
        /// </summary>
        /// <param name="tabStrip">Tab strip to add to</param>
        /// <param name="tabIndex">Index number of tab</param>
        public SpeedOptions(UITabstrip tabStrip, int tabIndex)
        {
            // Add tab.
            UIPanel speedTab = PanelUtils.AddTab(tabStrip, "Speed", tabIndex, true);

            // Lifespan multiplier.  Simple integer.
            UISlider lifeMult = PanelUtils.AddSliderWithValue(speedTab, "Factor to slow down how fast citizens age, compared to base game.\r\n\r\n1 is base game speed (35 in-game weeks is equal to 10 years of age), 2 is twice as slow (70 in-game weeks for 10 years of age), 3 is three times as slow, etc.  A multiplier of 15 means that citizens will age approximately one year for each in-game year.\r\n\r\nDoes not affect lifecycles or ages - only the speed at which citizens age relative to game speed.\r\n\r\n(Game default 1, mod default 3)", 1f, 15f, 1f, DataStore.lifeSpanMultiplier, (value) => { });

            // Reset to saved button.
            UIButton lifeMultReset = PanelUtils.CreateButton(speedTab, "Reset to saved");

            lifeMultReset.eventClicked += (control, clickEvent) =>
            {
                // Retrieve saved value from datastore - inverted value (see above).
                lifeMult.value = DataStore.lifeSpanMultiplier;
            };

            // Turn off autolayout to fit next button to the right at the same y-value and increase button Y-value to clear slider.
            //speedTab.autoLayout = false;
            lifeMultReset.relativePosition = new Vector3(lifeMultReset.relativePosition.x, lifeMult.relativePosition.y + 40);

            // Save settings button.
            UIButton lifeMultSave = PanelUtils.CreateButton(speedTab, "Save and apply");

            lifeMultSave.relativePosition = PanelUtils.PositionRightOf(lifeMultReset);
            lifeMultSave.eventClicked    += (control, value) =>
            {
                // Update mod settings - inverted value (see above).
                DataStore.lifeSpanMultiplier = (int)lifeMult.value;
                Debugging.Message("lifespan multiplier set to: " + DataStore.lifeSpanMultiplier);

                // Update WG configuration file.
                PanelUtils.SaveXML();
            };
        }
Beispiel #6
0
        /// <summary>
        /// Manually applies Harmony patches.
        /// </summary>
        public static void ApplyPrefix(MethodInfo originalMethod, MethodInfo patchMethod)
        {
            Harmony harmonyInstance = new Harmony(harmonyID);

            // Check if the patch is already installed before proceeding.
            if (!IsPrefixInstalled(originalMethod))
            {
                if (originalMethod == null)
                {
                    string message = "null original method passed for patching";
                    Debugging.Message(message);
                    throw new UnassignedReferenceException(message);
                }

                if (patchMethod == null)
                {
                    string message = "null patch method passed for patching";
                    Debugging.Message(message);
                    throw new UnassignedReferenceException(message);
                }

                Debugging.Message("patching " + originalMethod.Name);
                harmonyInstance.Patch(originalMethod, prefix: new HarmonyMethod(patchMethod));
            }
        }
Beispiel #7
0
 // Output buffer
 public static void ReleaseBuffer()
 {
     if (sb.Length > 0)
     {
         Debugging.Message(sb.ToString());
         sb.Remove(0, sb.Length);
     }
 }
Beispiel #8
0
        /// <summary>
        /// Harmony transpiler patching ResidentAI.UpdateHealth.
        /// </summary>
        /// <param name="instructions">CIL code to alter.</param>
        /// <returns>Patched CIL code</returns>
        private static IEnumerable <CodeInstruction> Transpiler(IEnumerable <CodeInstruction> instructions)
        {
            // The checks we're targeting for removal are fortunately clearly flagged by the call to Die().
            // We're going to remove the immediately following:
            // (Singleton<SimulationManager>.instance.m_randomizer.Int32(2u) == 0); { Singleton<CitizenManager>.instance.ReleaseCitizen(citizenID); return true; }
            var codes = new List <CodeInstruction>(instructions);

            // Deal with each of the operands consecutively and independently to avoid risk of error.

            // Stores the number of operands to cut.
            int cutCount = 0;

            // Iterate through each opcode in the CIL, looking for a calls
            for (int i = 0; i < codes.Count; i++)
            {
                if (codes[i].opcode == OpCodes.Call && codes[i].operand == AccessTools.Method(typeof(ResidentAI), "Die"))
                {
                    // Found call to ResidentAI.Die.
                    // Increment i to start from the following instruction.
                    i++;


                    // Now, count forward from this operand until we encounter ldc.i4.2; that's the last instruction to be cut.
                    while (codes[i + cutCount].opcode != OpCodes.Ldc_I4_2)
                    {
                        cutCount++;
                    }

                    // The following instruction is a call to ColossalFramework.Math.Randomizer; we keep the call and replace the operand with our own KeepCorpse method.
                    codes[i + cutCount + 1].operand = AccessTools.Method(typeof(AIUtils), "KeepCorpse");

                    StringBuilder logMessage = new StringBuilder("Lifecycle Rebalance Revisited: ResidentAI.Die transpiler removing CIL (offset ");
                    logMessage.Append(cutCount);
                    logMessage.Append(") from ");
                    logMessage.Append(i);
                    logMessage.Append(" (");
                    logMessage.Append(codes[i].opcode);
                    logMessage.Append(" ");
                    logMessage.Append(codes[i].operand);
                    logMessage.Append(" to ");
                    logMessage.Append(codes[i + cutCount].opcode);
                    logMessage.Append(" ");
                    logMessage.Append(codes[i + cutCount].operand);
                    logMessage.AppendLine(")");
                    Debugging.Message(logMessage.ToString());

                    // Remove the CIL from the ldarg.0 to the throw (inclusive).
                    // +1 to avoid fencepost error (need to include original instruction as well).
                    codes.RemoveRange(i, cutCount + 1);

                    // We're done with this one - no point in continuing the loop.
                    break;
                }
            }

            return(codes.AsEnumerable());
        }
Beispiel #9
0
        /// <summary>
        /// Manually removes specified Harmony patches.
        /// </summary>
        public static void RevertPrefix(MethodInfo originalMethod, MethodInfo patchMethod)
        {
            Harmony harmonyInstance = new Harmony(harmonyID);

            // Check if the patch is installed before proceeding.
            if (IsPrefixInstalled(originalMethod))
            {
                Debugging.Message("removing patch from " + originalMethod.Name);
                harmonyInstance.Unpatch(originalMethod, patchMethod);
            }
        }
        /// <summary>
        /// Calculates and populates the sickness probabilities DataStore table from the configuration file.
        /// </summary>
        public static void CalculateSicknessProbabilities()
        {
            StringBuilder logMessage = new StringBuilder("sickness probability table using factor of " + ModSettings.decadeFactor + ":\r\n");

            // Do conversion from sicknessProbInXML
            for (int i = 0; i < DataStore.sicknessProbInXML.Length; ++i)
            {
                // Simple division
                DataStore.sicknessProbCalc[i] = (int)(100000 * ((DataStore.sicknessProbInXML[i]) * ModSettings.decadeFactor));
                logMessage.AppendLine(i + ": " + DataStore.sicknessProbInXML[i] + " : " + DataStore.sicknessProbCalc[i] + " : " + (int)(100000 * ((DataStore.sicknessProbInXML[i]) / 25)));
            }
            Debugging.Message(logMessage.ToString());
        }
Beispiel #11
0
        /// <summary>
        /// Remove all Harmony patches applied by this mod.
        /// </summary>
        public static void UnpatchAll()
        {
            // Only unapply if patches appplied.
            if (patched)
            {
                Debugging.Message("reverting Harmony patches");

                // Unapply patches, but only with our HarmonyID.
                Harmony harmonyInstance = new Harmony(harmonyID);
                harmonyInstance.UnpatchAll(harmonyID);
                patched = false;
            }
        }
Beispiel #12
0
        /// <summary>
        /// Apply all Harmony patches.
        /// </summary>
        public static void PatchAll()
        {
            // Don't do anything if already patched.
            if (!patched)
            {
                Debugging.Message("deploying Harmony patches");

                // Apply all annotated patches and update flag.
                Harmony harmonyInstance = new Harmony(harmonyID);
                harmonyInstance.PatchAll();
                patched = true;
            }
        }
        /// <summary>
        /// Checks for mod conflicts and displays user notification if necessary.
        /// </summary>
        /// <returns>True if conflict found, false otherwise</returns>
        public bool CheckConflicts()
        {
            bool   conflictDetected = false;
            string conflictName     = string.Empty;


            // Check for conflicting mods.
            if (IsModEnabled(654707599ul))
            {
                // Original WG Citizen Lifecycle Rebalance.
                conflictDetected = true;
                conflictName     = "WG Citizen Lifecycle Rebalance";
                ErrorNotification.messageText = "Original WG Citizen Lifecycle Rebalance mod detected - Lifecycle Rebalance Revisited is shutting down to protect your game.  Only ONE of these mods can be enabled at the same time; please unsubscribe from WG Citizen Lifecycle Rebalance, which is now deprecated!";
            }
            else if (IsModInstalled(1372431101ul))
            {
                // Painter mod detected.
                conflictDetected = true;
                conflictName     = "Painter";
                ErrorNotification.messageText = "The old Painter mod causes problems with the Harmony libraries used by this mod, resulting in random errors.  Please UNSUBSCRIBE from Painter (merely disabling is NOT sufficient); the Repaint mod can be used as a replacement.";
            }
            else if (IsModInstalled("VanillaGarbageBinBlocker"))
            {
                // Garbage Bin Conroller mod detected.
                conflictDetected = true;
                conflictName     = "Garbage Bin Controller";
                Debugging.Message("Garbage Bin Controller mod detected - Lifecycle Rebalance Revisited exiting");
                ErrorNotification.messageText = "The Garbage Bin Controller mod causes problems with the Harmony libraries used by this mod, resulting in random errors.  Please UNSUBSCRIBE from Garbage Bin Controller (merely disabling is NOT sufficient).";
            }
            else if (IsModInstalled(2097938060) && IsModInstalled(2027161563))
            {
                // Beta and main version simultaneously installed.
                conflictDetected = true;
                conflictName     = "Beta";
                ErrorNotification.messageText = "Lifecycle Rebalance Revisited: both Beta and production versions detected.  Lifecycle Rebalance Revisited is shutting down to protect your game.  Please only subscribe to one of these at a time.";
            }

            // Mod conflict was detected.  Notify the user.
            if (conflictDetected)
            {
                // Show error notification.  Message text has already been set above.
                ErrorNotification notification = new ErrorNotification();
                notification.Create();
                ErrorNotification.headerText = "Mod conflict detected!";
                notification.Show();

                Debugging.Message("incompatible " + conflictName + " mod detected.  Shutting down");
            }

            return(conflictDetected);
        }
        private static string GetConfigPath()
        {
            var configPathAttribute = typeof(C).GetCustomAttributes(typeof(ConfigurationPathAttribute), true)
                                      .FirstOrDefault() as ConfigurationPathAttribute;

            if (configPathAttribute != null)
            {
                return(configPathAttribute.Value);
            }
            else
            {
                Debugging.Message("ConfigurationPath attribute missing in " + typeof(C).Name);
                return(typeof(C).Name + ".xml");
            }
        }
        /// <summary>
        ///
        /// </summary>
        public static void readFromXML()
        {
            // Switch to default which is the cities skylines in the application data area.
            currentFileLocation = ColossalFramework.IO.DataLocation.localApplicationData + Path.DirectorySeparatorChar + XML_FILE;

            if (File.Exists(currentFileLocation))
            {
                // Load in from XML - Designed to be flat file for ease
                WG_XMLBaseVersion reader = new XML_VersionTwo();
                XmlDocument       doc    = new XmlDocument();
                try
                {
                    doc.Load(currentFileLocation);
                    int version = Convert.ToInt32(doc.DocumentElement.Attributes["version"].InnerText);
                    if (version == 1)
                    {
                        reader = new XML_VersionOne();

                        // Make a back up copy of the old system to be safe
                        File.Copy(currentFileLocation, currentFileLocation + ".ver1", true);
                        string error = "Detected an old version of the XML (v1). " + currentFileLocation + ".ver1 has been created for future reference and will be upgraded to the new version.";
                        Debugging.bufferWarning(error);
                        Debugging.Message(error);
                    }
                    else if (version <= 0) // Uh oh... version 0 was a while back..
                    {
                        string error = "Detected an unsupported version of the XML (v0 or less). Backing up for a new configuration as :" + currentFileLocation + ".ver0";
                        Debugging.bufferWarning(error);
                        Debugging.Message(error);
                        File.Copy(currentFileLocation, currentFileLocation + ".ver0", true);
                        return;
                    }
                    reader.readXML(doc);
                }
                catch (Exception e)
                {
                    // Game will now use defaults
                    Debugging.bufferWarning("The following exception(s) were detected while loading the XML file. Some (or all) values may not be loaded.");
                    Debugging.bufferWarning(e.Message);
                }
            }
            else
            {
                Debugging.Message("configuration file not found. Will output new file to : " + currentFileLocation);
            }
        }
        /// <summary>
        /// Called by the game when level loading is complete.
        /// </summary>
        /// <param name="mode">Loading mode (e.g. game, editor, scenario, etc.)</param>
        public override void OnLevelLoaded(LoadMode mode)
        {
            // Don't do anything if not in game.
            if (mode != LoadMode.LoadGame && mode != LoadMode.NewGame)
            {
                Debugging.Message("not loading into game; exiting");
                return;
            }

            // Don't do anything if we've already been here.
            if (!isModCreated)
            {
                // Check for mod conflicts.
                ModConflicts modConflicts = new ModConflicts();
                if (modConflicts.CheckConflicts())
                {
                    // Conflict detected.  Unpatch everything before exiting without doing anything further.
                    Patcher.UnpatchAll();
                    return;
                }

                Debugging.Message("v" + LifecycleRebalance.Version + " loading");

                // Wait for Harmony if it hasn't already happened.
                if (!Patcher.patched)
                {
                    // Set timeout counter, just in case.
                    DateTime startTime = DateTime.Now;

                    try
                    {
                        Debugging.Message("waiting for Harmony");
                        while (!Patcher.patched)
                        {
                            if (CitiesHarmony.API.HarmonyHelper.IsHarmonyInstalled)
                            {
                                Patcher.PatchAll();
                                break;
                            }

                            // Three minutes should be sufficient wait.
                            if (DateTime.Now > startTime.AddMinutes(3))
                            {
                                throw new TimeoutException("Harmony loading timeout: " + startTime.ToString() + " : " + DateTime.Now.ToString());
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        Debugging.Message("Harmony loading exception");
                        Debugging.LogException(e);

                        // Show error notification to user.
                        ErrorNotification notification = new ErrorNotification();
                        notification.Create();
                        ErrorNotification.headerText  = "Harmony loading error!";
                        ErrorNotification.messageText = "Lifecycle Rebalance Revisited can't load properly because the required Harmony mod dependency didn't load.  In most cases, a simple game restart should be enough to fix this.\r\n\r\nIf this notification persists, please manually subscribe to the Harmony mod on the Steam workshop (if you're already subscribed, try unsubscribing and re-subscribing) and restart your game again.";
                        notification.Show();
                        return;
                    }
                }

                Debugging.Message("Harmony ready, proceeding");

                // Set flag.
                isModCreated = true;

                // Load and apply mod settings (configuration file loaded above).
                settingsFile = Configuration <SettingsFile> .Load();

                ModSettings.VanillaCalcs      = settingsFile.UseVanilla;
                ModSettings.LegacyCalcs       = settingsFile.UseLegacy;
                ModSettings.CustomRetirement  = settingsFile.CustomRetirement;
                ModSettings.RetirementYear    = settingsFile.RetirementYear;
                ModSettings.UseTransportModes = settingsFile.UseTransportModes;
                ModSettings.randomImmigrantEd = settingsFile.RandomImmigrantEd;
                Debugging.UseDeathLog         = settingsFile.LogDeaths;
                Debugging.UseImmigrationLog   = settingsFile.LogImmigrants;
                Debugging.UseTransportLog     = settingsFile.LogTransport;
                Debugging.UseSicknessLog      = settingsFile.LogSickness;

                // Apply sickness probabilities.
                CalculateSicknessProbabilities();

                // Report status and any debugging messages.
                Debugging.Message("death logging " + (Debugging.UseDeathLog ? "enabled" : "disabled") + ", immigration logging " + (Debugging.UseImmigrationLog ? "enabled" : "disabled") + ", transportation logging " + (Debugging.UseTransportLog ? "enabled" : "disabled"));
                Debugging.ReleaseBuffer();

                // Prime Threading.counter to continue from frame index.
                int temp = (int)(Singleton <SimulationManager> .instance.m_currentFrameIndex / 4096u);
                Threading.counter = temp % DataStore.lifeSpanMultiplier;
                try
                {
                    WG_XMLBaseVersion xml = new XML_VersionTwo();
                    xml.writeXML(currentFileLocation);
                }
                catch (Exception e)
                {
                    Debugging.LogException(e);
                }

                // Set up options panel event handler.
                OptionsPanel.OptionsEventHook();

                Debugging.Message("successfully loaded");

                // Check if we need to display update notification.
                if (settingsFile.NotificationVersion != 3)
                {
                    // No update notification "Don't show again" flag found; show the notification.
                    UpdateNotification notification = new UpdateNotification();
                    notification.Create();
                    notification.Show();
                }
            }
        }
        /// <summary>
        /// StartConnectionTransferImpl transpiler.
        /// </summary>
        /// <param name="original">Original method to patch</param>
        /// <param name="instructions">Original ILCode</param>
        /// <param name="generator">ILCode generator</param>
        /// <returns>Replacement ILCode</returns>
        public static IEnumerable <CodeInstruction> Transpiler(MethodBase original, IEnumerable <CodeInstruction> instructions, ILGenerator generator)
        {
            Debugging.Message("starting StartConnectionTransferImpl transpiler");

            // Local variables used by the patch.
            // These need to be set up prior to the i loop that the patch goes into.
            var ageArray       = generator.DeclareLocal(typeof(int[]));
            var childrenAgeMax = generator.DeclareLocal(typeof(int));
            var childrenAgeMin = generator.DeclareLocal(typeof(int));
            var minAdultAge    = generator.DeclareLocal(typeof(int));

            // Transpiler meta.
            var instructionsEnumerator = instructions.GetEnumerator();
            var instruction            = (CodeInstruction)null;
            var startFound             = false;
            var instructionsRemaining  = 1;

            // Find start pont of patch - keep going while we have instructions left and we've got instructions remaining to patch.
            while (instructionsEnumerator.MoveNext() && instructionsRemaining > 0)
            {
                // Get next instruction and add it to output.
                instruction = instructionsEnumerator.Current;
                yield return(instruction);

                // Decrement remaining instructions counter if we're patching.
                if (startFound)
                {
                    instructionsRemaining -= 1;
                }
                // Otherwise, check to see if we've found the start.
                // We're looking for stloc.s 16 (initialising the for i = 0 at the start of the key loop); this only occurs twice in the original method, and we want the first.
                else if (instruction.opcode == OpCodes.Stloc_S && instruction.operand is LocalBuilder builder && builder.LocalIndex == iLoopVarIndex)
                {
                    // Set the flag.
                    startFound = true;

                    // Set additional local variable values before we start the loop.
                    yield return(new CodeInstruction(OpCodes.Ldc_I4_0));

                    yield return(new CodeInstruction(OpCodes.Stloc_S, childrenAgeMax.LocalIndex));

                    yield return(new CodeInstruction(OpCodes.Ldc_I4_0));

                    yield return(new CodeInstruction(OpCodes.Stloc_S, childrenAgeMin.LocalIndex));

                    yield return(new CodeInstruction(OpCodes.Ldc_I4_0));

                    yield return(new CodeInstruction(OpCodes.Stloc_S, minAdultAge.LocalIndex));

                    yield return(new CodeInstruction(OpCodes.Ldloc_S, numVarIndex));

                    yield return(new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(StartConnectionTransferImplPatch), nameof(StartConnectionTransferImplPatch.GetAgeArray))));

                    yield return(new CodeInstruction(OpCodes.Stloc_S, ageArray.LocalIndex));
                }
            }

            // Save the labels from the current (original) instruction.
            var startForLabels = instructionsEnumerator.Current.labels;

            // Now we've got the location and added the local variable setup, skip forward until we find the location for the end of the patch.
            // Looking for Stloc_S 22 (initialising bool flag4 = false).
            do
            {
                instruction = instructionsEnumerator.Current;
            }while ((instruction.opcode != OpCodes.Stloc_S || !(instruction.operand is LocalBuilder builder && builder.LocalIndex == flag4VarIndex)) && instructionsEnumerator.MoveNext());

            // Load required variables for patch method onto stack.
            yield return(new CodeInstruction(OpCodes.Ldloc_S, iLoopVarIndex)
            {
                labels = startForLabels
            });

            yield return(new CodeInstruction(OpCodes.Ldloc_S, educationVarIndex));

            yield return(new CodeInstruction(OpCodes.Ldloc_S, ageArray.LocalIndex));

            yield return(new CodeInstruction(OpCodes.Ldloca_S, childrenAgeMax.LocalIndex));

            yield return(new CodeInstruction(OpCodes.Ldloca_S, childrenAgeMin.LocalIndex));

            yield return(new CodeInstruction(OpCodes.Ldloca_S, minAdultAge.LocalIndex));

            yield return(new CodeInstruction(OpCodes.Ldloca_S, education2VarIndex));

            yield return(new CodeInstruction(OpCodes.Ldloca_S, ageVarIndex));

            // Add call to patch method.
            yield return(new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(StartConnectionTransferImplPatch), nameof(StartConnectionTransferImplPatch.RandomizeImmigrants))));

            // Patch done; add remaining instructions.
            while (instructionsEnumerator.MoveNext())
            {
                yield return(instructionsEnumerator.Current);
            }

            Debugging.Message("StartConnectionTransferImpl transpiler completed");
        }
Beispiel #18
0
        /// <summary>
        /// Adds logging options tab to tabstrip.
        /// </summary
        /// <param name="tabStrip">Tab strip to add to</param>
        /// <param name="tabIndex">Index number of tab</param>
        public LoggingOptions(UITabstrip tabStrip, int tabIndex)
        {
            // Add tab.
            UIPanel loggingTab = PanelUtils.AddTab(tabStrip, "Logging", tabIndex, true);

            // Logging options.
            UICheckBox deathCheckBox = PanelUtils.AddPlainCheckBox(loggingTab, "Log deaths to 'Lifecycle death log.txt'");

            deathCheckBox.isChecked          = OptionsPanel.settings.LogDeaths;
            deathCheckBox.eventCheckChanged += (control, isChecked) =>
            {
                // Update mod settings.
                Debugging.UseDeathLog = isChecked;

                // Update configuration file.
                OptionsPanel.settings.LogDeaths = isChecked;
                Configuration <SettingsFile> .Save();

                Debugging.Message("death logging " + (OptionsPanel.settings.LogDeaths ? "enabled" : "disabled"));
            };

            UICheckBox immigrantCheckBox = PanelUtils.AddPlainCheckBox(loggingTab, "Log immigrants to 'Lifecycle immigration log.txt'");

            immigrantCheckBox.isChecked          = OptionsPanel.settings.LogImmigrants;
            immigrantCheckBox.eventCheckChanged += (control, isChecked) =>
            {
                // Update mod settings.
                Debugging.UseImmigrationLog = isChecked;

                // Update configuration file.
                OptionsPanel.settings.LogImmigrants = isChecked;
                Configuration <SettingsFile> .Save();

                Debugging.Message("immigrant logging " + (OptionsPanel.settings.LogImmigrants ? "enabled" : "disabled"));
            };

            UICheckBox transportCheckBox = PanelUtils.AddPlainCheckBox(loggingTab, "Log custom transport choices to 'Lifecycle transport log.txt' WARNING - SLOW!");

            transportCheckBox.isChecked          = OptionsPanel.settings.LogTransport;
            transportCheckBox.eventCheckChanged += (control, isChecked) =>
            {
                // Update mod settings.
                Debugging.UseTransportLog = isChecked;

                // Update configuration file.
                OptionsPanel.settings.LogTransport = isChecked;
                Configuration <SettingsFile> .Save();

                Debugging.Message("transport choices logging " + (OptionsPanel.settings.LogTransport ? "enabled" : "disabled"));
            };

            UICheckBox sicknessCheckBox = PanelUtils.AddPlainCheckBox(loggingTab, "Log sickness events to 'Lifecycle sickness log.txt'");

            sicknessCheckBox.isChecked          = OptionsPanel.settings.LogSickness;
            sicknessCheckBox.eventCheckChanged += (control, isChecked) =>
            {
                // Update mod settings.
                Debugging.UseSicknessLog = isChecked;

                // Update configuration file.
                OptionsPanel.settings.LogSickness = isChecked;
                Configuration <SettingsFile> .Save();

                Debugging.Message("sickness logging " + (OptionsPanel.settings.LogSickness ? "enabled" : "disabled"));
            };
        }