Decompiled source of Valheim Donations v5.1.0

ValheimDonationSystem.dll

Decompiled 13 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using HarmonyLib;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
using UnityEngine.Networking;

[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: AssemblyCompany("ValheimDonationSystem")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+1afeb5969f5b2c052d17afc7d227ef32cdd445b1")]
[assembly: AssemblyProduct("ValheimDonationSystem")]
[assembly: AssemblyTitle("ValheimDonationSystem")]
[assembly: AssemblyVersion("1.0.0.0")]
public static class BackendClient
{
	public delegate void Callback<T>(bool ok, T result, string error);

	public static IEnumerator Get<T>(string path, Callback<T> cb)
	{
		return Send("GET", path, null, cb);
	}

	public static IEnumerator Post<T>(string path, object body, Callback<T> cb)
	{
		return Send("POST", path, body, cb);
	}

	private static IEnumerator Send<T>(string method, string path, object body, Callback<T> cb)
	{
		if (!Config.Ready)
		{
			cb?.Invoke(ok: false, default(T), "backend not configured (valcoin_config.json missing backend_url/plugin_token)");
			yield break;
		}
		string text = Config.BackendUrl.TrimEnd(new char[1] { '/' }) + path;
		UnityWebRequest req = new UnityWebRequest(text, method);
		try
		{
			req.timeout = 15;
			req.SetRequestHeader("Authorization", "Bearer " + Config.PluginToken);
			req.SetRequestHeader("Accept", "application/json");
			req.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
			if (body != null)
			{
				string s = JsonConvert.SerializeObject(body);
				byte[] bytes = Encoding.UTF8.GetBytes(s);
				req.uploadHandler = (UploadHandler)new UploadHandlerRaw(bytes);
				req.SetRequestHeader("Content-Type", "application/json");
			}
			yield return req.SendWebRequest();
			if ((int)req.result != 1)
			{
				if (cb != null)
				{
					object arg = (int)req.responseCode;
					string error = req.error;
					DownloadHandler downloadHandler = req.downloadHandler;
					cb(ok: false, default(T), $"{arg} {error}: {((downloadHandler != null) ? downloadHandler.text : null)}");
				}
				yield break;
			}
			T result;
			try
			{
				string text2 = req.downloadHandler.text;
				result = (string.IsNullOrEmpty(text2) ? default(T) : JsonConvert.DeserializeObject<T>(text2));
			}
			catch (Exception ex)
			{
				cb?.Invoke(ok: false, default(T), "json parse failed: " + ex.Message);
				yield break;
			}
			cb?.Invoke(ok: true, result, null);
		}
		finally
		{
			((IDisposable)req)?.Dispose();
		}
	}
}
public static class Catalog
{
	public class Sku
	{
		public string Id;

		public string Name;

		public string Description;

		public int Price;

		public string Effect;

		public string Perk;

		public int Charges = 1;

		public string Item;

		public int WeeklyCap;

		public string RequiresBoss;
	}

	private static readonly string CatalogPath = Path.Combine(Paths.ConfigPath, "valcoin_shop.yaml");

	private static readonly Regex KvRe = new Regex("^\\s*([a-zA-Z_]+)\\s*:\\s*(.*)$", RegexOptions.Compiled);

	private static readonly Regex SkuRe = new Regex("^  ([a-z0-9_]+)\\s*:\\s*$", RegexOptions.Compiled);

	private static readonly Regex FieldRe = new Regex("^    ([a-zA-Z_]+)\\s*:\\s*(.*)$", RegexOptions.Compiled);

	public static Dictionary<string, Sku> Items { get; private set; } = new Dictionary<string, Sku>();

	public static List<Sku> Order { get; private set; } = new List<Sku>();

	public static void Load()
	{
		EnsureFile();
		try
		{
			Parse(File.ReadAllLines(CatalogPath));
			Debug.Log((object)$"[Valcoin] Shop catalog loaded: {Items.Count} SKU(s).");
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[Valcoin] Failed to parse shop catalog: " + ex.Message));
			Items = new Dictionary<string, Sku>();
			Order = new List<Sku>();
		}
	}

	private static void EnsureFile()
	{
		if (File.Exists(CatalogPath))
		{
			return;
		}
		try
		{
			File.WriteAllText(CatalogPath, "# Valcoin shop catalog\n# -----------------------------------------------------------------------\n# Each SKU has:\n#   name:          what shows in /shop\n#   description:   helper text\n#   price:         Valcoin cost\n#   effect:        grant_perk | add_charges | grant_item\n#   perk:          (perk effects) identifier the plugin understands\n#   charges:       (add_charges) how many uses each purchase grants\n#   item:          (grant_item) comma list of \"prefab\" or \"prefab:qty\"\n#   weekly_cap:    (grant_item) max purchases per player per week (0 = unlimited)\n#   requires_boss: (grant_item) global boss key gate, e.g. defeated_bonemass\n# A full ecosystem-aware catalog is in examples/valcoin_shop.example.yaml.\n# Edit and restart the server to apply changes.\n\nshop:\n  donor_badge:\n    name: \"Donor Badge\"\n    description: \"A star next to your name in chat. Forever.\"\n    price: 500\n    effect: grant_perk\n    perk: donor_badge\n\n  chat_title:\n    name: \"Chat Title\"\n    description: \"Unlocks /title <name> to set a custom prefix (e.g. [Jarl])\"\n    price: 1500\n    effect: grant_perk\n    perk: chat_title\n\n  companion_flair:\n    name: \"Patron's Companion Flair\"\n    description: \"A donor-only badge colour and name style on your Dvergr companions. Cosmetic only.\"\n    price: 2000\n    effect: grant_perk\n    perk: companion_flair\n");
			Debug.LogWarning((object)("[Valcoin] Created shop catalog template at " + CatalogPath));
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[Valcoin] Could not write catalog template: " + ex.Message));
		}
	}

	private static void Parse(string[] lines)
	{
		Dictionary<string, Sku> items = new Dictionary<string, Sku>();
		List<Sku> order = new List<Sku>();
		bool flag = false;
		Sku sku = null;
		foreach (string text in lines)
		{
			if (string.IsNullOrWhiteSpace(text) || text.TrimStart(Array.Empty<char>()).StartsWith("#"))
			{
				continue;
			}
			if (!flag)
			{
				if (Regex.IsMatch(text, "^shop\\s*:\\s*$"))
				{
					flag = true;
				}
				continue;
			}
			Match match = SkuRe.Match(text);
			if (match.Success)
			{
				if (sku != null)
				{
					Commit(sku, items, order);
				}
				sku = new Sku
				{
					Id = match.Groups[1].Value
				};
				continue;
			}
			Match match2 = FieldRe.Match(text);
			if (sku != null && match2.Success)
			{
				string value = match2.Groups[1].Value;
				string text2 = StripQuotes(match2.Groups[2].Value.Trim());
				switch (value)
				{
				case "name":
					sku.Name = text2;
					break;
				case "description":
					sku.Description = text2;
					break;
				case "price":
					int.TryParse(text2, out sku.Price);
					break;
				case "effect":
					sku.Effect = text2;
					break;
				case "perk":
					sku.Perk = text2;
					break;
				case "charges":
					int.TryParse(text2, out sku.Charges);
					break;
				case "item":
					sku.Item = text2;
					break;
				case "weekly_cap":
					int.TryParse(text2, out sku.WeeklyCap);
					break;
				case "requires_boss":
					sku.RequiresBoss = text2;
					break;
				}
			}
			else if (text.Length > 0 && text[0] != ' ' && KvRe.IsMatch(text))
			{
				break;
			}
		}
		if (sku != null)
		{
			Commit(sku, items, order);
		}
		Items = items;
		Order = order;
	}

	private static void Commit(Sku s, Dictionary<string, Sku> items, List<Sku> order)
	{
		if (string.IsNullOrEmpty(s.Id) || string.IsNullOrEmpty(s.Effect))
		{
			return;
		}
		if (s.Effect == "grant_item")
		{
			if (string.IsNullOrEmpty(s.Item))
			{
				return;
			}
		}
		else if (string.IsNullOrEmpty(s.Perk))
		{
			return;
		}
		if (string.IsNullOrEmpty(s.Name))
		{
			s.Name = s.Id;
		}
		items[s.Id] = s;
		order.Add(s);
	}

	public static string Serialize()
	{
		try
		{
			return JsonConvert.SerializeObject((object)Order);
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[Valcoin] Catalog serialize failed: " + ex.Message));
			return null;
		}
	}

	public static void ApplyRemote(string json)
	{
		if (string.IsNullOrEmpty(json))
		{
			return;
		}
		try
		{
			List<Sku> list = JsonConvert.DeserializeObject<List<Sku>>(json);
			if (list == null)
			{
				return;
			}
			Dictionary<string, Sku> dictionary = new Dictionary<string, Sku>();
			foreach (Sku item in list)
			{
				if (!string.IsNullOrEmpty(item.Id))
				{
					dictionary[item.Id] = item;
				}
			}
			Items = dictionary;
			Order = list;
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[Valcoin] Catalog ApplyRemote failed: " + ex.Message));
		}
	}

	private static string StripQuotes(string v)
	{
		if (v.Length >= 2 && v[0] == '"' && v[v.Length - 1] == '"')
		{
			return v.Substring(1, v.Length - 2);
		}
		return v;
	}
}
public class CatalogSync : MonoBehaviour
{
	private const float IntervalSeconds = 30f;

	private Coroutine _loop;

	private void Start()
	{
		_loop = ((MonoBehaviour)this).StartCoroutine(Loop());
	}

	private void OnDestroy()
	{
		if (_loop != null)
		{
			((MonoBehaviour)this).StopCoroutine(_loop);
		}
	}

	private IEnumerator Loop()
	{
		while ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
		{
			yield return (object)new WaitForSeconds(2f);
		}
		while (true)
		{
			if (ZRoutedRpc.instance != null)
			{
				RpcLayer.BroadcastCatalog(Catalog.Serialize());
			}
			yield return (object)new WaitForSeconds(30f);
		}
	}
}
[HarmonyPatch]
public static class ChatRpcSlashPatch
{
	private static MethodBase TargetMethod()
	{
		return typeof(Chat).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "RPC_ChatMessage");
	}

	private static bool Prefix(MethodBase __originalMethod, object[] __args)
	{
		try
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return true;
			}
			int num = -1;
			string text = null;
			string text2 = null;
			string senderName = null;
			long peerId = 0L;
			ParameterInfo[] parameters = __originalMethod.GetParameters();
			for (int i = 0; i < parameters.Length; i++)
			{
				string name = parameters[i].Name;
				object obj = __args[i];
				switch (name)
				{
				case "text":
					text = obj as string;
					num = i;
					break;
				case "sender":
					if (obj is long num2)
					{
						peerId = num2;
					}
					break;
				case "senderNetworkUserId":
					text2 = obj as string;
					break;
				case "userInfo":
					if (obj != null)
					{
						Type type = obj.GetType();
						senderName = (type.GetField("Name")?.GetValue(obj) as string) ?? (type.GetProperty("Name")?.GetValue(obj) as string) ?? senderName;
						text2 = (type.GetField("NetworkUserId")?.GetValue(obj) as string) ?? (type.GetProperty("NetworkUserId")?.GetValue(obj) as string) ?? text2;
					}
					break;
				}
			}
			if (string.IsNullOrEmpty(text))
			{
				return true;
			}
			string text3 = SteamIdResolver.FromNetworkUserId(text2) ?? SteamIdResolver.FromPeerId(peerId);
			if (text[0] != '/')
			{
				if (num >= 0)
				{
					string text4 = BuildChatPrefix(text3);
					if (text4 != null)
					{
						__args[num] = text4 + text;
					}
				}
				return true;
			}
			Player senderPlayer = ((!string.IsNullOrEmpty(text3)) ? SteamIdResolver.OnlinePlayerFor(text3) : null);
			if ((Object)(object)senderPlayer == (Object)null && !string.IsNullOrEmpty(senderName))
			{
				senderPlayer = ((IEnumerable<Player>)Player.GetAllPlayers()).FirstOrDefault((Func<Player, bool>)((Player p) => p.GetPlayerName().Equals(senderName, StringComparison.OrdinalIgnoreCase)));
			}
			string[] array = text.Split(new char[1] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
			string text5 = array[0].ToLowerInvariant();
			string text6 = ((array.Length > 1) ? array[1].Trim() : "");
			string[] args = ((text6.Length == 0) ? Array.Empty<string>() : text6.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
			switch (text5)
			{
			case "/coins":
				HandleCoins(text3, senderPlayer);
				return false;
			case "/donate":
				DonateFlow.Run(text3, senderName, delegate(string m)
				{
					Tell(senderPlayer, m);
				});
				return false;
			case "/topdonors":
				TopDonorsFetcher.Fetch(delegate(string m)
				{
					Tell(senderPlayer, m);
				});
				return false;
			case "/shop":
				HandleShop(text3, senderPlayer);
				return false;
			case "/buy":
				HandleBuy(text3, senderPlayer, args);
				return false;
			case "/gift":
				HandleGift(text3, senderName, senderPlayer, args);
				return false;
			case "/title":
				HandleTitle(text3, senderPlayer, text6);
				return false;
			case "/givecoins":
				HandleGiveCoins(text3, senderPlayer, args);
				return false;
			case "/removecoins":
				HandleRemoveCoins(text3, senderPlayer, args);
				return false;
			default:
				return true;
			}
		}
		catch (Exception arg)
		{
			Debug.LogError((object)$"[Valcoin] ChatRpcSlashPatch error: {arg}");
			return true;
		}
	}

	private static string BuildChatPrefix(string steam64)
	{
		if (string.IsNullOrEmpty(steam64))
		{
			return null;
		}
		bool flag = PerkManager.Has(steam64, "donor_badge");
		string text = (PerkManager.Has(steam64, "chat_title") ? PerkManager.Title(steam64) : null);
		if (!flag && string.IsNullOrEmpty(text))
		{
			return null;
		}
		StringBuilder stringBuilder = new StringBuilder(8 + (text?.Length ?? 0));
		if (flag)
		{
			stringBuilder.Append("⭐ ");
		}
		if (!string.IsNullOrEmpty(text))
		{
			stringBuilder.Append('[').Append(text).Append("] ");
		}
		return stringBuilder.ToString();
	}

	private static void HandleCoins(string steam64, Player p)
	{
		if (string.IsNullOrEmpty(steam64))
		{
			Tell(p, "⚠\ufe0f Couldn't resolve your Steam ID.");
			return;
		}
		int balance = CoinManager.GetBalance(steam64);
		PerkManager.PlayerPerks playerPerks = PerkManager.Get(steam64);
		string text = $"\ud83d\udcb0 You have {balance} Valcoins.";
		if (playerPerks.perks.Count > 0)
		{
			text = text + "  Perks: " + string.Join(", ", playerPerks.perks);
		}
		Tell(p, text);
	}

	private static void HandleShop(string steam64, Player p)
	{
		if (Catalog.Order.Count == 0)
		{
			Tell(p, "\ud83d\uded2 The shop is empty. Ask an admin to set up valcoin_shop.yaml.");
			return;
		}
		int num = ((!string.IsNullOrEmpty(steam64)) ? CoinManager.GetBalance(steam64) : 0);
		Tell(p, $"\ud83d\uded2 Shop  (your balance: {num} Valcoins)");
		foreach (Catalog.Sku item in Catalog.Order)
		{
			string text = "";
			if (item.Effect == "grant_item")
			{
				if (item.WeeklyCap > 0)
				{
					text += $"  [max {item.WeeklyCap}/week]";
				}
				if (!string.IsNullOrEmpty(item.RequiresBoss))
				{
					text = text + "  [needs " + item.RequiresBoss + "]";
				}
			}
			else if (!string.IsNullOrEmpty(steam64))
			{
				if (item.Effect == "grant_perk" && PerkManager.Has(steam64, item.Perk))
				{
					text = "  [owned]";
				}
				else if (item.Effect == "add_charges")
				{
					text = $"  [you have {PerkManager.Charges(steam64, item.Perk)}]";
				}
			}
			Tell(p, $"  {item.Id}  ·  {item.Price}c  ·  {item.Name}{text}");
			if (!string.IsNullOrEmpty(item.Description))
			{
				Tell(p, "      " + item.Description);
			}
		}
		Tell(p, "Buy with: /buy <id>");
	}

	private static void HandleBuy(string steam64, Player p, string[] args)
	{
		if (args.Length != 1)
		{
			Tell(p, "⚠\ufe0f Usage: /buy <sku>  (see /shop)");
			return;
		}
		Player capturedPlayer = p;
		ShopHandler.Buy(steam64, args[0].ToLowerInvariant(), delegate(string msg)
		{
			Tell(capturedPlayer, msg);
		});
	}

	private static void HandleGift(string fromSteam64, string fromName, Player fromPlayer, string[] args)
	{
		if (args.Length != 2 || !int.TryParse(args[1], out var result))
		{
			Tell(fromPlayer, "⚠\ufe0f Usage: /gift <playerName> <amount>");
			return;
		}
		GiftFlow.Run(fromSteam64, fromName, args[0], result, delegate(string m)
		{
			Tell(fromPlayer, m);
		});
	}

	private static void HandleTitle(string steam64, Player p, string rest)
	{
		if (string.IsNullOrEmpty(steam64))
		{
			Tell(p, "⚠\ufe0f Couldn't resolve your Steam ID.");
		}
		else if (!PerkManager.Has(steam64, "chat_title"))
		{
			Tell(p, "\ud83d\udd12 You need the \"chat_title\" perk. /buy chat_title");
		}
		else if (string.IsNullOrEmpty(rest))
		{
			string text = PerkManager.Title(steam64);
			Tell(p, string.IsNullOrEmpty(text) ? "Usage: /title <text>  (or /title clear)" : ("Your title is currently: [" + text + "]   Change with /title <text>, remove with /title clear"));
		}
		else if (rest.Equals("clear", StringComparison.OrdinalIgnoreCase) || rest == "-")
		{
			PerkManager.SetTitle(steam64, null);
			Tell(p, "✅ Title cleared.");
		}
		else if (rest.Length > 16)
		{
			Tell(p, "⚠\ufe0f Title must be 16 characters or fewer.");
		}
		else if (!Regex.IsMatch(rest, "^[\\w \\-'\\.!?]+$"))
		{
			Tell(p, "⚠\ufe0f Letters, numbers and basic punctuation only.");
		}
		else
		{
			PerkManager.SetTitle(steam64, rest);
			Tell(p, "✅ Title set to [" + rest + "].");
		}
	}

	private static void HandleGiveCoins(string callerSteam64, Player caller, string[] args)
	{
		if (string.IsNullOrEmpty(callerSteam64) || !Plugin.AdminSteamIDs.Contains(callerSteam64))
		{
			Tell(caller, "\ud83d\udeab You are not authorized.");
			return;
		}
		if (args.Length != 2 || !int.TryParse(args[1], out var result) || result <= 0)
		{
			Tell(caller, "⚠\ufe0f Usage: /givecoins <playerName> <amount>");
			return;
		}
		if (!ResolveTargetByName(args[0], out var steam, out var player))
		{
			Tell(caller, "⚠\ufe0f Player not found or their Steam ID couldn't be resolved.");
			return;
		}
		CoinManager.AddCoins(steam, result);
		Tell(caller, $"✅ Gave {result} Valcoins to {args[0]}.");
		if ((Object)(object)player != (Object)null)
		{
			((Character)player).Message((MessageType)1, $"\ud83c\udf81 +{result} Valcoins from admin!", 0, (Sprite)null);
		}
	}

	private static void HandleRemoveCoins(string callerSteam64, Player caller, string[] args)
	{
		if (string.IsNullOrEmpty(callerSteam64) || !Plugin.AdminSteamIDs.Contains(callerSteam64))
		{
			Tell(caller, "\ud83d\udeab You are not authorized.");
			return;
		}
		if (args.Length != 2 || !int.TryParse(args[1], out var result) || result <= 0)
		{
			Tell(caller, "⚠\ufe0f Usage: /removecoins <playerName> <amount>");
			return;
		}
		if (!ResolveTargetByName(args[0], out var steam, out var player))
		{
			Tell(caller, "⚠\ufe0f Player not found or their Steam ID couldn't be resolved.");
			return;
		}
		int num = Math.Max(0, CoinManager.GetBalance(steam) - result);
		CoinManager.SetBalance(steam, num);
		Tell(caller, $"✅ Removed {result} from {args[0]} (new balance: {num}).");
		if ((Object)(object)player != (Object)null)
		{
			((Character)player).Message((MessageType)1, $"❌ {result} Valcoins removed by admin.", 0, (Sprite)null);
		}
	}

	private static bool ResolveTargetByName(string name, out string steam64, out Player player)
	{
		steam64 = null;
		player = null;
		if ((Object)(object)ZNet.instance == (Object)null)
		{
			return false;
		}
		ZNetPeer val = ((IEnumerable<ZNetPeer>)ZNet.instance.GetConnectedPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => p.m_playerName != null && p.m_playerName.Equals(name, StringComparison.OrdinalIgnoreCase)));
		if (val == null)
		{
			return false;
		}
		steam64 = SteamIdResolver.FromPeer(val);
		if (string.IsNullOrEmpty(steam64))
		{
			return false;
		}
		player = ((IEnumerable<Player>)Player.GetAllPlayers()).FirstOrDefault((Func<Player, bool>)((Player pp) => pp.GetPlayerName().Equals(name, StringComparison.OrdinalIgnoreCase)));
		return true;
	}

	private static void Tell(Player player, string msg)
	{
		if ((Object)(object)player != (Object)null)
		{
			((Character)player).Message((MessageType)1, msg, 0, (Sprite)null);
		}
		else
		{
			Debug.Log((object)("[Valcoin] " + msg));
		}
	}
}
public class SharedCoroutineRunner : MonoBehaviour
{
	private static SharedCoroutineRunner _instance;

