using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using ImprovedVillagerInventoryCleanup.Behaviour;
using ImprovedVillagerInventoryCleanup.Diagnostics;
using ImprovedVillagerInventoryCleanup.UI;
using Microsoft.CodeAnalysis;
using SSSGame;
using SSSGame.AI;
using SSSGame.AI.FSM;
using SSSGame.Network;
using SSSGame.UI;
using SSSGame.Water;
using SandSailorStudio.Inventory;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("ImprovedVillagerInventoryCleanup")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+082438c78bb02628f8360910712f25eeb35a5c2b")]
[assembly: AssemblyProduct("ImprovedVillagerInventoryCleanup")]
[assembly: AssemblyTitle("ImprovedVillagerInventoryCleanup")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace ImprovedVillagerInventoryCleanup
{
[BepInPlugin("aska.improved.villager.inventory.cleanup", "Improved Villager Inventory Cleanup", "0.6.1")]
public sealed class Plugin : BasePlugin
{
public const string PluginGuid = "aska.improved.villager.inventory.cleanup";
public const string PluginName = "Improved Villager Inventory Cleanup";
public const string PluginVersion = "0.6.1";
private Harmony _harmony;
internal static ManualLogSource Log { get; private set; }
internal static ConfigEntry<float> TrackingWindowSeconds { get; private set; }
internal static ConfigEntry<float> SnapshotIntervalSeconds { get; private set; }
internal static ConfigEntry<float> CleanupTrackingWindowSeconds { get; private set; }
internal static ConfigEntry<bool> EnableImprovedCleanup { get; private set; }
internal static ConfigEntry<string> EligibleCategories { get; private set; }
internal static ConfigEntry<string> ExcludedCategories { get; private set; }
internal static ConfigEntry<bool> CleanEquippedTools { get; private set; }
internal static ConfigEntry<bool> KeepCleaningUntilDone { get; private set; }
internal static ConfigEntry<float> RecheckIntervalSeconds { get; private set; }
internal static ConfigEntry<bool> AddCleanupToStationsWithout { get; private set; }
internal static ConfigEntry<float> StorageSearchDistance { get; private set; }
internal static ConfigEntry<bool> AllowGroundDrops { get; private set; }
internal static ConfigEntry<bool> EyeOfOdinFallback { get; private set; }
internal static ConfigEntry<float> EyeOfOdinStandOffDistance { get; private set; }
internal static ConfigEntry<bool> ForceEyeOfOdinDrops { get; private set; }
internal static ConfigEntry<bool> ShowCleanupButton { get; private set; }
internal static ConfigEntry<float> CleanupButtonX { get; private set; }
internal static ConfigEntry<float> CleanupButtonY { get; private set; }
public override void Load()
{
//IL_02ca: Unknown result type (might be due to invalid IL or missing references)
//IL_02d1: Expected O, but got Unknown
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Expected O, but got Unknown
//IL_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: Unknown result type (might be due to invalid IL or missing references)
//IL_0046: Expected O, but got Unknown
//IL_0046: Unknown result type (might be due to invalid IL or missing references)
//IL_0253: Unknown result type (might be due to invalid IL or missing references)
//IL_0259: Expected O, but got Unknown
//IL_0298: Unknown result type (might be due to invalid IL or missing references)
//IL_029e: Expected O, but got Unknown
Log = ((BasePlugin)this).Log;
BindConfig();
DiagnosticLog.Initialize();
bool flag = default(bool);
try
{
_harmony = new Harmony("aska.improved.villager.inventory.cleanup");
_harmony.PatchAll();
ClassInjector.RegisterTypeInIl2Cpp<DiagnosticBehaviour>();
GameObject val = new GameObject("ImprovedVillagerInventoryCleanup.Diagnostics");
Object.DontDestroyOnLoad((Object)val);
((Object)val).hideFlags = (HideFlags)61;
val.AddComponent<DiagnosticBehaviour>();
DiagnosticLog.Write("plugin_loaded", $"version={"0.6.1"} game_version={Application.version} improved_cleanup={EnableImprovedCleanup.Value} eligible={DiagnosticLog.Quote(EligibleCategories.Value)} excluded={DiagnosticLog.Quote(ExcludedCategories.Value)} clean_equipped_tools={CleanEquippedTools.Value} keep_cleaning={KeepCleaningUntilDone.Value} recheck_seconds={RecheckIntervalSeconds.Value:0.#} add_missing_quests={AddCleanupToStationsWithout.Value} storage_search_distance={StorageSearchDistance.Value:0.#} last_resort_drops={EyeOfOdinFallback.Value} force_last_resort={ForceEyeOfOdinDrops.Value} cleanup_button={ShowCleanupButton.Value}");
ManualLogSource log = Log;
BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(9, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>("Improved Villager Inventory Cleanup");
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>("0.6.1");
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" loaded.");
}
log.LogInfo(val2);
ManualLogSource log2 = Log;
val2 = new BepInExInfoLogInterpolatedStringHandler(19, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Diagnostic output: ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(DiagnosticLog.OutputPath);
}
log2.LogInfo(val2);
}
catch (Exception ex)
{
ManualLogSource log3 = Log;
BepInExErrorLogInterpolatedStringHandler val3 = new BepInExErrorLogInterpolatedStringHandler(17, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Failed to start: ");
((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<Exception>(ex);
}
log3.LogError(val3);
DiagnosticLog.WriteException("plugin_start_failed", ex);
}
}
private void BindConfig()
{
TrackingWindowSeconds = ((BasePlugin)this).Config.Bind<float>("Diagnostics", "TrackingWindowSeconds", 600f, "How long a villager remains under detailed observation after a workstation change.");
SnapshotIntervalSeconds = ((BasePlugin)this).Config.Bind<float>("Diagnostics", "SnapshotIntervalSeconds", 5f, "How often an observed villager's inventory and active quest are recorded.");
CleanupTrackingWindowSeconds = ((BasePlugin)this).Config.Bind<float>("Diagnostics", "CleanupTrackingWindowSeconds", 180f, "How long a villager is observed after starting a cleanup quest.");
EnableImprovedCleanup = ((BasePlugin)this).Config.Bind<bool>("Cleanup", "EnableImprovedCleanup", true, "Master switch. False restores vanilla cleanup exactly.");
EligibleCategories = ((BasePlugin)this).Config.Bind<string>("Cleanup", "EligibleItemCategories", "*", "Comma-separated item categories villagers may put away when their job does not need them. '*' means every category. A category covers everything under it: 'Tools' covers 'Tools/Axes'. Items the current job needs are always kept, whatever this says.");
ExcludedCategories = ((BasePlugin)this).Config.Bind<string>("Cleanup", "ExcludedItemCategories", "Resources/Food, Resources/Elements, Bags, Armor, Weapons, Tools/Torches", "Comma-separated categories never put away by this mod, even when eligible above. Defaults keep food, water and smilk, bags, clothing, weapons and torches, which villagers need for survival, dressing, defence or light rather than for their job.");
CleanEquippedTools = ((BasePlugin)this).Config.Bind<bool>("Cleanup", "CleanEquippedTools", true, "Also put away a tool the villager has equipped (in hand) when their current job does not need it. Never applies to warriors, whose tools can be weapons.");
KeepCleaningUntilDone = ((BasePlugin)this).Config.Bind<bool>("Cleanup", "KeepCleaningUntilDone", true, "Keep asking a villager to clean up until they carry nothing their job does not need. Vanilla only asks on a job or schedule change, and forgets the request if the run is interrupted.");
RecheckIntervalSeconds = ((BasePlugin)this).Config.Bind<float>("Cleanup", "RecheckIntervalSeconds", 20f, "How often every villager is checked for unneeded items. A villager whose cleanup could not remove anything is checked less and less often, up to 10 minutes, so nobody loops.");
AddCleanupToStationsWithout = ((BasePlugin)this).Config.Bind<bool>("Cleanup", "AddCleanupToStationsWithout", true, "Give workers of stations that have no cleanup quest of their own (such as the fire and air altars) the game's standard cleanup quest.");
StorageSearchDistance = ((BasePlugin)this).Config.Bind<float>("Cleanup", "StorageSearchDistance", 100000f, "How far (metres) a cleaning villager will look for a storage that accepts an item. The large default means any storage in the settlement. 0 or less keeps the game's own limit.");
AllowGroundDrops = ((BasePlugin)this).Config.Bind<bool>("Cleanup", "AllowGroundDrops", true, "Allow ground drops during cleanups the game started because of trash.");
ItemEligibility.Reload();
EligibleCategories.SettingChanged += OnCategoriesChanged;
ExcludedCategories.SettingChanged += OnCategoriesChanged;
EyeOfOdinFallback = ((BasePlugin)this).Config.Bind<bool>("LastResort", "DropAtEyeOfOdinWhenNoStorage", true, "When no storage anywhere will take an item, drop it on the ground in front of the Eye of Odin. Villagers who live at an outpost drop it in front of their outpost instead.");
EyeOfOdinStandOffDistance = ((BasePlugin)this).Config.Bind<float>("LastResort", "StandOffDistance", 4f, "How many metres in front of the Eye of Odin (or outpost) villagers stand to drop items. Use a negative value if they end up behind it.");
ForceEyeOfOdinDrops = ((BasePlugin)this).Config.Bind<bool>("Debug", "ForceLastResortDrops", false, "TEST ONLY. Skip storage entirely and send every cleaned-up item to the last-resort drop point.");
ShowCleanupButton = ((BasePlugin)this).Config.Bind<bool>("Debug", "ShowCleanupButton", false, "Show a 'Clean inventory now' button while a villager's menu is open.");
CleanupButtonX = ((BasePlugin)this).Config.Bind<float>("Debug", "CleanupButtonX", 0.5f, "Horizontal centre of the button, as a fraction of screen width (0 = left, 1 = right).");
CleanupButtonY = ((BasePlugin)this).Config.Bind<float>("Debug", "CleanupButtonY", 0.15f, "Top of the button, as a fraction of screen height (0 = top, 1 = bottom).");
}
private static void OnCategoriesChanged(object sender, EventArgs args)
{
ItemEligibility.Reload();
}
public override bool Unload()
{
Harmony harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
DiagnosticLog.Write("plugin_unloaded", "normal=true");
DiagnosticLog.Shutdown();
return true;
}
}
}
namespace ImprovedVillagerInventoryCleanup.UI
{
internal static class CleanupButton
{
private static VillagerMenu _openMenu;
private static string _message;
private static float _messageUntil;
internal static void OnMenuOpened(VillagerMenu menu)
{
_openMenu = menu;
}
internal static void OnMenuClosed(VillagerMenu menu)
{
if ((Object)(object)_openMenu != (Object)null && (Object)(object)menu != (Object)null && ((Il2CppObjectBase)_openMenu).Pointer == ((Il2CppObjectBase)menu).Pointer)
{
_openMenu = null;
}
}
internal static void Draw()
{
//IL_0090: Unknown result type (might be due to invalid IL or missing references)
//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
if (!Plugin.ShowCleanupButton.Value || (Object)(object)_openMenu == (Object)null)
{
return;
}
try
{
if ((Object)(object)_openMenu == (Object)null || !((Behaviour)_openMenu).isActiveAndEnabled)
{
return;
}
Villager villager = _openMenu.GetVillager();
if (!((Object)(object)villager == (Object)null))
{
float num = 260f;
float num2 = 40f;
float num3 = (float)Screen.width * Plugin.CleanupButtonX.Value - num / 2f;
float num4 = (float)Screen.height * Plugin.CleanupButtonY.Value;
if (GUI.Button(new Rect(num3, num4, num, num2), "Clean inventory now (" + ((Character)villager).GetName() + ")"))
{
_message = ManualCleanup.Request(villager);
_messageUntil = Time.realtimeSinceStartup + 4f;
}
if (_message != null && Time.realtimeSinceStartup < _messageUntil)
{
GUI.Label(new Rect(num3, num4 + num2 + 4f, num + 200f, 24f), _message);
}
}
}
catch (Exception exception)
{
_openMenu = null;
DiagnosticLog.WriteException("cleanup_button_failed", exception);
}
}
}
}
namespace ImprovedVillagerInventoryCleanup.Patches
{
internal sealed class AssignmentCallState
{
internal string PreviousWorkstation;
internal string InventoryBefore;
}
[HarmonyPatch(typeof(Villager), "AssignToWorkstation", new Type[] { typeof(IWorkstation) })]
internal static class AssignToWorkstationPatch
{
private static void Prefix(Villager __instance, IWorkstation __0, out AssignmentCallState __state)
{
__state = new AssignmentCallState
{
PreviousWorkstation = GameDescribe.Workstation((__instance != null) ? __instance.GetWorkstation() : null),
InventoryBefore = GameDescribe.Inventory(__instance)
};
DiagnosticTracker.Track(__instance, "assign_to_workstation_called");
DiagnosticLog.Write("assignment_requested", $"{GameDescribe.Villager(__instance)} previous_workstation={__state.PreviousWorkstation} requested_workstation={GameDescribe.Workstation(__0)} inventory_before={__state.InventoryBefore}");
}
private static void Postfix(Villager __instance, IWorkstation __0, bool __result, AssignmentCallState __state)
{
DiagnosticLog.Write("assignment_result", $"{GameDescribe.Villager(__instance)} success={__result} previous_workstation={__state?.PreviousWorkstation} requested_workstation={GameDescribe.Workstation(__0)} current_workstation={GameDescribe.Workstation((__instance != null) ? __instance.GetWorkstation() : null)} inventory_after={GameDescribe.Inventory(__instance)}");
}
}
[HarmonyPatch(typeof(Villager), "_OnWorkstationChanged")]
internal static class WorkstationChangedPatch
{
private static void Postfix(Villager __instance, IWorkstation __0, bool __1)
{
DiagnosticTracker.Track(__instance, "workstation_changed_event");
DiagnosticLog.Write("workstation_changed", $"{GameDescribe.Villager(__instance)} workstation={GameDescribe.Workstation(__0)} is_assignment={__1} inventory={GameDescribe.Inventory(__instance)}");
}
}
[HarmonyPatch(typeof(Villager), "ReleaseFromWorkstation", new Type[]
{
typeof(IWorkstation),
typeof(int)
})]
internal static class ReleaseFromWorkstationPatch
{
private static void Prefix(Villager __instance, IWorkstation __0, int __1)
{
DiagnosticTracker.Track(__instance, "release_from_workstation_called");
DiagnosticLog.Write("workstation_release_requested", $"{GameDescribe.Villager(__instance)} workstation={GameDescribe.Workstation(__0)} reason={__1} inventory={GameDescribe.Inventory(__instance)}");
}
private static void Postfix(Villager __instance, bool __result)
{
DiagnosticLog.Write("workstation_release_result", $"{GameDescribe.Villager(__instance)} success={__result} current_workstation={GameDescribe.Workstation((__instance != null) ? __instance.GetWorkstation() : null)} inventory={GameDescribe.Inventory(__instance)}");
}
}
internal static class DepositAnyOverride
{
internal static void Apply(CleanupInventoryData data)
{
if (data == null || !Plugin.EnableImprovedCleanup.Value || VoyageGuard.IsExempt(data.agent))
{
return;
}
try
{
if (!data.depositAny)
{
data.depositAny = true;
}
if (Plugin.AllowGroundDrops.Value && data.onlyAllowDepositingToStorage)
{
data.onlyAllowDepositingToStorage = false;
}
}
catch (Exception exception)
{
DiagnosticLog.WriteException("deposit_any_override_failed", exception);
}
}
}
internal sealed class CheckItemSnapshot
{
internal Item PreviousItem;
internal NicePriority PreviousPriority;
internal int PreviousNeededAtWorkCount;
internal int PreviousNeededForQuestCount;
}
[HarmonyPatch(typeof(CleanupInventoryData), "CheckItem", new Type[] { typeof(Item) })]
internal static class CleanupCheckItemBehaviourPatch
{
private static void Prefix(CleanupInventoryData __instance, Item __0, out CheckItemSnapshot __state)
{
//IL_0057: Unknown result type (might be due to invalid IL or missing references)
//IL_005c: Unknown result type (might be due to invalid IL or missing references)
__state = null;
CleanupScanContext.AdmissionPending = false;
CleanupScanContext.AdmissionUsed = false;
if (__instance == null || VoyageGuard.IsExempt(__instance.agent))
{
return;
}
DepositAnyOverride.Apply(__instance);
CleanupWatcher.Register(__instance);
if (!Plugin.EnableImprovedCleanup.Value || !ItemEligibility.IsEligible(__0))
{
return;
}
try
{
__state = new CheckItemSnapshot
{
PreviousItem = __instance.itemToDrop,
PreviousPriority = __instance.itemToDropPriority,
PreviousNeededAtWorkCount = __instance._neededAtWorkCount,
PreviousNeededForQuestCount = __instance._neededForQuestCount
};
CleanupScanContext.AdmissionPending = true;
}
catch (Exception exception)
{
DiagnosticLog.WriteException("tool_admission_prefix_failed", exception);
}
}
[HarmonyPriority(600)]
private static void Postfix(CleanupInventoryData __instance, Item __0, CheckItemSnapshot __state)
{
//IL_004b: Unknown result type (might be due to invalid IL or missing references)
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_0053: Unknown result type (might be due to invalid IL or missing references)
//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
//IL_0137: Unknown result type (might be due to invalid IL or missing references)
//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
bool admissionUsed = CleanupScanContext.AdmissionUsed;
CleanupScanContext.AdmissionPending = false;
CleanupScanContext.AdmissionUsed = false;
if (!admissionUsed || __state == null || __instance == null || __0 == null)
{
return;
}
try
{
Item itemToDrop = __instance.itemToDrop;
if (itemToDrop == null || !(((Il2CppObjectBase)itemToDrop).Pointer == ((Il2CppObjectBase)__0).Pointer))
{
return;
}
Villager agent = __instance.agent;
NicePriority itemToDropPriority = __instance.itemToDropPriority;
if (IsAcceptable(agent, __0, itemToDropPriority))
{
if (DiagnosticTracker.IsTracked(agent))
{
DiagnosticLog.Write("item_admission_selected", $"{GameDescribe.Villager(agent)} item={GameDescribe.Item(__0)} vanilla_priority={itemToDropPriority}");
}
return;
}
__instance.itemToDrop = __state.PreviousItem;
__instance.itemToDropPriority = __state.PreviousPriority;
__instance._neededAtWorkCount = __state.PreviousNeededAtWorkCount;
__instance._neededForQuestCount = __state.PreviousNeededForQuestCount;
if (DiagnosticTracker.IsTracked(agent))
{
DiagnosticLog.Write("item_admission_reverted", $"{GameDescribe.Villager(agent)} item={GameDescribe.Item(__0)} vanilla_priority={itemToDropPriority}");
}
}
catch (Exception exception)
{
DiagnosticLog.WriteException("item_admission_postfix_failed", exception);
}
}
private static bool IsAcceptable(Villager villager, Item item, NicePriority priority)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Invalid comparison between Unknown and I4
if ((int)priority == 0)
{
return true;
}
if ((int)priority != 6)
{
return false;
}
if (!Plugin.CleanEquippedTools.Value || !ItemEligibility.IsTool(item))
{
return false;
}
try
{
return (Object)(object)villager != (Object)null && !villager.IsWarrior;
}
catch
{
return false;
}
}
}
[HarmonyPatch(typeof(CleanupInventoryData), "_CheckContainerNeedingSpace")]
internal static class CleanupContainerNeedingSpacePatch
{
private static void Postfix(ref bool __result)
{
if (!(!CleanupScanContext.AdmissionPending | __result))
{
__result = true;
CleanupScanContext.AdmissionUsed = true;
}
}
}
[HarmonyPatch(typeof(CleanupInventoryData), "StorageToDropIntoPredicate")]
internal static class CleanupStoragePredicateBehaviourPatch
{
private static void Prefix(CleanupInventoryData __instance)
{
DepositAnyOverride.Apply(__instance);
}
}
[HarmonyPatch(typeof(CleanupInventoryData), "ResourceStoragePredicate")]
internal static class CleanupResourceStoragePredicateBehaviourPatch
{
private static void Prefix(CleanupInventoryData __instance)
{
DepositAnyOverride.Apply(__instance);
}
}
[HarmonyPatch(typeof(CleanupInventoryData), "CheckItem", new Type[] { typeof(Item) })]
internal static class CleanupCheckItemPatch
{
private static void Postfix(CleanupInventoryData __instance, Item __0, bool __result)
{
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
Villager villager = ((__instance != null) ? __instance.agent : null);
if (DiagnosticTracker.IsTracked(villager))
{
DiagnosticLog.Write("cleanup_fsm_check_item", $"{GameDescribe.Villager(villager)} accepted={__result} item={GameDescribe.Item(__0)} selected_priority={__instance.itemToDropPriority} only_storage={__instance.onlyAllowDepositingToStorage} deposit_any={__instance.depositAny} keep_any_fuel={__instance.keepAnyFuel} workstation_check={GameDescribe.Workstation(__instance.workstationToCheckWith)}");
}
}
}
[HarmonyPatch(typeof(CleanupInventoryData), "StorageToDropIntoPredicate")]
internal static class CleanupStoragePredicatePatch
{
private static void Postfix(CleanupInventoryData __instance, StorageInteraction __0, bool __result)
{
Villager villager = ((__instance != null) ? __instance.agent : null);
if (DiagnosticTracker.IsTracked(villager))
{
DiagnosticLog.Write("cleanup_storage_candidate", $"{GameDescribe.Villager(villager)} accepted={__result} storage={GameDescribe.Workstation(__0)} item_to_drop={GameDescribe.Item(__instance.itemToDrop)} only_storage={__instance.onlyAllowDepositingToStorage} deposit_any={__instance.depositAny} {GameDescribe.StorageDecision(__0, __instance.itemToDrop)}");
}
}
}
[HarmonyPatch(typeof(CleanupInventoryData), "ResourceStoragePredicate")]
internal static class CleanupResourceStoragePredicatePatch
{
private static void Postfix(CleanupInventoryData __instance, IResourceStorageSite __0, bool __result)
{
Villager villager = ((__instance != null) ? __instance.agent : null);
if (DiagnosticTracker.IsTracked(villager))
{
DiagnosticLog.Write("cleanup_resource_storage_candidate", $"{GameDescribe.Villager(villager)} accepted={__result} storage={GameDescribe.Workstation(__0)} item_to_drop={GameDescribe.Item(__instance.itemToDrop)}");
}
}
}
[HarmonyPatch(typeof(CleanupInventoryQuestData), "_IsTrash", new Type[] { typeof(Item) })]
internal static class CleanupIsTrashPatch
{
private static void Postfix(CleanupInventoryQuestData __instance, Item __0, bool __result)
{
Villager villager = ((__instance != null) ? ((WorkstationQuestData)__instance).GetVillager() : null);
if (DiagnosticTracker.IsTracked(villager))
{
DiagnosticLog.Write("cleanup_item_classified", $"{GameDescribe.Villager(villager)} is_trash={__result} item={GameDescribe.Item(__0)} cleanup_requested={__instance.CleanupRequested} trash_detected={__instance.TrashDetected} important={__instance.Important} working={((WorkstationQuestData)__instance).IsWorking}");
}
}
}
[HarmonyPatch(typeof(CleanupInventoryQuestData), "IsWhitelistedByStorage")]
internal static class CleanupStorageWhitelistPatch
{
private static void Postfix(CleanupInventoryQuestData __instance, IResourceStorageSite __0, bool __result)
{
Villager villager = ((__instance != null) ? ((WorkstationQuestData)__instance).GetVillager() : null);
if (DiagnosticTracker.IsTracked(villager))
{
DiagnosticLog.Write("cleanup_storage_whitelist", $"{GameDescribe.Villager(villager)} allowed={__result} storage={GameDescribe.Workstation(__0)}");
}
}
}
[HarmonyPatch(typeof(CleanupInventoryQuestData), "Start")]
internal static class CleanupQuestStartPatch
{
private static void Postfix(CleanupInventoryQuestData __instance)
{
Villager val = ((__instance != null) ? ((WorkstationQuestData)__instance).GetVillager() : null);
if (!((Object)(object)val == (Object)null))
{
DiagnosticTracker.Track(val, "cleanup_quest_started", Plugin.CleanupTrackingWindowSeconds.Value);
DiagnosticLog.Write("cleanup_quest_started", $"{GameDescribe.Villager(val)} cleanup_requested={__instance.CleanupRequested} trash_detected={__instance.TrashDetected} important={__instance.Important} working={((WorkstationQuestData)__instance).IsWorking} inventory={GameDescribe.Inventory(val)}");
}
}
}
[HarmonyPatch(typeof(CleanupInventoryQuestData), "Stop")]
internal static class CleanupQuestStopPatch
{
private static void Prefix(CleanupInventoryQuestData __instance)
{
Villager villager = ((__instance != null) ? ((WorkstationQuestData)__instance).GetVillager() : null);
if (DiagnosticTracker.IsTracked(villager))
{
DiagnosticLog.Write("cleanup_quest_stopped", $"{GameDescribe.Villager(villager)} cleanup_requested={__instance.CleanupRequested} trash_detected={__instance.TrashDetected} important={__instance.Important} working={((WorkstationQuestData)__instance).IsWorking} inventory={GameDescribe.Inventory(villager)}");
}
}
}
internal static class ForceEyeOfOdin
{
internal static bool AppliesTo(CleanupInventoryData data)
{
if (!Plugin.ForceEyeOfOdinDrops.Value || data == null)
{
return false;
}
try
{
Villager agent = data.agent;
return (Object)(object)agent != (Object)null && (Object)(object)agent.GetWorkstation() != (Object)null && !VoyageGuard.IsExempt(agent);
}
catch
{
return false;
}
}
}
[HarmonyPatch(typeof(CleanupInventoryData), "StorageToDropIntoPredicate")]
internal static class ForceStorageToDropIntoPatch
{
[HarmonyPriority(600)]
private static void Postfix(CleanupInventoryData __instance, ref bool __result)
{
if (__result && ForceEyeOfOdin.AppliesTo(__instance))
{
__result = false;
}
}
}
[HarmonyPatch(typeof(CleanupInventoryData), "ResourceStoragePredicate")]
internal static class ForceResourceStoragePatch
{
[HarmonyPriority(600)]
private static void Postfix(CleanupInventoryData __instance, ref bool __result)
{
if (__result && ForceEyeOfOdin.AppliesTo(__instance))
{
__result = false;
}
}
}
internal static class StorageSearchContext
{
internal static CleanupInventoryData Data;
internal static Workstation Site;
internal static int Frame = -1;
internal static bool Applies(CleanupInventoryData data)
{
if (data != null && Plugin.EnableImprovedCleanup.Value)
{
return !VoyageGuard.IsExempt(data.agent);
}
return false;
}
}
[HarmonyPatch(typeof(CleanupInventoryData), "StorageToDropIntoPredicate")]
internal static class WorkstationStorageRulePatch
{
[HarmonyPriority(600)]
private static void Postfix(CleanupInventoryData __instance, StorageInteraction __0, ref bool __result)
{
if (__result && StorageSearchContext.Applies(__instance))
{
Villager agent = __instance.agent;
if (!StorageRules.Allows(__0, __instance.itemToDrop, agent, (agent != null) ? agent.GetWorkstation() : null))
{
__result = false;
}
}
}
}
[HarmonyPatch(typeof(CleanupInventoryData), "ResourceStoragePredicate")]
internal static class StorageSearchMarkerPatch
{
private static void Prefix(CleanupInventoryData __instance, IResourceStorageSite __0)
{
StorageSearchContext.Data = __instance;
StorageSearchContext.Site = ((__0 != null) ? ((Il2CppObjectBase)__0).TryCast<Workstation>() : null);
StorageSearchContext.Frame = Time.frameCount;
}
}
[HarmonyPatch(typeof(Settlement), "_FindStorageInteractionToDepositPredicate")]
internal static class SettlementSlotRulePatch
{
private static bool Prefix(Settlement __instance, StorageInteraction __0, ref bool __result)
{
CleanupInventoryData data = StorageSearchContext.Data;
if (StorageSearchContext.Frame != Time.frameCount || !StorageSearchContext.Applies(data))
{
return true;
}
Item itemToDrop = data.itemToDrop;
ItemInfo itemToDeposit = __instance._itemToDeposit;
if ((Object)(object)((itemToDrop != null) ? itemToDrop.info : null) == (Object)null || (Object)(object)itemToDeposit == (Object)null || itemToDeposit.id != itemToDrop.info.id)
{
return true;
}
if (__instance._ignoreSurplusLimits)
{
return true;
}
if (StorageRules.Allows(__0, itemToDrop, data.agent, StorageSearchContext.Site))
{
return true;
}
__result = false;
return false;
}
}
[HarmonyPatch(typeof(VillagerMenu), "OnActivate")]
internal static class VillagerMenuActivatePatch
{
private static void Postfix(VillagerMenu __instance)
{
CleanupButton.OnMenuOpened(__instance);
}
}
[HarmonyPatch(typeof(VillagerMenu), "OnDeactivate")]
internal static class VillagerMenuDeactivatePatch
{
private static void Postfix(VillagerMenu __instance)
{
CleanupButton.OnMenuClosed(__instance);
}
}
[HarmonyPatch(typeof(VillagerMenu), "OnClosed")]
internal static class VillagerMenuClosedPatch
{
private static void Postfix(VillagerMenu __instance)
{
CleanupButton.OnMenuClosed(__instance);
}
}
}
namespace ImprovedVillagerInventoryCleanup.Diagnostics
{
public sealed class DiagnosticBehaviour : MonoBehaviour
{
public DiagnosticBehaviour(IntPtr pointer)
: base(pointer)
{
}
public void Update()
{
DiagnosticTracker.SnapshotAll();
CleanupWatcher.Tick();
CleanupScheduler.Tick();
}
public void OnGUI()
{
CleanupButton.Draw();
}
}
internal static class DiagnosticLog
{
private static readonly object Sync = new object();
private static StreamWriter _writer;
internal static string OutputPath { get; private set; }
internal static void Initialize()
{
lock (Sync)
{
string text = Path.Combine(Paths.ConfigPath, "ImprovedVillagerInventoryCleanup");
Directory.CreateDirectory(text);
OutputPath = Path.Combine(text, "diagnostic-latest.log");
_writer?.Dispose();
_writer = new StreamWriter(OutputPath, append: false)
{
AutoFlush = true
};
WriteUnsafe("session_started", $"utc={DateTime.UtcNow:O}");
}
}
internal static void Write(string eventName, string details)
{
//IL_0031: Unknown result type (might be due to invalid IL or missing references)
//IL_0037: Expected O, but got Unknown
try
{
lock (Sync)
{
WriteUnsafe(eventName, details);
}
ManualLogSource log = Plugin.Log;
if (log != null)
{
bool flag;
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(17, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("IVIC_DIAG event=");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(eventName);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(details);
}
log.LogInfo(val);
}
}
catch
{
}
}
internal static void WriteException(string eventName, Exception exception)
{
Write(eventName, "exception=" + Quote(exception.ToString()));
}
internal static string Quote(string value)
{
if (value == null)
{
return "<null>";
}
return "\"" + value.Replace("\\", "\\\\").Replace("\r", "\\r").Replace("\n", "\\n")
.Replace("\"", "\\\"") + "\"";
}
internal static void Shutdown()
{
lock (Sync)
{
_writer?.Flush();
_writer?.Dispose();
_writer = null;
}
}
private static void WriteUnsafe(string eventName, string details)
{
_writer?.WriteLine($"utc={DateTime.UtcNow:O} realtime={Time.realtimeSinceStartup:0.000} event={eventName} {details}");
}
}
internal static class DiagnosticTracker
{
private sealed class Observation
{
internal Villager Villager;
internal float ExpiresAt;
internal float NextSnapshotAt;
}
private static readonly Dictionary<int, Observation> Observations = new Dictionary<int, Observation>();
private static int KeyFor(Villager villager)
{
try
{
return villager.PersistentUniqueID;
}
catch
{
return 0;
}
}
internal static void Track(Villager villager, string reason, float windowSeconds = -1f)
{
if ((Object)(object)villager == (Object)null)
{
return;
}
try
{
float realtimeSinceStartup = Time.realtimeSinceStartup;
int num = KeyFor(villager);
if (num == 0)
{
return;
}
float val = ((windowSeconds < 0f) ? Plugin.TrackingWindowSeconds.Value : windowSeconds);
float num2 = realtimeSinceStartup + Math.Max(10f, val);
if (Observations.TryGetValue(num, out var value))
{
if (num2 > value.ExpiresAt)
{
value.ExpiresAt = num2;
}
value.Villager = villager;
return;
}
Observations[num] = new Observation
{
Villager = villager,
ExpiresAt = num2,
NextSnapshotAt = realtimeSinceStartup
};
DiagnosticLog.Write("tracking_started", $"{GameDescribe.Villager(villager)} reason={reason} window={Math.Max(10f, val):0.###} inventory={GameDescribe.Inventory(villager)}");
}
catch (Exception exception)
{
DiagnosticLog.WriteException("tracking_start_failed", exception);
}
}
internal static bool IsTracked(Villager villager)
{
if ((Object)(object)villager == (Object)null)
{
return false;
}
try
{
int num = KeyFor(villager);
return num != 0 && Observations.ContainsKey(num);
}
catch
{
return false;
}
}
internal static bool TryGetVillager(QuestRunner runner, out Villager villager)
{
villager = null;
if ((Object)(object)runner == (Object)null)
{
return false;
}
foreach (Observation value in Observations.Values)
{
try
{
Villager villager2 = value.Villager;
IntPtr? obj;
if (villager2 == null)
{
obj = null;
}
else
{
QuestRunner questRunner = villager2.GetQuestRunner();
obj = ((questRunner != null) ? new IntPtr?(((Il2CppObjectBase)questRunner).Pointer) : ((IntPtr?)null));
}
if (obj == ((Il2CppObjectBase)runner).Pointer)
{
villager = value.Villager;
return true;
}
}
catch
{
}
}
return false;
}
internal static void SnapshotAll()
{
float realtimeSinceStartup = Time.realtimeSinceStartup;
List<int> list = new List<int>();
foreach (KeyValuePair<int, Observation> observation in Observations)
{
Observation value = observation.Value;
if ((Object)(object)value.Villager == (Object)null || realtimeSinceStartup >= value.ExpiresAt)
{
list.Add(observation.Key);
}
else if (!(realtimeSinceStartup < value.NextSnapshotAt))
{
value.NextSnapshotAt = realtimeSinceStartup + Math.Max(1f, Plugin.SnapshotIntervalSeconds.Value);
try
{
DiagnosticLog.Write("periodic_snapshot", $"{GameDescribe.Villager(value.Villager)} workstation={GameDescribe.Workstation(value.Villager.GetWorkstation())} active_quest={GameDescribe.Quest(value.Villager.GetQuestRunner())} inventory={GameDescribe.Inventory(value.Villager)}");
}
catch (Exception exception)
{
DiagnosticLog.WriteException("periodic_snapshot_failed", exception);
}
}
}
foreach (int item in list)
{
Observations.Remove(item);
}
}
}
internal static class GameDescribe
{
internal static string Villager(Villager villager)
{
if ((Object)(object)villager == (Object)null)
{
return "<null>";
}
try
{
return $"name={DiagnosticLog.Quote(((Character)villager).GetName())} instance={((Object)villager).GetInstanceID()} persistent={villager.PersistentUniqueID}";
}
catch (Exception ex)
{
return $"instance={SafeInstanceId(villager)} describe_error={DiagnosticLog.Quote(ex.Message)}";
}
}
internal static string Workstation(object workstation)
{
if (workstation == null)
{
return "<none>";
}
try
{
return "type=" + workstation.GetType().FullName + " value=" + DiagnosticLog.Quote(workstation.ToString());
}
catch
{
return "<unreadable>";
}
}
internal static string Item(Item item)
{
if (item == null)
{
return "<null>";
}
try
{
ItemInfo info = item.info;
if (!((Object)(object)info == (Object)null))
{
return $"name={DiagnosticLog.Quote(info.Name)} id={info.id} count={item.count} category={DiagnosticLog.Quote(CategoryPath(info))} tier={SafeTier(info)}";
}
return $"name=<null> id=<null> count={item.count}";
}
catch (Exception ex)
{
return "describe_error=" + DiagnosticLog.Quote(ex.Message);
}
}
internal static string CategoryPath(ItemInfo info)
{
try
{
ItemCategoryInfo val = info.category;
if ((Object)(object)val == (Object)null)
{
return "<none>";
}
List<string> list = new List<string>(8);
int num = 0;
while ((Object)(object)val != (Object)null && num < 8)
{
list.Add(val.Name ?? "<unnamed>");
val = val.parent;
num++;
}
list.Reverse();
return string.Join("/", list);
}
catch
{
return "<unreadable>";
}
}
private static string SafeTier(ItemInfo info)
{
try
{
return $"{info.tier}(shown={info.showTier})";
}
catch
{
return "<unreadable>";
}
}
internal static string Inventory(Villager villager)
{
if ((Object)(object)villager == (Object)null)
{
return "<null>";
}
try
{
ItemCollection inventory = villager.GetInventory();
List<Item> val = ((inventory != null) ? inventory.GetAllItems() : null);
if (val == null || val.Count == 0)
{
return "[]";
}
List<string> list = new List<string>(val.Count);
for (int i = 0; i < val.Count; i++)
{
list.Add("{" + Item(val[i]) + "}");
}
return "[" + string.Join(",", list) + "]";
}
catch (Exception ex)
{
return "<inventory_error:" + DiagnosticLog.Quote(ex.Message) + ">";
}
}
internal static string StorageDecision(StorageInteraction storage, Item item)
{
if ((Object)(object)storage == (Object)null)
{
return "storage=<null>";
}
try
{
ItemInfo val = ((item != null) ? item.info : null);
string value = (((Object)(object)val == (Object)null) ? "<no_item_info>" : storage.Check(val).ToString());
ItemContainer container = storage.Container;
return $"storage_check={value} can_store_type={InvokeMetric(container, "CanStoreItemType", val)} has_space={InvokeMetric(container, "HasSpace", val)} remaining_capacity={InvokeMetric(container, "GetRemainingCapacity", val)} existing_count={InvokeMetric(container, "GetItemCount", val)}";
}
catch (Exception ex)
{
return "storage_detail_error=" + DiagnosticLog.Quote(ex.Message);
}
}
internal static string Quest(QuestRunner runner)
{
if ((Object)(object)runner == (Object)null)
{
return "<none>";
}
try
{
Quest activeQuest = runner.GetActiveQuest();
return (activeQuest == (Quest)null) ? "<none>" : ("type=" + ((object)activeQuest).GetType().FullName + " value=" + DiagnosticLog.Quote(((Object)activeQuest).ToString()));
}
catch (Exception ex)
{
return "<quest_error:" + DiagnosticLog.Quote(ex.Message) + ">";
}
}
private static int SafeInstanceId(Villager villager)
{
try
{
return ((Object)villager).GetInstanceID();
}
catch
{
return 0;
}
}
private static string InvokeMetric(object target, string methodName, object itemInfo)
{
if (target == null)
{
return "<no_container>";
}
try
{
MethodInfo[] methods = target.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public);
foreach (MethodInfo methodInfo in methods)
{
if (methodInfo.Name != methodName)
{
continue;
}
ParameterInfo[] parameters = methodInfo.GetParameters();
object obj;
if (parameters.Length == 0)
{
obj = methodInfo.Invoke(target, null);
}
else
{
if (parameters.Length != 1 || itemInfo == null || !parameters[0].ParameterType.IsInstanceOfType(itemInfo))
{
continue;
}
obj = methodInfo.Invoke(target, new object[1] { itemInfo });
}
return obj?.ToString() ?? "<null>";
}
return "<signature_unavailable>";
}
catch (Exception ex)
{
return "<error:" + ex.GetBaseException().Message + ">";
}
}
}
}
namespace ImprovedVillagerInventoryCleanup.Behaviour
{
internal static class CleanupScanContext
{
internal static bool AdmissionPending;
internal static bool AdmissionUsed;
}
internal static class CleanupScheduler
{
private sealed class Backoff
{
internal float NextAllowedAt;
internal float DelaySeconds;
internal int CountAtLastRequest = -1;
}
private sealed class Injected
{
internal CleanupInventoryQuest Quest;
internal IntPtr Workstation;
}
private const float MaxBackoffSeconds = 600f;
private const int VillagersPerFrame = 3;
private static readonly Queue<Villager> Pending = new Queue<Villager>();
private static float _nextSweepAt;
private static readonly Dictionary<IntPtr, Backoff> BackoffByVillager = new Dictionary<IntPtr, Backoff>();
private static readonly Dictionary<IntPtr, Injected> InjectedByVillager = new Dictionary<IntPtr, Injected>();
private static readonly Dictionary<IntPtr, CleanupInventoryQuest> InjectedQuestByStation = new Dictionary<IntPtr, CleanupInventoryQuest>();
private static vFSMBehaviour _cleanupBehaviour;
private static readonly HashSet<IntPtr> DistanceApplied = new HashSet<IntPtr>();
internal static void Tick()
{
if (!Plugin.EnableImprovedCleanup.Value)
{
return;
}
float realtimeSinceStartup = Time.realtimeSinceStartup;
if (Pending.Count == 0 && realtimeSinceStartup >= _nextSweepAt)
{
_nextSweepAt = realtimeSinceStartup + Mathf.Max(5f, Plugin.RecheckIntervalSeconds.Value);
StartSweep();
}
for (int i = 0; i < 3; i++)
{
if (Pending.Count <= 0)
{
break;
}
Villager val = Pending.Dequeue();
try
{
if ((Object)(object)val != (Object)null)
{
CheckVillager(val, realtimeSinceStartup);
}
}
catch (Exception exception)
{
DiagnosticLog.WriteException("scheduler_check_failed", exception);
}
}
}
private static void StartSweep()
{
try
{
ApplyStorageSearchDistance();
StorageRules.RebuildIndex();
foreach (Villager item in Object.FindObjectsOfType<Villager>())
{
Pending.Enqueue(item);
}
}
catch (Exception exception)
{
DiagnosticLog.WriteException("scheduler_sweep_failed", exception);
}
}
private static void CheckVillager(Villager villager, float now)
{
if (VoyageGuard.IsExempt(villager))
{
return;
}
Workstation workstation = villager.GetWorkstation();
QuestRunner questRunner = villager.GetQuestRunner();
if ((Object)(object)questRunner == (Object)null)
{
return;
}
if (Plugin.AddCleanupToStationsWithout.Value)
{
EnsureCleanupQuest(villager, workstation, questRunner);
}
if (Plugin.KeepCleaningUntilDone.Value && !((Object)(object)workstation == (Object)null) && !IsCleanupActiveOrRequested(questRunner))
{
List<string> list = FindUnneeded(villager, workstation);
IntPtr pointer = ((Il2CppObjectBase)villager).Pointer;
if (!BackoffByVillager.TryGetValue(pointer, out var value))
{
value = new Backoff
{
DelaySeconds = Plugin.RecheckIntervalSeconds.Value
};
BackoffByVillager[pointer] = value;
}
if (list.Count == 0)
{
value.CountAtLastRequest = -1;
value.DelaySeconds = Plugin.RecheckIntervalSeconds.Value;
}
else if (!(now < value.NextAllowedAt))
{
bool flag = value.CountAtLastRequest < 0 || list.Count < value.CountAtLastRequest;
value.DelaySeconds = (flag ? Plugin.RecheckIntervalSeconds.Value : Mathf.Min(value.DelaySeconds * 2f, 600f));
value.NextAllowedAt = now + value.DelaySeconds;
value.CountAtLastRequest = list.Count;
DiagnosticLog.Write("unneeded_items_found", $"{GameDescribe.Villager(villager)} count={list.Count} items={DiagnosticLog.Quote(string.Join(", ", list))} progress={flag} next_check_seconds={value.DelaySeconds:0}");
ManualCleanup.RequestCleanup(villager, important: false, "unneeded_items");
}
}
}
private static bool IsCleanupActiveOrRequested(QuestRunner runner)
{
Quest activeQuest = runner.GetActiveQuest();
if ((Quest)(object)((activeQuest != null) ? ((Il2CppObjectBase)activeQuest).TryCast<CleanupInventoryQuest>() : null) != (Quest)null)
{
return true;
}
Enumerator<Quest, QuestData> enumerator = runner._questDataTable.GetEnumerator();
while (enumerator.MoveNext())
{
QuestData value = enumerator.Current.Value;
CleanupInventoryQuestData val = ((value != null) ? ((Il2CppObjectBase)value).TryCast<CleanupInventoryQuestData>() : null);
if (val != null && val.CleanupRequested)
{
return true;
}
}
return false;
}
private static List<string> FindUnneeded(Villager villager, Workstation workstation)
{
List<string> list = new List<string>();
ItemCollection inventory = villager.GetInventory();
List<Item> val = ((inventory != null) ? inventory.GetAllItems() : null);
if (val == null)
{
return list;
}
for (int i = 0; i < val.Count; i++)
{
Item val2 = val[i];
ItemInfo val3 = ((val2 != null) ? val2.info : null);
if (!((Object)(object)val3 == (Object)null) && ItemEligibility.IsEligible(val2))
{
EquipmentItem val4 = ((Il2CppObjectBase)val2).TryCast<EquipmentItem>();
if ((val4 == null || !val4.IsEquipped() || (Plugin.CleanEquippedTools.Value && ItemEligibility.IsTool(val2) && !villager.IsWarrior)) && !workstation.IsItemNeededByVillager(val3, villager) && !workstation.IsFuelNeededByVillager(val3, villager))
{
list.Add(val3.Name);
}
}
}
return list;
}
private static void EnsureCleanupQuest(Villager villager, Workstation workstation, QuestRunner runner)
{
IntPtr pointer = ((Il2CppObjectBase)villager).Pointer;
InjectedByVillager.TryGetValue(pointer, out var value);
bool flag = false;
Enumerator<Quest, QuestData> enumerator = runner._questDataTable.GetEnumerator();
while (enumerator.MoveNext())
{
Quest key = enumerator.Current.Key;
CleanupInventoryQuest val = ((key != null) ? ((Il2CppObjectBase)key).TryCast<CleanupInventoryQuest>() : null);
if (!((Quest)(object)val == (Quest)null) && (value == null || !(((Il2CppObjectBase)val).Pointer == ((Il2CppObjectBase)value.Quest).Pointer)))
{
flag = true;
}
}
if (value != null && ((Object)(object)workstation == (Object)null || flag || value.Workstation != ((Il2CppObjectBase)workstation).Pointer))
{
runner.RemoveQuest((Quest)(object)value.Quest);
InjectedByVillager.Remove(pointer);
DiagnosticLog.Write("cleanup_quest_removed", $"{GameDescribe.Villager(villager)} has_own={flag}");
value = null;
}
if (!((Object)(object)workstation == (Object)null || flag) && value == null)
{
CleanupInventoryQuest orCreateInjectedQuest = GetOrCreateInjectedQuest(workstation);
if (!((Quest)(object)orCreateInjectedQuest == (Quest)null))
{
runner.AddQuest((Quest)(object)orCreateInjectedQuest);
InjectedByVillager[pointer] = new Injected
{
Quest = orCreateInjectedQuest,
Workstation = ((Il2CppObjectBase)workstation).Pointer
};
DiagnosticLog.Write("cleanup_quest_added", $"{GameDescribe.Villager(villager)} station={DiagnosticLog.Quote(((Object)workstation).name)} station_type={((Object)workstation).GetIl2CppType().FullName}");
}
}
}
private static CleanupInventoryQuest GetOrCreateInjectedQuest(Workstation workstation)
{
//IL_0034: Unknown result type (might be due to invalid IL or missing references)
//IL_003a: Expected O, but got Unknown
IntPtr pointer = ((Il2CppObjectBase)workstation).Pointer;
if (InjectedQuestByStation.TryGetValue(pointer, out var value) && (Quest)(object)value != (Quest)null)
{
return value;
}
vFSMBehaviour val = FindCleanupBehaviour();
if ((Object)(object)val == (Object)null)
{
return null;
}
CleanupInventoryQuest val2 = new CleanupInventoryQuest(workstation, val);
InjectedQuestByStation[pointer] = val2;
return val2;
}
private static vFSMBehaviour FindCleanupBehaviour()
{
if ((Object)(object)_cleanupBehaviour != (Object)null)
{
return _cleanupBehaviour;
}
foreach (CraftingStation item in Object.FindObjectsOfType<CraftingStation>())
{
vFSMBehaviour cleanupInventoryBehaviour = item.cleanupInventoryBehaviour;
if (!((Object)(object)cleanupInventoryBehaviour == (Object)null))
{
_cleanupBehaviour = cleanupInventoryBehaviour;
DiagnosticLog.Write("cleanup_behaviour_found", "source=" + DiagnosticLog.Quote(((Object)item).name) + " behaviour=" + DiagnosticLog.Quote(((Object)cleanupInventoryBehaviour).name));
break;
}
}
return _cleanupBehaviour;
}
private static void ApplyStorageSearchDistance()
{
float value = Plugin.StorageSearchDistance.Value;
if (value <= 0f)
{
return;
}
foreach (FSM_CleanupInventory item in Resources.FindObjectsOfTypeAll<FSM_CleanupInventory>())
{
if (!((Object)(object)item == (Object)null) && !Mathf.Approximately(item.maxDistanceToStorage, value))
{
if (DistanceApplied.Add(((Il2CppObjectBase)item).Pointer))
{
DiagnosticLog.Write("storage_search_distance_set", $"fsm={DiagnosticLog.Quote(((Object)item).name)} authored={item.maxDistanceToStorage:0.#} now={value:0.#}");
}
item.maxDistanceToStorage = value;
}
}
}
}
internal static class CleanupWatcher
{
private sealed class Watched
{
internal CleanupInventoryData Data;
internal string LastState = "";
internal float StateSince;
internal bool StallReported;
}
private const float StallSeconds = 30f;
private static readonly Dictionary<IntPtr, Watched> ByPointer = new Dictionary<IntPtr, Watched>();
internal static void Register(CleanupInventoryData data)
{
if (data != null)
{
IntPtr pointer = ((Il2CppObjectBase)data).Pointer;
if (!(pointer == IntPtr.Zero) && !ByPointer.ContainsKey(pointer))
{
ByPointer[pointer] = new Watched
{
Data = data
};
}
}
}
internal static void Tick()
{
if (ByPointer.Count == 0)
{
return;
}
List<IntPtr> list = null;
foreach (KeyValuePair<IntPtr, Watched> item in ByPointer)
{
try
{
Inspect(item.Value);
}
catch (Exception ex)
{
if (list == null)
{
list = new List<IntPtr>();
}
list.Add(item.Key);
DiagnosticLog.Write("cleanup_watch_dropped", "reason=" + DiagnosticLog.Quote(ex.Message));
}
}
if (list == null)
{
return;
}
foreach (IntPtr item2 in list)
{
ByPointer.Remove(item2);
}
}
private static void Inspect(Watched watched)
{
//IL_0317: Unknown result type (might be due to invalid IL or missing references)
//IL_023a: Unknown result type (might be due to invalid IL or missing references)
//IL_023f: Unknown result type (might be due to invalid IL or missing references)
CleanupInventoryData data = watched.Data;
Item itemToDrop = data.itemToDrop;
Interaction interaction = data.interaction;
Transform workstationTransform = data.workstationTransform;
Villager agent = data.agent;
string text = ((itemToDrop == null) ? "nothing" : (((Object)(object)interaction != (Object)null) ? "deposit" : ((!((Object)(object)workstationTransform != (Object)null)) ? "picked" : "ground_drop")));
bool value = false;
if (text == "ground_drop" && EyeOfOdinEnabled() && !VoyageGuard.IsExempt(agent))
{
value = TryRedirect(data, agent, workstationTransform);
}
workstationTransform = data.workstationTransform;
string text2 = $"{text}|{((itemToDrop != null) ? new IntPtr?(((Il2CppObjectBase)itemToDrop).Pointer) : ((IntPtr?)null))}|{((interaction != null) ? new IntPtr?(((Il2CppObjectBase)interaction).Pointer) : ((IntPtr?)null))}|{((workstationTransform != null) ? new IntPtr?(((Il2CppObjectBase)workstationTransform).Pointer) : ((IntPtr?)null))}";
float realtimeSinceStartup = Time.realtimeSinceStartup;
if (text2 == watched.LastState)
{
if (itemToDrop != null && !watched.StallReported && !(realtimeSinceStartup - watched.StateSince < 30f))
{
watched.StallReported = true;
DiagnosticLog.Write("cleanup_stalled", $"{GameDescribe.Villager(agent)} outcome={text} seconds={realtimeSinceStartup - watched.StateSince:0} item={GameDescribe.Item(itemToDrop)} storage={DescribeObject((Object)(object)interaction)} walk_target={DescribeObject((Object)(object)workstationTransform)} walk_target_position={(((Object)(object)workstationTransform == (Object)null) ? "<none>" : ((object)workstationTransform.position/*cast due to .constrained prefix*/).ToString())} villager_position={SafePosition(agent)}");
}
}
else
{
watched.LastState = text2;
watched.StateSince = realtimeSinceStartup;
watched.StallReported = false;
if (itemToDrop != null || DiagnosticTracker.IsTracked(agent))
{
DiagnosticLog.Write("cleanup_state", $"{GameDescribe.Villager(agent)} outcome={text} item={GameDescribe.Item(itemToDrop)} priority={data.itemToDropPriority} storage={DescribeObject((Object)(object)interaction)} walk_target={DescribeObject((Object)(object)workstationTransform)} redirected_to_drop_point={value}");
}
}
}
private static bool EyeOfOdinEnabled()
{
if (!Plugin.EyeOfOdinFallback.Value)
{
return Plugin.ForceEyeOfOdinDrops.Value;
}
return true;
}
private static bool TryRedirect(CleanupInventoryData data, Villager villager, Transform currentTarget)
{
Transform val = DropPoint.Get(villager);
if ((Object)(object)val == (Object)null)
{
DiagnosticLog.Write("drop_point_unavailable", GameDescribe.Villager(villager) + " item=" + GameDescribe.Item(data.itemToDrop));
return false;
}
if (((Il2CppObjectBase)currentTarget).Pointer == ((Il2CppObjectBase)val).Pointer)
{
return false;
}
data.workstationTransform = val;
DiagnosticLog.Write("drop_point_redirect", $"{GameDescribe.Villager(villager)} item={GameDescribe.Item(data.itemToDrop)} forced={Plugin.ForceEyeOfOdinDrops.Value} replaced_target={DiagnosticLog.Quote(((Object)currentTarget).name)}");
return true;
}
private static string SafePosition(Villager villager)
{
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
try
{
return ((Object)(object)villager == (Object)null) ? "<none>" : ((object)((Component)villager).transform.position/*cast due to .constrained prefix*/).ToString();
}
catch
{
return "<unreadable>";
}
}
private static string DescribeObject(Object value)
{
if (value == (Object)null)
{
return "<none>";
}
try
{
return DiagnosticLog.Quote(value.name);
}
catch
{
return "<unreadable>";
}
}
}
internal static class DropPoint
{
private static readonly Dictionary<IntPtr, GameObject> MarkerByStructure = new Dictionary<IntPtr, GameObject>();
private static readonly HashSet<IntPtr> Logged = new HashSet<IntPtr>();
internal static Transform Get(Villager villager)
{
Settlement val = ((villager != null) ? villager.GetSettlement() : null);
if ((Object)(object)val == (Object)null)
{
return null;
}
Structure val2 = FindHomeOutpost(villager, val);
if ((Object)(object)val2 != (Object)null)
{
return MarkerFor(val2, "outpost");
}
Structure settlementCore = val._settlementCore;
if (!((Object)(object)settlementCore == (Object)null))
{
return MarkerFor(settlementCore, "eye_of_odin");
}
return null;
}
private static Structure FindHomeOutpost(Villager villager, Settlement settlement)
{
try
{
int homeOutpostId = villager.GetHomeOutpostId();
if (homeOutpostId == Settlement.c_DefaultOutpostId)
{
return null;
}
List<OutpostStructure> outposts = settlement._outposts;
if (outposts == null)
{
return null;
}
for (int i = 0; i < outposts.Count; i++)
{
OutpostStructure val = outposts[i];
if ((Object)(object)val != (Object)null && ((Structure)val).OutpostId == homeOutpostId)
{
return (Structure)(object)val;
}
}
}
catch (Exception exception)
{
DiagnosticLog.WriteException("outpost_lookup_failed", exception);
}
return null;
}
private static Transform MarkerFor(Structure structure, string kind)
{
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
//IL_000e: Unknown result type (might be due to invalid IL or missing references)
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_0052: Unknown result type (might be due to invalid IL or missing references)
//IL_0058: Expected O, but got Unknown
//IL_0078: Unknown result type (might be due to invalid IL or missing references)
//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
//IL_0102: Unknown result type (might be due to invalid IL or missing references)
Transform transform = ((Component)structure).transform;
Vector3 val = transform.position + transform.forward * Plugin.EyeOfOdinStandOffDistance.Value;
IntPtr pointer = ((Il2CppObjectBase)structure).Pointer;
if (!MarkerByStructure.TryGetValue(pointer, out var value) || (Object)(object)value == (Object)null)
{
value = new GameObject("ImprovedVillagerInventoryCleanup.DropPoint." + kind);
Object.DontDestroyOnLoad((Object)(object)value);
((Object)value).hideFlags = (HideFlags)61;
MarkerByStructure[pointer] = value;
}
value.transform.position = val;
if (Logged.Add(pointer))
{
DiagnosticLog.Write("drop_point_resolved", $"kind={kind} structure={DiagnosticLog.Quote(((Object)structure).name)} structure_position={transform.position} stand_point={val}");
}
return value.transform;
}
}
internal static class ItemEligibility
{
private static readonly Dictionary<int, bool> CacheByItemId = new Dictionary<int, bool>();
private static bool _allEligible;
private static string[] _eligible = Array.Empty<string>();
private static string[] _excluded = Array.Empty<string>();
internal static void Reload()
{
_eligible = ParseList(Plugin.EligibleCategories.Value, out _allEligible);
_excluded = ParseList(Plugin.ExcludedCategories.Value, out var _);
CacheByItemId.Clear();
}
internal static bool IsEligible(Item item)
{
try
{
ItemInfo val = ((item != null) ? item.info : null);
if ((Object)(object)val == (Object)null)
{
return false;
}
if (CacheByItemId.TryGetValue(val.id, out var value))
{
return value;
}
string path = NormalisedPath(val);
bool flag = (_allEligible || MatchesAny(path, _eligible)) && !MatchesAny(path, _excluded);
CacheByItemId[val.id] = flag;
return flag;
}
catch
{
return false;
}
}
internal static bool IsTool(Item item)
{
try
{
ItemInfo val = ((item != null) ? item.info : null);
return (Object)(object)val != (Object)null && MatchesAny(NormalisedPath(val), new string[1] { "tools" });
}
catch
{
return false;
}
}
private static string NormalisedPath(ItemInfo info)
{
List<string> list = new List<string>(8);
ItemCategoryInfo val = info.category;
int num = 0;
while ((Object)(object)val != (Object)null && num < 8)
{
list.Add(NormaliseSegment(val.Name));
val = val.parent;
num++;
}
list.Reverse();
return string.Join("/", list);
}
private static string NormaliseSegment(string name)
{
if (string.IsNullOrEmpty(name))
{
return "";
}
string text = name;
if (text.StartsWith("category.", StringComparison.Ordinal) && text.EndsWith("_name", StringComparison.Ordinal))
{
text = text.Substring(9, text.Length - 9 - 5);
int num = text.IndexOf('_');
if (num >= 0)
{
text = text.Substring(num + 1);
}
}
StringBuilder stringBuilder = new StringBuilder(text.Length);
string text2 = text;
foreach (char c in text2)
{
if (!char.IsWhiteSpace(c))
{
stringBuilder.Append(char.ToLowerInvariant(c));
}
}
return stringBuilder.ToString();
}
private static string[] ParseList(string value, out bool containsWildcard)
{
containsWildcard = false;
List<string> list = new List<string>();
string[] array = (value ?? "").Split(',');
for (int i = 0; i < array.Length; i++)
{
string text = array[i].Trim().Trim('/');
if (text.Length == 0)
{
continue;
}
if (text == "*")
{
containsWildcard = true;
continue;
}
string[] array2 = text.Split('/');
for (int j = 0; j < array2.Length; j++)
{
array2[j] = NormaliseSegment(array2[j]);
}
list.Add(string.Join("/", array2));
}
return list.ToArray();
}
private static bool MatchesAny(string path, string[] entries)
{
foreach (string text in entries)
{
if (path == text)
{
return true;
}
if (path.StartsWith(text + "/", StringComparison.Ordinal))
{
return true;
}
}
return false;
}
}
internal static class ManualCleanup
{
internal static string Request(Villager villager)
{
if ((Object)(object)villager == (Object)null)
{
return "No villager selected.";
}
if (VoyageGuard.IsExempt(villager))
{
return ((Character)villager).GetName() + " is away on a voyage; left vanilla.";
}
DiagnosticTracker.Track(villager, "manual_cleanup_button", Plugin.CleanupTrackingWindowSeconds.Value);
if (RequestCleanup(villager, important: true, "button") <= 0)
{
return "This villager has no cleanup quest (no job?).";
}
return "Cleanup requested for " + ((Character)villager).GetName() + ".";
}
internal static int RequestCleanup(Villager villager, bool important, string reason)
{
//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
try
{
QuestRunner questRunner = villager.GetQuestRunner();
if ((Object)(object)questRunner == (Object)null)
{
return 0;
}
int num = 0;
Enumerator<Quest, QuestData> enumerator = questRunner._questDataTable.GetEnumerator();
while (enumerator.MoveNext())
{
KeyValuePair<Quest, QuestData> current = enumerator.Current;
QuestData value = current.Value;
CleanupInventoryQuestData val = ((value != null) ? ((Il2CppObjectBase)value).TryCast<CleanupInventoryQuestData>() : null);
if (val != null)
{
val.CleanupRequested = true;
if (important)
{
val.Important = true;
}
questRunner.ReevaluateQuest(current.Key);
num++;
DiagnosticLog.Write("cleanup_requested", $"{GameDescribe.Villager(villager)} reason={reason} important={important} quest_status={((QuestData)val).questStatus} quest_priority={current.Key.GetPriority((QuestData)(object)val):0.##} active_quest_after={GameDescribe.Quest(questRunner)}");
}
}
if (num == 0)
{
DiagnosticLog.Write("cleanup_request_failed", GameDescribe.Villager(villager) + " reason=" + reason + " why=no_cleanup_quest");
}
return num;
}
catch (Exception exception)
{
DiagnosticLog.WriteException("cleanup_request_failed", exception);
return 0;
}
}
}
internal static class StorageRules
{
private const int PriorityNone = 3;
private const float RebuildThrottleSeconds = 2f;
private static Dictionary<IntPtr, List<ResourceStorageTaskData>> _tasksByInteraction = new Dictionary<IntPtr, List<ResourceStorageTaskData>>();
private static readonly HashSet<IntPtr> KnownUntasked = new HashSet<IntPtr>();
private static float _lastRebuildAt = -100f;
private static readonly HashSet<string> LoggedRefusals = new HashSet<string>();
internal static bool Allows(StorageInteraction storage, Item item, Villager villager, Workstation owner)
{
if ((Object)(object)storage == (Object)null || (Object)(object)((item != null) ? item.info : null) == (Object)null)
{
return false;
}
try
{
ItemInfo info = item.info;
int num = Math.Max(1, item.count);
ItemContainer container = storage.Container;
if (container == null)
{
return Refuse(villager, storage, item, "no_container");
}
if (!container.HasSpace(info, num))
{
return Refuse(villager, storage, item, $"full remaining={container.GetRemainingCapacity(info)}");
}
List<ResourceStorageTaskData> list = TasksFor(storage);
if (list != null)
{
ResourceStorageTaskData val = FindTask(list, info);
if (val == null)
{
return Refuse(villager, storage, item, "no_task_for_item");
}
return CheckTask((WorkstationTaskData)(object)val, container, info, num, villager, storage, item);
}
if ((Object)(object)owner == (Object)null)
{
return true;
}
WorkstationTaskData val2 = FindStationTask(owner, info);
if (val2 != null)
{
return CheckTask(val2, container, info, num, villager, storage, item);
}
if ((Object)(object)villager != (Object)null && (owner.IsItemNeededByVillager(info, villager) || owner.IsFuelNeededByVillager(info, villager)))
{
return true;
}
return Refuse(villager, storage, item, "station_has_no_task_for_item station=" + DiagnosticLog.Quote(((Object)owner).name));
}
catch (Exception exception)
{
DiagnosticLog.WriteException("storage_rules_failed", exception);
return false;
}
}
private static bool CheckTask(WorkstationTaskData task, ItemContainer container, ItemInfo info, int count, Villager villager, StorageInteraction storage, Item item)
{
int priority = task.priority;
if (priority >= 3 || priority < 0)
{
return Refuse(villager, storage, item, $"task_priority_none raw_priority={priority}");
}
ItemInfoQuantity itemInfoQuantity = task.itemInfoQuantity;
int num = ((itemInfoQuantity != null) ? itemInfoQuantity.quantity : 0);
int itemCount = container.GetItemCount(info);
if (itemCount + count > num)
{
return Refuse(villager, storage, item, $"task_quantity existing={itemCount} adding={count} limit={num}");
}
return true;
}
private static WorkstationTaskData FindStationTask(Workstation owner, ItemInfo info)
{
List<WorkstationTaskData> workstationTaskDatas = owner.GetWorkstationTaskDatas();
if (workstationTaskDatas == null)
{
return null;
}
for (int i = 0; i < workstationTaskDatas.Count; i++)
{
WorkstationTaskData val = workstationTaskDatas[i];
object obj;
if (val == null)
{
obj = null;
}
else
{
ItemInfoQuantity itemInfoQuantity = val.itemInfoQuantity;
obj = ((itemInfoQuantity != null) ? itemInfoQuantity.itemInfo : null);
}
ItemInfo val2 = (ItemInfo)obj;
if ((Object)(object)val2 != (Object)null && val2.id == info.id)
{
return val;
}
}
return null;
}
internal static void RebuildIndex()
{
_lastRebuildAt = Time.realtimeSinceStartup;
Dictionary<IntPtr, List<ResourceStorageTaskData>> dictionary = new Dictionary<IntPtr, List<ResourceStorageTaskData>>();
try
{
foreach (ResourceStorage item in Object.FindObjectsOfType<ResourceStorage>())
{
List<WorkstationTaskData> val = ((item != null) ? ((Workstation)item)._taskDatas : null);
if (val == null)
{
continue;
}
for (int i = 0; i < val.Count; i++)
{
WorkstationTaskData obj = val[i];
ResourceStorageTaskData val2 = ((obj != null) ? ((Il2CppObjectBase)obj).TryCast<ResourceStorageTaskData>() : null);
object obj2;
if (val2 == null)
{
obj2 = null;
}
else
{
StorageSupply storageSupply = val2.storageSupply;
obj2 = ((storageSupply != null) ? storageSupply.interaction : null);
}
StorageInteraction val3 = (StorageInteraction)obj2;
if (!((Object)(object)val3 == (Object)null))
{
IntPtr pointer = ((Il2CppObjectBase)val3).Pointer;
if (!dictionary.TryGetValue(pointer, out var value))
{
value = (dictionary[pointer] = new List<ResourceStorageTaskData>());
}
value.Add(val2);
}
}
}
}
catch (Exception exception)
{
DiagnosticLog.WriteException("storage_task_index_failed", exception);
}
_tasksByInteraction = dictionary;
KnownUntasked.Clear();
}
private static List<ResourceStorageTaskData> TasksFor(StorageInteraction storage)
{
IntPtr pointer = ((Il2CppObjectBase)storage).Pointer;
if (_tasksByInteraction.TryGetValue(pointer, out var value))
{
return value;
}
if (KnownUntasked.Contains(pointer))
{
return null;
}
if (Time.realtimeSinceStartup - _lastRebuildAt >= 2f)
{
RebuildIndex();
if (_tasksByInteraction.TryGetValue(pointer, out value))
{
return value;
}
}
KnownUntasked.Add(pointer);
return null;
}
private static ResourceStorageTaskData FindTask(List<ResourceStorageTaskData> tasks, ItemInfo info)
{
ResourceStorageTaskData val = null;
foreach (ResourceStorageTaskData task in tasks)
{
object obj;
if (task == null)
{
obj = null;
}
else
{
ItemInfoQuantity itemInfoQuantity = ((WorkstationTaskData)task).itemInfoQuantity;
obj = ((itemInfoQuantity != null) ? itemInfoQuantity.itemInfo : null);
}
ItemInfo val2 = (ItemInfo)obj;
if ((Object)(object)val2 == (Object)null)
{
if (val == null)
{
val = task;
}
}
else if (val2.id == info.id)
{
return task;
}
}
return val;
}
private static bool Refuse(Villager villager, StorageInteraction storage, Item item, string reason)
{
DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(3, 4);
defaultInterpolatedStringHandler.AppendFormatted((villager != null) ? new IntPtr?(((Il2CppObjectBase)villager).Pointer) : ((IntPtr?)null));
defaultInterpolatedStringHandler.AppendLiteral("|");
defaultInterpolatedStringHandler.AppendFormatted(((Il2CppObjectBase)storage).Pointer);
defaultInterpolatedStringHandler.AppendLiteral("|");
ItemInfo info = item.info;
defaultInterpolatedStringHandler.AppendFormatted((info != null) ? new int?(info.id) : ((int?)null));
defaultInterpolatedStringHandler.AppendLiteral("|");
defaultInterpolatedStringHandler.AppendFormatted(reason);
string item2 = defaultInterpolatedStringHandler.ToStringAndClear();
if (LoggedRefusals.Add(item2))
{
DiagnosticLog.Write("storage_refused", $"{GameDescribe.Villager(villager)} storage={DiagnosticLog.Quote(((Object)storage).name)} structure={DiagnosticLog.Quote(StructureName(storage))} item={GameDescribe.Item(item)} reason={reason}");
}
return false;
}
private static string StructureName(StorageInteraction storage)
{
try
{
Structure componentInParent = ((Component)storage).GetComponentInParent<Structure>();
return ((componentInParent != null) ? ((Object)componentInParent).name : null) ?? "<none>";
}
catch
{
return "<unreadable>";
}
}
}
internal static class VoyageGuard
{
private sealed class Cached
{
internal bool Away;
internal float CheckedAt;
}
private const float CacheSeconds = 1f;
private static readonly Dictionary<IntPtr, Cached> ByVillager = new Dictionary<IntPtr, Cached>();
internal static bool IsExempt(Villager villager)
{
if ((Object)(object)villager == (Object)null)
{
return false;
}
IntPtr pointer = ((Il2CppObjectBase)villager).Pointer;
float realtimeSinceStartup = Time.realtimeSinceStartup;
if (ByVillager.TryGetValue(pointer, out var value) && realtimeSinceStartup - value.CheckedAt < 1f)
{
return value.Away;
}
string stageText;
bool flag = ReadAway(villager, out stageText);
if (value == null)
{
value = new Cached();
ByVillager[pointer] = value;
}
else if (value.Away != flag)
{
DiagnosticLog.Write("voyage_state_changed", $"{GameDescribe.Villager(villager)} away={flag} stage={stageText}");
}
value.Away = flag;
value.CheckedAt = realtimeSinceStartup;
return flag;
}
private unsafe static bool ReadAway(Villager villager, out string stageText)
{
//IL_0030: Unknown result type (might be due to invalid IL or missing references)
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_0045: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Invalid comparison between Unknown and I4
//IL_0049: Unknown result type (might be due to invalid IL or missing references)
//IL_004b: Invalid comparison between Unknown and I4
stageText = "none";
try
{
SailingShip sailingShip = villager.GetSailingShip();
if ((Object)(object)sailingShip == (Object)null)
{
return false;
}
ExpeditionNetworkState netState = sailingShip.netState;
if ((Object)(object)netState == (Object)null)
{
return false;
}
ExpeditionStage stage = netState.Stage;
stageText = ((object)(*(ExpeditionStage*)(&stage))/*cast due to .constrained prefix*/).ToString();
return (int)stage == 3 || (int)stage == 4;
}
catch
{
return false;
}
}
}
}