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(); + } + } + } + } +}