	public static SharedCoroutineRunner Instance
	{
		get
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			if ((Object)(object)_instance == (Object)null)
			{
				GameObject val = new GameObject("ValcoinCoroutineRunner");
				Object.DontDestroyOnLoad((Object)val);
				_instance = val.AddComponent<SharedCoroutineRunner>();
			}
			return _instance;
		}
	}
}
public static class CoinManager
{
	private class State
	{
		public Dictionary<string, int> balances = new Dictionary<string, int>();

		public List<long> recentGrants = new List<long>();
	}

	private static readonly string SaveDir = Path.Combine(Paths.ConfigPath, "valcoin_data");

	private static readonly string SaveFile = Path.Combine(SaveDir, "coin_balances.json");

	private const int RecentGrantCap = 5000;

	private static State _state = new State();

	private static HashSet<long> _seen = new HashSet<long>();

	public static void Load()
	{
		try
		{
			Directory.CreateDirectory(SaveDir);
			if (!File.Exists(SaveFile))
			{
				return;
			}
			string text = File.ReadAllText(SaveFile);
			State state;
			try
			{
				state = JsonConvert.DeserializeObject<State>(text);
				if (state == null || state.balances == null)
				{
					throw new Exception("not new shape");
				}
			}
			catch
			{
				Dictionary<string, int> balances = JsonConvert.DeserializeObject<Dictionary<string, int>>(text) ?? new Dictionary<string, int>();
				state = new State
				{
					balances = balances
				};
			}
			_state = state;
			State state2 = _state;
			if (state2.recentGrants == null)
			{
				state2.recentGrants = new List<long>();
			}
			_seen = new HashSet<long>(_state.recentGrants);
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[CoinManager] Failed to load: " + ex.Message));
			_state = new State();
			_seen = new HashSet<long>();
		}
	}

