using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FishNet;
using FishNet.Broadcast;
using FishNet.Connection;
using FishNet.Object;
using FishNet.Serializing;
using FishNet.Transporting;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using SpongeMods.Core;
using SpongeMods.ModMenu;
using SpongeMods.UILib;
using TMPro;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp-firstpass")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: IgnoresAccessChecksTo("FishNet.Runtime")]
[assembly: AssemblyCompany("SpongeMods")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("World item persistence and save backups.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+a149c533da6d929b7267867242f5011f9c2152dc")]
[assembly: AssemblyProduct("SpongeMods.QOL")]
[assembly: AssemblyTitle("SpongeMods.QOL")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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 SpongeMods.QOL
{
internal readonly struct Snapshot
{
public readonly string Path;
public readonly DateTime TakenUtc;
public readonly long Bytes;
public string When => TakenUtc.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
public string Size => ((float)Bytes / 1024f).ToString("0.0", CultureInfo.InvariantCulture) + " KB";
public Snapshot(string path, DateTime takenUtc, long bytes)
{
Path = path;
TakenUtc = takenUtc;
Bytes = bytes;
}
}
internal static class Backups
{
private const string FolderName = "SpongeBackups";
private const string Extension = ".txt";
private const string Stamp = "yyyyMMdd-HHmmss";
public static string FolderFor(string saveName)
{
return Path.Combine(SideCar.Folder, "SpongeBackups", SideCar.Clean(saveName));
}
public static string LiveFileFor(string saveName)
{
return Path.Combine(SideCar.Folder, saveName + ".txt");
}
public static void Capture(string saveName)
{
if (string.IsNullOrEmpty(saveName))
{
return;
}
string text = LiveFileFor(saveName);
if (!File.Exists(text))
{
return;
}
try
{
string text2 = FolderFor(saveName);
Directory.CreateDirectory(text2);
string text3 = Path.Combine(text2, DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture) + ".txt");
if (!File.Exists(text3))
{
File.Copy(text, text3);
Prune(saveName);
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not write a backup: " + ex.Message));
}
}
public static List<string> Saves()
{
List<string> list = new List<string>();
string path = Path.Combine(SideCar.Folder, "SpongeBackups");
if (!Directory.Exists(path))
{
return list;
}
try
{
string[] directories = Directory.GetDirectories(path);
foreach (string path2 in directories)
{
list.Add(Path.GetFileName(path2));
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not list backups: " + ex.Message));
}
list.Sort(StringComparer.OrdinalIgnoreCase);
return list;
}
public static List<Snapshot> List(string saveName)
{
List<Snapshot> list = new List<Snapshot>();
if (string.IsNullOrEmpty(saveName))
{
return list;
}
string path = FolderFor(saveName);
if (!Directory.Exists(path))
{
return list;
}
try
{
string[] files = Directory.GetFiles(path, "*.txt");
foreach (string text in files)
{
if (DateTime.TryParseExact(Path.GetFileNameWithoutExtension(text), "yyyyMMdd-HHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var result))
{
list.Add(new Snapshot(text, result, new FileInfo(text).Length));
}
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not read the backup folder: " + ex.Message));
}
list.Sort((Snapshot left, Snapshot right) => right.TakenUtc.CompareTo(left.TakenUtc));
return list;
}
public static bool Restore(string saveName, Snapshot snapshot)
{
try
{
if (!File.Exists(snapshot.Path))
{
return false;
}
Capture(saveName);
File.Copy(snapshot.Path, LiveFileFor(saveName), overwrite: true);
Plugin.Log.LogInfo((object)("Restored '" + saveName + "' from " + snapshot.When + "."));
return true;
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Restore failed: " + ex.Message));
return false;
}
}
private static void Prune(string saveName)
{
int value = Plugin.BackupsKept.Value;
List<Snapshot> list = List(saveName);
for (int i = value; i < list.Count; i++)
{
try
{
File.Delete(list[i].Path);
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not prune a backup: " + ex.Message));
}
}
}
}
[HarmonyPatch(typeof(SaveManager), "SaveServer")]
internal static class BackupOnSavePatch
{
private static void Postfix(bool autoSave)
{
if (Plugin.BackupsEnabled.Value && (!autoSave || Plugin.BackupAutoSaves.Value))
{
Backups.Capture(SaveManager.CurServerSave?.Name);
}
}
}
internal sealed class BackupScreen
{
private const int Rows = 10;
private readonly Menu _menu;
private List<string> _saves = new List<string>();
private List<Snapshot> _shots = new List<Snapshot>();
private int _saveIndex;
private string _notice = string.Empty;
private float _noticeUntil;
public bool Open { get; private set; }
public BackupScreen(Palette palette)
{
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Expected O, but got Unknown
//IL_005a: Unknown result type (might be due to invalid IL or missing references)
_menu = new Menu("QOL.Backups", 720f, 10, palette, 25f, 17f);
_menu.PlaceAt(new Placement((Anchor)4, 0f, 0f));
}
public void Toggle()
{
Open = !Open;
_menu.Visible(Open);
if (Open)
{
Reload();
}
}
public void Close()
{
Open = false;
_menu.Visible(false);
}
private void Reload()
{
_saves = Backups.Saves();
_saveIndex = Mathf.Clamp(_saveIndex, 0, Mathf.Max(0, _saves.Count - 1));
LoadShots();
}
private void LoadShots()
{
_shots = ((_saves.Count > 0) ? Backups.List(_saves[_saveIndex]) : new List<Snapshot>());
if (_saves.Count == 0)
{
_menu.Selectable = false;
_menu.SetItems((IEnumerable<string>)new string[1] { "No backups yet. One is taken each time the game saves." });
return;
}
_menu.Selectable = true;
List<string> list = new List<string>(_shots.Count);
foreach (Snapshot shot in _shots)
{
list.Add(shot.When + " " + shot.Size);
}
_menu.SetItems((IEnumerable<string>)list);
}
public void Step()
{
if (Input.GetKeyDown((KeyCode)274))
{
_menu.Move(1);
}
else if (Input.GetKeyDown((KeyCode)273))
{
_menu.Move(-1);
}
else if (Input.GetKeyDown((KeyCode)275))
{
SelectSave(1);
}
else if (Input.GetKeyDown((KeyCode)276))
{
SelectSave(-1);
}
else if (Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271))
{
RestoreSelected();
}
else if (Input.GetKeyDown((KeyCode)27))
{
Close();
}
Draw();
}
private void SelectSave(int by)
{
if (_saves.Count != 0)
{
_saveIndex = (_saveIndex + by + _saves.Count) % _saves.Count;
_menu.Rewind();
LoadShots();
}
}
private void RestoreSelected()
{
int cursor = _menu.Cursor;
if (_saves.Count != 0 && cursor >= 0 && cursor < _shots.Count)
{
if ((Object)(object)Server.Instance != (Object)null && ((NetworkBehaviour)Server.Instance).IsServerInitialized)
{
Notify("Quit to the main menu first, or the running game will overwrite it.");
return;
}
string text = _saves[_saveIndex];
Notify(Backups.Restore(text, _shots[cursor]) ? ("Restored. Load '" + text + "' to play it.") : "Restore failed; see the log.");
LoadShots();
}
}
private void Notify(string text)
{
_notice = text;
_noticeUntil = Time.unscaledTime + 6f;
}
private void Draw()
{
if (_saves.Count == 0)
{
_menu.Title = "Save backups";
_menu.Header = string.Empty;
_menu.SetItems((IEnumerable<string>)new string[1] { "No backups yet. One is taken each time the game saves." });
_menu.Selectable = false;
_menu.Footer = "Esc close";
_menu.Draw();
}
else
{
_menu.Selectable = true;
string text = _saves[_saveIndex];
_menu.Title = ((_saves.Count > 1) ? $"Save backups — {text} ({_saveIndex + 1} of {_saves.Count})" : ("Save backups — " + text));
_menu.Header = " TAKEN SIZE";
_menu.Footer = ((Time.unscaledTime < _noticeUntil) ? _notice : "↑↓ choose backup ←→ choose save Enter restore Esc close");
_menu.Draw();
}
}
}
[BepInPlugin("SpongeMods.QOL", "Sponge's QOL", "1.0.0")]
[BepInDependency("SpongeMods.Core", "1.0.0")]
[BepInDependency("SpongeMods.UILib", "1.0.0")]
[BepInDependency("SpongeMods.ModMenu", "1.0.0")]
public sealed class Plugin : BaseUnityPlugin
{
public const string Id = "SpongeMods.QOL";
public const string Version = "1.0.0";
internal static ConfigEntry<bool> MoreWorldItems;
internal static ConfigEntry<int> WorldItemLimit;
internal static ConfigEntry<bool> KeepLiveCreatures;
internal static ConfigEntry<bool> BackupsEnabled;
internal static ConfigEntry<bool> BackupAutoSaves;
internal static ConfigEntry<int> BackupsKept;
internal static ConfigEntry<KeyboardShortcut> BackupKey;
internal static ConfigEntry<bool> StackItems;
internal static ConfigEntry<int> StackSize;
internal static ConfigEntry<bool> ShowCounts;
private BackupScreen _screen;
private readonly StackBadges _badges = new StackBadges();
internal static ManualLogSource Log { get; private set; }
private void Awake()
{
//IL_0056: Unknown result type (might be due to invalid IL or missing references)
//IL_0060: Expected O, but got Unknown
//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
//IL_00f6: Expected O, but got Unknown
//IL_0115: Unknown result type (might be due to invalid IL or missing references)
//IL_016d: Unknown result type (might be due to invalid IL or missing references)
//IL_0177: Expected O, but got Unknown
//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
Log = ((BaseUnityPlugin)this).Logger;
MoreWorldItems = ((BaseUnityPlugin)this).Config.Bind<bool>("World items", "Raise the limit", true, "The game keeps at most 64 dropped items in a save and silently discards the rest. Turn this on to keep more.");
WorldItemLimit = ((BaseUnityPlugin)this).Config.Bind<int>("World items", "Limit", 256, new ConfigDescription("How many dropped items a save may keep. Every item makes the save file bigger and the load slower, so this is not free.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(64, 2048), Array.Empty<object>()));
KeepLiveCreatures = ((BaseUnityPlugin)this).Config.Bind<bool>("World items", "Keep live fish", false, "Also keep fish that are still alive. The game deliberately drops these, so this is off by default.");
BackupsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Backups", "Enabled", true, "Keep a dated copy of your save every time the game saves.");
BackupAutoSaves = ((BaseUnityPlugin)this).Config.Bind<bool>("Backups", "Include autosaves", true, "Back up on autosaves too, not only when you save deliberately.");
BackupsKept = ((BaseUnityPlugin)this).Config.Bind<int>("Backups", "How many to keep", 20, new ConfigDescription("Older backups past this count are deleted.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 200), Array.Empty<object>()));
BackupKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Backups", "Open list", new KeyboardShortcut((KeyCode)291, Array.Empty<KeyCode>()), "Opens the list of backups. Restoring is only possible from the main menu.");
StackItems = ((BaseUnityPlugin)this).Config.Bind<bool>("Stacking", "Enabled", true, "Let identical items share one inventory slot. Fish never stack, because each one has its own weight and value.");
StackSize = ((BaseUnityPlugin)this).Config.Bind<int>("Stacking", "Stack size", 10, new ConfigDescription("Most of one item a single slot may hold.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2, 99), Array.Empty<object>()));
ShowCounts = ((BaseUnityPlugin)this).Config.Bind<bool>("Stacking", "Show counts", true, "Write the count on the inventory slot. Turn this off if it looks wrong with your interface scale.");
try
{
new Harmony("SpongeMods.QOL").PatchAll();
}
catch (Exception ex)
{
Log.LogError((object)("Could not install QOL patches: " + ex.Message));
}
Tabs.Register();
Log.LogInfo((object)"QOL ready.");
}
private void OnDestroy()
{
_badges.Wipe();
((Link)StackWire.Instance).Drop();
}
private void Update()
{
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
((Link)StackWire.Instance).Step();
_badges.Step();
KeyboardShortcut value = BackupKey.Value;
if (((KeyboardShortcut)(ref value)).IsDown())
{
if (_screen == null)
{
_screen = new BackupScreen(Palette.Default);
}
_screen.Toggle();
}
BackupScreen screen = _screen;
if (screen != null && screen.Open)
{
_screen.Step();
}
}
}
internal sealed class StackBadges
{
private readonly HashSet<int> _written = new HashSet<int>();
private readonly List<int> _cleared = new List<int>();
private float _next;
public void Step()
{
if (Time.unscaledTime < _next)
{
return;
}
_next = Time.unscaledTime + 0.25f;
if (!Plugin.StackItems.Value || !Plugin.ShowCounts.Value)
{
Wipe();
return;
}
Player localPlayer = Player.LocalPlayer;
PlayerInventory val = (((Object)(object)localPlayer != (Object)null) ? localPlayer.Inventory : null);
if ((Object)(object)val == (Object)null || val._itemSlots == null)
{
return;
}
List<InventorySlot> itemSlots = val._itemSlots;
for (int i = 0; i < itemSlots.Count; i++)
{
InventorySlot val2 = itemSlots[i];
if ((Object)(object)val2 == (Object)null || (Object)(object)val2._itemText == (Object)null)
{
continue;
}
int num = Held((byte)i);
if (num > 1)
{
string text = "x" + num;
if (((TMP_Text)val2._itemText).text != text)
{
((TMP_Text)val2._itemText).text = text;
}
_written.Add(i);
}
else if (_written.Remove(i))
{
((TMP_Text)val2._itemText).text = string.Empty;
}
}
}
private static int Held(byte slot)
{
if (InstanceFinder.IsServerStarted)
{
Player localPlayer = Player.LocalPlayer;
PlayerInventory val = (((Object)(object)localPlayer != (Object)null) ? localPlayer.Inventory : null);
Item item = default(Item);
if ((Object)(object)val != (Object)null && val._items.TryGetValue(slot, ref item))
{
return Stacks.Count(item);
}
}
if (!StackWire.Shown.TryGetValue(slot, out var value))
{
return 1;
}
return value;
}
public void Wipe()
{
if (_written.Count == 0)
{
return;
}
Player localPlayer = Player.LocalPlayer;
PlayerInventory val = (((Object)(object)localPlayer != (Object)null) ? localPlayer.Inventory : null);
List<InventorySlot> list = (((Object)(object)val != (Object)null) ? val._itemSlots : null);
if (list != null)
{
foreach (int item in _written)
{
if (item >= 0 && item < list.Count && (Object)(object)list[item] != (Object)null && (Object)(object)list[item]._itemText != (Object)null)
{
((TMP_Text)list[item]._itemText).text = string.Empty;
}
}
}
_written.Clear();
_cleared.Clear();
}
}
internal struct StackSync : IBroadcast
{
public byte[] Slots;
public byte[] Counts;
}
internal sealed class StackWire : Link
{
private const int MaxSlots = 32;
public static readonly StackWire Instance = new StackWire();
public static readonly Dictionary<byte, int> Shown = new Dictionary<byte, int>();
protected override void Describe()
{
Link.Describe<StackSync>((Action<Writer, StackSync>)delegate(Writer writer, StackSync value)
{
byte[] array = value.Slots ?? Array.Empty<byte>();
byte[] array2 = value.Counts ?? Array.Empty<byte>();
int num = Link.Sane((array.Length < array2.Length) ? array.Length : array2.Length, 32);
writer.WriteInt32(num);
for (int i = 0; i < num; i++)
{
writer.WriteUInt8Unpacked(array[i]);
writer.WriteUInt8Unpacked(array2[i]);
}
}, (Func<Reader, StackSync>)delegate(Reader reader)
{
int num = Link.Sane(reader.ReadInt32(), 32);
byte[] array = new byte[num];
byte[] array2 = new byte[num];
for (int i = 0; i < num; i++)
{
array[i] = reader.ReadUInt8Unpacked();
array2[i] = reader.ReadUInt8Unpacked();
}
return new StackSync
{
Slots = array,
Counts = array2
};
});
}
protected override void AttachClient()
{
InstanceFinder.ClientManager.RegisterBroadcast<StackSync>((Action<StackSync, Channel>)OnCounts);
}
protected override void DetachClient()
{
Shown.Clear();
if ((Object)(object)InstanceFinder.ClientManager != (Object)null)
{
InstanceFinder.ClientManager.UnregisterBroadcast<StackSync>((Action<StackSync, Channel>)OnCounts);
}
}
private static void OnCounts(StackSync message, Channel channel)
{
Shown.Clear();
if (message.Slots != null && message.Counts != null)
{
int num = ((message.Slots.Length < message.Counts.Length) ? message.Slots.Length : message.Counts.Length);
for (int i = 0; i < num; i++)
{
Shown[message.Slots[i]] = message.Counts[i];
}
}
}
public static void Publish(NetworkConnection owner, List<byte> slots, List<byte> counts)
{
Link.TellOne<StackSync>(owner, new StackSync
{
Slots = slots.ToArray(),
Counts = counts.ToArray()
});
}
}
internal static class Stacks
{
private static readonly Dictionary<Item, int> _counts = new Dictionary<Item, int>();
private static bool _splitting;
private static readonly List<Item> _stale = new List<Item>();
private static readonly List<byte> _slotBuffer = new List<byte>();
private static readonly List<byte> _countBuffer = new List<byte>();
public static int Ceiling => Mathf.Clamp(Plugin.StackSize.Value, 2, 99);
public static bool Splitting => _splitting;
public static int Count(Item item)
{
if (!((Object)(object)item != (Object)null) || !_counts.TryGetValue(item, out var value))
{
return 1;
}
return value;
}
public static void Set(Item item, int count)
{
if (!((Object)(object)item == (Object)null))
{
if (count <= 1)
{
_counts.Remove(item);
}
else
{
_counts[item] = count;
}
}
}
public static bool CanStack(Item item)
{
if ((Object)(object)item == (Object)null || !Plugin.StackItems.Value)
{
return false;
}
if (item.IsDestroying || ((NetworkBehaviour)item).IsDeinitializing)
{
return false;
}
if ((Object)(object)item.Creature != (Object)null || (Object)(object)item.Tool != (Object)null || (Object)(object)item.DeadPlayer != (Object)null)
{
return false;
}
if (item.IsQuestItem)
{
return false;
}
if (Mathf.Approximately(item.BettingMultiplier, 1f))
{
return Mathf.Approximately(item.KillScoreMultiplier, 1f);
}
return false;
}
public static bool Same(Item left, Item right)
{
if ((Object)(object)left == (Object)null || (Object)(object)right == (Object)null || (Object)(object)left == (Object)(object)right)
{
return false;
}
if (left.ID == right.ID && left.CurSkin == right.CurSkin)
{
return Mathf.Abs(left.Cookness - right.Cookness) <= 0.01f;
}
return false;
}
public static Item FindRoom(PlayerInventory inventory, Item incoming)
{
if ((Object)(object)inventory == (Object)null || !CanStack(incoming))
{
return null;
}
foreach (KeyValuePair<byte, Item> item in inventory._items)
{
Item value = item.Value;
if (!((Object)(object)value == (Object)null) && CanStack(value) && Same(value, incoming) && Count(value) < Ceiling)
{
return value;
}
}
return null;
}
public static void Merge(PlayerInventory inventory, Item into, Item incoming)
{
Set(into, Count(into) + 1);
((NetworkBehaviour)inventory).Despawn(((Component)incoming).gameObject, (DespawnType?)null);
Publish(inventory);
}
public static void Split(PlayerInventory inventory, Item taken, byte slot, int had)
{
//IL_0081: Unknown result type (might be due to invalid IL or missing references)
//IL_0093: Unknown result type (might be due to invalid IL or missing references)
//IL_009a: Unknown result type (might be due to invalid IL or missing references)
//IL_009b: Unknown result type (might be due to invalid IL or missing references)
//IL_008e: Unknown result type (might be due to invalid IL or missing references)
Set(taken, 1);
if (had <= 1 || (Object)(object)inventory == (Object)null || (Object)(object)taken == (Object)null)
{
return;
}
Item spawnable = GameInfo.GetSpawnable(taken.ID);
if ((Object)(object)spawnable == (Object)null || (Object)(object)ItemManager.Instance == (Object)null)
{
Plugin.Log.LogWarning((object)$"Could not restock slot {slot}; {had - 1} item(s) were lost.");
return;
}
Player player = inventory._player;
Vector3 val = (((Object)(object)player != (Object)null && (Object)(object)player.Transform != (Object)null) ? player.Transform.position : Vector3.zero);
Item val2 = ItemManager.Instance.SpawnNewItem(spawnable, val, Quaternion.identity);
if ((Object)(object)val2 == (Object)null)
{
Plugin.Log.LogWarning((object)$"Could not restock slot {slot}; {had - 1} item(s) were lost.");
return;
}
val2._cookness.Value = taken.Cookness;
val2._curSkin.Value = taken.CurSkin;
val2.SetSyncedHolder(player, true);
_splitting = true;
try
{
inventory.AddItem(slot, val2);
}
finally
{
_splitting = false;
}
Set(val2, had - 1);
Publish(inventory);
}
public static void Publish(PlayerInventory inventory)
{
if ((Object)(object)inventory == (Object)null)
{
return;
}
Forget();
_slotBuffer.Clear();
_countBuffer.Clear();
foreach (KeyValuePair<byte, Item> item in inventory._items)
{
int num = Count(item.Value);
if (num > 1)
{
_slotBuffer.Add(item.Key);
_countBuffer.Add((byte)Mathf.Clamp(num, 0, 255));
}
}
StackWire.Publish(((NetworkBehaviour)inventory).Owner, _slotBuffer, _countBuffer);
}
private static void Forget()
{
foreach (KeyValuePair<Item, int> count in _counts)
{
if ((Object)(object)count.Key == (Object)null)
{
_stale.Add(count.Key);
}
}
if (_stale.Count != 0)
{
for (int i = 0; i < _stale.Count; i++)
{
_counts.Remove(_stale[i]);
}
_stale.Clear();
}
}
private static string FileFor(string save)
{
return SideCar.PathFor("sponge-stacks", save);
}
public static void Persist(PlayerInventory inventory)
{
string text = SaveManager.CurServerSave?.Name;
if (string.IsNullOrEmpty(text) || (Object)(object)inventory?._player == (Object)null)
{
return;
}
try
{
StackFile stackFile = Read(text);
string owner = inventory._player.SteamID.ToString(CultureInfo.InvariantCulture);
stackFile.Players.RemoveAll((StackOwner entry) => entry.SteamIdText == owner);
StackOwner stackOwner = new StackOwner
{
SteamIdText = owner
};
foreach (KeyValuePair<byte, Item> item in inventory._items)
{
int num = Count(item.Value);
if (num > 1)
{
stackOwner.Slots.Add(new StackSlot
{
Slot = item.Key,
Count = num
});
}
}
if (stackOwner.Slots.Count > 0)
{
stackFile.Players.Add(stackOwner);
}
SideCar.Write(FileFor(text), (object)stackFile, (Action<string>)Plugin.Log.LogWarning);
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not write stack counts: " + ex.Message));
}
}
public static void Restore(PlayerInventory inventory)
{
string text = SaveManager.CurServerSave?.Name;
if (string.IsNullOrEmpty(text) || (Object)(object)inventory?._player == (Object)null)
{
return;
}
try
{
StackFile stackFile = Read(text);
string text2 = inventory._player.SteamID.ToString(CultureInfo.InvariantCulture);
Item val = default(Item);
foreach (StackOwner player in stackFile.Players)
{
if (player.SteamIdText != text2)
{
continue;
}
foreach (StackSlot slot in player.Slots)
{
if (inventory._items.TryGetValue(slot.Slot, ref val) && (Object)(object)val != (Object)null)
{
Set(val, Mathf.Clamp(slot.Count, 1, Ceiling));
}
}
break;
}
Publish(inventory);
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not read stack counts: " + ex.Message));
}
}
private static StackFile Read(string save)
{
return SideCar.Read<StackFile>(FileFor(save), (Action<string>)Plugin.Log.LogWarning);
}
}
[Serializable]
internal class StackFile
{
public int Version = 1;
public List<StackOwner> Players = new List<StackOwner>();
}
[Serializable]
internal class StackOwner
{
public string SteamIdText;
public List<StackSlot> Slots = new List<StackSlot>();
}
[Serializable]
internal class StackSlot
{
public byte Slot;
public int Count;
}
[HarmonyPatch(typeof(PlayerInventory), "ServerTryStoreHeldItem")]
internal static class StoreIntoStackPatch
{
private static bool Prefix(PlayerInventory __instance, Item item, ref bool __result)
{
if (Stacks.Splitting || !Plugin.StackItems.Value)
{
return true;
}
if (!((NetworkBehaviour)__instance).IsServerInitialized || (Object)(object)item == (Object)null || ((NetworkBehaviour)item).IsDeinitializing || (Object)(object)item.DeadPlayer != (Object)null || (Object)(object)item.SyncedHolder != (Object)(object)__instance._player)
{
return true;
}
if (__instance.HasSyncedInventoryItem(item))
{
return true;
}
Item val = Stacks.FindRoom(__instance, item);
if ((Object)(object)val == (Object)null)
{
return true;
}
Stacks.Merge(__instance, val, item);
__result = true;
return false;
}
}
[HarmonyPatch(typeof(PlayerInventory), "AddItem")]
internal static class AddIntoStackPatch
{
private static bool Prefix(PlayerInventory __instance, byte index, Item item)
{
if (Stacks.Splitting || !Plugin.StackItems.Value)
{
return true;
}
if (!((NetworkBehaviour)__instance).IsServerInitialized || (Object)(object)item == (Object)null || (Object)(object)item.SyncedHolder != (Object)(object)__instance._player)
{
return true;
}
Item val = default(Item);
if (!__instance._items.TryGetValue(index, ref val) || (Object)(object)val == (Object)null)
{
return true;
}
if (!Stacks.CanStack(item) || !Stacks.CanStack(val) || !Stacks.Same(val, item))
{
return true;
}
if (Stacks.Count(val) >= Stacks.Ceiling)
{
return true;
}
Stacks.Merge(__instance, val, item);
return false;
}
}
[HarmonyPatch(typeof(PlayerInventory), "RemoveItem")]
internal static class TakeFromStackPatch
{
private static void Prefix(PlayerInventory __instance, Item item, out StackTake __state)
{
__state = default(StackTake);
if (!Plugin.StackItems.Value || !((NetworkBehaviour)__instance).IsServerInitialized || (Object)(object)item == (Object)null)
{
return;
}
int num = Stacks.Count(item);
if (num <= 1)
{
return;
}
foreach (KeyValuePair<byte, Item> item2 in __instance._items)
{
if (!((Object)(object)item2.Value != (Object)(object)item))
{
__state = new StackTake
{
Found = true,
Slot = item2.Key,
Had = num
};
break;
}
}
}
private static void Postfix(PlayerInventory __instance, Item item, StackTake __state)
{
if (__state.Found)
{
Stacks.Split(__instance, item, __state.Slot, __state.Had);
}
}
}
internal struct StackTake
{
public bool Found;
public byte Slot;
public int Had;
}
[HarmonyPatch(typeof(PlayerInventory), "OnItemsChange")]
internal static class HideStockedItemPatch
{
private static void Postfix(PlayerInventory __instance, Item item, bool asServer)
{
if (!asServer && !((Object)(object)item == (Object)null) && Plugin.StackItems.Value && !((Object)(object)item == (Object)(object)__instance.SyncedCurItem) && (Object)(object)item.SyncedHolder == (Object)(object)__instance._player)
{
item.PutInInventory();
}
}
}
[HarmonyPatch(typeof(PlayerInventory), "SaveInventory")]
internal static class SaveStacksPatch
{
private static void Prefix(PlayerInventory __instance)
{
if (Plugin.StackItems.Value && ((NetworkBehaviour)__instance).IsServerInitialized)
{
Stacks.Persist(__instance);
}
}
}
[HarmonyPatch(typeof(PlayerInventory), "LoadFromSave")]
internal static class LoadStacksPatch
{
private static void Postfix(PlayerInventory __instance)
{
if (Plugin.StackItems.Value)
{
Stacks.Restore(__instance);
}
}
}
internal static class Tabs
{
public static void Register()
{
SpongeMenu.Tab("SpongeMods.QOL", "Quality of life", 30).Heading("Stacking").Toggle("Stack identical items", Plugin.StackItems)
.OnlyHost()
.Number("Stack size", Plugin.StackSize)
.OnlyHost()
.Toggle("Write the count on the slot", Plugin.ShowCounts)
.Note("Fish never stack, because each one has its own weight and value.")
.Heading("Dropped items")
.Toggle("Keep more of them", Plugin.MoreWorldItems)
.OnlyHost()
.Number("Limit", Plugin.WorldItemLimit)
.OnlyHost()
.Toggle("Keep live fish too", Plugin.KeepLiveCreatures)
.OnlyHost()
.Note("Every item kept makes the save bigger and the load slower, so this is not free.")
.Heading("Backups")
.Toggle("Keep a history", Plugin.BackupsEnabled)
.Toggle("Back up autosaves too", Plugin.BackupAutoSaves)
.Number("How many to keep", Plugin.BackupsKept)
.Note("Backups are yours alone, kept beside your saves. Restoring one is only possible from the main menu.");
}
}
[HarmonyPatch(typeof(SaveManager), "SaveWorldItems")]
internal static class WorldItemLimitPatch
{
private static bool Prefix(ref List<SavedWorldItem> __result)
{
//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
//IL_00db: Unknown result type (might be due to invalid IL or missing references)
//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
//IL_011f: Expected O, but got Unknown
if (!Plugin.MoreWorldItems.Value)
{
return true;
}
List<Item> list = new List<Item>();
HashSet<Item> hashSet = new HashSet<Item>();
foreach (KeyValuePair<Transform, Item> item in ItemManager.Items)
{
Item value = item.Value;
if (hashSet.Add(value) && ShouldSave(value))
{
list.Add(value);
}
}
list.Sort((Item left, Item right) => SaveManager.GetWorldItemSavePriority(left).CompareTo(SaveManager.GetWorldItemSavePriority(right)));
int num = Mathf.Min(list.Count, Plugin.WorldItemLimit.Value);
List<SavedWorldItem> list2 = new List<SavedWorldItem>(num);
for (int num2 = 0; num2 < num; num2++)
{
Item val = list[num2];
list2.Add(new SavedWorldItem
{
Item = SaveManager.ItemToSavedItem((byte)0, val),
Position = ((Component)val).transform.position,
Rotation = ((Component)val).transform.rotation,
IsDeadCreature = (Object.op_Implicit((Object)(object)val.Creature) && val.Creature.IsDead)
});
}
__result = list2;
return false;
}
private static bool ShouldSave(Item item)
{
if (SaveManager.CanSaveWorldItem(item))
{
return true;
}
if (!Plugin.KeepLiveCreatures.Value)
{
return false;
}
if (!Object.op_Implicit((Object)(object)item) || !Object.op_Implicit((Object)(object)item.Creature) || item.Creature.IsDead)
{
return false;
}
if (((NetworkBehaviour)item).IsServerInitialized && !item.IsDestroying && !((NetworkBehaviour)item).IsDeinitializing && !Object.op_Implicit((Object)(object)item.SyncedHolder) && !Object.op_Implicit((Object)(object)item.Holder) && !item.IsInInventory && !Object.op_Implicit((Object)(object)item.BirdHolder) && !Object.op_Implicit((Object)(object)item.AttachedRod) && !Object.op_Implicit((Object)(object)item.DeadPlayer))
{
return !item.IgnoredByCloseDots;
}
return false;
}
}
}
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
internal sealed class IgnoresAccessChecksToAttribute : Attribute
{
internal IgnoresAccessChecksToAttribute(string assemblyName)
{
}
}
}