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");
}
}
}