	public static void Save()
	{
		try
		{
			File.WriteAllText(SaveFile, JsonConvert.SerializeObject((object)_state, (Formatting)1));
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[CoinManager] Failed to save: " + ex.Message));
		}
	}

	public static int GetBalance(string steamId)
	{
		if (!_state.balances.TryGetValue(steamId, out var value))
		{
			return 0;
		}
		return value;
	}

	public static void AddCoins(string steamId, int amount)
	{
		_state.balances[steamId] = GetBalance(steamId) + amount;
		Save();
	}

	public static void SetBalance(string steamId, int amount)
	{
		_state.balances[steamId] = Math.Max(0, amount);
		Save();
	}

	public static bool TryApplyGrant(long grantId, string steamId, int amount)
	{
		if (_seen.Contains(grantId))
		{
			return false;
		}
		_seen.Add(grantId);
		_state.recentGrants.Add(grantId);
		if (_state.recentGrants.Count > 5000)
		{
			int count = _state.recentGrants.Count - 5000;
			List<long> range = _state.recentGrants.GetRange(0, count);
			_state.recentGrants.RemoveRange(0, count);
			foreach (long item in range)
			{
				_seen.Remove(item);
			}
		}
		AddCoins(steamId, amount);
		return true;
	}
}
public static class Config
{
	public static string BackendUrl { get; private set; }

	public static string PluginToken { get; private set; }

	public static float PollIntervalSeconds { get; private set; } = 10f;

	public static string UiToggleKey { get; private set; } = "F8";

	public static string CodexToggleKey { get; private set; } = "F4";

	public static bool WelcomeEnabled { get; private set; } = true;

	public static string WelcomeMessage { get; private set; }

	public static bool Ready
	{
		get
		{
			if (!string.IsNullOrEmpty(BackendUrl))
			{
				return !string.IsNullOrEmpty(PluginToken);
			}
			return false;
		}
	}

	public static void Load()
	{
		BackendUrl = Environment.GetEnvironmentVariable("VALCOIN_BACKEND_URL");
		PluginToken = Environment.GetEnvironmentVariable("VALCOIN_PLUGIN_TOKEN");
		try
		{
			string text = Path.Combine(Paths.ConfigPath, "valcoin_config.json");
			if (File.Exists(text))
			{
				JObject val = JObject.Parse(File.ReadAllText(text));
				BackendUrl = (string.IsNullOrEmpty(BackendUrl) ? ((string)val["backend_url"]) : BackendUrl);
				PluginToken = (string.IsNullOrEmpty(PluginToken) ? ((string)val["plugin_token"]) : PluginToken);
				if (val["poll_interval_seconds"] != null)
				{
					PollIntervalSeconds = (float)val["poll_interval_seconds"];
				}
				if (val["ui_toggle_key"] != null)
				{
					UiToggleKey = (string)val["ui_toggle_key"];
				}
				if (val["codex_toggle_key"] != null)
				{
					CodexToggleKey = (string)val["codex_toggle_key"];
				}
				if (val["welcome_message_enabled"] != null)
				{
					WelcomeEnabled = (bool)val["welcome_message_enabled"];
				}
				if (val["welcome_message"] != null)
				{
					WelcomeMessage = (string)val["welcome_message"];
				}
			}
			else
			{
				File.WriteAllText(text, "{\n  \"backend_url\":  \"https://your-app.fly.dev\",\n  \"plugin_token\": \"paste-the-PLUGIN_TOKEN-from-your-fly-secrets\",\n  \"poll_interval_seconds\": 10,\n\n  \"ui_toggle_key\": \"F8\",\n  \"codex_toggle_key\": \"F4\",\n  \"welcome_message_enabled\": true,\n  \"welcome_message\": null\n}\n");
				Debug.LogWarning((object)("[Valcoin] Created template config at " + text + ". Fill in backend_url + plugin_token."));
			}
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[Valcoin] Failed to load config: " + ex.Message));
		}
		if (!Ready)
		{
			Debug.LogWarning((object)"[Valcoin] Backend not configured; /donate and grant polling are disabled.");
		}
	}
}
public class DonationCodex : MonoBehaviour
{
	private enum Section
	{
		Overview,
		Perks,
		Patrons,
		Donate
	}

	private class StateResp
	{
		public int balance;

		public TopEntry[] top_donors;
	}

	private class TopEntry
	{
		public int rank;

		public string name;

		public int total_coins;
	}

	private const int PanelW = 560;

	private const int PanelH = 520;

	private Section _section;

	private bool _open;

	private KeyCode _toggleKey = (KeyCode)285;

	private bool _online;

	private int _balance = -1;

	private List<TopEntry> _patrons = new List<TopEntry>();

	private float _lastFetch;

	private const float RefreshSeconds = 20f;

	private Vector2 _scroll;

	private GUIStyle _bg;

	private GUIStyle _hdr;

	private GUIStyle _sub;

	private GUIStyle _btn;

	private GUIStyle _btnActive;

	private GUIStyle _btnDim;

	private GUIStyle _line;

	private GUIStyle _label;

	private GUIStyle _pillOn;

	private GUIStyle _pillOff;

	private bool _stylesReady;

	private bool _wasOpen;

