diff --git a/FinishTasks/FinishTasksPatches.cs b/FinishTasks/FinishTasksPatches.cs
index 00c9b97a..fc5ebf22 100644
--- a/FinishTasks/FinishTasksPatches.cs
+++ b/FinishTasks/FinishTasksPatches.cs
@@ -1,251 +1,258 @@
-/*
- * Copyright 2024 Peter Han
- * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
- * and associated documentation files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all copies or
- * substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
- * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
- * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
-using Database;
-using HarmonyLib;
-using PeterHan.PLib.AVC;
-using PeterHan.PLib.Core;
-using PeterHan.PLib.Database;
-using System.Collections.Generic;
-using PeterHan.PLib.PatchManager;
-using PeterHan.PLib.UI;
-using UnityEngine;
-
-using FINISHTASK = PeterHan.FinishTasks.FinishTasksStrings.UI.SCHEDULEGROUPS.FINISHTASK;
-using System.Reflection;
-
-namespace PeterHan.FinishTasks {
- ///
- /// Patches which will be applied via annotations for Rest for the Weary.
- ///
- public sealed class FinishTasksPatches : KMod.UserMod2 {
- ///
- /// A dummy schedule block type, since the game compares whether schedule types are
- /// different by checking to see if they permit different types of jobs. Even if no
- /// chore actually has this type, it is enough to differentiate FinishTask from Work.
- ///
- public static ScheduleBlockType FinishBlock { get; private set; }
-
- ///
- /// The colors used for displaying the finish task on the schedule.
- ///
- private static ColorStyleSetting FinishColor;
-
- ///
- /// The schedule group to use for finishing tasks.
- ///
- public static ScheduleGroup FinishTask { get; private set; }
-
- ///
- /// The precondition evaluated to see if a Duplicant can start a new Work type task.
- ///
- private static Chore.Precondition CAN_START_NEW = new Chore.Precondition() {
- id = "PeterHan.FinishTasks.CanStartNewTask",
- description = FinishTasksStrings.DUPLICANTS.CHORES.PRECONDITIONS.
- CAN_START_NEW_TASK,
- fn = CheckStartNew
- };
-
- ///
- /// The ID for the IsScheduledTime precondition.
- ///
- private static string IsScheduledTimeID;
-
- ///
- /// Cached reference to Db.Get().ScheduleBlockTypes.Work.
- ///
- private static ScheduleBlockType Work;
-
- ///
- /// Checks to see if a Duplicant can start a new task.
- ///
- /// The context related to the chore in question.
- /// The current work chore.
- /// true if new chores can be started, or false otherwise.
- private static bool CheckStartNew(ref Chore.Precondition.Context context,
- object targetChore) {
- var state = context.consumerState;
- var driver = state.choreDriver;
- var scheduleBlock = state.scheduleBlock;
- var alertStatus = ClusterManager.Instance.GetWorld(driver.GetMyWorldId());
- bool start = true, normal = !alertStatus.IsYellowAlert() && !alertStatus.
- IsRedAlert();
- // Bypass on red/yellow alert, only evaluate condition during Finish Tasks blocks,
- // allow the current chore to continue, or new work chores to be evaluated if the
- // current chore is compulsory like emotes
- if (normal && scheduleBlock != null && scheduleBlock.GroupId == FinishTask.Id) {
- var currentChore = driver.GetCurrentChore();
- // Allow the task that the Duplicant initially was doing to continue even if
- // temporarily interrupted
- var savedChore = driver.TryGetComponent(out FinishChoreDetector detector) ?
- null : (detector.IsAcquiringChore ? currentChore : detector.TaskToFinish);
- start = currentChore != null && (currentChore == context.chore || currentChore.
- masterPriority.priority_class == PriorityScreen.PriorityClass.compulsory ||
- savedChore == context.chore);
- }
- return start;
- }
-
- ///
- /// Applied to MinionConfig to add a task completion sensor to each Duplicant.
- ///
- [PLibPatch(RunAt.AfterDbInit, nameof(BaseMinionConfig.BaseMinion),
- RequireType = nameof(BaseMinionConfig), PatchType = HarmonyPatchType.Postfix)]
- internal static void MinionConfig_Postfix(GameObject __result) {
- if (__result != null)
- __result.AddOrGet();
- }
-
- public override void OnLoad(Harmony harmony) {
- base.OnLoad(harmony);
- FinishBlock = null;
- FinishColor = ScriptableObject.CreateInstance();
- FinishColor.activeColor = new Color(0.8f, 0.6f, 1.0f, 1.0f);
- FinishColor.inactiveColor = new Color(0.5f, 0.286f, 1.0f, 1.0f);
- FinishColor.disabledColor = new Color(0.4f, 0.4f, 0.416f, 1.0f);
- FinishColor.disabledActiveColor = new Color(0.6f, 0.588f, 0.625f, 1.0f);
- FinishColor.hoverColor = FinishColor.activeColor;
- FinishColor.disabledhoverColor = new Color(0.48f, 0.46f, 0.5f, 1.0f);
- FinishTask = null;
- IsScheduledTimeID = string.Empty;
- Work = null;
- PUtil.InitLibrary();
- LocString.CreateLocStringKeys(typeof(FinishTasksStrings.DUPLICANTS));
- LocString.CreateLocStringKeys(typeof(FinishTasksStrings.UI));
- new PPatchManager(harmony).RegisterPatchClass(typeof(FinishTasksPatches));
- new PLocalization().Register();
- new PVersionCheck().Register(this, new SteamVersionChecker());
- }
-
- ///
- /// Applied to StandardChoreBase to add a precondition for not starting new work chores
- /// during finish tasks blocks.
- ///
- [HarmonyPatch]
- public static class StandardChoreBase_AddPrecondition_Patch {
- internal static IEnumerable TargetMethods() {
- const string METHOD_NAME = nameof(Chore.AddPrecondition);
- yield return typeof(StandardChoreBase).GetMethodSafe(METHOD_NAME, false,
- PPatchTools.AnyArguments);
- yield return typeof(MovePickupableChore).GetMethodSafe(METHOD_NAME, false,
- PPatchTools.AnyArguments);
- }
-
- ///
- /// Applied after AddPrecondition runs.
- ///
- internal static void Postfix(Chore __instance, Chore.Precondition precondition,
- object data) {
- if (precondition.id == IsScheduledTimeID && (data is ScheduleBlockType type) &&
- type == Work)
- // Any task classified as Work gets our finish time precondition
- __instance.AddPrecondition(CAN_START_NEW, __instance);
- }
- }
-
- ///
- /// Applied to MingleMonitor to schedule our Finish Mingle chore during Finish Tasks
- /// time.
- ///
- [HarmonyPatch(typeof(MingleMonitor), nameof(MingleMonitor.InitializeStates))]
- public static class MingleMonitor_InitializeStates_Patch {
- ///
- /// Creates a Finish Tasks Mingle chore.
- ///
- private static Chore CreateMingleChore(MingleMonitor.Instance smi) {
- return new FinishMingleChore(smi.master);
- }
-
- ///
- /// Applied after InitializeStates runs.
- ///
- internal static void Postfix(MingleMonitor __instance) {
- __instance.mingle.ToggleRecurringChore(CreateMingleChore);
- }
- }
-
- ///
- /// Applied to ScheduleBlockTypes to create and add our dummy chore type.
- ///
- [HarmonyPatch(typeof(ScheduleBlockTypes), MethodType.Constructor, typeof(ResourceSet))]
- public static class ScheduleBlockTypes_Constructor_Patch {
- ///
- /// Applied after the constructor runs.
- ///
- internal static void Postfix(ScheduleBlockTypes __instance) {
- var color = FinishColor != null ? FinishColor.activeColor : Color.green;
- FinishBlock = __instance.Add(new ScheduleBlockType(FINISHTASK.ID, __instance,
- FINISHTASK.NAME, FINISHTASK.DESCRIPTION, color));
- // Allow localization to update the string (this is running in Db.Initialize)
- CAN_START_NEW.description = FinishTasksStrings.DUPLICANTS.CHORES.
- PRECONDITIONS.CAN_START_NEW_TASK;
- }
- }
-
- ///
- /// Applied to ScheduleGroups to add our new block type.
- ///
- [HarmonyPatch(typeof(ScheduleGroups), MethodType.Constructor, typeof(ResourceSet))]
- public static class ScheduleGroups_Constructor_Patch {
- ///
- /// Applied after the constructor runs.
- ///
- internal static void Postfix(ScheduleGroups __instance) {
- Work = Db.Get().ScheduleBlockTypes.Work;
- if (Work == null || FinishBlock == null)
- PUtil.LogError("Schedule block types undefined for FinishTask group!");
- else
- // Default schedule does not contain this type
- FinishTask = __instance.Add(FINISHTASK.ID, 0, FINISHTASK.NAME, FINISHTASK.
- DESCRIPTION, FinishColor.inactiveColor,
- FINISHTASK.NOTIFICATION_TOOLTIP, new List {
- Work, FinishBlock
- });
- IsScheduledTimeID = ChorePreconditions.instance.IsScheduledTime.id;
- }
- }
-
- ///
- /// Applied to ScheduleScreenEntry to add a button for Finish Tasks.
- ///
- [HarmonyPatch(typeof(ScheduleScreenEntry), nameof(ScheduleScreenEntry.Setup))]
- public static class ScheduleScreenEntry_Setup_Patch {
- ///
- /// Applied after Setup runs.
- ///
- internal static void Postfix(ScheduleScreenEntry __instance) {
- var bathtime = __instance.PaintButtonBathtime;
- if (bathtime != null && FinishBlock != null) {
- var button = Util.KInstantiateUI(bathtime, bathtime.GetParent());
- if (button.TryGetComponent(out MultiToggle toggle)) {
- // Set the color
- ref var ads = ref toggle.states[0].additional_display_settings[0];
- ads.color = FinishColor.inactiveColor;
- ads.color_on_hover = FinishColor.hoverColor;
- toggle.states[1].additional_display_settings[0].color = FinishColor.
- inactiveColor;
- }
- button.name = FINISHTASK.ID;
- __instance.ConfigPaintButton(button, FinishTask, Def.GetUISprite(
- Assets.GetPrefab("PropClock")).first);
- __instance.RefreshPaintButtons();
- }
- }
- }
- }
-}
+/*
+ * Copyright 2024 Peter Han
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+ * and associated documentation files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+ * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+using Database;
+using HarmonyLib;
+using PeterHan.PLib.AVC;
+using PeterHan.PLib.Core;
+using PeterHan.PLib.Database;
+using System.Collections.Generic;
+using System.Reflection;
+using PeterHan.PLib.PatchManager;
+using PeterHan.PLib.UI;
+using UnityEngine;
+
+using FINISHTASK = PeterHan.FinishTasks.FinishTasksStrings.UI.SCHEDULEGROUPS.FINISHTASK;
+
+namespace PeterHan.FinishTasks {
+ ///
+ /// Patches which will be applied via annotations for Rest for the Weary.
+ ///
+ public sealed class FinishTasksPatches : KMod.UserMod2 {
+ ///
+ /// A dummy schedule block type, since the game compares whether schedule types are
+ /// different by checking to see if they permit different types of jobs. Even if no
+ /// chore actually has this type, it is enough to differentiate FinishTask from Work.
+ ///
+ public static ScheduleBlockType FinishBlock { get; private set; }
+
+ ///
+ /// The colors used for displaying the finish task on the schedule.
+ ///
+ private static ColorStyleSetting FinishColor;
+
+ ///
+ /// The schedule group to use for finishing tasks.
+ ///
+ public static ScheduleGroup FinishTask { get; private set; }
+
+ ///
+ /// The precondition evaluated to see if a Duplicant can start a new Work type task.
+ ///
+ private static Chore.Precondition CAN_START_NEW = new Chore.Precondition() {
+ id = "PeterHan.FinishTasks.CanStartNewTask",
+ description = FinishTasksStrings.DUPLICANTS.CHORES.PRECONDITIONS.
+ CAN_START_NEW_TASK,
+ fn = CheckStartNew
+ };
+
+ ///
+ /// The ID for the IsScheduledTime precondition.
+ ///
+ private static string IsScheduledTimeID;
+
+ ///
+ /// Cached reference to Db.Get().ScheduleBlockTypes.Work.
+ ///
+ private static ScheduleBlockType Work;
+
+ ///
+ /// Checks to see if a Duplicant can start a new task.
+ ///
+ /// The context related to the chore in question.
+ /// The current work chore.
+ /// true if new chores can be started, or false otherwise.
+ private static bool CheckStartNew(ref Chore.Precondition.Context context,
+ object targetChore) {
+ var state = context.consumerState;
+ var driver = state.choreDriver;
+ var scheduleBlock = state.scheduleBlock;
+ var alertStatus = ClusterManager.Instance.GetWorld(driver.GetMyWorldId());
+ bool start = true, normal = !alertStatus.IsYellowAlert() && !alertStatus.
+ IsRedAlert();
+ // Bypass on red/yellow alert, only evaluate condition during Finish Tasks blocks,
+ // allow the current chore to continue, or new work chores to be evaluated if the
+ // current chore is compulsory like emotes
+ if (normal && scheduleBlock != null && scheduleBlock.GroupId == FinishTask.Id) {
+ var currentChore = driver.GetCurrentChore();
+ // Allow the task that the Duplicant initially was doing to continue even if
+ // temporarily interrupted
+ var savedChore = driver.TryGetComponent(out FinishChoreDetector detector) ?
+ null : (detector.IsAcquiringChore ? currentChore : detector.TaskToFinish);
+ start = currentChore != null && (currentChore == context.chore || currentChore.
+ masterPriority.priority_class == PriorityScreen.PriorityClass.compulsory ||
+ savedChore == context.chore);
+ }
+ return start;
+ }
+
+ ///
+ /// Applied to MinionConfig to add a task completion sensor to each Duplicant.
+ ///
+ [PLibPatch(RunAt.AfterDbInit, nameof(BaseMinionConfig.BaseMinion),
+ RequireType = nameof(BaseMinionConfig), PatchType = HarmonyPatchType.Postfix)]
+ internal static void MinionConfig_Postfix(GameObject __result) {
+ if (__result != null)
+ __result.AddOrGet();
+ }
+
+ public override void OnLoad(Harmony harmony) {
+ base.OnLoad(harmony);
+ FinishBlock = null;
+ FinishColor = ScriptableObject.CreateInstance();
+ FinishColor.activeColor = new Color(0.8f, 0.6f, 1.0f, 1.0f);
+ FinishColor.inactiveColor = new Color(0.5f, 0.286f, 1.0f, 1.0f);
+ FinishColor.disabledColor = new Color(0.4f, 0.4f, 0.416f, 1.0f);
+ FinishColor.disabledActiveColor = new Color(0.6f, 0.588f, 0.625f, 1.0f);
+ FinishColor.hoverColor = FinishColor.activeColor;
+ FinishColor.disabledhoverColor = new Color(0.48f, 0.46f, 0.5f, 1.0f);
+ FinishTask = null;
+ IsScheduledTimeID = string.Empty;
+ Work = null;
+ PUtil.InitLibrary();
+ LocString.CreateLocStringKeys(typeof(FinishTasksStrings.DUPLICANTS));
+ LocString.CreateLocStringKeys(typeof(FinishTasksStrings.UI));
+ new PPatchManager(harmony).RegisterPatchClass(typeof(FinishTasksPatches));
+ new PLocalization().Register();
+ new PVersionCheck().Register(this, new SteamVersionChecker());
+ }
+
+ ///
+ /// Applied to StandardChoreBase to add a precondition for not starting new work chores
+ /// during finish tasks blocks.
+ ///
+ [HarmonyPatch(typeof(StandardChoreBase), nameof(Chore.AddPrecondition))]
+ public static class StandardChoreBase_AddPrecondition_Patch {
+ ///
+ /// Applied after AddPrecondition runs.
+ ///
+ internal static void Postfix(Chore __instance, Chore.Precondition precondition,
+ object data) {
+ if (precondition.id == IsScheduledTimeID && (data is ScheduleBlockType type) &&
+ type == Work)
+ // Any task classified as Work gets our finish time precondition
+ __instance.AddPrecondition(CAN_START_NEW, __instance);
+ }
+ }
+
+ ///
+ /// Applied to MovePickupableChore to add a precondition for not starting new Relocate
+ /// Item chores during finish tasks blocks.
+ ///
+ [HarmonyPatch(typeof(MovePickupableChore), MethodType.Constructor,
+ typeof(IStateMachineTarget), typeof(GameObject), typeof(System.Action))]
+ public static class MovePickupableChore_AddPrecondition_Patch {
+ ///
+ /// Applied after the constructor runs.
+ ///
+ internal static void Postfix(Chore __instance) {
+ __instance.AddPrecondition(CAN_START_NEW, __instance);
+ }
+ }
+
+ ///
+ /// Applied to MingleMonitor to schedule our Finish Mingle chore during Finish Tasks
+ /// time.
+ ///
+ [HarmonyPatch(typeof(MingleMonitor), nameof(MingleMonitor.InitializeStates))]
+ public static class MingleMonitor_InitializeStates_Patch {
+ ///
+ /// Creates a Finish Tasks Mingle chore.
+ ///
+ private static Chore CreateMingleChore(MingleMonitor.Instance smi) {
+ return new FinishMingleChore(smi.master);
+ }
+
+ ///
+ /// Applied after InitializeStates runs.
+ ///
+ internal static void Postfix(MingleMonitor __instance) {
+ __instance.mingle.ToggleRecurringChore(CreateMingleChore);
+ }
+ }
+
+ ///
+ /// Applied to ScheduleBlockTypes to create and add our dummy chore type.
+ ///
+ [HarmonyPatch(typeof(ScheduleBlockTypes), MethodType.Constructor, typeof(ResourceSet))]
+ public static class ScheduleBlockTypes_Constructor_Patch {
+ ///
+ /// Applied after the constructor runs.
+ ///
+ internal static void Postfix(ScheduleBlockTypes __instance) {
+ var color = FinishColor != null ? FinishColor.activeColor : Color.green;
+ FinishBlock = __instance.Add(new ScheduleBlockType(FINISHTASK.ID, __instance,
+ FINISHTASK.NAME, FINISHTASK.DESCRIPTION, color));
+ // Allow localization to update the string (this is running in Db.Initialize)
+ CAN_START_NEW.description = FinishTasksStrings.DUPLICANTS.CHORES.
+ PRECONDITIONS.CAN_START_NEW_TASK;
+ }
+ }
+
+ ///
+ /// Applied to ScheduleGroups to add our new block type.
+ ///
+ [HarmonyPatch(typeof(ScheduleGroups), MethodType.Constructor, typeof(ResourceSet))]
+ public static class ScheduleGroups_Constructor_Patch {
+ ///
+ /// Applied after the constructor runs.
+ ///
+ internal static void Postfix(ScheduleGroups __instance) {
+ Work = Db.Get().ScheduleBlockTypes.Work;
+ if (Work == null || FinishBlock == null)
+ PUtil.LogError("Schedule block types undefined for FinishTask group!");
+ else
+ // Default schedule does not contain this type
+ FinishTask = __instance.Add(FINISHTASK.ID, 0, FINISHTASK.NAME, FINISHTASK.
+ DESCRIPTION, FinishColor.inactiveColor,
+ FINISHTASK.NOTIFICATION_TOOLTIP, new List {
+ Work, FinishBlock
+ });
+ IsScheduledTimeID = ChorePreconditions.instance.IsScheduledTime.id;
+ }
+ }
+
+ ///
+ /// Applied to ScheduleScreenEntry to add a button for Finish Tasks.
+ ///
+ [HarmonyPatch(typeof(ScheduleScreenEntry), nameof(ScheduleScreenEntry.Setup))]
+ public static class ScheduleScreenEntry_Setup_Patch {
+ ///
+ /// Applied after Setup runs.
+ ///
+ internal static void Postfix(ScheduleScreenEntry __instance) {
+ var bathtime = __instance.PaintButtonBathtime;
+ if (bathtime != null && FinishBlock != null) {
+ var button = Util.KInstantiateUI(bathtime, bathtime.GetParent());
+ if (button.TryGetComponent(out MultiToggle toggle)) {
+ // Set the color
+ ref var ads = ref toggle.states[0].additional_display_settings[0];
+ ads.color = FinishColor.inactiveColor;
+ ads.color_on_hover = FinishColor.hoverColor;
+ toggle.states[1].additional_display_settings[0].color = FinishColor.
+ inactiveColor;
+ }
+ button.name = FINISHTASK.ID;
+ __instance.ConfigPaintButton(button, FinishTask, Def.GetUISprite(
+ Assets.GetPrefab("PropClock")).first);
+ __instance.RefreshPaintButtons();
+ }
+ }
+ }
+ }
+}