using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using FishNet.Object; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("0.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 StickyItems { [BepInPlugin("welffi.howtofish.stickyitems", "StickyItems", "1.0.17")] public class Plugin : BaseUnityPlugin { public const string Guid = "welffi.howtofish.stickyitems"; public const string Name = "StickyItems"; public const string Version = "1.0.17"; internal static ManualLogSource Log; private void Awake() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; new Harmony("welffi.howtofish.stickyitems").PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"StickyItems 1.0.17 loaded - dropped items and fish will persist across saves."); } } [Serializable] internal class SavedGroundItemFile { public List Items = new List(); public List Positions = new List(); public List Rotations = new List(); } internal static class GroundItemSave { private static readonly MethodInfo ItemToSavedItemMethod = AccessTools.Method(typeof(SaveManager), "ItemToSavedItem", (Type[])null, (Type[])null); private static readonly FieldInfo ItemsSpawnedField = AccessTools.Field(typeof(ItemSpawner), "_itemsSpawned"); private static bool _capturedForStop; private static bool _isHostingSession; private static string _cachedSaveName; private static bool _hasRestoredThisSession; private static readonly Dictionary _lastGoodStates = new Dictionary(); internal static string GetFilePath(string saveName) { return Path.Combine(Application.persistentDataPath, "Saves", saveName + "_stickyitems.json"); } internal static bool IsFallenItem(Item item, out string reason, bool skipSyncVarChecks = false) { if (!Object.op_Implicit((Object)(object)item)) { reason = "null/destroyed"; return false; } if (item.IsDestroying) { reason = "IsDestroying"; return false; } if (Object.op_Implicit((Object)(object)item.SyncedHolder)) { reason = "SyncedHolder=" + (object)item.SyncedHolder; return false; } if (Object.op_Implicit((Object)(object)item.Holder)) { reason = "Holder=" + (object)item.Holder; return false; } if (Object.op_Implicit((Object)(object)item.BirdHolder)) { reason = "BirdHolder"; return false; } if (Object.op_Implicit((Object)(object)item.DeadPlayer)) { reason = "DeadPlayer"; return false; } if (item.IsInInventory) { reason = "IsInInventory"; return false; } if (!skipSyncVarChecks && Object.op_Implicit((Object)(object)item.Creature) && !item.Creature.IsDead) { reason = "Creature alive"; return false; } reason = null; return true; } internal static SavedItem CaptureItem(Item item) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown return (SavedItem)ItemToSavedItemMethod.Invoke(null, new object[2] { (byte)0, item }); } internal static HashSet GetSpawnerManagedItems() { HashSet hashSet = new HashSet(); ItemSpawner[] array = Object.FindObjectsByType((FindObjectsInactive)0); foreach (ItemSpawner obj in array) { if (!(ItemsSpawnedField.GetValue(obj) is List list)) { continue; } foreach (Item item in list) { if (Object.op_Implicit((Object)(object)item)) { hashSet.Add(item); } } } return hashSet; } internal static void ResetStopFlag() { _capturedForStop = false; } internal static bool ConsumeStopFlag() { bool capturedForStop = _capturedForStop; _capturedForStop = false; return capturedForStop; } internal static void CaptureBeforeStop() { _capturedForStop = true; SaveNow("OnStopClient-prefix", trustCachedHostFlag: true); } internal static void ClearHostingSession() { _isHostingSession = false; _cachedSaveName = null; _lastGoodStates.Clear(); } internal static void ResetRestoreFlag() { _hasRestoredThisSession = false; } internal static void TryRestore(string source) { //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) if (_hasRestoredThisSession) { return; } if (SaveManager.CurServerSave == null) { Plugin.Log.LogInfo((object)("StickyItems [" + source + "]: restore skipped, CurServerSave is null.")); return; } if (!Object.op_Implicit((Object)(object)Server.Instance) || !((NetworkBehaviour)Server.Instance).IsServerInitialized) { Plugin.Log.LogInfo((object)$"StickyItems [{source}]: restore skipped, Server.Instance={Object.op_Implicit((Object)(object)Server.Instance)}, IsServerInitialized={Object.op_Implicit((Object)(object)Server.Instance) && ((NetworkBehaviour)Server.Instance).IsServerInitialized}."); return; } if (!Object.op_Implicit((Object)(object)ItemManager.Instance)) { Plugin.Log.LogInfo((object)("StickyItems [" + source + "]: restore skipped, ItemManager.Instance is null.")); return; } _hasRestoredThisSession = true; MarkHostingSession(); string filePath = GetFilePath(SaveManager.CurServerSave.Name); if (!File.Exists(filePath)) { Plugin.Log.LogInfo((object)("StickyItems [" + source + "]: no save file at '" + filePath + "'.")); return; } try { string text = File.ReadAllText(filePath); Plugin.Log.LogInfo((object)$"StickyItems [{source}]: read {text.Length} chars from '{filePath}': {text}"); SavedGroundItemFile savedGroundItemFile = JsonUtility.FromJson(text); if (savedGroundItemFile?.Items == null) { return; } int num = 0; for (int i = 0; i < savedGroundItemFile.Items.Count; i++) { SavedItem val = savedGroundItemFile.Items[i]; if (val == null || !val.Exists) { continue; } Item val2 = GameInfo.IDToItem(val.ItemID); if (!Object.op_Implicit((Object)(object)val2)) { continue; } Item val3 = ItemManager.Instance.SpawnNewItem(val2, savedGroundItemFile.Positions[i], savedGroundItemFile.Rotations[i]); if (Object.op_Implicit((Object)(object)val3)) { if (Object.op_Implicit((Object)(object)val3.Creature)) { val3.Creature.ServerKillOnSpawn(); } val3.LoadFromSave(val); val3.ServerSetSkin(val.SkinIndex); num++; Plugin.Log.LogInfo((object)$"StickyItems [{source}]: restored item '{((Object)val3).name}' (ID={val3.ID}) saved(Weight={val.Weight}, KillScoreMultiplier={val.KillScoreMultiplier}, Cookness={val.Cookness}, BettingMultiplier={val.BettingMultiplier}) now(Weight={val3.RandomizedWeight}, KillScoreMultiplier={val3.KillScoreMultiplier}, Cookness={val3.Cookness}, BettingMultiplier={val3.BettingMultiplier})."); if (Object.op_Implicit((Object)(object)val3.Weapon)) { Plugin.Log.LogInfo((object)$"StickyItems [{source}]: weapon '{((Object)val3).name}' saved(Sight={val.Sight}, BarrelAttachment={val.BarrelAttachment}, AmmoType={val.AmmoType}, ExtendedMag={val.ExtendedMag}, LaserSight={val.LaserSight}) now(Sight={val3.Weapon.Attachments.Sight}, BarrelAttachment={val3.Weapon.Attachments.BarrelAttachment}, AmmoType={val3.Weapon.Attachments.AmmoType}, ExtendedMag={val3.Weapon.Attachments.ExtendedMag}, LaserSight={val3.Weapon.Attachments.LaserSight})."); } } } Plugin.Log.LogInfo((object)$"StickyItems [{source}]: restored {num} of {savedGroundItemFile.Items.Count} saved items from '{filePath}'."); } catch (Exception arg) { Plugin.Log.LogError((object)$"StickyItems [{source}]: TryRestore threw - {arg}"); } } internal static void MarkHostingSession() { _isHostingSession = true; if (SaveManager.CurServerSave != null) { _cachedSaveName = SaveManager.CurServerSave.Name; } } internal static void SaveNow(string source, bool trustCachedHostFlag = false) { //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) bool flag = (trustCachedHostFlag ? _isHostingSession : (Object.op_Implicit((Object)(object)Server.Instance) && ((NetworkBehaviour)Server.Instance).IsServerInitialized)); string text = (trustCachedHostFlag ? _cachedSaveName : SaveManager.CurServerSave?.Name); if (text == null || !flag) { Plugin.Log.LogInfo((object)string.Format("StickyItems [{0}]: skipped (saveName={1}, isHost={2}, trustCachedHostFlag={3}).", source, text ?? "null", flag, trustCachedHostFlag)); return; } if (!trustCachedHostFlag) { _isHostingSession = true; _cachedSaveName = text; } bool flag2 = trustCachedHostFlag; try { SavedGroundItemFile savedGroundItemFile = new SavedGroundItemFile(); HashSet hashSet = new HashSet(); HashSet spawnerManagedItems = GetSpawnerManagedItems(); int num = 0; foreach (Item value2 in ItemManager.Items.Values) { if ((Object)(object)value2 != (Object)null && !hashSet.Add(value2)) { continue; } num++; if ((Object)(object)value2 != (Object)null && spawnerManagedItems.Contains(value2)) { Plugin.Log.LogInfo((object)$"StickyItems [{source}]: rejected item '{((Object)value2).name}' (ID={value2.ID}) - SpawnerManaged"); continue; } SavedItem value = null; bool flag3 = flag2 && Object.op_Implicit((Object)(object)value2) && _lastGoodStates.TryGetValue(value2, out value); if (!IsFallenItem(value2, out var reason, flag3)) { Plugin.Log.LogInfo((object)("StickyItems [" + source + "]: rejected item '" + (Object.op_Implicit((Object)(object)value2) ? ((Object)value2).name : "null") + "' (ID=" + (Object.op_Implicit((Object)(object)value2) ? value2.ID.ToString() : "?") + ") - " + reason)); continue; } if (!flag3) { value = CaptureItem(value2); _lastGoodStates[value2] = value; } savedGroundItemFile.Items.Add(value); savedGroundItemFile.Positions.Add(((Component)value2).transform.position); savedGroundItemFile.Rotations.Add(((Component)value2).transform.rotation); Plugin.Log.LogInfo((object)string.Format("StickyItems [{0}]: captured item '{1}' (ID={2}) at {3} (state {4}).", source, ((Object)value2).name, value2.ID, ((Component)value2).transform.position, flag3 ? "from cache" : "live")); if (Object.op_Implicit((Object)(object)value2.Weapon)) { Plugin.Log.LogInfo((object)$"StickyItems [{source}]: weapon capture '{((Object)value2).name}' saving(Sight={value.Sight}, BarrelAttachment={value.BarrelAttachment}, AmmoType={value.AmmoType}, ExtendedMag={value.ExtendedMag}, LaserSight={value.LaserSight})."); } } string filePath = GetFilePath(text); Directory.CreateDirectory(Path.GetDirectoryName(filePath)); string text2 = JsonUtility.ToJson((object)savedGroundItemFile); File.WriteAllText(filePath, text2); Plugin.Log.LogInfo((object)$"StickyItems [{source}]: wrote {savedGroundItemFile.Items.Count} of {num} registered items to '{filePath}': {text2}"); } catch (Exception arg) { Plugin.Log.LogError((object)$"StickyItems [{source}]: SaveNow threw - {arg}"); } } } [HarmonyPatch(typeof(ItemManager), "OnStopClient")] internal static class ItemManager_OnStopClient_StickyItems { private static void Prefix() { GroundItemSave.CaptureBeforeStop(); } private static void Postfix() { GroundItemSave.ClearHostingSession(); GroundItemSave.ResetRestoreFlag(); } } [HarmonyPatch(typeof(SaveManager), "SaveServer")] internal static class SaveManager_SaveServer_StickyItems { private static void Postfix() { if (GroundItemSave.ConsumeStopFlag()) { Plugin.Log.LogInfo((object)"StickyItems [SaveServer-postfix]: skipped, already captured by OnStopClient-prefix."); } else { GroundItemSave.SaveNow("SaveServer-postfix"); } } } [HarmonyPatch(typeof(SaveManager), "OnServerLoaded")] internal static class SaveManager_OnServerLoaded_StickyItems { private static void Postfix() { GroundItemSave.ResetStopFlag(); GroundItemSave.ClearHostingSession(); GroundItemSave.TryRestore("OnServerLoaded"); } } [HarmonyPatch(typeof(ItemManager), "OnStartClient")] internal static class ItemManager_OnStartClient_StickyItems { private static void Postfix() { GroundItemSave.TryRestore("ItemManager.OnStartClient"); } } }