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.Logging;
using FishNet;
using FishNet.Broadcast;
using FishNet.Connection;
using FishNet.Object;
using FishNet.Serializing;
using FishNet.Transporting;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
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: AssemblyCompany("NoSharedMoney")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.4.0.0")]
[assembly: AssemblyInformationalVersion("1.4.0")]
[assembly: AssemblyProduct("NoSharedMoney")]
[assembly: AssemblyTitle("NoSharedMoney")]
[assembly: AssemblyVersion("1.4.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 NoSharedMoney
{
public static class BackupLink
{
private const string CompanionId = "nosharedmoney";
private const string CompanionName = "NoSharedMoney";
private static MethodInfo _sanitise;
public static bool Linked { get; private set; }
public static void TryRegister()
{
try
{
Type type = FindCompanionsType();
if (type == null)
{
Plugin.Log.LogInfo((object)"SaveBackups is not installed, so wallets will not be included in save backups. Installing it is recommended, since a lost wallet file cannot be recovered any other way.");
return;
}
_sanitise = type.GetMethod("SanitiseSaveName", BindingFlags.Static | BindingFlags.Public);
MethodInfo method = type.GetMethod("Register", BindingFlags.Static | BindingFlags.Public);
if (method == null)
{
Plugin.Log.LogWarning((object)"SaveBackups is installed but its companion API looks different than expected, so wallets will not be backed up.");
return;
}
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length != 3)
{
Plugin.Log.LogWarning((object)"SaveBackups is installed but its companion API takes different arguments, so wallets will not be backed up.");
return;
}
Delegate obj = Delegate.CreateDelegate(parameters[2].ParameterType, typeof(BackupLink).GetMethod("PathForSave", BindingFlags.Static | BindingFlags.NonPublic));
method.Invoke(null, new object[3] { "nosharedmoney", "NoSharedMoney", obj });
Linked = true;
Plugin.Log.LogInfo((object)"Registered with SaveBackups. Wallets are now captured and restored alongside the save.");
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not register with SaveBackups, wallets will not be backed up: " + ex.Message));
}
}
private static Type FindCompanionsType()
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
if (!(assembly.GetName().Name != "SaveBackups"))
{
return assembly.GetType("SaveBackups.Companions");
}
}
return null;
}
private static string PathForSave(string saveName)
{
if (string.IsNullOrEmpty(saveName))
{
return null;
}
try
{
if (_sanitise != null)
{
string text = _sanitise.Invoke(null, new object[1] { saveName }) as string;
if (!string.IsNullOrEmpty(text))
{
return WalletStore.PathForSanitised(text);
}
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("SaveBackups could not clean up the save name, falling back to our own rule: " + ex.Message));
}
return WalletStore.PublicPathFor(saveName);
}
}
internal static class Balances
{
private static readonly Dictionary<ulong, int> _known = new Dictionary<ulong, int>();
private static FieldInfo _nameField;
private static bool _resolved;
public static void Reset()
{
_known.Clear();
}
public static void Apply(ulong[] ids, int[] money)
{
if (ids != null && money != null)
{
_known.Clear();
for (int i = 0; i < ids.Length && i < money.Length; i++)
{
_known[ids[i]] = money[i];
}
RedrawAll();
}
}
public static bool TryGet(ulong steamID, out int money)
{
return _known.TryGetValue(steamID, out money);
}
private static bool Resolve()
{
if (_resolved)
{
return _nameField != null;
}
_resolved = true;
_nameField = AccessTools.Field(typeof(OtherPlayer), "_nameText");
if (_nameField == null)
{
Plugin.Log.LogWarning((object)"OtherPlayer._nameText is missing in this build, so balances will not appear under player names.");
}
return _nameField != null;
}
public static void RedrawAll()
{
if (!Plugin.ShowBalancesOnNameplates.Value || !Resolve())
{
return;
}
foreach (Player player in PlayerManager.Players)
{
Redraw(player);
}
}
public static void Redraw(Player player)
{
if ((Object)(object)player == (Object)null || (Object)(object)player == (Object)(object)Player.LocalPlayer || !Plugin.ShowBalancesOnNameplates.Value || !Resolve())
{
return;
}
try
{
OtherPlayer other = player.Other;
if ((Object)(object)other == (Object)null)
{
return;
}
object? value = _nameField.GetValue(other);
TextMeshProUGUI val = (TextMeshProUGUI)((value is TextMeshProUGUI) ? value : null);
if (!((Object)(object)val == (Object)null))
{
string text = player.SteamName;
if (string.IsNullOrEmpty(text))
{
text = "Player";
}
if (!_known.TryGetValue(player.SteamID, out var value2))
{
((TMP_Text)val).text = text;
}
else
{
((TMP_Text)val).text = text + "\n<size=70%>$" + value2 + "</size>";
}
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not draw a balance under a player name: " + ex.Message));
}
}
}
internal static class BoatCosts
{
private static readonly HashSet<string> _shared = new HashSet<string>(StringComparer.Ordinal) { "BuyBoatMotor", "BuyBoatRadar" };
public static bool Applies(string method)
{
if (Plugin.SplitBoatCosts.Value && method != null)
{
return _shared.Contains(method);
}
return false;
}
public static List<Player> Present()
{
List<Player> list = new List<Player>();
foreach (Player player in PlayerManager.Players)
{
if ((Object)(object)player != (Object)null)
{
list.Add(player);
}
}
return list;
}
public static bool CanEveryoneAfford(int cost, Player buyer, out string reason)
{
reason = null;
List<Player> list = Present();
if (list.Count <= 1)
{
return cost <= Wallets.Get(buyer);
}
int num = cost / list.Count;
int num2 = cost - num * list.Count;
Player val = (list.Contains(buyer) ? buyer : list[0]);
List<string> list2 = new List<string>();
foreach (Player item in list)
{
int num3 = num + (((Object)(object)item == (Object)(object)val) ? num2 : 0);
if (Wallets.Get(item) < num3)
{
list2.Add(Name(item) + " needs " + num3);
}
}
if (list2.Count == 0)
{
return true;
}
reason = string.Join(", ", list2.ToArray());
return false;
}
public static void Charge(int cost, Player buyer)
{
List<Player> list = Present();
if (list.Count <= 1)
{
Wallets.Remove(buyer, cost);
Refl.PlayMoneySounds(increase: false, buyer);
return;
}
int num = cost / list.Count;
int num2 = cost - num * list.Count;
Player val = (list.Contains(buyer) ? buyer : list[0]);
foreach (Player item in list)
{
int num3 = num + (((Object)(object)item == (Object)(object)val) ? num2 : 0);
if (num3 > 0)
{
Wallets.Remove(item, num3);
Refl.PlayMoneySounds(increase: false, item);
}
}
Plugin.Log.LogInfo((object)($"Boat purchase of {cost} split {num} each between {list.Count} player(s)" + ((num2 > 0) ? $", with {num2} extra on {Name(val)}." : ".")));
}
private static string Name(Player player)
{
if ((Object)(object)player == (Object)null)
{
return "someone";
}
string steamName = player.SteamName;
if (!string.IsNullOrEmpty(steamName))
{
return steamName;
}
return "a player";
}
}
internal static class BossLoot
{
private static readonly HashSet<string> _names = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private static bool _scanned;
public static void Reset()
{
_names.Clear();
_scanned = false;
}
public static void Learn(Item prefab)
{
if (!((Object)(object)prefab == (Object)null))
{
string text = BaseName(((Object)prefab).name);
if (!string.IsNullOrEmpty(text) && _names.Add(text))
{
Plugin.Log.LogInfo((object)("'" + text + "' counts as boss loot, so selling it will be split between everyone."));
}
}
}
private static string BaseName(string name)
{
if (string.IsNullOrEmpty(name))
{
return null;
}
int num = name.IndexOf("(Clone)", StringComparison.OrdinalIgnoreCase);
return ((num >= 0) ? name.Substring(0, num) : name).Trim();
}
public static void Scan()
{
//IL_002f: Unknown result type (might be due to invalid IL or missing references)
if (_scanned)
{
return;
}
int allCreatureCount = GameInfo.AllCreatureCount;
if (allCreatureCount == 0)
{
return;
}
_scanned = true;
int num = 0;
try
{
for (int i = 0; i < allCreatureCount; i++)
{
Creature creature = GameInfo.GetCreature(i);
if (!((Object)(object)creature == (Object)null) && (int)creature.BossType != 0)
{
num++;
Learn(Field(creature, "_spawnOnDeath"));
Learn(Field(creature, "_meat"));
}
}
Plugin.Log.LogInfo((object)$"Checked {allCreatureCount} creature(s) and found {num} boss type(s) dropping {_names.Count} sellable item(s) that will be split.");
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not read the boss loot list up front, so it will be learned when a boss dies instead: " + ex.Message));
}
}
private static Item Field(Creature creature, string name)
{
Type type = ((object)creature).GetType();
while (type != null)
{
FieldInfo field = type.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
object? value = field.GetValue(creature);
return (Item)((value is Item) ? value : null);
}
type = type.BaseType;
}
return null;
}
public static bool Includes(Item item)
{
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)item == (Object)null)
{
return false;
}
Creature creature = item.Creature;
if ((Object)(object)creature != (Object)null && (int)creature.BossType != 0)
{
return true;
}
Scan();
string text = BaseName(((Object)item).name);
if (text != null)
{
return _names.Contains(text);
}
return false;
}
}
public static class ClientMoney
{
public static int Money { get; private set; }
public static void Apply(int money)
{
int num = money - Money;
Money = money;
Refl.SetStaticMoney(money);
Refl.RaiseOnItemSold();
PlayerUI.SetMoney(money, (num < 0) ? (-num) : num, num > 0);
}
public static void Reset()
{
Money = 0;
Refl.SetStaticMoney(0);
}
}
public struct MoneySyncBroadcast : IBroadcast
{
public int Money;
}
public struct ModHandshakeBroadcast : IBroadcast
{
public int Version;
}
public struct BalancesBroadcast : IBroadcast
{
public ulong[] Ids;
public int[] Money;
}
public static class Net
{
public const int ProtocolVersion = 1;
private static bool _serializersRegistered;
private static bool _serverRegistered;
private static bool _clientRegistered;
public static void RegisterSerializers()
{
if (_serializersRegistered)
{
return;
}
_serializersRegistered = true;
GenericWriter<MoneySyncBroadcast>.SetWrite((Action<Writer, MoneySyncBroadcast>)delegate(Writer w, MoneySyncBroadcast v)
{
w.WriteInt32(v.Money);
});
GenericReader<MoneySyncBroadcast>.SetRead((Func<Reader, MoneySyncBroadcast>)((Reader r) => new MoneySyncBroadcast
{
Money = r.ReadInt32()
}));
GenericWriter<ModHandshakeBroadcast>.SetWrite((Action<Writer, ModHandshakeBroadcast>)delegate(Writer w, ModHandshakeBroadcast v)
{
w.WriteInt32(v.Version);
});
GenericReader<ModHandshakeBroadcast>.SetRead((Func<Reader, ModHandshakeBroadcast>)((Reader r) => new ModHandshakeBroadcast
{
Version = r.ReadInt32()
}));
GenericWriter<BalancesBroadcast>.SetWrite((Action<Writer, BalancesBroadcast>)delegate(Writer w, BalancesBroadcast v)
{
int num = ((v.Ids != null && v.Money != null) ? Math.Min(v.Ids.Length, v.Money.Length) : 0);
w.WriteInt32(num);
for (int i = 0; i < num; i++)
{
w.WriteUInt64(v.Ids[i]);
w.WriteInt32(v.Money[i]);
}
});
GenericReader<BalancesBroadcast>.SetRead((Func<Reader, BalancesBroadcast>)delegate(Reader r)
{
int num = r.ReadInt32();
if (num < 0 || num > 64)
{
num = 0;
}
ulong[] array = new ulong[num];
int[] array2 = new int[num];
for (int i = 0; i < num; i++)
{
array[i] = r.ReadUInt64();
array2[i] = r.ReadInt32();
}
return new BalancesBroadcast
{
Ids = array,
Money = array2
};
});
}
public static void TryRegisterHandlers()
{
if (!((Object)(object)InstanceFinder.NetworkManager == (Object)null))
{
RegisterSerializers();
if (!_serverRegistered && (Object)(object)InstanceFinder.ServerManager != (Object)null)
{
_serverRegistered = true;
InstanceFinder.ServerManager.RegisterBroadcast<ModHandshakeBroadcast>((Action<NetworkConnection, ModHandshakeBroadcast, Channel>)OnHandshakeFromClient, true);
Plugin.Log.LogDebug((object)"Registered server broadcast handler.");
}
if (!_clientRegistered && (Object)(object)InstanceFinder.ClientManager != (Object)null)
{
_clientRegistered = true;
InstanceFinder.ClientManager.RegisterBroadcast<MoneySyncBroadcast>((Action<MoneySyncBroadcast, Channel>)OnMoneyFromServer);
InstanceFinder.ClientManager.RegisterBroadcast<BalancesBroadcast>((Action<BalancesBroadcast, Channel>)OnBalancesFromServer);
Plugin.Log.LogDebug((object)"Registered client broadcast handler.");
}
}
}
private static void OnHandshakeFromClient(NetworkConnection conn, ModHandshakeBroadcast msg, Channel channel)
{
Wallets.MarkModded(conn, msg.Version);
}
private static void OnMoneyFromServer(MoneySyncBroadcast msg, Channel channel)
{
ClientMoney.Apply(msg.Money);
}
private static void OnBalancesFromServer(BalancesBroadcast msg, Channel channel)
{
Balances.Apply(msg.Ids, msg.Money);
}
public static void PushBalances(ulong[] ids, int[] money)
{
if (!InstanceFinder.IsServerStarted || (Object)(object)InstanceFinder.ServerManager == (Object)null)
{
return;
}
try
{
RegisterSerializers();
InstanceFinder.ServerManager.Broadcast<BalancesBroadcast>(new BalancesBroadcast
{
Ids = ids,
Money = money
}, true, (Channel)0);
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not send balances to clients: " + ex.Message));
}
}
public static void SendHandshake()
{
if (!InstanceFinder.IsClientStarted || (Object)(object)InstanceFinder.ClientManager == (Object)null)
{
return;
}
try
{
InstanceFinder.ClientManager.Broadcast<ModHandshakeBroadcast>(new ModHandshakeBroadcast
{
Version = 1
}, (Channel)0);
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Failed to send handshake: " + ex));
}
}
public static void SendMoney(NetworkConnection conn, int money)
{
if (!InstanceFinder.IsServerStarted || (Object)(object)InstanceFinder.ServerManager == (Object)null || conn == (NetworkConnection)null || !conn.IsActive)
{
return;
}
try
{
InstanceFinder.ServerManager.Broadcast<MoneySyncBroadcast>(conn, new MoneySyncBroadcast
{
Money = money
}, true, (Channel)0);
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Failed to send money to client " + conn.ClientId + ": " + ex));
}
}
}
public static class Ownership
{
private class Holder
{
public Player Player;
}
private static ConditionalWeakTable<Item, Holder> _lastHeldBy = new ConditionalWeakTable<Item, Holder>();
public static void Record(Item item, Player player)
{
if (!((Object)(object)item == (Object)null) && !((Object)(object)player == (Object)null) && InstanceFinder.IsServerStarted)
{
_lastHeldBy.GetOrCreateValue(item).Player = player;
}
}
public static Player Resolve(Item item)
{
if ((Object)(object)item == (Object)null)
{
return null;
}
if ((Object)(object)item.Holder != (Object)null)
{
return item.Holder;
}
if ((Object)(object)item.SyncedHolder != (Object)null)
{
return item.SyncedHolder;
}
if (_lastHeldBy.TryGetValue(item, out var value) && (Object)(object)value.Player != (Object)null)
{
return value.Player;
}
return item.LastHolder;
}
public static void Clear()
{
_lastHeldBy = new ConditionalWeakTable<Item, Holder>();
}
}
public static class PurchaseContext
{
public static Player Buyer;
public static string Method;
private static int _depth;
public static bool Active
{
get
{
if (_depth > 0)
{
return (Object)(object)Buyer != (Object)null;
}
return false;
}
}
public static bool InsidePurchase => _depth > 0;
public static void Enter(Player buyer, string method)
{
_depth++;
if ((Object)(object)buyer != (Object)null)
{
Buyer = buyer;
}
if (method != null)
{
Method = method;
}
}
public static void Exit()
{
_depth--;
if (_depth <= 0)
{
_depth = 0;
Buyer = null;
Method = null;
}
}
}
public static class Patches
{
private static readonly string[] SpendMethods = new string[8] { "BuyItem", "BuyBait", "BuyAttachment", "BuyBulletUpgrade", "BuySharpnessUpgrade", "BuyBoatMotor", "BuyBoatRadar", "UnlockPocket" };
public static void ApplyAll(Harmony harmony)
{
PatchMoneyManager(harmony);
PatchSaveManager(harmony);
PatchSpendSites(harmony);
PatchOwnership(harmony);
PatchExtras(harmony);
}
private static void PatchOwnership(Harmony harmony)
{
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_0042: Expected O, but got Unknown
//IL_007e: Unknown result type (might be due to invalid IL or missing references)
//IL_008b: Expected O, but got Unknown
Type typeFromHandle = typeof(Patches);
MethodInfo methodInfo = AccessTools.Method(typeof(Item), "SetSyncedHolder", (Type[])null, (Type[])null);
if (methodInfo != null)
{
harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "SetSyncedHolderPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
}
else
{
Plugin.Log.LogWarning((object)"Could not find Item.SetSyncedHolder. Sales by other players may be credited to the host.");
}
MethodInfo methodInfo2 = AccessTools.Method(typeof(Item), "PickUp", (Type[])null, (Type[])null);
if (methodInfo2 != null)
{
harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "PickUpPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
}
}
private static void SetSyncedHolderPostfix(Item __instance, Player newHolder)
{
if ((Object)(object)__instance != (Object)null && (Object)(object)newHolder != (Object)null && (Object)(object)__instance.SyncedHolder == (Object)(object)newHolder)
{
Ownership.Record(__instance, newHolder);
}
}
private static void PickUpPostfix(Item __instance, Player player)
{
Ownership.Record(__instance, player);
}
private static void PatchMoneyManager(Harmony harmony)
{
//IL_002b: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Expected O, but got Unknown
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_005d: Expected O, but got Unknown
//IL_0073: Unknown result type (might be due to invalid IL or missing references)
//IL_0081: Expected O, but got Unknown
//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
//IL_00ca: Expected O, but got Unknown
//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
//IL_00ee: Expected O, but got Unknown
//IL_0104: Unknown result type (might be due to invalid IL or missing references)
//IL_0112: Expected O, but got Unknown
//IL_0129: Unknown result type (might be due to invalid IL or missing references)
//IL_0136: Expected O, but got Unknown
//IL_014d: Unknown result type (might be due to invalid IL or missing references)
//IL_015a: Expected O, but got Unknown
//IL_0171: Unknown result type (might be due to invalid IL or missing references)
//IL_017e: Expected O, but got Unknown
Type typeFromHandle = typeof(MoneyManager);
Type typeFromHandle2 = typeof(Patches);
harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle, "AddMoney", (Type[])null, (Type[])null), new HarmonyMethod(typeFromHandle2, "AddMoneyPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle, "RemoveMoney", (Type[])null, (Type[])null), new HarmonyMethod(typeFromHandle2, "RemoveMoneyPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle, "SellItem", (Type[])null, (Type[])null), new HarmonyMethod(typeFromHandle2, "SellItemPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
MethodInfo methodInfo = AccessTools.Method(typeof(BossManager), "SpawnBossTrophy", (Type[])null, (Type[])null);
if (methodInfo == null)
{
Plugin.Log.LogWarning((object)"BossManager.SpawnBossTrophy is missing in this build, so boss meat can only be recognised from the creature list.");
}
else
{
harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeFromHandle2, "SpawnBossTrophyPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
}
harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle, "CanAfford", (Type[])null, (Type[])null), new HarmonyMethod(typeFromHandle2, "CanAffordPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle, "OnChangeMoney", (Type[])null, (Type[])null), new HarmonyMethod(typeFromHandle2, "SkipPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle, "OnStartServer", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeFromHandle2, "OnStartServerPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle, "OnStartClient", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeFromHandle2, "OnStartClientPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle, "OnStopClient", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeFromHandle2, "OnStopClientPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
}
private static void PatchSaveManager(Harmony harmony)
{
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Expected O, but got Unknown
//IL_0049: Expected O, but got Unknown
harmony.Patch((MethodBase)AccessTools.Method(typeof(SaveManager), "SaveServer", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Patches), "SaveServerPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "SaveServerFinalizer", (Type[])null), (HarmonyMethod)null);
}
private static void PatchSpendSites(Harmony harmony)
{
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Expected O, but got Unknown
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_002c: Expected O, but got Unknown
HarmonyMethod val = new HarmonyMethod(typeof(Patches), "SpendPrefix", (Type[])null);
HarmonyMethod val2 = new HarmonyMethod(typeof(Patches), "SpendFinalizer", (Type[])null);
MethodInfo[] methods = typeof(Server).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
List<string> list = new List<string>();
string[] spendMethods = SpendMethods;
foreach (string text in spendMethods)
{
string text2 = "RpcLogic___" + text + "___";
bool flag = false;
MethodInfo[] array = methods;
foreach (MethodInfo methodInfo in array)
{
if (methodInfo.Name.StartsWith(text2, StringComparison.Ordinal))
{
harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, val2, (HarmonyMethod)null);
list.Add(text);
flag = true;
break;
}
}
if (!flag)
{
Plugin.Log.LogError((object)("Could not find Server." + text2 + "* - purchases through '" + text + "' will be charged to the wrong wallet."));
}
}
Plugin.Log.LogInfo((object)string.Format("Hooked {0}/{1} purchase paths: {2}", list.Count, SpendMethods.Length, string.Join(", ", list.ToArray())));
}
private static void PatchExtras(Harmony harmony)
{
//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
//IL_00bc: Expected O, but got Unknown
//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
//IL_0105: Expected O, but got Unknown
//IL_0140: Unknown result type (might be due to invalid IL or missing references)
//IL_014e: Expected O, but got Unknown
//IL_018c: Unknown result type (might be due to invalid IL or missing references)
//IL_0199: Expected O, but got Unknown
Type typeFromHandle = typeof(Patches);
MethodInfo methodInfo = null;
foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(typeof(Server)))
{
if (declaredMethod.Name.IndexOf("RpcLogic___SendChatMessage", StringComparison.Ordinal) >= 0)
{
ParameterInfo[] parameters = declaredMethod.GetParameters();
if (parameters.Length == 2 && parameters[0].ParameterType == typeof(string))
{
methodInfo = declaredMethod;
break;
}
}
}
if (methodInfo == null)
{
Plugin.Log.LogWarning((object)"Could not find the server chat handler, so /pay will not work.");
}
else
{
harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeFromHandle, "ChatPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
}
MethodInfo methodInfo2 = AccessTools.Method(typeof(DazedCommands), "IsServerCommand", (Type[])null, (Type[])null);
if (methodInfo2 == null)
{
Plugin.Log.LogWarning((object)"Could not find the command filter, so /pay may be blocked. Use !pay instead.");
}
else
{
harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeFromHandle, "IsServerCommandPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
}
MethodInfo methodInfo3 = AccessTools.Method(typeof(PlayerThinking), "ThinkingInput", (Type[])null, (Type[])null);
if (methodInfo3 == null)
{
Plugin.Log.LogWarning((object)"Could not find the journal input handler, so Tab may open the journal while you are typing.");
}
else
{
harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeFromHandle, "ThinkingInputPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
}
MethodInfo methodInfo4 = AccessTools.Method(typeof(OtherPlayer), "SetPlayerName", (Type[])null, (Type[])null);
if (methodInfo4 == null)
{
Plugin.Log.LogWarning((object)"OtherPlayer.SetPlayerName is missing, so balances will not appear under player names.");
}
else
{
harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "SetPlayerNamePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
}
}
private static bool ChatPrefix(string __0, NetworkConnection __1)
{
if (!InstanceFinder.IsServerStarted || !Plugin.EnablePayCommand.Value || !PayCommand.Looks(__0))
{
return true;
}
try
{
PayCommand.Handle(__0, __1);
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("A /pay command failed: " + ex));
}
return false;
}
private static bool IsServerCommandPrefix(string fullCommand, ref bool __result)
{
if (!Plugin.EnablePayCommand.Value || !PayCommand.Looks(fullCommand))
{
return true;
}
__result = false;
return false;
}
private static bool ThinkingInputPrefix()
{
return !ChatManager.IsTyping;
}
private static void SetPlayerNamePostfix()
{
Balances.RedrawAll();
}
private static bool SkipPrefix()
{
return false;
}
private static bool AddMoneyPrefix(int amount, Player player)
{
if (!InstanceFinder.IsServerStarted)
{
return false;
}
Wallets.Add(player, amount);
Refl.PlayMoneySounds(increase: true, player);
return false;
}
private static bool RemoveMoneyPrefix(int amount, Player player)
{
if (!InstanceFinder.IsServerStarted)
{
return false;
}
if (BoatCosts.Applies(PurchaseContext.Method))
{
BoatCosts.Charge(amount, player ?? PurchaseContext.Buyer);
return false;
}
Wallets.Remove(player, amount);
Refl.PlayMoneySounds(increase: false, player);
return false;
}
private static bool SellItemPrefix(Item item)
{
if (!InstanceFinder.IsServerStarted || (Object)(object)item == (Object)null)
{
return false;
}
Player val = Ownership.Resolve(item);
if ((Object)(object)val == (Object)null)
{
if (!Plugin.UnclaimedSalesGoToHost.Value)
{
Plugin.Log.LogInfo((object)$"Sold '{item.GetName()}' for {item.TotalWorth} but nobody had held it; income discarded.");
return false;
}
val = Player.LocalPlayer;
if ((Object)(object)val == (Object)null)
{
return false;
}
Plugin.Log.LogInfo((object)$"Sold '{item.GetName()}' for {item.TotalWorth} but nobody had held it; credited the host.");
}
if (Plugin.ShareBossMoney.Value && BossLoot.Includes(item))
{
ShareOut(item, val);
return false;
}
Wallets.Add(val, item.TotalWorth);
Refl.PlayMoneySounds(increase: true, val);
return false;
}
private static void SpawnBossTrophyPrefix(Item trophy, Item meat)
{
BossLoot.Learn(trophy);
BossLoot.Learn(meat);
}
private static void ShareOut(Item item, Player seller)
{
int totalWorth = item.TotalWorth;
List<Player> list = new List<Player>();
foreach (Player player in PlayerManager.Players)
{
if ((Object)(object)player != (Object)null && !((NetworkBehaviour)player).IsDeinitializing && Wallets.CanHoldMoney(player))
{
list.Add(player);
}
}
if (list.Count <= 1 || totalWorth <= 0)
{
Wallets.Add(seller, totalWorth);
Refl.PlayMoneySounds(increase: true, seller);
Plugin.Log.LogInfo((object)$"Boss drop '{item.GetName()}' sold for {totalWorth}, but there is only {list.Count} player here, so all of it went to the seller.");
return;
}
if (Plugin.DuplicateBossMoney.Value)
{
foreach (Player item2 in list)
{
Wallets.Add(item2, totalWorth);
Refl.PlayMoneySounds(increase: true, item2);
}
Plugin.Log.LogInfo((object)$"Boss drop '{item.GetName()}' sold for {totalWorth}, paid in full to each of {list.Count} player(s).");
return;
}
int num = totalWorth / list.Count;
int num2 = totalWorth - num * list.Count;
Player val = (list.Contains(seller) ? seller : list[0]);
foreach (Player item3 in list)
{
int num3 = num + (((Object)(object)item3 == (Object)(object)val) ? num2 : 0);
if (num3 > 0)
{
Wallets.Add(item3, num3);
Refl.PlayMoneySounds(increase: true, item3);
}
}
Plugin.Log.LogInfo((object)($"Boss drop '{item.GetName()}' sold for {totalWorth}, split {num} each between {list.Count} player(s)" + ((num2 > 0) ? $", with {num2} left over going to the seller." : ".")));
}
private static bool CanAffordPrefix(int cost, ref bool __result)
{
if (!InstanceFinder.IsServerStarted)
{
return true;
}
if (PurchaseContext.Active)
{
if (BoatCosts.Applies(PurchaseContext.Method))
{
__result = BoatCosts.CanEveryoneAfford(cost, PurchaseContext.Buyer, out var reason);
if (!__result)
{
Plugin.Log.LogInfo((object)$"Boat purchase of {cost} refused because the cost is shared and somebody cannot pay: {reason}.");
}
return false;
}
__result = cost <= Wallets.Get(PurchaseContext.Buyer);
return false;
}
if (PurchaseContext.InsidePurchase)
{
__result = false;
Plugin.Log.LogWarning((object)"A purchase arrived with no identifiable buyer, so it was refused rather than given away.");
return false;
}
return true;
}
private static void SpendPrefix(object[] __args, MethodBase __originalMethod)
{
PurchaseContext.Enter(ResolveBuyer(__args), MethodNameOf(__originalMethod));
}
private static string MethodNameOf(MethodBase method)
{
if (method == null)
{
return null;
}
string[] spendMethods = SpendMethods;
foreach (string text in spendMethods)
{
if (method.Name.IndexOf(text, StringComparison.Ordinal) >= 0)
{
return text;
}
}
return method.Name;
}
private static void SpendFinalizer()
{
PurchaseContext.Exit();
}
private static Player ResolveBuyer(object[] args)
{
if (args == null)
{
return null;
}
object[] array = args;
foreach (object obj in array)
{
Player val = (Player)((obj is Player) ? obj : null);
if (val != null)
{
return val;
}
}
array = args;
foreach (object obj2 in array)
{
Item val2 = (Item)((obj2 is Item) ? obj2 : null);
if (val2 != null && (Object)(object)val2 != (Object)null)
{
Player val3 = val2.Holder;
if ((Object)(object)val3 == (Object)null)
{
val3 = val2.SyncedHolder;
}
if ((Object)(object)val3 == (Object)null)
{
val3 = val2.LastHolder;
}
if ((Object)(object)val3 != (Object)null)
{
return val3;
}
}
}
Plugin.Log.LogWarning((object)"Could not work out who is buying; the purchase will be refused.");
return null;
}
private static void SaveServerPrefix(out int __state)
{
__state = Refl.GetStaticMoney();
if (InstanceFinder.IsServerStarted && Wallets.SafeToPersist)
{
Refl.SetStaticMoney(Wallets.PersistedTotal());
}
}
private static void SaveServerFinalizer(int __state)
{
Refl.SetStaticMoney(__state);
if (InstanceFinder.IsServerStarted)
{
Wallets.Save();
}
}
private static void OnStartServerPostfix()
{
Wallets.LoadForServer();
}
private static void OnStartClientPostfix()
{
ClientMoney.Reset();
Net.TryRegisterHandlers();
Net.SendHandshake();
}
private static void OnStopClientPostfix()
{
ClientMoney.Reset();
if (!InstanceFinder.IsServerStarted)
{
Wallets.Save();
Wallets.OnServerStopped();
}
}
}
internal static class PayCommand
{
public const string Prefix = "/pay";
private static readonly string[] Prefixes = new string[2] { "/pay", "!pay" };
public static bool Looks(string message)
{
if (string.IsNullOrEmpty(message))
{
return false;
}
string text = message.TrimStart();
string[] prefixes = Prefixes;
foreach (string text2 in prefixes)
{
if (text.StartsWith(text2, StringComparison.OrdinalIgnoreCase) && (text.Length == text2.Length || char.IsWhiteSpace(text[text2.Length])))
{
return true;
}
}
return false;
}
public static void Handle(string message, NetworkConnection sender)
{
Player val = SenderOf(sender);
if ((Object)(object)val == (Object)null)
{
Announce(sender, "Could not work out who sent that.");
return;
}
string[] array = (message ?? "").Trim().Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (array.Length < 3)
{
Announce(sender, "Usage: /pay <name or steam id> <amount> (or !pay)");
return;
}
string text = array[^1];
string text2 = string.Join(" ", array, 1, array.Length - 2);
if (!int.TryParse(text, out var result))
{
Announce(sender, "'" + text + "' is not a number.");
return;
}
if (result <= 0)
{
Announce(sender, "You can only send a positive amount.");
return;
}
Player val2 = Find(text2, val);
if ((Object)(object)val2 == (Object)null)
{
Announce(sender, "No player here matches '" + text2 + "'.");
return;
}
if ((Object)(object)val2 == (Object)(object)val)
{
Announce(sender, "You cannot pay yourself.");
return;
}
if (!Wallets.CanHoldMoney(val2))
{
Announce(sender, "That player cannot be paid right now.");
return;
}
int num = Wallets.Get(val);
if (result > num)
{
Announce(sender, "You only have $" + num + ".");
return;
}
Wallets.Remove(val, result);
Wallets.Add(val2, result);
Refl.PlayMoneySounds(increase: false, val);
Refl.PlayMoneySounds(increase: true, val2);
string text3 = NameOf(val) + " paid " + NameOf(val2) + " $" + result + ".";
Broadcast(text3);
Plugin.Log.LogInfo((object)text3);
}
private static Player SenderOf(NetworkConnection sender)
{
if (sender == (NetworkConnection)null)
{
return Player.LocalPlayer;
}
foreach (Player player in PlayerManager.Players)
{
if ((Object)(object)player != (Object)null && ((NetworkBehaviour)player).Owner == sender)
{
return player;
}
}
return null;
}
private static Player Find(string text, Player asking)
{
if (string.IsNullOrEmpty(text))
{
return null;
}
text = text.Trim();
if (ulong.TryParse(text, out var result) && result != 0)
{
foreach (Player player in PlayerManager.Players)
{
if ((Object)(object)player != (Object)null && player.SteamID == result)
{
return player;
}
}
return null;
}
List<Player> list = new List<Player>();
foreach (Player player2 in PlayerManager.Players)
{
if ((Object)(object)player2 == (Object)null || (Object)(object)player2 == (Object)(object)asking)
{
continue;
}
string steamName = player2.SteamName;
if (!string.IsNullOrEmpty(steamName))
{
if (string.Equals(steamName, text, StringComparison.OrdinalIgnoreCase))
{
return player2;
}
if (steamName.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
{
list.Add(player2);
}
}
}
if (list.Count != 1)
{
return null;
}
return list[0];
}
private static string NameOf(Player player)
{
if ((Object)(object)player == (Object)null)
{
return "someone";
}
string steamName = player.SteamName;
if (!string.IsNullOrEmpty(steamName))
{
return steamName;
}
return "a player";
}
private static void Announce(NetworkConnection sender, string text)
{
Broadcast(text);
Plugin.Log.LogInfo((object)("/pay: " + text));
}
private static void Broadcast(string text)
{
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
try
{
if ((Object)(object)OnlineChatManager.Instance != (Object)null)
{
OnlineChatManager.Instance.SendChatMessage(SteamManager.CurrentLobbyID.m_SteamID, text);
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not announce a payment in chat: " + ex.Message));
}
}
}
internal static class PayComplete
{
private static FieldInfo _instanceField;
private static FieldInfo _inputField;
private static bool _resolved;
private static string _stem;
private static string _lastWritten;
private static int _next;
public static void Reset()
{
_stem = null;
_lastWritten = null;
_next = 0;
}
private static bool Resolve()
{
if (_resolved)
{
if (_instanceField != null)
{
return _inputField != null;
}
return false;
}
_resolved = true;
_instanceField = AccessTools.Field(typeof(ChatManager), "_instance");
_inputField = AccessTools.Field(typeof(ChatManager), "_chatInputField");
if (_instanceField == null || _inputField == null)
{
Plugin.Log.LogWarning((object)"The chat input box could not be reached, so tab completion for /pay is off.");
}
if (_instanceField != null)
{
return _inputField != null;
}
return false;
}
private static TMP_InputField Field()
{
object value = _instanceField.GetValue(null);
if (value != null)
{
object? value2 = _inputField.GetValue(value);
return (TMP_InputField)((value2 is TMP_InputField) ? value2 : null);
}
return null;
}
public static void Tick()
{
if (!Plugin.EnablePayCommand.Value || !ChatManager.IsTyping || !Input.GetKeyDown((KeyCode)9) || !Resolve())
{
return;
}
try
{
Complete();
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Tab completion failed: " + ex.Message));
}
}
private static void Complete()
{
TMP_InputField val = Field();
if ((Object)(object)val == (Object)null)
{
return;
}
string text = val.text ?? "";
if (!PayCommand.Looks(text))
{
return;
}
string[] array = text.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
bool flag = text.EndsWith(" ", StringComparison.Ordinal);
if (array.Length > 2 || (array.Length == 2 && flag && !IsCycle(text)))
{
return;
}
string stem = ((array.Length == 2 && !flag) ? array[1] : "");
if (IsCycle(text))
{
stem = _stem ?? "";
}
else
{
_stem = stem;
_next = 0;
}
List<string> list = Candidates(stem);
if (list.Count != 0)
{
if (_next >= list.Count)
{
_next = 0;
}
string text2 = list[_next];
_next++;
string text3 = (val.text = "/pay " + text2 + " ");
val.caretPosition = text3.Length;
val.stringPosition = text3.Length;
_lastWritten = text3;
if (list.Count > 1)
{
Plugin.Log.LogInfo((object)("Tab completion matches: " + string.Join(", ", list.ToArray())));
}
}
}
private static bool IsCycle(string text)
{
if (_lastWritten != null)
{
return string.Equals(text, _lastWritten, StringComparison.Ordinal);
}
return false;
}
private static List<string> Candidates(string stem)
{
List<string> list = new List<string>();
foreach (Player player in PlayerManager.Players)
{
if (!((Object)(object)player == (Object)null) && !((Object)(object)player == (Object)(object)Player.LocalPlayer))
{
string steamName = player.SteamName;
if (!string.IsNullOrEmpty(steamName) && (string.IsNullOrEmpty(stem) || steamName.IndexOf(stem, StringComparison.OrdinalIgnoreCase) >= 0))
{
list.Add(steamName);
}
}
}
list.Sort(StringComparer.OrdinalIgnoreCase);
return list;
}
}
[BepInPlugin("dazed.howtofish.nosharedmoney", "NoSharedMoney", "1.4.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BaseUnityPlugin
{
public const string Guid = "dazed.howtofish.nosharedmoney";
public const string Name = "NoSharedMoney";
public const string Version = "1.4.0";
public static ManualLogSource Log;
public static ConfigEntry<int> StartingMoney;
public static ConfigEntry<bool> WarnInChat;
public static ConfigEntry<bool> UnclaimedSalesGoToHost;
public static ConfigEntry<bool> ShareBossMoney;
public static ConfigEntry<bool> DuplicateBossMoney;
public static ConfigEntry<bool> SplitBoatCosts;
public static ConfigEntry<bool> ShowBalancesOnNameplates;
public static ConfigEntry<bool> EnablePayCommand;
private Harmony _harmony;
private float _nextReconcile;
private static bool _connectionsHooked;
private void Awake()
{
//IL_0132: Unknown result type (might be due to invalid IL or missing references)
//IL_013c: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
StartingMoney = ((BaseUnityPlugin)this).Config.Bind<int>("General", "StartingMoney", 0, "How much money a player who has never played this save starts with.");
WarnInChat = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "WarnInChat", true, "Host only: announce in chat when a connected player is missing the mod.");
UnclaimedSalesGoToHost = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "UnclaimedSalesGoToHost", true, "If something is sold that no player ever held, credit the host instead of discarding the money.");
ShareBossMoney = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShareBossMoney", true, "Split the money from anything a boss or mini boss drops, including its meat, evenly between everyone in the session when it is sold. Everything else still pays only the person who sold it. Only the host's setting is used.");
DuplicateBossMoney = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DuplicateBossMoney", false, "Changes ShareBossMoney from splitting to duplicating: every player is paid the full sale price instead of a share of it. This creates new money rather than dividing it. Ignored when ShareBossMoney is off. Only the host's setting is used.");
SplitBoatCosts = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "SplitBoatCosts", false, "Split the cost of boat upgrades, the motor and the radar, evenly between everyone in the session instead of one player paying the whole thing. Everyone must be able to afford their share or the purchase is refused. Only the host's setting is used.");
ShowBalancesOnNameplates = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowBalancesOnNameplates", true, "Show each player's balance under their name above their head. This is your own setting and only changes what you see.");
EnablePayCommand = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnablePayCommand", true, "Allow /pay <name or steam id> <amount> in chat to send money to another player. Only the host's setting is used.");
if (!Refl.Resolve())
{
Log.LogError((object)"Aborting: the game's money internals do not look the way this mod expects.");
return;
}
BackupLink.TryRegister();
Net.RegisterSerializers();
_harmony = new Harmony("dazed.howtofish.nosharedmoney");
Patches.ApplyAll(_harmony);
Server.OnServerStopped += OnServerStopped;
Log.LogInfo((object)"NoSharedMoney 1.4.0 loaded. Money is now per-player; every player needs this mod installed.");
}
private void OnDestroy()
{
Server.OnServerStopped -= OnServerStopped;
if (_connectionsHooked && (Object)(object)InstanceFinder.ServerManager != (Object)null)
{
InstanceFinder.ServerManager.OnRemoteConnectionState -= OnRemoteConnectionState;
}
_connectionsHooked = false;
Harmony harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
}
private static void HookConnections()
{
if (!_connectionsHooked && !((Object)(object)InstanceFinder.ServerManager == (Object)null))
{
_connectionsHooked = true;
InstanceFinder.ServerManager.OnRemoteConnectionState += OnRemoteConnectionState;
}
}
private static void OnRemoteConnectionState(NetworkConnection conn, RemoteConnectionStateArgs args)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
if ((int)args.ConnectionState == 0)
{
Wallets.Forget(conn);
}
}
private static void OnServerStopped()
{
Wallets.Save();
Wallets.OnServerStopped();
}
private void Update()
{
Net.TryRegisterHandlers();
HookConnections();
PayComplete.Tick();
if (!(Time.unscaledTime < _nextReconcile))
{
_nextReconcile = Time.unscaledTime + 0.25f;
if (InstanceFinder.IsServerStarted)
{
BossLoot.Scan();
Wallets.Reconcile();
}
}
}
}
public static class Refl
{
private static FieldInfo _moneyBackingField;
private static FieldInfo _onItemSoldField;
private static MethodInfo _moneySound;
private static MethodInfo _observerMoneySound;
public static bool Resolve()
{
bool flag = true;
_moneyBackingField = AccessTools.Field(typeof(MoneyManager), "<Money>k__BackingField");
flag &= Require(_moneyBackingField, "MoneyManager.<Money>k__BackingField");
_onItemSoldField = AccessTools.Field(typeof(MoneyManager), "OnItemSold");
flag &= Require(_onItemSoldField, "MoneyManager.OnItemSold");
_moneySound = AccessTools.Method(typeof(MoneyManager), "MoneySound", new Type[2]
{
typeof(bool),
typeof(Player)
}, (Type[])null);
flag &= Require(_moneySound, "MoneyManager.MoneySound");
_observerMoneySound = AccessTools.Method(typeof(MoneyManager), "ObserverMoneySound", new Type[2]
{
typeof(bool),
typeof(Player)
}, (Type[])null);
return flag & Require(_observerMoneySound, "MoneyManager.ObserverMoneySound");
}
private static bool Require(MemberInfo member, string name)
{
if (member != null)
{
return true;
}
Plugin.Log.LogError((object)("Could not find " + name + ". The game has probably updated; NoSharedMoney will not work correctly."));
return false;
}
public static void SetStaticMoney(int value)
{
_moneyBackingField?.SetValue(null, value);
}
public static int GetStaticMoney()
{
if (_moneyBackingField == null)
{
return 0;
}
return (int)_moneyBackingField.GetValue(null);
}
public static void RaiseOnItemSold()
{
(_onItemSoldField?.GetValue(null) as Action)?.Invoke();
}
public static void PlayMoneySounds(bool increase, Player player)
{
MoneyManager instance = MoneyManager.Instance;
if ((Object)(object)instance == (Object)null || (Object)(object)player == (Object)null)
{
return;
}
object[] parameters = new object[2] { increase, player };
try
{
_observerMoneySound?.Invoke(instance, parameters);
_moneySound?.Invoke(instance, parameters);
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Money sound failed: " + ex.Message));
}
}
}
public static class Wallets
{
private const ulong SyntheticKeyBase = 18446462598732840960uL;
private static readonly Dictionary<ulong, int> _wallets = new Dictionary<ulong, int>();
private static readonly Dictionary<ulong, int> _lastSent = new Dictionary<ulong, int>();
private static readonly HashSet<int> _moddedClientIds = new HashSet<int>();
private static readonly Dictionary<int, float> _firstSeen = new Dictionary<int, float>();
private static readonly HashSet<int> _warnedClientIds = new HashSet<int>();
private static string _saveName;
private static int _unassignedPot;
private static bool _loaded;
private static bool _readOnly;
private static bool _warnedAboutSteam;
private static string _lastBalances = "";
public static bool SafeToPersist
{
get
{
if (_loaded)
{
return !_readOnly;
}
return false;
}
}
public static bool IsModded(NetworkConnection conn)
{
if (conn != (NetworkConnection)null)
{
return _moddedClientIds.Contains(conn.ClientId);
}
return false;
}
public static void LoadForServer()
{
_wallets.Clear();
_lastSent.Clear();
_moddedClientIds.Clear();
_firstSeen.Clear();
_warnedClientIds.Clear();
_unassignedPot = 0;
_loaded = true;
_readOnly = false;
_saveName = ((SaveManager.CurServerSave != null) ? SaveManager.CurServerSave.Name : null);
bool unreadable;
WalletFile walletFile = WalletStore.Load(_saveName, out unreadable);
if (unreadable)
{
_readOnly = true;
Plugin.Log.LogError((object)("The wallet file for '" + _saveName + "' exists but could not be read. Nobody's money has been shared out and the file will not be overwritten, so it can still be recovered. Restore this save from SaveBackups, or fix the file by hand, then reload."));
}
else if (walletFile != null)
{
foreach (WalletEntry wallet in walletFile.Wallets)
{
if (ulong.TryParse(wallet.SteamID, out var result) && result != 0L)
{
_wallets[result] = Mathf.Max(0, wallet.Money);
}
}
_unassignedPot = Mathf.Max(0, walletFile.UnassignedPot);
if (_unassignedPot > 0)
{
Plugin.Log.LogInfo((object)$"Carrying over an unclaimed balance of {_unassignedPot} for the next player to join.");
}
Plugin.Log.LogInfo((object)$"Loaded {_wallets.Count} individual wallet(s) totalling {Total()} for save '{_saveName}'.");
}
else
{
MigrateSharedPot();
Save();
}
}
private static void MigrateSharedPot()
{
int num = ((SaveManager.CurServerSave != null) ? SaveManager.CurServerSave.Money : 0);
if (num <= 0)
{
Plugin.Log.LogInfo((object)"No shared balance to migrate; everyone starts from the configured amount.");
return;
}
List<ulong> list = new List<ulong>();
if (SaveManager.CurServerSave != null && SaveManager.CurServerSave.Players != null)
{
foreach (SavedPlayer player in SaveManager.CurServerSave.Players)
{
if (player != null && player.SteamID != 0L && !list.Contains(player.SteamID))
{
list.Add(player.SteamID);
}
}
}
if (list.Count == 0)
{
_unassignedPot = num;
Plugin.Log.LogInfo((object)$"Migrating shared balance of {num}: no player records in the save, so it goes to the first player who joins.");
return;
}
int num2 = num / list.Count;
int num3 = num - num2 * list.Count;
for (int i = 0; i < list.Count; i++)
{
_wallets[list[i]] = num2 + ((i == 0) ? num3 : 0);
}
Plugin.Log.LogInfo((object)$"Migrated shared balance of {num} evenly across {list.Count} saved player(s) ({num2} each, remainder {num3} to the first).");
}
public static void OnServerStopped()
{
Ownership.Clear();
_loaded = false;
_wallets.Clear();
_lastSent.Clear();
_moddedClientIds.Clear();
_firstSeen.Clear();
_warnedClientIds.Clear();
_saveName = null;
_unassignedPot = 0;
}
private static ulong KeyFor(Player player)
{
if ((Object)(object)player == (Object)null)
{
return 0uL;
}
if (!SteamIdentitiesAreReal())
{
NetworkConnection owner = ((NetworkBehaviour)player).Owner;
if (owner != (NetworkConnection)null && owner.ClientId >= 0)
{
return (ulong)(-281474976710656L | (uint)owner.ClientId);
}
return 0uL;
}
ulong steamID = player.SteamID;
if (steamID != 0L)
{
return steamID;
}
NetworkConnection owner2 = ((NetworkBehaviour)player).Owner;
if (owner2 != (NetworkConnection)null && owner2.ClientId >= 0)
{
return (ulong)(-281474976710656L | (uint)owner2.ClientId);
}
return 0uL;
}
private static bool SteamIdentitiesAreReal()
{
try
{
if (ConnectionManager.IsUsingSteam)
{
return true;
}
if (!_warnedAboutSteam)
{
_warnedAboutSteam = true;
Plugin.Log.LogWarning((object)"This session is not using Steam, so the game gives every player the host's Steam id. Wallets are keyed per connection instead, which works for this session but cannot be saved between sessions.");
}
return false;
}
catch (Exception)
{
return true;
}
}
public static bool CanHoldMoney(Player player)
{
return KeyFor(player) != 0;
}
private static bool IsSynthetic(ulong key)
{
return (key & 0xFFFF000000000000uL) == 18446462598732840960uL;
}
public static int Get(Player player)
{
ulong num = KeyFor(player);
if (num == 0L)
{
return 0;
}
return EnsureWallet(num);
}
private static int EnsureWallet(ulong key)
{
if (_wallets.TryGetValue(key, out var value))
{
return value;
}
int num = Plugin.StartingMoney.Value;
if (_unassignedPot > 0)
{
num += _unassignedPot;
Plugin.Log.LogInfo((object)$"Handed the migrated shared balance of {_unassignedPot} to the first joining player.");
_unassignedPot = 0;
}
num = Mathf.Max(0, num);
_wallets[key] = num;
return num;
}
public static void Add(Player player, int amount)
{
Set(player, Get(player) + Mathf.Abs(amount));
}
public static void Remove(Player player, int amount)
{
Set(player, Get(player) - Mathf.Abs(amount));
}
public static void Set(Player player, int value)
{
ulong num = KeyFor(player);
if (num == 0L)
{
Plugin.Log.LogWarning((object)"Tried to change the wallet of a player with no identity; ignoring.");
return;
}
EnsureWallet(num);
_wallets[num] = Mathf.Clamp(value, 0, int.MaxValue);
Push(player);
}
public static int Total()
{
int num = 0;
foreach (int value in _wallets.Values)
{
num += value;
}
return num;
}
public static int PersistedTotal()
{
int num = _unassignedPot;
foreach (KeyValuePair<ulong, int> wallet in _wallets)
{
if (!IsSynthetic(wallet.Key))
{
num += wallet.Value;
}
}
return num;
}
public static void Push(Player player)
{
ulong num = KeyFor(player);
if (num == 0L || !_wallets.TryGetValue(num, out var value) || (_lastSent.TryGetValue(num, out var value2) && value2 == value))
{
return;
}
if ((Object)(object)Player.LocalPlayer == (Object)(object)player)
{
_lastSent[num] = value;
ClientMoney.Apply(value);
return;
}
NetworkConnection owner = ((NetworkBehaviour)player).Owner;
if (!(owner == (NetworkConnection)null) && IsModded(owner))
{
_lastSent[num] = value;
Net.SendMoney(owner, value);
}
}
public static void Forget(NetworkConnection conn)
{
if (conn == (NetworkConnection)null)
{
return;
}
_moddedClientIds.Remove(conn.ClientId);
_warnedClientIds.Remove(conn.ClientId);
_firstSeen.Remove(conn.ClientId);
foreach (Player player in PlayerManager.Players)
{
if ((Object)(object)player != (Object)null && ((NetworkBehaviour)player).Owner == conn)
{
_lastSent.Remove(KeyFor(player));
break;
}
}
ulong num = SteamIdOf(conn);
if (num != 0L)
{
_lastSent.Remove(num);
}
_lastSent.Remove((ulong)(-281474976710656L | (uint)conn.ClientId));
}
private static ulong SteamIdOf(NetworkConnection conn)
{
try
{
ulong result;
return ulong.TryParse(conn.GetAddress(), out result) ? result : 0;
}
catch (Exception)
{
return 0uL;
}
}
public static void MarkModded(NetworkConnection conn, int version)
{
if (conn == (NetworkConnection)null)
{
return;
}
if (version != 1)
{
Plugin.Log.LogWarning((object)$"Client {conn.ClientId} runs NoSharedMoney protocol {version}, this server is {1}. Wallet sync may misbehave; use matching mod versions.");
}
if (_moddedClientIds.Add(conn.ClientId))
{
Plugin.Log.LogInfo((object)$"Client {conn.ClientId} has NoSharedMoney installed.");
}
foreach (Player player in PlayerManager.Players)
{
if ((Object)(object)player != (Object)null && ((NetworkBehaviour)player).Owner == conn)
{
_lastSent.Remove(KeyFor(player));
Push(player);
break;
}
}
}
public static void PushBalances()
{
if (!InstanceFinder.IsServerStarted)
{
return;
}
List<ulong> list = new List<ulong>();
List<int> list2 = new List<int>();
foreach (Player player in PlayerManager.Players)
{
if (!((Object)(object)player == (Object)null) && !((NetworkBehaviour)player).IsDeinitializing)
{
ulong steamID = player.SteamID;
if (steamID != 0L)
{
list.Add(steamID);
list2.Add(Get(player));
}
}
}
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < list.Count; i++)
{
stringBuilder.Append(list[i]).Append('=').Append(list2[i])
.Append(';');
}
string text = stringBuilder.ToString();
if (!(text == _lastBalances))
{
_lastBalances = text;
Net.PushBalances(list.ToArray(), list2.ToArray());
Balances.Apply(list.ToArray(), list2.ToArray());
}
}
public static void Reconcile()
{
if (!_loaded || !InstanceFinder.IsServerStarted)
{
return;
}
PushBalances();
List<Player> players = PlayerManager.Players;
if (players == null)
{
return;
}
for (int i = 0; i < players.Count; i++)
{
Player val = players[i];
if ((Object)(object)val == (Object)null || ((NetworkBehaviour)val).IsDeinitializing)
{
continue;
}
ulong num = KeyFor(val);
if (num == 0L)
{
continue;
}
EnsureWallet(num);
Push(val);
NetworkConnection owner = ((NetworkBehaviour)val).Owner;
if (owner == (NetworkConnection)null || (Object)(object)val == (Object)(object)Player.LocalPlayer)
{
continue;
}
if (!_firstSeen.ContainsKey(owner.ClientId))
{
_firstSeen[owner.ClientId] = Time.time;
}
if (!IsModded(owner) && Time.time - _firstSeen[owner.ClientId] > 10f && _warnedClientIds.Add(owner.ClientId))
{
string text = "'" + val.SteamName + "' does not have NoSharedMoney installed. Their money display will be wrong and they will not be able to buy anything.";
Plugin.Log.LogWarning((object)text);
if (Plugin.WarnInChat.Value)
{
ChatManager.ChatMessage("<i>[NoSharedMoney] " + text + "</i>");
}
}
}
}
public static void Save()
{
if (!_loaded || string.IsNullOrEmpty(_saveName))
{
return;
}
if (_readOnly)
{
Plugin.Log.LogWarning((object)"Not writing wallets: the existing file could not be read and overwriting it would destroy the only copy.");
return;
}
WalletFile walletFile = new WalletFile
{
UnassignedPot = _unassignedPot
};
foreach (KeyValuePair<ulong, int> wallet in _wallets)
{
if (!IsSynthetic(wallet.Key))
{
walletFile.Wallets.Add(new WalletEntry
{
SteamID = wallet.Key.ToString(),
Money = wallet.Value
});
}
}
WalletStore.Save(_saveName, walletFile);
Plugin.Log.LogInfo((object)$"Saved {walletFile.Wallets.Count} wallet(s) totalling {Total()} for save '{_saveName}'.");
}
}
[Serializable]
public class WalletEntry
{
public string SteamID;
public int Money;
}
[Serializable]
public class WalletFile
{
public List<WalletEntry> Wallets = new List<WalletEntry>();
public int UnassignedPot;
}
public static class WalletStore
{
private static string Folder => Path.Combine(Application.persistentDataPath, "NoSharedMoney");
private static string PathFor(string saveName)
{
char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
foreach (char oldChar in invalidFileNameChars)
{
saveName = saveName.Replace(oldChar, '_');
}
return Path.Combine(Folder, saveName + ".json");
}
public static string PublicPathFor(string saveName)
{
if (!string.IsNullOrEmpty(saveName))
{
return PathFor(saveName);
}
return null;
}
public static string PathForSanitised(string sanitisedName)
{
if (!string.IsNullOrEmpty(sanitisedName))
{
return Path.Combine(Folder, sanitisedName + ".json");
}
return null;
}
private static void WriteAtomic(string path, string content)
{
string text = path + ".writing";
File.WriteAllText(text, content);
if (!File.Exists(path))
{
File.Move(text, path);
return;
}
try
{
File.Replace(text, path, null);
}
catch (Exception)
{
File.Delete(path);
File.Move(text, path);
}
}
public static WalletFile Load(string saveName, out bool unreadable)
{
unreadable = false;
if (string.IsNullOrEmpty(saveName))
{
return null;
}
string path = PathFor(saveName);
try
{
if (!File.Exists(path))
{
return null;
}
WalletFile walletFile = JsonConvert.DeserializeObject<WalletFile>(File.ReadAllText(path));
if (walletFile == null)
{
unreadable = true;
return null;
}
if (walletFile.Wallets == null)
{
walletFile.Wallets = new List<WalletEntry>();
}
return walletFile;
}
catch (Exception ex)
{
unreadable = true;
Plugin.Log.LogError((object)("Failed to read wallets: " + ex));
return null;
}
}
public static void Save(string saveName, WalletFile file)
{
if (string.IsNullOrEmpty(saveName))
{
return;
}
try
{
Directory.CreateDirectory(Folder);
string text = JsonConvert.SerializeObject((object)file, (Formatting)1);
if (string.IsNullOrEmpty(text) || text == "{}")
{
Plugin.Log.LogError((object)"Wallet serialization produced nothing; refusing to overwrite the file.");
}
else
{
WriteAtomic(PathFor(saveName), text);
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Failed to write wallets: " + ex));
}
}
}
}