	private void Awake()
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		if (!string.IsNullOrEmpty(Config.CodexToggleKey) && Enum.TryParse<KeyCode>(Config.CodexToggleKey, ignoreCase: true, out KeyCode result))
		{
			_toggleKey = result;
		}
		_online = Config.Ready;
		Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
	}

	private void Update()
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		if (Input.GetKeyDown(_toggleKey))
		{
			Toggle();
		}
		if (_open)
		{
			Cursor.lockState = (CursorLockMode)0;
			Cursor.visible = true;
			DonationUiState.SetMouseCapture(value: false);
		}
		else if (_wasOpen)
		{
			DonationUiState.SetMouseCapture(value: true);
		}
		if (_open != _wasOpen)
		{
			DonationUiState.CodexOpen = _open;
			_wasOpen = _open;
		}
	}

	private void OnDestroy()
	{
		DonationUiState.CodexOpen = false;
	}

	private void Toggle()
	{
		_open = !_open;
		if (_open)
		{
			RefreshSoon(force: true);
		}
	}

	private void RefreshSoon(bool force = false)
	{
		if (!Config.Ready)
		{
			_online = false;
		}
		else if (force || !(Time.realtimeSinceStartup - _lastFetch < 20f))
		{
			_lastFetch = Time.realtimeSinceStartup;
			((MonoBehaviour)this).StartCoroutine(Fetch());
		}
	}

	private IEnumerator Fetch()
	{
		string steam64 = ResolveLocalSteam64();
		string path = (string.IsNullOrEmpty(steam64) ? "/api/leaderboard/top?limit=5" : ("/api/state/" + steam64 + "?top=5"));
		yield return BackendClient.Get(path, delegate(bool ok, StateResp r, string err)
		{
			_online = ok && r != null;
			if (_online)
			{
				if (r.balance != 0 || !string.IsNullOrEmpty(steam64))
				{
					_balance = r.balance;
				}
				_patrons = ((r.top_donors != null) ? new List<TopEntry>(r.top_donors) : new List<TopEntry>());
			}
		});
	}

	private string ResolveLocalSteam64()
	{
		try
		{
			object obj = Type.GetType("ZSteamMatchmaking, assembly_valheim")?.GetField("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
			return (obj?.GetType().GetMethod("GetSteamID")?.Invoke(obj, null))?.ToString();
		}
		catch
		{
			return null;
		}
	}

	private void OnGUI()
	{
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0209: Unknown result type (might be due to invalid IL or missing references)
		//IL_021d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0222: Unknown result type (might be due to invalid IL or missing references)
		if (!_open)
		{
			return;
		}
		if (!_stylesReady)
		{
			InitStyles();
		}
		if (Menu.IsVisible() || ((Object)(object)InventoryGui.instance != (Object)null && InventoryGui.IsVisible()) || ((Object)(object)Minimap.instance != (Object)null && Minimap.IsOpen()))
		{
			_open = false;
			return;
		}
		Rect val = default(Rect);
		((Rect)(ref val))..ctor((float)(Screen.width - 560) / 2f, (float)(Screen.height - 520) / 2f, 560f, 520f);
		GUI.Box(val, GUIContent.none, _bg);
		GUILayout.BeginArea(new Rect(((Rect)(ref val)).x + 14f, ((Rect)(ref val)).y + 14f, ((Rect)(ref val)).width - 28f, ((Rect)(ref val)).height - 28f));
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		GUILayout.Label("Donation Codex", _hdr, Array.Empty<GUILayoutOption>());
		GUILayout.FlexibleSpace();
		GUILayout.Label(_online ? "● Live" : "● Offline", _online ? _pillOn : _pillOff, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) });
		GUILayout.Space(6f);
		if (GUILayout.Button("✕", _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(30f) }))
		{
			_open = false;
		}
		GUILayout.EndHorizontal();
		GUILayout.Label(_online ? ("Balance: " + ((_balance < 0) ? "—" : _balance.ToString()) + " Valcoins") : "The donation service isn't connected yet — everything here still works to browse; it activates once the operator brings it online.", _sub, Array.Empty<GUILayoutOption>());
		DrawHr();
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		NavButton("Overview", Section.Overview);
		NavButton("Perks & Shop", Section.Perks);
		NavButton("Patrons", Section.Patrons);
		NavButton("Donate", Section.Donate);
		GUILayout.EndHorizontal();
		DrawHr();
		_scroll = GUILayout.BeginScrollView(_scroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) });
		switch (_section)
		{
		case Section.Overview:
			DrawOverview();
			break;
		case Section.Perks:
			DrawPerks();
			break;
		case Section.Patrons:
			DrawPatrons();
			break;
		case Section.Donate:
			DrawDonate();
			break;
		}
		GUILayout.EndScrollView();
		GUILayout.EndArea();
	}

	private void NavButton(string label, Section s)
	{
		if (GUILayout.Button(label, (_section == s) ? _btnActive : _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) }))
		{
			_section = s;
		}
	}

	private void DrawOverview()
	{
		//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.Label("Support the realm", _hdr, Array.Empty<GUILayoutOption>());
		GUILayout.Label("This server is kept alight by its patrons. Donating is always optional — playing is free, and perks are cosmetic or weekly-limited supplies, never power.", _label, Array.Empty<GUILayoutOption>());
		GUILayout.Space(8f);
		GUILayout.Label("How Valcoins work", _sub, Array.Empty<GUILayoutOption>());
		Bullet("Run /donate to get a code + a portal link.");
		Bullet("Donate through the portal; Valcoins are credited automatically.");
		Bullet("Spend them in Perks & Shop; check your balance any time.");
		GUILayout.Space(8f);
		GUILayout.Label("Commands", _sub, Array.Empty<GUILayoutOption>());
		Bullet("/donate — get your donation code + link");
		Bullet("/coins — show your Valcoin balance + perks");
		Bullet("/shop — list everything for sale");
		Bullet("/buy <id> — purchase an item");
		Bullet("/gift <player> <amount> — share Valcoins");
		Bullet("/topdonors — the patron leaderboard");
		Bullet("/title <text> — set a chat title (with the perk)");
		GUILayout.Space(8f);
		GUILayout.Label($"Press {_toggleKey} any time to open this Codex.", _label, Array.Empty<GUILayoutOption>());
	}

	private void DrawPerks()
	{
		if (Catalog.Order.Count == 0)
		{
			GUILayout.Label("The shop is empty — the operator hasn't set up valcoin_shop.yaml yet.", _label, Array.Empty<GUILayoutOption>());
			return;
		}
		string text = ResolveLocalSteam64();
		foreach (Catalog.Sku item in Catalog.Order)
		{
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label($"{item.Name}  ·  {item.Price}c", _label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
			if (item.Effect == "grant_perk" && !string.IsNullOrEmpty(text) && PerkManager.Has(text, item.Perk))
			{
				GUILayout.Label("✓ owned", _label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) });
			}
			else if (_online)
			{
				if (GUILayout.Button("Buy", _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }))
				{
					RpcLayer.SendAction("buy:" + item.Id);
				}
			}
			else
			{
				GUILayout.Label("Buy", _btnDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) });
			}
			GUILayout.EndHorizontal();
			if (item.Effect == "grant_item")
			{
				string text2 = "";
				if (item.WeeklyCap > 0)
				{
					text2 += $"max {item.WeeklyCap}/week";
				}
				if (!string.IsNullOrEmpty(item.RequiresBoss))
				{
					text2 = text2 + ((text2.Length > 0) ? " · " : "") + "needs " + item.RequiresBoss;
				}
				if (text2.Length > 0)
				{
					GUILayout.Label("    " + text2, _label, Array.Empty<GUILayoutOption>());
				}
			}
			if (!string.IsNullOrEmpty(item.Description))
			{
				GUILayout.Label("    " + item.Description, _label, Array.Empty<GUILayoutOption>());
			}
			GUILayout.Space(6f);
		}
		if (!_online)
		{
			DrawHr();
			GUILayout.Label("Purchasing activates once the donation service is online. You can browse the full catalog now.", _sub, Array.Empty<GUILayoutOption>());
		}
	}

	private void DrawPatrons()
	{
		GUILayout.Label("Top Patrons", _hdr, Array.Empty<GUILayoutOption>());
		GUILayout.Space(4f);
		if (!_online)
		{
			GUILayout.Label("The patron leaderboard appears here once the donation service is online.", _label, Array.Empty<GUILayoutOption>());
			return;
		}
		if (_patrons.Count == 0)
		{
			GUILayout.Label("No patrons yet — be the first. Head to the Donate tab!", _label, Array.Empty<GUILayoutOption>());
			return;
		}
		foreach (TopEntry patron in _patrons)
		{
			GUILayout.Label(string.Format("  {0}.  {1}  —  {2} coins", patron.rank, patron.name ?? "Anonymous", patron.total_coins), _label, Array.Empty<GUILayoutOption>());
		}
		GUILayout.Space(8f);
		if (GUILayout.Button("Refresh", _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }))
		{
			_patrons.Clear();
			RefreshSoon(force: true);
		}
	}

	private void DrawDonate()
	{
		GUILayout.Label("Make a donation", _hdr, Array.Empty<GUILayoutOption>());
		GUILayout.Label("Get a personal code, donate through the portal link, and your Valcoins are credited automatically within a few seconds.", _label, Array.Empty<GUILayoutOption>());
		GUILayout.Space(8f);
		if (_online)
		{
			if (GUILayout.Button("\ud83c\udf81  Get my donation code", _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(34f) }))
			{
				RpcLayer.SendAction("donate");
			}
			GUILayout.Space(4f);
			GUILayout.Label("Your code + link will appear in the top-left message area.", _label, Array.Empty<GUILayoutOption>());
		}
		else
		{
			GUILayout.Label("Donations aren't connected yet.", _sub, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Once the operator brings the donation service online, this button will hand you a code and a link — no update needed on your side.", _label, Array.Empty<GUILayoutOption>());
		}
	}

	private void Bullet(string text)
	{
		GUILayout.Label("•  " + text, _label, Array.Empty<GUILayoutOption>());
	}

	private void DrawHr()
	{
		GUILayout.Box(GUIContent.none, _line, (GUILayoutOption[])(object)new GUILayoutOption[2]
		{
			GUILayout.Height(1f),
			GUILayout.ExpandWidth(true)
		});
		GUILayout.Space(4f);
	}

	private void InitStyles()
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Expected O, but got Unknown
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Expected O, but got Unknown
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0073: Unknown result type (might be due to invalid IL or missing references)
		//IL_007f: Expected O, but got Unknown
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ce: Expected O, but got Unknown
		//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
		//IL_0102: Unknown result type (might be due to invalid IL or missing references)
		//IL_010f: Expected O, but got Unknown
		//IL_011b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0125: Expected O, but got Unknown
		//IL_012c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0136: Expected O, but got Unknown
		//IL_0155: Unknown result type (might be due to invalid IL or missing references)
		//IL_016f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0180: Unknown result type (might be due to invalid IL or missing references)
		//IL_018a: Expected O, but got Unknown
		//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01af: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b9: Expected O, but got Unknown
		//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ff: Unknown result type (might be due to invalid IL or missing references)
		//IL_020b: Expected O, but got Unknown
		//IL_0225: Unknown result type (might be due to invalid IL or missing references)
		//IL_023a: Unknown result type (might be due to invalid IL or missing references)
		//IL_023f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0247: Unknown result type (might be due to invalid IL or missing references)
		//IL_0253: Expected O, but got Unknown
		//IL_026d: Unknown result type (might be due to invalid IL or missing references)
		//IL_027e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0288: Expected O, but got Unknown
		//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
		_bg = new GUIStyle(GUI.skin.box);
		_bg.normal.background = SolidTex(new Color(0.08f, 0.07f, 0.05f, 0.97f));
		_bg.padding = new RectOffset(12, 12, 12, 12);
		_hdr = new GUIStyle(GUI.skin.label)
		{
			fontSize = 16,
			fontStyle = (FontStyle)1
		};
		_hdr.normal.textColor = new Color(0.85f, 0.7f, 0.4f);
		_sub = new GUIStyle(GUI.skin.label)
		{
			fontSize = 12,
			fontStyle = (FontStyle)2,
			wordWrap = true
		};
		_sub.normal.textColor = new Color(0.75f, 0.72f, 0.62f);
		_btn = new GUIStyle(GUI.skin.button)
		{
			fontSize = 13
		};
		_btn.padding = new RectOffset(10, 10, 6, 6);
		_btnActive = new GUIStyle(_btn);
		_btnActive.normal.background = SolidTex(new Color(0.6f, 0.45f, 0.2f, 1f));
		_btnActive.normal.textColor = Color.white;
		_btnDim = new GUIStyle(_btn);
		_btnDim.normal.textColor = new Color(0.5f, 0.48f, 0.42f);
		_line = new GUIStyle();
		_line.normal.background = SolidTex(new Color(0.3f, 0.25f, 0.18f, 0.6f));
		_label = new GUIStyle(GUI.skin.label)
		{
			fontSize = 12,
			wordWrap = true
		};
		_label.normal.textColor = new Color(0.88f, 0.85f, 0.78f);
		_pillOn = new GUIStyle(GUI.skin.label)
		{
			fontSize = 12,
			fontStyle = (FontStyle)1
		};
		_pillOn.normal.textColor = new Color(0.5f, 0.85f, 0.45f);
		_pillOff = new GUIStyle(_pillOn);
		_pillOff.normal.textColor = new Color(0.85f, 0.6f, 0.3f);
		_stylesReady = true;
	}

	private static Texture2D SolidTex(Color c)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Expected O, but got Unknown
		Texture2D val = new Texture2D(1, 1);
		val.SetPixel(0, 0, c);
		val.Apply();
		return val;
	}
}
public class DonationPanel : MonoBehaviour
{
	private enum Tab
	{
		Donate,
		Shop,
		Gift,
		Top
	}

	private class StateResp
	{
		public int balance;

		public TopEntry[] top_donors;

		public int donor_count;

		public int total_donated_coins;
	}

	private class TopEntry
	{
		public int rank;

		public string name;

		public int total_coins;
	}

	private const int PanelW = 520;

	private const int PanelH = 480;

	private Tab _tab;

	private bool _open;

	private KeyCode _toggleKey = (KeyCode)289;

	private int _balance;

	private List<string> _perks = new List<string>();

	private List<TopEntry> _topDonors = new List<TopEntry>();

	private readonly List<string> _log = new List<string>();

	private const int LogCap = 12;

	private Vector2 _logScroll;

	private string _giftTo = "";

	private string _giftAmount = "";

	private string _titleText = "";

	private GUIStyle _bg;

	private GUIStyle _hdr;

	private GUIStyle _btn;

	private GUIStyle _btnActive;

	private GUIStyle _btnDim;

	private GUIStyle _line;

	private GUIStyle _logLine;

	private GUIStyle _label;

	private bool _stylesReady;

	private float _lastStateFetch;

	private const float StateRefreshSeconds = 15f;

	private bool _online;

	private const float AutoRefreshSeconds = 20f;

	private bool _wasOpen;

	private Vector2 _shopScroll;

