Decompiled source of StickyItems v1.0.17

StickyItems.dll

Decompiled 7 hours ago
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<SavedItem> Items = new List<SavedItem>();

		public List<Vector3> Positions = new List<Vector3>();

		public List<Quaternion> Rotations = new List<Quaternion>();
	}
	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<Item, SavedItem> _lastGoodStates = new Dictionary<Item, SavedItem>();

		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<Item> GetSpawnerManagedItems()
		{
			HashSet<Item> hashSet = new HashSet<Item>();
			ItemSpawner[] array = Object.FindObjectsByType<ItemSpawner>((FindObjectsInactive)0);
			foreach (ItemSpawner obj in array)
			{
				if (!(ItemsSpawnedField.GetValue(obj) is List<Item> 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<SavedGroundItemFile>(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<Item> hashSet = new HashSet<Item>();
				HashSet<Item> 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");
		}
	}
}