diff --git a/HugsLib.csproj b/HugsLib.csproj
index 777e414..0e99609 100644
--- a/HugsLib.csproj
+++ b/HugsLib.csproj
@@ -24,13 +24,14 @@
AnyCPU
- pdbonly
- true
- bin\Release\
+ none
+ false
+ Mods\HugsLib\Assemblies\
TRACE
prompt
4
true
+ Auto
@@ -56,9 +57,11 @@
+
-
+
+
@@ -68,8 +71,7 @@
-
-
+
@@ -77,7 +79,6 @@
-
@@ -99,6 +100,7 @@
+
diff --git a/Mods/HugsLib/About/About.xml b/Mods/HugsLib/About/About.xml
index 75def43..662452f 100644
--- a/Mods/HugsLib/About/About.xml
+++ b/Mods/HugsLib/About/About.xml
@@ -2,10 +2,10 @@
HugsLib
UnlimitedHugs
- 0.16.0
+ 0.17.0
https://ludeon.com/forums/index.php?topic=28066.0
<color=orange><b>Important: </b> This mod should be loaded right after Core to work properly.</color>\n
HugsLib is a library that provides shared functionality to other mods.
-Version: 2.4.3
+Version: 3.0.0
\ No newline at end of file
diff --git a/Mods/HugsLib/About/Version.xml b/Mods/HugsLib/About/Version.xml
index b15534a..8f30333 100644
--- a/Mods/HugsLib/About/Version.xml
+++ b/Mods/HugsLib/About/Version.xml
@@ -1,5 +1,5 @@
- 2.4.3
+ 3.0.0
UnlimitedHugs/RimworldHugsLib
\ No newline at end of file
diff --git a/Mods/HugsLib/Assemblies/0Harmony.dll b/Mods/HugsLib/Assemblies/0Harmony.dll
index 9654e2a..a95b6df 100644
Binary files a/Mods/HugsLib/Assemblies/0Harmony.dll and b/Mods/HugsLib/Assemblies/0Harmony.dll differ
diff --git a/Mods/HugsLib/Assemblies/HugsLib.dll b/Mods/HugsLib/Assemblies/HugsLib.dll
index 6c90b2b..61e9321 100644
Binary files a/Mods/HugsLib/Assemblies/HugsLib.dll and b/Mods/HugsLib/Assemblies/HugsLib.dll differ
diff --git a/Mods/HugsLib/Languages/English/Keyed/English.xml b/Mods/HugsLib/Languages/English/Keyed/English.xml
index b88302d..f39dcbb 100644
--- a/Mods/HugsLib/Languages/English/Keyed/English.xml
+++ b/Mods/HugsLib/Languages/English/Keyed/English.xml
@@ -15,6 +15,9 @@
Shows all available mod update news, including those that have already been displayed.
Show now
None of the active mods have any update news to display.
+ (unnamed mod)
+ Show settings
+ Mod settings for {0}
New Mod Features
The following mod updates were installed, and these are the new features and improvements that came with them.\nYou can disable this dialog in Options > Mod Settings
@@ -23,7 +26,7 @@
Opens your browser on a page where you can learn more about this update.\nAddress: {0}
Copied to clipboard.
-
+
Copy
Share logs
Confirm
@@ -37,18 +40,12 @@
Retry
Open in browser
- Files
+ +
Open log file
Open save folder
Open mods folder
- Restart game
- Changes have been saved.\n\nTo activate the new mod configuration it is recommended to restart the game.
- Restart now
- Restart automatically
- Restarting
- Restart after Mods menu changes
- Automatically restarts the game after changes to the Mods menu have been made.
+ The game language has changed. The game will now restart automatically.
Improper mod load order
<b>The HugsLib mod</b> should always be loaded after <b>Core</b> to avoid issues.\nPlease adjust your mod order in the Mods menu and restart the game.
diff --git a/Mods/HugsLib/Languages/Russian/Keyed/Russian.xml b/Mods/HugsLib/Languages/Russian/Keyed/Russian.xml
index ab91b1f..40b4c95 100644
--- a/Mods/HugsLib/Languages/Russian/Keyed/Russian.xml
+++ b/Mods/HugsLib/Languages/Russian/Keyed/Russian.xml
@@ -15,6 +15,9 @@
Показывает все доступные новости обновлений, включая те, что уже были показаны.
Показать сейчас
Ни один из загруженных модов не содержит новостей.
+ (мод без названия)
+ Показать настройки
+ Настройки мода {0}
Новости Обновлений Модов
Здесь показаны недавно установленные версии модов и новый функционал, который они добавили в игру.\nЭто окно можно отключить в меню Настройки > Настройки Модов
@@ -37,16 +40,13 @@
Повторить попытку
Открыть в браузере
- Файлы
+ +
Открыть журнал событий
Открыть папку с сохраненными играми
Открыть папку с модами
- Требуется перезапуск
- Изменения сохранены.\n\nЧтобы изменения вступили в силу, необходимо перезапустить игру.
- Перезапустить сейчас
- Перезапускать автоматически
- Перезапуск
- Перезапускать при перенастройке модов
- Автоматически перезапускает игру если состав или порядок загрузки модов был изменен.
+Язык игры был изменен. Игра будет автоматически перезапущена.
+
+Неверный порядок модов
+<b>Мод HugsLib</b> должен всегда загружаться после <b>Core</b> во избежание проблем.\nПожалуйста исправьте порядок в меню модов.
\ No newline at end of file
diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs
index 44d5380..6c86fa7 100644
--- a/Properties/AssemblyInfo.cs
+++ b/Properties/AssemblyInfo.cs
@@ -32,5 +32,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.16.0")]
-[assembly: AssemblyFileVersion("0.16.0")]
+[assembly: AssemblyVersion("0.17.0")]
+[assembly: AssemblyFileVersion("0.17.0")]
diff --git a/README.md b/README.md
index 16e8996..e312c1a 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,25 @@
![HugsLib logo](http://i.imgur.com/1d35OiC.png)
+![Version](https://img.shields.io/badge/Rimworld-A17-brightgreen.svg)
+
A lightweight shared mod library for Rimworld. Provides a foundation for mods and delivers shared functionality.
-**Notice:** HugsLib has changed and must now be installed as a separate mod by the players. The library itself is no longer to be included with your mods. You can, however, include the checker assembly to ensure that the player will be notified if they are missing the necessary version of the library ([RimworldHugsLibChecker](https://github.com/UnlimitedHugs/RimworldHugsLibChecker)).
+**Notice:** HugsLib must be installed as a separate mod by the players. The library itself is no longer to be included with your mods. You can, however, include the checker assembly to ensure that the player will be notified if they are missing the necessary version of the library ([RimworldHugsLibChecker](https://github.com/UnlimitedHugs/RimworldHugsLibChecker)).
## Current features
-- Mod foundation: Base class to build mods on. Extending classes have access to custom logging, settings, and receive the following events from the library controller: Initialize, Tick, Update, FixedUpdate, OnGUI, WorldLoaded, MapComponentsInitializing, MapLoaded, SceneLoaded, SettingsChanged, DefsLoaded.
+- Mod foundation: Base class to build mods on. Extending classes have access to custom logging, settings, and receive the following events from the library controller: Initialize, DefsLoaded, Tick, Update, FixedUpdate, OnGUI, WorldLoaded, MapComponentsInitializing, MapLoaded, MapDiscarded, SceneLoaded, SettingsChanged.
- Persistent in-game settings: Implementing mods can create custom settings of various types that can be changed by the player in the new Mod Settings menu. Settings are stored in a file in the user folder.
- Mod update news: Mods can provide a message for each version they release, highlighting new features. These messages will be shown once to the player the next time he starts the game. This is a good way to ensure that new mod features do not go unnoticed by the majority of players. This is especially true on Steam, where the player may not have even read the description before subscribing. Messages include support for images and basic formatting.
-- Log publisher: Adds a keyboard shortcut (**Ctrl+F12**) to publish the logs from within the game. Returns a URL that you can share with others or send to a mod author. The published logs also include the list of running mods and their versions. This is a great way for a mod author to get the logs from a player who is experiencing an issue with his mod.
+- Log publisher: Adds a keyboard shortcut (**Ctrl+F12**) to publish the logs from within the game. Returns a URL that you can share with others or send to a mod author. The published logs also include the list of running mods and their versions, as well as the full list of active Harmony patches. This is a great way for a mod author to get the logs from a player who is experiencing an issue with his mod.
- Checker assembly: A small dll designed to be included with your mod, that ensures the player is running at least the version of the library you specify. A dialog is displayed if a problem is detected, helping the player to resolve the issue. This is how the library stays up to date. See [RimworldHugsLibChecker](https://github.com/UnlimitedHugs/RimworldHugsLibChecker) for more info.
- UtilityWorldObjects: A convenient way to store your data in a save file. Since A16 MapComponents are no longer a reliable way to store your data, and UWO's are designed to be a drop-in replacement.
- Custom tick scheduling: Includes tools for executing callbacks with a specified tick delay, and registering recurring ticks with non-standard intervals. Recurring ticks are distributed uniformly across the time spectrum, to minimize the performance impact of the ticking entity.
-- Detouring: provides special attributes for more convenient detouring. Detours are safety-checked to prevent improper use. Repeated detours of the same method are not allowed and will generate an error. Mods can implement special methods to handle detouring errors, which allows for graceful failure and easier pinpointing of player issues.
-- GUI injection: provides a special attribute that allows a method to be executed whenever a given window type is drawn. This allows to inject drawing code for any window in the game.
-- Auto-restarter: adds a prompt to the Mods dialog to restart the game when changes to the mod configuration have been made. Mod load order changes are also detected.
-- Log window additions: adds buttons to copy the selected log message and activate the log publisher. Also adds a menu to find common files: open the log file and browse the user data and mods folders.
+- Auto-restarter: Automatically restarts the game when the language is changed.
+- Log window additions: Adds a menu to find common files: open the log file and browse the user data and mods folders.
+- Harmony library: HugsLib includes the [Harmony library](https://github.com/pardeike/Harmony) by Andreas Pardeike.
## Compatibility
-The only detour by the library itself is the `Window.WindowOnGUI` method, used to power the GUI injection system.
+There are no known compatibility issues at this time. Please use the [Harmony library](https://github.com/pardeike/Harmony) for your detouring needs and everything should work well together.
## Usage
This is a public library similar to CCL, designed to be easily updateable between Rimworld versions. Feel free to use it for your own projects.
\ No newline at end of file
diff --git a/Source/Core/HugsLibMod.cs b/Source/Core/HugsLibMod.cs
new file mode 100644
index 0000000..2e3d9f8
--- /dev/null
+++ b/Source/Core/HugsLibMod.cs
@@ -0,0 +1,13 @@
+using Verse;
+
+namespace HugsLib.Core {
+ ///
+ /// Entry point for the library.
+ /// Instantiated by the game at the start of DoPlayLoad().
+ ///
+ public class HugsLibMod : Mod {
+ public HugsLibMod(ModContentPack content) : base(content) {
+ HugsLibController.EarlyInitialize();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/Core/KeyBindingHandler.cs b/Source/Core/KeyBindingHandler.cs
index 132a85c..b10156e 100644
--- a/Source/Core/KeyBindingHandler.cs
+++ b/Source/Core/KeyBindingHandler.cs
@@ -1,7 +1,7 @@
-using HugsLib.Restarter;
-using HugsLib.Shell;
+using HugsLib.Shell;
using HugsLib.Utils;
using UnityEngine;
+using Verse;
namespace HugsLib.Core {
///
@@ -17,7 +17,7 @@ public static void OnGUI() {
ShellOpenLog.Execute();
}
if (HugsLibKeyBingings.RestartRimworld.JustPressed) {
- AutoRestarter.PerformRestart();
+ LongEventHandler.ExecuteWhenFinished(GenCommandLine.Restart);
}
}
}
diff --git a/Source/Core/ShortHashCollisionResolver.cs b/Source/Core/ShortHashCollisionResolver.cs
deleted file mode 100644
index 3f5b78f..0000000
--- a/Source/Core/ShortHashCollisionResolver.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using HugsLib.Utils;
-using Verse;
-
-namespace HugsLib.Core {
- ///
- /// A fix for Defs that ended up being assigned a duplicate short hash.
- /// Looks through all defs and when a collision is detected assigns a new short has to the Def.
- /// This is a temporary fix for a vanilla issue.
- ///
- public static class ShortHashCollisionResolver {
- public static void ResolveCollisions() {
- try {
- var seenHashes = new HashSet();
- var defsToRehash = new List();
- foreach (Type current in GenDefDatabase.AllDefTypesWithDatabases()) {
- var type = typeof (DefDatabase<>).MakeGenericType(current);
- var property = type.GetProperty("AllDefs");
- var getMethod = property.GetGetMethod();
- var allDefsInDatabase = (IEnumerable) getMethod.Invoke(null, null);
- defsToRehash.Clear();
- foreach (Def def in allDefsInDatabase) {
- if (seenHashes.Contains(def.shortHash)) {
- defsToRehash.Add(def);
- } else {
- seenHashes.Add(def.shortHash);
- }
- }
- defsToRehash.SortBy(d => d.defName);
- for (int i = 0; i < defsToRehash.Count; i++) {
- var def = defsToRehash[i];
- def.shortHash = 0;
- InjectedDefHasher.GiveShortHasToDef(def);
- Log.Message(def.defName + " " + def.shortHash);
- }
- seenHashes.Clear();
- }
- } catch (Exception e) {
- HugsLibController.Logger.ReportException(e);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Source/Core/StaticInitalizer.cs b/Source/Core/StaticInitalizer.cs
new file mode 100644
index 0000000..9c59373
--- /dev/null
+++ b/Source/Core/StaticInitalizer.cs
@@ -0,0 +1,13 @@
+using Verse;
+
+namespace HugsLib.Core {
+ ///
+ /// Provides an entry point for late controller setup during static constructor initialization.
+ ///
+ [StaticConstructorOnStartup]
+ internal static class StaticInitalizer {
+ static StaticInitalizer() {
+ HugsLibController.Instance.LateInitalize();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/HugsLibController.cs b/Source/HugsLibController.cs
index 8b011f4..53c7e33 100644
--- a/Source/HugsLibController.cs
+++ b/Source/HugsLibController.cs
@@ -5,7 +5,6 @@
using HugsLib.Core;
using HugsLib.Logs;
using HugsLib.News;
-using HugsLib.Restarter;
using HugsLib.Settings;
using HugsLib.Source.Attrib;
using HugsLib.Utils;
@@ -18,13 +17,16 @@ namespace HugsLib {
/// The hub of the library. Instantiates classes that extend ModBase and forwards some of the more useful events to them.
/// The minor version of the assembly should reflect the current major Rimworld version, just like CCL.
/// This gives us the ability to release updates to the library without breaking compatibility with the mods that implement it.
+ /// See Core.HugsLibMod for the entry point.
///
- [StaticConstructorOnStartup]
public class HugsLibController {
private const string SceneObjectName = "HugsLibProxy";
private const string ModIdentifier = "HugsLib";
private const string HarmonyInstanceIdentifier = "UnlimitedHugs.HugsLib";
+ private static bool earlyInitalizationCompleted;
+ private static bool lateInitalizationCompleted;
+
private static HugsLibController instance;
public static HugsLibController Instance {
get { return instance ?? (instance = new HugsLibController()); }
@@ -42,23 +44,35 @@ public static ModSettingsManager SettingsManager {
get { return Instance.Settings; }
}
- // entry point
- static HugsLibController() {
- Logger = new ModLogger(ModIdentifier);
- CreateSceneObject();
- Instance.InitializeController();
+ // most of the initalization happens during Verse.Mod instantiation. Pretty much no vanilla data is yet loaded at this point.
+ internal static void EarlyInitialize() {
+ try {
+ if (earlyInitalizationCompleted) {
+ Log.Warning("[HugsLib][warn] Attempted repeated early initialization of controller: " + Environment.StackTrace);
+ return;
+ }
+ earlyInitalizationCompleted = true;
+ Logger = new ModLogger(ModIdentifier);
+ CreateSceneObject();
+ Instance.InitializeController();
+ } catch (Exception e) {
+ Log.Message("[HugsLib][ERR] An exception occurred during early initialization: "+e);
+ }
}
-
+
internal static ModLogger Logger { get; private set; }
private static void CreateSceneObject() {
- if (GameObject.Find(SceneObjectName) != null) {
- Logger.Error("Another version of the library is already loaded. The HugsLib assembly should be loaded as a standalone mod.");
- return;
- }
- var obj = new GameObject(SceneObjectName);
- GameObject.DontDestroyOnLoad(obj);
- obj.AddComponent();
+ // this must execute in the main thread
+ LongEventHandler.ExecuteWhenFinished(() => {
+ if (GameObject.Find(SceneObjectName) != null) {
+ Logger.Error("Another version of the library is already loaded. The HugsLib assembly should be loaded as a standalone mod.");
+ return;
+ }
+ var obj = new GameObject(SceneObjectName);
+ GameObject.DontDestroyOnLoad(obj);
+ obj.AddComponent();
+ });
}
private static VersionFile ReadOwnVersionFile() {
@@ -76,6 +90,7 @@ private static VersionFile ReadOwnVersionFile() {
private readonly List childMods = new List();
private readonly List initializedMods = new List();
+ private readonly HashSet autoHarmonyPatchedAssemblies = new HashSet();
private Dictionary assemblyContentPacks;
private SettingHandle updateNewsSetting;
private bool initializationInProgress;
@@ -86,12 +101,12 @@ private static VersionFile ReadOwnVersionFile() {
public DistributedTickScheduler DistributedTicker { get; private set; }
public LogPublisher LogUploader { get; private set; }
- internal AutoRestarter AutoRestarter { get; private set; }
internal HarmonyInstance HarmonyInst { get; private set; }
private HugsLibController() {
}
+ // called during Verse.Mod instantiation
private void InitializeController() {
try {
PrepareReflection();
@@ -101,16 +116,32 @@ private void InitializeController() {
CallbackScheduler = new CallbackScheduler();
DistributedTicker = new DistributedTickScheduler();
LogUploader = new LogPublisher();
- AutoRestarter = new AutoRestarter();
- RegisterOwnSettings();
ReadOwnVersionFile();
LoadOrderChecker.ValidateLoadOrder();
- LongEventHandler.QueueLongEvent(LoadReloadInitialize, "Initializing", true, null);
} catch (Exception e) {
Logger.ReportException(e);
}
}
+ // called during static constructor initalization
+ internal void LateInitalize() {
+ try {
+ if (!earlyInitalizationCompleted) {
+ Logger.Error("Attempted late initialization before early initalization: "+ Environment.StackTrace);
+ return;
+ }
+ if (lateInitalizationCompleted) {
+ Logger.Warning("Attempted repeated late initialization of controller: " + Environment.StackTrace);
+ return;
+ }
+ lateInitalizationCompleted = true;
+ RegisterOwnSettings();
+ LongEventHandler.QueueLongEvent(LoadReloadInitialize, "Initializing", true, null);
+ } catch (Exception e) {
+ Logger.Error("An exception occurred during late initialization: " + e);
+ }
+ }
+
// executed both at startup and after a def reload
internal void LoadReloadInitialize() {
try {
@@ -259,6 +290,16 @@ internal void OnMapInitFinalized(Map map) {
LongEventHandler.QueueLongEvent(() => OnMapLoaded(map), null, false, null);
}
+ internal bool ShouldHarmonyAutoPatch(Assembly assembly, string modId) {
+ if (autoHarmonyPatchedAssemblies.Contains(assembly)) {
+ Logger.Warning("The {0} assembly contains multiple ModBase mods with HarmonyAutoPatch set to true. This warning was caused by modId {1}.", assembly.GetName().Name, modId);
+ return false;
+ } else {
+ autoHarmonyPatchedAssemblies.Add(assembly);
+ return true;
+ }
+ }
+
private void OnMapLoaded(Map map){
try {
for (int i = 0; i < childMods.Count; i++) {
@@ -307,7 +348,6 @@ private void OnSettingsChanged() {
private void OnDefsLoaded() {
try {
- ShortHashCollisionResolver.ResolveCollisions();
RegisterOwnSettings();
UtilityWorldObjectManager.OnDefsLoaded();
for (int i = 0; i < childMods.Count; i++) {
@@ -362,8 +402,10 @@ private void EnumerateModAssemblies() {
private void ApplyHarmonyPatches() {
try {
- HarmonyInst = HarmonyInstance.Create(HarmonyInstanceIdentifier);
- HarmonyInst.PatchAll(typeof (HugsLibController).Assembly);
+ if (ShouldHarmonyAutoPatch(typeof (HugsLibController).Assembly, ModIdentifier)) {
+ HarmonyInst = HarmonyInstance.Create(HarmonyInstanceIdentifier);
+ HarmonyInst.PatchAll(typeof (HugsLibController).Assembly);
+ }
} catch (Exception e) {
Logger.ReportException(e);
}
@@ -375,21 +417,24 @@ private void PrepareReflection() {
}
private void RegisterOwnSettings() {
- var pack = Settings.GetModSettings(ModIdentifier);
- pack.EntryName = "HugsLib_ownSettingsName".Translate();
- pack.DisplayPriority = ModSettingsPack.ListPriority.Lower;
- updateNewsSetting = pack.GetHandle("modUpdateNews", "HugsLib_setting_showNews_label".Translate(), "HugsLib_setting_showNews_desc".Translate(), true);
- var allNewsHandle = pack.GetHandle("showAllNews", "HugsLib_setting_allNews_label".Translate(), "HugsLib_setting_allNews_desc".Translate(), false);
- allNewsHandle.Unsaved = true;
- allNewsHandle.CustomDrawer = rect => {
- if (Widgets.ButtonText(rect, "HugsLib_setting_allNews_button".Translate())) {
- if (!UpdateFeatures.TryShowDialog(true)) {
- Find.WindowStack.Add(new Dialog_MessageBox("HugsLib_setting_allNews_fail".Translate()));
+ try {
+ var pack = Settings.GetModSettings(ModIdentifier);
+ pack.EntryName = "HugsLib_ownSettingsName".Translate();
+ pack.DisplayPriority = ModSettingsPack.ListPriority.Lowest;
+ updateNewsSetting = pack.GetHandle("modUpdateNews", "HugsLib_setting_showNews_label".Translate(), "HugsLib_setting_showNews_desc".Translate(), true);
+ var allNewsHandle = pack.GetHandle("showAllNews", "HugsLib_setting_allNews_label".Translate(), "HugsLib_setting_allNews_desc".Translate(), false);
+ allNewsHandle.Unsaved = true;
+ allNewsHandle.CustomDrawer = rect => {
+ if (Widgets.ButtonText(rect, "HugsLib_setting_allNews_button".Translate())) {
+ if (!UpdateFeatures.TryShowDialog(true)) {
+ Find.WindowStack.Add(new Dialog_MessageBox("HugsLib_setting_allNews_fail".Translate()));
+ }
}
- }
- return false;
- };
- AutoRestarter.CreateSettingsHandles(pack);
+ return false;
+ };
+ } catch (Exception e) {
+ Logger.ReportException(e);
+ }
}
}
}
\ No newline at end of file
diff --git a/Source/Logs/LogPublisher.cs b/Source/Logs/LogPublisher.cs
index c6432e0..7194c03 100644
--- a/Source/Logs/LogPublisher.cs
+++ b/Source/Logs/LogPublisher.cs
@@ -235,7 +235,13 @@ private string RedactRimworldPaths(string log) {
}
private string RedactRendererInformation(string log) {
- return RedactString(log, "GfxDevice: ", "\nBegin MonoManager", "[Renderer information redacted]");
+ // apparently renderer information can appear multiple times in the log
+ for (int i = 0; i < 5; i++) {
+ var redacted = RedactString(log, "GfxDevice: ", "\nBegin MonoManager", "[Renderer information redacted]");
+ if (log.Length == redacted.Length) break;
+ log = redacted;
+ }
+ return log;
}
private string RedactPlayerConnectInformation(string log) {
diff --git a/Source/Logs/LogWindowInjection.cs b/Source/Logs/LogWindowInjection.cs
index 31cc17a..ed081ac 100644
--- a/Source/Logs/LogWindowInjection.cs
+++ b/Source/Logs/LogWindowInjection.cs
@@ -30,8 +30,8 @@ public static void DrawLogWindowExtensions(Window window, Rect inRect) {
if (widgetRow.ButtonText("HugsLib_logs_shareBtn".Translate())) {
HugsLibController.Instance.LogUploader.ShowPublishPrompt();
}
- // Files drop-down menu
GUI.color = prevColor;
+ // Files drop-down menu
if (widgetRow.ButtonText("HugsLib_logs_filesBtn".Translate())) {
Find.WindowStack.Add(new FloatMenu(new List {
new FloatMenuOption("HugsLib_logs_openLogFile".Translate(), () => {
@@ -45,26 +45,21 @@ public static void DrawLogWindowExtensions(Window window, Rect inRect) {
})
}));
}
- if (selectedMessage != null) {
- var copyButtonPos = new Vector2(inRect.width - MessageDetailsScrollBarWidth, inRect.height);
- if (DoAutoWidthButton(copyButtonPos, "HugsLib_logs_copy".Translate())) {
- CopyMessage(selectedMessage);
- }
- }
}
+
+
private static bool DoAutoWidthButton(Vector2 position, string label) {
+ return Widgets.ButtonText(GetAutoWidthRect(position, label), label);
+ }
+
+ private static Rect GetAutoWidthRect(Vector2 position, string label) {
const float ButtonPaddingX = 16f;
const float ButtonPaddingY = 2f;
var buttonSize = Text.CalcSize(label);
buttonSize.x += ButtonPaddingX;
buttonSize.y += ButtonPaddingY;
- var buttonRect = new Rect(position.x - buttonSize.x, position.y - buttonSize.y, buttonSize.x, buttonSize.y);
- return Widgets.ButtonText(buttonRect, label);
- }
-
- private static void CopyMessage(LogMessage logMessage) {
- HugsLibUtility.CopyToClipboard(logMessage.text + "\n" + logMessage.StackTrace);
+ return new Rect(position.x - buttonSize.x, position.y, buttonSize.x, buttonSize.y);
}
}
}
\ No newline at end of file
diff --git a/Source/ModBase.cs b/Source/ModBase.cs
index 738ab76..6167817 100644
--- a/Source/ModBase.cs
+++ b/Source/ModBase.cs
@@ -78,10 +78,12 @@ protected ModBase() {
Logger = new ModLogger(modId);
Settings = HugsLibController.Instance.Settings.GetModSettings(modId);
if (HarmonyAutoPatch) {
- var harmonyId = HarmonyInstancePrefix + ModIdentifier;
+ var harmonyId = HarmonyInstancePrefix + modId;
try {
- HarmonyInst = HarmonyInstance.Create(harmonyId);
- HarmonyInst.PatchAll(GetType().Assembly);
+ if (HugsLibController.Instance.ShouldHarmonyAutoPatch(GetType().Assembly, modId)) {
+ HarmonyInst = HarmonyInstance.Create(harmonyId);
+ HarmonyInst.PatchAll(GetType().Assembly);
+ }
} catch (Exception e) {
HugsLibController.Logger.Error("Failed to apply Harmony patches for {0}. Exception was: {1}", harmonyId, e);
}
diff --git a/Source/Patches/Dialog_Options_Patch.cs b/Source/Patches/Dialog_Options_Patch.cs
index 5dfb20a..23a6d6f 100644
--- a/Source/Patches/Dialog_Options_Patch.cs
+++ b/Source/Patches/Dialog_Options_Patch.cs
@@ -1,16 +1,51 @@
-using Harmony;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Emit;
+using Harmony;
using HugsLib.Settings;
using RimWorld;
using UnityEngine;
using Verse;
namespace HugsLib.Patches {
+ ///
+ /// Replaces the "Mod Settings" button in the Options dialog with our own.
+ ///
[HarmonyPatch(typeof(Dialog_Options))]
[HarmonyPatch("DoWindowContents")]
[HarmonyPatch(new[] { typeof(Rect) })]
internal static class Dialog_Options_Patch {
- private static void Postfix(Window __instance, Rect inRect) {
- OptionsDialogInjection.DrawSettingsDialogExtensions(__instance, inRect);
+ private delegate bool ModSettingsButtonReplacementMethod(Listing_Standard _this, string label, string highlightTag);
+
+ private const string ButtonLabelToKill = "ModSettings";
+ private static readonly MethodInfo ExpectedButtonMethod = Traverse.Create().Method("ButtonText", new[] {typeof(Listing_Standard), typeof (string), typeof (string)}).GetValue();
+
+ [HarmonyTranspiler]
+ public static IEnumerable ReplaceModOptionsButton(this IEnumerable instructions) {
+ var expectedButtonMethod = AccessTools.Method(typeof (Listing_Standard), "ButtonText", new[] {typeof (string), typeof (string)});
+ if (expectedButtonMethod == null || expectedButtonMethod.ReturnType != typeof(bool)) {
+ HugsLibController.Logger.Error("Failed to reflect required method for transpiler: "+Environment.StackTrace);
+ }
+ var labelFound = false;
+ var buttonInjectCompleted = false;
+ foreach (var instruction in instructions) {
+ if (expectedButtonMethod != null && !buttonInjectCompleted) {
+ // find the right button by its untranslated label
+ if (instruction.opcode == OpCodes.Ldstr && instruction.operand as string == ButtonLabelToKill) {
+ labelFound = true;
+ } else if (labelFound && instruction.opcode == OpCodes.Callvirt && expectedButtonMethod.Equals(instruction.operand)) {
+ // replace the button call with out own method
+ instruction.operand = ((ModSettingsButtonReplacementMethod) ((_this, label, highlightTag) => {
+ OptionsDialogInjection.DrawModSettingsButton(_this);
+ return false;
+ })).Method;
+ buttonInjectCompleted = true;
+ }
+ }
+
+ yield return instruction;
+ }
}
}
}
\ No newline at end of file
diff --git a/Source/Patches/EditWindow_Log_Patch.cs b/Source/Patches/EditWindow_Log_Patch.cs
index 21af627..68da1a9 100644
--- a/Source/Patches/EditWindow_Log_Patch.cs
+++ b/Source/Patches/EditWindow_Log_Patch.cs
@@ -4,11 +4,15 @@
using Verse;
namespace HugsLib.Patches {
+ ///
+ /// Adds extra buttons to the Log window.
+ ///
[HarmonyPatch(typeof(EditWindow_Log))]
[HarmonyPatch("DoWindowContents")]
[HarmonyPatch(new[]{typeof(Rect)})]
internal static class EditWindow_Log_Patch {
- private static void Postfix(Window __instance, Rect inRect) {
+ [HarmonyPostfix]
+ private static void ExtraLogWindowButtons(Window __instance, Rect inRect) {
LogWindowInjection.DrawLogWindowExtensions(__instance, inRect);
}
}
diff --git a/Source/Patches/Game_DeinitAndRemoveMap_Patch.cs b/Source/Patches/Game_DeinitAndRemoveMap_Patch.cs
index 4047388..ab7fa71 100644
--- a/Source/Patches/Game_DeinitAndRemoveMap_Patch.cs
+++ b/Source/Patches/Game_DeinitAndRemoveMap_Patch.cs
@@ -2,11 +2,15 @@
using Verse;
namespace HugsLib.Patches {
+ ///
+ /// Adds a hook for discarding maps.
+ ///
[HarmonyPatch(typeof (Game))]
[HarmonyPatch("DeinitAndRemoveMap")]
[HarmonyPatch(new[] { typeof(Map) })]
internal static class Game_DeinitAndRemoveMap_Patch {
- private static void Postfix(Map map) {
+ [HarmonyPostfix]
+ private static void MapRemovalHook(Map map) {
HugsLibController.Instance.OnMapDiscarded(map);
}
}
diff --git a/Source/Patches/Game_FinalizeInit_Patch.cs b/Source/Patches/Game_FinalizeInit_Patch.cs
index 4c2e91f..0bda5ea 100644
--- a/Source/Patches/Game_FinalizeInit_Patch.cs
+++ b/Source/Patches/Game_FinalizeInit_Patch.cs
@@ -3,11 +3,15 @@
using Verse;
namespace HugsLib.Patches {
+ ///
+ /// Adds a hook to produce the WorldLoaded callback for ModBase mods.
+ ///
[HarmonyPatch(typeof (Game))]
[HarmonyPatch("FinalizeInit")]
[HarmonyPatch(new Type[0])]
internal static class Game_FinalizeInit_Patch {
- private static void Postfix() {
+ [HarmonyPostfix]
+ private static void WorldLoadedHook() {
HugsLibController.Instance.OnPlayingStateEntered();
}
}
diff --git a/Source/Patches/LanguageDatabase_Patch.cs b/Source/Patches/LanguageDatabase_Patch.cs
new file mode 100644
index 0000000..e38e694
--- /dev/null
+++ b/Source/Patches/LanguageDatabase_Patch.cs
@@ -0,0 +1,23 @@
+using Harmony;
+using Verse;
+
+namespace HugsLib.Patches {
+ ///
+ /// Forces a game restart after a language change.
+ /// This is necessary to avoid creating problems for running mods caused by reloaded graphics and defs.
+ ///
+ [HarmonyPatch(typeof(LanguageDatabase))]
+ [HarmonyPatch("SelectLanguage")]
+ [HarmonyPatch(new[] { typeof(LoadedLanguage) })]
+ internal static class LanguageDatabase_Patch {
+ [HarmonyPrefix]
+ public static bool ForceRestartAfterLangChange(LoadedLanguage lang) {
+ Prefs.LangFolderName = lang.folderName;
+ Prefs.Save();
+ Find.WindowStack.Add(new Dialog_MessageBox("HugsLib_restart_language_text".Translate(), null, () => {
+ LongEventHandler.ExecuteWhenFinished(GenCommandLine.Restart);
+ }));
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/Patches/Map_ConstructComponents_Patch.cs b/Source/Patches/Map_ConstructComponents_Patch.cs
index 42c06af..449b325 100644
--- a/Source/Patches/Map_ConstructComponents_Patch.cs
+++ b/Source/Patches/Map_ConstructComponents_Patch.cs
@@ -3,11 +3,15 @@
using Verse;
namespace HugsLib.Patches {
+ ///
+ /// Adds a hook to produce the MapComponentsInitializing callback for ModBase mods.
+ ///
[HarmonyPatch(typeof(Map))]
[HarmonyPatch("ConstructComponents")]
[HarmonyPatch(new Type[0])]
internal static class Map_ConstructComponents_Patch {
- private static void Postfix(Map __instance) {
+ [HarmonyPostfix]
+ private static void MapComponentsInitHook(Map __instance) {
HugsLibController.Instance.OnMapComponentsConstructed(__instance);
}
}
diff --git a/Source/Patches/Map_FinalizeInit_Patch.cs b/Source/Patches/Map_FinalizeInit_Patch.cs
index 63741d7..b356103 100644
--- a/Source/Patches/Map_FinalizeInit_Patch.cs
+++ b/Source/Patches/Map_FinalizeInit_Patch.cs
@@ -3,11 +3,15 @@
using Verse;
namespace HugsLib.Patches {
+ ///
+ /// Adds a hook to produce the MapLoaded callback for ModBase mods.
+ ///
[HarmonyPatch(typeof(Map))]
[HarmonyPatch("FinalizeInit")]
[HarmonyPatch(new Type[0])]
internal static class Map_FinalizeInit_Patch {
- private static void Postfix(Map __instance) {
+ [HarmonyPostfix]
+ private static void MapLoadedHook(Map __instance) {
HugsLibController.Instance.OnMapInitFinalized(__instance);
}
}
diff --git a/Source/Patches/Page_ModsConfig_Patch.cs b/Source/Patches/Page_ModsConfig_Patch.cs
deleted file mode 100644
index 942ea33..0000000
--- a/Source/Patches/Page_ModsConfig_Patch.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Harmony;
-using HugsLib.Restarter;
-using RimWorld;
-using UnityEngine;
-using Verse;
-
-namespace HugsLib.Patches {
- [HarmonyPatch(typeof(Page_ModsConfig))]
- [HarmonyPatch("DoWindowContents")]
- [HarmonyPatch(new[] { typeof(Rect) })]
- internal static class Page_ModsConfig_Patch {
- private static void Postfix(Window __instance, Rect rect) {
- AutoRestarter.DoModsDialogControls(__instance, rect);
- }
- }
-}
\ No newline at end of file
diff --git a/Source/Patches/PlayDataLoader_Patch.cs b/Source/Patches/PlayDataLoader_Patch.cs
index 95debef..d3fe686 100644
--- a/Source/Patches/PlayDataLoader_Patch.cs
+++ b/Source/Patches/PlayDataLoader_Patch.cs
@@ -3,11 +3,15 @@
using Verse;
namespace HugsLib.Patches {
+ ///
+ /// Adds a hook to produce the DefsLoaded callback for ModBase mods.
+ ///
[HarmonyPatch(typeof(PlayDataLoader))]
[HarmonyPatch("DoPlayLoad")]
[HarmonyPatch(new Type[0])]
internal static class PlayDataLoader_Patch {
- private static void Postfix() {
+ [HarmonyPostfix]
+ private static void InitModsHook() {
HugsLibController.Instance.LoadReloadInitialize();
}
}
diff --git a/Source/Patches/Root_Patch.cs b/Source/Patches/Root_Patch.cs
index d838a05..901de73 100644
--- a/Source/Patches/Root_Patch.cs
+++ b/Source/Patches/Root_Patch.cs
@@ -3,11 +3,15 @@
using Verse;
namespace HugsLib.Patches {
+ ///
+ /// Hooks into the flow of the vanilla MonoBehaviour.Update()
+ ///
[HarmonyPatch(typeof(Root))]
[HarmonyPatch("Update")]
[HarmonyPatch(new Type[0])]
internal static class Root_Patch {
- private static void Postfix() {
+ [HarmonyPostfix]
+ private static void UpdateHook() {
HugsLibController.Instance.OnUpdate();
}
}
diff --git a/Source/Patches/UIRoot_Patch.cs b/Source/Patches/UIRoot_Patch.cs
index 8667e12..9c40bbd 100644
--- a/Source/Patches/UIRoot_Patch.cs
+++ b/Source/Patches/UIRoot_Patch.cs
@@ -3,11 +3,16 @@
using Verse;
namespace HugsLib.Patches {
+ ///
+ /// Hooks into the flow of the vanilla MonoBehaviour.OnGUI()
+ /// This allows to take advantage of automatic UI scaling and prevents GUI updates during a loading screen.
+ ///
[HarmonyPatch(typeof(UIRoot))]
[HarmonyPatch("UIRootOnGUI")]
[HarmonyPatch(new Type[0])]
internal static class UIRoot_Patch {
- private static void Postfix() {
+ [HarmonyPostfix]
+ private static void OnGUIHook() {
HugsLibController.Instance.OnGUI();
}
}
diff --git a/Source/Restarter/AutoRestarter.cs b/Source/Restarter/AutoRestarter.cs
deleted file mode 100644
index 7a649f9..0000000
--- a/Source/Restarter/AutoRestarter.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using System.Threading;
-using HugsLib.Settings;
-using HugsLib.Shell;
-using RimWorld;
-using UnityEngine;
-using Verse;
-
-namespace HugsLib.Restarter {
- ///
- /// Makes the the Close button in the Mods dialog prompt the player to restart the game if the mod configuration has changed.
- /// Will restart the game automatically if the autoRestart setting is active.
- /// Unlike in vanilla, changes in mod order are also detected and will require a restart.
- ///
- internal class AutoRestarter {
- private static readonly Vector2 CloseButSize = new Vector2(120f, 40f);
-
- private static int lastSeenWindowHash;
- private static int lastModListHash;
-
- public SettingHandle AutoRestartSetting { get; private set; }
-
- public static void PerformRestart() {
- LongEventHandler.QueueLongEvent(() => {
- // put up the loading screen while the game shuts down
- Thread.Sleep(5000);
- }, "HugsLib_restart_restarting", true, null);
- // execute in main thread
- LongEventHandler.ExecuteWhenFinished(() => ShellRestartRimWorld.Execute());
- }
-
- public static void DoModsDialogControls(Window window, Rect inRect) {
- // update mod list hash
- if (window.GetHashCode() != lastSeenWindowHash) {
- lastSeenWindowHash = window.GetHashCode();
- lastModListHash = GetModListHash();
- }
- // replace close button, handle keys
- Text.Font = GameFont.Small;
- window.doCloseButton = false;
- window.closeOnEscapeKey = false;
- var closeBtnRect = new Rect(inRect.width / 2f - CloseButSize.x / 2f, inRect.height - CloseButSize.y, CloseButSize.x, CloseButSize.y);
- var keyUsed = Event.current.type == EventType.KeyDown && (Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.Escape);
- if (Widgets.ButtonText(closeBtnRect, "CloseButton".Translate()) || keyUsed) {
- CloseAction(window);
- if(keyUsed) Event.current.Use();
- }
- }
-
- // TODO: on update, verify that this still does the same thing as Page_ModsConfig.PostClose
- private static void CloseAction(Window window) {
- ModsConfig.Save();
- if (lastModListHash != GetModListHash()) {
- if (HugsLibController.Instance.AutoRestarter.AutoRestartSetting) {
- PerformRestart();
- } else {
- Find.WindowStack.Add(new Dialog_RestartGame());
- }
- } else {
- window.Close();
- }
- }
-
- // an alternative way to detect changes to the mod list that takes mod order into account
- private static int GetModListHash() {
- int hash = 42;
- foreach (var modMetaData in ModsConfig.ActiveModsInLoadOrder) {
- if (modMetaData.enabled){
- unchecked {
- hash <<= 1;
- hash += hash + modMetaData.GetHashCode();
- }
- }
- }
- return hash;
-
- }
-
- public void CreateSettingsHandles(ModSettingsPack pack) {
- AutoRestartSetting = pack.GetHandle("autoRestart", "HugsLib_setting_autoRestart_label".Translate(), "HugsLib_setting_autoRestart_desc".Translate(), false);
- }
- }
-}
\ No newline at end of file
diff --git a/Source/Restarter/Dialog_RestartGame.cs b/Source/Restarter/Dialog_RestartGame.cs
deleted file mode 100644
index 64a5801..0000000
--- a/Source/Restarter/Dialog_RestartGame.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using RimWorld;
-using UnityEngine;
-using Verse;
-
-namespace HugsLib.Restarter {
- ///
- /// A dialog that offers the player to restart the game after a mod configuration change.
- ///
- public class Dialog_RestartGame : Window {
- private static readonly Color RestartButtonColor = new Color(.55f, 1f, .55f);
-
- private bool autoCheckboxIsOn;
-
- public override Vector2 InitialSize {
- get { return new Vector2(500f, 300f); }
- }
-
- public Dialog_RestartGame() {
- closeOnEscapeKey = false;
- absorbInputAroundWindow = true;
- doCloseX = true;
- doCloseButton = false;
- autoCheckboxIsOn = HugsLibController.Instance.AutoRestarter.AutoRestartSetting.Value;
- }
-
- public override void DoWindowContents(Rect inRect) {
- const float titleHeight = 42f;
- const float textHeight = 100f;
- const float buttonMargin = 20f;
- const float buttonHeight = 35f;
- const float toggleHeight = 30f;
- const float toggleExtraWidth = 40f;
-
- // title
- Text.Font = GameFont.Medium;
- Widgets.Label(new Rect(0, inRect.y, inRect.width, titleHeight), "HugsLib_restart_title".Translate());
-
- // text
- Text.Font = GameFont.Small;
- Rect textRect = new Rect(inRect.x, inRect.y + titleHeight, inRect.width, textHeight);
- Widgets.Label(textRect, "HugsLib_restart_text".Translate());
-
- // toggle
- bool toggleWasOn = autoCheckboxIsOn;
- var toggleLabelText = "HugsLib_restart_autoToggle".Translate();
- var labelTextSize = Text.CalcSize(toggleLabelText);
- Widgets.CheckboxLabeled(new Rect(inRect.x, inRect.y + titleHeight + textHeight, labelTextSize.x + toggleExtraWidth, toggleHeight), toggleLabelText, ref autoCheckboxIsOn);
- if (toggleWasOn != autoCheckboxIsOn) {
- CheckboxToggleAction();
- }
-
- // restart button
- GUI.color = RestartButtonColor;
- float buttonWidth = inRect.width / 2f - buttonMargin;
- if (Widgets.ButtonText(new Rect(inRect.width / 2f + 20f, inRect.height - buttonHeight, buttonWidth, buttonHeight), "HugsLib_restart_restartNowBtn".Translate()) ||
- (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Return)) {
- RestartAction();
- Close();
- }
-
- // close button
- GUI.color = Color.white;
- if (Widgets.ButtonText(new Rect(inRect.x, inRect.height - buttonHeight, buttonWidth, buttonHeight), "CloseButton".Translate()) ||
- (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape)) {
- CloseAction();
- Close();
- }
- }
-
- private void CheckboxToggleAction() {
- HugsLibController.Instance.AutoRestarter.AutoRestartSetting.Value = autoCheckboxIsOn;
- HugsLibController.SettingsManager.SaveChanges();
- }
-
- private void RestartAction() {
- AutoRestarter.PerformRestart();
- }
-
- private void CloseAction() {
- var modsDialog = Find.WindowStack.WindowOfType();
- if (modsDialog != null) {
- modsDialog.Close();
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Source/Settings/Dialog_ModSettings.cs b/Source/Settings/Dialog_ModSettings.cs
index 9b09071..4722d40 100644
--- a/Source/Settings/Dialog_ModSettings.cs
+++ b/Source/Settings/Dialog_ModSettings.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using HugsLib.Utils;
+using RimWorld;
using UnityEngine;
using Verse;
@@ -16,6 +17,7 @@ public class Dialog_ModSettings : Window {
private const float ModEntryLabelHeight = 40f;
private const float ModEntryLabelPadding = 4f;
private const float ModEntryHeight = ModEntryLabelHeight + ModEntryLabelPadding;
+ private const float ModEntryShowSettingsButtonHeight = 34f;
private const float HandleEntryPadding = 3f;
private const float HandleEntryHeight = 34f;
private const float ScrollBarWidthMargin = 18f;
@@ -25,18 +27,18 @@ public class Dialog_ModSettings : Window {
private readonly Color BadValueOutlineColor = new Color(.9f, .1f, .1f, 1f);
private readonly Dictionary handleControlInfo = new Dictionary();
private readonly SettingsHandleDrawer defaultHandleDrawer;
- private readonly Dictionary handleDrawers;
+ private readonly Dictionary handleDrawers;
private Vector2 scrollPosition;
private float totalContentHeight;
private bool settingsHaveChanged;
private string currentlyDrawnEntry;
private bool closingScheduled;
-
+
public override Vector2 InitialSize {
get { return new Vector2(650f, 700f); }
}
-
+
public Dialog_ModSettings() {
closeOnEscapeKey = true;
doCloseButton = false;
@@ -62,7 +64,7 @@ public override void PreOpen() {
public override void PostClose() {
base.PostClose();
- if(settingsHaveChanged) HugsLibController.Instance.Settings.SaveChanges();
+ if (settingsHaveChanged) HugsLibController.Instance.Settings.SaveChanges();
}
public override void DoWindowContents(Rect inRect) {
@@ -85,11 +87,14 @@ public override void DoWindowContents(Rect inRect) {
var entry = listedMods[i];
currentlyDrawnEntry = entry.ModName;
DrawModEntryHeader(entry, scrollViewTotal.width, ref curY);
- for (int j = 0; j < entry.handles.Count; j++) {
- var handle = entry.handles[j];
+ if (entry.VanillaMod!=null) {
+ DrawVanillaModEntry(entry, scrollViewTotal.width, ref curY);
+ }
+ for (int j = 0; j < entry.Handles.Count; j++) {
+ var handle = entry.Handles[j];
if (handle.VisibilityPredicate != null) {
try {
- if(!handle.VisibilityPredicate()) continue;
+ if (!handle.VisibilityPredicate()) continue;
} catch (Exception e) {
HugsLibController.Logger.ReportException(e, currentlyDrawnEntry, true, "SettingsHandle.VisibilityPredicate");
}
@@ -122,7 +127,7 @@ public override void DoWindowContents(Rect inRect) {
// draws the header with the name of the mod
private void DrawModEntryHeader(ModEntry entry, float width, ref float curY) {
- if(entry.ModName.NullOrEmpty()) return;
+ if (entry.ModName.NullOrEmpty()) return;
var labelRect = new Rect(0f, curY, width, ModEntryLabelHeight).ContractedBy(ModEntryLabelPadding);
Text.Font = GameFont.Medium;
Widgets.Label(labelRect, entry.ModName);
@@ -135,22 +140,45 @@ private void DrawModEntryHeader(ModEntry entry, float width, ref float curY) {
curY += ModEntryLabelPadding;
}
+ // draws the "show settings" button for vanilla mod settings
+ private void DrawVanillaModEntry(ModEntry entry, float scrollViewWidth, ref float curY) {
+ var buttonText = "HugsLib_setting_show_settings".Translate();
+ var buttonWidth = Text.CalcSize(buttonText).x + 20f;
+ var buttonRect = new Rect(scrollViewWidth - (buttonWidth+HandleEntryPadding), (curY - ModEntryHeight) + (ModEntryLabelHeight - ModEntryShowSettingsButtonHeight) / 2f, buttonWidth, ModEntryShowSettingsButtonHeight);
+ if (Widgets.ButtonText(buttonRect, buttonText)) {
+ Find.WindowStack.Add(new Dialog_VanillaModSettings(entry.VanillaMod));
+ }
+ }
+
// draws the label and appropriate input for a single setting
private void DrawHandleEntry(SettingHandle handle, Rect parentRect, ref float curY, float scrollViewHeight) {
var entryHeight = HandleEntryHeight;
if (handle.CustomDrawer != null && handle.CustomDrawerHeight > entryHeight) {
- entryHeight = handle.CustomDrawerHeight + HandleEntryPadding * 2;
+ entryHeight = handle.CustomDrawerHeight + HandleEntryPadding*2;
}
var skipDrawing = curY - scrollPosition.y + entryHeight < 0f || curY - scrollPosition.y > scrollViewHeight;
if (!skipDrawing) {
var entryRect = new Rect(parentRect.x, parentRect.y + curY, parentRect.width, entryHeight).ContractedBy(HandleEntryPadding);
var mouseOver = Mouse.IsOver(entryRect);
if (mouseOver) Widgets.DrawHighlight(entryRect);
- var controlRect = new Rect(entryRect.x + entryRect.width / 2f, entryRect.y, entryRect.width / 2f, entryRect.height);
+ var controlRect = new Rect(entryRect.x + entryRect.width/2f, entryRect.y, entryRect.width/2f, entryRect.height);
GenUI.SetLabelAlign(TextAnchor.MiddleLeft);
- var leftHalfRect = new Rect(entryRect.x, entryRect.y, entryRect.width / 2f - HandleEntryPadding, entryRect.height);
- var labelRect = handle.CustomDrawer == null ? leftHalfRect : entryRect; // give full width to the label just in case
+ var leftHalfRect = new Rect(entryRect.x, entryRect.y, entryRect.width/2f - HandleEntryPadding, entryRect.height);
+ // give full width to the label if custom control drawer is used- this allows handle titles to be used as section titles
+ var labelRect = handle.CustomDrawer == null ? leftHalfRect : entryRect;
+ // reduce text size if label is long and wraps over to he second line
+ var expectedLabelHeight = Text.CalcHeight(handle.Title, labelRect.width);
+ if (expectedLabelHeight > labelRect.height) {
+ Text.Font = GameFont.Tiny;
+ labelRect = new Rect(labelRect.x, labelRect.y - 1f, labelRect.width, labelRect.height + 2f);
+ } else {
+ Text.Font = GameFont.Small;
+ }
Widgets.Label(labelRect, handle.Title);
+ if (Text.Font == GameFont.Tiny) {
+ Text.Font = GameFont.Small;
+ GenUI.SetLabelAlign(TextAnchor.MiddleLeft);
+ }
GenUI.ResetLabelAlign();
bool valueChanged = false;
if (handle.CustomDrawer == null) {
@@ -290,7 +318,7 @@ private void DrawBadTextValueOutline(Rect rect) {
private void ResetAllSettings() {
foreach (var pack in listedMods) {
- foreach (var handle in pack.handles) {
+ foreach (var handle in pack.Handles) {
handle.ResetToDefault();
}
}
@@ -304,43 +332,66 @@ private void ResetSetting(SettingHandle handle) {
settingsHaveChanged = true;
}
+ // pulls all available mods with settings to display
private void EnumerateSettings() {
listedMods.Clear();
totalContentHeight = 0;
- var packs = HugsLibController.Instance.Settings.ModSettingsPacks.ToList();
+
+ // get HugsLib settings packs
+ foreach (var pack in HugsLibController.Instance.Settings.ModSettingsPacks) {
+ var handles = pack.Handles.Where(h => !h.NeverVisible).ToList();
+ if (handles.Count == 0) continue;
+ totalContentHeight += ModEntryHeight + handles.Count*HandleEntryHeight;
+ var entry = new ModEntry(pack.EntryName, handles, null);
+ entry.DisplayPriority = pack.DisplayPriority;
+ listedMods.Add(entry);
+ }
+ // get vanilla mods
+ foreach (var mod in LoadedModManager.ModHandles) {
+ if(mod == null || mod.SettingsCategory().NullOrEmpty()) continue;
+ totalContentHeight += ModEntryHeight;
+ listedMods.Add(new ModEntry(mod.SettingsCategory(), new List(0), mod));
+ }
+ // normalize improperly named mods
+ foreach (var listedMod in listedMods) {
+ if (listedMod.ModName.NullOrEmpty()) {
+ listedMod.ModName = "HugsLib_setting_unnamed_mod".Translate();
+ listedMod.DisplayPriority = ModSettingsPack.ListPriority.Lower;
+ }
+ }
// sort by display priority, entry name
- packs.Sort((p1, p2) => {
+ listedMods.Sort((p1, p2) => {
if (p1.DisplayPriority != p2.DisplayPriority) return p1.DisplayPriority.CompareTo(p2.DisplayPriority);
- return String.Compare(p1.EntryName, p2.EntryName, StringComparison.Ordinal);
+ return String.Compare(p1.ModName, p2.ModName, StringComparison.Ordinal);
});
- foreach (var pack in packs) {
- var handles = pack.Handles.Where(h => !h.NeverVisible).ToList();
- if(handles.Count == 0) continue;
- totalContentHeight += ModEntryHeight + handles.Count * HandleEntryHeight;
- listedMods.Add(new ModEntry(pack.EntryName, handles));
- }
}
-
+
+ // prepares support objects to store data for settings handle controls
private void PopulateControlInfo() {
handleControlInfo.Clear();
for (int i = 0; i < listedMods.Count; i++) {
- for (int j = 0; j < listedMods[i].handles.Count; j++) {
- var handle = listedMods[i].handles[j];
+ for (int j = 0; j < listedMods[i].Handles.Count; j++) {
+ var handle = listedMods[i].Handles[j];
handleControlInfo.Add(handle, new HandleControlInfo(handle));
}
}
}
+ // stores a mod to be displayed in the settings listing
private class ModEntry {
- public readonly string ModName;
- public readonly List handles;
+ public string ModName;
+ public ModSettingsPack.ListPriority DisplayPriority;
+ public readonly List Handles;
+ public readonly Mod VanillaMod;
- public ModEntry(string modName, List handles) {
+ public ModEntry(string modName, List handles, Mod vanillaMod) {
ModName = modName;
- this.handles = handles;
+ Handles = handles;
+ VanillaMod = vanillaMod;
}
}
+ // support data for each settings handle to allow gui controls to properly display and validate
private class HandleControlInfo {
public readonly string controlName;
public readonly List enumNames;
diff --git a/Source/Settings/Dialog_VanillaModSettings.cs b/Source/Settings/Dialog_VanillaModSettings.cs
new file mode 100644
index 0000000..6ab0a87
--- /dev/null
+++ b/Source/Settings/Dialog_VanillaModSettings.cs
@@ -0,0 +1,69 @@
+using System;
+using HugsLib;
+using UnityEngine;
+using Verse;
+
+namespace RimWorld {
+ ///
+ /// Displays custom settings controls for mods that use the vanilla mod settings system.
+ /// The dialog shows the controls for a single mod only and is opened through Dialog_ModSettings.
+ ///
+ public class Dialog_VanillaModSettings : Window {
+
+ private const float TitleLabelHeight = 32f;
+ private readonly Color TitleLineColor = new Color(0.3f, 0.3f, 0.3f);
+
+ private readonly Mod selectedMod;
+ private Exception guiException;
+
+ public override Vector2 InitialSize {
+ get { return new Vector2(900f, 700f); }
+ }
+
+ public Dialog_VanillaModSettings(Mod mod) {
+ selectedMod = mod;
+ forcePause = true;
+ doCloseX = true;
+ closeOnEscapeKey = true;
+ doCloseButton = true;
+ closeOnClickedOutside = true;
+ absorbInputAroundWindow = true;
+ }
+
+ public override void PreClose() {
+ base.PreClose();
+ if (selectedMod != null) {
+ selectedMod.WriteSettings();
+ }
+ }
+
+ public override void DoWindowContents(Rect inRect) {
+ if (selectedMod == null) return;
+ var modName = selectedMod.SettingsCategory();
+ if (!modName.NullOrEmpty()) {
+ var titleRect = new Rect(0f, 0f, inRect.width, TitleLabelHeight);
+ Text.Font = GameFont.Medium;
+ Widgets.Label(titleRect, "HugsLib_setting_mod_name_title".Translate(modName));
+
+ var prevColor = GUI.color;
+ GUI.color = TitleLineColor;
+ Widgets.DrawLineHorizontal(0f, TitleLabelHeight, inRect.width);
+ GUI.color = prevColor;
+ }
+
+ Text.Font = GameFont.Small;
+ var modContentRect = new Rect(0f, 40f, inRect.width, inRect.height - 40f - CloseButSize.y);
+ if (guiException == null) {
+ try {
+ selectedMod.DoSettingsWindowContents(modContentRect);
+ } catch (Exception e) {
+ guiException = e;
+ HugsLibController.Logger.ReportException(e);
+ }
+ } else {
+ Text.Font = GameFont.Tiny;
+ Widgets.Label(modContentRect, "An error occurred while displaying the mods settings page:\n"+guiException);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/Settings/ModSettingsPack.cs b/Source/Settings/ModSettingsPack.cs
index fb99ad1..7c0d413 100644
--- a/Source/Settings/ModSettingsPack.cs
+++ b/Source/Settings/ModSettingsPack.cs
@@ -12,7 +12,7 @@ namespace HugsLib.Settings {
///
public class ModSettingsPack {
public enum ListPriority {
- Higher, Normal, Lower
+ Higher, Normal, Lower, Lowest
}
///
diff --git a/Source/Settings/OptionsDialogInjection.cs b/Source/Settings/OptionsDialogInjection.cs
index 3f929dd..cf9a48a 100644
--- a/Source/Settings/OptionsDialogInjection.cs
+++ b/Source/Settings/OptionsDialogInjection.cs
@@ -1,4 +1,5 @@
-using UnityEngine;
+using RimWorld;
+using UnityEngine;
using Verse;
namespace HugsLib.Settings {
@@ -6,19 +7,16 @@ namespace HugsLib.Settings {
/// Injects the "Mod Settings" button into the Options dialog.
///
internal static class OptionsDialogInjection {
- private const float ButtonBottomOffset = 90f;
- private static readonly Vector2 ButtonSize = new Vector2(277f, 40f);
private static readonly Color ButtonColor = new Color(.55f, 1f, .55f);
- public static void DrawSettingsDialogExtensions(Window window, Rect inRect) {
- var btnRect = new Rect(inRect.x, inRect.height - ButtonBottomOffset - ButtonSize.y, ButtonSize.x, ButtonSize.y);
+ public static void DrawModSettingsButton(Listing_Standard listing) {
var prevColor = GUI.color;
GUI.color = ButtonColor;
- if (Widgets.ButtonText(btnRect, "HugsLib_settings_btn".Translate())) {
- Find.WindowStack.TryRemove(window);
+ if (listing.ButtonText("HugsLib_settings_btn".Translate())) {
+ Find.WindowStack.TryRemove(typeof(Dialog_Options));
Find.WindowStack.Add(new Dialog_ModSettings());
}
GUI.color = prevColor;
- }
+ }
}
}
\ No newline at end of file
diff --git a/Source/Shell/ShellRestartRimWorld.cs b/Source/Shell/ShellRestartRimWorld.cs
index ab2ea54..15c0e05 100644
--- a/Source/Shell/ShellRestartRimWorld.cs
+++ b/Source/Shell/ShellRestartRimWorld.cs
@@ -6,6 +6,7 @@ namespace HugsLib.Shell {
/// A Command to cleanly restart RimWorld on the target machine.
///
public static class ShellRestartRimWorld {
+ [Obsolete(message: "Use of GenCommandLine.Restart() is recommended since A17.")]
public static bool Execute() {
HugsLibController.Logger.Message("Restarting RimWorld");
if (Shell.StartProcess(GetParsedArgs())) {
diff --git a/Source/Test/TestMod.cs b/Source/Test/TestMod.cs
index 6365a92..b5bad5d 100644
--- a/Source/Test/TestMod.cs
+++ b/Source/Test/TestMod.cs
@@ -96,7 +96,7 @@ public override void DefsLoaded() {
var spinner = Settings.GetHandle("intSpinner", "Spinner", "desc", 5, Validators.IntRangeValidator(0, 30));
spinner.SpinnerIncrement = 2;
var enumHandle = Settings.GetHandle("enumThing", "Enum setting", "", HandleEnum.DefaultValue, null, "test_enumSetting_");
- var toggle = Settings.GetHandle("toggle", "Toggle setting", "Toggle setting", false);
+ var toggle = Settings.GetHandle("toggle", "Toggle setting extra long title that would not fit into one line", "Toggle setting", false);
var custom = Settings.GetHandle("custom", "custom setting", "custom setting desc", false);
custom.CustomDrawerHeight = 30f;
custom.CustomDrawer = rect => {
diff --git a/Source/Test/TestUWO.cs b/Source/Test/TestUWO.cs
index 5d4a731..a0b2e85 100644
--- a/Source/Test/TestUWO.cs
+++ b/Source/Test/TestUWO.cs
@@ -8,7 +8,7 @@ internal class TestUWO1 : UtilityWorldObject {
public override void ExposeData() {
base.ExposeData();
- Scribe_Values.LookValue(ref testInt, "testInt", 0);
+ Scribe_Values.Look(ref testInt, "testInt", 0);
}
public void UpdateAndReport() {
@@ -22,7 +22,7 @@ internal class TestUWO2 : UtilityWorldObject {
public override void ExposeData() {
base.ExposeData();
- Scribe_Values.LookValue(ref testString, "testString", "");
+ Scribe_Values.Look(ref testString, "testString", "");
}
public void UpdateAndReport() {
diff --git a/Source/Test/VanillaMod.cs b/Source/Test/VanillaMod.cs
new file mode 100644
index 0000000..4e8d525
--- /dev/null
+++ b/Source/Test/VanillaMod.cs
@@ -0,0 +1,39 @@
+#if TEST_MOD
+using System;
+using UnityEngine;
+using Verse;
+
+namespace HugsLib.Test {
+ public class VanillaMod : Mod {
+ public VanillaMod(ModContentPack content) : base(content) {
+ HugsLibController.Logger.Message("vanilla mod 1 construct");
+ }
+
+ public override void DoSettingsWindowContents(Rect inRect) {
+ Widgets.ButtonText(new Rect(inRect.x, inRect.y, 200f, 30f), "stuff1");
+ Widgets.ButtonText(new Rect(inRect.x, inRect.y + 40f, 200f, 30f), "things1");
+ }
+
+ public override string SettingsCategory() {
+ return "VanillaMod I";
+ }
+ }
+
+ public class VanillaMod2 : Mod {
+ public VanillaMod2(ModContentPack content)
+ : base(content) {
+ HugsLibController.Logger.Message("vanilla mod 2 construct");
+ }
+
+ public override void DoSettingsWindowContents(Rect inRect) {
+ Widgets.ButtonText(new Rect(inRect.x + 210, inRect.y, 200f, 30f), "stuff2");
+ Widgets.ButtonText(new Rect(inRect.x + 210, inRect.y + 40f, 200f, 30f), "things2");
+ //throw new Exception("honk");
+ }
+
+ public override string SettingsCategory() {
+ return "VanillaMod II";
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/Source/Utils/Dialog_Message.cs b/Source/Utils/Dialog_Message.cs
index 5e9b935..76fdd71 100644
--- a/Source/Utils/Dialog_Message.cs
+++ b/Source/Utils/Dialog_Message.cs
@@ -1,4 +1,5 @@
-using UnityEngine;
+using System;
+using UnityEngine;
using Verse;
namespace HugsLib.Utils {
@@ -9,6 +10,7 @@ public class Dialog_Message : Window {
private readonly string title;
private readonly string message;
private readonly string closeButtonText;
+ private readonly Action postCloseAction;
public override Vector2 InitialSize {
get { return new Vector2(500f, 400f); }
@@ -17,10 +19,11 @@ public override Vector2 InitialSize {
/// A title to display in the dialog
/// A message to display in the dialog
/// A custom label to the close button. Optional- when null, the default label will be used instead.
- public Dialog_Message(string title, string message, string closeButtonText = null) {
+ public Dialog_Message(string title, string message, string closeButtonText = null, Action postCloseAction = null) {
this.title = title;
this.message = message;
this.closeButtonText = closeButtonText;
+ this.postCloseAction = postCloseAction;
closeOnEscapeKey = true;
doCloseButton = false;
doCloseX = true;
@@ -41,5 +44,10 @@ public override void DoWindowContents(Rect inRect) {
Close();
}
}
+
+ public override void PostClose() {
+ base.PostClose();
+ if (postCloseAction != null) postCloseAction();
+ }
}
}
\ No newline at end of file
diff --git a/Source/Utils/HarmonyUtility.cs b/Source/Utils/HarmonyUtility.cs
index 51a31f4..cdf89c5 100644
--- a/Source/Utils/HarmonyUtility.cs
+++ b/Source/Utils/HarmonyUtility.cs
@@ -53,19 +53,19 @@ public static string DescribePatchedMethods(HarmonyInstance instance) {
if (HasActivePatches(patches)) {
// write prefixes
if (patches.Prefixes != null && patches.Prefixes.Count > 0) {
- builder.Append("Pre: ");
+ builder.Append("PRE: ");
AppendPatchList(patches.Prefixes, builder);
}
// write postfixes
if (patches.Postfixes != null && patches.Postfixes.Count > 0) {
EnsureEndsWithSpace(builder);
- builder.Append("Post: ");
+ builder.Append("post: ");
AppendPatchList(patches.Postfixes, builder);
}
// write transpilers
if (patches.Transpilers != null && patches.Transpilers.Count > 0) {
EnsureEndsWithSpace(builder);
- builder.Append("Trans: ");
+ builder.Append("TRANS: ");
AppendPatchList(patches.Transpilers, builder);
}
} else {
diff --git a/Source/Utils/HugsLibUtility.cs b/Source/Utils/HugsLibUtility.cs
index 7a225e2..f4fd469 100644
--- a/Source/Utils/HugsLibUtility.cs
+++ b/Source/Utils/HugsLibUtility.cs
@@ -71,7 +71,31 @@ public static void ToggleDesignation(this Thing thing, DesignationDef def, bool
}
}
+ // Checks if a cell has a designation of a given def
+ public static bool HasDesignation(this IntVec3 pos, DesignationDef def, Map map = null) {
+ if (map == null) {
+ map = Find.VisibleMap;
+ }
+ if (map == null || map.designationManager == null) return false;
+ return map.designationManager.DesignationAt(pos, def) != null;
+ }
+
+ // Adds or removes a designation of a given def on a cell. Fails silently if designation is already in the desired state.
+ public static void ToggleDesignation(this IntVec3 pos, DesignationDef def, bool enable, Map map = null) {
+ if (map == null) {
+ map = Find.VisibleMap;
+ }
+ if (map == null || map.designationManager == null) throw new Exception("ToggleDesignation requires a map argument or VisibleMap must be set");
+ var des = map.designationManager.DesignationAt(pos, def);
+ if (enable && des == null) {
+ map.designationManager.AddDesignation(new Designation(pos, def));
+ } else if (!enable && des != null) {
+ map.designationManager.RemoveDesignation(des);
+ }
+ }
+
public static bool MethodMatchesSignature(this MethodInfo method, Type expectedReturnType, params Type[] expectedParameters) {
+ if (method == null) return false;
if (method.ReturnType != expectedReturnType) return false;
var methodParams = method.GetParameters();
if (expectedParameters != null) {
diff --git a/Source/Utils/InjectedDefHasher.cs b/Source/Utils/InjectedDefHasher.cs
index 7eae36a..310c5e2 100644
--- a/Source/Utils/InjectedDefHasher.cs
+++ b/Source/Utils/InjectedDefHasher.cs
@@ -10,15 +10,21 @@ public static class InjectedDefHasher {
private static MethodInfo giveShortHashMethod;
internal static void PrepareReflection() {
- giveShortHashMethod = typeof(ShortHashGiver).GetMethod("GiveShortHash", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof(Def) }, null);
+ giveShortHashMethod = typeof(ShortHashGiver).GetMethod("GiveShortHash", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof(Def), typeof(Type) }, null);
if (giveShortHashMethod == null) {
HugsLibController.Logger.Error("Failed to reflect ShortHashGiver.GiveShortHash");
}
}
- public static void GiveShortHasToDef(Def newDef) {
+ ///
+ /// Give a short hash to a def created at runtime.
+ /// Short hashes are used for proper saving of defs in compressed maps within a save file.
+ ///
+ ///
+ /// The type of defs your def will be saved with. For example, use typeof(ThingDef) if your def extends ThingDef.
+ public static void GiveShortHasToDef(Def newDef, Type defType) {
if(giveShortHashMethod == null) throw new Exception("Hasher not initalized");
- giveShortHashMethod.Invoke(null, new object[] { newDef });
+ giveShortHashMethod.Invoke(null, new object[] { newDef, defType });
}
}
}
\ No newline at end of file
diff --git a/Source/Utils/ModLogger.cs b/Source/Utils/ModLogger.cs
index 4adfaa4..d498dca 100644
--- a/Source/Utils/ModLogger.cs
+++ b/Source/Utils/ModLogger.cs
@@ -9,6 +9,9 @@ namespace HugsLib.Utils {
/// A logger that prefixes all mesages with the identifier of the issuing mod.
///
public class ModLogger {
+ private const string WarningPrefix = "warn";
+ private const string ErrorPrefix = "ERR";
+
private readonly StringBuilder builder;
private readonly string logPrefix;
private string lastExceptionLocation;
@@ -24,14 +27,14 @@ public ModLogger(string logPrefix) {
/// The message to write
/// Optional substitution values for the message
public void Message(string message, params object[] substitiutions) {
- Log.Message(FormatOutput(message, substitiutions));
+ Log.Message(FormatOutput(message, null, substitiutions));
}
///
/// Same as Message(), but the console will display the message as a warning.
///
public void Warning(string message, params object[] substitiutions) {
- Log.Warning(FormatOutput(message, substitiutions));
+ Log.Warning(FormatOutput(message, WarningPrefix, substitiutions));
}
///
@@ -39,7 +42,7 @@ public void Warning(string message, params object[] substitiutions) {
/// This will open the Log window in in Dev mode.
///
public void Error(string message, params object[] substitiutions) {
- Log.Error(FormatOutput(message, substitiutions));
+ Log.Error(FormatOutput(message, ErrorPrefix, substitiutions));
}
///
@@ -50,7 +53,7 @@ public void Trace(params object[] strings) {
if (!Prefs.DevMode) return;
if (strings.Length == 1) {
var msg = strings[0];
- Log.Message(FormatOutput(msg != null ? msg.ToString() : "null"));
+ Log.Message(FormatOutput(msg != null ? msg.ToString() : "null", null));
} else {
Log.Message(strings.ListElements());
}
@@ -61,7 +64,7 @@ public void Trace(params object[] strings) {
///
public void TraceFormat(string message, params object[] substitiutions) {
if (!Prefs.DevMode) return;
- Log.Message(FormatOutput(message, substitiutions));
+ Log.Message(FormatOutput(message, null, substitiutions));
}
///
@@ -80,18 +83,20 @@ public void ReportException(Exception e, string modIdentifier = null, bool repor
lastExceptionLocation = location;
string message;
if (modIdentifier != null) {
- message = FormatOutput("{0} caused an exception during {1}: {2}", modIdentifier, location, e);
+ message = FormatOutput("{0} caused an exception during {1}: {2}", ErrorPrefix, modIdentifier, location, e);
} else {
- message = FormatOutput("Exception during {0}: {1}", location, e);
+ message = FormatOutput("Exception during {0}: {1}", ErrorPrefix, location, e);
}
Log.Error(message);
}
- private string FormatOutput(string message, params object[] substitiutions) {
+ private string FormatOutput(string message, string extraPrefix, params object[] substitiutions) {
builder.Length = 0;
- builder.Append("[");
- builder.Append(logPrefix);
- builder.Append("] ");
+ builder.AppendFormat("[{0}]", logPrefix);
+ if (extraPrefix!=null) {
+ builder.AppendFormat("[{0}]", extraPrefix);
+ }
+ builder.Append(" ");
if (substitiutions.Length>0) {
builder.AppendFormat(message, substitiutions);
} else {
diff --git a/Source/Utils/UtilityWorldObjectManager.cs b/Source/Utils/UtilityWorldObjectManager.cs
index da0571d..9098b84 100644
--- a/Source/Utils/UtilityWorldObjectManager.cs
+++ b/Source/Utils/UtilityWorldObjectManager.cs
@@ -71,7 +71,7 @@ private static void InjectUtilityObjectDef() {
useDynamicDrawer = true
};
- InjectedDefHasher.GiveShortHasToDef(def);
+ InjectedDefHasher.GiveShortHasToDef(def, typeof(WorldObject));
DefDatabase.Add(def);
}
}