	private void Awake()
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		if (!string.IsNullOrEmpty(Config.UiToggleKey) && Enum.TryParse<KeyCode>(Config.UiToggleKey, ignoreCase: true, out KeyCode result))
		{
			_toggleKey = result;
		}
		RpcLayer.OnPanelMessage = (Action<string>)Delegate.Combine(RpcLayer.OnPanelMessage, new Action<string>(AddLog));
		Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
	}

	private void OnDestroy()
	{
		RpcLayer.OnPanelMessage = (Action<string>)Delegate.Remove(RpcLayer.OnPanelMessage, new Action<string>(AddLog));
		DonationUiState.PanelOpen = false;
	}

	private void Update()
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		if (Input.GetKeyDown(_toggleKey))
		{
			Toggle();
		}
		if (_open)
		{
			Cursor.lockState = (CursorLockMode)0;
			Cursor.visible = true;
			DonationUiState.SetMouseCapture(value: false);
			RefreshStateSoon();
		}
		else if (_wasOpen)
		{
			DonationUiState.SetMouseCapture(value: true);
		}
		if (_open != _wasOpen)
		{
			DonationUiState.PanelOpen = _open;
			_wasOpen = _open;
		}
	}

	private void Toggle()
	{
		_open = !_open;
		if (_open)
		{
			RefreshStateSoon(force: true);
		}
	}

	private void RefreshStateSoon(bool force = false)
	{
		if (!Config.Ready)
		{
			_online = false;
			return;
		}
		float num = (force ? 1f : 20f);
		if (!(Time.realtimeSinceStartup - _lastStateFetch < num))
		{
			_lastStateFetch = Time.realtimeSinceStartup;
			((MonoBehaviour)this).StartCoroutine(FetchState());
		}
	}

	private IEnumerator FetchState()
	{
		string text = ResolveLocalSteam64();
		if (string.IsNullOrEmpty(text))
		{
			yield break;
		}
		yield return BackendClient.Get("/api/state/" + text + "?top=5", delegate(bool ok, StateResp r, string err)
		{
			_online = ok && r != null;
			if (_online)
			{
				_balance = r.balance;
				_topDonors = ((r.top_donors != null) ? new List<TopEntry>(r.top_donors) : new List<TopEntry>());
			}
		});
	}

	private string ResolveLocalSteam64()
	{
		try
		{
			object obj = Type.GetType("ZSteamMatchmaking, assembly_valheim")?.GetField("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
			return (obj?.GetType().GetMethod("GetSteamID")?.Invoke(obj, null))?.ToString();
		}
		catch
		{
			return null;
		}
	}

	private void InitStyles()
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Expected O, but got Unknown
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Expected O, but got Unknown
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0073: Unknown result type (might be due to invalid IL or missing references)
		//IL_007f: Expected O, but got Unknown
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c0: Expected O, but got Unknown
		//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d6: Expected O, but got Unknown
		//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e7: Expected O, but got Unknown
		//IL_0106: Unknown result type (might be due to invalid IL or missing references)
		//IL_0120: Unknown result type (might be due to invalid IL or missing references)
		//IL_0131: Unknown result type (might be due to invalid IL or missing references)
		//IL_013b: Expected O, but got Unknown
		//IL_0155: Unknown result type (might be due to invalid IL or missing references)
		//IL_0160: Unknown result type (might be due to invalid IL or missing references)
		//IL_016a: Expected O, but got Unknown
		//IL_0189: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bc: Expected O, but got Unknown
		//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fd: Expected O, but got Unknown
		//IL_0217: Unknown result type (might be due to invalid IL or missing references)
		_bg = new GUIStyle(GUI.skin.box);
		_bg.normal.background = SolidTex(new Color(0.08f, 0.07f, 0.05f, 0.97f));
		_bg.padding = new RectOffset(12, 12, 12, 12);
		_hdr = new GUIStyle(GUI.skin.label)
		{
			fontSize = 16,
			fontStyle = (FontStyle)1
		};
		_hdr.normal.textColor = new Color(0.85f, 0.7f, 0.4f);
		_btn = new GUIStyle(GUI.skin.button)
		{
			fontSize = 13
		};
		_btn.padding = new RectOffset(10, 10, 6, 6);
		_btnActive = new GUIStyle(_btn);
		_btnActive.normal.background = SolidTex(new Color(0.6f, 0.45f, 0.2f, 1f));
		_btnActive.normal.textColor = Color.white;
		_btnDim = new GUIStyle(_btn);
		_btnDim.normal.textColor = new Color(0.5f, 0.48f, 0.42f);
		_line = new GUIStyle();
		_line.normal.background = SolidTex(new Color(0.3f, 0.25f, 0.18f, 0.6f));
		_logLine = new GUIStyle(GUI.skin.label)
		{
			fontSize = 12,
			wordWrap = true
		};
		_logLine.normal.textColor = new Color(0.9f, 0.9f, 0.85f);
		_label = new GUIStyle(GUI.skin.label)
		{
			fontSize = 12
		};
		_label.normal.textColor = new Color(0.88f, 0.85f, 0.78f);
		_stylesReady = true;
	}

	private static Texture2D SolidTex(Color c)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Expected O, but got Unknown
		Texture2D val = new Texture2D(1, 1);
		val.SetPixel(0, 0, c);
		val.Apply();
		return val;
	}

	private void OnGUI()
	{
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
		if (!_open)
		{
			return;
		}
		if (!_stylesReady)
		{
			InitStyles();
		}
		if (Menu.IsVisible() || ((Object)(object)InventoryGui.instance != (Object)null && InventoryGui.IsVisible()) || ((Object)(object)Minimap.instance != (Object)null && Minimap.IsOpen()))
		{
			_open = false;
			return;
		}
		Rect val = default(Rect);
		((Rect)(ref val))..ctor((float)(Screen.width - 520) / 2f, (float)(Screen.height - 480) / 2f, 520f, 480f);
		GUI.Box(val, GUIContent.none, _bg);
		GUILayout.BeginArea(new Rect(((Rect)(ref val)).x + 12f, ((Rect)(ref val)).y + 12f, ((Rect)(ref val)).width - 24f, ((Rect)(ref val)).height - 24f));
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		GUILayout.Label("Valheim Donations", _hdr, Array.Empty<GUILayoutOption>());
		GUILayout.FlexibleSpace();
		if (GUILayout.Button("✕", _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(30f) }))
		{
			_open = false;
		}
		GUILayout.EndHorizontal();
		GUILayout.Label($"Balance:  {_balance} Valcoins", _label, Array.Empty<GUILayoutOption>());
		string text = string.Join(", ", PerksForLocalPlayer());
		if (!string.IsNullOrEmpty(text))
		{
			GUILayout.Label("Perks: " + text, _label, Array.Empty<GUILayoutOption>());
		}
		if (!_online)
		{
			GUILayout.Label("\ud83d\udd0c Donation service offline — browse now; it activates when the operator connects it.", _label, Array.Empty<GUILayoutOption>());
		}
		DrawHr();
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		TabButton("Donate", Tab.Donate);
		TabButton("Shop", Tab.Shop);
		TabButton("Gift", Tab.Gift);
		TabButton("Top", Tab.Top);
		GUILayout.EndHorizontal();
		DrawHr();
		switch (_tab)
		{
		case Tab.Donate:
			DrawDonate();
			break;
		case Tab.Shop:
			DrawShop();
			break;
		case Tab.Gift:
			DrawGift();
			break;
		case Tab.Top:
			DrawTop();
			break;
		}
		DrawHr();
		DrawLog();
		GUILayout.EndArea();
	}

	private void TabButton(string label, Tab t)
	{
		if (GUILayout.Button(label, (_tab == t) ? _btnActive : _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) }))
		{
			_tab = t;
		}
	}

	private void DrawHr()
	{
		GUILayout.Box(GUIContent.none, _line, (GUILayoutOption[])(object)new GUILayoutOption[2]
		{
			GUILayout.Height(1f),
			GUILayout.ExpandWidth(true)
		});
		GUILayout.Space(4f);
	}

	private void DrawDonate()
	{
		GUILayout.Label("Get a donation code, then donate via the link.\nFunds turn into Valcoins automatically.", _label, Array.Empty<GUILayoutOption>());
		GUILayout.Space(6f);
		if (_online)
		{
			if (GUILayout.Button("\ud83c\udf81  Get my donation code", _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(34f) }))
			{
				RpcLayer.SendAction("donate");
			}
		}
		else
		{
			GUILayout.Label("\ud83c\udf81  Get my donation code", _btnDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(34f) });
		}
	}

	private void DrawShop()
	{
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		if (Catalog.Order.Count == 0)
		{
			GUILayout.Label("Shop is empty — ask the operator to set up valcoin_shop.yaml.", _label, Array.Empty<GUILayoutOption>());
			return;
		}
		_shopScroll = GUILayout.BeginScrollView(_shopScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) });
		string text = ResolveLocalSteam64();
		foreach (Catalog.Sku item in Catalog.Order)
		{
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label($"{item.Name}  —  {item.Price}c", _label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
			bool flag = item.Effect == "grant_perk" && !string.IsNullOrEmpty(text) && PerkManager.Has(text, item.Perk);
			int num = ((item.Effect == "add_charges" && !string.IsNullOrEmpty(text)) ? PerkManager.Charges(text, item.Perk) : 0);
			if (flag)
			{
				GUILayout.Label("✓ owned", _label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) });
			}
			else if (num > 0)
			{
				GUILayout.Label($"x{num} held", _label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) });
			}
			if (!flag)
			{
				if (_online)
				{
					if (GUILayout.Button("Buy", _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }))
					{
						RpcLayer.SendAction("buy:" + item.Id);
					}
				}
				else
				{
					GUILayout.Label("Buy", _btnDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) });
				}
			}
			GUILayout.EndHorizontal();
			if (!string.IsNullOrEmpty(item.Description))
			{
				GUILayout.Label("    " + item.Description, _label, Array.Empty<GUILayoutOption>());
			}
			GUILayout.Space(4f);
		}
		GUILayout.EndScrollView();
	}

	private void DrawGift()
	{
		GUILayout.Label("Send Valcoins to another player on the server.", _label, Array.Empty<GUILayoutOption>());
		GUILayout.Space(4f);
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		GUILayout.Label("To:", _label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) });
		_giftTo = GUILayout.TextField(_giftTo ?? "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(200f) });
		GUILayout.EndHorizontal();
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		GUILayout.Label("Amount:", _label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) });
		_giftAmount = GUILayout.TextField(_giftAmount ?? "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) });
		GUILayout.EndHorizontal();
		GUILayout.Space(6f);
		if (_online)
		{
			if (GUILayout.Button("\ud83c\udf81  Send gift", _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) }))
			{
				if (string.IsNullOrWhiteSpace(_giftTo) || string.IsNullOrWhiteSpace(_giftAmount))
				{
					AddLog("⚠\ufe0f Fill in both fields.");
				}
				else
				{
					RpcLayer.SendAction("gift:" + _giftTo.Trim() + ":" + _giftAmount.Trim());
				}
			}
		}
		else
		{
			GUILayout.Label("\ud83c\udf81  Send gift", _btnDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) });
		}
		string text = ResolveLocalSteam64();
		if (!string.IsNullOrEmpty(text) && PerkManager.Has(text, "chat_title"))
		{
			GUILayout.Space(10f);
			GUILayout.Label("Chat title (clear with empty + Set):", _label, Array.Empty<GUILayoutOption>());
			_titleText = GUILayout.TextField(_titleText ?? "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(200f) });
			if (GUILayout.Button("Set title", _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }))
			{
				RpcLayer.SendAction("title:" + (string.IsNullOrWhiteSpace(_titleText) ? "clear" : _titleText.Trim()));
			}
		}
	}

	private void DrawTop()
	{
		GUILayout.Label("Lifetime donor leaderboard:", _label, Array.Empty<GUILayoutOption>());
		GUILayout.Space(4f);
		if (_topDonors.Count == 0)
		{
			GUILayout.Label("(none yet — be the first!)", _label, Array.Empty<GUILayoutOption>());
		}
		else
		{
			foreach (TopEntry topDonor in _topDonors)
			{
				GUILayout.Label(string.Format("  {0}. {1}  —  {2} coins", topDonor.rank, topDonor.name ?? "Anonymous", topDonor.total_coins), _label, Array.Empty<GUILayoutOption>());
			}
		}
		GUILayout.Space(6f);
		if (GUILayout.Button("Refresh", _btn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }))
		{
			_topDonors.Clear();
			RefreshStateSoon();
		}
	}

	private void DrawLog()
	{
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		if (_log.Count != 0)
		{
			GUILayout.Label("Messages", _label, Array.Empty<GUILayoutOption>());
			_logScroll = GUILayout.BeginScrollView(_logScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(80f) });
			for (int num = _log.Count - 1; num >= 0; num--)
			{
				GUILayout.Label("• " + _log[num], _logLine, Array.Empty<GUILayoutOption>());
			}
			GUILayout.EndScrollView();
		}
	}

	private IEnumerable<string> PerksForLocalPlayer()
	{
		string s = ResolveLocalSteam64();
		if (!string.IsNullOrEmpty(s))
		{
			if (PerkManager.Has(s, "donor_badge"))
			{
				yield return "donor_badge";
			}
			if (PerkManager.Has(s, "chat_title"))
			{
				yield return "chat_title";
			}
			if (PerkManager.Has(s, "companion_flair"))
			{
				yield return "companion_flair";
			}
		}
	}

	private void AddLog(string msg)
	{
		_log.Add(msg);
		if (_log.Count > 12)
		{
			_log.RemoveAt(0);
		}
		RefreshStateSoon();
	}
}
public static class DonationUiState
{
	public static bool CodexOpen;

	public static bool PanelOpen;

	private static FieldInfo _mouseCapture;

	public static bool AnyOpen
	{
		get
		{
			if (!CodexOpen)
			{
				return PanelOpen;
			}
			return true;
		}
	}

	public static void SetMouseCapture(bool value)
	{
		GameCamera instance = GameCamera.instance;
		if (!((Object)(object)instance == (Object)null))
		{
			if (_mouseCapture == null)
			{
				_mouseCapture = typeof(GameCamera).GetField("m_mouseCapture", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			}
			_mouseCapture?.SetValue(instance, value);
		}
	}
}
[HarmonyPatch(typeof(Player), "TakeInput")]
internal static class DonationPlayerTakeInputPatch
{
	private static void Postfix(ref bool __result)
	{
		if (DonationUiState.AnyOpen)
		{
			__result = false;
		}
	}
}
[HarmonyPatch(typeof(PlayerController), "TakeInput", new Type[] { typeof(bool) })]
internal static class DonationPlayerControllerTakeInputPatch
{
	private static void Postfix(ref bool __result)
	{
		if (DonationUiState.AnyOpen)
		{
			__result = false;
		}
	}
}
public static class DonateFlow
{
	private class ClaimResp
	{
		public string code;

		public string expires_at;

		public string donation_url;

