Decompiled source of How to Webfish v1.0.1

BepInEx/plugins/WFxHTF/WFxHTF.Adapter.dll

Decompiled a day 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.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FishNet;
using FishNet.Connection;
using FishNet.Managing.Client;
using FishNet.Managing.Object;
using FishNet.Object;
using HarmonyLib;
using Steamworks;
using UnityEngine;
using UnityEngine.SceneManagement;
using WFxHTF.Core;
using WFxHTF.Data;
using WFxHTF.Runtime;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("WFxHTF")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+166b9aae0d28865ba70c4894450aeb4db4b21aeb")]
[assembly: AssemblyProduct("WFxHTF.Adapter")]
[assembly: AssemblyTitle("WFxHTF.Adapter")]
[assembly: AssemblyVersion("0.1.0.0")]
namespace WFxHTF.Adapter;

[BepInPlugin("com.wfxhtf.adapter", "WFxHTF Adapter", "1.0.1")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public sealed class AdapterPlugin : BaseUnityPlugin
{
	public const string Guid = "com.wfxhtf.adapter";

	public static ManualLogSource Log;

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

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

	private Harmony _harmony;

	private ConfigEntry<KeyCode> _placeKey;

	private ConfigEntry<bool> _economyLog;

	private ConfigEntry<KeyCode> _lureKey;

	private ConfigEntry<KeyCode> _charmKey;

	private void Awake()
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		_harmony = new Harmony("com.wfxhtf.adapter");
		_placeKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keys", "PlaceAquarium", (KeyCode)112, "Host: place the packed aquarium.");
		_lureKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keys", "WebFishingLure", (KeyCode)108, "Switch your Web Fishing Lure on and off (once you've bought it).");
		_charmKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keys", "ShinyCharm", (KeyCode)108, "Ctrl + this switches your Shiny Charm on and off (once you've bought it).");
		_economyLog = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "EconomyLog", false, "Host: log catches, sales and tank income to %LOCALAPPDATA%\\WFxHTF\\logs (economy check).");
		Hook("native journal", delegate
		{
			GameHooks.NativeJournalOpen = StaticBool("PlayerThinking", "IsThinking");
			GameHooks.NativeJournalHint = "Open your journal (Tab) first, then press " + (((object)Unsafe.As<KeyCode, KeyCode>(ref Plugin.Instance.JournalPanel?.ToggleKey)/*cast due to .constrained prefix*/).ToString() ?? "J");
		});
		Hook("input block", delegate
		{
			InputBlockPatch.Install(_harmony);
		});
		Hook("cursor", delegate
		{
			InputBlockPatch.InstallCursor();
		});
		bool num = Hook("world aquarium", InstallTank);
		if (num)
		{
			Hook("save hook", delegate
			{
				SavePatch.Install(_harmony);
			});
		}
		if (num)
		{
			Hook("interact prompt", delegate
			{
				TankInteractable.Check();
			});
		}
		if (num)
		{
			Hook("WF fish", InstallWfFish);
		}
		if (num)
		{
			Hook("slot machine", delegate
			{
				SlotMachinePatch.Install(_harmony);
			});
		}
		if (num)
		{
			Hook("shiny creatures", delegate
			{
				NativeShiny.Install(_harmony);
				Plugin instance2 = Plugin.Instance;
				if ((Object)(object)((instance2 != null) ? instance2.Console : null) != (Object)null)
				{
					NativeShiny.Instance.RegisterCommands(Plugin.Instance.Console);
				}
			});
		}
		if (num)
		{
			Hook("TNT fish", delegate
			{
				if (WfFishService.Instance == null)
				{
					throw new InvalidOperationException("needs WF fish");
				}
				WfFishService.InstallTnt(_harmony);
			});
		}
		if (num)
		{
			Hook("hotbar icon", delegate
			{
				HotbarIconPatch.Install(_harmony);
			});
		}
		if (num)
		{
			Hook("rain", delegate
			{
				WeatherHooks.Install();
				TankService.Instance.RainOk = true;
			});
		}
		if (num)
		{
			Hook("tank attacker", delegate
			{
				ThreatController.Check();
				TankService.Instance.ThreatHookOk = WfFishService.Instance != null;
			});
		}
		if (num)
		{
			Hook("multiplayer", InstallNet);
		}
		if (num)
		{
			Hook("shop", delegate
			{
				//IL_002a: Unknown result type (might be due to invalid IL or missing references)
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				//IL_003b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0040: Unknown result type (might be due to invalid IL or missing references)
				if (WfFishService.Instance == null)
				{
					throw new InvalidOperationException("needs WF fish");
				}
				KeyItemService.Check();
				KeyItemService keyItemService = ((Component)this).gameObject.AddComponent<KeyItemService>();
				keyItemService.LureKey = _lureKey.Value;
				keyItemService.CharmKey = _charmKey.Value;
				Plugin instance2 = Plugin.Instance;
				if ((Object)(object)((instance2 != null) ? instance2.Console : null) != (Object)null)
				{
					keyItemService.RegisterCommands(Plugin.Instance.Console);
				}
			});
		}
		if (num)
		{
			Hook("economy log", delegate
			{
				EconomyLog.Install(_harmony, _economyLog.Value);
				Plugin instance2 = Plugin.Instance;
				if ((Object)(object)((instance2 != null) ? instance2.Console : null) != (Object)null)
				{
					EconomyLog.Instance.RegisterCommands(Plugin.Instance.Console);
				}
			});
		}
		string text = $"{_ok.Count} hooks ok" + ((_ok.Count > 0) ? (" (" + string.Join(", ", _ok) + ")") : "") + ((_failed.Count > 0) ? ("; FAILED: " + string.Join("; ", _failed)) : "");
		Plugin instance = Plugin.Instance;
		if (instance != null)
		{
			instance.Feature("adapter", text);
		}
		if (_failed.Count > 0)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("adapter: " + text));
		}
		else
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)("adapter: " + text));
		}
	}

	private void InstallTank()
	{
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		Game.Check();
		GamePorts.Check();
		GameHooks.NativeFishModel = NativeFish.Model;
		GameHooks.NativeFishName = NativeFish.Name;
		TankService tankService = ((Component)this).gameObject.AddComponent<TankService>();
		tankService.PlaceKey = _placeKey.Value;
		Plugin instance = Plugin.Instance;
		if ((Object)(object)((instance != null) ? instance.Console : null) != (Object)null)
		{
			tankService.RegisterCommands(Plugin.Instance.Console);
		}
	}

	private void InstallNet()
	{
		NetService.Check();
		NetService netService = ((Component)this).gameObject.AddComponent<NetService>();
		Plugin instance = Plugin.Instance;
		if ((Object)(object)((instance != null) ? instance.Console : null) != (Object)null)
		{
			netService.RegisterCommands(Plugin.Instance.Console);
		}
	}

	private void InstallWfFish()
	{
		WfFishService.Install(_harmony);
		Plugin instance = Plugin.Instance;
		if ((Object)(object)((instance != null) ? instance.Console : null) != (Object)null)
		{
			WfFishService.Instance.RegisterCommands(Plugin.Instance.Console);
		}
	}

	private bool Hook(string feature, Action resolve)
	{
		try
		{
			resolve();
			_ok.Add(feature);
			return true;
		}
		catch (Exception ex)
		{
			_failed.Add(feature + ": " + ex.GetType().Name + ": " + ex.Message);
			return false;
		}
	}

	private static Func<bool> StaticBool(string typeName, string property)
	{
		PropertyInfo propertyInfo = (AccessTools.TypeByName(typeName) ?? throw new MissingMemberException("type " + typeName + " not found")).GetProperty(property, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingMemberException(typeName + "." + property + " not found");
		if (propertyInfo.PropertyType != typeof(bool))
		{
			throw new MissingMemberException(typeName + "." + property + " is " + propertyInfo.PropertyType.Name + ", not bool");
		}
		MethodInfo method = propertyInfo.GetGetMethod(nonPublic: true) ?? throw new MissingMemberException(typeName + "." + property + " has no getter");
		return (Func<bool>)Delegate.CreateDelegate(typeof(Func<bool>), method);
	}
}
internal sealed class EconomyLog
{
	private bool _enabled;

	private SessionTally _t = new SessionTally();

	private JsonLinesLog _log;

	private readonly HashSet<Item> _hooked = new HashSet<Item>();

	private float _nextSnapshot;

	private bool _warned;

	private const int KeepLogs = 40;

	public static EconomyLog Instance { get; private set; }

	public static string Dir => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "WFxHTF", "logs");

	private static double Now => Game.PlaytimeSeconds;

	public static void Install(Harmony h, bool enabled)
	{
		//IL_0093: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Expected O, but got Unknown
		Game.Require(typeof(MoneyManager), "SellItem", "Instance", "_money");
		Game.Require(typeof(Item), "TotalWorth", "Currency", "Creature");
		Instance = new EconomyLog
		{
			_enabled = enabled
		};
		h.Patch((MethodBase)AccessTools.Method(typeof(MoneyManager), "SellItem", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(EconomyLog), "SellPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
	}

	private void Write(string kind, params (string, object)[] fields)
	{
		try
		{
			_t.Seen(Now);
			JsonLinesLog log = _log;
			if (log != null)
			{
				log.Write(Now, kind, fields);
			}
		}
		catch (Exception ex)
		{
			if (!_warned)
			{
				_warned = true;
				AdapterPlugin.Log.LogWarning((object)("economy log: " + ex.Message));
			}
		}
	}

	public void OnWorldLoaded(string world)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Expected O, but got Unknown
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Expected O, but got Unknown
		Close();
		_t = new SessionTally();
		_hooked.Clear();
		if (!_enabled)
		{
			return;
		}
		try
		{
			Prune();
			_log = new JsonLinesLog(Path.Combine(Dir, $"economy_{DateTime.Now:yyyyMMdd_HHmmss}.jsonl"));
			Write("start", ("world", world), ("mod", "1.0.1"), ("game", Application.version));
			AdapterPlugin.Log.LogInfo((object)("economy log: " + _log.Path));
		}
		catch (Exception ex)
		{
			AdapterPlugin.Log.LogWarning((object)("economy log off: " + ex.Message));
			_log = null;
		}
	}

	public void OnWorldUnloaded()
	{
		if (_log != null)
		{
			Write("end", ("summary", _t.Report()));
		}
		Close();
	}

	private void Close()
	{
		try
		{
			JsonLinesLog log = _log;
			if (log != null)
			{
				log.Dispose();
			}
		}
		catch
		{
		}
		_log = null;
	}

	public void Tick(Aquarium tank)
	{
		if (!(Time.unscaledTime < _nextSnapshot))
		{
			_nextSnapshot = Time.unscaledTime + 60f;
			if (tank != null)
			{
				_t.Tray(tank.State.TrayCoins);
			}
			int num = (((Object)(object)MoneyManager.Instance != (Object)null) ? MoneyManager.Instance._money.Value : MoneyManager.Money);
			Write("snapshot", ("money", num), ("tray", (tank != null) ? Math.Round(tank.State.TrayCoins, 2) : 0.0), ("tankFish", tank?.State.Fish.Count ?? 0), ("players", Game.PlayerIds.Length));
		}
	}

	private static void Prune()
	{
		try
		{
			if (!Directory.Exists(Dir))
			{
				return;
			}
			foreach (FileInfo item in (from f in new DirectoryInfo(Dir).GetFiles("economy_*.jsonl")
				orderby f.Name descending
				select f).Skip(39))
			{
				item.Delete();
			}
		}
		catch (Exception ex)
		{
			AdapterPlugin.Log.LogWarning((object)("economy log: couldn't tidy old logs: " + ex.Message));
		}
	}

	public void OnBite(Item item, WfItemTag tag, string bait, bool generalLure, ulong by = 0uL, float shinyChance = 0f)
	{
		//IL_0079: Unknown result type (might be due to invalid IL or missing references)
		//IL_0093: Expected I4, but got Unknown
		if ((Object)(object)item == (Object)null)
		{
			return;
		}
		if (_hooked.Count > 200)
		{
			_hooked.RemoveWhere((Item i) => (Object)(object)i == (Object)null);
		}
		_hooked.Add(item);
		if (tag != null)
		{
			SessionTally t = _t;
			t.WfBites++;
			_t.WfQualities[Mathf.Clamp((int)tag.Quality, 0, _t.WfQualities.Length - 1)]++;
			if (tag.Shiny)
			{
				SessionTally t2 = _t;
				t2.ShinyBites++;
			}
			Write("bite", ("wf", true), ("species", tag.SpeciesId), ("quality", ((object)Unsafe.As<Quality, Quality>(ref tag.Quality)/*cast due to .constrained prefix*/).ToString()), ("shiny", tag.Shiny), ("kg", Math.Round(tag.WeightKg, 2)), ("worth", (int)((float)tag.Worth * tag.WeightMultiplier)), ("lure", tag.LureTier), ("bait", bait), ("by", by.ToString()), ("shinyChance", shinyChance));
		}
		else
		{
			if ((Object)(object)item.Creature != (Object)null)
			{
				SessionTally t3 = _t;
				t3.NativeBites++;
			}
			Write("bite", ("wf", false), ("species", NativeFish.IdOf(item)), ("creature", (Object)(object)item.Creature != (Object)null), ("worth", item.TotalWorth), ("bait", bait), ("generalLure", generalLure), ("shiny", NativeShiny.Instance?.Is(item) ?? false), ("by", by.ToString()), ("shinyChance", shinyChance));
		}
	}

	public void OnTnt(WfItemTag tag)
	{
		SessionTally t = _t;
		t.TntWf++;
		Write("tnt", ("species", tag.SpeciesId), ("quality", ((object)Unsafe.As<Quality, Quality>(ref tag.Quality)/*cast due to .constrained prefix*/).ToString()), ("shiny", tag.Shiny), ("worth", (int)((float)tag.Worth * tag.WeightMultiplier)));
	}

	public void OnDeath(Creature c, WfItemTag tag)
	{
		if (!((Object)(object)c == (Object)null) && _hooked.Remove((Item)(object)c))
		{
			if (tag != null)
			{
				SessionTally t = _t;
				t.WfLanded++;
			}
			else
			{
				SessionTally t2 = _t;
				t2.NativeLanded++;
			}
			Write("landed", ("wf", tag != null), ("species", tag?.SpeciesId ?? NativeFish.IdOf((Item)(object)c)));
		}
	}

	private static void SellPostfix(Item item)
	{
		try
		{
			if (Game.IsHost)
			{
				Instance?.OnSold(item);
			}
		}
		catch (Exception ex)
		{
			AdapterPlugin.Log.LogWarning((object)("economy log (sale): " + ex.Message));
		}
	}

	private void OnSold(Item item)
	{
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Invalid comparison between Unknown and I4
		//IL_0131: Unknown result type (might be due to invalid IL or missing references)
		//IL_0136: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)item == (Object)null))
		{
			WfItemTag val = WfFishService.Instance?.TagOf(item);
			int totalWorth = item.TotalWorth;
			bool flag = (int)item.Currency == 0;
			if (val != null && flag)
			{
				SessionTally t = _t;
				t.WfSold++;
				SessionTally t2 = _t;
				t2.WfIncome += totalWorth;
			}
			else if ((Object)(object)item.Creature != (Object)null && flag)
			{
				SessionTally t3 = _t;
				t3.NativeSold++;
				SessionTally t4 = _t;
				t4.NativeIncome += totalWorth;
			}
			else
			{
				SessionTally t5 = _t;
				t5.OtherSold++;
				SessionTally t6 = _t;
				t6.OtherIncome += (flag ? totalWorth : 0);
			}
			Write("sold", ("wf", val != null), ("species", val?.SpeciesId ?? NativeFish.IdOf(item)), ("worth", totalWorth), ("currency", ((object)item.Currency/*cast due to .constrained prefix*/).ToString()), ("quality", ((object)Unsafe.As<Quality, Quality>(ref val?.Quality)/*cast due to .constrained prefix*/).ToString()), ("shiny", val?.Shiny ?? false));
		}
	}

	public void OnTankResult(ulong sender, AqRequest req, AqResult r, Aquarium tank)
	{
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0057: Expected I4, but got Unknown
		if (req == null || r == null || !r.Ok)
		{
			return;
		}
		AqRequestKind kind = req.Kind;
		switch ((int)kind)
		{
		case 0:
		{
			AqFish val = tank?.State.Fish.Find((AqFish x) => x.Uid == r.Amount);
			SessionTally t3 = _t;
			t3.Deposits++;
			SessionTally t4 = _t;
			t4.DepositedValue += val?.Value ?? 0;
			Write("deposit", ("species", val?.SpeciesId), ("value", val?.Value ?? 0), ("shiny", val != null && (val.Shiny || (val.Drip && !NativeFish.IsNative(val.SpeciesId)))), ("by", sender.ToString()));
			break;
		}
		case 3:
		{
			SessionTally t = _t;
			t.Upgrades++;
			SessionTally t2 = _t;
			t2.UpgradeSpent += r.Amount;
			Write("upgrade", ("type", ((object)Unsafe.As<UpgradeType, UpgradeType>(ref req.Upgrade)/*cast due to .constrained prefix*/).ToString()), ("cost", r.Amount));
			break;
		}
		case 2:
			Write("collect", ("by", sender.ToString()), ("theirShare", r.Amount));
			break;
		case 8:
			Write("takeout", ("uid", req.Uid), ("by", sender.ToString()));
			break;
		case 4:
			Write("zen", ("on", tank?.State.Zen ?? false));
			break;
		case 1:
		case 5:
		case 6:
		case 7:
			break;
		}
	}

	public void OnFood(Item item)
	{
		if (!((Object)(object)item == (Object)null))
		{
			SessionTally t = _t;
			t.FoodItems++;
			SessionTally t2 = _t;
			t2.FoodValue += item.TotalWorth;
			WfItemTag val = WfFishService.Instance?.TagOf(item);
			Write("food", ("wf", val != null), ("species", val?.SpeciesId ?? NativeFish.IdOf(item)), ("worth", item.TotalWorth));
		}
	}

	public void OnPurchase(ulong player, string item, int price)
	{
		Write("purchase", ("by", player.ToString()), ("item", item), ("price", price));
	}

	public void OnPaid(ulong player, int coins)
	{
		SessionTally t = _t;
		t.TankPaid += coins;
		Write("payout", ("to", player.ToString()), ("coins", coins));
	}

	public void OnTankEvents(List<AqEvent> events)
	{
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Invalid comparison between Unknown and I4
		if (events == null)
		{
			return;
		}
		foreach (AqEvent @event in events)
		{
			if ((int)@event.Kind == 0)
			{
				SessionTally t = _t;
				t.TankDeaths++;
			}
			else if ((int)@event.Kind == 2)
			{
				if (@event.Defeated)
				{
					SessionTally t2 = _t;
					t2.AttacksWon++;
				}
				else
				{
					SessionTally t3 = _t;
					t3.AttacksLost++;
				}
			}
			Write("tank", ("event", ((object)Unsafe.As<AqEventKind, AqEventKind>(ref @event.Kind)/*cast due to .constrained prefix*/).ToString()), ("species", @event.SpeciesId), ("reason", @event.Reason), ("defeated", @event.Defeated));
		}
	}

	public void RegisterCommands(DebugConsole c)
	{
		c.Register("econ.summary", "economy totals for this session (host) next to the simulator's limits", (Func<string[], string>)delegate
		{
			if (!Game.IsHost)
			{
				return "host only (the economy runs on the host)";
			}
			Aquarium val = TankService.Instance?.HostTank;
			if (val != null)
			{
				_t.Tray(val.State.TrayCoins);
			}
			_t.Seen(Now);
			string text = _t.Report();
			JsonLinesLog log = _log;
			return text + "\nlog: " + (((log != null) ? log.Path : null) ?? (_enabled ? "none yet (load a world)" : "off (config Debug.EconomyLog)"));
		});
		c.Register("econ.reset", "start a fresh economy tally and log file (host)", (Func<string[], string>)delegate
		{
			if (!Game.IsHost)
			{
				return "host only";
			}
			OnWorldUnloaded();
			OnWorldLoaded(Game.WorldName);
			JsonLinesLog log = _log;
			return "new tally; log: " + (((log != null) ? log.Path : null) ?? "off");
		});
	}
}
internal static class Game
{
	public static bool IsHost
	{
		get
		{
			if ((Object)(object)Server.Instance != (Object)null)
			{
				return ((NetworkBehaviour)Server.Instance).IsServerInitialized;
			}
			return false;
		}
	}

	public static bool InHostedWorld
	{
		get
		{
			if (IsHost && SaveManager.CurServerSave != null)
			{
				return (Object)(object)Player.LocalPlayer != (Object)null;
			}
			return false;
		}
	}

	public static bool InClientWorld
	{
		get
		{
			if (!IsHost && (Object)(object)Player.LocalPlayer != (Object)null)
			{
				return InstanceFinder.IsClientStarted;
			}
			return false;
		}
	}

	public static string WorldId
	{
		get
		{
			ServerSaveObject curServerSave = SaveManager.CurServerSave;
			ulong steamId = Identity.SteamId;
			if (curServerSave != null && steamId != 0L)
			{
				return steamId + ":" + curServerSave.Name;
			}
			return null;
		}
	}

	public static string WorldName => SaveManager.CurServerSave?.Name;

	public static double PlaytimeSeconds => SaveManager.GetTotalPlaytime();

	public static int ActiveMainIsland
	{
		get
		{
			if ((Object)(object)OnlineIslandManager.Instance == (Object)null || IslandManager.IsLoading || (Object)(object)Island.CurIsland == (Object)null)
			{
				return -1;
			}
			byte activeIsland = OnlineIslandManager.ActiveIsland;
			if (!IslandManager.IsMainIsland(activeIsland) || Island.CurIsland.IsMini)
			{
				return -1;
			}
			return activeIsland;
		}
	}

	public static int CurrentMainIsland
	{
		get
		{
			if (!((Object)(object)OnlineIslandManager.Instance != (Object)null))
			{
				return 0;
			}
			return OnlineIslandManager.CurIsland;
		}
	}

	public static int MainIslandCount
	{
		get
		{
			object obj = AccessTools.Field(typeof(IslandManager), "_instance")?.GetValue(null);
			return ((obj == null) ? null : (AccessTools.Field(typeof(IslandManager), "_islandInfos")?.GetValue(obj) as Array))?.Length ?? 5;
		}
	}

	public static Vector3 IslandOrigin => Island.IslandPos;

	public static LayerMask Ground => GameInfo.LevelLayer;

	public static LayerMask PlacementBlockers => LayerMask.op_Implicit(~LayerMask.GetMask(new string[3] { "Ignore Raycast", "UI", "Water" }));

	public static bool LocalInputBlocked
	{
		get
		{
			if (!((Object)(object)Player.LocalPlayer == (Object)null))
			{
				return Player.LocalPlayer.BlockInputs;
			}
			return true;
		}
	}

	public static ulong[] PlayerIds => (from p in PlayerManager.Players
		where (Object)(object)p != (Object)null
		select p.SteamID).ToArray();

	public static void Check()
	{
		Require(typeof(SaveManager), "CurServerSave", "GetTotalPlaytime", "SaveServer");
		Require(typeof(ServerSaveObject), "Name");
		Require(typeof(Server), "Instance");
		Require(typeof(Player), "LocalPlayer", "SteamID", "SteamName", "Rigidbody", "BlockInputs", "Transform");
		Require(typeof(PlayerManager), "Players");
		Require(typeof(OnlineIslandManager), "Instance", "ActiveIsland", "CurIsland", "TpToSpecificIsland");
		Require(typeof(IslandManager), "_instance", "_islandInfos");
		Require(typeof(IslandManager), "IsLoading", "IsMainIsland");
		Require(typeof(Island), "CurIsland", "IslandPos");
		Require(typeof(GameInfo), "LevelLayer");
		Require(typeof(WaterManager), "GetWaterHeight");
		Require(typeof(ChatManager), "ChatMessage");
	}

	public static void Require(Type t, params string[] members)
	{
		foreach (string text in members)
		{
			if (t.GetMember(text, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Length == 0)
			{
				throw new MissingMemberException(t.Name + "." + text + " not found");
			}
		}
	}

	public static float WaterHeightAt(Vector3 p)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		return WaterManager.GetWaterHeight(p);
	}

	public static bool IsLocalPlayerCollider(Collider c)
	{
		Player localPlayer = Player.LocalPlayer;
		if ((Object)(object)localPlayer == (Object)null || (Object)(object)c == (Object)null)
		{
			return false;
		}
		if (!((Component)c).transform.IsChildOf(((Component)localPlayer).transform))
		{
			if ((Object)(object)c.attachedRigidbody != (Object)null)
			{
				return (Object)(object)c.attachedRigidbody == (Object)(object)localPlayer.Rigidbody;
			}
			return false;
		}
		return true;
	}

	public static string NameOf(ulong id)
	{
		Player? obj = ((IEnumerable<Player>)PlayerManager.Players).FirstOrDefault((Func<Player, bool>)((Player p) => (Object)(object)p != (Object)null && p.SteamID == id));
		if (obj == null)
		{
			return null;
		}
		return obj.SteamName;
	}

	public static void LocalChat(string message)
	{
		ChatManager.ChatMessage("<color=#e6b422>[WFxHTF]</color> " + message);
	}
}
internal sealed class GamePorts : IAquariumPorts
{
	public const float BossReach = 12f;

	private readonly Dictionary<HeldFish, int> _values = new Dictionary<HeldFish, int>();

	private static FieldRef<Item, float> _weight;

	private const float MaxTakeOutDistance = 30f;

	private static Aquarium Tank => TankService.Instance?.Tank;

	public static void Check()
	{
		Game.Require(typeof(Player), "Holding");
		Game.Require(typeof(PlayerHolding), "HeldItem");
		Game.Require(typeof(Item), "SyncedHolder", "Creature", "DestroyItem", "IsDestroying", "TotalWorth", "RandomizedWeight", "_weight");
		Game.Require(typeof(Creature), "DeadCreatures", "BossType", "IsDead", "ServerKillOnSpawn", "_isDrip", "_syncedRandomWeight");
		Game.Require(typeof(Player), "Transform");
		Game.Require(typeof(Server), "Spawn");
		Game.Require(typeof(MoneyManager), "Money", "AddMoney", "RemoveMoney", "Instance", "_money");
		NativeFish.Check();
		_weight = AccessTools.FieldRefAccess<Item, float>("_weight");
	}

	private static Player PlayerOf(ulong id)
	{
		return ((IEnumerable<Player>)PlayerManager.Players).FirstOrDefault((Func<Player, bool>)((Player p) => (Object)(object)p != (Object)null && p.SteamID == id));
	}

	public static Item HeldDeadCreature(ulong id)
	{
		Player val = PlayerOf(id);
		Item val2 = (((Object)(object)val != (Object)null && (Object)(object)val.Holding != (Object)null) ? val.Holding.HeldItem : null);
		if ((Object)(object)val2 == (Object)null || val2.IsDestroying || (Object)(object)val2.SyncedHolder != (Object)(object)val)
		{
			return null;
		}
		Creature creature = val2.Creature;
		if (!((Object)(object)creature != (Object)null) || !creature.IsDead)
		{
			return null;
		}
		return val2;
	}

	public static Item Offered(ulong id)
	{
		Item val = HeldDeadCreature(id);
		if ((Object)(object)val != (Object)null)
		{
			return val;
		}
		Aquarium tank = Tank;
		Vector3? at = TankService.Instance?.TankPosition;
		if (tank == null || !AllowsBosses(tank) || !at.HasValue)
		{
			return null;
		}
		return (Item)(object)(from c in Creature.DeadCreatures
			where (Object)(object)c != (Object)null && (int)c.BossType != 0 && !((Item)c).IsDestroying && !((NetworkBehaviour)c).IsDeinitializing && Vector3.Distance(((Component)c).transform.position, at.Value) <= 12f
			orderby Vector3.Distance(((Component)c).transform.position, at.Value)
			select c).FirstOrDefault();
	}

	public static bool AllowsBosses(Aquarium tank)
	{
		return tank.State.CapacityLevel >= tank.Config.MaxCapacityLevel;
	}

	public static HeldFish Describe(Item item, out int value)
	{
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_009b: Expected O, but got Unknown
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: Invalid comparison between Unknown and I4
		//IL_0131: Unknown result type (might be due to invalid IL or missing references)
		//IL_0136: Unknown result type (might be due to invalid IL or missing references)
		//IL_013e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0154: Unknown result type (might be due to invalid IL or missing references)
		//IL_0156: Unknown result type (might be due to invalid IL or missing references)
		//IL_015b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0173: Unknown result type (might be due to invalid IL or missing references)
		//IL_018e: Unknown result type (might be due to invalid IL or missing references)
		//IL_019a: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b3: Expected O, but got Unknown
		value = -1;
		if ((Object)(object)item == (Object)null)
		{
			return null;
		}
		WfItemTag val = WfFishService.Instance?.TagOf(item);
		if (val != null)
		{
			value = (int)((float)val.Worth * val.WeightMultiplier);
			return new HeldFish
			{
				SpeciesId = val.SpeciesId,
				WeightKg = val.WeightKg,
				Quality = val.Quality,
				Drip = val.Shiny,
				Shiny = val.Shiny,
				Cookness = item.Cookness,
				BetMultiplier = item.BettingMultiplier,
				KillScore = item.KillScoreMultiplier
			};
		}
		Creature creature = item.Creature;
		bool num;
		if (!((Object)(object)creature != (Object)null) || (int)creature.BossType <= 0)
		{
			num = !NativeFish.CanLiveInTank(creature);
		}
		else
		{
			if (Tank == null)
			{
				goto IL_00dd;
			}
			num = !AllowsBosses(Tank);
		}
		if (!num)
		{
			int num2 = WfFishService.Instance?.MaxRegularWorth ?? int.MaxValue;
			value = Mathf.Min(item.TotalWorth, num2);
			float num3 = _weight.Invoke(item) * item.RandomizedWeight;
			string speciesId = NativeFish.IdOf(item);
			NativeFish.RememberName(speciesId, NativeShiny.Plain(item.GetName()));
			return new HeldFish
			{
				SpeciesId = speciesId,
				WeightKg = ((num3 > 0f) ? num3 : 0.01f),
				Quality = (Quality)0,
				Drip = ((Object)(object)creature != (Object)null && creature.IsDrip),
				Shiny = (NativeShiny.Instance != null && NativeShiny.Instance.Is(item)),
				Cookness = item.Cookness,
				BetMultiplier = item.BettingMultiplier,
				KillScore = item.KillScoreMultiplier
			};
		}
		goto IL_00dd;
		IL_00dd:
		return null;
	}

	public HeldFish PeekHeldFish(ulong playerId)
	{
		_values.Clear();
		int value;
		HeldFish val = Describe(Offered(playerId), out value);
		if (val != null)
		{
			_values[val] = value;
		}
		return val;
	}

	public bool TakeHeldFish(ulong playerId, HeldFish fish)
	{
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		Item val = Offered(playerId);
		int value;
		HeldFish val2 = Describe(val, out value);
		if (val2 == null || val2.SpeciesId != fish.SpeciesId || val2.Quality != fish.Quality || val2.WeightKg != fish.WeightKg || val2.Drip != fish.Drip || val2.Shiny != fish.Shiny)
		{
			return false;
		}
		WfFishService.Instance?.Forget(val);
		val.DestroyItem((byte)0, byte.MaxValue);
		return true;
	}

	public int ValueOf(HeldFish fish)
	{
		if (!_values.TryGetValue(fish, out var value))
		{
			return -1;
		}
		return value;
	}

	public int TakeFood(ulong playerId, int units)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		if (units <= 0)
		{
			return 0;
		}
		Item val = HeldDeadCreature(playerId);
		if ((Object)(object)val == (Object)null || (int)val.Creature.BossType != 0)
		{
			return 0;
		}
		try
		{
			EconomyLog.Instance?.OnFood(val);
		}
		catch
		{
		}
		WfFishService.Instance?.Forget(val);
		val.DestroyItem((byte)3, byte.MaxValue);
		return Plugin.Instance.Aquarium.FoodUnitsPerItem;
	}

	public bool TryCharge(ulong playerId, int coins)
	{
		int num = (((Object)(object)MoneyManager.Instance != (Object)null) ? MoneyManager.Instance._money.Value : MoneyManager.Money);
		if (coins < 0 || num < coins)
		{
			return false;
		}
		MoneyManager.RemoveMoney(coins, PlayerOf(playerId), (Currency)0);
		return true;
	}

	public void Pay(ulong playerId, int coins)
	{
		if (coins <= 0)
		{
			return;
		}
		MoneyManager.AddMoney(coins, PlayerOf(playerId), (Currency)0);
		try
		{
			EconomyLog.Instance?.OnPaid(playerId, coins);
		}
		catch
		{
		}
	}

	public bool GiveFish(ulong playerId, AqFish fish)
	{
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_008c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b5: 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_00c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_014e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0150: Unknown result type (might be due to invalid IL or missing references)
		//IL_0155: Unknown result type (might be due to invalid IL or missing references)
		//IL_0156: Unknown result type (might be due to invalid IL or missing references)
		//IL_015b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0288: Unknown result type (might be due to invalid IL or missing references)
		//IL_028e: Unknown result type (might be due to invalid IL or missing references)
		Player val = PlayerOf(playerId);
		if ((Object)(object)val == (Object)null || (Object)(object)val.Transform == (Object)null || fish == null || (Object)(object)Server.Instance == (Object)null)
		{
			return false;
		}
		Vector3 position = val.Transform.position;
		Vector3? val2 = TankService.Instance?.TankPosition;
		if (val2.HasValue)
		{
			Vector3 val3 = val2.Value - position;
			if (!(((Vector3)(ref val3)).sqrMagnitude > 900f))
			{
				Vector3 val4 = val2.Value - position;
				val4.y = 0f;
				if (((Vector3)(ref val4)).sqrMagnitude < 0.001f)
				{
					val4 = Vector3.forward;
				}
				((Vector3)(ref val4)).Normalize();
				Vector3 val5 = position + val4 * 0.8f + Vector3.up * 1.2f;
				FishDef val6 = null;
				Creature val7;
				if (NativeFish.IsNative(fish.SpeciesId))
				{
					val7 = NativeFish.Prefab(fish.SpeciesId);
				}
				else
				{
					val6 = Plugin.Instance.Cache.Find(fish.SpeciesId);
					if (val6 == null || val6.Id != fish.SpeciesId || WfFishService.Instance == null)
					{
						return false;
					}
					val7 = WfFishService.Instance.BodyOf(val6);
				}
				if ((Object)(object)val7 == (Object)null)
				{
					return false;
				}
				Creature val8 = Object.Instantiate<Creature>(val7, val5, Quaternion.LookRotation(Vector3.Cross(Vector3.up, val4)));
				try
				{
					if (val6 != null)
					{
						WfFishService.Instance.TagTakenOut(val8, fish, val6);
					}
					else
					{
						float num = _weight.Invoke((Item)(object)val8);
						if (num > 0f)
						{
							float num2 = Mathf.Clamp(fish.WeightKg / num, 0.05f, 50f);
							((Item)val8)._syncedRandomWeight.Value = ((num2 == 1f) ? 1.0002f : num2);
						}
						val8._isDrip.Value = fish.Drip;
						if (fish.Shiny)
						{
							NativeShiny.Instance?.Mark((Item)(object)val8, "taken out of the tank");
						}
					}
					((Item)val8)._cookness.Value = Finite(fish.Cookness, 0f, 0f, 10f);
					((Item)val8)._bettingMultiplier.Value = Finite(fish.BetMultiplier, 1f, 0f, 1000f);
					((Item)val8)._killScoreMultiplier.Value = Finite(fish.KillScore, 1f, 0f, 100f);
					val8.ServerKillOnSpawn();
					((NetworkBehaviour)Server.Instance).Spawn(((Component)val8).gameObject, (NetworkConnection)null, default(Scene));
					return true;
				}
				catch (Exception ex)
				{
					if ((Object)(object)val8 != (Object)null && ((NetworkBehaviour)val8).IsSpawned)
					{
						AdapterPlugin.Log.LogWarning((object)("take-out: the fish is out, but spawning reported: " + ex.Message));
						return true;
					}
					AdapterPlugin.Log.LogWarning((object)("take-out failed, the fish stays in the tank: " + ex.Message));
					if ((Object)(object)val8 != (Object)null)
					{
						WfFishService.Instance?.Forget((Item)(object)val8);
						NativeShiny.Instance?.Forget((Item)(object)val8);
						Object.Destroy((Object)(object)((Component)val8).gameObject);
					}
					return false;
				}
			}
		}
		return false;
	}

	private static float Finite(float v, float fallback, float min, float max)
	{
		if (!float.IsNaN(v) && !float.IsInfinity(v))
		{
			return Mathf.Clamp(v, min, max);
		}
		return fallback;
	}

	public IReadOnlyCollection<ulong> PlayersInLobby()
	{
		return (IReadOnlyCollection<ulong>)(object)Game.PlayerIds;
	}

	public string NameOf(ulong playerId)
	{
		return Game.NameOf(playerId) ?? "player";
	}

	public bool IsHost(ulong playerId)
	{
		if (Game.IsHost)
		{
			return playerId == Identity.SteamId;
		}
		return false;
	}
}
internal static class HotbarIconPatch
{
	private static FieldRef<InventorySlot, MeshFilter> _filter;

	private static FieldRef<InventorySlot, Renderer> _renderer;

	private static readonly Dictionary<InventorySlot, Material> Original = new Dictionary<InventorySlot, Material>();

	private static readonly Dictionary<string, Material> Materials = new Dictionary<string, Material>();

	public static void Install(Harmony h)
	{
		//IL_0098: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a5: Expected O, but got Unknown
		Game.Require(typeof(InventorySlot), "SetItem", "_filter", "_renderer");
		Game.Require(typeof(Item), "Mesh", "InventoryMeshScale");
		_filter = AccessTools.FieldRefAccess<InventorySlot, MeshFilter>("_filter");
		_renderer = AccessTools.FieldRefAccess<InventorySlot, Renderer>("_renderer");
		h.Patch((MethodBase)AccessTools.Method(typeof(InventorySlot), "SetItem", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(HotbarIconPatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
	}

	private static void Postfix(InventorySlot __instance, Item item)
	{
		try
		{
			Apply(__instance, item);
		}
		catch (Exception ex)
		{
			AdapterPlugin.Log.LogWarning((object)("hotbar icon: " + ex.Message));
		}
	}

	private static void Apply(InventorySlot slot, Item item)
	{
		//IL_014e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0153: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
		//IL_020c: Unknown result type (might be due to invalid IL or missing references)
		//IL_018b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0190: Unknown result type (might be due to invalid IL or missing references)
		//IL_0194: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_01be: Unknown result type (might be due to invalid IL or missing references)
		//IL_0286: Unknown result type (might be due to invalid IL or missing references)
		//IL_024e: Unknown result type (might be due to invalid IL or missing references)
		//IL_025a: Unknown result type (might be due to invalid IL or missing references)
		//IL_025f: Unknown result type (might be due to invalid IL or missing references)
		//IL_026b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0270: Unknown result type (might be due to invalid IL or missing references)
		MeshFilter filter = _filter.Invoke(slot);
		Renderer val = _renderer.Invoke(slot);
		if ((Object)(object)filter == (Object)null || (Object)(object)val == (Object)null)
		{
			return;
		}
		WfItemTag val2 = ((!((Object)(object)item != (Object)null)) ? null : WfFishService.Instance?.TagOf(item));
		SpriteCache cache = Plugin.Instance.Cache;
		FishDef val3 = ((val2 != null) ? cache.Find(val2.SpeciesId) : null);
		if (val3 == null)
		{
			if (Original.TryGetValue(slot, out var value))
			{
				val.sharedMaterial = value;
				Original.Remove(slot);
			}
			return;
		}
		Mesh val4 = cache.FrameMesh(val3, 0);
		Texture2D val5 = cache.Frame(val3, 0, val2.Shiny);
		if ((Object)(object)val4 == (Object)null || (Object)(object)val5 == (Object)null)
		{
			return;
		}
		if (!Original.ContainsKey(slot))
		{
			Original[slot] = val.sharedMaterial;
		}
		string key = val3.Id + "|" + ((object)Unsafe.As<Quality, Quality>(ref val2.Quality)/*cast due to .constrained prefix*/).ToString() + (val2.Shiny ? "|shiny" : "");
		if (!Materials.TryGetValue(key, out var value2) || (Object)(object)value2 == (Object)null)
		{
			value2 = (Materials[key] = SpriteMaterial.Create(val5, SpriteFish.Tint(val2.Quality)));
		}
		if (!((Object)(object)value2 == (Object)null))
		{
			float num = 1f;
			Mesh mesh = item.Mesh;
			if ((Object)(object)mesh != (Object)null)
			{
				Bounds bounds = mesh.bounds;
				float x = ((Bounds)(ref bounds)).size.x;
				bounds = mesh.bounds;
				float y = ((Bounds)(ref bounds)).size.y;
				bounds = mesh.bounds;
				num = Mathf.Max(x, Mathf.Max(y, ((Bounds)(ref bounds)).size.z)) * item.InventoryMeshScale;
			}
			filter.mesh = val4;
			val.sharedMaterial = value2;
			((Component)filter).transform.localScale = Vector3.one * Mathf.Max(0.05f, num);
			Camera val7 = ((IEnumerable<Camera>)Camera.allCameras).FirstOrDefault((Func<Camera, bool>)((Camera c) => (Object)(object)c != (Object)null && (Object)(object)c.targetTexture != (Object)null && (c.cullingMask & (1 << ((Component)filter).gameObject.layer)) != 0));
			if ((Object)(object)val7 != (Object)null)
			{
				((Component)filter).transform.rotation = Quaternion.LookRotation(((Component)filter).transform.position - ((Component)val7).transform.position, ((Component)val7).transform.up);
			}
			else
			{
				((Component)filter).transform.localRotation = Quaternion.identity;
			}
		}
	}
}
internal sealed class KeyItemService : MonoBehaviour
{
	public KeyCode LureKey = (KeyCode)108;

	public KeyCode CharmKey = (KeyCode)108;

	private int _standIsland = -2;

	private int _standTries;

	private float _nextTry;

	private float _errorAt;

	private ShopStand _lureStand;

	private ShopStand _charmStand;

	private readonly Dictionary<ulong, GlowOrb> _orbs = new Dictionary<ulong, GlowOrb>();

	private readonly Dictionary<ulong, GameObject> _invertedBaits = new Dictionary<ulong, GameObject>();

	private static readonly Color CharmColour = new Color(0.75f, 0.92f, 1f);

	private bool _standsHere;

	public static KeyItemService Instance { get; private set; }

	private Plugin P => Plugin.Instance;

	public static List<PlayerKeyItems> Players
	{
		get
		{
			object obj = TankService.Instance?.KeyItems?.Players;
			if (obj == null)
			{
				NetService instance = NetService.Instance;
				if (instance == null)
				{
					return null;
				}
				KeyItemsState clientKeyItems = instance.ClientKeyItems;
				if (clientKeyItems == null)
				{
					return null;
				}
				obj = clientKeyItems.Players;
			}
			return (List<PlayerKeyItems>)obj;
		}
	}

	private static PlayerKeyItems Me => Of(Identity.SteamId);

	public static void Check()
	{
		Game.Require(typeof(BaitPurchasable), "_bait");
		Game.Require(typeof(ItemPurchasable), "_itemToPurchase");
		Game.Require(typeof(Item), "FishingRod");
		Game.Require(typeof(FishingRod), "Bait");
		Game.Require(typeof(BaitInfo), "Mesh");
		Game.Require(typeof(GameInfo), "AllBaits");
	}

	private void Awake()
	{
		Instance = this;
	}

	public static PlayerKeyItems Of(ulong id)
	{
		return Players?.FirstOrDefault((Func<PlayerKeyItems, bool>)((PlayerKeyItems p) => p != null && p.SteamId == id));
	}

	private void Update()
	{
		if (!Game.InHostedWorld && !Game.InClientWorld)
		{
			ClearAll();
			return;
		}
		try
		{
			UpdateStands();
			UpdateVisuals();
			UpdateInput();
		}
		catch (Exception ex)
		{
			if (Time.unscaledTime >= _errorAt)
			{
				AdapterPlugin.Log.LogError((object)("shop: " + ex));
				_errorAt = Time.unscaledTime + 10f;
			}
		}
	}

	private void UpdateStands()
	{
		int activeMainIsland = Game.ActiveMainIsland;
		if (activeMainIsland != _standIsland)
		{
			_standsHere = false;
			DestroyStands();
			_standIsland = activeMainIsland;
			_standTries = 0;
			_nextTry = Time.unscaledTime + 2f;
		}
		if (activeMainIsland < 0)
		{
			return;
		}
		int num = ((P.Mapping.WfLureIsland < 0) ? (Game.MainIslandCount - 1) : P.Mapping.WfLureIsland);
		bool flag = activeMainIsland == num && (Object)(object)_lureStand == (Object)null;
		bool flag2 = activeMainIsland == P.Mapping.ShinyCharmIsland && (Object)(object)_charmStand == (Object)null;
		if (!_standsHere && (flag || flag2) && !(Time.unscaledTime < _nextTry) && _standTries < 10)
		{
			_nextTry = Time.unscaledTime + 3f;
			if (++_standTries == 10)
			{
				AdapterPlugin.Log.LogWarning((object)$"shop: no How to Fish shop or NPC found on island {activeMainIsland}; the mod's stand isn't placed there");
			}
			Material palette;
			Purchasable val = FindShop(out palette);
			if (flag && (Object)(object)val != (Object)null)
			{
				_lureStand = BuildLure(val, palette);
			}
			if (flag2)
			{
				_charmStand = BuildCharm(val);
			}
		}
	}

	private ShopStand BuildLure(Purchasable counter, Material palette)
	{
		return Build((Component)(object)counter, BesideCounter(((Component)counter).transform, 1), "WFxHTF Web Fishing Lure", LureDisplay(palette), LurePrompt, UseLure);
	}

	private ShopStand BuildCharm(Purchasable counter)
	{
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		NPC val = FindNpc();
		if ((Object)(object)val == (Object)null && (Object)(object)counter == (Object)null)
		{
			return null;
		}
		GameObject gameObject = ((Component)GlowOrb.Create("Shiny Charm display", 0.12f, CharmColour)).gameObject;
		if (!((Object)(object)val != (Object)null))
		{
			return Build((Component)(object)counter, BesideCounter(((Component)counter).transform, -1), "WFxHTF Shiny Charm", gameObject, CharmPrompt, UseCharm);
		}
		return Build((Component)(object)val, BehindNpc(((Component)val).transform), "WFxHTF Shiny Charm", gameObject, CharmPrompt, UseCharm);
	}

	private static NPC FindNpc()
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		Vector3 origin = Game.IslandOrigin;
		return (from n in Object.FindObjectsByType<NPC>((FindObjectsInactive)0)
			where (Object)(object)n != (Object)null && ((Behaviour)n).isActiveAndEnabled && Flat(((Component)n).transform.position - origin) < 500f
			orderby Flat(((Component)n).transform.position - origin)
			select n).FirstOrDefault();
	}

	private static (Vector3 Right, Vector3 Fwd) Axes(Transform a)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_0057: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		Vector3 right = a.right;
		right.y = 0f;
		Vector3 forward = a.forward;
		forward.y = 0f;
		if (((Vector3)(ref right)).sqrMagnitude < 0.001f)
		{
			right = Vector3.right;
		}
		if (((Vector3)(ref forward)).sqrMagnitude < 0.001f)
		{
			forward = Vector3.forward;
		}
		return (Right: ((Vector3)(ref right)).normalized, Fwd: ((Vector3)(ref forward)).normalized);
	}

	private static Vector3[] BesideCounter(Transform a, int side)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: 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_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: 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_0071: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
		var (val, val2) = Axes(a);
		return (Vector3[])(object)new Vector3[6]
		{
			val * 1.6f * (float)side,
			val * 2.6f * (float)side,
			-val * 1.6f * (float)side,
			val2 * 1.6f,
			-val2 * 1.6f,
			val * 3.6f * (float)side
		};
	}

	private static Vector3[] BehindNpc(Transform a)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: 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)
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: Unknown result type (might be due to invalid IL or missing references)
		//IL_0069: Unknown result type (might be due to invalid IL or missing references)
		//IL_0070: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: Unknown result type (might be due to invalid IL or missing references)
		//IL_0097: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a7: 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_00b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
		var (val, val2) = Axes(a);
		return (Vector3[])(object)new Vector3[7]
		{
			-val2 * 1.4f,
			-val2 * 2f,
			-val2 * 1.4f + val * 0.9f,
			-val2 * 1.4f - val * 0.9f,
			-val2 * 2.6f,
			val * 1.6f,
			-val * 1.6f
		};
	}

	private static Purchasable FindShop(out Material palette)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		palette = null;
		Vector3 origin = Game.IslandOrigin;
		List<Purchasable> all = Counters();
		List<Purchasable> source = all.Where((Purchasable p) => p is BaitPurchasable || p is ItemPurchasable).ToList();
		Purchasable val = (from p in source
			where p is BaitPurchasable
			orderby Flat(((Component)p).transform.position - origin)
			select p).FirstOrDefault() ?? (from p in source
			orderby Neighbours(p, all) descending, Flat(((Component)p).transform.position - origin)
			select p).FirstOrDefault();
		if ((Object)(object)val != (Object)null)
		{
			palette = (from r in ((Component)val).GetComponentsInChildren<Renderer>()
				select r.sharedMaterial).FirstOrDefault((Func<Material, bool>)((Material m) => (Object)(object)m != (Object)null));
		}
		return val;
	}

	private static List<Purchasable> Counters()
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		Vector3 origin = Game.IslandOrigin;
		Transform boat = (((Object)(object)BoatManager.Boat != (Object)null) ? ((Component)BoatManager.Boat).transform : null);
		return (from p in Object.FindObjectsByType<Purchasable>((FindObjectsInactive)0)
			where (Object)(object)p != (Object)null && ((Behaviour)p).isActiveAndEnabled && (!((Object)(object)boat != (Object)null) || !((Component)p).transform.IsChildOf(boat)) && Flat(((Component)p).transform.position - origin) < 500f
			select p).ToList();
	}

	private static int Neighbours(Purchasable p, List<Purchasable> all)
	{
		return all.Count((Purchasable o) => (Object)(object)o != (Object)(object)p && Flat(((Component)o).transform.position - ((Component)p).transform.position) < 12f);
	}

	private static float Flat(Vector3 d)
	{
		d.y = 0f;
		return ((Vector3)(ref d)).magnitude;
	}

	private static string FromMe(Vector3 at)
	{
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_0065: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: 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)
		Transform val = (((Object)(object)Player.LocalPlayer != (Object)null) ? Player.LocalPlayer.Transform : null);
		Transform val2 = (((Object)(object)Camera.main != (Object)null) ? ((Component)Camera.main).transform : val);
		if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null)
		{
			return $"at {at}";
		}
		Vector3 val3 = at - val.position;
		float num = Flat(val3);
		if (num < 2f)
		{
			return "right here";
		}
		Vector3 forward = val2.forward;
		forward.y = 0f;
		val3.y = 0f;
		float num2 = Vector3.SignedAngle((((Vector3)(ref forward)).sqrMagnitude > 0.0001f) ? forward : Vector3.forward, val3, Vector3.up);
		string arg = (new string[8] { "ahead", "ahead-right", "right", "behind-right", "behind", "behind-left", "left", "ahead-left" })[(int)Mathf.Repeat(Mathf.Round(num2 / 45f), 8f)];
		return $"{num:0} m {arg}";
	}

	private ShopStand Build(Component anchor, Vector3[] offsets, string name, GameObject display, Func<string> prompt, Action<Player> use)
	{
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_0069: Unknown result type (might be due to invalid IL or missing references)
		//IL_007f: Unknown result type (might be due to invalid IL or missing references)
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0101: Unknown result type (might be due to invalid IL or missing references)
		//IL_0119: Unknown result type (might be due to invalid IL or missing references)
		//IL_013d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0152: 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)
		Transform transform = anchor.transform;
		Vector3 item = Axes(transform).Fwd;
		RaycastHit val2 = default(RaycastHit);
		foreach (Vector3 val in offsets)
		{
			if (!Physics.Raycast(transform.position + val + Vector3.up * 1.5f, Vector3.down, ref val2, 6f, LayerMask.op_Implicit(Game.Ground), (QueryTriggerInteraction)1) || ((RaycastHit)(ref val2)).normal.y < 0.7f || ((RaycastHit)(ref val2)).point.y > transform.position.y + 0.5f)
			{
				continue;
			}
			Vector3 point = ((RaycastHit)(ref val2)).point;
			if (!Physics.CheckBox(point + Vector3.up * 0.8f, new Vector3(0.35f, 0.7f, 0.35f), Quaternion.identity, LayerMask.op_Implicit(Game.PlacementBlockers) & ~LayerMask.op_Implicit(Game.Ground), (QueryTriggerInteraction)1))
			{
				ShopStand val3 = ShopStand.Create(name, point, Quaternion.LookRotation(item, Vector3.up), display, 1.05f);
				Transform transform2 = new GameObject("InteractPoint").transform;
				transform2.SetParent(((Component)val3).transform, false);
				transform2.localPosition = new Vector3(0f, val3.Size.y / 2f, 0f);
				try
				{
					TankInteractable.Attach(transform2, val3.Size, val3.Outline, prompt, use);
				}
				catch (Exception ex)
				{
					AdapterPlugin.Log.LogWarning((object)("shop stand prompt unavailable: " + ex.Message));
				}
				AdapterPlugin.Log.LogInfo((object)$"shop: {name} next to {((Object)transform).name} ({((object)anchor).GetType().Name} at {transform.position}) at {point}, {FromMe(point)}");
				return val3;
			}
		}
		Object.Destroy((Object)(object)display);
		AdapterPlugin.Log.LogWarning((object)("shop: no free ground next to " + ((Object)transform).name + " for " + name));
		return null;
	}

	private GameObject LureDisplay(Material palette)
	{
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0093: Unknown result type (might be due to invalid IL or missing references)
		//IL_0099: Expected O, but got Unknown
		//IL_009e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Expected O, but got Unknown
		//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00de: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
		//IL_010b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0110: Unknown result type (might be due to invalid IL or missing references)
		//IL_0114: Unknown result type (might be due to invalid IL or missing references)
		//IL_012f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0145: Unknown result type (might be due to invalid IL or missing references)
		//IL_015b: 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_0164: Unknown result type (might be due to invalid IL or missing references)
		//IL_0169: 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_017e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0183: Unknown result type (might be due to invalid IL or missing references)
		//IL_018d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0192: Unknown result type (might be due to invalid IL or missing references)
		BaitInfo val = (from b in GameInfo.AllBaits
			where (Object)(object)b != (Object)null && (Object)(object)b.Mesh != (Object)null && WfFishService.Instance != null && WfFishService.Instance.IsGeneralLure(b)
			orderby b.Cost descending
			select b).FirstOrDefault();
		if ((Object)(object)val == (Object)null || (Object)(object)palette == (Object)null)
		{
			return ((Component)GlowOrb.Create("Web Fishing Lure display", 0.08f, new Color(1f, 0.6f, 0.9f))).gameObject;
		}
		GameObject val2 = new GameObject("Web Fishing Lure display");
		GameObject val3 = new GameObject("Lure");
		val3.transform.SetParent(val2.transform, false);
		val3.AddComponent<MeshFilter>().sharedMesh = val.Mesh;
		((Renderer)val3.AddComponent<MeshRenderer>()).sharedMaterial = palette;
		Bounds bounds = val.Mesh.bounds;
		float x = ((Bounds)(ref bounds)).size.x;
		bounds = val.Mesh.bounds;
		float y = ((Bounds)(ref bounds)).size.y;
		bounds = val.Mesh.bounds;
		float num = Mathf.Max(x, Mathf.Max(y, ((Bounds)(ref bounds)).size.z));
		val3.transform.localScale = Vector3.one * (0.35f / Mathf.Max(0.01f, num));
		Transform transform = val3.transform;
		bounds = val.Mesh.bounds;
		transform.localPosition = -((Bounds)(ref bounds)).center * val3.transform.localScale.x + Vector3.up * 0.15f;
		ShinyLook.Apply(val2, true);
		return val2;
	}

	private void DestroyStands()
	{
		if ((Object)(object)_lureStand != (Object)null)
		{
			Object.Destroy((Object)(object)((Component)_lureStand).gameObject);
		}
		if ((Object)(object)_charmStand != (Object)null)
		{
			Object.Destroy((Object)(object)((Component)_charmStand).gameObject);
		}
		_lureStand = (_charmStand = null);
	}

	private string LurePrompt()
	{
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		PlayerKeyItems me = Me;
		if (me == null || !me.WfLure)
		{
			return $"Web Fishing Lure\n${P.Mapping.WfLurePrice}";
		}
		return string.Format("Web Fishing Lure (yours)\n[{0}] switch {1}", LureKey, me.WfLureOn ? "off" : "on");
	}

	private string CharmPrompt()
	{
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		PlayerKeyItems me = Me;
		if (me == null || !me.ShinyCharm)
		{
			return $"Shiny Charm\n${P.Mapping.ShinyCharmPrice}";
		}
		return string.Format("Shiny Charm (yours)\n[Ctrl+{0}] switch {1}", CharmKey, me.ShinyCharmOff ? "on" : "off");
	}

	private void UseLure(Player player)
	{
		if ((Object)(object)player != (Object)(object)Player.LocalPlayer)
		{
			return;
		}
		PlayerKeyItems me = Me;
		if (me != null && me.WfLure)
		{
			SetLure(!Me.WfLureOn);
			return;
		}
		TankService.Instance?.RequestKeyItem((AqRequestKind)9, on: false, delegate(AqResult r)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			Chat(r.Ok ? $"<b>You bought the Web Fishing Lure!</b> While it's on, only WEBFISHING fish bite. Press {LureKey} to switch it off and on." : ("Couldn't buy the Web Fishing Lure: " + r.Error));
		});
	}

	private void UseCharm(Player player)
	{
		if ((Object)(object)player != (Object)(object)Player.LocalPlayer)
		{
			return;
		}
		PlayerKeyItems me = Me;
		if (me != null && me.ShinyCharm)
		{
			SetCharm(Me.ShinyCharmOff);
			return;
		}
		TankService.Instance?.RequestKeyItem((AqRequestKind)10, on: false, delegate(AqResult r)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			Chat(r.Ok ? $"<b><color=#fff6a0>You bought the Shiny Charm!</color></b> Shiny fish are now 1 in 500 for you (bosses stay 1 in 100). Ctrl+{CharmKey} switches it off and on." : ("Couldn't buy the Shiny Charm: " + r.Error));
		});
	}

	private void SetLure(bool on)
	{
		TankService.Instance?.RequestKeyItem((AqRequestKind)11, on, delegate(AqResult r)
		{
			Chat((!r.Ok) ? ("Web Fishing Lure: " + r.Error) : (on ? "Web Fishing Lure <b>on</b>: only WEBFISHING fish bite." : "Web Fishing Lure <b>off</b>."));
		});
	}

	private void SetCharm(bool on)
	{
		TankService.Instance?.RequestKeyItem((AqRequestKind)12, on, delegate(AqResult r)
		{
			Chat((!r.Ok) ? ("Shiny Charm: " + r.Error) : (on ? "Shiny Charm <b>on</b>: shiny fish are 1 in 500 for you." : "Shiny Charm <b>off</b>."));
		});
	}

	private void UpdateInput()
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0097: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
		PlayerKeyItems me = Me;
		if (me != null && me.WfLureOn)
		{
			Hud.ShowSmall("wflure", $"Web Fishing Lure: on  [{LureKey}]");
		}
		else
		{
			Hud.Hide("wflure");
		}
		if (me != null && me.ShinyCharm && !me.ShinyCharmOff)
		{
			Hud.ShowSmall("wfshinycharm", $"Shiny Charm: on  [Ctrl+{CharmKey}]");
		}
		else
		{
			Hud.Hide("wfshinycharm");
		}
		if (me != null && !Game.LocalInputBlocked)
		{
			bool ctrlHeld = Keys.CtrlHeld;
			if (ctrlHeld && me.ShinyCharm && Keys.Down(CharmKey))
			{
				SetCharm(me.ShinyCharmOff);
			}
			else if (!ctrlHeld && me.WfLure && Keys.Down(LureKey))
			{
				SetLure(!me.WfLureOn);
			}
		}
	}

	private static void Chat(string text)
	{
		try
		{
			Game.LocalChat(text);
		}
		catch
		{
		}
	}

	public void RegisterCommands(DebugConsole c)
	{
		c.Register("shop.status", "the mod's shop: stands on this island and who owns what", (Func<string[], string>)delegate
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			int num = ((P.Mapping.WfLureIsland < 0) ? (Game.MainIslandCount - 1) : P.Mapping.WfLureIsland);
			List<string> list = new List<string>
			{
				string.Format("Web Fishing Lure: island {0} for ${1}, stand {2}", num, P.Mapping.WfLurePrice, ((Object)(object)_lureStand != (Object)null) ? FromMe(((Component)_lureStand).transform.position) : "not on this island"),
				string.Format("Shiny Charm: island {0} for ${1}, stand {2}", P.Mapping.ShinyCharmIsland, P.Mapping.ShinyCharmPrice, ((Object)(object)_charmStand != (Object)null) ? FromMe(((Component)_charmStand).transform.position) : "not on this island"),
				$"you are on island {Game.ActiveMainIsland}"
			};
			foreach (PlayerKeyItems item in Players ?? new List<PlayerKeyItems>())
			{
				list.Add("  " + DisplayText.VisibleOr(Game.NameOf(item.SteamId), item.SteamId.ToString()) + ": lure " + (item.WfLure ? (item.WfLureOn ? "on" : "off") : "no") + ", charm " + (item.ShinyCharm ? (item.ShinyCharmOff ? "off" : "on") : "no"));
			}
			return string.Join("\n", list);
		});
		c.Register("shop.give", "DEBUG (host): <lure|charm> give yourself a shop item for free ('shop.give none' takes both back)", (Func<string[], string>)delegate(string[] a)
		{
			KeyItems val = TankService.Instance?.KeyItems;
			if (val == null)
			{
				return "host only (load a world)";
			}
			string text = ((a.Length != 0) ? a[0].ToLowerInvariant() : "");
			switch (text)
			{
			case "lure":
				val.Grant(Identity.SteamId, (bool?)true, (bool?)null);
				break;
			case "charm":
				val.Grant(Identity.SteamId, (bool?)null, (bool?)true);
				break;
			case "none":
				val.Grant(Identity.SteamId, (bool?)false, (bool?)false);
				break;
			default:
				return "usage: shop.give <lure|charm|none>";
			}
			NetService.Instance?.OnKeyItemsChanged();
			return (!(text == "none")) ? ("you have the " + ((text == "lure") ? "Web Fishing Lure (on; L switches it)" : "Shiny Charm")) : "taken back";
		});
		c.Register("shop.list", "the shop counters on this island, nearest first (where the mod's stands can go)", (Func<string[], string>)delegate
		{
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			if (Game.ActiveMainIsland < 0)
			{
				return "stand on a main island";
			}
			List<Purchasable> all = Counters();
			Transform me = (((Object)(object)Player.LocalPlayer != (Object)null) ? Player.LocalPlayer.Transform : null);
			Material palette;
			Purchasable pick = FindShop(out palette);
			List<string> list = (from p in all.OrderBy((Purchasable p) => (!((Object)(object)me != (Object)null)) ? 0f : Flat(((Component)p).transform.position - me.position)).Take(20)
				select string.Format("{0}{1} [{2}], {3}, {4} counters around", ((Object)(object)p == (Object)(object)pick) ? "* " : "  ", ((Object)p).name.Replace("(Purchasable)", "").Trim(), ((object)p).GetType().Name, FromMe(((Component)p).transform.position), Neighbours(p, all))).ToList();
			list.Insert(0, $"island {Game.ActiveMainIsland}: {all.Count} counters (* = where the Web Fishing Lure stand goes)");
			NPC val = FindNpc();
			list.Add(((Object)(object)val != (Object)null) ? ("NPC (the Shiny Charm stand goes behind it): " + ((Object)val).name + ", " + FromMe(((Component)val).transform.position)) : "no NPC on this island");
			return string.Join("\n", list);
		});
		c.Register("shop.here", "DEBUG: put both shop stands on this island (next to its shop) until you leave it", (Func<string[], string>)delegate
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			if (Game.ActiveMainIsland < 0)
			{
				return "stand on a main island";
			}
			DestroyStands();
			Material palette;
			Purchasable val = FindShop(out palette);
			if ((Object)(object)val != (Object)null)
			{
				_lureStand = BuildLure(val, palette);
			}
			_charmStand = BuildCharm(val);
			_standsHere = true;
			return "Web Fishing Lure: " + (((Object)(object)_lureStand != (Object)null) ? FromMe(((Component)_lureStand).transform.position) : "no room or no shop") + "; Shiny Charm: " + (((Object)(object)_charmStand != (Object)null) ? FromMe(((Component)_charmStand).transform.position) : "no room or no shop/NPC");
		});
	}

	private void UpdateVisuals()
	{
		//IL_019a: Unknown result type (might be due to invalid IL or missing references)
		//IL_019f: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_017b: Unknown result type (might be due to invalid IL or missing references)
		HashSet<ulong> seen = new HashSet<ulong>();
		foreach (Player player in PlayerManager.Players)
		{
			if ((Object)(object)player == (Object)null)
			{
				continue;
			}
			ulong steamID = player.SteamID;
			seen.Add(steamID);
			PlayerKeyItems val = Of(steamID);
			Item val2 = (((Object)(object)player.Holding != (Object)null) ? player.Holding.HeldItem : null);
			FishingRod val3 = (((Object)(object)val2 != (Object)null) ? val2.FishingRod : null);
			GameObject val4 = (((Object)(object)val3 != (Object)null && (Object)(object)val3.Bait != (Object)null) ? ((Component)val3.Bait).gameObject : null);
			bool flag = val != null && val.WfLureOn && (Object)(object)val4 != (Object)null;
			if (_invertedBaits.TryGetValue(steamID, out var value) && ((Object)(object)value != (Object)(object)val4 || !flag))
			{
				if ((Object)(object)value != (Object)null)
				{
					ShinyLook.Remove(value);
				}
				_invertedBaits.Remove(steamID);
			}
			if (flag && !ShinyLook.Is(val4))
			{
				ShinyLook.Apply(val4, false);
				_invertedBaits[steamID] = val4;
			}
			bool num = val != null && val.ShinyCharm && !val.ShinyCharmOff && (Object)(object)player.Transform != (Object)null;
			_orbs.TryGetValue(steamID, out var value2);
			if (num)
			{
				if ((Object)(object)value2 == (Object)null)
				{
					value2 = (_orbs[steamID] = GlowOrb.Create("WFxHTF Shiny Charm", 0.07f, CharmColour));
				}
				((Component)value2).transform.position = player.Transform.position + Vector3.up * 2.05f;
			}
			else if ((Object)(object)value2 != (Object)null || _orbs.ContainsKey(steamID))
			{
				if ((Object)(object)value2 != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)value2).gameObject);
				}
				_orbs.Remove(steamID);
			}
		}
		foreach (ulong item in _orbs.Keys.Where((ulong id) => !seen.Contains(id)).ToList())
		{
			if ((Object)(object)_orbs[item] != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)_orbs[item]).gameObject);
			}
			_orbs.Remove(item);
		}
		foreach (ulong item2 in _invertedBaits.Keys.Where((ulong id) => !seen.Contains(id)).ToList())
		{
			if ((Object)(object)_invertedBaits[item2] != (Object)null)
			{
				ShinyLook.Remove(_invertedBaits[item2]);
			}
			_invertedBaits.Remove(item2);
		}
	}

	private void ClearAll()
	{
		DestroyStands();
		_standIsland = -2;
		foreach (GlowOrb value in _orbs.Values)
		{
			if ((Object)(object)value != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)value).gameObject);
			}
		}
		_orbs.Clear();
		foreach (GameObject value2 in _invertedBaits.Values)
		{
			if ((Object)(object)value2 != (Object)null)
			{
				ShinyLook.Remove(value2);
			}
		}
		_invertedBaits.Clear();
		Hud.Hide("wflure");
		Hud.Hide("wfshinycharm");
	}
}
internal static class NativeFish
{
	public const string Prefix = "htf:";

	private static FieldRef<Creature, GameObject> _normalLook;

	private static FieldRef<Creature, GameObject> _dripLook;

	private static FieldRef<Item, Mesh> _iconMesh;

	private static readonly Dictionary<string, string> Names = new Dictionary<string, string>();

	public static void Check()
	{
		Game.Require(typeof(GameInfo), "GetSpawnable");
		Game.Require(typeof(Creature), "_disableOnShiny", "_enableOnShiny");
		Game.Require(typeof(Item), "_mesh");
		_normalLook = AccessTools.FieldRefAccess<Creature, GameObject>("_disableOnShiny");
		_dripLook = AccessTools.FieldRefAccess<Creature, GameObject>("_enableOnShiny");
		_iconMesh = AccessTools.FieldRefAccess<Item, Mesh>("_mesh");
	}

	public static bool IsNative(string speciesId)
	{
		return speciesId?.StartsWith("htf:", StringComparison.Ordinal) ?? false;
	}

	public static string IdOf(Item item)
	{
		Item spawnable = GameInfo.GetSpawnable(item.ID);
		return "htf:" + (((Object)(object)spawnable != (Object)null) ? ((Object)spawnable).name : ((Object)item).name.Replace("(Clone)", "").Trim());
	}

	public static Creature Prefab(string speciesId)
	{
		if (!IsNative(speciesId))
		{
			return null;
		}
		Item spawnable = GameInfo.GetSpawnable(speciesId.Substring("htf:".Length).Replace(" ", "").ToLowerInvariant());
		return (Creature)(object)((spawnable is Creature) ? spawnable : null);
	}

	public static bool CanLiveInTank(Creature c)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)c != (Object)null && (int)c.BossType == 0)
		{
			if (!(c is Fish))
			{
				return c is Crab;
			}
			return true;
		}
		return false;
	}

	public static void RememberName(string speciesId, string name)
	{
		if (!string.IsNullOrEmpty(speciesId) && !string.IsNullOrEmpty(name))
		{
			Names[speciesId] = name;
		}
	}

	public static string Name(string speciesId)
	{
		if (speciesId == null)
		{
			return null;
		}
		if (Names.TryGetValue(speciesId, out var value))
		{
			return value;
		}
		if (!IsNative(speciesId))
		{
			return null;
		}
		string text = speciesId.Substring("htf:".Length);
		StringBuilder stringBuilder = new StringBuilder(text.Length + 4);
		for (int i = 0; i < text.Length; i++)
		{
			if (i > 0 && char.IsUpper(text[i]) && char.IsLower(text[i - 1]))
			{
				stringBuilder.Append(' ');
			}
			stringBuilder.Append(text[i]);
		}
		return stringBuilder.ToString();
	}

	public static GameObject Model(string speciesId, bool drip)
	{
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Expected O, but got Unknown
		//IL_0256: Unknown result type (might be due to invalid IL or missing references)
		//IL_025b: Unknown result type (might be due to invalid IL or missing references)
		//IL_026d: Unknown result type (might be due to invalid IL or missing references)
		//IL_027b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0280: Unknown result type (might be due to invalid IL or missing references)
		//IL_028a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0296: Unknown result type (might be due to invalid IL or missing references)
		//IL_029b: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_02bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_02be: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d0: Unknown result type (might be due to invalid IL or missing references)
		//IL_02da: Unknown result type (might be due to invalid IL or missing references)
		//IL_02e4: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0300: Unknown result type (might be due to invalid IL or missing references)
		//IL_030a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0371: Unknown result type (might be due to invalid IL or missing references)
		Creature val = Prefab(speciesId);
		if ((Object)(object)val == (Object)null)
		{
			AdapterPlugin.Log.LogWarning((object)("tank: no How to Fish creature named " + speciesId));
			return null;
		}
		GameObject val2 = new GameObject("WFxHTF " + ((Object)val).name + (drip ? " (drip)" : ""));
		Transform src = ((Component)val).transform;
		GameObject val3 = _normalLook.Invoke(val);
		GameObject val4 = _dripLook.Invoke(val);
		GameObject on = ((drip && (Object)(object)val4 != (Object)null) ? val4 : val3);
		GameObject off = (((Object)(object)on == (Object)(object)val4) ? val3 : val4);
		if (drip && (Object)(object)val4 == (Object)null)
		{
			AdapterPlugin.Log.LogWarning((object)("tank: " + speciesId + " has no drip look; showing the normal one"));
		}
		List<(Mesh, Material[], Transform)> list = new List<(Mesh, Material[], Transform)>();
		MeshFilter[] componentsInChildren = ((Component)val).GetComponentsInChildren<MeshFilter>(true);
		foreach (MeshFilter val5 in componentsInChildren)
		{
			MeshRenderer component = ((Component)val5).GetComponent<MeshRenderer>();
			if ((Object)(object)component != (Object)null && (Object)(object)val5.sharedMesh != (Object)null && Include(((Component)val5).transform))
			{
				list.Add((val5.sharedMesh, ((Renderer)component).sharedMaterials, ((Component)val5).transform));
			}
		}
		SkinnedMeshRenderer[] componentsInChildren2 = ((Component)val).GetComponentsInChildren<SkinnedMeshRenderer>(true);
		foreach (SkinnedMeshRenderer val6 in componentsInChildren2)
		{
			if ((Object)(object)val6.sharedMesh != (Object)null && Include(((Component)val6).transform))
			{
				list.Add((val6.sharedMesh, ((Renderer)val6).sharedMaterials, ((Component)val6).transform));
			}
		}
		if (list.Count == 0)
		{
			Mesh val7 = _iconMesh.Invoke((Item)(object)val);
			Renderer val8 = ((IEnumerable<Renderer>)((Component)val).GetComponentsInChildren<Renderer>(true)).FirstOrDefault((Func<Renderer, bool>)((Renderer r) => r is MeshRenderer || r is SkinnedMeshRenderer));
			if ((Object)(object)val7 != (Object)null && (Object)(object)val8 != (Object)null)
			{
				list.Add((val7, val8.sharedMaterials, ((Component)val8).transform));
			}
		}
		foreach (var item4 in list)
		{
			Mesh item = item4.Item1;
			Material[] item2 = item4.Item2;
			Transform item3 = item4.Item3;
			GameObject val9 = new GameObject(((Object)item).name);
			val9.transform.SetParent(val2.transform, false);
			val9.transform.localPosition = src.InverseTransformPoint(item3.position);
			val9.transform.localRotation = Quaternion.Inverse(src.rotation) * item3.rotation;
			Vector3 lossyScale = src.lossyScale;
			val9.transform.localScale = new Vector3(item3.lossyScale.x / lossyScale.x, item3.lossyScale.y / lossyScale.y, item3.lossyScale.z / lossyScale.z);
			val9.AddComponent<MeshFilter>().sharedMesh = item;
			((Renderer)val9.AddComponent<MeshRenderer>()).sharedMaterials = item2;
		}
		if (list.Count == 0)
		{
			Object.Destroy((Object)(object)val2);
			AdapterPlugin.Log.LogWarning((object)("tank: no visible mesh found for " + speciesId));
			return null;
		}
		val2.transform.localScale = src.localScale;
		AdapterPlugin.Log.LogInfo((object)string.Format("tank: model for {0}{1}: {2} mesh(es) ({3})", speciesId, drip ? " (drip)" : "", list.Count, string.Join(", ", list.Select(((Mesh, Material[], Transform) p) => ((Object)p.Item1).name))));
		return val2;
		bool Include(Transform t)
		{
			if ((Object)(object)off != (Object)null && (Object)(object)off != (Object)(object)on && t.IsChildOf(off.transform))
			{
				return false;
			}
			return Shown(t, src, ((Object)(object)on != (Object)null) ? on.transform : null);
		}
	}

	private static bool Shown(Transform t, Transform root, Transform switchedOnAtSpawn)
	{
		Transform val = t;
		while ((Object)(object)val != (Object)null)
		{
			if (!((Component)val).gameObject.activeSelf && (Object)(object)val != (Object)(object)switchedOnAtSpawn)
			{
				return false;
			}
			val = (((Object)(object)val == (Object)(object)root) ? null : val.parent);
		}
		return true;
	}
}
internal sealed class NativeShiny
{
	private readonly Rng _rng = new Rng((ulong)(DateTime.UtcNow.Ticks ^ 0x5348494E59L));

	private readonly HashSet<Item> _live = new HashSet<Item>();

	private readonly List<NativeShinyKey> _keys = new List<NativeShinyKey>();

	private readonly List<NativeShinyKey> _unmatched = new List<NativeShinyKey>();

	private readonly List<(Creature Body, float Weight)> _pendingLoads = new List<(Creature, float)>();

	private readonly List<(ShinyMark Msg, float At)> _remotePending = new List<(ShinyMark, float)>();

	private bool _worldLoaded;

	private bool _forceNext;

	private const string NamePrefix = "Shiny ";

	public static NativeShiny Instance { get; private set; }

	private Plugin P => Plugin.Instance;

	public static void Install(Harmony h)
	{
		//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ae: Expected O, but got Unknown
		//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e4: Expected O, but got Unknown
		//IL_010d: Unknown result type (might be due to invalid IL or missing references)
		//IL_011a: Expected O, but got Unknown
		Game.Require(typeof(BossManager), "InitializeBossFight");
		Game.Require(typeof(Creature), "LoadFromSave", "_syncedRandomWeight", "BossType");
		Game.Require(typeof(Item), "GetName", "ID");
		Instance = new NativeShiny();
		h.Patch((MethodBase)AccessTools.Method(typeof(BossManager), "InitializeBossFight", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(NativeShiny), "BossPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		h.Patch((MethodBase)AccessTools.Method(typeof(Creature), "LoadFromSave", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(NativeShiny), "LoadPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		h.Patch((MethodBase)AccessTools.Method(typeof(Item), "GetName", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(NativeShiny), "NamePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
	}

	public bool Is(Item item)
	{
		if ((Object)(object)item != (Object)null)
		{
			return _live.Contains(item);
		}
		return false;
	}

	public void Forget(Item item)
	{
		if ((Object)(object)item != (Object)null)
		{
			_live.Remove(item);
		}
	}

	public bool Roll(Item item, float chance, string why)
	{
		if ((Object)(object)item == (Object)null || (Object)(object)item.Creature == (Object)null || !Game.IsHost || Is(item))
		{
			return false;
		}
		if (_forceNext)
		{
			_forceNext = false;
		}
		else if (_rng.Next01() >= (double)chance)
		{
			return false;
		}
		Mark(item, why);
		return true;
	}

	public void Mark(Item item, string why)
	{
		if (!((Object)(object)item == (Object)null) && _live.Add(item))
		{
			if (Game.IsHost && (Object)(object)item.Creature != (Object)null && why != "loaded from the save")
			{
				MakeKeyUnique(item.Creature);
			}
			try
			{
				ShinyLook.Apply(((Component)item).gameObject, true);
			}
			catch (Exception ex)
			{
				AdapterPlugin.Log.LogWarning((object)("shiny look: " + ex.Message));
			}
			if (Game.IsHost)
			{
				NetService.Instance?.OnShinyMarked(item);
			}
			AdapterPlugin.Log.LogInfo((object)("shiny How to Fish creature: " + ((Object)item).name.Replace("(Clone)", "") + " (" + why + ")"));
		}
	}

	private void MakeKeyUnique(Creature c)
	{
		float w = ((Item)c)._syncedRandomWeight.Value;
		if (w == 1f || _keys.Any((NativeShinyKey k) => k.Matches((int)((Item)c).ID, w)) || Creature.DeadCreatures.Any((Creature o) => (Object)(object)o != (Object)null && (Object)(object)o != (Object)(object)c && ((Item)o).ID == ((Item)c).ID && Mathf.Abs(((Item)o)._syncedRandomWeight.Value - w) <= 1E-05f * Mathf.Max(1f, w)))
		{
			((Item)c)._syncedRandomWeight.Value = w * (1.0002f + (float)_rng.Next01() * 0.0008f);
		}
	}

	public void OnWorldLoaded(List<NativeShinyKey> saved)
	{
		List<(Creature, float)> list = _pendingLoads.Where(((Creature Body, float Weight) p) => (Object)(object)p.Body != (Object)null).ToList();
		_pendingLoads.Clear();
		OnWorldUnloaded();
		foreach (NativeShinyKey item in saved ?? new List<NativeShinyKey>())
		{
			if (item != null && item.ItemId >= 0 && item.ItemId <= 255 && item.Weight > 0f && !float.IsInfinity(item.Weight))
			{
				_keys.Add(item);
			}
		}
		_unmatched.AddRange(_keys);
		_worldLoaded = true;
		foreach (var (c, weight) in list)
		{
			Reload(c, weight);
		}
		if (_keys.Count > 0)
		{
			AdapterPlugin.Log.LogInfo((object)$"shiny: {_keys.Count} shiny creature(s) in the world file, {_keys.Count - _unmatched.Count} found among the {list.Count} loaded so far");
		}
	}

	public void OnWorldUnloaded()
	{
		_live.Clear();
		_keys.Clear();
		_unmatched.Clear();
		_pendingLoads.RemoveAll(((Creature Body, float Weight) p) => (Object)(object)p.Body == (Object)null);
		_remotePending.Clear();
		_worldLoaded = false;
	}

	public List<NativeShinyKey> KeysInSave(ServerSaveObject save)
	{
		//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
		//IL_020c: Expected O, but got Unknown
		List<SavedItem> saved = new List<SavedItem>();
		if (save?.WorldItems != null)
		{
			saved.AddRange(from val in save.WorldItems
				where val?.Item != null
				select val.Item);
		}
		if (save?.Players != null)
		{
			foreach (SavedPlayer item2 in save.Players.Where((SavedPlayer p) => p != null))
			{
				if (item2.HeldItem != null)
				{
					saved.Add(item2.HeldItem);
				}
				if (item2.InventoryItems != null)
				{
					saved.AddRange(item2.InventoryItems.Where((SavedItem i) => i != null));
				}
			}
		}
		_live.RemoveWhere((Item i) => (Object)(object)i == (Object)null);
		List<NativeShinyKey> list = new List<NativeShinyKey>(_keys);
		foreach (Item item in _live.Where((Item i) => (Object)(object)i != (Object)null && (Object)(object)i.Creature != (Object)null))
		{
			float w = ((Item)item.Creature)._syncedRandomWeight.Value;
			if (!list.Any((NativeShinyKey k) => k.Matches((int)item.ID, w)))
			{
				list.Add(new NativeShinyKey
				{
					ItemId = item.ID,
					Weight = w
				});
			}
		}
		List<NativeShinyKey> list2 = list.Where((NativeShinyKey k) => saved.Any((SavedItem s) => s.Exists && k.Matches((int)s.ItemID, s.Weight))).ToList();
		_keys.Clear();
		_keys.AddRange(list2);
		return list2;
	}

	private static void LoadPostfix(Creature __instance, SavedItem savedItem)
	{
		NativeShiny instance = Instance;
		if (instance == null || (Object)(object)__instance == (Object)null || savedItem == null || !InstanceFinder.IsServerStarted)
		{
			return;
		}
		if (!instance._worldLoaded)
		{
			instance._pendingLoads.RemoveAll(((Creature Body, float Weight) p) => (Object)(object)p.Body == (Object)null);
			instance._pendingLoads.Add((__instance, savedItem.Weight));
		}
		else
		{
			instance.Reload(__instance, savedItem.Weight);
		}
	}

	private void Reload(Creature c, float weight)
	{
		if (!((Object)(object)c == (Object)null))
		{
			int num = _unmatched.FindIndex((NativeShinyKey k) => k.Matches((int)((Item)c).ID, weight));
			if (num >= 0)
			{
				_unmatched.RemoveAt(num);
				Mark((Item)(object)c, "loaded from the save");
			}
		}
	}

	private static void BossPostfix(Creature creature)
	{
		try
		{
			NativeShiny instance = Instance;
			if (instance != null && !((Object)(object)creature == (Object)null) && Game.IsHost && (instance.Is((Item)(object)creature) || instance.Roll((Item)(object)creature, instance.P.Mapping.BossShinyChance, "boss")))
			{
				TryChat("<b><color=#fff6a0>A SHINY boss appeared!</color></b>");
			}
		}
		catch (Exception ex)
		{
			AdapterPlugin.Log.LogWarning((object)("shiny boss: " + ex.Message));
		}
	}

	private static void NamePostfix(Item __instance, ref string __result)
	{
		if (Instance != null && (Object)(object)__instance != (Object)null && Instance._live.Contains(__instance) && __result != null && !__result.StartsWith("Shiny "))
		{
			__result = "Shiny " + __result;
		}
	}

	public static string Plain(string name)
	{
		if (name == null || !name.StartsWith("Shiny "))
		{
			return name;
		}
		return name.Substring("Shiny ".Length);
	}

	private static void TryChat(string text)
	{
		try
		{
			Game.LocalChat(text);
		}
		catch
		{
		}
	}

	public void RegisterCommands(DebugConsole c)
	{
		c.Register("shiny.held", "DEBUG (host): make the How to Fish creature in your hand shiny", (Func<string[], string>)delegate
		{
			if (!Game.IsHost)
			{
				return "host only";
			}
			Item val = (((Object)(object)Player.LocalPlayer != (Object)null && (Object)(object)Player.LocalPlayer.Holding != (Object)null) ? Player.LocalPlayer.Holding.HeldItem : null);
			if ((Object)(object)val == (Object)null || (Object)(object)val.Creature == (Object)null)
			{
				return "hold a How to Fish creature first";
			}
			if (WfFishService.Instance?.TagOf(val) != null)
			{
				return "that's a WEBFISHING fish (use wf.nextbite <name> shiny)";
			}
			Mark(val, "shiny.held");
			return val.GetName() + " is shiny now";
		});
		c.Register("shiny.next", "DEBUG (host): the next How to Fish bite, TNT fish or boss is shiny", (Func<string[], string>)delegate
		{
			if (!Game.IsHost)
			{
				return "host only";
			}
			_forceNext = true;
			return "the next How to Fish bite, TNT fish or boss will be shiny";
		});
	}

	public void OnClientJoined()
	{
		OnWorldUnloaded();
	}

	public void OnClientLeft()
	{
		OnWorldUnloaded();
	}

	public IEnumerable<(Item Item, ShinyMark Msg)> LiveMarks()
	{
		return from i in _live.Where((Item i) => (Object)(object)i != (Object)null).ToList()
			select ((Item i, ShinyMark))(i: i, new ShinyMark
			{
				ItemId = i.ID
			});
	}

	public void OnRemote(ShinyMark m)
	{
		if (!Game.IsHost && m != null && !TryApplyRemote(m))
		{
			_remotePending.RemoveAll(((ShinyMark Msg, float At) p) => p.Msg.ObjectId == m.ObjectId);
			if (_remotePending.Count >= 300)
			{
				_remotePending.RemoveAt(0);
			}
			_remotePending.Add((m, Time.unscaledTime));
		}
	}

	public void RetryRemote()
	{
		for (int num = _remotePending.Count - 1; num >= 0; num--)
		{
			if (TryApplyRemote(_remotePending[num].Msg))
			{
				_remotePending.RemoveAt(num);
			}
			else if (Time.unscaledTime - _remotePending[num].At > 60f)
			{
				_remotePending.RemoveAt(num);
			}
		}
	}

	private bool TryApplyRemote(ShinyMark m)
	{
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		ClientManager clientManager = InstanceFinder.ClientManager;
		if ((Object)(object)clientManager == (Object)null || clientManager.Objects == null || !((ManagedObjects)clientManager.Objects).Spawned.TryGetValue(m.ObjectId, out var value) || (Object)(object)value == (Object)null)
		{
			return false;
		}
		Item component = ((Component)value).GetComponent<Item>();
		if ((Object)(object)component == (Object)null || component.ID != m.ItemId)
		{
			return false;
		}
		bool num = !Is(component);
		Mark(component, "from the host");
		if (num && (Object)(object)component.Creature != (Object)null && (int)component.Creature.BossType != 0 && !component.Creature.IsDead)
		{
			TryChat("<b><color=#fff6a0>A SHINY boss appeared!</color></b>");
		}
		return true;
	}
}
internal sealed class NetService : MonoBehaviour
{
	public enum Role
	{
		Off,
		Host,
		Client
	}

	private sealed class Peer
	{
		public ulong Id;

		public string Name;

		public NetCompat Compat;

		public readonly RateLimit Rate = new RateLimit();

		public double LastSnapshot = double.NegativeInfinity;

		public double LastWorldSent = double.NegativeInfinity;

		public int Dropped;

		public bool Warned;

		public bool HasCache;

		public bool Linked
		{
			get
			{
				if (Compat != null)
				{
					return Compat.Linked;
				}
				return false;
			}
		}
	}

	private readonly SteamLink _link = new SteamLink();

	private Role _role;

	private float _errorLogAt;

	private readonly Dictionary<ulong, Peer> _peers = new Dictionary<ulong, Peer>();

	private readonly HashSet<ulong> _present = new HashSet<ulong>();

	private const int MaxFromClient = 16384;

	private readonly AqBroadcaster _bc = new AqBroadcaster();

	private readonly List<(Item Item, SpawnTag Msg, float At)> _outTags = new List<(Item, SpawnTag, float)>();

	private readonly List<(Item Item, ShinyMark Msg, float At)> _outMarks = new List<(Item, ShinyMark, float)>();

	private bool? _sentRain;

	private AqMirror _loop;

	private int _loopDeltas;

	private int _loopTags;

	private int _loopRain;

	private string _loopProblem;

	private ulong _hostId;

	private bool _hostAnswered;

	private NetCompat _hostCompat;

	private double _helloAt = double.NegativeInfinity;

	private int _helloTries;

	private AqMirror _mirror;

	private readonly PendingRequests _pending = new PendingRequests();

	private bool? _clientRain;

	private const int MaxHelloTries = 40;

	private const int MaxKeyItemOwners = 256;

	public static NetService Instance { get; private set; }

	private Plugin P => Plugin.Instance;

	private double Now => Time.realtimeSinceStartupAsDouble;

	public KeyItemsState ClientKeyItems { get; private set; }

	public Role Current => _role;

	private bool ClientLinked
	{
		get
		{
			if (_role == Role.Client && _hostCompat != null)
			{
				return _hostCompat.Linked;
			}
			return false;
		}
	}

	public AqMirror ClientMirror
	{
		get
		{
			if (!ClientLinked || _mirror == null || _mirror.State == null)
			{
				return null;
			}
			return _mirror;
		}
	}

	private bool AnyoneListening
	{
		get
		{
			if (!_peers.Values.Any((Peer p) => p.Linked))
			{
				return _loop != null;
			}
			return true;
		}
	}

	public static void Che

BepInEx/plugins/WFxHTF/WFxHTF.Core.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using WFxHTF.Data;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("WFxHTF")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+166b9aae0d28865ba70c4894450aeb4db4b21aeb")]
[assembly: AssemblyProduct("WFxHTF.Core")]
[assembly: AssemblyTitle("WFxHTF.Core")]
[assembly: AssemblyVersion("0.1.0.0")]
namespace WFxHTF.Core;

[JsonConverter(typeof(StringEnumConverter))]
public enum AqEventKind
{
	FishDied,
	AttackStarted,
	AttackEnded
}
public sealed class AqEvent
{
	public AqEventKind Kind;

	public long Uid;

	public string SpeciesId;

	public string Reason;

	public bool Defeated;
}
public sealed class Aquarium
{
	private const float TimerEps = 0.0001f;

	public readonly AquariumConfig Config;

	public AquariumState State;

	public int Players = 1;

	public const float MaxCoordinate = 100000f;

	public int Capacity => Config.BaseCapacity + State.CapacityLevel * Config.CapacityPerLevel;

	public float SizeScale
	{
		get
		{
			float num = (float.IsNaN(Config.MaxSizeScale) ? 1f : Math.Max(1f, Math.Min(3f, Config.MaxSizeScale)));
			if (Config.MaxCapacityLevel <= 0)
			{
				return 1f;
			}
			float num2 = (float)Math.Max(0, Math.Min(Config.MaxCapacityLevel, State.CapacityLevel)) / (float)Config.MaxCapacityLevel;
			return 1f + (num - 1f) * num2;
		}
	}

	public float PlayerIncomeFactor => 1f + Config.IncomePerExtraPlayer * (float)Math.Max(0, Players - 1);

	public int HopperCapacity => State.AutoFeederLevel * Config.HopperPerLevel;

	public bool AttackActive => State.Attack != null;

	public bool IsPlaced
	{
		get
		{
			if (State.Placement != null)
			{
				return State.Placement.Placed;
			}
			return false;
		}
	}

	public Aquarium(AquariumConfig config, AquariumState state = null)
	{
		Config = config ?? new AquariumConfig();
		State = state ?? new AquariumState
		{
			NextAttackInDays = Config.AttackIntervalDays
		};
	}

	public bool TryDeposit(string speciesId, float weightKg, Quality quality, ulong depositorId, string depositorName, int value, out long uid, out string error, bool drip = false, bool shiny = false, float cookness = 0f, float betMultiplier = 1f, float killScore = 1f)
	{
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		uid = 0L;
		if (string.IsNullOrEmpty(speciesId))
		{
			error = "no species";
			return false;
		}
		if (State.Fish.Count >= Capacity)
		{
			error = "tank full";
			return false;
		}
		if (value < 0 || !(weightKg > 0f) || float.IsInfinity(weightKg) || !Enum.IsDefined(typeof(Quality), quality))
		{
			error = "bad fish";
			return false;
		}
		uid = State.NextUid++;
		State.Fish.Add(new AqFish
		{
			Uid = uid,
			SpeciesId = speciesId,
			WeightKg = weightKg,
			Quality = quality,
			Drip = drip,
			Shiny = shiny,
			DepositorId = depositorId,
			DepositorName = depositorName,
			Value = value,
			Cookness = Finite(cookness, 0f, 0f, 10f),
			BetMultiplier = Finite(betMultiplier, 1f, 0f, 1000f),
			KillScore = Finite(killScore, 1f, 0f, 100f)
		});
		State.Seq++;
		error = null;
		return true;
	}

	private static float Finite(float v, float fallback, float min, float max)
	{
		if (!float.IsNaN(v) && !float.IsInfinity(v))
		{
			return Math.Max(min, Math.Min(max, v));
		}
		return fallback;
	}

	public int Feed(int units)
	{
		int i;
		for (i = 0; i < units; i++)
		{
			AqFish aqFish = (from f in State.Fish
				where f.Hunger < Config.FeedBelow
				orderby f.Hunger, f.Uid
				select f).FirstOrDefault();
			if (aqFish == null)
			{
				break;
			}
			aqFish.Hunger = 1f;
			aqFish.StarvedDays = 0f;
		}
		int num = Math.Min(units - i, HopperCapacity - State.HopperFood);
		if (num > 0)
		{
			State.HopperFood += num;
			i += num;
		}
		if (i > 0)
		{
			State.Seq++;
		}
		return i;
	}

	public Dictionary<ulong, int> Collect(IReadOnlyCollection<ulong> presentPlayers)
	{
		Dictionary<ulong, int> dictionary = PlanCollect(presentPlayers);
		foreach (KeyValuePair<ulong, int> item in dictionary)
		{
			Withdraw(item.Value);
		}
		return dictionary;
	}

	public Dictionary<ulong, int> PlanCollect(IReadOnlyCollection<ulong> presentPlayers)
	{
		Dictionary<ulong, int> dictionary = new Dictionary<ulong, int>();
		List<ulong> list = presentPlayers?.Distinct().ToList() ?? new List<ulong>();
		if (list.Count == 0)
		{
			return dictionary;
		}
		int num = (int)Math.Floor(State.TrayCoins / (double)list.Count);
		if (num <= 0)
		{
			return dictionary;
		}
		foreach (ulong item in list)
		{
			dictionary[item] = num;
		}
		return dictionary;
	}

	public void Withdraw(int coins)
	{
		if (coins > 0)
		{
			State.TrayCoins = Math.Max(0.0, State.TrayCoins - (double)coins);
			State.Seq++;
		}
	}

	public AqFish Remove(long uid)
	{
		int num = State.Fish.FindIndex((AqFish f) => f.Uid == uid);
		if (num < 0)
		{
			return null;
		}
		AqFish result = State.Fish[num];
		State.Fish.RemoveAt(num);
		State.Seq++;
		return result;
	}

	public int? UpgradeCost(UpgradeType type)
	{
		int num = Level(type);
		int[] array = Prices(type);
		if (num >= array.Length)
		{
			return null;
		}
		return array[num];
	}

	public bool ApplyUpgrade(UpgradeType type)
	{
		if (!UpgradeCost(type).HasValue)
		{
			return false;
		}
		switch (type)
		{
		case UpgradeType.Capacity:
			State.CapacityLevel++;
			break;
		case UpgradeType.AutoFeeder:
			State.AutoFeederLevel++;
			break;
		case UpgradeType.Decor:
			State.DecorLevel++;
			break;
		}
		State.Seq++;
		return true;
	}

	public bool IsLiveOn(int activeIsland)
	{
		if (IsPlaced)
		{
			return State.Placement.Island == activeIsland;
		}
		return false;
	}

	public bool TryPlace(int island, float x, float y, float z, float rotY, out string error)
	{
		error = null;
		if (AttackActive)
		{
			error = "can't move the tank during an attack";
			return false;
		}
		if (island < 0 || island > 255)
		{
			error = "bad island";
			return false;
		}
		float[] array = new float[4] { x, y, z, rotY };
		foreach (float num in array)
		{
			if (float.IsNaN(num) || float.IsInfinity(num) || Math.Abs(num) > 100000f)
			{
				error = "bad position";
				return false;
			}
		}
		bool isPlaced = IsPlaced;
		State.Placement = new TankPlacement
		{
			Placed = true,
			Island = island,
			X = x,
			Y = y,
			Z = z,
			RotY = (rotY % 360f + 360f) % 360f
		};
		if (!isPlaced)
		{
			State.NextAttackInDays = Math.Max(State.NextAttackInDays, Config.AttackGraceDays);
		}
		State.Seq++;
		return true;
	}

	public bool TryPack(out string error)
	{
		error = null;
		if (AttackActive)
		{
			error = "can't pack the tank during an attack";
			return false;
		}
		if (!IsPlaced)
		{
			error = "the tank is already packed";
			return false;
		}
		State.Placement.Placed = false;
		State.Seq++;
		return true;
	}

	public void Hold(double tankDay)
	{
		State.LastTickDay = tankDay;
	}

	public bool TryToggleZen(out string error)
	{
		if (AttackActive)
		{
			error = "can't change Zen mode during an attack";
			return false;
		}
		if (State.ZenCooldownDays > 0f)
		{
			error = "Zen mode was changed recently: try again in " + RealTimeText(State.ZenCooldownDays) + " (counts down while the tank is on your island)";
			return false;
		}
		State.Zen = !State.Zen;
		State.ZenCooldownDays = (State.Zen ? Config.ZenToggleCooldownDays : Math.Max(Config.ZenToggleCooldownDays, Config.AttackGraceDays * 1.5f));
		if (!State.Zen)
		{
			State.NextAttackInDays = Math.Max(State.NextAttackInDays, Config.AttackGraceDays);
		}
		State.Seq++;
		error = null;
		return true;
	}

	public List<AqEvent> ReportThreatHp(float hp, float maxHp)
	{
		List<AqEvent> list = new List<AqEvent>();
		if (State.Attack == null)
		{
			return list;
		}
		State.Attack.ThreatMaxHp = Math.Max(maxHp, 0f);
		State.Attack.ThreatHp = Math.Max(hp, 0f);
		if (hp <= 0f)
		{
			EndAttack(list, defeated: true);
		}
		State.Seq++;
		return list;
	}

	public List<AqEvent> TickTo(double tankDay)
	{
		double num = tankDay - State.LastTickDay;
		State.LastTickDay = tankDay;
		if (num <= 0.0)
		{
			return new List<AqEvent>();
		}
		return Advance(Math.Min(num, Config.MaxTickGapDays));
	}

	public void OnWorldLoaded(double tankDay)
	{
		State.LastTickDay = tankDay;
		State.ZenCooldownDays = Math.Min(State.ZenCooldownDays, Math.Max(Config.ZenToggleCooldownDays, Config.AttackGraceDays * 1.5f));
		State.Seq++;
	}

	public string RealTimeText(double days)
	{
		double num = Math.Max(0.0, days * (double)Config.TankDayRealMinutes * 60.0);
		return $"{(int)(num / 60.0)}:{(int)(num % 60.0):00}";
	}

	public double TankDays(double playtimeSeconds)
	{
		return playtimeSeconds / 60.0 / (double)Config.TankDayRealMinutes;
	}

	public List<AqEvent> Advance(double days, bool live = true)
	{
		List<AqEvent> list = new List<AqEvent>();
		if (days <= 0.0)
		{
			return list;
		}
		double num = days;
		bool flag = false;
		while (num > 1E-09)
		{
			float num2 = (float)Math.Min(Config.StepDays, num);
			Step(num2, list, live && !flag);
			if (!flag && list.Exists((AqEvent e) => e.Kind == AqEventKind.AttackStarted))
			{
				flag = true;
			}
			num -= (double)num2;
		}
		State.Seq++;
		return list;
	}

	private void Step(float dt, List<AqEvent> events, bool live)
	{
		AquariumState state = State;
		AquariumConfig config = Config;
		if (state.ZenCooldownDays > 0f)
		{
			state.ZenCooldownDays = Math.Max(0f, state.ZenCooldownDays - dt);
		}
		float num = config.IncomeRate * (1f + (float)state.DecorLevel * config.DecorIncomeBonusPerLevel) * (state.Zen ? config.ZenIncomeRate : 1f) * PlayerIncomeFactor;
		for (int num2 = state.Fish.Count - 1; num2 >= 0; num2--)
		{
			AqFish aqFish = state.Fish[num2];
			aqFish.Hunger = Math.Max(0f, aqFish.Hunger - config.HungerPerDay * (state.Zen ? config.ZenHungerRate : 1f) * dt);
			if (aqFish.Hunger < config.AutoFeedBelow && state.AutoFeederLevel > 0 && state.HopperFood > 0)
			{
				state.HopperFood--;
				aqFish.Hunger = 1f;
				aqFish.StarvedDays = 0f;
			}
			if (aqFish.Hunger > 0f)
			{
				state.TrayCoins += (float)aqFish.Value * num * dt;
				if (state.Attack == null)
				{
					aqFish.Health = Math.Min(1f, aqFish.Health + config.HealthRegenPerDay * dt);
				}
			}
			else
			{
				aqFish.StarvedDays += dt;
				if (aqFish.StarvedDays >= config.StarveDeathDays - 0.0001f)
				{
					Kill(num2, "starved", events);
					continue;
				}
			}
			if (state.Attack != null && live)
			{
				aqFish.Health -= config.AttackDrainPerDay * dt;
				if (aqFish.Health <= 0f)
				{
					aqFish.Health = 0f;
					Kill(num2, "attack", events);
				}
			}
		}
		if (state.Attack != null)
		{
			if (live)
			{
				state.Attack.ElapsedDays += dt;
				if (state.Fish.Count == 0)
				{
					EndAttack(events, defeated: false);
				}
				else if (state.Attack.ElapsedDays >= config.AttackTimeoutDays - 0.0001f)
				{
					int index = WeakestIndex();
					Kill(index, "attack", events);
					EndAttack(events, defeated: false);
				}
			}
		}
		else
		{
			if (state.Zen)
			{
				return;
			}
			state.NextAttackInDays -= dt;
			if (!live)
			{
				state.NextAttackInDays = Math.Max(state.NextAttackInDays, config.AttackGraceDays);
			}
			else if (state.NextAttackInDays <= 0.0001f)
			{
				if (state.Fish.Count > 0)
				{
					state.Attack = new AqAttack
					{
						ThreatHp = 1f,
						ThreatMaxHp = 1f
					};
					events.Add(new AqEvent
					{
						Kind = AqEventKind.AttackStarted
					});
				}
				else
				{
					state.NextAttackInDays = config.AttackIntervalDays;
				}
			}
		}
	}

	public List<AqEvent> ForceAttack()
	{
		List<AqEvent> list = new List<AqEvent>();
		if (State.Attack != null || State.Fish.Count == 0)
		{
			return list;
		}
		State.Attack = new AqAttack
		{
			ThreatHp = 1f,
			ThreatMaxHp = 1f
		};
		list.Add(new AqEvent
		{
			Kind = AqEventKind.AttackStarted
		});
		State.Seq++;
		return list;
	}

	private int WeakestIndex()
	{
		int num = 0;
		for (int i = 1; i < State.Fish.Count; i++)
		{
			AqFish aqFish = State.Fish[i];
			AqFish aqFish2 = State.Fish[num];
			if (aqFish.Health < aqFish2.Health || (aqFish.Health == aqFish2.Health && (aqFish.Value < aqFish2.Value || (aqFish.Value == aqFish2.Value && aqFish.Uid < aqFish2.Uid))))
			{
				num = i;
			}
		}
		return num;
	}

	private void Kill(int index, string reason, List<AqEvent> events)
	{
		AqFish aqFish = State.Fish[index];
		State.Fish.RemoveAt(index);
		events.Add(new AqEvent
		{
			Kind = AqEventKind.FishDied,
			Uid = aqFish.Uid,
			SpeciesId = aqFish.SpeciesId,
			Reason = reason
		});
	}

	private void EndAttack(List<AqEvent> events, bool defeated)
	{
		State.Attack = null;
		State.NextAttackInDays = Config.AttackIntervalDays;
		events.Add(new AqEvent
		{
			Kind = AqEventKind.AttackEnded,
			Defeated = defeated
		});
	}

	private int Level(UpgradeType t)
	{
		return t switch
		{
			UpgradeType.AutoFeeder => State.AutoFeederLevel, 
			UpgradeType.Capacity => State.CapacityLevel, 
			_ => State.DecorLevel, 
		};
	}

	private int[] Prices(UpgradeType t)
	{
		int[] array = t switch
		{
			UpgradeType.AutoFeeder => Config.AutoFeederPrices, 
			UpgradeType.Capacity => Config.CapacityPrices, 
			_ => Config.DecorPrices, 
		};
		int num = t switch
		{
			UpgradeType.AutoFeeder => Config.MaxAutoFeederLevel, 
			UpgradeType.Capacity => Config.MaxCapacityLevel, 
			_ => Config.MaxDecorLevel, 
		};
		if (array.Length <= num)
		{
			return array;
		}
		return array.Take(num).ToArray();
	}

	public string Hash()
	{
		return AquariumSync.HashOf(State);
	}

	public bool IsValid(out string why)
	{
		why = null;
		AquariumState s = State;
		if (s.Schema < 1 || s.Schema > 1)
		{
			why = $"schema {s.Schema}";
			return false;
		}
		if (s.Fish == null)
		{
			why = "no fish list";
			return false;
		}
		if (s.Fish.Any((AqFish f) => f == null || string.IsNullOrEmpty(f.SpeciesId)))
		{
			why = "fish without a species";
			return false;
		}
		if (s.TrayCoins < 0.0 || double.IsNaN(s.TrayCoins))
		{
			why = "bad tray";
			return false;
		}
		if (s.Fish.Select((AqFish f) => f.Uid).Distinct().Count() != s.Fish.Count)
		{
			why = "duplicate fish uid";
			return false;
		}
		if (s.Fish.Any((AqFish f) => f.Uid >= s.NextUid))
		{
			why = "uid counter behind fish";
			return false;
		}
		if (s.Placement != null)
		{
			TankPlacement placement = s.Placement;
			if (placement.Island < -1 || placement.Island > 255)
			{
				why = "bad placement island";
				return false;
			}
			float[] array = new float[4] { placement.X, placement.Y, placement.Z, placement.RotY };
			foreach (float num2 in array)
			{
				if (float.IsNaN(num2) || float.IsInfinity(num2) || Math.Abs(num2) > 100000f)
				{
					why = "bad placement";
					return false;
				}
			}
			if (placement.Placed && placement.Island < 0)
			{
				why = "placed without an island";
				return false;
			}
		}
		return true;
	}
}
public sealed class HeldFish
{
	public string SpeciesId;

	public float WeightKg;

	public Quality Quality;

	public bool Drip;

	public bool Shiny;

	public float Cookness;

	public float BetMultiplier = 1f;

	public float KillScore = 1f;
}
public interface IAquariumPorts
{
	HeldFish PeekHeldFish(ulong playerId);

	bool TakeHeldFish(ulong playerId, HeldFish fish);

	int ValueOf(HeldFish fish);

	int TakeFood(ulong playerId, int units);

	bool TryCharge(ulong playerId, int coins);

	void Pay(ulong playerId, int coins);

	IReadOnlyCollection<ulong> PlayersInLobby();

	string NameOf(ulong playerId);

	bool IsHost(ulong playerId);

	bool GiveFish(ulong playerId, AqFish fish);
}
public sealed class AquariumHost
{
	public const int MaxRequestIdLength = 64;

	public const int MaxFoodPerRequest = 1000;

	public readonly Aquarium Tank;

	private readonly IAquariumPorts _ports;

	private readonly Dictionary<string, AqResult> _seen = new Dictionary<string, AqResult>();

	private readonly Queue<string> _seenOrder = new Queue<string>();

	private const int SeenLimit = 1024;

	public AquariumHost(Aquarium tank, IAquariumPorts ports)
	{
		Tank = tank;
		_ports = ports;
	}

	public AqResult Handle(ulong senderId, AqRequest req)
	{
		if (req == null)
		{
			return Fail("no request");
		}
		if (!Enum.IsDefined(typeof(AqRequestKind), req.Kind))
		{
			return Fail("unknown request");
		}
		bool flag = req.Kind != AqRequestKind.SnapshotPlease;
		if (flag && string.IsNullOrEmpty(req.RequestId))
		{
			return Fail("request id required");
		}
		if (req.RequestId != null && req.RequestId.Length > 64)
		{
			return Fail("request id too long");
		}
		string key = senderId + ":" + req.RequestId;
		if (flag && _seen.TryGetValue(key, out var value))
		{
			return value;
		}
		AqResult aqResult;
		try
		{
			aqResult = Execute(senderId, req);
		}
		catch (Exception ex)
		{
			return new AqResult
			{
				RequestId = req.RequestId,
				Ok = false,
				Error = "host error: " + ex.GetType().Name
			};
		}
		aqResult.RequestId = req.RequestId;
		if (flag)
		{
			Remember(key, aqResult);
		}
		return aqResult;
	}

	private AqResult Execute(ulong sender, AqRequest req)
	{
		//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0136: Unknown result type (might be due to invalid IL or missing references)
		switch (req.Kind)
		{
		case AqRequestKind.Deposit:
		{
			if (Tank.State.Fish.Count >= Tank.Capacity)
			{
				return Fail("tank full");
			}
			HeldFish heldFish = _ports.PeekHeldFish(sender);
			if (heldFish == null)
			{
				return Fail("not holding a fish");
			}
			if (string.IsNullOrEmpty(heldFish.SpeciesId) || !IsFinitePositive(heldFish.WeightKg) || !Enum.IsDefined(typeof(Quality), heldFish.Quality))
			{
				return Fail("bad fish");
			}
			int num = _ports.ValueOf(heldFish);
			if (num < 0)
			{
				return Fail("bad fish");
			}
			string depositorName = _ports.NameOf(sender);
			if (!_ports.TakeHeldFish(sender, heldFish))
			{
				return Fail("fish is gone");
			}
			Tank.TryDeposit(heldFish.SpeciesId, heldFish.WeightKg, heldFish.Quality, sender, depositorName, num, out var uid, out var _, heldFish.Drip, heldFish.Shiny, heldFish.Cookness, heldFish.BetMultiplier, heldFish.KillScore);
			return new AqResult
			{
				Ok = true,
				Amount = uid
			};
		}
		case AqRequestKind.Feed:
		{
			if (req.FoodUnits <= 0 || req.FoodUnits > 1000)
			{
				return Fail("bad food amount");
			}
			int num2 = FeedableUnits(req.FoodUnits);
			if (num2 == 0)
			{
				return Fail("no fish need food and the hopper is full");
			}
			int num3 = _ports.TakeFood(sender, num2);
			if (num3 <= 0)
			{
				return Fail("no food in inventory");
			}
			return new AqResult
			{
				Ok = true,
				Amount = Tank.Feed(Math.Min(num3, num2))
			};
		}
		case AqRequestKind.Collect:
		{
			int num5 = 0;
			string text = null;
			foreach (KeyValuePair<ulong, int> item in Tank.PlanCollect(_ports.PlayersInLobby()))
			{
				try
				{
					_ports.Pay(item.Key, item.Value);
					Tank.Withdraw(item.Value);
					if (item.Key == sender)
					{
						num5 = item.Value;
					}
				}
				catch (Exception ex)
				{
					text = text ?? $"paying {item.Key} failed: {ex.GetType().Name}";
				}
			}
			return new AqResult
			{
				Ok = (text == null),
				Error = text,
				Amount = num5
			};
		}
		case AqRequestKind.Upgrade:
		{
			if (!Enum.IsDefined(typeof(UpgradeType), req.Upgrade))
			{
				return Fail("unknown upgrade");
			}
			int? num4 = Tank.UpgradeCost(req.Upgrade);
			if (!num4.HasValue)
			{
				return Fail("already at max level");
			}
			if (!_ports.TryCharge(sender, num4.Value))
			{
				return Fail("not enough coins");
			}
			Tank.ApplyUpgrade(req.Upgrade);
			return new AqResult
			{
				Ok = true,
				Amount = num4.Value
			};
		}
		case AqRequestKind.ToggleZen:
		{
			if (!Tank.TryToggleZen(out var error3))
			{
				return Fail(error3);
			}
			return new AqResult
			{
				Ok = true
			};
		}
		case AqRequestKind.Place:
		{
			if (!_ports.IsHost(sender))
			{
				return Fail("only the host can move the tank");
			}
			if (!Tank.TryPlace(req.Island, req.X, req.Y, req.Z, req.RotY, out var error4))
			{
				return Fail(error4);
			}
			return new AqResult
			{
				Ok = true
			};
		}
		case AqRequestKind.Pack:
		{
			if (!_ports.IsHost(sender))
			{
				return Fail("only the host can move the tank");
			}
			if (!Tank.TryPack(out var error2))
			{
				return Fail(error2);
			}
			return new AqResult
			{
				Ok = true
			};
		}
		case AqRequestKind.TakeOut:
		{
			if (!Tank.IsPlaced)
			{
				return Fail("the aquarium is packed up");
			}
			if (Tank.AttackActive)
			{
				return Fail("not while the tank is under attack");
			}
			AqFish aqFish = Tank.State.Fish.Find((AqFish f) => f.Uid == req.Uid);
			if (aqFish == null)
			{
				return Fail("that fish isn't in the tank");
			}
			if (!_ports.GiveFish(sender, aqFish))
			{
				return Fail("couldn't hand the fish over (stand next to the aquarium)");
			}
			Tank.Remove(req.Uid);
			return new AqResult
			{
				Ok = true,
				Amount = req.Uid
			};
		}
		case AqRequestKind.SnapshotPlease:
			return new AqResult
			{
				Ok = true
			};
		default:
			return Fail("unknown request");
		}
	}

	private int FeedableUnits(int offered)
	{
		int num = 0;
		foreach (AqFish item in Tank.State.Fish)
		{
			if (item.Hunger < Tank.Config.FeedBelow)
			{
				num++;
			}
		}
		int val = Tank.HopperCapacity - Tank.State.HopperFood;
		return Math.Min(offered, num + Math.Max(0, val));
	}

	private static bool IsFinitePositive(float v)
	{
		if (!float.IsNaN(v) && !float.IsInfinity(v))
		{
			return v > 0f;
		}
		return false;
	}

	private static AqResult Fail(string error)
	{
		return new AqResult
		{
			Ok = false,
			Error = error
		};
	}

	private void Remember(string key, AqResult r)
	{
		if (!_seen.ContainsKey(key))
		{
			_seen[key] = r;
			_seenOrder.Enqueue(key);
			while (_seenOrder.Count > 1024)
			{
				_seen.Remove(_seenOrder.Dequeue());
			}
		}
	}
}
[JsonConverter(typeof(StringEnumConverter))]
public enum UpgradeType
{
	Capacity,
	AutoFeeder,
	Decor
}
public sealed class AquariumConfig
{
	public int BaseCapacity = 6;

	public int CapacityPerLevel = 3;

	public int MaxCapacityLevel = 5;

	public float MaxSizeScale = 1.75f;

	public int MaxAutoFeederLevel = 3;

	public int MaxDecorLevel = 5;

	public int FoodUnitsPerItem = 8;

	public int HopperPerLevel = 10;

	public float AutoFeedBelow = 0.3f;

	public float HungerPerDay = 0.5f;

	public float FeedBelow = 0.5f;

	public float StarveDeathDays = 1f;

	public float IncomeRate = 0.22f;

	public float IncomePerExtraPlayer = 0.35f;

	public float DecorIncomeBonusPerLevel = 0.05f;

	public float ZenIncomeRate = 0.1f;

	public float ZenHungerRate = 0.1f;

	public float ZenToggleCooldownDays = 0.25f;

	public float AttackIntervalDays = 2f;

	public float AttackGraceDays = 0.1f;

	public float AttackTimeoutDays = 0.5f;

	public float AttackDrainPerDay = 1f;

	public int ThreatHp = 400;

	public float HealthRegenPerDay = 0.5f;

	public bool TankCanBeHitByBosses;

	public float TankDayRealMinutes = 20f;

	public float MaxTickGapDays = 0.05f;

	public int[] CapacityPrices = new int[5] { 200, 400, 800, 1600, 3200 };

	public int[] AutoFeederPrices = new int[3] { 300, 900, 2000 };

	public int[] DecorPrices = new int[5] { 100, 250, 500, 1000, 2000 };

	public double StepDays = 1.0 / 96.0;
}
public sealed class AqFish
{
	public long Uid;

	public string SpeciesId;

	public float WeightKg;

	public Quality Quality;

	public bool Drip;

	public bool Shiny;

	public ulong DepositorId;

	public string DepositorName;

	public int Value;

	public float Cookness;

	public float BetMultiplier = 1f;

	public float KillScore = 1f;

	public float Hunger = 1f;

	public float Health = 1f;

	public float StarvedDays;
}
public sealed class AqAttack
{
	public float ThreatHp;

	public float ThreatMaxHp;

	public float ElapsedDays;
}
public sealed class AquariumState
{
	public const int CurrentSchema = 1;

	public int Schema = 1;

	public long Seq;

	public long NextUid = 1L;

	public List<AqFish> Fish = new List<AqFish>();

	public double TrayCoins;

	public int CapacityLevel;

	public int AutoFeederLevel;

	public int DecorLevel;

	public int HopperFood;

	public bool Zen;

	public float ZenCooldownDays;

	public float NextAttackInDays = 2f;

	public AqAttack Attack;

	public double LastTickDay;

	public TankPlacement Placement;
}
public sealed class TankPlacement
{
	public bool Placed;

	public int Island = -1;

	public float X;

	public float Y;

	public float Z;

	public float RotY;
}
public static class AquariumSync
{
	public static AquariumState Clone(AquariumState s)
	{
		return Hashing.Clone<AquariumState>(s);
	}

	public static string HashOf(AquariumState s)
	{
		return Hashing.Short((object)Clone(s));
	}

	public static AqSnapshot Snapshot(AquariumState s)
	{
		AquariumState aquariumState = Clone(s);
		return new AqSnapshot
		{
			State = aquariumState,
			Hash = Hashing.Short((object)aquariumState)
		};
	}

	public static AqHash HashMessage(AquariumState s)
	{
		return new AqHash
		{
			Seq = s.Seq,
			Hash = HashOf(s)
		};
	}

	public static AqDelta Diff(AquariumState prev, AquariumState next)
	{
		Dictionary<long, string> dictionary = prev.Fish.ToDictionary((AqFish f) => f.Uid, (AqFish f) => Hashing.CanonicalJson((object)f));
		AqDelta aqDelta = new AqDelta
		{
			BaseSeq = prev.Seq,
			Seq = next.Seq,
			Hash = HashOf(next)
		};
		foreach (AqFish item in next.Fish)
		{
			if (!dictionary.TryGetValue(item.Uid, out var value) || value != Hashing.CanonicalJson((object)item))
			{
				aqDelta.Upserts.Add(Clone(item));
			}
		}
		HashSet<long> nowUids = new HashSet<long>(next.Fish.Select((AqFish f) => f.Uid));
		aqDelta.Removed.AddRange(from f in prev.Fish
			where !nowUids.Contains(f.Uid)
			select f.Uid);
		aqDelta.Scalars = Clone(next);
		aqDelta.Scalars.Fish = new List<AqFish>();
		return aqDelta;
	}

	public static AquariumState Apply(AquariumState mirror, AqDelta d)
	{
		if (mirror == null || d == null || d.Scalars == null || mirror.Seq != d.BaseSeq)
		{
			return null;
		}
		Dictionary<long, AqFish> byUid = mirror.Fish.ToDictionary((AqFish f) => f.Uid, Clone);
		foreach (long item in d.Removed)
		{
			byUid.Remove(item);
		}
		foreach (AqFish upsert in d.Upserts)
		{
			byUid[upsert.Uid] = Clone(upsert);
		}
		AquariumState aquariumState = Clone(d.Scalars);
		List<long> order = mirror.Fish.Select((AqFish f) => f.Uid).Where(byUid.ContainsKey).ToList();
		order.AddRange(from u in byUid.Keys
			where !order.Contains(u)
			orderby u
			select u);
		aquariumState.Fish = order.Select((long u) => byUid[u]).ToList();
		if (!(HashOf(aquariumState) == d.Hash))
		{
			return null;
		}
		return aquariumState;
	}

	private static AqFish Clone(AqFish f)
	{
		return Hashing.Clone<AqFish>(f);
	}
}
public sealed class EconomyConfig
{
	public float[] QualityMultiplier = new float[6] { 1f, 1.2f, 1.5f, 2f, 2.5f, 3f };

	public float[][] QualityChancesByBaitTier = new float[4][]
	{
		new float[1] { 1f },
		new float[3] { 1f, 0.15f, 0.05f },
		new float[5] { 1f, 0.8f, 0.45f, 0.15f, 0.05f },
		new float[6] { 1f, 0.98f, 0.75f, 0.55f, 0.25f, 0.05f }
	};

	public Quality DripVisualMinQuality = (Quality)4;
}
public static class Economy
{
	public static int SellValue(FishDef def, LureStats lure, float weightKg, Quality q, EconomyConfig eco)
	{
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		float val = BaseValue(def, lure, weightKg) * eco.QualityMultiplier[q];
		int val2 = (int)Math.Ceiling(Math.Max(0f, val));
		return Math.Max(1, Math.Min(val2, lure.MaxRegularWorth));
	}

	public static float BaseValue(FishDef def, LureStats lure, float weightKg)
	{
		float val = ((def.Htf.WeightKgMean > 0f) ? (weightKg / def.Htf.WeightKgMean) : 1f);
		return lure.AvgWorth * def.Htf.RelativeWorth * (float)Math.Sqrt(Math.Max(0.01f, val));
	}

	public static int MaxHp(FishDef def, LureStats lure)
	{
		return Math.Max(1, (int)Math.Round(lure.AvgHp * def.Htf.RelativeHp));
	}

	public static Quality RollQuality(int baitTier, EconomyConfig eco, Rng rng)
	{
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		float[][] qualityChancesByBaitTier = eco.QualityChancesByBaitTier;
		if (qualityChancesByBaitTier.Length == 0)
		{
			return (Quality)0;
		}
		int num = Math.Max(0, Math.Min(qualityChancesByBaitTier.Length - 1, baitTier));
		return QualityInfo.Roll(qualityChancesByBaitTier[num], (Func<double>)rng.Next01, 1f);
	}

	public static float RollWeightKg(FishDef def, Rng rng)
	{
		HtfTargets htf = def.Htf;
		double val = (double)htf.WeightKgMean * (1.0 + rng.NextGaussian() * (double)htf.WeightSpread);
		return (float)Math.Max(htf.WeightKgMin, Math.Min(htf.WeightKgMax, val));
	}

	public static bool ShowsDripVisual(Quality q, EconomyConfig eco)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		return q >= eco.DripVisualMinQuality;
	}

	public static bool ForcesHighTierSkin(FishDef def)
	{
		return def.Htf.UltraRare;
	}
}
public sealed class JournalEntry
{
	public bool Caught;

	public int Count;

	public string FirstCatchUtc;

	public float BestWeightKg;

	public Quality BestQuality;

	public ulong FirstCatcherId;

	public string FirstCatcherName;

	public string FirstIsland;

	public string FirstBait;

	public int ShinyCount;
}
public sealed class Journal
{
	public const int CurrentSchema = 1;

	public const string ScopePlayer = "player";

	public const string ScopeWorld = "world";

	public int Schema = 1;

	public string Scope = "player";

	public string OwnerId = "";

	public SortedDictionary<string, JournalEntry> Entries = new SortedDictionary<string, JournalEntry>(StringComparer.Ordinal);

	public int CaughtCount
	{
		get
		{
			int num = 0;
			foreach (KeyValuePair<string, JournalEntry> entry in Entries)
			{
				if (entry.Value.Caught)
				{
					num++;
				}
			}
			return num;
		}
	}

	public static Journal ForPlayer(ulong steamId)
	{
		return new Journal
		{
			Scope = "player",
			OwnerId = steamId.ToString()
		};
	}

	public static Journal ForWorld(string worldId)
	{
		return new Journal
		{
			Scope = "world",
			OwnerId = worldId
		};
	}

	public bool Record(CatchEvent e, DateTime utc)
	{
		//IL_00f7: 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_0106: Unknown result type (might be due to invalid IL or missing references)
		//IL_010b: Unknown result type (might be due to invalid IL or missing references)
		if (string.IsNullOrEmpty(e.SpeciesId))
		{
			throw new ArgumentException("speciesId required");
		}
		if (Scope == "player" && e.HookerId.ToString() != OwnerId)
		{
			return false;
		}
		if (!Entries.TryGetValue(e.SpeciesId, out var value))
		{
			value = (Entries[e.SpeciesId] = new JournalEntry());
		}
		bool num = !value.Caught;
		value.Count++;
		if (num)
		{
			value.Caught = true;
			value.FirstCatchUtc = utc.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ");
			value.FirstCatcherId = e.HookerId;
			value.FirstCatcherName = e.HookerName;
			value.FirstIsland = e.Island;
			value.FirstBait = e.Bait;
		}
		if (e.WeightKg > value.BestWeightKg)
		{
			value.BestWeightKg = e.WeightKg;
		}
		if (e.Quality > value.BestQuality)
		{
			value.BestQuality = e.Quality;
		}
		if (e.Shiny)
		{
			value.ShinyCount++;
		}
		return num;
	}

	public bool IsValid(out string why)
	{
		why = null;
		if (Schema < 1 || Schema > 1)
		{
			why = $"schema {Schema}";
			return false;
		}
		if (Scope != "player" && Scope != "world")
		{
			why = "scope " + Scope;
			return false;
		}
		if (Entries == null)
		{
			why = "no entries";
			return false;
		}
		foreach (KeyValuePair<string, JournalEntry> entry in Entries)
		{
			if (entry.Value == null || entry.Value.Count < 0 || (entry.Value.Caught && entry.Value.Count == 0))
			{
				why = "bad entry " + entry.Key;
				return false;
			}
		}
		return true;
	}
}
public sealed class PlayerKeyItems
{
	public ulong SteamId;

	public bool WfLure;

	public bool WfLureOn;

	public bool ShinyCharm;

	public bool ShinyCharmOff;
}
public sealed class KeyItems
{
	public readonly List<PlayerKeyItems> Players;

	public KeyItems(List<PlayerKeyItems> players)
	{
		Players = players ?? new List<PlayerKeyItems>();
		Players.RemoveAll((PlayerKeyItems p) => p == null || p.SteamId == 0);
	}

	public PlayerKeyItems Find(ulong id)
	{
		return Players.FirstOrDefault((PlayerKeyItems p) => p.SteamId == id);
	}

	private PlayerKeyItems Get(ulong id)
	{
		PlayerKeyItems playerKeyItems = Find(id);
		if (playerKeyItems == null)
		{
			List<PlayerKeyItems> players = Players;
			PlayerKeyItems obj = new PlayerKeyItems
			{
				SteamId = id
			};
			playerKeyItems = obj;
			players.Add(obj);
		}
		return playerKeyItems;
	}

	public bool LureOn(ulong id)
	{
		return Find(id)?.WfLureOn ?? false;
	}

	public bool HasCharm(ulong id)
	{
		return Find(id)?.ShinyCharm ?? false;
	}

	public bool CharmOn(ulong id)
	{
		PlayerKeyItems playerKeyItems = Find(id);
		if (playerKeyItems != null && playerKeyItems.ShinyCharm)
		{
			return !playerKeyItems.ShinyCharmOff;
		}
		return false;
	}

	public AqResult Handle(ulong sender, AqRequest req, int lurePrice, int charmPrice, Func<int, bool> tryCharge)
	{
		if (req == null || sender == 0L)
		{
			return Fail("no request");
		}
		switch (req.Kind)
		{
		case AqRequestKind.BuyWfLure:
		{
			PlayerKeyItems playerKeyItems4 = Find(sender);
			if (playerKeyItems4 != null && playerKeyItems4.WfLure)
			{
				return Fail("you already have the Web Fishing Lure");
			}
			if (!tryCharge(lurePrice))
			{
				return Fail($"it costs ${lurePrice}");
			}
			PlayerKeyItems playerKeyItems5 = Get(sender);
			playerKeyItems5.WfLure = true;
			playerKeyItems5.WfLureOn = true;
			return new AqResult
			{
				RequestId = req.RequestId,
				Ok = true,
				Amount = lurePrice
			};
		}
		case AqRequestKind.BuyShinyCharm:
		{
			PlayerKeyItems playerKeyItems2 = Find(sender);
			if (playerKeyItems2 != null && playerKeyItems2.ShinyCharm)
			{
				return Fail("you already have the Shiny Charm");
			}
			if (!tryCharge(charmPrice))
			{
				return Fail($"it costs ${charmPrice}");
			}
			PlayerKeyItems playerKeyItems3 = Get(sender);
			playerKeyItems3.ShinyCharm = true;
			playerKeyItems3.ShinyCharmOff = false;
			return new AqResult
			{
				RequestId = req.RequestId,
				Ok = true,
				Amount = charmPrice
			};
		}
		case AqRequestKind.SetShinyCharm:
		{
			PlayerKeyItems playerKeyItems6 = Find(sender);
			if (playerKeyItems6 == null || !playerKeyItems6.ShinyCharm)
			{
				return Fail("you don't have the Shiny Charm");
			}
			playerKeyItems6.ShinyCharmOff = !req.On;
			return new AqResult
			{
				RequestId = req.RequestId,
				Ok = true
			};
		}
		case AqRequestKind.SetWfLure:
		{
			PlayerKeyItems playerKeyItems = Find(sender);
			if (playerKeyItems == null || !playerKeyItems.WfLure)
			{
				return Fail("you don't have the Web Fishing Lure");
			}
			playerKeyItems.WfLureOn = req.On;
			return new AqResult
			{
				RequestId = req.RequestId,
				Ok = true
			};
		}
		default:
			return Fail("not a shop request");
		}
		AqResult Fail(string why)
		{
			return new AqResult
			{
				RequestId = req.RequestId,
				Ok = false,
				Error = why
			};
		}
	}

	public void Grant(ulong id, bool? lure, bool? charm)
	{
		PlayerKeyItems playerKeyItems = Get(id);
		if (lure.HasValue)
		{
			playerKeyItems.WfLure = lure.Value;
			playerKeyItems.WfLureOn = lure.Value;
		}
		if (charm.HasValue)
		{
			playerKeyItems.ShinyCharm = charm.Value;
			playerKeyItems.ShinyCharmOff = false;
		}
	}

	public static bool IsKeyItemRequest(AqRequestKind k)
	{
		if (k != AqRequestKind.BuyWfLure && k != AqRequestKind.BuyShinyCharm && k != AqRequestKind.SetWfLure)
		{
			return k == AqRequestKind.SetShinyCharm;
		}
		return true;
	}

	public KeyItemsState State()
	{
		return new KeyItemsState
		{
			Players = Players.Select((PlayerKeyItems p) => new PlayerKeyItems
			{
				SteamId = p.SteamId,
				WfLure = p.WfLure,
				WfLureOn = p.WfLureOn,
				ShinyCharm = p.ShinyCharm,
				ShinyCharmOff = p.ShinyCharmOff
			}).ToList()
		};
	}
}
public sealed class KeyItemsState : IMessage
{
	public List<PlayerKeyItems> Players = new List<PlayerKeyItems>();
}
public static class MessageCodec
{
	public const int Protocol = 1;

	public const int MaxBytes = 524288;

	private static readonly Dictionary<string, Type> Types;

	private static readonly JsonSerializer Serializer;

	static MessageCodec()
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Expected O, but got Unknown
		Types = new Dictionary<string, Type>();
		Serializer = JsonSerializer.Create(new JsonSerializerSettings
		{
			NullValueHandling = (NullValueHandling)1,
			MissingMemberHandling = (MissingMemberHandling)0
		});
		Type[] array = new Type[13]
		{
			typeof(Handshake),
			typeof(SpawnTag),
			typeof(CatchEvent),
			typeof(JournalSnapshot),
			typeof(AqSnapshot),
			typeof(AqDelta),
			typeof(AqHash),
			typeof(AqRequest),
			typeof(AqResult),
			typeof(AttackState),
			typeof(RainMessage),
			typeof(ShinyMark),
			typeof(KeyItemsState)
		};
		foreach (Type type in array)
		{
			Types[type.Name] = type;
		}
	}

	public static byte[] Encode(IMessage msg)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Expected O, but got Unknown
		JObject val = new JObject
		{
			["v"] = JToken.op_Implicit(1),
			["t"] = JToken.op_Implicit(msg.GetType().Name),
			["p"] = (JToken)(object)JObject.FromObject((object)msg, Serializer)
		};
		byte[] bytes = Encoding.UTF8.GetBytes(((JToken)val).ToString((Formatting)0, Array.Empty<JsonConverter>()));
		if (bytes.Length > 524288)
		{
			throw new InvalidOperationException($"{msg.GetType().Name} is {bytes.Length} bytes; limit {524288}");
		}
		return bytes;
	}

	public static IMessage Decode(byte[] bytes, out string error)
	{
		int protocol;
		return Decode(bytes, out error, out protocol);
	}

	public static IMessage Decode(byte[] bytes, out string error, out int protocol)
	{
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Expected O, but got Unknown
		error = null;
		protocol = -1;
		if (bytes == null || bytes.Length == 0)
		{
			error = "empty";
			return null;
		}
		if (bytes.Length > 524288)
		{
			error = "too large";
			return null;
		}
		try
		{
			JObject val2;
			using (StringReader stringReader = new StringReader(Encoding.UTF8.GetString(bytes)))
			{
				JsonTextReader val = new JsonTextReader((TextReader)stringReader)
				{
					DateParseHandling = (DateParseHandling)0,
					MaxDepth = 64
				};
				try
				{
					val2 = JObject.Load((JsonReader)(object)val);
				}
				finally
				{
					((IDisposable)val)?.Dispose();
				}
			}
			int num = (protocol = ((JToken)val2).Value<int?>((object)"v") ?? (-1));
			string text = ((JToken)val2).Value<string>((object)"t");
			if (num != 1 && text != "Handshake")
			{
				error = $"protocol {num}, expected {1}";
				return null;
			}
			if (text == null || !Types.TryGetValue(text, out var value))
			{
				error = "unknown type " + text;
				return null;
			}
			JToken obj = val2["p"];
			JObject val3 = (JObject)(object)((obj is JObject) ? obj : null);
			if (val3 == null)
			{
				error = "no payload";
				return null;
			}
			return (IMessage)((JToken)val3).ToObject(value, Serializer);
		}
		catch (Exception ex)
		{
			error = "malformed: " + ex.Message;
			return null;
		}
	}
}
public interface IMessage
{
}
public sealed class Handshake : IMessage
{
	public string ModVersion;

	public int Protocol;

	public string GameBuild;

	public string FishDefHash;

	public bool HasCache;

	public ulong SteamId;

	public string Name;
}
public sealed class SpawnTag : IMessage
{
	public int ObjectId;

	public WfItemTag Tag;

	public bool Threat;
}
public sealed class CatchEvent : IMessage
{
	public string SpeciesId;

	public float WeightKg;

	public Quality Quality;

	public ulong HookerId;

	public string HookerName;

	public ulong KillerId;

	public string Island;

	public string Bait;

	public bool Shiny;
}
public sealed class JournalSnapshot : IMessage
{
	public Journal Journal;
}
public sealed class AqSnapshot : IMessage
{
	public AquariumState State;

	public string Hash;

	public AquariumConfig Config;
}
public sealed class AqDelta : IMessage
{
	public long BaseSeq;

	public long Seq;

	public List<AqFish> Upserts = new List<AqFish>();

	public List<long> Removed = new List<long>();

	public AquariumState Scalars;

	public string Hash;

	public List<AqEvent> Events = new List<AqEvent>();
}
public sealed class AqHash : IMessage
{
	public long Seq;

	public string Hash;
}
[JsonConverter(typeof(StringEnumConverter))]
public enum AqRequestKind
{
	Deposit,
	Feed,
	Collect,
	Upgrade,
	ToggleZen,
	SnapshotPlease,
	Place,
	Pack,
	TakeOut,
	BuyWfLure,
	BuyShinyCharm,
	SetWfLure,
	SetShinyCharm
}
public sealed class AqRequest : IMessage
{
	public string RequestId;

	public AqRequestKind Kind;

	public int FoodUnits;

	public UpgradeType Upgrade;

	public int Island;

	public float X;

	public float Y;

	public float Z;

	public float RotY;

	public long Uid;

	public bool On;
}
public sealed class AqResult : IMessage
{
	public string RequestId;

	public bool Ok;

	public string Error;

	public long Amount;
}
public sealed class ShinyMark : IMessage
{
	public int ObjectId;

	public int ItemId;
}
public sealed class RainMessage : IMessage
{
	public bool Active;

	public float SecondsLeft;
}
[JsonConverter(typeof(StringEnumConverter))]
public enum AttackPhase
{
	None,
	Active,
	Defeated,
	TimedOut
}
public sealed class AttackState : IMessage
{
	public AttackPhase Phase;

	public float Hp;

	public float MaxHp;
}
public sealed class NetCompat
{
	public bool Linked;

	public readonly List<string> Warnings = new List<string>();

	public static NetCompat Check(Handshake mine, Handshake theirs, string theirName)
	{
		NetCompat netCompat = new NetCompat();
		string text = (string.IsNullOrEmpty(theirName) ? "the other player" : Clean(theirName, 32));
		if (mine == null || theirs == null)
		{
			netCompat.Warnings.Add("no handshake");
			return netCompat;
		}
		if (theirs.Protocol != mine.Protocol || !SameText(theirs.ModVersion, mine.ModVersion))
		{
			netCompat.Warnings.Add("WFxHTF versions differ (" + text + " has " + Show(theirs.ModVersion) + ", you have " + Show(mine.ModVersion) + "): the aquarium, WEBFISHING fish and rain aren't shared this session. Install the same version to play together.");
			return netCompat;
		}
		netCompat.Linked = true;
		if (!SameText(theirs.GameBuild, mine.GameBuild))
		{
			netCompat.Warnings.Add("How to Fish versions differ (" + text + " " + Show(theirs.GameBuild) + ", you " + Show(mine.GameBuild) + "); things may misbehave.");
		}
		if (!theirs.HasCache)
		{
			netCompat.Warnings.Add(text + " hasn't extracted the WEBFISHING fish yet, so they see How to Fish fish in their place.");
		}
		else if (mine.HasCache && !SameText(theirs.FishDefHash, mine.FishDefHash))
		{
			netCompat.Warnings.Add(text + "'s WEBFISHING fish data differs from yours; a few fish may look like How to Fish fish.");
		}
		return netCompat;
	}

	private static bool SameText(string a, string b)
	{
		return string.Equals(a ?? "", b ?? "", StringComparison.Ordinal);
	}

	private static string Show(string s)
	{
		if (!string.IsNullOrEmpty(s))
		{
			return Clean(s, 24);
		}
		return "unknown";
	}

	public static string Clean(string s, int max)
	{
		if (string.IsNullOrEmpty(s))
		{
			return "";
		}
		StringBuilder stringBuilder = new StringBuilder(Math.Min(s.Length, max));
		foreach (char c in s)
		{
			if (stringBuilder.Length >= max)
			{
				stringBuilder.Append("...");
				break;
			}
			if (c != '<' && c != '>' && !char.IsControl(c))
			{
				stringBuilder.Append(c);
			}
		}
		return stringBuilder.ToString();
	}
}
public sealed class AqBroadcaster
{
	public double IntervalSeconds = 1.0;

	private AquariumState _baseline;

	private double _lastSent = double.NegativeInfinity;

	private bool _poked;

	private readonly List<AqEvent> _events = new List<AqEvent>();

	public bool HasBaseline => _baseline != null;

	public void AddEvents(IEnumerable<AqEvent> events)
	{
		if (events != null && _baseline != null)
		{
			_events.AddRange(events);
		}
	}

	public void Poke()
	{
		_poked = true;
	}

	public AqSnapshot SnapshotFor(AquariumState current, AquariumConfig config)
	{
		if (_baseline == null)
		{
			_baseline = AquariumSync.Clone(current);
		}
		AqSnapshot aqSnapshot = AquariumSync.Snapshot(_baseline);
		aqSnapshot.Config = config;
		return aqSnapshot;
	}

	public AqDelta Tick(AquariumState current, double now)
	{
		if (_baseline == null || current == null)
		{
			return null;
		}
		if ((!_poked && _events.Count <= 0 && !(now - _lastSent >= IntervalSeconds)) || (_baseline.Seq == current.Seq && _events.Count == 0))
		{
			return null;
		}
		AqDelta aqDelta = AquariumSync.Diff(_baseline, current);
		aqDelta.Events = new List<AqEvent>(_events);
		_events.Clear();
		_baseline = AquariumSync.Clone(current);
		_lastSent = now;
		_poked = false;
		return aqDelta;
	}

	public void Reset()
	{
		_baseline = null;
		_events.Clear();
		_poked = false;
		_lastSent = double.NegativeInfinity;
	}
}
public sealed class AqMirror
{
	public double RetrySeconds = 5.0;

	private bool _awaiting = true;

	private double _askedAt = double.NegativeInfinity;

	private readonly List<AqEvent> _events = new List<AqEvent>();

	public const int MaxFish = 500;

	private static readonly List<AqEvent> NoEvents = new List<AqEvent>();

	public AquariumState State { get; private set; }

	public AquariumConfig Config { get; private set; }

	public int Applied { get; private set; }

	public int Resyncs { get; private set; }

	public string LastProblem { get; private set; }

	public bool InSync
	{
		get
		{
			if (State != null)
			{
				return !_awaiting;
			}
			return false;
		}
	}

	public void OnSnapshot(AqSnapshot s)
	{
		if (s?.State == null)
		{
			LastProblem = "empty snapshot";
			return;
		}
		if (AquariumSync.HashOf(s.State) != s.Hash)
		{
			LastProblem = "snapshot hash mismatch";
			_awaiting = true;
			return;
		}
		if (!Sane(s.State, s.Config ?? Config, out var why))
		{
			LastProblem = "bad snapshot: " + why;
			_awaiting = true;
			return;
		}
		State = s.State;
		if (s.Config != null)
		{
			Config = s.Config;
		}
		_awaiting = false;
		LastProblem = null;
	}

	public void OnDelta(AqDelta d)
	{
		if (State == null || _awaiting || d == null)
		{
			return;
		}
		if ((d.Upserts?.Count ?? 0) > 500 || (d.Removed?.Count ?? 0) > 2000)
		{
			_awaiting = true;
			_askedAt = double.NegativeInfinity;
			Resyncs++;
			LastProblem = "oversized delta";
			return;
		}
		AquariumState aquariumState = AquariumSync.Apply(State, d);
		string why;
		string text = ((aquariumState != null) ? ((!Sane(aquariumState, Config, out why)) ? ("bad delta: " + why) : null) : ((d.BaseSeq != State.Seq) ? $"delta base {d.BaseSeq} != mine {State.Seq}" : "delta hash mismatch"));
		if (text != null)
		{
			_awaiting = true;
			_askedAt = double.NegativeInfinity;
			Resyncs++;
			LastProblem = text;
			return;
		}
		State = aquariumState;
		Applied++;
		if (d.Events != null)
		{
			_events.AddRange(d.Events);
		}
	}

	private static bool Sane(AquariumState s, AquariumConfig cfg, out string why)
	{
		if (s.Fish != null && s.Fish.Count > 500)
		{
			why = $"{s.Fish.Count} fish";
			return false;
		}
		return new Aquarium(cfg, s).IsValid(out why);
	}

	public bool ShouldAskForSnapshot(double now)
	{
		if (!_awaiting || now - _askedAt < RetrySeconds)
		{
			return false;
		}
		_askedAt = now;
		return true;
	}

	public List<AqEvent> TakeEvents()
	{
		if (_events.Count == 0)
		{
			return NoEvents;
		}
		List<AqEvent> result = new List<AqEvent>(_events);
		_events.Clear();
		return result;
	}
}
public sealed class PendingRequests
{
	public double TimeoutSeconds = 10.0;

	private readonly Dictionary<string, (Action<AqResult> Done, double At)> _open = new Dictionary<string, (Action<AqResult>, double)>();

	public int Count => _open.Count;

	public void Add(AqRequest req, Action<AqResult> done, double now)
	{
		if (req == null || string.IsNullOrEmpty(req.RequestId))
		{
			throw new ArgumentException("request id required");
		}
		_open[req.RequestId] = (done, now);
	}

	public bool Resolve(AqResult r)
	{
		if (r == null || r.RequestId == null || !_open.TryGetValue(r.RequestId, out (Action<AqResult>, double) value))
		{
			return false;
		}
		_open.Remove(r.RequestId);
		value.Item1?.Invoke(r);
		return true;
	}

	public void Expire(double now)
	{
		foreach (string item in (from kv in _open
			where now - kv.Value.At >= TimeoutSeconds
			select kv.Key).ToList())
		{
			Fail(item, "no answer from the host");
		}
	}

	public void FailAll(string error)
	{
		foreach (string item in _open.Keys.ToList())
		{
			Fail(item, error);
		}
	}

	private void Fail(string id, string error)
	{
		(Action<AqResult> Done, double At) tuple = _open[id];
		_open.Remove(id);
		tuple.Done?.Invoke(new AqResult
		{
			RequestId = id,
			Ok = false,
			Error = error
		});
	}
}
public sealed class RateLimit
{
	public double Burst = 40.0;

	public double PerSecond = 20.0;

	private double _tokens = double.NaN;

	private double _at;

	public bool Allow(double now)
	{
		if (double.IsNaN(_tokens))
		{
			_tokens = Burst;
			_at = now;
		}
		_tokens = Math.Min(Burst, _tokens + Math.Max(0.0, now - _at) * PerSecond);
		_at = now;
		if (_tokens < 1.0)
		{
			return false;
		}
		_tokens -= 1.0;
		return true;
	}
}
public sealed class RainConfig
{
	public bool Enabled = true;

	public float MeanMinutesBetween = 30f;

	public float MinDurationMinutes = 4f;

	public float MaxDurationMinutes = 8f;
}
public sealed class RainState
{
	public bool Active;

	public double NextChangeAt;

	public ulong RngState = 790741uL;
}
public sealed class RainScheduler
{
	public readonly RainConfig Config;

	public readonly RainState State;

	public bool Raining
	{
		get
		{
			if (Config.Enabled)
			{
				return State.Active;
			}
			return false;
		}
	}

	public RainScheduler(RainConfig config, RainState state)
	{
		Config = config ?? new RainConfig();
		State = state ?? new RainState();
	}

	public bool Update(double playtimeSeconds)
	{
		if (!Config.Enabled)
		{
			bool active = State.Active;
			State.Active = false;
			return active;
		}
		if (State.NextChangeAt <= 0.0)
		{
			State.NextChangeAt = playtimeSeconds + DryGap();
			return false;
		}
		bool flag = false;
		for (int i = 0; i < 2; i++)
		{
			if (!(playtimeSeconds >= State.NextChangeAt))
			{
				break;
			}
			State.Active = !State.Active;
			State.NextChangeAt = Math.Max(State.NextChangeAt, playtimeSeconds - 1.0) + (State.Active ? Shower() : DryGap());
			flag = !flag;
		}
		return flag;
	}

	public RainMessage Message(double playtimeSeconds)
	{
		return new RainMessage
		{
			Active = Raining,
			SecondsLeft = (float)Math.Max(0.0, State.NextChangeAt - playtimeSeconds)
		};
	}

	private double DryGap()
	{
		return 60.0 * (double)Config.MeanMinutesBetween * (0.5 + Next01());
	}

	private double Shower()
	{
		return 60.0 * ((double)Config.MinDurationMinutes + (double)(Config.MaxDurationMinutes - Config.MinDurationMinutes) * Next01());
	}

	private double Next01()
	{
		Rng rng = new Rng(State.RngState);
		double result = rng.Next01();
		State.RngState = rng.State;
		return result;
	}
}
public sealed class Rng
{
	public ulong State;

	public Rng(ulong seed)
	{
		State = ((seed == 0L) ? 11400714819323198485uL : seed);
	}

	public ulong NextU64()
	{
		ulong state = State;
		state ^= state >> 12;
		state ^= state << 25;
		return (State = state ^ (state >> 27)) * 2685821657736338717L;
	}

	public double Next01()
	{
		return (double)(NextU64() >> 11) * 1.1102230246251565E-16;
	}

	public double NextGaussian()
	{
		double d = 1.0 - Next01();
		double num = Next01();
		return Math.Sqrt(-2.0 * Math.Log(d)) * Math.Cos(Math.PI * 2.0 * num);
	}
}
public sealed class SessionTally
{
	public const double MaxAquariumShare = 0.3;

	public const double MaxWfIncomeShare = 0.6;

	public const double MaxWfValueRatio = 1.5;

	public double StartPlaytime = double.NaN;

	public double LastPlaytime;

	public int NativeBites;

	public int WfBites;

	public int ShinyBites;

	public int NativeLanded;

	public int WfLanded;

	public int TntWf;

	public readonly int[] WfQualities = new int[6];

	public int NativeSold;

	public int WfSold;

	public int OtherSold;

	public long NativeIncome;

	public long WfIncome;

	public long OtherIncome;

	public int Deposits;

	public int FoodItems;

	public int Upgrades;

	public int TankDeaths;

	public int AttacksWon;

	public int AttacksLost;

	public long DepositedValue;

	public long FoodValue;

	public long TankPaid;

	public long UpgradeSpent;

	public double TrayStart = double.NaN;

	public double TrayNow;

	public double Minutes
	{
		get
		{
			if (!double.IsNaN(StartPlaytime))
			{
				return Math.Max(0.0, LastPlaytime - StartPlaytime) / 60.0;
			}
			return 0.0;
		}
	}

	public double Hours => Math.Max(1.0 / 60.0, Minutes / 60.0);

	public double TankGross => (double)TankPaid + (double.IsNaN(TrayStart) ? 0.0 : (TrayNow - TrayStart));

	public double TankNet => TankGross - (double)FoodValue;

	public double WfShareOfBites
	{
		get
		{
			if (NativeBites + WfBites <= 0)
			{
				return 0.0;
			}
			return (double)WfBites / (double)(NativeBites + WfBites);
		}
	}

	public double NativeAvgSale
	{
		get
		{
			if (NativeSold <= 0)
			{
				return 0.0;
			}
			return (double)NativeIncome / (double)NativeSold;
		}
	}

	public double WfAvgSale
	{
		get
		{
			if (WfSold <= 0)
			{
				return 0.0;
			}
			return (double)WfIncome / (double)WfSold;
		}
	}

	public double WfValueRatio
	{
		get
		{
			if (!(NativeAvgSale > 0.0))
			{
				return 0.0;
			}
			return WfAvgSale / NativeAvgSale;
		}
	}

	public double WfIncomeShare
	{
		get
		{
			if (NativeIncome <= 0)
			{
				return 0.0;
			}
			return (double)WfIncome / (double)NativeIncome;
		}
	}

	public double AquariumShare
	{
		get
		{
			if (NativeIncome <= 0)
			{
				return 0.0;
			}
			return TankNet / (double)NativeIncome;
		}
	}

	public void Seen(double playtime)
	{
		if (double.IsNaN(StartPlaytime))
		{
			StartPlaytime = playtime;
		}
		LastPlaytime = Math.Max(LastPlaytime, playtime);
	}

	public void Tray(double coins)
	{
		if (double.IsNaN(TrayStart))
		{
			TrayStart = coins;
		}
		TrayNow = coins;
	}

	private static string Flag(double v, double limit)
	{
		if (!(v > limit))
		{
			return "ok";
		}
		return "HIGH";
	}

	public string Report()
	{
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.AppendLine($"session: {Minutes:0} min of play");
		stringBuilder.AppendLine(string.Format("bites: {0} (How to Fish {1}, WEBFISHING {2} = {3:P0}{4})", NativeBites + WfBites, NativeBites, WfBites, WfShareOfBites, (ShinyBites > 0) ? $", {ShinyBites} shiny" : ""));
		stringBuilder.AppendLine("  WEBFISHING qualities: " + string.Join(" / ", WfQualities) + " (Normal..Alpha)");
		stringBuilder.AppendLine(string.Format("landed (killed after hooking): How to Fish {0}, WEBFISHING {1}{2}", NativeLanded, WfLanded, (TntWf > 0) ? $"; WEBFISHING fish from TNT {TntWf}" : ""));
		stringBuilder.AppendLine($"sold: How to Fish {NativeSold} fish ${NativeIncome} (avg ${NativeAvgSale:0}), WEBFISHING {WfSold} fish ${WfIncome} (avg ${WfAvgSale:0})" + ((OtherSold > 0) ? $", other items {OtherSold} ${OtherIncome}" : ""));
		stringBuilder.AppendLine($"  WEBFISHING avg sale / How to Fish avg sale: {WfValueRatio:0.00} (sim limit {1.5}) {Flag(WfValueRatio, 1.5)}");
		stringBuilder.AppendLine($"  WEBFISHING income / How to Fish income: {WfIncomeShare:0.00} (sim limit {0.6}) {Flag(WfIncomeShare, 0.6)}");
		stringBuilder.AppendLine($"tank: {Deposits} fish put in (worth ${DepositedValue}), {FoodItems} fed (worth ${FoodValue}), paid out ${TankPaid}, tray {(double.IsNaN(TrayStart) ? 0.0 : (TrayNow - TrayStart)):+0;-0;0}, upgrades {Upgrades} for ${UpgradeSpent}");
		stringBuilder.AppendLine($"  tank net ${TankNet:0} = {AquariumShare:0.00} of How to Fish income (sim limit {0.3}) {Flag(AquariumShare, 0.3)}; deaths {TankDeaths}, attacks won {AttacksWon} lost {AttacksLost}");
		stringBuilder.Append($"per hour: How to Fish ${(double)NativeIncome / Hours:0}, WEBFISHING ${(double)WfIncome / Hours:0}, tank net ${TankNet / Hours:0}");
		return stringBuilder.ToString();
	}
}
public sealed class JsonLinesLog : IDisposable
{
	private readonly StreamWriter _w;

	public string Path { get; }

	public JsonLinesLog(string path)
	{
		Path = path;
		Directory.CreateDirectory(System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(path)));
		_w = new StreamWriter(path, append: true, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false))
		{
			AutoFlush = true
		};
	}

	public void Write(double t, string kind, params (string Key, object Value)[] fields)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Expected O, but got Unknown
		JObject val = new JObject
		{
			["t"] = JToken.op_Implicit(Math.Round(t, 1)),
			["kind"] = JToken.op_Implicit(kind)
		};
		for (int i = 0; i < fields.Length; i++)
		{
			var (text, obj) = fields[i];
			val[text] = (JToken)((obj == null) ? ((object)JValue.CreateNull()) : ((object)JToken.FromObject(obj)));
		}
		_w.WriteLine(((JToken)val).ToString((Formatting)0, Array.Empty<JsonConverter>()));
	}

	public void Dispose()
	{
		_w.Dispose();
	}
}
public enum LoadStatus
{
	Loaded,
	Recovered,
	Fresh,
	Unsupported,
	Unreadable
}
public sealed class LoadResult<T>
{
	public T Value;

	public LoadStatus Status;

	public string Source;

	public string Problem;

	public bool Recovered => Status == LoadStatus.Recovered;

	public bool CanSave
	{
		get
		{
			if (Status != LoadStatus.Unsupported)
			{
				return Status != LoadStatus.Unreadable;
			}
			return false;
		}
	}
}
public sealed class SidecarKind<T> where T : class
{
	public Func<T> Fresh;

	public Func<T, string> Validate;

	public Func<JObject, string> NewerVersion;
}
public static class Sidecars
{
	public static SidecarKind<Journal> PlayerJournal(ulong steamId)
	{
		string why;
		return new SidecarKind<Journal>
		{
			Fresh = () => Journal.ForPlayer(steamId),
			Validate = (Journal j) => (!j.IsValid(out why)) ? why : null,
			NewerVersion = (JObject o) => Newer(o, "journal", 1)
		};
	}

	public static SidecarKind<WorldSidecar> World(string worldId, string modVersion, AquariumConfig cfg)
	{
		return new SidecarKind<WorldSidecar>
		{
			Fresh = () => WorldSidecar.New(worldId, modVersion, cfg),
			Validate = (WorldSidecar w) => w.Validate(worldId),
			NewerVersion = (JObject o) => Newer(o, "world file", 1) ?? Newer((JObject)/*isinst with value type is only supported in some contexts*/, "world journal", 1) ?? Newer((JObject)/*isinst with value type is only supported in some contexts*/, "aquarium", 1)
		};
	}

	private static string Newer(JObject o, string what, int supported)
	{
		int valueOrDefault = ((int?)((o != null) ? o["Schema"] : null)).GetValueOrDefault();
		if (valueOrDefault <= supported)
		{
			return null;
		}
		return $"{what} has schema {valueOrDefault}; this mod version supports {supported}";
	}
}
public static class SidecarStore
{
	public const int Backups = 5;

	public const int MaxCorruptKept = 3;

	public static readonly TimeSpan DefaultBackupInterval = TimeSpan.FromMinutes(5.0);

	private static readonly Dictionary<string, object> Locks = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

	public static string BackupPath(string path, int n)
	{
		return $"{path}.bak{n}";
	}

	private static object LockFor(string path)
	{
		string fullPath = Path.GetFullPath(path);
		lock (Locks)
		{
			if (!Locks.TryGetValue(fullPath, out var value))
			{
				value = (Locks[fullPath] = new object());
			}
			return value;
		}
	}

	public static void Save<T>(string path, T value, SidecarKind<T> kind, TimeSpan? minBackupInterval = null) where T : class
	{
		lock (LockFor(path))
		{
			if (File.Exists(path))
			{
				string text = NewerVersion(path, kind);
				if (text != null)
				{
					throw new InvalidOperationException("refusing to overwrite " + Path.GetFileName(path) + ": " + text);
				}
			}
			try
			{
				if (File.Exists(path) && RotationDue(path, minBackupInterval ?? DefaultBackupInterval) && BackupsMovable(path))
				{
					Rotate(path);
				}
			}
			catch (IOException)
			{
			}
			catch (UnauthorizedAccessException)
			{
			}
			JsonFile.WriteAtomic<T>(path, value);
		}
	}

	private static bool RotationDue(string path, TimeSpan interval)
	{
		string path2 = BackupPath(path, 1);
		if (File.Exists(path2))
		{
			return DateTime.UtcNow - File.GetLastWriteTimeUtc(path2) >= interval;
		}
		return true;
	}

	private static bool BackupsMovable(string path)
	{
		for (int i = 1; i <= 5; i++)
		{
			string path2 = BackupPath(path, i);
			if (!File.Exists(path2))
			{
				continue;
			}
			try
			{
				using (new FileStream(path2, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
				{
				}
			}
			catch (IOException)
			{
				return false;
			}
			catch (UnauthorizedAccessException)
			{
				return false;
			}
		}
		return true;
	}

	private static void Rotate(string path)
	{
		string path2 = BackupPath(path, 5);
		if (File.Exists(path2))
		{
			File.Delete(path2);
		}
		for (int num = 4; num >= 1; num--)
		{
			string text = BackupPath(path, num);
			if (File.Exists(text))
			{
				File.Move(text, BackupPath(path, num + 1));
			}
		}
		JsonFile.WriteDurable(BackupPath(path, 1), File.ReadAllBytes(path));
	}

	public static LoadResult<T> Load<T>(string path, SidecarKind<T> kind) where T : class
	{
		lock (LockFor(path))
		{
			string text = JsonFile.TempPath(path);
			bool flag = File.Exists(path) && File.Exists(text) && File.GetLastWriteTimeUtc(text) > File.GetLastWriteTimeUtc(path);
			List<(string, string)> list = new List<(string, string)>();
			if (flag)
			{
				list.Add((text, "tmp"));
			}
			list.Add((path, "main"));
			if (!flag)
			{
				list.Add((text, "tmp"));
			}
			for (int i = 1; i <= 5; i++)
			{
				list.Add((BackupPath(path, i), $"bak{i}"));
			}
			string text2 = (flag ? "the last save was interrupted; its complete temp file is newer than the main file" : null);
			foreach (var (text3, text4) in list)
			{
				if (!File.Exists(text3))
				{
					if (text4 == "main")
					{
						text2 = "main file missing";
					}
					continue;
				}
				string text5 = NewerVersion(text3, kind);
				if (text5 != null)
				{
					return new LoadResult<T>
					{
						Value = kind.Fresh(),
						Status = LoadStatus.Unsupported,
						Source = text4,
						Problem = Path.GetFileName(text3) + ": " + text5 + ". Update the mod. Saving is disabled."
					};
				}
				string problem;
				bool ioError;
				T val = TryRead(text3, kind.Validate, out problem, out ioError);
				int num = 0;
				while (val == null && ioError && text4 == "main" && num < 3)
				{
					Thread.Sleep(150);
					val = TryRead(text3, kind.Validate, out problem, out ioError);
					num++;
				}
				if (val == null && ioError && text4 == "main")
				{
					return new LoadResult<T>
					{
						Value = kind.Fresh(),
						Status = LoadStatus.Unreadable,
						Source = text4,
						Problem = Path.GetFileName(text3) + " couldn't be read (" + problem + "). Close whatever has it open and reload. Saving is disabled."
					};
				}
				if (val != null)
				{
					return new LoadResult<T>
					{
						Value = val,
						Status = ((!(text4 == "main")) ? LoadStatus.Recovered : LoadStatus.Loaded),
						Source = text4,
						Problem = ((text4 == "main") ? null : text2)
					};
				}
				if (text4 == "main")
				{
					text2 = "main: " + problem;
					Quarantine(path);
				}
				else
				{
					text2 = text2 ?? (text4 + ": " + problem);
				}
			}
			return new LoadResult<T>
			{
				Value = kind.Fresh(),
				Status = LoadStatus.Fresh,
				Source = "new",
				Problem = ((text2 == "main file missing") ? null : text2)
			};
		}
	}

	private static string NewerVersion<T>(string file, SidecarKind<T> kind) where T : class
	{
		if (kind.NewerVersion == null)
		{
			return null;
		}
		try
		{
			return kind.NewerVersion(JObject.Parse(File.ReadAllText(file)));
		}
		catch
		{
			return null;
		}
	}

	private static void Quarantine(string path)
	{
		try
		{
			File.Move(path, $"{path}.corrupt-{DateTime.UtcNow:yyyyMMddTHHmmssfffZ}");
			foreach (string item in Directory.GetFiles(Path.GetDirectoryName(Path.GetFullPath(path)), Path.GetFileName(path) + ".corrupt-*").OrderByDescending<string, string>((string f) => f, StringComparer.Ordinal).Skip(3))
			{
				File.Delete(item);
			}
		}
		catch (IOException)
		{
		}
		catch (UnauthorizedAccessException)
		{
		}
	}

	private static T TryRead<T>(string path, Func<T, string> validate, out string problem, out bool ioError) where T : class
	{
		problem = null;
		ioError = false;
		try
		{
			T val = JsonFile.Read<T>(path);
			if (val == null)
			{
				problem = "empty file";
				return null;
			}
			string text = validate?.Invoke(val);
			if (text != null)
			{
				problem = text;
				return null;
			}
			return val;
		}
		catch (Exception ex)
		{
			ioError = (ex is IOException && !(ex is FileNotFoundException)) || ex is UnauthorizedAccessException;
			problem = ex.GetType().Name + ": " + ex.Message;
			return null;
		}
	}
}
public sealed class WfItemTag
{
	public string SpeciesId;

	public Quality Quality;

	public float WeightKg;

	public int ItemId;

	public float WeightMultiplier;

	public int Worth;

	public int MaxHp;

	public int LureTier;

	public bool Drip;

	public bool Shiny;

	public ulong HookerId;

	public string HookerName;

	public bool Matches(int itemId, float weightMultiplier)
	{
		if (ItemId == itemId)
		{
			return Math.Abs(WeightMultiplier - weightMultiplier) <= 1E-05f * Math.Max(1f, Math.Abs(weightMultiplier));
		}
		return false;
	}

	public bool IsValid(out string why)
	{
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		why = null;
		if (string.IsNullOrEmpty(SpeciesId))
		{
			why = "no species";
		}
		else if (!Enum.IsDefined(typeof(Quality), Quality))
		{
			why = "bad quality";
		}
		else if (ItemId < 0 || ItemId > 255)
		{
			why = "bad item id";
		}
		else if (!(WeightMultiplier > 0f) || float.IsInfinity(WeightMultiplier) || !(WeightKg > 0f) || float.IsInfinity(WeightKg))
		{
			why = "bad weight";
		}
		else if (Worth < 0 || MaxHp < 0)
		{
			why = "bad stats";
		}
		return why == null;
	}
}
public sealed class NativeShinyKey
{
	public int ItemId;

	public float Weight;

	public bool Matches(int itemId, float weight)
	{
		if (ItemId == itemId)
		{
			return Math.Abs(Weight - weight) <= 1E-05f * Math.Max(1f, Math.Abs(weight));
		}
		return false;
	}
}
public static class WfCatch
{
	public static List<FishDef> Eligible(IEnumerable<FishDef> defs, int island, int mainIslandCount, bool rain, int lureTier)
	{
		return defs.Where((FishDef d) => FishMapper.CanSpawn(d, island, mainIslandCount, rain, lureTier)).OrderBy<FishDef, string>((FishDef d) => d.Id, StringComparer.Ordinal).ToList();
	}

	public static FishDef Roll(LureStats lure, IList<FishDef> eligible, MappingConfig cfg, double r1, double r2)
	{
		if (lure == null || eligible == null || eligible.Count == 0)
		{
			return null;
		}
		Dictionary<string, float> dictionary = FishMapper.InjectedWeights(lure, (IEnumerable<FishDef>)eligible, cfg);
		double num = dictionary.Values.Sum();
		if (num <= 0.0)
		{
			return null;
		}
		if (r1 >= num / (num + (double)lure.NativeWeightTotal))
		{
			return null;
		}
		double num2 = r2 * num;
		FishDef result = null;
		foreach (FishDef item in eligible)
		{
			if (dictionary.TryGetValue(item.Id, out var value) && !(value <= 0f))
			{
				result = item;
				num2 -= (double)value;
				if (num2 < 0.0)
				{
					return item;
				}
			}
		}
		return result;
	}

	public static WfItemTag Make(FishDef def, LureStats lure, EconomyConfig eco, Rng rng, int itemId, ulong hookerId, string hookerName, Quality? quality = null)
	{
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0079: Unknown result type (might be due to invalid IL or missing references)
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
		Quality val = (Quality)(((??)quality) ?? Economy.RollQuality(lure.Tier, eco, rng));
		float num = Economy.RollWeightKg(def, rng);
		float num2 = ((def.Htf.WeightKgMean > 0f) ? def.Htf.WeightKgMean : num);
		float num3 = Math.Max(0.01f, num / num2);
		int num4 = Economy.SellValue(def, lure, num, val, eco);
		return new WfItemTag
		{
			SpeciesId = def.Id,
			Quality = val,
			WeightKg = num,
			ItemId = itemId,
			WeightMultiplier = num3,
			Worth = Math.Max(1, (int)Math.Round((float)num4 / num3)),
			MaxHp = Economy.MaxHp(def, lure),
			LureTier = lure.Tier,
			Drip = (def.Htf.UltraRare || Economy.ShowsDripVisual(val, eco)),
			HookerId = hookerId,
			HookerName = hookerName
		};
	}

	public static void MakeKeyUnique(WfItemTag tag, IEnumerable<WfItemTag> existing)
	{
		List<WfItemTag> source = existing.ToList();
		for (int i = 0; i < 100; i++)
		{
			if (tag.WeightMultiplier != 1f && !source.Any((WfItemTag t) => t != tag && t.Matches(tag.ItemId, tag.WeightMultiplier)))
			{
				break;
			}
			tag.WeightMultiplier += 0.0001f;
		}
	}

	public static WfItemTag Find(IEnumerable<WfItemTag> tags, int itemId, float weightMultiplier)
	{
		return (from t in tags?.Where((WfItemTag t) => t.Matches(itemId, weightMultiplier))
			orderby Math.Abs(t.WeightMultiplier - weightMultiplier)
			select t).FirstOrDefault();
	}
}
public sealed class WorldSidecar
{
	public const int CurrentSchema = 1;

	public int Schema = 1;

	public string WorldId;

	public string ModVersion;

	public Journal Journal;

	public AquariumState Aquarium;

	public RainState Rain = new RainState();

	public List<WfItemTag> Items = new List<WfItemTag>();

	public List<NativeShinyKey> ShinyNatives = new List<NativeShinyKey>();

	public List<PlayerKeyItems> KeyItems = new List<PlayerKeyItems>();

	public static WorldSidecar New(string worldId, string modVersion, AquariumConfig cfg)
	{
		return new WorldSidecar
		{
			WorldId = worldId,
			ModVersion = modVersion,
			Journal = Journal.ForWorld(worldId),
			Aquarium = new Aquarium(cfg).State
		};
	}

	public static string FileName(string worldId)
	{
		return "world_" + Sanitize(worldId) + "-" + Hashing.Short((object)(worldId ?? "")).Substring(0, 8) + ".wfxhtf.json";
	}

	public static string JournalFileName(ulong steamId)
	{
		return $"journal_{steamId}.json";
	}

	public string Validate(string expectedWorldId)
	{
		if (Schema < 1 || Schema > 1)
		{
			return $"schema {Schema}";
		}
		if (WorldId != expectedWorldId)
		{
			return "world id " + WorldId + ", expected " + expectedWorldId;
		}
		if (Journal == null)
		{
			return "journal missing";
		}
		if (!Journal.IsValid(out var why))
		{
			return "journal: " + why;
		}
		if (Aquarium == null)
		{
			return "aquarium missing";
		}
		if (!new Aquarium(null, Aquarium).IsValid(out var why2))
		{
			return "aquarium: " + why2;
		}
		return null;
	}

	private static string Sanitize(string id)
	{
		char[] array = (id ?? "unknown").ToCharArray();
		for (int i = 0; i < array.Length; i++)
		{
			if (!char.IsLetterOrDigit(array[i]) && array[i] != '-' && array[i] != '_')
			{
				array[i] = '_';
			}
		}
		return new string(array);
	}
}

BepInEx/plugins/WFxHTF/WFxHTF.Data.dll

Decompiled a day ago
using System;
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.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("WFxHTF")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+166b9aae0d28865ba70c4894450aeb4db4b21aeb")]
[assembly: AssemblyProduct("WFxHTF.Data")]
[assembly: AssemblyTitle("WFxHTF.Data")]
[assembly: AssemblyVersion("0.1.0.0")]
namespace WFxHTF.Data;

public static class DisplayText
{
	public static string VisibleOr(string s, string fallback)
	{
		if (string.IsNullOrEmpty(s))
		{
			return fallback;
		}
		int num = 0;
		while (num < s.Length)
		{
			switch (CharUnicodeInfo.GetUnicodeCategory(s, num))
			{
			default:
				return s;
			case UnicodeCategory.NonSpacingMark:
			case UnicodeCategory.EnclosingMark:
			case UnicodeCategory.SpaceSeparator:
			case UnicodeCategory.LineSeparator:
			case UnicodeCategory.ParagraphSeparator:
			case UnicodeCategory.Control:
			case UnicodeCategory.Format:
			case UnicodeCategory.Surrogate:
			case UnicodeCategory.PrivateUse:
			case UnicodeCategory.OtherNotAssigned:
				num += ((!char.IsSurrogatePair(s, num)) ? 1 : 2);
				break;
			}
		}
		return fallback;
	}
}
public static class FishArgs
{
	public static string Split(string[] args, out Quality? quality, out double? number)
	{
		quality = null;
		number = null;
		if (args == null || args.Length == 0)
		{
			return "";
		}
		int num = args.Length;
		if (num > 1 && IsNumber(args[num - 1], out var v))
		{
			number = v;
			num--;
		}
		if (num > 1 && !IsNumber(args[num - 1], out var _) && Enum.TryParse<Quality>(args[num - 1], ignoreCase: true, out var result) && Enum.IsDefined(typeof(Quality), result))
		{
			quality = result;
			num--;
		}
		return string.Join(" ", args, 0, num);
	}

	private static bool IsNumber(string s, out double v)
	{
		return double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out v);
	}
}
[JsonConverter(typeof(StringEnumConverter))]
public enum SizeClass
{
	Small,
	Medium,
	Eel,
	Flat,
	Shark,
	Giant
}
[JsonConverter(typeof(StringEnumConverter))]
public enum ZoneRule
{
	Freshwater,
	Saltwater,
	AnyDuringRain,
	Anywhere
}
[JsonConverter(typeof(StringEnumConverter))]
public enum Aggression
{
	Passive,
	Flee,
	Bite
}
[JsonConverter(typeof(StringEnumConverter))]
public enum RainCondition
{
	ModRain,
	Storm,
	Night,
	Either
}
public sealed class HtfTargets
{
	public SizeClass SizeClass;

	public ZoneRule Zone;

	public float SpawnWeight;

	public int MinBaitTier;

	public float RelativeWorth;

	public float RelativeHp;

	public Aggression Aggression;

	public float WeightKgMean;

	public float WeightSpread;

	public float WeightKgMin;

	public float WeightKgMax;

	public float WfWorth;

	public bool UltraRare;
}
public sealed class FishVisual
{
	public string[] Frames;

	public float Fps;

	public int Width;

	public int Height;

	public string Filter = "point";

	public bool Billboard = true;
}
public sealed class FishDef
{
	public string Id;

	public string Name;

	public WfFish Wf;

	public HtfTargets Htf;

	public FishVisual Visual;
}
public static class FishMapper
{
	private static readonly HashSet<string> Eels = new HashSet<string> { "wf:ocean_eel", "wf:lake_leech" };

	private static readonly HashSet<string> Flats = new HashSet<string> { "wf:ocean_flounder", "wf:ocean_stingray", "wf:ocean_manta_ray", "wf:ocean_golden_manta_ray" };

	private static readonly HashSet<string> Sharks = new HashSet<string> { "wf:lake_bullshark", "wf:ocean_greatwhiteshark", "wf:ocean_hammerhead_shark", "wf:ocean_dogfish", "wf:ocean_sawfish", "wf:rain_heliocoprion", "wf:lake_alligator" };

	private static readonly HashSet<string> ExtraBiters = new HashSet<string> { "wf:rain_leedsichthys" };

	public static List<FishDef> MapAll(WfCatalog catalog, MappingConfig cfg)
	{
		List<FishDef> list = catalog.Fish.Select((WfFish f) => Map(f, cfg)).ToList();
		double num = list.Sum((FishDef d) => d.Htf.SpawnWeight);
		if (num <= 0.0)
		{
			return list;
		}
		double e = cfg.ValueSpreadExponent;
		double num2 = list.Sum((FishDef d) => (double)d.Htf.SpawnWeight * Math.Pow(Math.Max(0.01, d.Htf.WfWorth), e)) / num;
		double num3 = list.Sum((FishDef d) => (double)d.Htf.SpawnWeight * Math.Max(0.1, d.Wf.CatchDifficulty)) / num;
		foreach (FishDef item in list)
		{
			item.Htf.RelativeWorth = Clamp((float)(Math.Pow(Math.Max(0.01, item.Htf.WfWorth), e) / num2), cfg.RelativeWorthMin, cfg.RelativeWorthMax);
			item.Htf.RelativeHp = Clamp((float)(Math.Max(0.1, item.Wf.CatchDifficulty) / num3), cfg.RelativeHpMin, cfg.RelativeHpMax);
		}
		return list;
	}

	public static FishDef Map(WfFish wf, MappingConfig cfg)
	{
		int num = Math.Max(0, Math.Min(2, wf.Tier));
		SizeClass sizeClass = ClassifySize(wf, cfg);
		float num2 = Clamp(cfg.WeightCoeff * (float)Math.Pow(wf.AvgSizeCm, cfg.WeightExponent), cfg.MinWeightKg, cfg.MaxMeanWeightKg);
		HtfTargets htf = new HtfTargets
		{
			SizeClass = sizeClass,
			Zone = ZoneFor(wf.Pool),
			SpawnWeight = wf.LootWeight * cfg.SpawnWeightScale * PoolScale(wf.Pool, cfg),
			MinBaitTier = cfg.MinBaitTierByWfTier[num],
			RelativeWorth = 1f,
			RelativeHp = 1f,
			Aggression = ((sizeClass == SizeClass.Shark || ExtraBiters.Contains(wf.Id)) ? Aggression.Bite : ((wf.CatchDifficulty >= cfg.FleeDifficulty) ? Aggression.Flee : Aggression.Passive)),
			WeightKgMean = num2,
			WeightSpread = cfg.WeightSpread,
			WeightKgMin = Math.Max(cfg.MinWeightKg, num2 * cfg.WeightRollMin),
			WeightKgMax = num2 * cfg.WeightRollMax,
			WfWorth = WfWorth(wf),
			UltraRare = wf.Rare
		};
		return new FishDef
		{
			Id = wf.Id,
			Name = wf.Name,
			Wf = wf,
			Htf = htf,
			Visual = new FishVisual
			{
				Frames = (wf.Sprite?.Frames?.ToArray() ?? Array.Empty<string>()),
				Fps = (wf.Sprite?.Fps ?? 0f),
				Width = (wf.Sprite?.Width ?? 0),
				Height = (wf.Sprite?.Height ?? 0),
				Filter = (wf.Sprite?.Filter ?? "point")
			}
		};
	}

	public static SizeClass ClassifySize(WfFish wf, MappingConfig cfg)
	{
		if (Eels.Contains(wf.Id))
		{
			return SizeClass.Eel;
		}
		if (Flats.Contains(wf.Id))
		{
			return SizeClass.Flat;
		}
		if (Sharks.Contains(wf.Id))
		{
			return SizeClass.Shark;
		}
		if (wf.AvgSizeCm >= cfg.GiantMinCm)
		{
			return SizeClass.Giant;
		}
		if (!(wf.AvgSizeCm <= cfg.SmallMaxCm))
		{
			return SizeClass.Medium;
		}
		return SizeClass.Small;
	}

	public static ZoneRule ZoneFor(WfPool pool)
	{
		return pool switch
		{
			WfPool.Freshwater => ZoneRule.Freshwater, 
			WfPool.Saltwater => ZoneRule.Saltwater, 
			WfPool.Rain => ZoneRule.AnyDuringRain, 
			_ => ZoneRule.Anywhere, 
		};
	}

	public static bool IslandIsSaltwater(int islandIndex, int islandCount)
	{
		return islandIndex >= islandCount - 2;
	}

	public static bool CanSpawn(FishDef def, int islandIndex, int islandCount, bool rainConditionActive, int baitTier)
	{
		if (baitTier < def.Htf.MinBaitTier)
		{
			return false;
		}
		return def.Htf.Zone switch
		{
			ZoneRule.Freshwater => !IslandIsSaltwater(islandIndex, islandCount), 
			ZoneRule.Saltwater => IslandIsSaltwater(islandIndex, islandCount), 
			ZoneRule.AnyDuringRain => rainConditionActive, 
			_ => true, 
		};
	}

	public static Dictionary<string, float> InjectedWeights(LureStats lure, IEnumerable<FishDef> eligible, MappingConfig cfg)
	{
		List<FishDef> list = eligible.Where((FishDef d) => d.Htf.SpawnWeight > 0f).ToList();
		Dictionary<string, float> dictionary = new Dictionary<string, float>();
		float num = Clamp(cfg.WfCatchShare, 0f, 0.95f);
		double num2 = list.Sum((FishDef d) => d.Htf.SpawnWeight);
		if (num2 <= 0.0 || num <= 0f || lure.NativeWeightTotal <= 0f)
		{
			return dictionary;
		}
		double num3 = lure.NativeWeightTotal * num / (1f - num);
		foreach (FishDef item in list)
		{
			dictionary[item.Id] = (float)(num3 * (double)item.Htf.SpawnWeight / num2);
		}
		return dictionary;
	}

	public static bool RainConditionActive(RainCondition cond, bool modRain, bool storm = false, bool night = false)
	{
		return cond switch
		{
			RainCondition.ModRain => modRain, 
			RainCondition.Storm => storm, 
			RainCondition.Night => night, 
			_ => storm || night, 
		};
	}

	public static float WfWorth(WfFish wf)
	{
		if (!wf.GenerateWorth)
		{
			return wf.SellValue;
		}
		double num = 1.0 + 0.25 * (double)wf.Tier;
		double num2 = wf.LootWeight;
		double num3 = Math.Pow(5.0 * num, 2.5 - num2);
		if (num2 < 0.4)
		{
			num3 *= 1.1;
		}
		if (num2 < 0.15)
		{
			num3 *= 1.25;
		}
		if (num2 < 0.05)
		{
			num3 *= 1.5;
		}
		return (float)num3;
	}

	private static float PoolScale(WfPool pool, MappingConfig cfg)
	{
		return pool switch
		{
			WfPool.Misc => cfg.MiscSpawnScale, 
			WfPool.Rain => cfg.RainSpawnScale, 
			_ => 1f, 
		};
	}

	private static float Clamp(float v, float lo, float hi)
	{
		if (!(v < lo))
		{
			if (!(v > hi))
			{
				return v;
			}
			return hi;
		}
		return lo;
	}
}
public static class Hashing
{
	private static readonly JsonSerializer Canonical = JsonSerializer.Create(new JsonSerializerSettings
	{
		Formatting = (Formatting)0,
		NullValueHandling = (NullValueHandling)0
	});

	private static readonly JsonSerializer Reader = JsonSerializer.Create(new JsonSerializerSettings
	{
		DateParseHandling = (DateParseHandling)0
	});

	public static string CanonicalJson(object value)
	{
		StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
		Canonical.Serialize((TextWriter)stringWriter, value);
		return stringWriter.ToString();
	}

	public static T Clone<T>(T value)
	{
		return FromJson<T>(CanonicalJson(value));
	}

	public static T FromJson<T>(string json)
	{
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Expected O, but got Unknown
		using StringReader stringReader = new StringReader(json);
		JsonTextReader val = new JsonTextReader((TextReader)stringReader);
		try
		{
			return Reader.Deserialize<T>((JsonReader)(object)val);
		}
		finally
		{
			((IDisposable)val)?.Dispose();
		}
	}

	public static string Short(object value)
	{
		using SHA256 sHA = SHA256.Create();
		byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(CanonicalJson(value)));
		StringBuilder stringBuilder = new StringBuilder(16);
		for (int i = 0; i < 8; i++)
		{
			stringBuilder.Append(array[i].ToString("x2"));
		}
		return stringBuilder.ToString();
	}

	public static string FishDefHash(WfCatalog catalog)
	{
		return Short(catalog.Fish);
	}
}
public static class JsonFile
{
	public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
	{
		Formatting = (Formatting)1,
		NullValueHandling = (NullValueHandling)1,
		MissingMemberHandling = (MissingMemberHandling)0,
		FloatFormatHandling = (FloatFormatHandling)2
	};

	private static readonly JsonSerializer Serializer = JsonSerializer.Create(Settings);

	public static T Read<T>(string path)
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Expected O, but got Unknown
		using StringReader stringReader = new StringReader(File.ReadAllText(path, Encoding.UTF8));
		JsonTextReader val = new JsonTextReader((TextReader)stringReader);
		try
		{
			return Serializer.Deserialize<T>((JsonReader)(object)val);
		}
		finally
		{
			((IDisposable)val)?.Dispose();
		}
	}

	public static string ToJson<T>(T value)
	{
		StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
		Serializer.Serialize((TextWriter)stringWriter, (object)value);
		return stringWriter.ToString();
	}

	public static string TempPath(string path)
	{
		return path + ".tmp";
	}

	public static void WriteAtomic<T>(string path, T value)
	{
		Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path)));
		string text = TempPath(path);
		WriteDurable(text, Encoding.UTF8.GetBytes(ToJson(value)));
		if (File.Exists(path))
		{
			File.Replace(text, path, null, ignoreMetadataErrors: true);
		}
		else
		{
			File.Move(text, path);
		}
	}

	public static void WriteDurable(string path, byte[] bytes)
	{
		using FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
		fileStream.Write(bytes, 0, bytes.Length);
		fileStream.Flush(flushToDisk: true);
	}
}
public sealed class MappingConfig
{
	public float WfCatchShare = 0.25f;

	public float SpawnWeightScale = 1f;

	public float RainSpawnScale = 1f;

	public float MiscSpawnScale = 0.02f;

	public RainCondition RainCondition;

	public float ShinyChance = 0.0005f;

	public float TntWfShare = 0.25f;

	public float BossShinyChance = 0.01f;

	public float ShinyCharmChance = 0.002f;

	public int WfLurePrice = 1000;

	public int WfLureIsland = -1;

	public int ShinyCharmPrice = 9999;

	public int ShinyCharmIsland;

	public int[] MinBaitTierByWfTier = new int[3] { 0, 0, 1 };

	public float ValueSpreadExponent = 0.5f;

	public float RelativeWorthMin = 0.25f;

	public float RelativeWorthMax = 4f;

	public float RelativeHpMin = 0.5f;

	public float RelativeHpMax = 2.5f;

	public float FleeDifficulty = 3f;

	public float WeightCoeff = 1.2E-05f;

	public float WeightExponent = 3f;

	public float WeightSpread = 0.35f;

	public float MinWeightKg = 0.01f;

	public float MaxMeanWeightKg = 1500f;

	public float WeightRollMin = 0.3f;

	public float WeightRollMax = 3f;

	public float SmallMaxCm = 40f;

	public float GiantMinCm = 500f;
}
public sealed class NativeCatch
{
	public string Name;

	public int Id;

	public float Weight;

	public int Worth;

	public int MaxHp;

	public bool IsBoss;

	public bool Foreign;
}
public sealed class NativeBait
{
	public int Index;

	public string Name;

	public int Cost;

	public bool Euro;

	public List<NativeCatch> Catches = new List<NativeCatch>();
}
public sealed class LureStats
{
	public int Tier;

	public NativeBait Bait;

	public float NativeWeightTotal;

	public float AvgWorth;

	public int MaxRegularWorth;

	public float AvgHp;
}
public static class NativeEconomy
{
	public const int MinSpeciesForGeneralLure = 4;

	public static List<LureStats> GeneralLures(IEnumerable<NativeBait> baits)
	{
		List<NativeBait> list = (from b in baits
			where !b.Euro && b.Catches.Count((NativeCatch c) => !c.IsBoss && !c.Foreign) >= 4
			group b by b.Name into g
			select g.First() into b
			orderby b.Cost, b.Index
			select b).ToList();
		List<LureStats> list2 = new List<LureStats>();
		for (int num = 0; num < list.Count; num++)
		{
			List<NativeCatch> list3 = list[num].Catches.Where((NativeCatch c) => !c.IsBoss && !c.Foreign && c.Weight > 0f).ToList();
			float num2 = list3.Sum((NativeCatch c) => c.Weight);
			list2.Add(new LureStats
			{
				Tier = num,
				Bait = list[num],
				NativeWeightTotal = list[num].Catches.Where((NativeCatch c) => !c.Foreign).Sum((NativeCatch c) => Math.Max(0f, c.Weight)),
				AvgWorth = ((num2 > 0f) ? (list3.Sum((NativeCatch c) => (float)c.Worth * c.Weight) / num2) : 0f),
				MaxRegularWorth = ((list3.Count > 0) ? list3.Max((NativeCatch c) => c.Worth) : 0),
				AvgHp = ((num2 > 0f) ? (list3.Sum((NativeCatch c) => (float)c.MaxHp * c.Weight) / num2) : 0f)
			});
		}
		return list2;
	}
}
[JsonConverter(typeof(StringEnumConverter))]
public enum Quality
{
	Normal,
	Shining,
	Glistening,
	Opulent,
	Radiant,
	Alpha
}
public static class QualityInfo
{
	public const int Count = 6;

	public static readonly float[] WfWorth = new float[6] { 1f, 1.8f, 4f, 6f, 10f, 15f };

	public static readonly string[] Tint = new string[6] { "#ffffff", "#e5f5f0", "#eafcf5", "#d5fcf5", "#fcf0d5", "#fcd5df" };

	public static string Prefix(Quality q)
	{
		if (q != Quality.Normal)
		{
			return q.ToString() + " ";
		}
		return "";
	}

	public static Quality Roll(float[] chances, Func<double> next01, float boost = 1f)
	{
		Quality result = Quality.Normal;
		if (chances == null)
		{
			return result;
		}
		for (int i = 0; i < chances.Length && i < 6; i++)
		{
			if (next01() < (double)(chances[i] * boost))
			{
				result = (Quality)i;
			}
		}
		return result;
	}
}
public struct PixelRect
{
	public int X;

	public int Y;

	public int W;

	public int H;

	public PixelRect(int x, int y, int w, int h)
	{
		X = x;
		Y = y;
		W = w;
		H = h;
	}
}
public static class SpriteRects
{
	public static List<PixelRect> Build(byte[] alpha, int w, int h, byte cutoff = 128)
	{
		List<PixelRect> list = new List<PixelRect>();
		Dictionary<long, int> dictionary = new Dictionary<long, int>();
		Dictionary<long, int> dictionary2 = new Dictionary<long, int>();
		for (int i = 0; i < h; i++)
		{
			dictionary2.Clear();
			int num = i * w;
			int j = 0;
			while (j < w)
			{
				if (alpha[num + j] < cutoff)
				{
					j++;
					continue;
				}
				int num2 = j;
				for (; j < w && alpha[num + j] >= cutoff; j++)
				{
				}
				long key = ((long)num2 << 32) | (uint)j;
				if (dictionary.TryGetValue(key, out var value))
				{
					PixelRect value2 = list[value];
					value2.H++;
					list[value] = value2;
					dictionary2[key] = value;
				}
				else
				{
					list.Add(new PixelRect(num2, i, j - num2, 1));
					dictionary2[key] = list.Count - 1;
				}
			}
			Dictionary<long, int> dictionary3 = dictionary;
			dictionary = dictionary2;
			dictionary2 = dictionary3;
		}
		return list;
	}
}
[JsonConverter(typeof(StringEnumConverter))]
public enum WfPool
{
	Freshwater,
	Saltwater,
	Rain,
	Misc
}
public sealed class WfFish
{
	public string Id;

	public string Name;

	public string Description;

	public string Blurb;

	public string LootTable;

	public WfPool Pool;

	public int Tier;

	public float CatchDifficulty;

	public float CatchSpeed;

	public float LootWeight;

	public float AvgSizeCm;

	public int SellValue;

	public float SellMultiplier = 1f;

	public int ObtainXp;

	public bool GenerateWorth;

	public bool Rare;

	public WfSprite Sprite;
}
public sealed class WfSprite
{
	public List<string> Frames = new List<string>();

	public float Fps;

	public int Width;

	public int Height;

	public string Filter = "point";
}
public sealed class WfCatalog
{
	public const int CurrentSchema = 1;

	public int Schema = 1;

	public string ExtractorVersion;

	public string ExtractedUtc;

	public string SourceFile;

	public string SourceSha256;

	public string SteamBuildId;

	public List<WfFish> Fish = new List<WfFish>();
}

BepInEx/plugins/WFxHTF/WFxHTF.Extraction.dll

Decompiled a day ago
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using WFxHTF.Data;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("WFxHTF")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+166b9aae0d28865ba70c4894450aeb4db4b21aeb")]
[assembly: AssemblyProduct("WFxHTF.Extraction")]
[assembly: AssemblyTitle("WFxHTF.Extraction")]
[assembly: AssemblyVersion("0.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace WFxHTF.Extraction
{
	public static class ContactSheet
	{
		public static void Write(string path, WfCatalog catalog)
		{
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("<!doctype html><html><head><meta charset=\"utf-8\"><title>WFxHTF contact sheet</title>\n<style>\nbody{font:13px system-ui,sans-serif;background:#1c1f24;color:#ddd;margin:16px}\nh2{margin:24px 0 8px;font-size:16px;color:#fff}\n.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:10px}\n.cell{background:#2a2e35;border-radius:6px;padding:8px;text-align:center}\n.cell.rare{outline:2px solid #e6b422}\n.img{height:128px;display:flex;align-items:center;justify-content:center;\n background:repeating-conic-gradient(#3a3f47 0 25%,#30343b 0 50%) 0 0/16px 16px;border-radius:4px}\n.img img{image-rendering:pixelated;max-width:150px;max-height:120px;min-width:64px}\n.name{font-weight:600;margin-top:6px;color:#fff}.meta{color:#9aa;font-size:11px}\n</style></head><body>");
			stringBuilder.Append($"<p>{catalog.Fish.Count} fish &middot; source build {WebUtility.HtmlEncode(catalog.SteamBuildId)} &middot; extracted {catalog.ExtractedUtc}. Gold outline = rare flag. Check: no mirrored, cropped or missing sprites.</p>");
			foreach (IGrouping<WfPool, WfFish> item in from f in catalog.Fish
				group f by f.Pool)
			{
				stringBuilder.Append($"<h2>{item.Key} ({item.Count()})</h2><div class=grid>");
				foreach (WfFish item2 in item)
				{
					string value = string.Join(",", item2.Sprite.Frames.Select((string x) => "\"fish/" + x + "\""));
					string text = ((item2.Sprite.Frames.Count > 1) ? $" data-frames=\"[{WebUtility.HtmlEncode(value)}]\" data-fps=\"{item2.Sprite.Fps}\"" : "");
					stringBuilder.Append("<div class=\"cell" + (item2.Rare ? " rare" : "") + "\"><div class=img><img src=\"fish/" + item2.Sprite.Frames[0] + "\"" + text + "></div>").Append("<div class=name>" + WebUtility.HtmlEncode(item2.Name) + "</div>").Append($"<div class=meta>{item2.Id} &middot; tier {item2.Tier} &middot; {item2.AvgSizeCm:0}cm<br>{item2.Sprite.Width}&times;{item2.Sprite.Height}px")
						.Append((item2.Sprite.Frames.Count > 1) ? $" &middot; {item2.Sprite.Frames.Count} frames" : "")
						.Append("</div></div>");
				}
				stringBuilder.Append("</div>");
			}
			stringBuilder.Append("<script>\ndocument.querySelectorAll('img[data-frames]').forEach(img=>{\n const fr=JSON.parse(img.dataset.frames),fps=+img.dataset.fps||10;let i=0;\n setInterval(()=>{i=(i+1)%fr.length;img.src=fr[i];},1000/fps);});\n</script></body></html>");
			File.WriteAllText(path, stringBuilder.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
		}
	}
	public sealed class ExtractOptions
	{
		public string WebfishingPath;

		public string GdrePath;

		public string Downloads;

		public string Cache;

		public bool KeepRaw;

		public bool ContactSheet;
	}
	public static class Extraction
	{
		public const string Version = "0.2.0";

		private const int WebfishingAppId = 3146520;

		private const int HowToFishAppId = 4001890;

		public const int ExpectedFishCount = 70;

		private const string Marker = ".wfxhtf-cache";

		private static readonly Dictionary<string, WfPool> Pools = new Dictionary<string, WfPool>
		{
			["lake"] = (WfPool)0,
			["ocean"] = (WfPool)1,
			["rain"] = (WfPool)2,
			["alien"] = (WfPool)3,
			["void"] = (WfPool)3
		};

		public static string Root => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "WFxHTF");

		public static string Run(ExtractOptions o, Action<string> log, Action<int, string> progress)
		{
			//IL_0a36: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a3b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a46: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a64: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a71: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a79: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a83: Expected O, but got Unknown
			//IL_0afc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b01: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b21: Expected O, but got Unknown
			//IL_0d7b: Unknown result type (might be due to invalid IL or missing references)
			//IL_060b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0610: Unknown result type (might be due to invalid IL or missing references)
			//IL_061d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0634: Unknown result type (might be due to invalid IL or missing references)
			//IL_064b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0662: Unknown result type (might be due to invalid IL or missing references)
			//IL_066a: Unknown result type (might be due to invalid IL or missing references)
			//IL_066e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0673: Unknown result type (might be due to invalid IL or missing references)
			//IL_0678: Unknown result type (might be due to invalid IL or missing references)
			//IL_068b: Unknown result type (might be due to invalid IL or missing references)
			//IL_06a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_06b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_06d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_06fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0711: Unknown result type (might be due to invalid IL or missing references)
			//IL_0725: Unknown result type (might be due to invalid IL or missing references)
			//IL_0738: Unknown result type (might be due to invalid IL or missing references)
			//IL_0752: Expected O, but got Unknown
			o = o ?? new ExtractOptions();
			log = log ?? ((Action<string>)delegate
			{
			});
			progress = progress ?? ((Action<int, string>)delegate
			{
			});
			Dictionary<string, WfPool> pools = Pools;
			string text = null;
			try
			{
				string fullPath = Path.GetFullPath(o.Cache ?? Path.Combine(Root, "cache"));
				if (!Paths.IsUnder(fullPath, Root) && !Paths.IsUnder(fullPath, Path.GetTempPath()))
				{
					throw new Exception("the cache must be inside " + Root + " or " + Path.GetTempPath());
				}
				if (Directory.Exists(fullPath) && Directory.EnumerateFileSystemEntries(fullPath).Any() && !File.Exists(Path.Combine(fullPath, ".wfxhtf-cache")))
				{
					throw new Exception(fullPath + " exists and is not a WFxHTF cache; refusing to replace it");
				}
				text = fullPath + ".staging";
				string raw = Path.Combine(text, "raw");
				string text2 = Path.Combine(text, "fish");
				progress(2, "Looking for WEBFISHING");
				string text3 = "";
				string text4 = o.WebfishingPath;
				if (text4 == null)
				{
					SteamGame obj = SteamLocator.Find(3146520) ?? throw new Exception("WEBFISHING (Steam app 3146520) isn't installed on this PC. Install it in Steam (owning it isn't enough), then try again.");
					text4 = obj.InstallDir;
					text3 = obj.BuildId;
				}
				if (Directory.Exists(text4))
				{
					string text5 = Path.Combine(text4, "webfishing.pck");
					string text6 = Path.Combine(text4, "webfishing.exe");
					string text7;
					if (!File.Exists(text5))
					{
						if (!File.Exists(text6))
						{
							throw new Exception("no webfishing.pck or webfishing.exe in " + text4);
						}
						text7 = text6;
					}
					else
					{
						text7 = text5;
					}
					text4 = text7;
				}
				if (!File.Exists(text4))
				{
					throw new Exception("not found: " + text4);
				}
				text4 = Path.GetFullPath(text4);
				List<string> list = new List<string> { Path.GetDirectoryName(text4) };
				SteamGame steamGame = SteamLocator.Find(4001890);
				if (steamGame != null)
				{
					list.Add(steamGame.InstallDir);
				}
				foreach (string item in list)
				{
					if (Paths.IsUnder(Paths.Resolve(fullPath), Paths.Resolve(item)))
					{
						throw new Exception("cache folder must not be inside a game folder (" + item + ")");
					}
				}
				log("WEBFISHING: " + text4 + " (build " + ((text3 == "") ? "?" : text3) + ")");
				progress(6, "Reading WEBFISHING");
				string text8 = Win.Sha256(text4);
				log("sha256: " + text8);
				string text9 = Path.Combine(Root, "tools");
				string text10 = Gdre.Locate(o.GdrePath, text9);
				if (text10 == null && o.GdrePath == null)
				{
					progress(10, "Looking for GDRE Tools in Downloads");
					text10 = GdreLocal.FromDownloads(o.Downloads ?? GdreLocal.DownloadsFolder(), text9, log);
					if (text10 != null)
					{
						progress(40, "GDRE Tools checked and unpacked");
					}
				}
				if (text10 == null)
				{
					throw new Exception("GDRE Tools not found: download GDRE_tools-v2.7.4-windows.zip from https://github.com/GDRETools/gdsdecomp/releases/tag/v2.7.4 into your Downloads folder and run this again (or unzip it into " + text9 + ", or pass --gdre <path to gdre_tools.exe>).");
				}
				Gdre gdre = new Gdre(text10);
				log("GDRE Tools: " + text10);
				progress(45, "Finding the fish in WEBFISHING");
				if (Directory.Exists(text))
				{
					Directory.Delete(text, recursive: true);
				}
				Directory.CreateDirectory(raw);
				Directory.CreateDirectory(text2);
				gdre.Recover(text4, raw, new string[1] { "res://Resources/Creatures_Fish_*/**" });
				if (gdre.Version != null && gdre.Version != "2.7.4")
				{
					log("WARNING: GDRE Tools " + gdre.Version + " differs from tested 2.7.4");
				}
				List<(WfFish, string)> list2 = new List<(WfFish, string)>();
				string path = Path.Combine(raw, "Resources");
				List<string> obj2 = (Directory.Exists(path) ? Directory.GetDirectories(path, "Creatures_Fish_*").SelectMany((string d) => Directory.GetFiles(d, "*.tres")).OrderBy<string, string>((string f) => f, StringComparer.Ordinal)
					.ToList() : new List<string>());
				if (obj2.Count == 0)
				{
					throw new Exception("GDRE recovered no fish resources; the pack layout may differ from WEBFISHING 1.x");
				}
				List<string> list3 = new List<string>();
				int num = 0;
				foreach (string item2 in obj2)
				{
					TresFile tresFile = TresFile.Parse(File.ReadAllText(item2));
					string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(item2);
					string text11 = tresFile.Str("loot_table", "none");
					string text12 = ((tresFile.Str("category") != "fish") ? "not category fish" : (tresFile.Bool("item_is_hidden", def: false) ? "hidden" : (tresFile.Bool("unobtainable", def: false) ? "unobtainable (test entry)" : ((!pools.ContainsKey(text11)) ? ("loot_table '" + text11 + "' excluded") : null))));
					if (text12 != null)
					{
						log("  skip " + fileNameWithoutExtension + ": " + text12);
						continue;
					}
					num++;
					string id = "wf:" + (fileNameWithoutExtension.StartsWith("fish_") ? fileNameWithoutExtension.Substring(5) : fileNameWithoutExtension);
					string text13 = tresFile.ExtPath("icon");
					if (text13 == null)
					{
						list3.Add(id + ": no icon texture");
						continue;
					}
					if (list2.Any<(WfFish, string)>(((WfFish Fish, string IconRes) e) => e.Fish.Id == id))
					{
						list3.Add(id + ": duplicate id (" + item2 + ")");
						continue;
					}
					list2.Add((new WfFish
					{
						Id = id,
						Name = tresFile.Str("item_name"),
						Description = tresFile.Str("item_description"),
						Blurb = tresFile.Str("catch_blurb"),
						LootTable = text11,
						Pool = pools[text11],
						Tier = tresFile.Int("tier", 0),
						CatchDifficulty = tresFile.Float("catch_difficulty", 1f),
						CatchSpeed = tresFile.Float("catch_speed", 120f),
						LootWeight = tresFile.Float("loot_weight", 1f),
						AvgSizeCm = tresFile.Float("average_size", 75f),
						SellValue = tresFile.Int("sell_value", 5),
						SellMultiplier = tresFile.Float("sell_multiplier", 1f),
						ObtainXp = tresFile.Int("obtain_xp", 30),
						GenerateWorth = tresFile.Bool("generate_worth", def: true),
						Rare = tresFile.Bool("rare", def: false)
					}, text13));
				}
				progress(65, "Reading the fish pictures");
				Dictionary<string, (List<string>, float)> dictionary = new Dictionary<string, (List<string>, float)>();
				Dictionary<string, string> dictionary2 = new Dictionary<string, string>();
				HashSet<string> hashSet = new HashSet<string>(list2.Select<(WfFish, string), string>(((WfFish Fish, string IconRes) e) => e.IconRes));
				HashSet<string> done = new HashSet<string>();
				int num2 = 0;
				while (hashSet.Count > 0 && num2 < 4)
				{
					List<string> list4 = new List<string>();
					foreach (string item3 in hashSet)
					{
						if (item3.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
						{
							list4.Add(item3 + ".import");
							list4.Add("res://.import/" + item3.Substring(item3.LastIndexOf('/') + 1) + "-*");
						}
						else
						{
							list4.Add(item3);
						}
					}
					gdre.Recover(text4, raw, list4);
					done.UnionWith(hashSet);
					HashSet<string> hashSet2 = new HashSet<string>();
					foreach (string item4 in hashSet.Where((string r) => !r.EndsWith(".png", StringComparison.OrdinalIgnoreCase)))
					{
						try
						{
							if (!item4.EndsWith(".tres", StringComparison.OrdinalIgnoreCase))
							{
								throw new Exception("unsupported texture file type");
							}
							if (!File.Exists(RawPath(item4)))
							{
								throw new Exception("not recovered");
							}
							TresFile t = TresFile.Parse(File.ReadAllText(RawPath(item4)));
							if (t.ResourceType != "AnimatedTexture")
							{
								throw new Exception("unsupported texture type " + t.ResourceType);
							}
							int count = t.Int("frames", 1);
							List<string> list5 = (from k in Enumerable.Range(0, count)
								select t.ExtPath($"frame_{k}/texture") ?? throw new Exception($"frame {k} missing")).ToList();
							if (list5.Count == 0)
							{
								throw new Exception("no frames");
							}
							dictionary[item4] = (list5, t.Float("fps", 4f));
							hashSet2.UnionWith(list5.Where((string f) => !done.Contains(f)));
						}
						catch (Exception ex)
						{
							dictionary2[item4] = ex.Message;
						}
					}
					hashSet = hashSet2;
					num2++;
				}
				progress(85, "Saving the fish");
				WfCatalog val = new WfCatalog
				{
					ExtractorVersion = "0.2.0",
					ExtractedUtc = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture),
					SourceFile = Path.GetFileName(text4),
					SourceSha256 = text8,
					SteamBuildId = text3
				};
				foreach (var (val2, text14) in list2)
				{
					try
					{
						if (dictionary2.TryGetValue(text14, out var value))
						{
							throw new Exception(text14 + ": " + value);
						}
						string text15 = val2.Id.Substring(3);
						object obj3;
						if (dictionary.TryGetValue(text14, out var value2))
						{
							(obj3, _) = value2;
						}
						else
						{
							obj3 = new List<string> { text14 };
						}
						List<string> list6 = (List<string>)obj3;
						WfSprite val3 = new WfSprite
						{
							Fps = ((list6.Count > 1) ? value2.Item2 : 0f)
						};
						for (int num3 = 0; num3 < list6.Count; num3++)
						{
							string text16 = RawPath(list6[num3]);
							if (!File.Exists(text16))
							{
								throw new Exception("texture not recovered: " + list6[num3]);
							}
							var (num4, num5) = PngCheck.Validate(text16);
							if (num3 == 0)
							{
								val3.Width = num4;
								val3.Height = num5;
							}
							else if (num4 != val3.Width || num5 != val3.Height)
							{
								throw new Exception($"frame {num3} is {num4}x{num5}, expected {val3.Width}x{val3.Height}");
							}
							string text17 = ((list6.Count > 1) ? $"{text15}_{num3:00}.png" : (text15 + ".png"));
							File.Copy(text16, Path.Combine(text2, text17), overwrite: true);
							val3.Frames.Add(text17);
						}
						val2.Sprite = val3;
						val.Fish.Add(val2);
					}
					catch (Exception ex2)
					{
						list3.Add(val2.Id + ": " + ex2.Message);
					}
				}
				val.Fish = val.Fish.OrderBy((WfFish f) => f.Pool).ThenBy<WfFish, string>((WfFish f) => f.Id, StringComparer.Ordinal).ToList();
				JsonFile.WriteAtomic<WfCatalog>(Path.Combine(text, "fishdefs.wf.json"), val);
				if (o.ContactSheet)
				{
					ContactSheet.Write(Path.Combine(text, "contact_sheet.html"), val);
				}
				foreach (IGrouping<WfPool, WfFish> item5 in from f in val.Fish
					group f by f.Pool)
				{
					log($"  {item5.Key,-10} {item5.Count()}");
				}
				log("  rare: " + string.Join(", ", from f in val.Fish
					where f.Rare
					select f.Name));
				foreach (string item6 in list3)
				{
					log("  FAIL " + item6);
				}
				log($"{val.Fish.Count}/{num} converted");
				if (list3.Count > 0 || val.Fish.Count != 70)
				{
					throw new Exception($"expected {70} fish to convert cleanly; existing cache left unchanged");
				}
				if (!o.KeepRaw)
				{
					Directory.Delete(raw, recursive: true);
				}
				File.WriteAllText(Path.Combine(text, ".wfxhtf-cache"), "WFxHTF extractor cache. Safe to delete; re-run the extractor to rebuild.\n");
				string text18 = fullPath + ".old";
				if (Directory.Exists(text18))
				{
					Directory.Delete(text18, recursive: true);
				}
				if (Directory.Exists(fullPath))
				{
					Directory.Move(fullPath, text18);
				}
				Directory.Move(text, fullPath);
				text = null;
				if (Directory.Exists(text18))
				{
					Directory.Delete(text18, recursive: true);
				}
				progress(100, $"{val.Fish.Count} WEBFISHING fish ready");
				log("cache: " + fullPath);
				if (o.ContactSheet)
				{
					log("contact sheet: " + Path.Combine(fullPath, "contact_sheet.html"));
				}
				return fullPath;
				string RawPath(string res)
				{
					return Path.Combine(raw, res.Substring("res://".Length).Replace('/', Path.DirectorySeparatorChar));
				}
			}
			finally
			{
				if (text != null && Directory.Exists(text) && !o.KeepRaw)
				{
					try
					{
						Directory.Delete(text, recursive: true);
					}
					catch (Exception ex3)
					{
						log("WARNING: could not delete " + text + ": " + ex3.Message);
					}
				}
			}
		}
	}
	public sealed class Gdre
	{
		public const string PinnedVersion = "2.7.4";

		public readonly string Exe;

		public string Version { get; private set; }

		public Gdre(string exe)
		{
			Exe = exe;
		}

		public static string Locate(string explicitPath, string toolsDir)
		{
			if (!string.IsNullOrEmpty(explicitPath))
			{
				if (!File.Exists(explicitPath))
				{
					return null;
				}
				return Path.GetFullPath(explicitPath);
			}
			try
			{
				if (Directory.Exists(toolsDir))
				{
					return Directory.EnumerateFiles(toolsDir, "gdre_tools.exe", SearchOption.AllDirectories).FirstOrDefault();
				}
			}
			catch (IOException)
			{
			}
			catch (UnauthorizedAccessException)
			{
			}
			return null;
		}

		public void Recover(string source, string outDir, IEnumerable<string> includes)
		{
			List<string> list = new List<string>
			{
				"--headless",
				"--recover=" + source,
				"--output=" + outDir
			};
			list.AddRange(includes.Select((string g) => "--include=" + g));
			var (num, text) = Run(list);
			if (Version == null)
			{
				Match match = Regex.Match(text, "GDRE Tools v(\\d+\\.\\d+\\.\\d+)");
				if (match.Success)
				{
					Version = match.Groups[1].Value;
				}
			}
			if (num != 0 || text.Contains("Recovery failed", StringComparison.OrdinalIgnoreCase))
			{
				throw new Exception($"GDRE recover failed (exit {num}). Last output:\n{Tail(text, 20)}");
			}
		}

		private (int, string) Run(List<string> args)
		{
			using Process process = Process.Start(new ProcessStartInfo(Exe)
			{
				RedirectStandardOutput = true,
				RedirectStandardError = true,
				UseShellExecute = false,
				CreateNoWindow = true,
				Arguments = string.Join(" ", args.Select(Quote))
			}) ?? throw new Exception("could not start GDRE Tools");
			Task<string> task = process.StandardOutput.ReadToEndAsync();
			Task<string> task2 = process.StandardError.ReadToEndAsync();
			if (!process.WaitForExit(600000))
			{
				try
				{
					process.Kill();
				}
				catch (InvalidOperationException)
				{
				}
				throw new Exception("GDRE Tools timed out after 10 minutes");
			}
			process.WaitForExit();
			return (process.ExitCode, task.Result + task2.Result);
		}

		private static string Quote(string a)
		{
			if (a.Length > 0 && a.IndexOfAny(new char[3] { ' ', '\t', '"' }) < 0)
			{
				return a;
			}
			StringBuilder stringBuilder = new StringBuilder("\"");
			int num = 0;
			foreach (char c in a)
			{
				switch (c)
				{
				case '\\':
					num++;
					continue;
				case '"':
					stringBuilder.Append('\\', num * 2 + 1).Append('"');
					break;
				default:
					stringBuilder.Append('\\', num).Append(c);
					break;
				}
				num = 0;
			}
			return stringBuilder.Append('\\', num * 2).Append('"').ToString();
		}

		private static string Tail(string s, int lines)
		{
			return string.Join("\n", s.Split('\n').TakeLast(lines));
		}
	}
	public static class GdreLocal
	{
		public const string ReleasePage = "https://github.com/GDRETools/gdsdecomp/releases/tag/v2.7.4";

		public const string ZipName = "GDRE_tools-v2.7.4-windows.zip";

		public const string ZipSha256 = "af24c563eee501effb194d83595de64696c32547b2246034dbfb891a24bfa0ad";

		public const string ExeSha256 = "dbcda88ded28b4c9cd0252ba5a4e9af416f91766d9067ca930876e7d4e07215a";

		private static string FolderName => Path.GetFileNameWithoutExtension("GDRE_tools-v2.7.4-windows.zip");

		public static string DownloadsFolder()
		{
			return Win.DownloadsFolder();
		}

		public static string FromDownloads(string downloads, string toolsDir, Action<string> log)
		{
			if (string.IsNullOrEmpty(downloads) || !Directory.Exists(downloads))
			{
				return null;
			}
			string text = Path.Combine(toolsDir, FolderName);
			foreach (FileInfo item in from f in new DirectoryInfo(downloads).GetFiles(FolderName + "*.zip")
				orderby f.LastWriteTimeUtc descending
				select f)
			{
				if (!SameHash(item.FullName, "af24c563eee501effb194d83595de64696c32547b2246034dbfb891a24bfa0ad"))
				{
					log(item.Name + " isn't the official GDRE Tools 2.7.4 release (fingerprint differs); skipped");
					continue;
				}
				Directory.CreateDirectory(toolsDir);
				if (Directory.Exists(text))
				{
					Directory.Delete(text, recursive: true);
				}
				ZipFile.ExtractToDirectory(item.FullName, text);
				string text2 = Path.Combine(text, "gdre_tools.exe");
				if (File.Exists(text2) && SameHash(text2, "dbcda88ded28b4c9cd0252ba5a4e9af416f91766d9067ca930876e7d4e07215a"))
				{
					log("GDRE Tools: unpacked " + item.Name + " into " + text);
					return text2;
				}
				Directory.Delete(text, recursive: true);
			}
			DirectoryInfo[] directories = new DirectoryInfo(downloads).GetDirectories(FolderName + "*");
			foreach (DirectoryInfo directoryInfo in directories)
			{
				string text3 = (File.Exists(Path.Combine(directoryInfo.FullName, "gdre_tools.exe")) ? directoryInfo.FullName : Path.Combine(directoryInfo.FullName, FolderName));
				string path = Path.Combine(text3, "gdre_tools.exe");
				if (File.Exists(path) && SameHash(path, "dbcda88ded28b4c9cd0252ba5a4e9af416f91766d9067ca930876e7d4e07215a"))
				{
					Directory.CreateDirectory(text);
					string[] files = Directory.GetFiles(text3);
					foreach (string text4 in files)
					{
						File.Copy(text4, Path.Combine(text, Path.GetFileName(text4)), overwrite: true);
					}
					log("GDRE Tools: copied " + directoryInfo.Name + " into " + text);
					return Path.Combine(text, "gdre_tools.exe");
				}
			}
			return null;
		}

		private static bool SameHash(string path, string sha256)
		{
			return Win.Sha256(path).Equals(sha256, StringComparison.OrdinalIgnoreCase);
		}
	}
	public static class Paths
	{
		public static bool IsUnder(string path, string dir)
		{
			string text = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
			string value = Path.GetFullPath(dir).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
			return text.StartsWith(value, StringComparison.OrdinalIgnoreCase);
		}

		public static string Resolve(string path)
		{
			return Path.GetFullPath(path);
		}

		public static bool IsReparsePoint(string path)
		{
			try
			{
				for (DirectoryInfo directoryInfo = new DirectoryInfo(Path.GetFullPath(path)); directoryInfo != null; directoryInfo = directoryInfo.Parent)
				{
					if (directoryInfo.Exists && (directoryInfo.Attributes & FileAttributes.ReparsePoint) != FileAttributes.None)
					{
						return true;
					}
				}
			}
			catch (IOException)
			{
			}
			catch (UnauthorizedAccessException)
			{
			}
			return false;
		}
	}
	public static class PngCheck
	{
		private static readonly byte[] Signature = new byte[8] { 137, 80, 78, 71, 13, 10, 26, 10 };

		private static readonly uint[] CrcTable = BuildCrcTable();

		private static uint[] BuildCrcTable()
		{
			uint[] array = new uint[256];
			for (uint num = 0u; num < 256; num++)
			{
				uint num2 = num;
				for (int i = 0; i < 8; i++)
				{
					num2 = (((num2 & 1) != 0) ? (0xEDB88320u ^ (num2 >> 1)) : (num2 >> 1));
				}
				array[num] = num2;
			}
			return array;
		}

		private static uint Crc32(ReadOnlySpan<byte> bytes)
		{
			uint num = uint.MaxValue;
			ReadOnlySpan<byte> readOnlySpan = bytes;
			for (int i = 0; i < readOnlySpan.Length; i++)
			{
				byte b = readOnlySpan[i];
				num = CrcTable[(num ^ b) & 0xFF] ^ (num >> 8);
			}
			return num ^ 0xFFFFFFFFu;
		}

		public static (int Width, int Height) Validate(string path)
		{
			byte[] array = File.ReadAllBytes(path);
			if (array.Length < 8 || !array.AsSpan(0, 8).SequenceEqual(Signature))
			{
				throw new InvalidDataException("not a PNG");
			}
			int i = 8;
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			int num5 = 0;
			bool flag = false;
			using MemoryStream memoryStream = new MemoryStream();
			int num6;
			for (; i + 12 <= array.Length; i += 12 + num6)
			{
				num6 = (int)BinaryPrimitives.ReadUInt32BigEndian(array.AsSpan(i));
				if (num6 < 0 || i + 12 + num6 > array.Length)
				{
					throw new InvalidDataException("truncated chunk");
				}
				Span<byte> span = array.AsSpan(i + 4, 4 + num6);
				uint num7 = BinaryPrimitives.ReadUInt32BigEndian(array.AsSpan(i + 8 + num6));
				if (Crc32(span) != num7)
				{
					throw new InvalidDataException("bad chunk CRC");
				}
				string text = Encoding.ASCII.GetString(span.Slice(0, 4));
				Span<byte> span2 = span.Slice(4);
				switch (text)
				{
				case "IHDR":
					num = (int)BinaryPrimitives.ReadUInt32BigEndian(span2);
					num2 = (int)BinaryPrimitives.ReadUInt32BigEndian(span2.Slice(4));
					num3 = span2[8];
					num4 = span2[9];
					num5 = span2[12];
					continue;
				case "IDAT":
					memoryStream.Write(span2);
					continue;
				case "IEND":
					break;
				default:
					continue;
				}
				flag = true;
				break;
			}
			if (!flag)
			{
				throw new InvalidDataException("missing IEND");
			}
			if (num <= 0 || num2 <= 0)
			{
				throw new InvalidDataException("empty image");
			}
			if (memoryStream.Length < 2)
			{
				throw new InvalidDataException("empty image data");
			}
			memoryStream.Position = 2L;
			using DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Decompress);
			using MemoryStream memoryStream2 = new MemoryStream();
			deflateStream.CopyTo(memoryStream2);
			if (num5 == 0)
			{
				long num8 = ((long)num * (long)(num4 switch
				{
					0 => 1, 
					2 => 3, 
					3 => 1, 
					4 => 2, 
					6 => 4, 
					_ => throw new InvalidDataException($"color type {num4}"), 
				}) * num3 + 7) / 8;
				long num9 = num2 * (1 + num8);
				if (memoryStream2.Length != num9)
				{
					throw new InvalidDataException($"pixel data {memoryStream2.Length} bytes, expected {num9}");
				}
			}
			return (Width: num, Height: num2);
		}
	}
	public sealed class SteamGame
	{
		public string InstallDir = "";

		public string BuildId = "";
	}
	public static class SteamLocator
	{
		public static IEnumerable<string> SteamRoots()
		{
			List<string> list = new List<string>();
			(bool, string, string)[] array = new(bool, string, string)[2]
			{
				(false, "Software\\Valve\\Steam", "SteamPath"),
				(true, "SOFTWARE\\WOW6432Node\\Valve\\Steam", "InstallPath")
			};
			for (int i = 0; i < array.Length; i++)
			{
				(bool, string, string) tuple = array[i];
				bool item = tuple.Item1;
				string item2 = tuple.Item2;
				string item3 = tuple.Item3;
				string text = Win.RegistryString(item, item2, item3);
				if (!string.IsNullOrEmpty(text))
				{
					list.Add(text);
				}
			}
			list.Add("C:\\Program Files (x86)\\Steam");
			return list.Select((string p) => Path.GetFullPath(p.Replace('/', '\\'))).Where(Directory.Exists).Distinct<string>(StringComparer.OrdinalIgnoreCase);
		}

		public static IEnumerable<string> Libraries()
		{
			List<string> list = new List<string>();
			foreach (string item in SteamRoots())
			{
				list.Add(item);
				string path = Path.Combine(item, "steamapps", "libraryfolders.vdf");
				if (!File.Exists(path))
				{
					continue;
				}
				foreach (Match item2 in Regex.Matches(File.ReadAllText(path), "\"path\"\\s+\"((?:[^\"\\\\]|\\\\.)*)\""))
				{
					list.Add(item2.Groups[1].Value.Replace("\\\\", "\\"));
				}
			}
			return list.Where(Directory.Exists).Select(Path.GetFullPath).Distinct<string>(StringComparer.OrdinalIgnoreCase);
		}

		public static SteamGame Find(int appId)
		{
			foreach (string item in Libraries())
			{
				string path = Path.Combine(item, "steamapps", $"appmanifest_{appId}.acf");
				if (!File.Exists(path))
				{
					continue;
				}
				string acf = File.ReadAllText(path);
				string text = Field(acf, "installdir");
				if (!(text == ""))
				{
					string text2 = Path.Combine(item, "steamapps", "common", text);
					if (Directory.Exists(text2))
					{
						return new SteamGame
						{
							InstallDir = text2,
							BuildId = Field(acf, "buildid")
						};
					}
				}
			}
			return null;
		}

		private static string Field(string acf, string name)
		{
			Match match = Regex.Match(acf, "\"" + name + "\"\\s+\"([^\"]*)\"");
			if (!match.Success)
			{
				return "";
			}
			return match.Groups[1].Value;
		}
	}
	public sealed class TresFile
	{
		public string ResourceType = "";

		public readonly Dictionary<int, (string Path, string Type)> ExtResources = new Dictionary<int, (string, string)>();

		public readonly Dictionary<string, string> Props = new Dictionary<string, string>(StringComparer.Ordinal);

		private static readonly Regex HeaderRx = new Regex("^\\[gd_resource\\s+type=\"([^\"]+)\"");

		private static readonly Regex ExtRx = new Regex("^\\[ext_resource\\s+path=\"([^\"]+)\"\\s+type=\"([^\"]+)\"\\s+id=(\\d+)\\]");

		private static readonly Regex ExtRefRx = new Regex("^ExtResource\\(\\s*(\\d+)\\s*\\)$");

		public static TresFile Parse(string text)
		{
			TresFile tresFile = new TresFile();
			bool flag = false;
			int num = 0;
			string[] array = text.Replace("\r\n", "\n").Split('\n');
			while (num < array.Length)
			{
				string text2 = array[num];
				string text3 = text2.Trim();
				num++;
				if (text3.Length == 0 || text3.StartsWith(";"))
				{
					continue;
				}
				if (text3.StartsWith("["))
				{
					Match match;
					if ((match = HeaderRx.Match(text3)).Success)
					{
						tresFile.ResourceType = match.Groups[1].Value;
					}
					else if ((match = ExtRx.Match(text3)).Success)
					{
						tresFile.ExtResources[int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture)] = (match.Groups[1].Value, match.Groups[2].Value);
					}
					flag = text3 == "[resource]";
				}
				else
				{
					if (!flag)
					{
						continue;
					}
					int num2 = text2.IndexOf(" = ", StringComparison.Ordinal);
					if (num2 >= 0)
					{
						string key = text2.Substring(0, num2).Trim();
						StringBuilder stringBuilder = new StringBuilder(text2.Substring(num2 + 3));
						while (!IsComplete(stringBuilder.ToString()) && num < array.Length)
						{
							stringBuilder.Append('\n').Append(array[num++]);
						}
						tresFile.Props[key] = stringBuilder.ToString().Trim();
					}
				}
			}
			return tresFile;
		}

		private static bool IsComplete(string v)
		{
			bool flag = false;
			bool flag2 = false;
			int num = 0;
			foreach (char c in v)
			{
				if (flag)
				{
					if (flag2)
					{
						flag2 = false;
						continue;
					}
					switch (c)
					{
					case '\\':
						flag2 = true;
						break;
					case '"':
						flag = false;
						break;
					}
					continue;
				}
				switch (c)
				{
				case '"':
					flag = true;
					break;
				case '(':
				case '[':
				case '{':
					num++;
					break;
				case ')':
				case ']':
				case '}':
					num--;
					break;
				}
			}
			if (!flag)
			{
				return num <= 0;
			}
			return false;
		}

		public bool Has(string key)
		{
			return Props.ContainsKey(key);
		}

		public string Str(string key, string def = "")
		{
			if (!Props.TryGetValue(key, out var value))
			{
				return def;
			}
			if (value.Length >= 2 && value[0] == '"')
			{
				string text = value;
				if (text[text.Length - 1] == '"')
				{
					return Unescape(value.Substring(1, value.Length - 2));
				}
			}
			return value;
		}

		public float Float(string key, float def)
		{
			if (!Props.TryGetValue(key, out var value) || !float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return def;
			}
			return result;
		}

		public int Int(string key, int def)
		{
			if (!Props.TryGetValue(key, out var value) || !double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return def;
			}
			return (int)Math.Round(result);
		}

		public bool Bool(string key, bool def)
		{
			if (!Props.TryGetValue(key, out var value))
			{
				return def;
			}
			return value == "true";
		}

		public string ExtPath(string key)
		{
			if (!Props.TryGetValue(key, out var value))
			{
				return null;
			}
			Match match = ExtRefRx.Match(value);
			if (!match.Success)
			{
				return null;
			}
			if (!ExtResources.TryGetValue(int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture), out (string, string) value2))
			{
				return null;
			}
			return value2.Item1;
		}

		private static string Unescape(string s)
		{
			StringBuilder stringBuilder = new StringBuilder(s.Length);
			for (int i = 0; i < s.Length; i++)
			{
				char c = s[i];
				if (c != '\\' || i + 1 >= s.Length)
				{
					stringBuilder.Append(c);
					continue;
				}
				char c2 = s[++i];
				StringBuilder stringBuilder2 = stringBuilder;
				stringBuilder2.Append(c2 switch
				{
					'n' => '\n', 
					't' => '\t', 
					'"' => '"', 
					'\\' => '\\', 
					_ => c2, 
				});
			}
			return stringBuilder.ToString();
		}
	}
	public static class Win
	{
		private static readonly UIntPtr HKEY_CURRENT_USER = new UIntPtr(2147483649u);

		private static readonly UIntPtr HKEY_LOCAL_MACHINE = new UIntPtr(2147483650u);

		private const uint RRF_RT_REG_SZ = 2u;

		private const uint RRF_RT_REG_EXPAND_SZ = 4u;

		private const uint RRF_NOEXPAND = 268435456u;

		public static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

		[DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
		private static extern int RegGetValueW(UIntPtr hkey, string subKey, string value, uint flags, out uint type, StringBuilder data, ref uint size);

		[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
		private static extern int SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid id, uint flags, IntPtr token, out IntPtr path);

		[DllImport("ole32.dll")]
		private static extern void CoTaskMemFree(IntPtr p);

		public static string RegistryString(bool machine, string subKey, string value)
		{
			if (!IsWindows)
			{
				return null;
			}
			try
			{
				StringBuilder stringBuilder = new StringBuilder(1024);
				uint size = (uint)(stringBuilder.Capacity * 2);
				uint type;
				return (RegGetValueW(machine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, subKey, value, 268435462u, out type, stringBuilder, ref size) == 0) ? Environment.ExpandEnvironmentVariables(stringBuilder.ToString()) : null;
			}
			catch (Exception)
			{
				return null;
			}
		}

		public static string DownloadsFolder()
		{
			string result = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads");
			if (!IsWindows)
			{
				return result;
			}
			try
			{
				if (SHGetKnownFolderPath(new Guid("374DE290-123F-4565-9164-39C4925E467B"), 0u, IntPtr.Zero, out var path) == 0)
				{
					string text = Marshal.PtrToStringUni(path);
					CoTaskMemFree(path);
					if (!string.IsNullOrEmpty(text) && Directory.Exists(text))
					{
						return text;
					}
				}
			}
			catch (Exception)
			{
			}
			return result;
		}

		public static string Sha256(string path)
		{
			using FileStream s = File.OpenRead(path);
			return Sha256(s);
		}

		public static string Sha256(Stream s)
		{
			using SHA256 sHA = SHA256.Create();
			return BitConverter.ToString(sHA.ComputeHash(s)).Replace("-", "").ToLowerInvariant();
		}
	}
}

BepInEx/plugins/WFxHTF/WFxHTF.Runtime.dll

Decompiled a day ago
using System;
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 System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.UI;
using WFxHTF.Core;
using WFxHTF.Data;
using WFxHTF.Extraction;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("WFxHTF")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+166b9aae0d28865ba70c4894450aeb4db4b21aeb")]
[assembly: AssemblyProduct("WFxHTF.Runtime")]
[assembly: AssemblyTitle("WFxHTF.Runtime")]
[assembly: AssemblyVersion("0.1.0.0")]
namespace WFxHTF.Runtime;

public static class ConfigBinder
{
	public static int Bind(ConfigFile file, object target, string section = null)
	{
		//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c6: Expected O, but got Unknown
		section = section ?? target.GetType().Name;
		int num = 0;
		FieldInfo[] fields = target.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public);
		foreach (FieldInfo fieldInfo in fields)
		{
			if (fieldInfo.IsInitOnly)
			{
				continue;
			}
			Type fieldType = fieldInfo.FieldType;
			object value = fieldInfo.GetValue(target);
			if (fieldType == typeof(float))
			{
				fieldInfo.SetValue(target, file.Bind<float>(section, fieldInfo.Name, (float)value, (ConfigDescription)null).Value);
			}
			else if (fieldType == typeof(int))
			{
				fieldInfo.SetValue(target, file.Bind<int>(section, fieldInfo.Name, (int)value, (ConfigDescription)null).Value);
			}
			else if (fieldType == typeof(bool))
			{
				fieldInfo.SetValue(target, file.Bind<bool>(section, fieldInfo.Name, (bool)value, (ConfigDescription)null).Value);
			}
			else if (fieldType == typeof(double))
			{
				fieldInfo.SetValue(target, file.Bind<double>(section, fieldInfo.Name, (double)value, (ConfigDescription)null).Value);
			}
			else if (fieldType == typeof(string))
			{
				fieldInfo.SetValue(target, file.Bind<string>(section, fieldInfo.Name, ((string)value) ?? "", (ConfigDescription)null).Value);
			}
			else if (fieldType.IsEnum)
			{
				ConfigEntry<string> val = file.Bind<string>(section, fieldInfo.Name, value.ToString(), new ConfigDescription("One of: " + string.Join(", ", Enum.GetNames(fieldType)), (AcceptableValueBase)null, Array.Empty<object>()));
				fieldInfo.SetValue(target, Enum.IsDefined(fieldType, val.Value) ? Enum.Parse(fieldType, val.Value) : value);
			}
			else if (fieldType == typeof(int[]))
			{
				fieldInfo.SetValue(target, ParseArray(file.Bind<string>(section, fieldInfo.Name, Join((int[])value), (ConfigDescription)null).Value, int.Parse, (int[])value));
			}
			else
			{
				if (!(fieldType == typeof(float[])))
				{
					continue;
				}
				fieldInfo.SetValue(target, ParseArray(file.Bind<string>(section, fieldInfo.Name, Join((float[])value), (ConfigDescription)null).Value, (string s) => float.Parse(s, CultureInfo.InvariantCulture), (float[])value));
			}
			num++;
		}
		return num;
	}

	private static string Join<T>(IEnumerable<T> xs)
	{
		return string.Join(", ", xs.Select((T x) => Convert.ToString(x, CultureInfo.InvariantCulture)));
	}

	private static T[] ParseArray<T>(string text, Func<string, T> parse, T[] fallback)
	{
		try
		{
			return (from s in text.Split(',')
				select parse(s.Trim())).ToArray();
		}
		catch
		{
			return fallback;
		}
	}
}
public sealed class DebugConsole : MonoBehaviour
{
	public KeyCode ToggleKey = (KeyCode)289;

	private readonly Dictionary<string, (string Help, Func<string[], string> Run)> _commands = new Dictionary<string, (string, Func<string[], string>)>();

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

	private string _input = "";

	private Vector2 _scroll;

	private Texture2D _bg;

	private CursorLockMode _prevLock;

	private bool _prevVisible;

	private int _lastToggleFrame = -100;

	public bool DebugAllowed;

	private const string DebugOff = "that's a cheat/test command: set [Debug] Commands = true in BepInEx\\config\\com.wfxhtf.mod.cfg to use it";

	public bool Open { get; private set; }

	public static bool IsDebug(string help)
	{
		return help?.StartsWith("DEBUG") ?? false;
	}

	public void Register(string name, string help, Func<string[], string> run)
	{
		_commands[name] = (help, IsDebug(help) ? ((Func<string[], string>)((string[] a) => (!DebugAllowed) ? "that's a cheat/test command: set [Debug] Commands = true in BepInEx\\config\\com.wfxhtf.mod.cfg to use it" : run(a))) : run);
	}

	public void Print(string line)
	{
		_log.Add(line);
		if (_log.Count > 200)
		{
			_log.RemoveAt(0);
		}
		_scroll.y = float.MaxValue;
	}

	private void Awake()
	{
		Register("help", "list commands", (string[] _) => string.Join("\n", from c in _commands
			where DebugAllowed || !IsDebug(c.Value.Help)
			orderby c.Key
			select $"{c.Key,-12} {c.Value.Help}"));
	}

	private void Update()
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		if (Keys.Down(ToggleKey))
		{
			Toggle();
		}
		if (Open)
		{
			Cursor.lockState = (CursorLockMode)0;
			Cursor.visible = true;
		}
	}

	private void Toggle()
	{
		if (Time.frameCount - _lastToggleFrame >= 5)
		{
			_lastToggleFrame = Time.frameCount;
			SetOpen(!Open);
		}
	}

	private void SetOpen(bool open)
	{
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//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)
		if (open != Open)
		{
			Open = open;
			if (open)
			{
				_prevLock = Cursor.lockState;
				_prevVisible = Cursor.visible;
			}
			else
			{
				GameHooks.ReleaseCursor(_prevLock, _prevVisible);
			}
		}
	}

	private void OnGUI()
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Invalid comparison between Unknown and I4
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
		//IL_012a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0136: Unknown result type (might be due to invalid IL or missing references)
		//IL_0150: Unknown result type (might be due to invalid IL or missing references)
		//IL_0155: Unknown result type (might be due to invalid IL or missing references)
		//IL_0085: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Expected O, but got Unknown
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Invalid comparison between Unknown and I4
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Invalid comparison between Unknown and I4
		//IL_019f: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a5: Invalid comparison between Unknown and I4
		//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01af: Invalid comparison between Unknown and I4
		//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bc: Invalid comparison between Unknown and I4
		if (!Open)
		{
			return;
		}
		Event current = Event.current;
		if ((int)current.type == 4 && (current.keyCode == ToggleKey || (int)current.keyCode == 27))
		{
			current.Use();
			if ((int)current.keyCode == 27)
			{
				_lastToggleFrame = Time.frameCount;
				SetOpen(open: false);
			}
			else
			{
				Toggle();
			}
			return;
		}
		float num = Mathf.Min(900, Screen.width - 40);
		float num2 = 320f;
		if ((Object)(object)_bg == (Object)null)
		{
			_bg = new Texture2D(1, 1);
			_bg.SetPixel(0, 0, new Color(0.04f, 0.05f, 0.07f, 0.95f));
			_bg.Apply();
		}
		GUI.DrawTexture(new Rect(20f, 20f, num, num2), (Texture)(object)_bg);
		GUI.Box(new Rect(20f, 20f, num, num2), "WFxHTF console  (" + ((object)Unsafe.As<KeyCode, KeyCode>(ref ToggleKey)/*cast due to .constrained prefix*/).ToString() + " or Esc to close, 'help' for commands)");
		GUILayout.BeginArea(new Rect(30f, 45f, num - 20f, num2 - 35f));
		_scroll = GUILayout.BeginScrollView(_scroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(num2 - 75f) });
		foreach (string item in _log)
		{
			GUILayout.Label(item, Array.Empty<GUILayoutOption>());
		}
		GUILayout.EndScrollView();
		Event current2 = Event.current;
		bool num3 = (int)current2.type == 4 && ((int)current2.keyCode == 13 || (int)current2.keyCode == 271);
		GUI.SetNextControlName("wf_input");
		_input = GUILayout.TextField(_input ?? "", Array.Empty<GUILayoutOption>());
		GUI.FocusControl("wf_input");
		GUILayout.EndArea();
		if (num3 && !string.IsNullOrWhiteSpace(_input))
		{
			Execute(_input);
			_input = "";
			current2.Use();
		}
	}

	public void Execute(string line)
	{
		Print("> " + line);
		string[] array = line.Trim().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
		if (array.Length == 0)
		{
			return;
		}
		if (!_commands.TryGetValue(array[0].ToLowerInvariant(), out (string, Func<string[], string>) value))
		{
			Print("unknown command; try 'help'");
			return;
		}
		try
		{
			Print(value.Item2(array.Skip(1).ToArray()) ?? "ok");
		}
		catch (Exception ex)
		{
			Print("error: " + ex.Message);
			Plugin.Log.LogError((object)$"console '{line}': {ex}");
		}
	}
}
public sealed class DebugTank : MonoBehaviour
{
	private SpriteCache _cache;

	private DebugConsole _console;

	public Aquarium Tank { get; private set; }

	public TankView View { get; private set; }

	public static DebugTank Spawn(SpriteCache cache, AquariumConfig cfg, DebugConsole console)
	{
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0072: Unknown result type (might be due to invalid IL or missing references)
		//IL_0077: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fd: Expected O, but got Unknown
		Camera val = UnityUtil.ViewCamera();
		if ((Object)(object)val == (Object)null)
		{
			return null;
		}
		Vector3 val2 = Vector3.ProjectOnPlane(((Component)val).transform.forward, Vector3.up);
		Vector3 val3 = ((Vector3)(ref val2)).normalized;
		if (((Vector3)(ref val3)).sqrMagnitude < 0.01f)
		{
			val3 = Vector3.forward;
		}
		Vector3 val4 = ((Component)val).transform.position + val3 * 4f;
		RaycastHit val5 = default(RaycastHit);
		val4.y = (Physics.Raycast(val4 + Vector3.up * 3f, Vector3.down, ref val5, 20f, -1, (QueryTriggerInteraction)1) ? ((RaycastHit)(ref val5)).point.y : (((Component)val).transform.position.y - 1.6f));
		TankView tankView = TankView.Create(cache, val4, Quaternion.LookRotation(val3, Vector3.up));
		DebugTank debugTank = ((Component)tankView).gameObject.AddComponent<DebugTank>();
		debugTank._cache = cache;
		debugTank._console = console;
		debugTank.View = tankView;
		debugTank.Tank = new Aquarium(cfg, (AquariumState)null);
		return debugTank;
	}

	private void Update()
	{
		Handle(Tank.Advance((double)Time.deltaTime / 60.0 / (double)Tank.Config.TankDayRealMinutes, true));
		View.Sync(Tank);
	}

	public int Handle(List<AqEvent> events)
	{
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0051: Invalid comparison between Unknown and I4
		foreach (AqEvent @event in events)
		{
			_console?.Print(string.Format("[tank] {0} {1} {2}{3}", @event.Kind, @event.SpeciesId, @event.Reason, ((int)@event.Kind != 2) ? "" : (@event.Defeated ? " (defeated)" : " (timed out)")));
		}
		View.OnEvents(events);
		return events.Count;
	}

	public string Add(FishDef def, Quality q)
	{
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		float weightKgMean = def.Htf.WeightKgMean;
		int num = Mathf.Max(1, Mathf.RoundToInt(20f * def.Htf.RelativeWorth));
		long num2 = default(long);
		string text = default(string);
		if (!Tank.TryDeposit(def.Id, weightKgMean, q, Identity.SteamId, Identity.Name, num, ref num2, ref text, false, false, 0f, 1f, 1f))
		{
			return "refused: " + text;
		}
		return $"deposited {def.Name} ({q}) as #{num2}, value {num}";
	}

	public string Dump()
	{
		//IL_0158: Unknown result type (might be due to invalid IL or missing references)
		AquariumState state = Tank.State;
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.AppendLine($"seq {state.Seq}  fish {state.Fish.Count}/{Tank.Capacity}  tray {state.TrayCoins:0.00}  zen {state.Zen}  hopper {state.HopperFood}");
		stringBuilder.AppendLine((state.Attack != null) ? $"ATTACK: {state.Attack.ElapsedDays:0.00}/{Tank.Config.AttackTimeoutDays} days, threat {state.Attack.ThreatHp}/{state.Attack.ThreatMaxHp}" : $"next attack in {state.NextAttackInDays:0.00} days");
		foreach (AqFish item in state.Fish)
		{
			stringBuilder.AppendLine(string.Format("  #{0} {1,-20} {2,-10} hunger {3:0.00} health {4:0.00} value {5}{6}", item.Uid, GameHooks.SpeciesName(_cache, item.SpeciesId), item.Quality, item.Hunger, item.Health, item.Value, (item.StarvedDays > 0f) ? $" STARVING {item.StarvedDays:0.00}d" : ""));
		}
		stringBuilder.Append("hash " + Tank.Hash());
		return stringBuilder.ToString();
	}
}
public sealed class FirstRunSetup : MonoBehaviour
{
	public bool Visible;

	private Thread _worker;

	private volatile bool _workerDone;

	private Exception _workerError;

	private readonly object _gate = new object();

	private readonly Queue<string> _incoming = new Queue<string>();

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

	private int _pct;

	private string _status = "";

	private string _error;

	private bool _finished;

	private float _closeAt = -1f;

	private GUIStyle _wrap;

	public const string GdrePage = "https://github.com/GDRETools/gdsdecomp/releases/tag/v2.7.4";

	public const string GdreZip = "GDRE_tools-v2.7.4-windows.zip";

	private float _gdreCheckAt;

	private bool _gdreReady;

	public bool Running => _worker != null;

	private static string ToolsDir => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "WFxHTF", "tools");

	private static string DownloadsDir => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads");

	private static bool GdreLooksReady()
	{
		try
		{
			if (Directory.Exists(ToolsDir) && Directory.GetFiles(ToolsDir, "gdre_tools.exe", SearchOption.AllDirectories).Length != 0)
			{
				return true;
			}
			return Directory.Exists(DownloadsDir) && Directory.GetFiles(DownloadsDir, "GDRE_tools-v2.7.4-windows*.zip").Length != 0;
		}
		catch (Exception)
		{
			return false;
		}
	}

	public string Run()
	{
		if (_worker != null)
		{
			return "the setup is already running";
		}
		_error = null;
		_workerError = null;
		_workerDone = false;
		_finished = false;
		_pct = 0;
		_status = "Starting";
		_closeAt = -1f;
		_tail.Clear();
		_worker = new Thread((ThreadStart)delegate
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			try
			{
				Extraction.Run(new ExtractOptions(), (Action<string>)Enqueue, (Action<int, string>)delegate(int pct, string text)
				{
					Enqueue($"@progress {pct} {text}");
				});
			}
			catch (Exception workerError)
			{
				_workerError = workerError;
			}
			finally
			{
				_workerDone = true;
			}
		})
		{
			IsBackground = true,
			Name = "WFxHTF WEBFISHING setup"
		};
		_worker.Start();
		Plugin.Log.LogInfo((object)"setup: reading the WEBFISHING fish");
		return "setting up the WEBFISHING fish";
	}

	private void Enqueue(string line)
	{
		if (line == null)
		{
			return;
		}
		lock (_gate)
		{
			_incoming.Enqueue(line);
		}
	}

	private void Update()
	{
		while (true)
		{
			string line;
			lock (_gate)
			{
				if (_incoming.Count == 0)
				{
					break;
				}
				line = _incoming.Dequeue();
				goto IL_0038;
			}
			IL_0038:
			Handle(line);
		}
		if (_worker != null && _workerDone)
		{
			Finish();
		}
		if (_worker != null && !Visible)
		{
			Hud.ShowSmall("wfsetup", $"Setting up WEBFISHING fish: {_pct}%");
		}
		else if (_finished && !Visible && _closeAt > 0f)
		{
			Hud.ShowSmall("wfsetup", _status);
		}
		else
		{
			Hud.Hide("wfsetup");
		}
		if (_closeAt > 0f && Time.unscaledTime >= _closeAt)
		{
			Visible = false;
			_closeAt = -1f;
			Hud.Hide("wfsetup");
		}
	}

	private void Handle(string line)
	{
		if (line.StartsWith("@progress "))
		{
			string[] array = line.Split(new char[1] { ' ' }, 3);
			if (array.Length >= 2 && int.TryParse(array[1], out var result))
			{
				_pct = Mathf.Clamp(result, 0, 100);
			}
			if (array.Length == 3)
			{
				_status = array[2];
			}
		}
		else if (!line.StartsWith("  skip "))
		{
			_tail.Add(line);
			if (_tail.Count > 40)
			{
				_tail.RemoveAt(0);
			}
		}
	}

	private void Finish()
	{
		_worker = null;
		Update();
		foreach (string item in _tail)
		{
			Plugin.Log.LogInfo((object)("setup> " + item));
		}
		if (_workerError == null && (Object)(object)Plugin.Instance != (Object)null && Plugin.Instance.ReloadCache())
		{
			_finished = true;
			_pct = 100;
			_status = $"Done: {Plugin.Instance.Cache.Defs.Count} WEBFISHING fish are ready.";
			_closeAt = Time.unscaledTime + 5f;
		}
		else
		{
			_error = ((_workerError != null) ? _workerError.Message : (Plugin.Instance?.Cache.Error ?? "the fish couldn't be loaded"));
			Plugin.Log.LogWarning((object)("setup failed: " + ((_workerError != null) ? _workerError.ToString() : _error)));
		}
	}

	private void OnGUI()
	{
		//IL_0065: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Expected O, but got Unknown
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0164: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
		//IL_02df: Unknown result type (might be due to invalid IL or missing references)
		//IL_0273: Unknown result type (might be due to invalid IL or missing references)
		//IL_0294: Unknown result type (might be due to invalid IL or missing references)
		//IL_0309: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_033e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0326: Unknown result type (might be due to invalid IL or missing references)
		if (!Visible)
		{
			return;
		}
		if (_wrap == null)
		{
			_wrap = new GUIStyle(GUI.skin.label)
			{
				wordWrap = true,
				richText = true
			};
		}
		float num = 600f;
		float num2 = 230f;
		Rect val = default(Rect);
		((Rect)(ref val))..ctor(((float)Screen.width - num) / 2f, ((float)Screen.height - num2) / 2f, num, num2);
		GUI.Box(val, "How to Webfish: WEBFISHING fish");
		Rect val2 = default(Rect);
		((Rect)(ref val2))..ctor(((Rect)(ref val)).x + 20f, ((Rect)(ref val)).y + 30f, num - 40f, 120f);
		Rect val3 = default(Rect);
		((Rect)(ref val3))..ctor(((Rect)(ref val)).x + 20f, ((Rect)(ref val)).y + num2 - 48f, 220f, 32f);
		Rect val4 = default(Rect);
		((Rect)(ref val4))..ctor(((Rect)(ref val)).x + num - 160f, ((Rect)(ref val)).y + num2 - 48f, 140f, 32f);
		if (_worker != null || _finished)
		{
			GUI.Label(val2, _status + (_finished ? "" : "\n\nThis takes about a minute. You can keep playing; the fish appear when it's done."), _wrap);
			Rect val5 = default(Rect);
			((Rect)(ref val5))..ctor(((Rect)(ref val)).x + 20f, ((Rect)(ref val)).y + 150f, num - 40f, 18f);
			GUI.Box(val5, "");
			Rect val6 = new Rect(((Rect)(ref val5)).x + 2f, ((Rect)(ref val5)).y + 2f, (((Rect)(ref val5)).width - 4f) * (float)_pct / 100f, ((Rect)(ref val5)).height - 4f);
			Color color = GUI.color;
			GUI.color = new Color(0.45f, 0.8f, 1f);
			GUI.DrawTexture(val6, (Texture)(object)Texture2D.whiteTexture);
			GUI.color = color;
			if (GUI.Button(val4, _finished ? "Close" : "Hide"))
			{
				Visible = false;
			}
			return;
		}
		Rect val7 = default(Rect);
		((Rect)(ref val7))..ctor(((Rect)(ref val)).x + (num - 220f) / 2f, ((Rect)(ref val)).y + num2 - 48f, 220f, 32f);
		if (Time.unscaledTime >= _gdreCheckAt)
		{
			_gdreReady = GdreLooksReady();
			_gdreCheckAt = Time.unscaledTime + 1f;
		}
		if (_error != null)
		{
			GUI.Label(val2, "<b>The setup didn't finish:</b> " + _error + "\n\nWEBFISHING must be installed in Steam. Details are in BepInEx\\LogOutput.log.", _wrap);
			if (GUI.Button(val3, "Try again"))
			{
				Run();
			}
			if (!_gdreReady && GUI.Button(val7, "GDRE download page"))
			{
				Application.OpenURL("https://github.com/GDRETools/gdsdecomp/releases/tag/v2.7.4");
			}
			if (GUI.Button(val4, "Close"))
			{
				Visible = false;
			}
		}
		else
		{
			GUI.Label(val2, "How to Webfish shows WEBFISHING's fish by reading them from <b>your own WEBFISHING</b> (it must be installed in Steam). This happens once, takes about a minute, and keeps them in %LOCALAPPDATA%\\WFxHTF.\n\n" + (_gdreReady ? "GDRE Tools found. Press <b>Set up now</b>." : "It needs GDRE Tools (free, open source). Press <b>GDRE download page</b>, download <b>GDRE_tools-v2.7.4-windows.zip</b> into your Downloads folder, then press <b>Set up now</b>."), _wrap);
			if (GUI.Button(val3, "Set up now"))
			{
				Run();
			}
			if (!_gdreReady && GUI.Button(val7, "GDRE download page"))
			{
				Application.OpenURL("https://github.com/GDRETools/gdsdecomp/releases/tag/v2.7.4");
			}
			if (GUI.Button(val4, "Later"))
			{
				Visible = false;
			}
		}
	}
}
public static class GameHooks
{
	public static Func<bool> NativeJournalOpen;

	public static string NativeJournalHint;

	public static Action RestoreCursor;

	public static Func<string, bool, GameObject> NativeFishModel;

	public static Func<Light> MainLight;

	public static Func<ParticleSystem> CloudTemplate;

	public static Func<Vector3, float> WaterHeightAt;

	public static Func<string, string> NativeFishName;

	public static string SpeciesName(SpriteCache cache, string speciesId)
	{
		FishDef val = cache?.Find(speciesId);
		if (val != null)
		{
			return val.Name;
		}
		try
		{
			return NativeFishName?.Invoke(speciesId) ?? speciesId;
		}
		catch
		{
			return speciesId;
		}
	}

	public static void ReleaseCursor(CursorLockMode prevLock, bool prevVisible)
	{
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		Action restoreCursor = RestoreCursor;
		if (restoreCursor != null)
		{
			try
			{
				restoreCursor();
				return;
			}
			catch
			{
			}
		}
		Cursor.lockState = prevLock;
		Cursor.visible = prevVisible;
	}
}
public sealed class GlowOrb : MonoBehaviour
{
	private static Mesh _sphere;

	private float _phase;

	private Transform _core;

	public static GlowOrb Create(string name, float radius, Color colour)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Expected O, but got Unknown
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: 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_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject(name);
		GlowOrb glowOrb = val.AddComponent<GlowOrb>();
		glowOrb._phase = Random.value * 10f;
		glowOrb._core = Ball(val.transform, "Core", radius, SpriteMaterial.CreatePlain(colour * 1.8f));
		Ball(glowOrb._core, "Halo", 1.7f, SpriteMaterial.CreatePlain(colour * 0.35f, PlainBlend.Additive));
		Sparkle.AttachBox(glowOrb._core, Vector3.zero, Vector3.one * 2.6f, radius * 4f);
		return glowOrb;
	}

	private static Transform Ball(Transform parent, string name, float scale, Material mat)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject(name);
		val.transform.SetParent(parent, false);
		val.transform.localScale = Vector3.one * scale * 2f;
		val.AddComponent<MeshFilter>().sharedMesh = Sphere();
		MeshRenderer obj = val.AddComponent<MeshRenderer>();
		((Renderer)obj).sharedMaterial = mat;
		((Renderer)obj).shadowCastingMode = (ShadowCastingMode)0;
		((Renderer)obj).receiveShadows = false;
		return val.transform;
	}

	private static Mesh Sphere()
	{
		if ((Object)(object)_sphere != (Object)null)
		{
			return _sphere;
		}
		GameObject obj = GameObject.CreatePrimitive((PrimitiveType)0);
		_sphere = obj.GetComponent<MeshFilter>().sharedMesh;
		Object.DestroyImmediate((Object)(object)obj);
		return _sphere;
	}

	private void Update()
	{
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)_core == (Object)null))
		{
			float num = Time.time + _phase;
			_core.localPosition = new Vector3(0f, Mathf.Sin(num * 1.7f) * 0.05f, 0f);
			_core.localRotation = Quaternion.Euler(0f, num * 40f, 0f);
		}
	}
}
public sealed class ShopStand : MonoBehaviour
{
	public Transform Display { get; private set; }

	public Vector3 Size { get; private set; }

	public GameObject[] Outline { get; private set; }

	public static ShopStand Create(string name, Vector3 groundPos, Quaternion rot, GameObject display, float displayHeight = 1.05f)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Expected O, but got Unknown
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: 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_0092: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0105: Unknown result type (might be due to invalid IL or missing references)
		//IL_0124: Unknown result type (might be due to invalid IL or missing references)
		//IL_0187: Unknown result type (might be due to invalid IL or missing references)
		//IL_0161: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject(name);
		val.transform.SetPositionAndRotation(groundPos, rot);
		ShopStand shopStand = val.AddComponent<ShopStand>();
		GameObject val2 = GameObject.CreatePrimitive((PrimitiveType)3);
		((Object)val2).name = "Post";
		val2.transform.SetParent(val.transform, false);
		val2.transform.localScale = new Vector3(0.45f, 0.9f, 0.45f);
		val2.transform.localPosition = new Vector3(0f, 0.45f, 0f);
		((Renderer)val2.GetComponent<MeshRenderer>()).sharedMaterial = SpriteMaterial.CreatePlain(new Color(0.42f, 0.3f, 0.2f));
		GameObject val3 = GameObject.CreatePrimitive((PrimitiveType)3);
		((Object)val3).name = "Top";
		Object.DestroyImmediate((Object)(object)val3.GetComponent<Collider>());
		val3.transform.SetParent(val.transform, false);
		val3.transform.localScale = new Vector3(0.6f, 0.06f, 0.6f);
		val3.transform.localPosition = new Vector3(0f, 0.93f, 0f);
		((Renderer)val3.GetComponent<MeshRenderer>()).sharedMaterial = SpriteMaterial.CreatePlain(new Color(0.3f, 0.21f, 0.14f));
		if ((Object)(object)display != (Object)null)
		{
			display.transform.SetParent(val.transform, false);
			display.transform.localPosition = new Vector3(0f, displayHeight, 0f);
			shopStand.Display = display.transform;
		}
		shopStand.Size = new Vector3(0.7f, 1.5f, 0.7f);
		shopStand.Outline = (GameObject[])(object)new GameObject[2] { val2, val3 };
		return shopStand;
	}

	private void Update()
	{
		if ((Object)(object)Display != (Object)null)
		{
			Display.Rotate(0f, 35f * Time.deltaTime, 0f, (Space)0);
		}
	}
}
public sealed class Hud : MonoBehaviour
{
	private static readonly SortedDictionary<string, string> Lines = new SortedDictionary<string, string>();

	private static readonly SortedDictionary<string, string> Small = new SortedDictionary<string, string>();

	private GUIStyle _style;

	private GUIStyle _smallStyle;

	public static void Show(string key, string richText)
	{
		Small.Remove(key);
		Lines[key] = richText;
	}

	public static void ShowSmall(string key, string richText)
	{
		Lines.Remove(key);
		Small[key] = richText;
	}

	public static void Hide(string key)
	{
		Lines.Remove(key);
		Small.Remove(key);
	}

	private void OnGUI()
	{
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Expected O, but got Unknown
		//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
		//IL_0101: Unknown result type (might be due to invalid IL or missing references)
		//IL_010d: Expected O, but got Unknown
		//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_0205: Unknown result type (might be due to invalid IL or missing references)
		//IL_0220: Unknown result type (might be due to invalid IL or missing references)
		if (Lines.Count > 0)
		{
			if (_style == null)
			{
				_style = new GUIStyle(GUI.skin.box)
				{
					alignment = (TextAnchor)4,
					fontSize = 20,
					richText = true,
					wordWrap = true
				};
			}
			float num = 760f;
			float num2 = 34 * Lines.Count + 12;
			GUI.Box(new Rect(((float)Screen.width - num) / 2f, (float)Screen.height * 0.78f - num2, num, num2), string.Join("\n", Lines.Values), _style);
		}
		if (Small.Count > 0)
		{
			int num3 = Mathf.Max(12, Mathf.RoundToInt((float)Screen.height / 57f));
			if (_smallStyle == null || _smallStyle.fontSize != num3)
			{
				_smallStyle = new GUIStyle(GUI.skin.label)
				{
					alignment = (TextAnchor)7,
					fontSize = num3,
					richText = true
				};
			}
			string text = string.Join("\n", Small.Values);
			float num4 = Screen.height;
			float num5 = (float)Screen.width / 2f + 0.29f * num4;
			float num6 = (float)Screen.width - 0.13f * num4;
			if (num6 - num5 < 8f * (float)num3)
			{
				num5 = (float)Screen.width - 16f * (float)num3;
				num6 = (float)Screen.width - 8f;
			}
			float num7 = (float)num3 * 1.5f * (float)Small.Count + 4f;
			Rect val = default(Rect);
			((Rect)(ref val))..ctor(num5, num4 - 0.022f * num4 - num7, num6 - num5, num7);
			Color color = GUI.color;
			GUI.color = new Color(0f, 0f, 0f, 0.8f);
			GUI.Label(new Rect(((Rect)(ref val)).x + 1f, ((Rect)(ref val)).y + 1f, ((Rect)(ref val)).width, ((Rect)(ref val)).height), StripColour(text), _smallStyle);
			GUI.color = color;
			GUI.Label(val, text, _smallStyle);
		}
	}

	private static string StripColour(string s)
	{
		return Regex.Replace(s, "</?color[^>]*>", "");
	}
}
public static class Identity
{
	private static ulong? _steamId;

	private static string _name;

	public static string SavesDir
	{
		get
		{
			string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "WFxHTF", "saves");
			Directory.CreateDirectory(text);
			return text;
		}
	}

	public static ulong SteamId
	{
		get
		{
			if (_steamId.HasValue && _steamId.Value != 0L)
			{
				return _steamId.Value;
			}
			try
			{
				object obj = Type("Steamworks.SteamUser")?.GetMethod("GetSteamID")?.Invoke(null, null);
				_steamId = ((obj?.GetType().GetField("m_SteamID")?.GetValue(obj) is ulong num) ? num : 0);
			}
			catch
			{
				_steamId = 0uL;
			}
			return _steamId.Value;
		}
	}

	public static string Name
	{
		get
		{
			if (!string.IsNullOrEmpty(_name))
			{
				return _name;
			}
			try
			{
				_name = Type("Steamworks.SteamFriends")?.GetMethod("GetPersonaName")?.Invoke(null, null) as string;
			}
			catch
			{
				_name = null;
			}
			return _name ?? "player";
		}
	}

	private static Type Type(string fullName)
	{
		return (from a in AppDomain.CurrentDomain.GetAssemblies()
			select a.GetType(fullName)).FirstOrDefault((Type t) => t != null);
	}
}
public sealed class JournalService
{
	public Journal Journal { get; private set; }

	public string Path { get; private set; }

	public string Status { get; private set; } = "not loaded";

	public bool CanSave { get; private set; } = true;

	public event Action Changed;

	public void Load()
	{
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		ulong steamId = Identity.SteamId;
		Path = System.IO.Path.Combine(Identity.SavesDir, WorldSidecar.JournalFileName(steamId));
		LoadResult<Journal> val = SidecarStore.Load<Journal>(Path, Sidecars.PlayerJournal(steamId));
		Journal = val.Value;
		CanSave = val.CanSave;
		Status = $"{val.Status} from {val.Source}" + ((val.Problem != null) ? (" (" + val.Problem + ")") : "") + $", {Journal.CaughtCount} species";
		if (val.Problem != null)
		{
			Plugin.Log.LogWarning((object)("journal: " + Status));
		}
		this.Changed?.Invoke();
	}

	public bool RecordLocal(string speciesId, float weightKg, Quality q, string island, string bait, bool shiny = false)
	{
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: 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)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		//IL_0070: Unknown result type (might be due to invalid IL or missing references)
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Expected O, but got Unknown
		if (Journal == null || Journal.OwnerId != Identity.SteamId.ToString())
		{
			Load();
		}
		CatchEvent val = new CatchEvent
		{
			SpeciesId = speciesId,
			WeightKg = weightKg,
			Quality = q,
			HookerId = Identity.SteamId,
			HookerName = Identity.Name,
			KillerId = Identity.SteamId,
			Island = island,
			Bait = bait,
			Shiny = shiny
		};
		bool result = Journal.Record(val, DateTime.UtcNow);
		Save();
		Action action = this.Changed;
		if (action != null)
		{
			action();
			return result;
		}
		return result;
	}

	public void Save()
	{
		if (!CanSave || Journal == null)
		{
			return;
		}
		try
		{
			SidecarStore.Save<Journal>(Path, Journal, Sidecars.PlayerJournal(Identity.SteamId), (TimeSpan?)null);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("journal save failed: " + ex.Message));
		}
	}
}
public sealed class JournalUI : MonoBehaviour
{
	public KeyCode ToggleKey = (KeyCode)106;

	private SpriteCache _cache;

	private JournalService _journal;

	private Font _font;

	private GameObject _root;

	private GameObject _panel;

	private GameObject _opener;

	private float _hintUntil;

	private bool _managedCursor;

	private GUIStyle _hintStyle;

	private Transform _grid;

	private Text _header;

	private Text _detail;

	private RawImage _detailIcon;

	private readonly List<(WfPool? Pool, Image Fill, Text Label)> _bars = new List<(WfPool?, Image, Text)>();

	private WfPool? _filter;

	private FishDef _selected;

	private CursorLockMode _prevLock;

	private bool _prevVisible;

	private static readonly Color Panel = new Color(0.07f, 0.09f, 0.12f, 1f);

	private static readonly Color Cell = new Color(0.16f, 0.19f, 0.24f, 1f);

	private static readonly Color Accent = new Color(0.55f, 0.78f, 1f, 1f);

	private static readonly Color Gold = new Color(0.9f, 0.71f, 0.13f, 1f);

	public bool Open
	{
		get
		{
			if ((Object)(object)_panel != (Object)null)
			{
				return _panel.activeSelf;
			}
			return false;
		}
	}

	private static bool Gated => GameHooks.NativeJournalOpen != null;

	public void Init(SpriteCache cache, JournalService journal)
	{
		ModUi.AddBlocker(() => Open && _managedCursor);
		_cache = cache;
		_journal = journal;
		_journal.Changed += delegate
		{
			if (Open)
			{
				Refresh();
			}
		};
	}

	private static bool Allowed()
	{
		Func<bool> nativeJournalOpen = GameHooks.NativeJournalOpen;
		if (nativeJournalOpen == null)
		{
			return true;
		}
		try
		{
			return nativeJournalOpen();
		}
		catch (Exception ex)
		{
			GameHooks.NativeJournalOpen = null;
			Plugin.Log.LogWarning((object)("journal: native journal check failed, the panel now opens anywhere: " + ex.Message));
			return true;
		}
	}

	private void Update()
	{
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		bool flag = Allowed();
		if (Open && !flag)
		{
			SetOpen(open: false);
		}
		if (Keys.Down(ToggleKey))
		{
			DebugConsole console = Plugin.Instance.Console;
			if (console == null || !console.Open)
			{
				if (Open)
				{
					SetOpen(open: false);
				}
				else if (flag)
				{
					SetOpen(open: true);
				}
				else
				{
					_hintUntil = Time.unscaledTime + 3f;
				}
			}
		}
		if (Open && Keys.Down((KeyCode)27))
		{
			SetOpen(open: false);
		}
		if (Open && _managedCursor)
		{
			Cursor.lockState = (CursorLockMode)0;
			Cursor.visible = true;
		}
		bool flag2 = Gated && flag && !Open && _cache != null && _cache.Loaded;
		if (flag2 && (Object)(object)_root == (Object)null)
		{
			TryBuild();
		}
		if ((Object)(object)_opener != (Object)null && _opener.activeSelf != flag2)
		{
			_opener.SetActive(flag2);
		}
	}

	private void OnGUI()
	{
		//IL_006e: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Expected O, but got Unknown
		if (!(Time.unscaledTime >= _hintUntil) && !string.IsNullOrEmpty(GameHooks.NativeJournalHint))
		{
			if (_hintStyle == null)
			{
				_hintStyle = new GUIStyle(GUI.skin.box)
				{
					alignment = (TextAnchor)4,
					fontSize = 22
				};
			}
			GUI.Box(new Rect((float)Screen.width / 2f - 320f, (float)Screen.height * 0.8f, 640f, 44f), GameHooks.NativeJournalHint, _hintStyle);
		}
	}

	public void SetOpen(bool open)
	{
		//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
		if (open == Open)
		{
			return;
		}
		if (open)
		{
			if (!_cache.Loaded)
			{
				Plugin.Instance.Console?.Print("journal: " + _cache.Error);
			}
			else if (!((Object)(object)_root == (Object)null) || TryBuild())
			{
				if (_journal.Journal == null || _journal.Journal.OwnerId != Identity.SteamId.ToString())
				{
					_journal.Load();
				}
				_managedCursor = !Gated;
				if (_managedCursor)
				{
					_prevLock = Cursor.lockState;
					_prevVisible = Cursor.visible;
				}
				_panel.SetActive(true);
				Refresh();
			}
		}
		else
		{
			_panel.SetActive(false);
			if (_managedCursor)
			{
				GameHooks.ReleaseCursor(_prevLock, _prevVisible);
			}
			_managedCursor = false;
		}
	}

	private bool TryBuild()
	{
		try
		{
			Build();
			return true;
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("journal: building the panel failed: " + ex));
			Plugin.Instance.Console?.Print("journal: panel failed to build (see BepInEx log)");
			if ((Object)(object)_root != (Object)null)
			{
				Object.Destroy((Object)(object)_root);
			}
			_root = (_panel = (_opener = null));
			_bars.Clear();
			return false;
		}
	}

	private void Build()
	{
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Expected O, but got Unknown
		//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_018d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0197: Expected O, but got Unknown
		//IL_028f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0299: Expected O, but got Unknown
		//IL_02c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_039f: Unknown result type (might be due to invalid IL or missing references)
		//IL_03fd: Unknown result type (might be due to invalid IL or missing references)
		//IL_0449: Unknown result type (might be due to invalid IL or missing references)
		//IL_046b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0481: Unknown result type (might be due to invalid IL or missing references)
		//IL_0497: Unknown result type (might be due to invalid IL or missing references)
		//IL_04a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_04aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_04ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_04b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_04d0: Unknown result type (might be due to invalid IL or missing references)
		//IL_04e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_04f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_0501: Expected O, but got Unknown
		//IL_0532: Unknown result type (might be due to invalid IL or missing references)
		//IL_057f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0649: Unknown result type (might be due to invalid IL or missing references)
		//IL_0653: Expected O, but got Unknown
		//IL_0677: Unknown result type (might be due to invalid IL or missing references)
		_font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf") ?? Font.CreateDynamicFontFromOSFont("Arial", 16);
		if ((Object)(object)EventSystem.current == (Object)null)
		{
			Plugin.Log.LogWarning((object)"journal: no EventSystem in scene; buttons won't respond");
		}
		_root = new GameObject("WFxHTF Journal", new Type[4]
		{
			typeof(RectTransform),
			typeof(Canvas),
			typeof(CanvasScaler),
			typeof(GraphicRaycaster)
		});
		Object.DontDestroyOnLoad((Object)(object)_root);
		Canvas component = _root.GetComponent<Canvas>();
		component.renderMode = (RenderMode)0;
		component.sortingOrder = 30000;
		CanvasScaler component2 = _root.GetComponent<CanvasScaler>();
		component2.uiScaleMode = (ScaleMode)1;
		component2.referenceResolution = new Vector2(1920f, 1080f);
		component2.matchWidthOrHeight = 0.5f;
		RectTransform val = Box(_root.transform, "Panel", Panel);
		Stretch(val, 0.08f, 0.08f, 0.92f, 0.92f);
		_panel = ((Component)val).gameObject;
		_header = Label((Transform)(object)val, "Header", "", 34, (TextAnchor)3, (FontStyle)1);
		Place(((Graphic)_header).rectTransform, 0.02f, 0.92f, 0.6f, 0.99f);
		Place(Button((Transform)(object)val, "Close", "Close (" + ((object)Unsafe.As<KeyCode, KeyCode>(ref ToggleKey)/*cast due to .constrained prefix*/).ToString() + ")", (UnityAction)delegate
		{
			SetOpen(open: false);
		}), 0.86f, 0.93f, 0.98f, 0.985f);
		(string, WfPool?)[] array = new(string, WfPool?)[5]
		{
			("All", null),
			("Freshwater", (WfPool)0),
			("Saltwater", (WfPool)1),
			("Rain", (WfPool)2),
			("Misc", (WfPool)3)
		};
		for (int num = 0; num < array.Length; num++)
		{
			(string, WfPool?) tuple = array[num];
			string item = tuple.Item1;
			WfPool? pool = tuple.Item2;
			float num2 = 0.02f + (float)num * 0.13f;
			Place(Button((Transform)(object)val, "Tab " + item, item, (UnityAction)delegate
			{
				_filter = pool;
				Refresh();
			}), num2, 0.85f, num2 + 0.12f, 0.905f);
			RectTransform val2 = Box((Transform)(object)val, "Bar " + item, Cell);
			Place(val2, num2, 0.825f, num2 + 0.12f, 0.84f);
			RectTransform val3 = Box((Transform)(object)val2, "Fill", Accent);
			Place(val3, 0f, 0f, 0f, 1f);
			Text val4 = Label((Transform)(object)val, "Count " + item, "", 18, (TextAnchor)4, (FontStyle)0);
			Place(((Graphic)val4).rectTransform, num2, 0.79f, num2 + 0.12f, 0.825f);
			_bars.Add((pool, ((Component)val3).GetComponent<Image>(), val4));
		}
		RectTransform val5 = Box((Transform)(object)val, "Scroll", new Color(0f, 0f, 0f, 0.25f));
		Place(val5, 0.02f, 0.03f, 0.66f, 0.78f);
		ScrollRect val6 = ((Component)val5).gameObject.AddComponent<ScrollRect>();
		val6.horizontal = false;
		val6.scrollSensitivity = 40f;
		RectTransform val7 = Box((Transform)(object)val5, "Viewport", new Color(0f, 0f, 0f, 0.01f));
		Place(val7, 0f, 0f, 1f, 1f);
		((Component)val7).gameObject.AddComponent<RectMask2D>();
		RectTransform component3 = new GameObject("Grid", new Type[1] { typeof(RectTransform) }).GetComponent<RectTransform>();
		((Transform)component3).SetParent((Transform)(object)val7, false);
		component3.anchorMin = new Vector2(0f, 1f);
		component3.anchorMax = new Vector2(1f, 1f);
		component3.pivot = new Vector2(0.5f, 1f);
		Vector2 offsetMin = (component3.offsetMax = Vector2.zero);
		component3.offsetMin = offsetMin;
		GridLayoutGroup obj = ((Component)component3).gameObject.AddComponent<GridLayoutGroup>();
		obj.cellSize = new Vector2(150f, 150f);
		obj.spacing = new Vector2(10f, 10f);
		((LayoutGroup)obj).padding = new RectOffset(10, 10, 10, 10);
		((Component)component3).gameObject.AddComponent<ContentSizeFitter>().verticalFit = (FitMode)2;
		val6.viewport = val7;
		val6.content = component3;
		_grid = (Transform)(object)component3;
		RectTransform val8 = Box((Transform)(object)val, "Detail", Cell);
		Place(val8, 0.68f, 0.03f, 0.98f, 0.78f);
		_detailIcon = new GameObject("Icon", new Type[2]
		{
			typeof(RectTransform),
			typeof(RawImage)
		}).GetComponent<RawImage>();
		((Transform)((Graphic)_detailIcon).rectTransform).SetParent((Transform)(object)val8, false);
		Place(((Graphic)_detailIcon).rectTransform, 0.1f, 0.55f, 0.9f, 0.97f);
		_detail = Label((Transform)(object)val8, "Text", "Select a fish.", 22, (TextAnchor)0, (FontStyle)0);
		Place(((Graphic)_detail).rectTransform, 0.06f, 0.03f, 0.96f, 0.53f);
		_panel.SetActive(false);
		RectTransform val9 = Button(_root.transform, "Opener", "WEBFISHING Journal (" + ((object)Unsafe.As<KeyCode, KeyCode>(ref ToggleKey)/*cast due to .constrained prefix*/).ToString() + ")", (UnityAction)delegate
		{
			SetOpen(open: true);
		});
		Place(val9, 0.79f, 0.04f, 0.97f, 0.1f);
		((Graphic)((Component)val9).GetComponentInChildren<Text>()).color = Gold;
		_opener = ((Component)val9).gameObject;
		_opener.SetActive(false);
	}

	private void Refresh()
	{
		//IL_0108: Unknown result type (might be due to invalid IL or missing references)
		//IL_012d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0126: Unknown result type (might be due to invalid IL or missing references)
		//IL_0186: Unknown result type (might be due to invalid IL or missing references)
		//IL_018d: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_027a: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b2: Expected O, but got Unknown
		//IL_02d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_032e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0327: Unknown result type (might be due to invalid IL or missing references)
		//IL_0396: Unknown result type (might be due to invalid IL or missing references)
		SortedDictionary<string, JournalEntry> entries = _journal.Journal?.Entries;
		int num = _cache.Defs.Count(Caught);
		_header.text = $"WEBFISHING Journal    {num} / {_cache.Defs.Count}";
		foreach (var bar in _bars)
		{
			List<FishDef> list = _cache.Defs.Where((FishDef d) => !bar.Pool.HasValue || (WfPool?)d.Wf.Pool == bar.Pool).ToList();
			int num2 = list.Count(Caught);
			float num3 = ((list.Count == 0) ? 0f : ((float)num2 / (float)list.Count));
			((Graphic)bar.Fill).rectTransform.anchorMax = new Vector2(num3, 1f);
			((Graphic)bar.Fill).color = ((num3 >= 1f) ? Gold : Accent);
			bar.Label.text = $"{num2} / {list.Count}";
			((Graphic)bar.Label).color = ((_filter == bar.Pool) ? Accent : Color.white);
		}
		for (int num4 = _grid.childCount - 1; num4 >= 0; num4--)
		{
			Object.Destroy((Object)(object)((Component)_grid.GetChild(num4)).gameObject);
		}
		foreach (FishDef item in _cache.Defs.Where((FishDef d) => !_filter.HasValue || (WfPool?)d.Wf.Pool == _filter))
		{
			bool flag = Caught(item);
			RectTransform val = Box(_grid, item.Id, Cell);
			Button obj = ((Component)val).gameObject.AddComponent<Button>();
			FishDef def = item;
			((UnityEvent)obj.onClick).AddListener((UnityAction)delegate
			{
				_selected = def;
				ShowDetail();
			});
			RawImage component = new GameObject("Icon", new Type[2]
			{
				typeof(RectTransform),
				typeof(RawImage)
			}).GetComponent<RawImage>();
			((Transform)((Graphic)component).rectTransform).SetParent((Transform)(object)val, false);
			Place(((Graphic)component).rectTransform, 0.05f, 0.2f, 0.95f, 0.98f);
			component.texture = (Texture)(object)_cache.Frame(item, 0);
			((Graphic)component).color = (flag ? Color.white : Color.black);
			((Graphic)component).raycastTarget = false;
			Text val2 = Label((Transform)(object)val, "Name", flag ? item.Name : "???", 16, (TextAnchor)4, (FontStyle)0);
			Place(((Graphic)val2).rectTransform, 0.02f, 0f, 0.98f, 0.2f);
			if (item.Htf.UltraRare && flag)
			{
				((Graphic)val2).color = Gold;
			}
		}
		ShowDetail();
		bool Caught(FishDef d)
		{
			if (entries != null && entries.TryGetValue(d.Id, out var value))
			{
				return value.Caught;
			}
			return false;
		}
	}

	private void ShowDetail()
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b1: 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_00c6: Expected I4, but got Unknown
		//IL_0222: Unknown result type (might be due to invalid IL or missing references)
		if (_selected == null)
		{
			_detailIcon.texture = null;
			((Graphic)_detailIcon).color = Color.clear;
			_detail.text = "Select a fish.";
			return;
		}
		FishDef selected = _selected;
		JournalEntry value = null;
		bool flag = _journal.Journal != null && _journal.Journal.Entries.TryGetValue(selected.Id, out value) && value.Caught;
		_detailIcon.texture = (Texture)(object)_cache.Frame(selected, 0);
		((Graphic)_detailIcon).color = (flag ? Color.white : Color.black);
		ZoneRule zone = selected.Htf.Zone;
		string text = (int)zone switch
		{
			0 => "Freshwater islands", 
			1 => "The last two islands (saltwater)", 
			2 => "Anywhere, while it rains", 
			_ => "Anywhere (very rare)", 
		};
		string text2 = (new string[4] { "Beginner", "Standard", "Professional", "Scientific" })[Mathf.Clamp(selected.Htf.MinBaitTier, 0, 3)];
		string text3 = (flag ? ("<b>" + selected.Name + "</b>\n<i>" + selected.Wf.Description + "</i>\n\n") : "<b>???</b>\n\n");
		text3 = text3 + "Where: " + text + "\nLure: " + text2 + " or better\n";
		if (selected.Htf.UltraRare)
		{
			text3 += "<color=#e6b422>Ultra-rare: guarantees a Legendary skin at the slot machine</color>\n";
		}
		if (flag)
		{
			text3 = text3 + string.Format("\nCaught: {0}{1}\nBest weight: {2:0.##} kg\nBest quality: {3}\n", value.Count, (value.ShinyCount > 0) ? $" ({value.ShinyCount} shiny!)" : "", value.BestWeightKg, value.BestQuality) + "First caught: " + LocalDate(value.FirstCatchUtc) + " by " + CatcherName(value) + "\n" + (string.IsNullOrEmpty(value.FirstIsland) ? "" : ("Island: " + value.FirstIsland + "\n")) + (string.IsNullOrEmpty(value.FirstBait) ? "" : ("Bait: " + value.FirstBait + "\n")) + "\n\"" + selected.Wf.Blurb + "\"";
		}
		_detail.text = text3;
	}

	private static string LocalDate(string utc)
	{
		if (!DateTime.TryParse(utc, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var result))
		{
			return utc;
		}
		return result.ToLocalTime().ToString("yyyy-MM-dd");
	}

	private static string CatcherName(JournalEntry e)
	{
		return DisplayText.VisibleOr(e.FirstCatcherName, (e.FirstCatcherId == Identity.SteamId) ? "you" : $"player {e.FirstCatcherId % 10000:0000}");
	}

	private RectTransform Box(Transform parent, string name, Color color)
	{
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject(name, new Type[2]
		{
			typeof(RectTransform),
			typeof(Image)
		});
		val.transform.SetParent(parent, false);
		((Graphic)val.GetComponent<Image>()).color = color;
		return val.GetComponent<RectTransform>();
	}

	private Text Label(Transform parent, string name, string text, int size, TextAnchor anchor, FontStyle style = (FontStyle)0)
	{
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject(name, new Type[2]
		{
			typeof(RectTransform),
			typeof(Text)
		});
		val.transform.SetParent(parent, false);
		Text component = val.GetComponent<Text>();
		component.font = _font;
		component.fontSize = size;
		component.alignment = anchor;
		component.fontStyle = style;
		((Graphic)component).color = Color.white;
		component.supportRichText = true;
		((Graphic)component).raycastTarget = false;
		component.horizontalOverflow = (HorizontalWrapMode)0;
		component.verticalOverflow = (VerticalWrapMode)0;
		component.text = text;
		return component;
	}

	private RectTransform Button(Transform parent, string name, string text, UnityAction onClick)
	{
		//IL_0003: Unknown result type (might be due to invalid IL or missing references)
		RectTransform val = Box(parent, name, Cell);
		((UnityEvent)((Component)val).gameObject.AddComponent<Button>().onClick).AddListener(onClick);
		Place(((Graphic)Label((Transform)(object)val, "Text", text, 22, (TextAnchor)4, (FontStyle)0)).rectTransform, 0f, 0f, 1f, 1f);
		return val;
	}

	private static void Stretch(RectTransform rt, float x0, float y0, float x1, float y1)
	{
		Place(rt, x0, y0, x1, y1);
	}

	private static void Place(RectTransform rt, float x0, float y0, float x1, float y1)
	{
		//IL_0003: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: 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)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		rt.anchorMin = new Vector2(x0, y0);
		rt.anchorMax = new Vector2(x1, y1);
		Vector2 offsetMin = (rt.offsetMax = Vector2.zero);
		rt.offsetMin = offsetMin;
	}
}
public sealed class Lineup : MonoBehaviour
{
	public sealed class Entry
	{
		public FishDef Def;

		public Quality Quality;

		public bool Shiny;

		public float Length;

		public string Label;

		public GameObject Body;
	}

	private const float SpanDegrees = 80f;

	private const float SlotPadding = 1.3f;

	private const float MinWidth = 0.5f;

	private const float LabelHeight = 0.5f;

	private const float LabelWiden = 1.7f;

	private static Lineup _current;

	public static bool Showing => (Object)(object)_current != (Object)null;

	public static void Clear()
	{
		if ((Object)(object)_current != (Object)null)
		{
			Object.Destroy((Object)(object)((Component)_current).gameObject);
		}
		_current = null;
	}

	public static float Show(SpriteCache cache, IList<Entry> entries, Vector3 feet, Vector3 forward, Func<Vector3, float?> floorAt)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Expected O, but got Unknown
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
		//IL_0100: Unknown result type (might be due to invalid IL or missing references)
		//IL_0101: Unknown result type (might be due to invalid IL or missing references)
		//IL_0106: Unknown result type (might be due to invalid IL or missing references)
		//IL_0108: Unknown result type (might be due to invalid IL or missing references)
		//IL_0109: Unknown result type (might be due to invalid IL or missing references)
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0111: Unknown result type (might be due to invalid IL or missing references)
		//IL_0116: Unknown result type (might be due to invalid IL or missing references)
		//IL_012a: Unknown result type (might be due to invalid IL or missing references)
		//IL_013c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0169: Unknown result type (might be due to invalid IL or missing references)
		//IL_0185: Unknown result type (might be due to invalid IL or missing references)
		//IL_018e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0195: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a1: 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_0223: Unknown result type (might be due to invalid IL or missing references)
		//IL_0228: Unknown result type (might be due to invalid IL or missing references)
		//IL_022a: Unknown result type (might be due to invalid IL or missing references)
		//IL_022f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0234: Unknown result type (might be due to invalid IL or missing references)
		//IL_032e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0333: Unknown result type (might be due to invalid IL or missing references)
		//IL_033c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0343: Unknown result type (might be due to invalid IL or missing references)
		//IL_025b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0260: Unknown result type (might be due to invalid IL or missing references)
		//IL_0269: Unknown result type (might be due to invalid IL or missing references)
		//IL_026e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0272: Unknown result type (might be due to invalid IL or missing references)
		//IL_0281: Unknown result type (might be due to invalid IL or missing references)
		//IL_0286: Unknown result type (might be due to invalid IL or missing references)
		//IL_028a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0295: Unknown result type (might be due to invalid IL or missing references)
		//IL_029e: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_02da: Unknown result type (might be due to invalid IL or missing references)
		//IL_0395: Unknown result type (might be due to invalid IL or missing references)
		//IL_039a: Unknown result type (might be due to invalid IL or missing references)
		//IL_03a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_03a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_03ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_03b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_03bc: Unknown result type (might be due to invalid IL or missing references)
		Clear();
		GameObject val = new GameObject("WFxHTF Lineup");
		_current = val.AddComponent<Lineup>();
		forward.y = 0f;
		if (((Vector3)(ref forward)).sqrMagnitude < 0.0001f)
		{
			forward = Vector3.forward;
		}
		((Vector3)(ref forward)).Normalize();
		List<float> list = entries.Select((Entry e) => Mathf.Max(new float[3]
		{
			e.Length,
			0.5f,
			BodyWidth(e.Body)
		}) * 1.3f).ToList();
		float num = list.Sum();
		float num2 = Mathf.Max(3.5f, num / 1.3962634f);
		float num3 = Mathf.Max(1f, num2 / 6f);
		float num4 = 0.5f * num3;
		Font builtinResource = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
		float num5 = (0f - num) / 2f;
		for (int num6 = 0; num6 < entries.Count; num6++)
		{
			Entry entry = entries[num6];
			float num7 = num5 + list[num6] / 2f;
			num5 += list[num6];
			Vector3 val2 = Quaternion.AngleAxis(num7 / num2 * 57.29578f, Vector3.up) * forward;
			Vector3 val3 = feet + val2 * num2;
			float num8 = floorAt?.Invoke(val3) ?? feet.y;
			Transform transform = new GameObject((entry.Def != null) ? entry.Def.Name : "fish").transform;
			transform.SetParent(val.transform, false);
			transform.position = new Vector3(val3.x, num8, val3.z);
			transform.rotation = Quaternion.LookRotation(val2);
			float num9 = num8 + 2f * num4 + 0.2f;
			AddLabel(transform, builtinResource, entry.Label, list[num6] * 1.7f, num3, num8 + 0.05f + (float)(num6 % 2) * (num4 + 0.05f));
			Bounds value;
			if ((Object)(object)entry.Body != (Object)null)
			{
				Transform transform2 = entry.Body.transform;
				transform2.SetParent(transform, true);
				transform2.rotation = Quaternion.LookRotation(Vector3.Cross(Vector3.up, val2), Vector3.up);
				Bounds? val4 = Bounds(entry.Body);
				if (val4.HasValue)
				{
					Vector3 position = transform2.position;
					float x = val3.x;
					value = val4.Value;
					float num10 = x - ((Bounds)(ref value)).center.x;
					float num11 = num9;
					value = val4.Value;
					float num12 = num11 - ((Bounds)(ref value)).min.y;
					float z = val3.z;
					value = val4.Value;
					transform2.position = position + new Vector3(num10, num12, z - ((Bounds)(ref value)).center.z);
					value = Bounds(entry.Body).Value;
					num9 = ((Bounds)(ref value)).max.y + 0.2f;
				}
				Renderer[] componentsInChildren = entry.Body.GetComponentsInChildren<Renderer>();
				for (int num13 = 0; num13 < componentsInChildren.Length; num13++)
				{
					componentsInChildren[num13].shadowCastingMode = (ShadowCastingMode)0;
				}
			}
			if (entry.Def == null)
			{
				continue;
			}
			SpriteFish spriteFish = SpriteFish.Create(cache, entry.Def, entry.Quality, new Vector3(val3.x, num9, val3.z), entry.Length, entry.Shiny);
			if (!((Object)(object)spriteFish == (Object)null))
			{
				((Component)spriteFish).transform.SetParent(transform, true);
				Bounds? val5 = Bounds(((Component)spriteFish).gameObject);
				if (val5.HasValue)
				{
					Transform transform3 = ((Component)spriteFish).transform;
					Vector3 position2 = transform3.position;
					Vector3 up = Vector3.up;
					float num14 = num9;
					value = val5.Value;
					transform3.position = position2 + up * (num14 - ((Bounds)(ref value)).min.y);
				}
			}
		}
		return num2;
	}

	private static float BodyWidth(GameObject body)
	{
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		Bounds? val = (((Object)(object)body != (Object)null) ? Bounds(body) : ((Bounds?)null));
		if (!val.HasValue)
		{
			return 0f;
		}
		Bounds value = val.Value;
		float x = ((Bounds)(ref value)).size.x;
		value = val.Value;
		return Mathf.Max(x, ((Bounds)(ref value)).size.z);
	}

	private static Bounds? Bounds(GameObject go)
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		Bounds? result = null;
		Renderer[] componentsInChildren = go.GetComponentsInChildren<Renderer>();
		foreach (Renderer val in componentsInChildren)
		{
			if (!result.HasValue)
			{
				result = val.bounds;
				continue;
			}
			Bounds value = result.Value;
			((Bounds)(ref value)).Encapsulate(val.bounds);
			result = value;
		}
		return result;
	}

	private static void AddLabel(Transform slot, Font font, string text, float widthMeters, float scale, float bottomY)
	{
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Expected O, but got Unknown
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		//IL_009b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_010a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0139: 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_0185: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject("Label", new Type[2]
		{
			typeof(RectTransform),
			typeof(Canvas)
		});
		RectTransform val2 = (RectTransform)val.transform;
		((Transform)val2).SetParent(slot, false);
		val.GetComponent<Canvas>().renderMode = (RenderMode)2;
		float num = 0.5f * scale;
		val2.sizeDelta = new Vector2(widthMeters / scale * 400f, 200f);
		((Transform)val2).localScale = Vector3.one * (scale / 400f);
		((Transform)val2).position = new Vector3(slot.position.x, bottomY + num / 2f, slot.position.z);
		Image component = new GameObject("Bg", new Type[2]
		{
			typeof(RectTransform),
			typeof(Image)
		}).GetComponent<Image>();
		((Transform)((Graphic)component).rectTransform).SetParent((Transform)(object)val2, false);
		Stretch(((Graphic)component).rectTransform);
		((Graphic)component).color = new Color(0.05f, 0.07f, 0.1f, 0.75f);
		Text component2 = new GameObject("Text", new Type[2]
		{
			typeof(RectTransform),
			typeof(Text)
		}).GetComponent<Text>();
		((Transform)((Graphic)component2).rectTransform).SetParent((Transform)(object)val2, false);
		Stretch(((Graphic)component2).rectTransform);
		((Graphic)component2).rectTransform.offsetMin = new Vector2(8f, 4f);
		((Graphic)component2).rectTransform.offsetMax = new Vector2(-8f, -4f);
		component2.font = font;
		component2.fontSize = 46;
		component2.resizeTextForBestFit = true;
		component2.resizeTextMinSize = 14;
		component2.resizeTextMaxSize = 46;
		component2.alignment = (TextAnchor)4;
		component2.supportRichText = true;
		((Graphic)component2).color = Color.white;
		component2.text = text;
	}

	private static void Stretch(RectTransform r)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//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_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: 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)
		r.anchorMin = Vector2.zero;
		r.anchorMax = Vector2.one;
		Vector2 offsetMin = (r.offsetMax = Vector2.zero);
		r.offsetMin = offsetMin;
	}
}
public static class ModUi
{
	private static readonly List<Func<bool>> Blockers = new List<Func<bool>>();

	public static bool BlocksGame
	{
		get
		{
			foreach (Func<bool> blocker in Blockers)
			{
				try
				{
					if (blocker())
					{
						return true;
					}
				}
				catch
				{
				}
			}
			return false;
		}
	}

	public static void AddBlocker(Func<bool> isOpen)
	{
		Blockers.Add(isOpen);
	}
}
public sealed class PlacementRules
{
	public LayerMask Ground = LayerMask.op_Implicit(-1);

	public LayerMask Blockers = LayerMask.op_Implicit(-1);

	public float MaxSlopeDeg = 25f;

	public float MaxStep = 0.35f;

	public float MinDistance = 1.5f;

	public float MaxDistance = 8f;

	public Func<Vector3, float> WaterHeightAt;

	public Func<Collider, bool> Ignore;
}
public sealed class PlacementGhost : MonoBehaviour
{
	public KeyCode RotateKey = (KeyCode)114;

	private PlacementRules _rules;

	private Action<Vector3, float> _onConfirm;

	private Action _onCancel;

	private TankView _ghost;

	private Vector3 _size;

	private float _yaw;

	private bool _valid;

	private Vector3 _pos;

	private string _why = "";

	private int _startFrame;

	public static PlacementGhost Current { get; private set; }

	public static PlacementGhost Begin(SpriteCache cache, PlacementRules rules, float startYaw, Action<Vector3, float> onConfirm, Action onCancel, float sizeScale = 1f)
	{
		//IL_0025: 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 ((Object)(object)Current != (Object)null)
		{
			Current.Cancel();
		}
		TankView tankView = TankView.CreateGhost(cache, sizeScale);
		PlacementGhost placementGhost = new GameObject("WFxHTF Placement").AddComponent<PlacementGhost>();
		placementGhost._ghost = tankView;
		placementGhost._size = tankView.OuterSize;
		placementGhost._rules = rules ?? new PlacementRules();
		placementGhost._yaw = startYaw;
		placementGhost._onConfirm = onConfirm;
		placementGhost._onCancel = onCancel;
		placementGhost._startFrame = Time.frameCount;
		Current = placementGhost;
		return placementGhost;
	}

	public void Cancel()
	{
		Action onCancel = _onCancel;
		Finish();
		onCancel?.Invoke();
	}

	private void Finish()
	{
		if ((Object)(object)Current == (Object)(object)this)
		{
			Current = null;
		}
		Hud.Hide("placement");
		if ((Object)(object)_ghost != (Object)null)
		{
			Object.Destroy((Object)(object)((Component)_ghost).gameObject);
		}
		Object.Destroy((Object)(object)((Component)this).gameObject);
	}

	private void Update()
	{
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_015d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0162: Unknown result type (might be due to invalid IL or missing references)
		//IL_0175: Unknown result type (might be due to invalid IL or missing references)
		Camera val = UnityUtil.ViewCamera();
		bool blocksGame = ModUi.BlocksGame;
		((Component)_ghost).gameObject.SetActive((Object)(object)val != (Object)null && !blocksGame);
		if ((Object)(object)val == (Object)null || blocksGame)
		{
			return;
		}
		if (Keys.Down(RotateKey))
		{
			_yaw = (_yaw + (Input.GetKey((KeyCode)304) ? (-15f) : 15f) + 360f) % 360f;
		}
		_valid = Evaluate(((Component)val).transform, out _pos, out _why);
		((Component)_ghost).transform.SetPositionAndRotation(_pos, Quaternion.Euler(0f, _yaw, 0f));
		_ghost.SetGhostValid(_valid);
		Hud.Show("placement", "<b>Placing the aquarium</b>   [Left click] place   [" + ((object)Unsafe.As<KeyCode, KeyCode>(ref RotateKey)/*cast due to .constrained prefix*/).ToString() + "] rotate   [Right click] cancel" + (_valid ? "" : ("\n<color=#ff8070>" + _why + "</color>")));
		if (Time.frameCount > _startFrame + 1)
		{
			if (Input.GetMouseButtonDown(1) || Keys.Down((KeyCode)27))
			{
				Cancel();
			}
			else if (Input.GetMouseButtonDown(0) && _valid)
			{
				Action<Vector3, float> onConfirm = _onConfirm;
				Vector3 pos = _pos;
				float yaw = _yaw;
				Finish();
				onConfirm?.Invoke(pos, yaw);
			}
		}
	}

	private bool Evaluate(Transform cam, out Vector3 pos, out string why)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//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_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: 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_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
		//IL_012e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0133: Unknown result type (might be due to invalid IL or missing references)
		//IL_0145: Unknown result type (might be due to invalid IL or missing references)
		//IL_014a: Unknown result type (might be due to invalid IL or missing references)
		//IL_015b: 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_0171: 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)
		//IL_0186: Unknown result type (might be due to invalid IL or missing references)
		//IL_018b: Unknown result type (might be due to invalid IL or missing references)
		//IL_019e: 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_01a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ad: 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_01b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ee: 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_0255: Unknown result type (might be due to invalid IL or missing references)
		//IL_025f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0264: Unknown result type (might be due to invalid IL or missing references)
		//IL_020d: 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)
		//IL_02e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f1: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f9: Unknown result type (might be due to invalid IL or missing references)
		//IL_02fe: Unknown result type (might be due to invalid IL or missing references)
		//IL_0303: Unknown result type (might be due to invalid IL or missing references)
		//IL_0305: Unknown result type (might be due to invalid IL or missing references)
		//IL_0307: Unknown result type (might be due to invalid IL or missing references)
		//IL_0290: Unknown result type (might be due to invalid IL or missing references)
		pos = cam.position + cam.forward * 4f;
		PlacementRules rules = _rules;
		RaycastHit val = default(RaycastHit);
		if (!Physics.Raycast(cam.position, cam.forward, ref val, rules.MaxDistance + 3f, LayerMask.op_Implicit(rules.Ground), (QueryTriggerInteraction)1))
		{
			why = "Aim at the ground";
			return false;
		}
		pos = ((RaycastHit)(ref val)).point;
		Vector3 val2 = ((RaycastHit)(ref val)).point - cam.position;
		val2.y = 0f;
		if (((Vector3)(ref val2)).magnitude > rules.MaxDistance)
		{
			why = "Too far away";
			return false;
		}
		if (((Vector3)(ref val2)).magnitude < rules.MinDistance)
		{
			why = "Too close to you";
			return false;
		}
		if (Vector3.Angle(((RaycastHit)(ref val)).normal, Vector3.up) > rules.MaxSlopeDeg)
		{
			why = "Too steep";
			return false;
		}
		Quaternion val3 = Quaternion.Euler(0f, _yaw, 0f);
		float num = _size.x / 2f;
		float num2 = _size.z / 2f;
		float num3 = float.MinValue;
		float num4 = float.MaxValue;
		Vector3[] array = (Vector3[])(object)new Vector3[5]
		{
			Vector3.zero,
			new Vector3(0f - num, 0f, 0f - num2),
			new Vector3(num, 0f, 0f - num2),
			new Vector3(0f - num, 0f, num2),
			new Vector3(num, 0f, num2)
		};
		RaycastHit val5 = default(RaycastHit);
		foreach (Vector3 val4 in array)
		{
			if (!Physics.Raycast(((RaycastHit)(ref val)).point + val3 * val4 + Vector3.up * (rules.MaxStep + 1.5f), Vector3.down, ref val5, rules.MaxStep * 2f + 3f, LayerMask.op_Implicit(rules.Ground), (QueryTriggerInteraction)1))
			{
				why = "Not enough ground under it";
				return false;
			}
			num3 = Mathf.Max(num3, ((RaycastHit)(ref val5)).point.y);
			num4 = Mathf.Min(num4, ((RaycastHit)(ref val5)).point.y);
		}
		pos = new Vector3(((RaycastHit)(ref val)).point.x, num3, ((RaycastHit)(ref val)).point.z);
		if (num3 - num4 > rules.MaxStep)
		{
			why = "Ground too uneven";
			return false;
		}
		if (rules.WaterHeightAt != null && num4 < rules.WaterHeightAt(pos) + 0.2f)
		{
			why = "Can't stand in water";
			return false;
		}
		float num5 = rules.MaxStep * 0.5f;
		Vector3 val6 = default(Vector3);
		((Vector3)(ref val6))..ctor(num - 0.03f, (_size.y - num5) / 2f, num2 - 0.03f);
		Collider[] array2 = Physics.OverlapBox(pos + Vector3.up * (num5 + val6.y), val6, val3, LayerMask.op_Implicit(rules.Blockers), (QueryTriggerInteraction)1);
		foreach (Collider val7 in array2)
		{
			if (!((Object)(object)val7 == (Object)null) && !((Component)val7).transform.IsChildOf(((Component)_ghost).transform) && (rules.Ignore == null || !rules.Ignore(val7)))
			{
				why = "Blocked by " + Nice(((Object)val7).name);
				return false;
			}
		}
		why = "";
		return true;
	}

	private static string Nice(string name)
	{
		int num = name.IndexOf('(');
		return ((num > 0) ? name.Substring(0, num) : name).Trim();
	}

	private void OnDestroy()
	{
		if ((Object)(object)_ghost != (Object)null)
		{
			Object.Destroy((Object)(object)((Component)_ghost).gameObject);
		}
		if ((Object)(object)Current == (Object)(object)this)
		{
			Current = null;
			Hud.Hide("placement");
		}
	}
}
[BepInPlugin("com.wfxhtf.mod", "WFxHTF", "1.0.1")]
public sealed class Plugin : BaseUnityPlugin
{
	public const string Guid = "com.wfxhtf.mod";

	public const string Version = "1.0.1";

	public static ManualLogSource Log;

	public static Plugin Instance;

	public readonly MappingConfig Mapping = new MappingConfig();

	public readonly EconomyConfig Economy = new EconomyConfig();

	public readonly AquariumConfig Aquarium = new AquariumConfig();

	public readonly RainConfig Rain = new RainConfig();

	private DebugTank _tank;

	private ConfigEntry<KeyCode> _consoleKey;

	private ConfigEntry<KeyCode> _spawnKey;

	private ConfigEntry<KeyCode> _journalKey;

	private ConfigEntry<bool> _debugCommands;

	private ConfigEntry<float> _spriteScale;

	private readonly List<SpriteFish> _spawned = new List<SpriteFish>();

	private int _spawnCursor;

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

	private static readonly (int Version, string Section, string Key, string OldDefault)[] DefaultChanges = new(int, string, string, string)[6]
	{
		(1, "AquariumConfig", "ZenToggleCooldownDays", "1"),
		(2, "AquariumConfig", "IncomeRate", "0.04"),
		(2, "AquariumConfig", "FoodUnitsPerItem", "4"),
		(2, "MappingConfig", "RainSpawnScale", "1"),
		(3, "MappingConfig", "RainSpawnScale", "8"),
		(4, "AquariumConfig", "IncomeRate", "0.18")
	};

	public SpriteCache Cache { get; } = new SpriteCache();

	public DebugConsole Console { get; private set; }

	public JournalService Journal { get; } = new JournalService();

	public JournalUI JournalPanel { get; private set; }

	public TankMenu TankMenu { get; private set; }

	public RainVisuals RainFx { get; private set; }

	public FirstRunSetup Setup { get; private set; }

	public event Action CacheLoaded;

	private void Awake()
	{
		//IL_026c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0271: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_0313: Unknown result type (might be due to invalid IL or missing references)
		//IL_0318: Unknown result type (might be due to invalid IL or missing references)
		//IL_0345: Unknown result type (might be due to invalid IL or missing references)
		Log = ((BaseUnityPlugin)this).Logger;
		Instance = this;
		int num = 0;
		object[] array = new object[4] { Mapping, Economy, Aquarium, Rain };
		foreach (object target in array)
		{
			num += ConfigBinder.Bind(((BaseUnityPlugin)this).Config, target);
		}
		if (ApplyDefaultChanges() > 0)
		{
			array = new object[4] { Mapping, Economy, Aquarium, Rain };
			foreach (object target2 in array)
			{
				ConfigBinder.Bind(((BaseUnityPlugin)this).Config, target2);
			}
		}
		_consoleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keys", "Console", (KeyCode)289, "Opens the WFxHTF debug console.");
		_spawnKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keys", "DebugSpawnNext", (KeyCode)290, "Debug: spawn the next WEBFISHING fish sprite in front of you (needs Debug.Commands).");
		_debugCommands = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Commands", false, "Cheat and test console commands (wf.nextbite, shop.give, tank.fill, shiny.next, ...). Off for normal play.");
		_journalKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keys", "Journal", (KeyCode)106, "Opens the WEBFISHING journal.");
		_spriteScale = ((BaseUnityPlugin)this).Config.Bind<float>("Visuals", "SpriteScale", 1f, "Size multiplier for WEBFISHING fish sprites.");
		Feature("config", $"{num} tunables bound");
		bool visible = false;
		if (Cache.Load(Mapping))
		{
			int num2 = Cache.Preload();
			Feature("cache", $"{Cache.Defs.Count} fish, fishDefHash {Cache.FishDefHash}, source build {Cache.Catalog.SteamBuildId}" + ((num2 > 0) ? $", {num2} textures FAILED" : ""));
		}
		else
		{
			Feature("cache", "MISSING: " + Cache.Error);
			visible = true;
		}
		Setup = ((Component)this).gameObject.AddComponent<FirstRunSetup>();
		Setup.Visible = visible;
		Console = ((Component)this).gameObject.AddComponent<DebugConsole>();
		Console.ToggleKey = _consoleKey.Value;
		Console.DebugAllowed = _debugCommands.Value;
		RegisterCommands();
		Feature("console", $"key {_consoleKey.Value}");
		ModUi.AddBlocker(() => Console.Open);
		((Component)this).gameObject.AddComponent<Hud>();
		TankMenu = ((Component)this).gameObject.AddComponent<TankMenu>();
		RainFx = ((Component)this).gameObject.AddComponent<RainVisuals>();
		JournalPanel = ((Component)this).gameObject.AddComponent<JournalUI>();
		JournalPanel.ToggleKey = _journalKey.Value;
		JournalPanel.Init(Cache, Journal);
		Feature("journal", $"key {_journalKey.Value}, saves in {Identity.SavesDir}");
		Feature("adapter", "not loaded: no How to Fish hooks active");
		Log.LogInfo((object)("WFxHTF 1.0.1 startup report:\n  " + string.Join("\n  ", _report)));
	}

	private int ApplyDefaultChanges()
	{
		//IL_00af: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b6: Expected O, but got Unknown
		ConfigEntry<int> applied = ((BaseUnityPlugin)this).Config.Bind<int>("Meta", "DefaultsVersion", 0, "Internal: which updated default values have been applied. Leave as is.");
		int num = DefaultChanges.Max(((int Version, string Section, string Key, string OldDefault) c) => c.Version);
		int num2 = 0;
		if (applied.Value >= num)
		{
			return 0;
		}
		foreach (var item in DefaultChanges.Where(((int Version, string Section, string Key, string OldDefault) c) => c.Version > applied.Value))
		{
			ConfigDefinition val = new ConfigDefinition(item.Section, item.Key);
			if (!((BaseUnityPlugin)this).Config.ContainsKey(val))
			{
				continue;
			}
			ConfigEntryBase val2 = ((BaseUnityPlugin)this).Config[val];
			string serializedValue = val2.GetSerializedValue();
			if (SameValue(serializedValue, item.OldDefault))
			{
				val2.BoxedValue = val2.DefaultValue;
				if (!SameValue(val2.GetSerializedValue(), serializedValue))
				{
					num2++;
					((BaseUnityPlugin)this).Logger.LogInfo((object)("config: " + item.Section + "." + item.Key + " " + item.OldDefault + " -> new default " + val2.GetSerializedValue()));
				}
			}
		}
		applied.Value = num;
		return num2;
	}

	private static bool SameValue(string a, string b)
	{
		if (!(a == b))
		{
			if (double.TryParse(a, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && double.TryParse(b, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
			{
				return Math.Abs(result - result2) < 1E-09;
			}
			return false;
		}
		return true;
	}

	public bool ReloadCache()
	{
		if (!Cache.Load(Mapping))
		{
			Log.LogWarning((object)("cache: " + Cache.Error));
			return false;
		}
		int num = Cache.Preload();
		Feature("cache", $"{Cache.Defs.Count} fish, fishDefHash {Cache.FishDefHash}, source build {Cache.Catalog.SteamBuildId}" + ((num > 0) ? $", {num} textures FAILED" : "") + " (set up in game)");
		Log.LogInfo((object)$"cache: {Cache.Defs.Count} WEBFISHING fish loaded after the in-game setup");
		try
		{
			this.CacheLoaded?.Invoke();
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("after setup: " + ex.Message));
		}
		return true;
	}

	public void Feature(string name, string detail)
	{
		string text = $"{name,-8} {detail}";
		int num = _report.FindIndex((string l) => l.StartsWith(name + " "));
		if (num >= 0)
		{
			_report[num] = text;
		}
		else
		{
			_report.Add(text);
		}
	}

	private void Update()
	{
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		if (_debugCommands.Value && Keys.Down(_spawnKey.Value) && Cache.Loaded && !Console.Open)
		{
			FishDef def = Cache.Defs[_spawnCursor++ % Cache.Defs.Count];
			Log.LogInfo((object)Spawn(def, (Quality)0));
		}
	}

	private string Spawn(FishDef def, Quality q)
	{
		//IL_002d: 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: Unknown result type (might be due to invalid IL or missing references)
		//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_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_0057: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		Camera val = UnityUtil.ViewCamera();
		if ((Object)(object)val == (Object)null)
		{
			return "no camera";
		}
		float num = SpriteFish.LengthMeters(def, _spriteScale.Value);
		Vector3 position = ((Component)val).transform.position + ((Component)val).transform.forward * (2f + num);
		SpriteFish spriteFish = SpriteFish.Create(Cache, def, q, position, num);
		if ((Object)(object)spriteFish == (Object)null)
		{
			return "could not create " + def.Id + " (texture or shader missing; shader = " + SpriteMaterial.ShaderName + ")";
		}
		_spawned.Add(spriteFish);
		return $"spawned {def.Name} ({def.Id}, {q}) {num:0.0} m, shader {SpriteMaterial.ShaderName}";
	}

	private FishDef ParseFish(string[] a, string usage, out Quality q, out double? number, out string error)
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Expected I4, but got Unknown
		Quality? val = default(Quality?);
		string text = FishArgs.Split(a, ref val, ref number);
		q = (Quality)(int)val.GetValueOrDefault();
		error = null;
		if (!Cache.Loaded)
		{
			error = Cache.Error;
			return null;
		}
		if (text.Length == 0)
		{
			error = "usage: " + usage;
			return null;
		}
		FishDef obj = Cache.Find(text);
		if (obj == null)
		{
			error = "no fish matches '" + text + "'";
		}
		return obj;
	}

	private static double ParseNumber(string[] a, int i, double fallback)
	{
		if (a.Length <= i || !double.TryParse(a[i], NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
		{
			return fallback;
		}
		return result;
	}

	private void RegisterCommands()
	{
		Console.Register("wf.firstrun", "show the WEBFISHING setup box (extract the fish from your WEBFISHING)", delegate
		{
			Setup.Visible = true;
			return "the setup box is open";
		});
		Console.Register("rain.clouds", "DEBUG: [height] [max size] [count] [darkness 0-1] tune the rain clouds live (shows the current values)", delegate(string[] a)
		{
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			RainVisuals rainFx = RainFx;
			if ((Object)(object)rainFx == (Object)null)
			{
				return "no rain visuals";
			}
			if (a.Length != 0)
			{
				rainFx.CloudHeight = Mathf.Clamp((float)ParseNumber(a, 0, rainFx.CloudHeight), 10f, 150f);
				rainFx.CloudMaxSize = Mathf.Clamp((float)ParseNumber(a, 1, rainFx.CloudMaxSize), 5f, 200f);
				rainFx.CloudMinSize = rainFx.CloudMaxSize * 0.5f;
				rainFx.CloudCount = Mathf.Clamp((int)ParseNumber(a, 2, rainFx.CloudCount), 0, 3000);
				float num = Mathf.Clamp01((float)ParseNumber(a, 3, 1f - rainFx.CloudColor.g / 0.6f));
				float num2 = 0.6f * (1f - num);
				rainFx.CloudColor = new Color(num2 * 0.93f, num2, num2 * 1.2f, 1f);
				rainFx.RebuildClouds();
			}
			return $"clouds: height {rainFx.CloudHeight:0}, size {rainFx.CloudMinSize:0}-{rainFx.CloudMaxSize:0} (flatness {rainFx.CloudFlatness:0.##}), count {rainFx.CloudCount}, colour {rainFx.CloudColor}" + ((rainFx.Amount <= 0f) ? "  (not raining: rain.start to see them)" : "");
		});
		Console.Register("wf.catch", "DEBUG: <id|name> [quality] [kg] record a catch in your journal", delegate(string[] a)
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			Quality q;
			double? number;
			string error;
			FishDef val = ParseFish(a, "wf.catch <id|name> [quality] [kg]", out q, out number, out error);
			if (val == null)
			{
				return error;
			}
			float num = (float)(number ?? ((double)val.Htf.WeightKgMean));
			bool flag = Journal.RecordLocal(val.Id, num, q, "debug", "debug");
			return string.Format("{0}{1} {2} {3:0.##} kg -> {4}", flag ? "NEW! " : "", val.Name, q, num, Journal.Path);
		});
		Console.Register("journal", "journal status (J opens it)", delegate
		{
			if (Journal.Journal == null)
			{
				Journal.Load();
			}
			return $"{Journal.Status}\nsteam id {Identity.SteamId} ({Identity.Name})\nfile {Journal.Path}";
		});
		Console.Register("aq.spawn", "DEBUG: place a debug tank in front of you", delegate
		{
			if (!Cache.Loaded)
			{
				return Cache.Error;
			}
			if ((Object)(object)_tank != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)((Component)_tank).gameObject);
			}
			_tank = DebugTank.Spawn(Cache, Aquarium, Console);
			return (!((Object)(object)_tank == (Object)null)) ? $"tank placed, capacity {_tank.Tank.Capacity}, tank day = {Aquarium.TankDayRealMinutes} real min" : "no camera";
		});
		Console.Register("aq.add", "DEBUG: <id|name> [quality] deposit a fish (debug tank)", delegate(string[] a)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_tank == (Object)null)
			{
				return "no tank: aq.spawn first";
			}
			Quality q;
			double? number;
			string error;
			FishDef val = ParseFish(a, "aq.add <id|name> [quality]", out q, out number, out error);
			return (val != null) ? _tank.Add(val, q) : error;
		});
		Console.Register("aq.fill", "DEBUG: fill the debug tank with random fish", delegate
		{
			if ((Object)(object)_tank == (Object)null)
			{
				return "no tank: aq.spawn first";
			}
			Random rng = new Random();
			List<FishDef> list = Cache.Defs.OrderBy((FishDef val) => rng.Next()).Take(_tank.Tank.Capacity - _tank.Tank.State.Fish.Count).ToList();
			foreach (FishDef item in list)
			{
				_tank.Add(item, (Quality)rng.Next(0, 6));
			}
			return $"added {list.Count}";
		});
		Console.Register("aq.dump", "DEBUG: print the debug tank's state", (string[] _) => (!((Object)(object)_tank == (Object)null)) ? _tank.Dump() : "no tank");
		Console.Register("aq.tick", "DEBUG: <days> advance the debug tank", delegate(string[] a)
		{
			if ((Object)(object)_tank == (Object)null)
			{
				return "no tank";
			}
			double num = ParseNumber(a, 0, 1.0);
			return $"advanced {num} days, {_tank.Handle(_tank.Tank.Advance(num, true))} events";
		});
		Console.Register("aq.attack", "DEBUG: start an attack now", (string[] _) => (!((Object)(object)_tank == (Object)null)) ? ((_tank.Handle(_tank.Tank.ForceAttack()) <= 0) ? "no attack (empty tank or one already running)" : "attack started (aq.defend to win, or wait it out)") : "no tank");
		Console.Register("aq.defend", "DEBUG: [hp 0-1] hurt the threat (default: kil