		public int ttl_minutes;
	}

	public static void Run(string steam64, string senderName, Action<string> reply)
	{
		if (string.IsNullOrEmpty(steam64))
		{
			reply("⚠\ufe0f Couldn't resolve your Steam ID.");
			return;
		}
		if (!Config.Ready)
		{
			reply("⚠\ufe0f Donations aren't configured on this server.");
			return;
		}
		reply("⌛ Generating your donation code...");
		((MonoBehaviour)SharedCoroutineRunner.Instance).StartCoroutine(BackendClient.Post("/api/claim", new
		{
			steam64 = steam64,
			name = senderName
		}, delegate(bool ok, ClaimResp r, string err)
		{
			if (!ok || r == null)
			{
				reply("❌ Couldn't reach the donations service. Try again later.");
				Debug.LogWarning((object)("[Valcoin] /donate failed: " + err));
			}
			else
			{
				reply("\ud83d\udc9b Donate at  " + r.donation_url);
				reply($"   Enter code:  {r.code}   (expires in {r.ttl_minutes} min)");
			}
		}));
	}
}
public static class GiftFlow
{
	private class TransferResp
	{
		public string status;

		public int balance;

		public int transferred;
	}

	public static void Run(string fromSteam64, string fromName, string toName, int amount, Action<string> reply)
	{
		if (string.IsNullOrEmpty(fromSteam64))
		{
			reply("⚠\ufe0f Couldn't resolve your Steam ID.");
			return;
		}
		if (string.IsNullOrEmpty(toName))
		{
			reply("⚠\ufe0f Specify a recipient.");
			return;
		}
		if (amount <= 0)
		{
			reply("⚠\ufe0f Amount must be positive.");
			return;
		}
		if (!ResolveTargetByName(toName, out var steam))
		{
			reply("⚠\ufe0f Player \"" + toName + "\" not found or no Steam ID.");
			return;
		}
		if (steam == fromSteam64)
		{
			reply("⚠\ufe0f You can't gift yourself.");
			return;
		}
		int balance = CoinManager.GetBalance(fromSteam64);
		if (balance < amount)
		{
			reply($"\ud83d\udcb8 Not enough Valcoins ({balance} / {amount}).");
			return;
		}
		string idempotency_key = $"gift-{Guid.NewGuid():N}";
		((MonoBehaviour)SharedCoroutineRunner.Instance).StartCoroutine(BackendClient.Post("/api/transfer", new
		{
			from_steam64 = fromSteam64,
			to_steam64 = steam,
			coins = amount,
			idempotency_key = idempotency_key,
			from_name = fromName,
			to_name = toName
		}, delegate(bool ok, TransferResp r, string err)
		{
			if (!ok || r == null)
			{
				reply("❌ Gift failed. (" + (err ?? "unknown") + ")");
			}
			else
			{
				CoinManager.SetBalance(fromSteam64, r.balance);
				reply($"\ud83c\udf81 Sent {amount} Valcoins to {toName}.  Your balance: {r.balance}");
			}
		}));
	}

	private static bool ResolveTargetByName(string name, out string steam64)
	{
		steam64 = null;
		if ((Object)(object)ZNet.instance == (Object)null)
		{
			return false;
		}
		foreach (ZNetPeer connectedPeer in ZNet.instance.GetConnectedPeers())
		{
			if (connectedPeer.m_playerName != null && connectedPeer.m_playerName.Equals(name, StringComparison.OrdinalIgnoreCase))
			{
				steam64 = SteamIdResolver.FromPeer(connectedPeer);
				return !string.IsNullOrEmpty(steam64);
			}
		}
		return false;
	}
}
public static class TopDonorsFetcher
{
	private class TopResp
	{
		public Entry[] donors;
	}

	private class Entry
	{
		public int rank;

		public string name;

		public int total_coins;
	}

	public static void Fetch(Action<string> emit, int limit = 5)
	{
		if (!Config.Ready)
		{
			emit("⚠\ufe0f Leaderboard unavailable.");
			return;
		}
		((MonoBehaviour)SharedCoroutineRunner.Instance).StartCoroutine(BackendClient.Get($"/api/leaderboard/top?limit={limit}", delegate(bool ok, TopResp r, string err)
		{
			if (!ok || r?.donors == null)
			{
				emit("❌ Couldn't fetch leaderboard. (" + (err ?? "unknown") + ")");
			}
			else if (r.donors.Length == 0)
			{
				emit("\ud83c\udfc6 No donors yet. Be the first — type /donate!");
			}
			else
			{
				emit("\ud83c\udfc6 Top donors:");
				Entry[] donors = r.donors;
				foreach (Entry entry in donors)
				{
					emit(string.Format("  {0}. {1}  —  {2} coins", entry.rank, entry.name ?? "Anonymous", entry.total_coins));
				}
			}
		}));
	}
}
public class GrantPoller : MonoBehaviour
{
	public class Grant
	{
		public long id;

		public string steam64;

		public int coins;

		public string source;

		public string note;

		public string created_at;
	}

	private class PendingResponse
	{
		public List<Grant> grants;
	}

	private class AckRequest
	{
		public List<long> ids;
	}

	private class AckResponse
	{
		public int acked;
	}

	private Coroutine _loop;

	private void Start()
	{
		_loop = ((MonoBehaviour)this).StartCoroutine(Loop());
	}

	private void OnDestroy()
	{
		if (_loop != null)
		{
			((MonoBehaviour)this).StopCoroutine(_loop);
		}
	}

	private IEnumerator Loop()
	{
		while ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
		{
			yield return (object)new WaitForSeconds(2f);
		}
		while (true)
		{
			yield return (object)new WaitForSeconds(Mathf.Max(2f, Config.PollIntervalSeconds));
			if (Config.Ready)
			{
				yield return Tick();
			}
		}
	}

	private IEnumerator Tick()
	{
		PendingResponse pending = null;
		string err = null;
		yield return BackendClient.Get("/api/grants/pending?limit=50", delegate(bool ok, PendingResponse r, string e)
		{
			if (ok)
			{
				pending = r;
			}
			else
			{
				err = e;
			}
		});
		if (err != null)
		{
			Debug.LogWarning((object)("[Valcoin] poll failed: " + err));
		}
		else
		{
			if (pending?.grants == null || pending.grants.Count == 0)
			{
				yield break;
			}
			List<long> list = new List<long>(pending.grants.Count);
			foreach (Grant grant in pending.grants)
			{
				try
				{
					bool num = CoinManager.TryApplyGrant(grant.id, grant.steam64, grant.coins);
					int balance = CoinManager.GetBalance(grant.steam64);
					if (num)
					{
						Player val = SteamIdResolver.OnlinePlayerFor(grant.steam64);
						if ((Object)(object)val != (Object)null)
						{
							((Character)val).Message((MessageType)1, $"<color=yellow>+{grant.coins} Valcoins!</color>  Balance: {balance}", 0, (Sprite)null);
						}
						else
						{
							Debug.Log((object)$"[Valcoin] +{grant.coins} to {grant.steam64} (offline). Balance: {balance}");
						}
					}
					else
					{
						Debug.Log((object)$"[Valcoin] grant {grant.id} replay (already applied locally); will re-ack.");
					}
					list.Add(grant.id);
				}
				catch (Exception ex)
				{
					Debug.LogError((object)$"[Valcoin] failed to apply grant {grant.id}: {ex.Message}");
				}
			}
			if (list.Count <= 0)
			{
				yield break;
			}
			yield return BackendClient.Post("/api/grants/ack", new AckRequest
			{
				ids = list
			}, delegate(bool ok, AckResponse r, string e)
			{
				if (!ok)
				{
					Debug.LogWarning((object)("[Valcoin] ack failed (will retry next tick): " + e));
				}
			});
		}
	}
}
public static class PerkManager
{
	public class Pos
	{
		public float x;

		public float y;

		public float z;
	}

	public class PlayerPerks
	{
		public HashSet<string> perks = new HashSet<string>();

		public Dictionary<string, int> charges = new Dictionary<string, int>();

		public string title;

		public Pos home;

		public string homeCooldownUntilUtc;
	}

	private class State
	{
		public Dictionary<string, PlayerPerks> players = new Dictionary<string, PlayerPerks>();
	}

	private static readonly string SaveDir = Path.Combine(Paths.ConfigPath, "valcoin_data");

	private static readonly string SaveFile = Path.Combine(SaveDir, "perks.json");

	private static State _state = new State();

	public static void Load()
	{
		try
		{
			Directory.CreateDirectory(SaveDir);
			if (File.Exists(SaveFile))
			{
				_state = JsonConvert.DeserializeObject<State>(File.ReadAllText(SaveFile)) ?? new State();
				if (_state.players == null)
				{
					_state.players = new Dictionary<string, PlayerPerks>();
				}
			}
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[PerkManager] Failed to load: " + ex.Message));
			_state = new State();
		}
	}

	public static void Save()
	{
		try
		{
			File.WriteAllText(SaveFile, JsonConvert.SerializeObject((object)_state, (Formatting)1));
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[PerkManager] Failed to save: " + ex.Message));
		}
	}

	public static PlayerPerks Get(string steam64)
	{
		if (string.IsNullOrEmpty(steam64))
		{
			return new PlayerPerks();
		}
		if (!_state.players.TryGetValue(steam64, out var value))
		{
			value = new PlayerPerks();
			_state.players[steam64] = value;
		}
		return value;
	}

	public static bool Has(string steam64, string perk)
	{
		if (string.IsNullOrEmpty(steam64))
		{
			return false;
		}
		if (_state.players.TryGetValue(steam64, out var value))
		{
			return value.perks.Contains(perk);
		}
		return false;
	}

	public static void Grant(string steam64, string perk)
	{
		Get(steam64).perks.Add(perk);
		Save();
	}

	public static void AddCharges(string steam64, string perk, int n)
	{
		PlayerPerks playerPerks = Get(steam64);
		playerPerks.charges.TryGetValue(perk, out var value);
		playerPerks.charges[perk] = value + n;
		Save();
	}

	public static int Charges(string steam64, string perk)
	{
		if (string.IsNullOrEmpty(steam64))
		{
			return 0;
		}
		if (!_state.players.TryGetValue(steam64, out var value) || !value.charges.TryGetValue(perk, out var value2))
		{
			return 0;
		}
		return value2;
	}

	public static bool ConsumeCharge(string steam64, string perk)
	{
		PlayerPerks playerPerks = Get(steam64);
		if (!playerPerks.charges.TryGetValue(perk, out var value) || value <= 0)
		{
			return false;
		}
		playerPerks.charges[perk] = value - 1;
		Save();
		return true;
	}

	public static void SetTitle(string steam64, string title)
	{
		Get(steam64).title = title;
		Save();
	}

	public static string Title(string steam64)
	{
		if (!_state.players.TryGetValue(steam64, out var value))
		{
			return null;
		}
		return value.title;
	}

	public static void SetHome(string steam64, Vector3 pos)
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		Get(steam64).home = new Pos
		{
			x = pos.x,
			y = pos.y,
			z = pos.z
		};
		Save();
	}

	public static Vector3? Home(string steam64)
	{
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		if (!_state.players.TryGetValue(steam64, out var value) || value.home == null)
		{
			return null;
		}
		return new Vector3(value.home.x, value.home.y, value.home.z);
	}

	public static int HomeCooldownRemaining(string steam64)
	{
		PlayerPerks playerPerks = Get(steam64);
		if (string.IsNullOrEmpty(playerPerks.homeCooldownUntilUtc))
		{
			return 0;
		}
		if (!DateTime.TryParse(playerPerks.homeCooldownUntilUtc, null, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var result))
		{
			return 0;
		}
		int val = (int)(result - DateTime.UtcNow).TotalSeconds;
		return Math.Max(0, val);
	}

	public static void StartHomeCooldown(string steam64, int seconds)
	{
		Get(steam64).homeCooldownUntilUtc = DateTime.UtcNow.AddSeconds(seconds).ToString("o");
		Save();
	}
}
[BepInPlugin("com.taeguk.valheimdonations", "Valheim Donations", "5.1.0")]
public class Plugin : BaseUnityPlugin
{
	public static HashSet<string> AdminSteamIDs = new HashSet<string>();

	private Harmony _harmony;

	private static readonly string AdminConfigPath = Path.Combine(Paths.ConfigPath, "valcoin_admins.yaml");

	private void Awake()
	{
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Expected O, but got Unknown
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_005a: Expected O, but got Unknown
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_006a: Expected O, but got Unknown
		((BaseUnityPlugin)this).Logger.LogInfo((object)"[Valheim Donations] Plugin loaded");
		Config.Load();
		EnsureAdminFile();
		LoadAdmins();
		CoinManager.Load();
		PerkManager.Load();
		Catalog.Load();
		GameObject val = new GameObject("ValcoinGrantPoller");
		val.AddComponent<GrantPoller>();
		Object.DontDestroyOnLoad((Object)val);
		GameObject val2 = new GameObject("ValcoinCatalogSync");
		val2.AddComponent<CatalogSync>();
		Object.DontDestroyOnLoad((Object)val2);
		_harmony = new Harmony("com.taeguk.valheimdonations");
		_harmony.PatchAll();
		((MonoBehaviour)this).StartCoroutine(InitRpcsWhenReady());
		SpawnClientUiIfNotServer();
		((BaseUnityPlugin)this).Logger.LogInfo((object)$"Startup complete. Admins: {AdminSteamIDs.Count}, Backend ready: {Config.Ready}");
	}

	private void OnDestroy()
	{
		Harmony harmony = _harmony;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
	}

	private IEnumerator InitRpcsWhenReady()
	{
		while ((Object)(object)ZNet.instance == (Object)null)
		{
			yield return null;
		}
		bool serverSide = ZNet.instance.IsServer();
		yield return RpcLayer.RegisterWhenReady(serverSide);
	}

	private void SpawnClientUiIfNotServer()
	{
		((MonoBehaviour)this).StartCoroutine(SpawnUiWhenZnetReady());
	}

	private IEnumerator SpawnUiWhenZnetReady()
	{
		while ((Object)(object)ZNet.instance == (Object)null)
		{
			yield return null;
		}
		if (!ZNet.instance.IsServer() || !ZNet.instance.IsDedicated())
		{
			GameObject val = new GameObject("ValcoinDonationPanel");
			val.AddComponent<DonationPanel>();
			Object.DontDestroyOnLoad((Object)val);
			GameObject val2 = new GameObject("ValcoinDonationCodex");
			val2.AddComponent<DonationCodex>();
			Object.DontDestroyOnLoad((Object)val2);
		}
	}

	private static void EnsureAdminFile()
	{
		try
		{
			Directory.CreateDirectory(Paths.ConfigPath);
			if (!File.Exists(AdminConfigPath))
			{
				File.WriteAllText(AdminConfigPath, "# Valcoin Admins (Steam64 IDs)\n# ------------------------------------------------------------\n# Add Steam64 IDs here to grant admin permission for:\n#   /givecoins <playerName> <amount>\n#   /removecoins <playerName> <amount>\n#\n# Find your Steam64 at https://steamid.io\n# Restart the server after changes.\nadmins:\n  - 76561198012345678   # <-- replace\n");
				Debug.LogWarning((object)("[Valcoin] Created admin file template at: " + AdminConfigPath));
			}
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[Valcoin] Failed to create admin YAML: " + ex.Message));
		}
	}

	private static void LoadAdmins()
	{
		try
		{
			HashSet<string> hashSet = new HashSet<string>();
			if (!File.Exists(AdminConfigPath))
			{
				AdminSteamIDs = hashSet;
				return;
			}
			bool flag = false;
			Regex regex = new Regex("^\\s*-\\s*(\\d{17})\\b", RegexOptions.Compiled);
			string[] array = File.ReadAllLines(AdminConfigPath);
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].TrimEnd(Array.Empty<char>());
				if (text.TrimStart(Array.Empty<char>()).StartsWith("#"))
				{
					continue;
				}
				if (!flag)
				{
					if (Regex.IsMatch(text, "^\\s*admins\\s*:\\s*$"))
					{
						flag = true;
					}
					continue;
				}
				Match match = regex.Match(text);
				if (match.Success)
				{
					hashSet.Add(match.Groups[1].Value);
				}
				else if (Regex.IsMatch(text, "^\\s*\\w+\\s*:\\s*$"))
				{
					break;
				}
			}
			AdminSteamIDs = hashSet;
			Debug.Log((object)$"[Valcoin] Loaded {hashSet.Count} admin Steam64 ID(s).");
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[Valcoin] Failed to load admin YAML: " + ex.Message));
			AdminSteamIDs = new HashSet<string>();
		}
	}
}
public static class RpcLayer
{
	public const string ActionRpc = "vc_action";

	public const string PanelRpc = "vc_panel";

	public const string CatalogRpc = "vc_catalog";

	private static bool _registeredServer;

	private static bool _registeredClient;

	public static Action<string> OnPanelMessage;

	public static IEnumerator RegisterWhenReady(bool serverSide)
	{
		while (ZRoutedRpc.instance == null)
		{
			yield return null;
		}
		if (serverSide && !_registeredServer)
		{
			ZRoutedRpc.instance.Register<string>("vc_action", (Action<long, string>)HandleActionOnServer);
			_registeredServer = true;
			Debug.Log((object)"[Valcoin] RPC registered (server): vc_action");
		}
		if (!serverSide && !_registeredClient)
		{
			ZRoutedRpc.instance.Register<string>("vc_panel", (Action<long, string>)HandlePanelOnClient);
			ZRoutedRpc.instance.Register<string>("vc_catalog", (Action<long, string>)HandleCatalogOnClient);
			_registeredClient = true;
			Debug.Log((object)"[Valcoin] RPC registered (client): vc_panel, vc_catalog");
		}
	}

	public static void SendAction(string action)
	{
		if (ZRoutedRpc.instance == null)
		{
			return;
		}
		try
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "vc_action", new object[1] { action });
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[Valcoin] SendAction failed: " + ex.Message));
		}
	}

	private static void HandleActionOnServer(long senderPeerID, string action)
	{
		if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
		{
			return;
		}
		try
		{
			UiActionRouter.Execute(senderPeerID, action);
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[Valcoin] HandleActionOnServer error: " + ex));
		}
	}

	public static void PushPanelMessage(long peerID, string msg)
	{
		if (ZRoutedRpc.instance == null)
		{
			return;
		}
		try
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(peerID, "vc_panel", new object[1] { msg });
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[Valcoin] PushPanelMessage failed: " + ex.Message));
		}
	}

	private static void HandlePanelOnClient(long _from, string msg)
	{
		if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
		{
			return;
		}
		try
		{
			OnPanelMessage?.Invoke(msg);
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[Valcoin] OnPanelMessage handler failed: " + ex));
		}
	}

	public static void BroadcastCatalog(string json)
	{
		if (ZRoutedRpc.instance == null || string.IsNullOrEmpty(json))
		{
			return;
		}
		try
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "vc_catalog", new object[1] { json });
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[Valcoin] BroadcastCatalog failed: " + ex.Message));
		}
	}

	private static void HandleCatalogOnClient(long _from, string json)
	{
		if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
		{
			return;
		}
		try
		{
			Catalog.ApplyRemote(json);
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[Valcoin] Catalog apply failed: " + ex));
		}
	}
}
public static class ShopHandler
{
	public delegate void TellFn(string msg);

	private class SpendResp
	{
		public string status;

		public int balance;

		public int spent;
	}

	public static void Buy(string steam64, string skuId, TellFn tell, Action onSuccess = null)
	{
		if (string.IsNullOrEmpty(steam64))
		{
			tell("⚠\ufe0f Couldn't resolve your Steam ID.");
			return;
		}
		if (!Config.Ready)
		{
			tell("⚠\ufe0f Shop is offline (backend not configured).");
			return;
		}
		if (!Catalog.Items.TryGetValue(skuId, out var sku))
		{
			tell("⚠\ufe0f Unknown SKU: " + skuId + ". Type /shop for the list.");
			return;
		}
		int balance = CoinManager.GetBalance(steam64);
		if (balance < sku.Price)
		{
			tell($"\ud83d\udcb8 Not enough Valcoins ({balance} / {sku.Price}).");
			return;
		}
		if (sku.Effect == "grant_perk" && PerkManager.Has(steam64, sku.Perk))
		{
			tell("✅ You already own \"" + sku.Name + "\".");
			return;
		}
		if (sku.Effect == "grant_item")
		{
			if (!string.IsNullOrEmpty(sku.RequiresBoss) && !BossGateSatisfied(sku.RequiresBoss))
			{
				tell("\ud83d\udd12 \"" + sku.Name + "\" unlocks after a later boss. Keep progressing!");
				return;
			}
			if (SteamIdResolver.ZdoFor(steam64) == null)
			{
				tell("⚠\ufe0f Couldn't find your character to deliver items. Spawn in, then try again.");
				return;
			}
		}
		string idempotency_key = $"buy-{skuId}-{Guid.NewGuid():N}";
		var body = new
		{
			steam64 = steam64,
			sku = skuId,
			coins = sku.Price,
			idempotency_key = idempotency_key,
			weekly_cap = ((sku.Effect == "grant_item") ? sku.WeeklyCap : 0)
		};
		((MonoBehaviour)SharedCoroutineRunner.Instance).StartCoroutine(BackendClient.Post("/api/spend", body, delegate(bool ok, SpendResp r, string err)
		{
			if (!ok || r == null)
			{
				if (err != null && err.Contains("429"))
				{
					tell(("\ud83d\uddd3\ufe0f Weekly limit reached for \"" + sku.Name + "\". " + ExtractDetail(err)).TrimEnd(Array.Empty<char>()));
				}
				else if (err != null && err.Contains("402"))
				{
					tell("\ud83d\udcb8 The server says you don't have enough Valcoins. Try /coins.");
				}
				else
				{
					tell("❌ Purchase failed. (" + (err ?? "unknown") + ")");
				}
			}
			else
			{
				CoinManager.SetBalance(steam64, r.balance);
				if (r.status == "duplicate")
				{
					tell($"↩\ufe0f \"{sku.Name}\" was already processed. Balance: {r.balance}.");
					onSuccess?.Invoke();
				}
				else
				{
					ApplyEffect(steam64, sku, tell);
					onSuccess?.Invoke();
				}
			}
		}));
	}

	private static void ApplyEffect(string steam64, Catalog.Sku sku, TellFn tell)
	{
		switch (sku.Effect)
		{
		case "grant_perk":
			PerkManager.Grant(steam64, sku.Perk);
			tell("\ud83c\udf89 Purchased \"" + sku.Name + "\" — perk \"" + sku.Perk + "\" unlocked!");
			break;
		case "add_charges":
			PerkManager.AddCharges(steam64, sku.Perk, sku.Charges);
			tell($"\ud83c\udf89 Purchased \"{sku.Name}\" — +{sku.Charges} {sku.Perk} charge(s).");
			break;
		case "grant_item":
		{
			int num = GrantItems(steam64, sku.Item);
			if (num > 0)
			{
				tell($"\ud83c\udf81 Purchased \"{sku.Name}\" — {num} item stack(s) dropped at your feet.");
			}
			else
			{
				tell("⚠\ufe0f \"" + sku.Name + "\" was charged but no items could be spawned (bad prefab id?). Tell an admin.");
			}
			break;
		}
		default:
			Debug.LogWarning((object)("[Valcoin] Unknown effect type \"" + sku.Effect + "\" for SKU " + sku.Id));
			tell("⚠\ufe0f \"" + sku.Name + "\" was charged but the effect couldn't be applied. Tell an admin.");
			break;
		}
	}

	private static int GrantItems(string steam64, string itemSpec)
	{
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0142: Unknown result type (might be due to invalid IL or missing references)
		//IL_0166: Unknown result type (might be due to invalid IL or missing references)
		//IL_016b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0170: Unknown result type (might be due to invalid IL or missing references)
		//IL_0174: Unknown result type (might be due to invalid IL or missing references)
		//IL_0176: Unknown result type (might be due to invalid IL or missing references)
		if (string.IsNullOrEmpty(itemSpec))
		{
			return 0;
		}
		if ((Object)(object)ZNetScene.instance == (Object)null)
		{
			Debug.LogWarning((object)"[Valcoin] grant_item: no ZNetScene.");
			return 0;
		}
		ZDO val = SteamIdResolver.ZdoFor(steam64);
		if (val == null)
		{
			Debug.LogWarning((object)"[Valcoin] grant_item: no ZDO for buyer.");
			return 0;
		}
		Vector3 position = val.GetPosition();
		int num = 0;
		string[] array = itemSpec.Split(new char[1] { ',' });
		for (int i = 0; i < array.Length; i++)
		{
			string text = array[i].Trim();
			if (text.Length == 0)
			{
				continue;
			}
			string text2 = text;
			int result = 1;
			int num2 = text.LastIndexOf(':');
			if (num2 > 0)
			{
				text2 = text.Substring(0, num2).Trim();
				if (!int.TryParse(text.Substring(num2 + 1), out result) || result < 1)
				{
					result = 1;
				}
			}
			GameObject prefab = ZNetScene.instance.GetPrefab(text2);
			if ((Object)(object)prefab == (Object)null)
			{
				Debug.LogWarning((object)("[Valcoin] grant_item: unknown prefab \"" + text2 + "\" — skipped."));
				continue;
			}
			ItemDrop component = prefab.GetComponent<ItemDrop>();
			int num3 = ((!((Object)(object)component != (Object)null) || component.m_itemData?.m_shared == null) ? 1 : Mathf.Max(1, component.m_itemData.m_shared.m_maxStackSize));
			int num4 = result;
			while (num4 > 0)
			{
				int num5 = Mathf.Min(num4, num3);
				Vector3 val2 = position + new Vector3(Random.Range(-1f, 1f), 1.5f, Random.Range(-1f, 1f));
				try
				{
					ItemDrop component2 = Object.Instantiate<GameObject>(prefab, val2, Quaternion.identity).GetComponent<ItemDrop>();
					if ((Object)(object)component2 != (Object)null)
					{
						component2.m_itemData.m_stack = num5;
					}
					num++;
				}
				catch (Exception ex)
				{
					Debug.LogError((object)("[Valcoin] grant_item: failed to spawn " + text2 + ": " + ex.Message));
				}
				num4 -= num5;
			}
		}
		return num;
	}

	private static bool BossGateSatisfied(string bossKey)
	{
		if (string.IsNullOrEmpty(bossKey))
		{
			return true;
		}
		try
		{
			if ((Object)(object)ZoneSystem.instance == (Object)null)
			{
				return true;
			}
			return ZoneSystem.instance.GetGlobalKey(bossKey);
		}
		catch (Exception ex)
		{
			Debug.LogWarning((object)("[Valcoin] boss-gate check failed for \"" + bossKey + "\": " + ex.Message));
			return true;
		}
	}

	private static string ExtractDetail(string err)
	{
		if (string.IsNullOrEmpty(err))
		{
			return "";
		}
		int num = err.IndexOf("\"detail\":\"", StringComparison.Ordinal);
		if (num < 0)
		{
			return "";
		}
		num += "\"detail\":\"".Length;
		int num2 = err.IndexOf('"', num);
		if (num2 <= num)
		{
			return "";
		}
		return err.Substring(num, num2 - num);
	}
}
public static class SteamIdResolver
{
	private static readonly Regex Steam64Re = new Regex("^7656119\\d{10}$", RegexOptions.Compiled);

	private static readonly Regex PlayFabIdRe = new Regex("^[A-Za-z0-9]{8,32}$", RegexOptions.Compiled);

	private const string PlayFabPrefix = "PlayFab_";

	public static string FromPeer(ZNetPeer peer)
	{
		if (peer == null)
		{
			return null;
		}
		try
		{
			ZRpc rpc = peer.m_rpc;
			ISocket val = ((rpc != null) ? rpc.GetSocket() : null);
			if (val == null)
			{
				return null;
			}
			string hostName = val.GetHostName();
			if (!string.IsNullOrEmpty(hostName) && Steam64Re.IsMatch(hostName))
			{
				return hostName;
			}
			if ((((object)val).GetType().Name ?? "").IndexOf("PlayFab", StringComparison.OrdinalIgnoreCase) >= 0 && !string.IsNullOrEmpty(hostName) && PlayFabIdRe.IsMatch(hostName))
			{
				return "PlayFab_" + hostName;
			}
		}
		catch
		{
		}
		return null;
	}

	public static string FromNetworkUserId(string nuid)
	{
		if (string.IsNullOrEmpty(nuid))
		{
			return null;
		}
		if (nuid.StartsWith("Steam_"))
		{
			string text = nuid.Substring("Steam_".Length);
			if (!Steam64Re.IsMatch(text))
			{
				return null;
			}
			return text;
		}
		if (nuid.StartsWith("Pla_") || nuid.StartsWith("PlayFab_"))
		{
			string text2 = (nuid.StartsWith("Pla_") ? nuid.Substring("Pla_".Length) : nuid.Substring("PlayFab_".Length));
			if (!PlayFabIdRe.IsMatch(text2))
			{
				return null;
			}
			return "PlayFab_" + text2;
		}
		if (!Steam64Re.IsMatch(nuid))
		{
			return null;
		}
		return nuid;
	}

	public static string FromPeerId(long peerId)
	{
		if (peerId == 0L || (Object)(object)ZNet.instance == (Object)null)
		{
			return null;
		}
		return FromPeer(ZNet.instance.GetPeer(peerId));
	}

	public static ZNetPeer PeerFor(string steam64)
	{
		if (string.IsNullOrEmpty(steam64) || (Object)(object)ZNet.instance == (Object)null)
		{
			return null;
		}
		return ((IEnumerable<ZNetPeer>)ZNet.instance.GetConnectedPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => FromPeer(p) == steam64));
	}

	public static ZDO ZdoFor(string steam64)
	{
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		ZNetPeer val = PeerFor(steam64);
		if (val == null || ZDOMan.instance == null)
		{
			return null;
		}
		return ZDOMan.instance.GetZDO(val.m_characterID);
	}

	public static Player OnlinePlayerFor(string steam64)
	{
		if (string.IsNullOrEmpty(steam64) || (Object)(object)ZNet.instance == (Object)null)
		{
			return null;
		}
		ZNetPeer val = ((IEnumerable<ZNetPeer>)ZNet.instance.GetConnectedPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => FromPeer(p) == steam64));
		if (val == null)
		{
			return null;
		}
		string name = val.m_playerName;
		return ((IEnumerable<Player>)Player.GetAllPlayers()).FirstOrDefault((Func<Player, bool>)((Player p) => (Object)(object)p != (Object)null && p.GetPlayerName().Equals(name, StringComparison.OrdinalIgnoreCase)));
	}
}
public static class UiActionRouter
{
	public static void Execute(long senderPeerID, string action)
	{
		if (string.IsNullOrEmpty(action))
		{
			return;
		}
		ZNet instance = ZNet.instance;
		ZNetPeer val = ((instance != null) ? instance.GetPeer(senderPeerID) : null);
		if (val == null)
		{
			return;
		}
		string text = SteamIdResolver.FromPeer(val);
		string playerName = val.m_playerName;
		int num = action.IndexOf(':');
		string text2;
		string text3;
		if (num < 0)
		{
			text2 = action;
			text3 = "";
		}
		else
		{
			text2 = action.Substring(0, num);
			text3 = action.Substring(num + 1);
		}
		switch (text2)
		{
		case "donate":
			DonateFlow.Run(text, playerName, Reply);
			break;
		case "buy":
			ShopHandler.Buy(text, text3.Trim().ToLowerInvariant(), Reply);
			break;
		case "gift":
			DoGift(text, playerName, text3, Reply);
			break;
		case "title":
			DoTitle(text, text3, Reply);
			break;
		case "topdonors":
			TopDonorsFetcher.Fetch(delegate(string reply)
			{
				Reply(reply);
			});
			break;
		default:
			Reply("⚠\ufe0f Unknown UI action: " + text2);
			break;
		}
		void Reply(string msg)
		{
			RpcLayer.PushPanelMessage(senderPeerID, msg);
		}
	}

	private static void DoGift(string fromSteam64, string fromName, string rest, Action<string> reply)
	{
		string[] array = rest.Split(new char[1] { ':' }, 2);
		int result;
		if (array.Length != 2)
		{
			reply("⚠\ufe0f Bad gift format.");
		}
		else if (!int.TryParse(array[1].Trim(), out result) || result <= 0)
		{
			reply("⚠\ufe0f Amount must be a positive number.");
		}
		else
		{
			GiftFlow.Run(fromSteam64, fromName, array[0].Trim(), result, reply);
		}
	}

	private static void DoTitle(string steam64, string rest, Action<string> reply)
	{
		if (string.IsNullOrEmpty(steam64))
		{
			reply("⚠\ufe0f Couldn't resolve your Steam ID.");
		}
		else if (!PerkManager.Has(steam64, "chat_title"))
		{
			reply("\ud83d\udd12 You need the \"chat_title\" perk. Buy it from the shop tab.");
		}
		else if (rest.Equals("clear", StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(rest))
		{
			PerkManager.SetTitle(steam64, null);
			reply("✅ Title cleared.");
		}
		else if (rest.Length > 16)
		{
			reply("⚠\ufe0f 16 characters or fewer.");
		}
		else if (!Regex.IsMatch(rest, "^[\\w \\-'\\.!?]+$"))
		{
			reply("⚠\ufe0f Letters, numbers and basic punctuation only.");
		}
		else
		{
			PerkManager.SetTitle(steam64, rest);
			reply("✅ Title set to [" + rest + "].");
		}
	}
}
public class WelcomeBanner : MonoBehaviour
{
	private const float DelaySeconds = 5f;

	private static bool _shownThisSession;

	public static void ResetForNewSession()
	{
		_shownThisSession = false;
	}

	public static void Show()
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Expected O, but got Unknown
		if (!_shownThisSession && Config.WelcomeEnabled)
		{
			GameObject val = new GameObject("ValcoinWelcome");
			val.AddComponent<WelcomeBanner>();
			Object.DontDestroyOnLoad((Object)val);
		}
	}

	private IEnumerator Start()
	{
		while ((Object)(object)Player.m_localPlayer == (Object)null)
		{
			yield return null;
		}
		yield return (object)new WaitForSeconds(5f);
		if (!((Object)(object)Player.m_localPlayer == (Object)null))
		{
			string text = (string.IsNullOrEmpty(Config.WelcomeMessage) ? ("\ud83d\udc9b  Press " + (Config.UiToggleKey ?? "F8") + "  or type /donate  to support the server") : Config.WelcomeMessage);
			((Character)Player.m_localPlayer).Message((MessageType)1, text, 0, (Sprite)null);
			_shownThisSession = true;
			Object.Destroy((Object)(object)((Component)this).gameObject);
		}
	}
}
[HarmonyPatch(typeof(Player), "OnSpawned")]
public static class WelcomeOnSpawn
{
	private static void Postfix(Player __instance)
	{
		if (!((Object)(object)ZNet.instance == (Object)null) && (!ZNet.instance.IsServer() || !ZNet.instance.IsDedicated()) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
		{
			WelcomeBanner.Show();
		}
	}
}