Decompiled source of FishComp v1.2.0

FishComp.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using FishComp.Commands;
using FishComp.Competition;
using FishComp.Networking;
using FishComp.Patches;
using FishComp.UI;
using HarmonyLib;
using Jotunn;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("FishComp")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+6cf0958373f80653656a83c12f040f294af5de17")]
[assembly: AssemblyProduct("FishComp")]
[assembly: AssemblyTitle("FishComp")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace FishComp
{
	[BepInPlugin("com.fishcomp.valheim", "FishComp", "1.2.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string ModGuid = "com.fishcomp.valheim";

		public const string ModName = "FishComp";

		public const string ModVersion = "1.2.0";

		private static GameObject _updaterHost;

		private Harmony harmony;

		public static Plugin Instance { get; private set; }

		public static ConfigEntry<float> ConfigPositionX { get; private set; }

		public static ConfigEntry<float> ConfigPositionY { get; private set; }

		public static ConfigEntry<float> ConfigSyncInterval { get; private set; }

		public CompetitionManager CompetitionManager { get; private set; }

		public RpcManager RpcManager { get; private set; }

		public LeaderboardUI UI { get; private set; }

		private void Awake()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			//IL_001b: Expected O, but got Unknown
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Expected O, but got Unknown
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Expected O, but got Unknown
			Instance = this;
			GameObject val = new GameObject("FishComp_UpdaterHost");
			Object.DontDestroyOnLoad((Object)val);
			_updaterHost = val;
			_updaterHost.AddComponent<ServerTicker>();
			ConfigPositionX = ((BaseUnityPlugin)this).Config.Bind<float>("UI", "PanelPositionX", 20f, "Horizontal position of the leaderboard panel from the left edge.");
			ConfigPositionY = ((BaseUnityPlugin)this).Config.Bind<float>("UI", "PanelPositionY", -180f, "Vertical position of the leaderboard panel from the top edge (negative = downward).");
			ConfigSyncInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Server", "SyncIntervalSeconds", 3f, "How often (in seconds) the server broadcasts competition state to all clients.");
			CompetitionManager = new CompetitionManager();
			RpcManager = new RpcManager();
			new FishCompCommands();
			PrefabManager.OnVanillaPrefabsAvailable += InitUI;
			harmony = new Harmony("com.fishcomp.valheim");
			MethodInfo methodInfo = AccessTools.Method(typeof(Humanoid), "Pickup", (Type[])null, (Type[])null);
			if (methodInfo != null)
			{
				MethodInfo methodInfo2 = AccessTools.Method(typeof(FishingPatch), "HumanoidPickupPostfix", (Type[])null, (Type[])null);
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Logger.LogInfo((object)"Patched Humanoid.Pickup");
			}
			else
			{
				Logger.LogError((object)"Humanoid.Pickup not found!");
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"FishComp v1.2.0 loaded.");
		}

		private void InitUI()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			PrefabManager.OnVanillaPrefabsAvailable -= InitUI;
			if ((int)SystemInfo.graphicsDeviceType == 4)
			{
				Logger.LogInfo((object)"Headless server detected — skipping LeaderboardUI creation.");
				return;
			}
			Logger.LogInfo((object)"InitUI called — creating LeaderboardUI");
			UI = new LeaderboardUI(_updaterHost);
		}
	}
}
namespace FishComp.UI
{
	public class LeaderboardUI
	{
		private class LeaderboardUpdater : MonoBehaviour
		{
			private LeaderboardUI leaderboardUI;

			public void Init(LeaderboardUI ui)
			{
				leaderboardUI = ui;
			}

			private void Update()
			{
				if (leaderboardUI != null)
				{
					leaderboardUI.UpdateFrame();
				}
			}
		}

		private GameObject _canvasRoot;

		private GameObject panel;

		private Text timerText;

		private Transform fishCaughtList;

		private bool _showTestUI;

		private bool _listDirty;

		private bool PanelExists
		{
			get
			{
				if ((Object)(object)panel != (Object)null)
				{
					return Object.op_Implicit((Object)(object)panel);
				}
				return false;
			}
		}

		public LeaderboardUI(GameObject updaterHost)
		{
			if (GUIManager.Instance == null)
			{
				Logger.LogError((object)"LeaderboardUI created before GUIManager is available.");
				return;
			}
			CompetitionManager.Instance.OnLeaderboardChanged += OnLeaderboardChanged;
			updaterHost.AddComponent<LeaderboardUpdater>().Init(this);
		}

		private void OnLeaderboardChanged()
		{
			_listDirty = true;
		}

		private void CreatePanel()
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Expected O, but got Unknown
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Expected O, but got Unknown
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: 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_01d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f7: Expected O, but got Unknown
			Logger.LogInfo((object)"CreatePanel() entered");
			if ((Object)(object)_canvasRoot == (Object)null)
			{
				_canvasRoot = new GameObject("FishCompCanvasRoot", new Type[4]
				{
					typeof(RectTransform),
					typeof(Canvas),
					typeof(CanvasScaler),
					typeof(GraphicRaycaster)
				});
				Object.DontDestroyOnLoad((Object)(object)_canvasRoot);
				Canvas component = _canvasRoot.GetComponent<Canvas>();
				component.renderMode = (RenderMode)0;
				component.sortingOrder = 100;
				CanvasScaler component2 = _canvasRoot.GetComponent<CanvasScaler>();
				component2.uiScaleMode = (ScaleMode)1;
				component2.referenceResolution = new Vector2(1920f, 1080f);
				component2.screenMatchMode = (ScreenMatchMode)0;
				component2.matchWidthOrHeight = 0.5f;
			}
			if ((Object)(object)panel != (Object)null)
			{
				Object.Destroy((Object)(object)panel);
				panel = null;
			}
			panel = new GameObject("FishCompPanel", new Type[2]
			{
				typeof(RectTransform),
				typeof(Image)
			});
			panel.transform.SetParent(_canvasRoot.transform, false);
			RectTransform component3 = panel.GetComponent<RectTransform>();
			component3.anchorMin = new Vector2(0f, 1f);
			component3.anchorMax = new Vector2(0f, 1f);
			component3.pivot = new Vector2(0f, 1f);
			component3.anchoredPosition = new Vector2(Plugin.ConfigPositionX.Value, Plugin.ConfigPositionY.Value);
			component3.sizeDelta = new Vector2(200f, 0f);
			Image component4 = panel.GetComponent<Image>();
			component4.sprite = null;
			((Graphic)component4).color = new Color(0f, 0f, 0f, 0.6f);
			VerticalLayoutGroup obj = panel.AddComponent<VerticalLayoutGroup>();
			((LayoutGroup)obj).padding = new RectOffset(10, 10, 8, 10);
			((HorizontalOrVerticalLayoutGroup)obj).spacing = 4f;
			((LayoutGroup)obj).childAlignment = (TextAnchor)0;
			((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
			ContentSizeFitter obj2 = panel.AddComponent<ContentSizeFitter>();
			obj2.horizontalFit = (FitMode)0;
			obj2.verticalFit = (FitMode)2;
			Font font = (((Object)(object)GUIManager.Instance.AveriaSerifBold != (Object)null) ? GUIManager.Instance.AveriaSerifBold : GUIManager.Instance.AveriaSerif);
			CreateText("Title", panel.transform, "Fishing Competition", 15, (FontStyle)1, font, (TextAnchor)4);
			timerText = CreateText("Timer", panel.transform, "Time Remaining: 00:00", 12, (FontStyle)0, font, (TextAnchor)0);
			fishCaughtList = CreateList("FishCaughtList", panel.transform);
			Logger.LogInfo((object)"CreatePanel() completed");
			_listDirty = true;
		}

		private static Text CreateText(string name, Transform parent, string text, int fontSize, FontStyle style, Font font, TextAnchor alignment = (TextAnchor)0)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_005e: 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_0071: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name, new Type[3]
			{
				typeof(RectTransform),
				typeof(Text),
				typeof(ContentSizeFitter)
			});
			val.transform.SetParent(parent, false);
			Text component = val.GetComponent<Text>();
			component.text = text;
			component.font = font;
			component.fontSize = fontSize;
			component.fontStyle = style;
			((Graphic)component).color = Color.white;
			component.alignment = alignment;
			component.horizontalOverflow = (HorizontalWrapMode)0;
			component.verticalOverflow = (VerticalWrapMode)0;
			ContentSizeFitter component2 = val.GetComponent<ContentSizeFitter>();
			component2.horizontalFit = (FitMode)0;
			component2.verticalFit = (FitMode)2;
			return component;
		}

		private static Transform CreateList(string name, Transform parent)
		{
			//IL_002e: 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_0040: 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)
			GameObject val = new GameObject(name, new Type[3]
			{
				typeof(RectTransform),
				typeof(VerticalLayoutGroup),
				typeof(ContentSizeFitter)
			});
			val.transform.SetParent(parent, false);
			VerticalLayoutGroup component = val.GetComponent<VerticalLayoutGroup>();
			((HorizontalOrVerticalLayoutGroup)component).spacing = 2f;
			((LayoutGroup)component).childAlignment = (TextAnchor)0;
			((HorizontalOrVerticalLayoutGroup)component).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)component).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)component).childForceExpandWidth = true;
			((HorizontalOrVerticalLayoutGroup)component).childForceExpandHeight = false;
			ContentSizeFitter component2 = val.GetComponent<ContentSizeFitter>();
			component2.horizontalFit = (FitMode)0;
			component2.verticalFit = (FitMode)2;
			return val.transform;
		}

		public void SetPosition(float x, float y)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			if (PanelExists)
			{
				panel.GetComponent<RectTransform>().anchoredPosition = new Vector2(x, y);
			}
		}

		public bool ToggleTestUI()
		{
			_showTestUI = !_showTestUI;
			Logger.LogInfo((object)$"ToggleTestUI: _showTestUI is now {_showTestUI}");
			_listDirty = true;
			return _showTestUI;
		}

		public void UpdateFrame()
		{
			if (!PanelExists)
			{
				if (GUIManager.Instance == null)
				{
					return;
				}
				CreatePanel();
				if (!PanelExists)
				{
					return;
				}
			}
			CompetitionManager instance = CompetitionManager.Instance;
			bool isRunning = instance.IsRunning;
			if (isRunning)
			{
				_showTestUI = false;
			}
			bool flag = isRunning || _showTestUI;
			panel.SetActive(flag);
			if (!flag)
			{
				return;
			}
			if (isRunning)
			{
				int num = Mathf.Max(0, Mathf.CeilToInt(instance.RemainingSeconds));
				timerText.text = $"Time Remaining: {num / 60:00}:{num % 60:00}";
			}
			if (!_listDirty)
			{
				return;
			}
			_listDirty = false;
			if (_showTestUI && !isRunning)
			{
				timerText.text = "Time Remaining: 05:00";
				RebuildList(fishCaughtList, new List<LeaderboardEntry>
				{
					new LeaderboardEntry
					{
						PlayerId = "1",
						PlayerName = "Fisherman1",
						FishCaught = 7
					},
					new LeaderboardEntry
					{
						PlayerId = "2",
						PlayerName = "Fisherman2",
						FishCaught = 4
					},
					new LeaderboardEntry
					{
						PlayerId = "3",
						PlayerName = "Fisherman3",
						FishCaught = 1
					}
				});
			}
			else if (isRunning)
			{
				RebuildList(fishCaughtList, (from e in instance.Entries.Values
					orderby e.FishCaught descending, e.PlayerName
					select e).ToList());
			}
			LayoutRebuilder.ForceRebuildLayoutImmediate(panel.GetComponent<RectTransform>());
		}

		private void RebuildList(Transform list, List<LeaderboardEntry> entries)
		{
			for (int num = list.childCount - 1; num >= 0; num--)
			{
				Object.Destroy((Object)(object)((Component)list.GetChild(num)).gameObject);
			}
			Font font = (((Object)(object)GUIManager.Instance.AveriaSerifBold != (Object)null) ? GUIManager.Instance.AveriaSerifBold : GUIManager.Instance.AveriaSerif);
			if (entries.Count == 0)
			{
				CreateText("NoEntries", list, "No catches yet", 12, (FontStyle)0, font, (TextAnchor)0);
				return;
			}
			for (int i = 0; i < entries.Count; i++)
			{
				LeaderboardEntry leaderboardEntry = entries[i];
				CreateText($"Entry{i + 1}", list, $"{i + 1}. {leaderboardEntry.PlayerName} -- {leaderboardEntry.FishCaught}", 12, (FontStyle)0, font, (TextAnchor)0);
			}
		}
	}
}
namespace FishComp.Patches
{
	public static class FishingPatch
	{
		public static void HumanoidPickupPostfix(Humanoid __instance, GameObject go, bool __result)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			if (__result && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !((Object)(object)go.GetComponent<Fish>() == (Object)null) && CompetitionManager.Instance != null && CompetitionManager.Instance.IsRunning)
			{
				Logger.LogInfo((object)"[FishComp][DIAG] HumanoidPickupPostfix counted=true");
				Logger.LogInfo((object)"Local player caught a fish — sending FishComp_ReportCatch to server");
				RpcManager.Instance.SendToServer("FishComp_ReportCatch", new ZPackage());
			}
		}
	}
}
namespace FishComp.Networking
{
	public class RpcManager
	{
		public const string RpcSyncState = "FishComp_SyncState";

		public const string RpcAnnounceWinner = "FishComp_AnnounceWinner";

		public const string RpcAnnounceStart = "FishComp_AnnounceStart";

		public const string RpcRequestStart = "FishComp_RequestStart";

		public const string RpcRequestEnd = "FishComp_RequestEnd";

		public const string RpcRequestReset = "FishComp_RequestReset";

		public const string RpcReportCatch = "FishComp_ReportCatch";

		private bool registered;

		private ZRoutedRpc registeredOn;

		private bool isApplyingRemoteState;

		public static RpcManager Instance { get; private set; }

		public bool Registered
		{
			get
			{
				if (registered)
				{
					return registeredOn == ZRoutedRpc.instance;
				}
				return false;
			}
		}

		public RpcManager()
		{
			Instance = this;
			CompetitionManager.Instance.OnLeaderboardChanged += OnLeaderboardChanged;
			CompetitionManager.Instance.OnCompetitionEnded += OnCompetitionEnded;
			Logger.LogInfo((object)"RpcManager subscribed to OnCompetitionEnded/OnLeaderboardChanged");
			PrefabManager.OnVanillaPrefabsAvailable += OnVanillaPrefabsAvailable;
		}

		private void OnVanillaPrefabsAvailable()
		{
			Register();
		}

		public void Register()
		{
			if (!Registered && ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.Register<ZPackage>("FishComp_SyncState", (Action<long, ZPackage>)RPC_SyncState);
				ZRoutedRpc.instance.Register<ZPackage>("FishComp_AnnounceWinner", (Action<long, ZPackage>)RPC_AnnounceWinner);
				ZRoutedRpc.instance.Register<ZPackage>("FishComp_AnnounceStart", (Action<long, ZPackage>)RPC_AnnounceStart);
				ZRoutedRpc.instance.Register<ZPackage>("FishComp_RequestStart", (Action<long, ZPackage>)RPC_RequestStart);
				ZRoutedRpc.instance.Register<ZPackage>("FishComp_RequestEnd", (Action<long, ZPackage>)RPC_RequestEnd);
				ZRoutedRpc.instance.Register<ZPackage>("FishComp_RequestReset", (Action<long, ZPackage>)RPC_RequestReset);
				ZRoutedRpc.instance.Register<ZPackage>("FishComp_ReportCatch", (Action<long, ZPackage>)RPC_ReportCatch);
				registeredOn = ZRoutedRpc.instance;
				registered = true;
				Logger.LogInfo((object)"RpcManager RPCs registered (SyncState, AnnounceWinner, AnnounceStart, RequestStart, RequestEnd, RequestReset, ReportCatch)");
			}
		}

		public void SendToServer(string rpcName, ZPackage pkg)
		{
			if (ZRoutedRpc.instance != null)
			{
				Register();
				Logger.LogInfo((object)("[FishComp][DIAG] SendToServer rpc=" + rpcName + " target=no-target"));
				ZRoutedRpc.instance.InvokeRoutedRPC(rpcName, new object[1] { pkg });
			}
		}

		private bool IsSenderAdmin(long sender)
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return false;
			}
			if (ZNet.instance.IsServer() && sender == ZNet.GetUID())
			{
				Logger.LogInfo((object)"[FishComp][DIAG] IsSenderAdmin branch=self-routed-server playerName=N/A steamId=N/A");
				return true;
			}
			ZNetPeer peer = ZNet.instance.GetPeer(sender);
			if (peer == null)
			{
				Logger.LogInfo((object)"[FishComp][DIAG] IsSenderAdmin branch=peer-null playerName=N/A steamId=N/A");
				Logger.LogWarning((object)$"[FishComp] IsSenderAdmin: no peer for sender {sender} (not server UID {ZNet.GetUID()}) — rejecting");
				return false;
			}
			if (peer.m_socket == null)
			{
				Logger.LogWarning((object)$"[FishComp] IsSenderAdmin: peer {sender} has no socket — rejecting");
				return false;
			}
			string hostName = peer.m_socket.GetHostName();
			bool flag = ZNet.instance.IsAdmin(hostName);
			Logger.LogInfo((object)("[FishComp][DIAG] IsSenderAdmin branch=" + (flag ? "peer-found-admin" : "peer-found-not-admin") + " playerName=" + peer.m_playerName + " steamId=" + hostName));
			return flag;
		}

		private static bool IsFromServer(long sender)
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return false;
			}
			if (ZNet.instance.IsServer())
			{
				return sender == ZNet.GetUID();
			}
			ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
			if (serverPeer != null)
			{
				return sender == serverPeer.m_uid;
			}
			return false;
		}

		public void BroadcastState()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			if (ZRoutedRpc.instance == null)
			{
				return;
			}
			Register();
			CompetitionManager instance = CompetitionManager.Instance;
			ZPackage val = new ZPackage();
			val.Write(instance.IsRunning);
			val.Write(instance.RemainingSeconds);
			val.Write(instance.Entries.Count);
			foreach (LeaderboardEntry value in instance.Entries.Values)
			{
				val.Write(value.PlayerId);
				val.Write(value.PlayerName);
				val.Write(value.FishCaught);
			}
			Logger.LogInfo((object)"[FishComp][DIAG] BroadcastState rpc=FishComp_SyncState target=Everybody");
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "FishComp_SyncState", new object[1] { val });
		}

		public void BroadcastWinner(CompetitionResult result)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			if (ZRoutedRpc.instance == null)
			{
				Logger.LogWarning((object)"BroadcastWinner called but ZRoutedRpc.instance is null — skipping");
				return;
			}
			Register();
			if (!Registered)
			{
				Logger.LogWarning((object)"BroadcastWinner called but RPCs are not registered — skipping");
				return;
			}
			if (result == null || result.TopByFishCaught == null)
			{
				Logger.LogInfo((object)"BroadcastWinner — no catches recorded, skipping announcement");
				return;
			}
			string playerName = result.TopByFishCaught.PlayerName;
			int fishCaught = result.TopByFishCaught.FishCaught;
			ZPackage val = new ZPackage();
			val.Write(playerName);
			val.Write(fishCaught);
			Logger.LogInfo((object)$"BroadcastWinner — most fish: {playerName} ({fishCaught} fish)");
			Logger.LogInfo((object)"[FishComp][DIAG] BroadcastWinner rpc=FishComp_AnnounceWinner target=Everybody");
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "FishComp_AnnounceWinner", new object[1] { val });
		}

		private void BroadcastAnnounceStart(int minutes)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			if (ZRoutedRpc.instance != null)
			{
				Register();
				ZPackage val = new ZPackage();
				val.Write(minutes);
				Logger.LogInfo((object)"[FishComp][DIAG] BroadcastAnnounceStart rpc=FishComp_AnnounceStart target=Everybody");
				ZRoutedRpc.instance.InvokeRoutedRPC(0L, "FishComp_AnnounceStart", new object[1] { val });
			}
		}

		private void RPC_SyncState(long sender, ZPackage pkg)
		{
			Logger.LogInfo((object)$"[FishComp][DIAG] RPC_SyncState sender={sender}");
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				return;
			}
			if (!IsFromServer(sender))
			{
				Logger.LogWarning((object)$"RPC_SyncState rejected — sender {sender} is not the server");
				return;
			}
			Logger.LogInfo((object)"RPC_SyncState received");
			bool isRunning = pkg.ReadBool();
			float remainingSeconds = pkg.ReadSingle();
			int num = pkg.ReadInt();
			List<LeaderboardEntry> list = new List<LeaderboardEntry>(num);
			for (int i = 0; i < num; i++)
			{
				list.Add(new LeaderboardEntry
				{
					PlayerId = pkg.ReadString(),
					PlayerName = pkg.ReadString(),
					FishCaught = pkg.ReadInt()
				});
			}
			isApplyingRemoteState = true;
			CompetitionManager.Instance.ApplyRemoteState(isRunning, remainingSeconds, list);
			isApplyingRemoteState = false;
		}

		private void RPC_AnnounceWinner(long sender, ZPackage pkg)
		{
			Logger.LogInfo((object)$"[FishComp][DIAG] RPC_AnnounceWinner sender={sender}");
			if (!IsFromServer(sender))
			{
				Logger.LogWarning((object)$"RPC_AnnounceWinner rejected — sender {sender} is not the server");
				return;
			}
			Logger.LogInfo((object)"RPC_AnnounceWinner received");
			string text = pkg.ReadString();
			int num = pkg.ReadInt();
			string text2 = (string.IsNullOrEmpty(text) ? "nobody" : $"{text} ({num} fish)");
			string text3 = "FishComp Results - Most Fish Caught: " + text2;
			if ((Object)(object)MessageHud.instance == (Object)null)
			{
				Logger.LogDebug((object)"RPC_AnnounceWinner — MessageHud.instance is null, skipping display");
			}
			else
			{
				MessageHud.instance.ShowMessage((MessageType)2, text3, 0, (Sprite)null, false, true);
			}
		}

		private void RPC_AnnounceStart(long sender, ZPackage pkg)
		{
			Logger.LogInfo((object)$"[FishComp][DIAG] RPC_AnnounceStart sender={sender}");
			if (!IsFromServer(sender))
			{
				Logger.LogWarning((object)$"RPC_AnnounceStart rejected — sender {sender} is not the server");
				return;
			}
			Logger.LogInfo((object)"RPC_AnnounceStart received");
			int num = pkg.ReadInt();
			if ((Object)(object)MessageHud.instance == (Object)null)
			{
				Logger.LogDebug((object)"RPC_AnnounceStart — MessageHud.instance is null, skipping display");
			}
			else
			{
				MessageHud.instance.ShowMessage((MessageType)2, $"A fishing competition has started! You have {num} minutes. Good luck!", 0, (Sprite)null, false, true);
			}
		}

		private void RPC_RequestStart(long sender, ZPackage pkg)
		{
			Logger.LogInfo((object)$"[FishComp][DIAG] RPC_RequestStart sender={sender}");
			Logger.LogInfo((object)$"RPC_RequestStart from sender {sender}");
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return;
			}
			if (!IsSenderAdmin(sender))
			{
				Logger.LogWarning((object)$"RPC_RequestStart rejected — sender {sender} is not admin");
				return;
			}
			int num = pkg.ReadInt();
			if (num < 1)
			{
				Logger.LogWarning((object)$"RPC_RequestStart rejected — invalid duration {num}");
				return;
			}
			if (CompetitionManager.Instance.IsRunning)
			{
				Logger.LogWarning((object)"RPC_RequestStart rejected — competition already running");
				return;
			}
			Logger.LogInfo((object)$"Starting competition for {num} minutes");
			CompetitionManager.Instance.StartCompetition(num);
			BroadcastAnnounceStart(num);
		}

		private void RPC_RequestEnd(long sender, ZPackage pkg)
		{
			Logger.LogInfo((object)$"[FishComp][DIAG] RPC_RequestEnd sender={sender}");
			Logger.LogInfo((object)$"RPC_RequestEnd from sender {sender}");
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				if (!IsSenderAdmin(sender))
				{
					Logger.LogWarning((object)$"RPC_RequestEnd rejected — sender {sender} is not admin");
					return;
				}
				if (!CompetitionManager.Instance.IsRunning)
				{
					Logger.LogWarning((object)"RPC_RequestEnd — no competition running");
					return;
				}
				Logger.LogInfo((object)"Ending competition via admin request");
				CompetitionManager.Instance.EndCompetition();
			}
		}

		private void RPC_RequestReset(long sender, ZPackage pkg)
		{
			Logger.LogInfo((object)$"[FishComp][DIAG] RPC_RequestReset sender={sender}");
			Logger.LogInfo((object)$"RPC_RequestReset from sender {sender}");
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				if (!IsSenderAdmin(sender))
				{
					Logger.LogWarning((object)$"RPC_RequestReset rejected — sender {sender} is not admin");
					return;
				}
				Logger.LogInfo((object)"Resetting competition via admin request");
				CompetitionManager.Instance.ResetCompetition();
			}
		}

		private void RPC_ReportCatch(long sender, ZPackage pkg)
		{
			Logger.LogInfo((object)$"[FishComp][DIAG] RPC_ReportCatch sender={sender}");
			Logger.LogInfo((object)$"RPC_ReportCatch from sender {sender}");
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || !CompetitionManager.Instance.IsRunning)
			{
				return;
			}
			ZNetPeer peer = ZNet.instance.GetPeer(sender);
			string durableIdentity;
			string text;
			if (peer != null)
			{
				if (peer.m_socket == null)
				{
					Logger.LogWarning((object)$"[FishComp] RPC_ReportCatch: peer {sender} has no socket — rejecting");
					return;
				}
				durableIdentity = peer.m_socket.GetHostName();
				text = peer.m_playerName;
			}
			else
			{
				if (!ZNet.instance.IsServer() || sender != ZNet.GetUID())
				{
					Logger.LogWarning((object)$"[FishComp] RPC_ReportCatch: no peer for sender {sender} (not server UID {ZNet.GetUID()}) — rejecting");
					return;
				}
				if ((Object)(object)Player.m_localPlayer == (Object)null)
				{
					Logger.LogWarning((object)$"[FishComp] RPC_ReportCatch: self-routed but no local player — rejecting sender {sender}");
					return;
				}
				durableIdentity = "#host";
				text = Player.m_localPlayer.GetPlayerName();
			}
			string opaqueKey = CompetitionManager.Instance.GetOpaqueKey(durableIdentity);
			if (string.IsNullOrEmpty(opaqueKey))
			{
				Logger.LogWarning((object)$"[FishComp] RPC_ReportCatch: no durable identity for sender {sender} — rejecting");
				return;
			}
			if (string.IsNullOrEmpty(text))
			{
				text = "Unknown";
			}
			Logger.LogInfo((object)$"[FishComp][DIAG] ReportCatch sender={sender} peerNull={peer == null} name={text} key={opaqueKey}");
			CompetitionManager.Instance.RecordCatch(opaqueKey, text);
		}

		private void OnLeaderboardChanged()
		{
			if (!isApplyingRemoteState && (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				BroadcastState();
			}
		}

		private void OnCompetitionEnded(CompetitionResult result)
		{
			Logger.LogInfo((object)"RpcManager.OnCompetitionEnded invoked");
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				BroadcastWinner(result);
				BroadcastState();
			}
			else
			{
				Logger.LogInfo((object)"OnCompetitionEnded — not server, skipping broadcast");
			}
		}
	}
}
namespace FishComp.Competition
{
	public class CompetitionResult
	{
		public LeaderboardEntry TopByFishCaught { get; set; }
	}
	public class CompetitionManager
	{
		public const string HostIdentity = "#host";

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

		private int nextIdentityKey;

		public static CompetitionManager Instance { get; private set; }

		public bool IsRunning { get; private set; }

		public float RemainingSeconds { get; private set; }

		public Dictionary<string, LeaderboardEntry> Entries { get; } = new Dictionary<string, LeaderboardEntry>();

		public event Action<CompetitionResult> OnCompetitionEnded;

		public event Action OnLeaderboardChanged;

		public CompetitionManager()
		{
			Instance = this;
		}

		public void StartCompetition(int minutes)
		{
			IsRunning = true;
			RemainingSeconds = minutes * 60;
			Entries.Clear();
			ClearIdentityKeys();
			this.OnLeaderboardChanged?.Invoke();
		}

		public CompetitionResult EndCompetition()
		{
			IsRunning = false;
			CompetitionResult competitionResult = new CompetitionResult
			{
				TopByFishCaught = Entries.Values.OrderByDescending((LeaderboardEntry e) => e.FishCaught).FirstOrDefault()
			};
			this.OnCompetitionEnded?.Invoke(competitionResult);
			return competitionResult;
		}

		public void ResetCompetition()
		{
			IsRunning = false;
			Entries.Clear();
			ClearIdentityKeys();
			RemainingSeconds = 0f;
			this.OnLeaderboardChanged?.Invoke();
		}

		public string GetOpaqueKey(string durableIdentity)
		{
			if (string.IsNullOrEmpty(durableIdentity))
			{
				return null;
			}
			if (!identityKeys.TryGetValue(durableIdentity, out var value))
			{
				int num = ++nextIdentityKey;
				value = "p" + num.ToString(CultureInfo.InvariantCulture);
				identityKeys[durableIdentity] = value;
			}
			return value;
		}

		private void ClearIdentityKeys()
		{
			identityKeys.Clear();
			nextIdentityKey = 0;
		}

		public void RecordCatch(string playerId, string playerName)
		{
			if (IsRunning)
			{
				if (!Entries.TryGetValue(playerId, out var value))
				{
					value = new LeaderboardEntry
					{
						PlayerId = playerId,
						PlayerName = playerName
					};
					Entries[playerId] = value;
				}
				value.RecordCatch();
				this.OnLeaderboardChanged?.Invoke();
			}
		}

		public void ApplyRemoteState(bool isRunning, float remainingSeconds, IEnumerable<LeaderboardEntry> entries)
		{
			IsRunning = isRunning;
			RemainingSeconds = remainingSeconds;
			Entries.Clear();
			foreach (LeaderboardEntry entry in entries)
			{
				Entries[entry.PlayerId] = entry;
			}
			this.OnLeaderboardChanged?.Invoke();
		}

		public void Tick(float deltaTime)
		{
			if (!IsRunning)
			{
				return;
			}
			RemainingSeconds -= deltaTime;
			if (RemainingSeconds <= 0f)
			{
				RemainingSeconds = 0f;
				if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
				{
					EndCompetition();
				}
			}
		}
	}
	[Serializable]
	public class LeaderboardEntry
	{
		public string PlayerId;

		public string PlayerName;

		public int FishCaught;

		public void RecordCatch()
		{
			FishCaught++;
		}
	}
	public class ServerTicker : MonoBehaviour
	{
		private float _syncTimer;

		private float _inWorldSeconds;

		private bool _loggedRegistrationTimeout;

		private bool _loggedWaitingForWorld;

		private void Start()
		{
			Logger.LogInfo((object)"ServerTicker started — competition tick active on all machines; periodic broadcast active on server");
		}

		private void RetryRpcRegistration()
		{
			if (RpcManager.Instance == null || RpcManager.Instance.Registered)
			{
				return;
			}
			RpcManager.Instance.Register();
			if (RpcManager.Instance.Registered)
			{
				Logger.LogInfo((object)"[FishComp][DIAG] ServerTicker: RpcManager registered");
				_inWorldSeconds = 0f;
				_loggedRegistrationTimeout = false;
				_loggedWaitingForWorld = false;
			}
			else if ((Object)(object)ZNet.instance == (Object)null)
			{
				_inWorldSeconds = 0f;
				_loggedRegistrationTimeout = false;
				if (!_loggedWaitingForWorld)
				{
					_loggedWaitingForWorld = true;
					Logger.LogInfo((object)"[FishComp][DIAG] ServerTicker: not in a world yet, deferring RPC registration (normal at the main menu)");
				}
			}
			else
			{
				_loggedWaitingForWorld = false;
				_inWorldSeconds += Time.deltaTime;
				if (_inWorldSeconds >= 30f && !_loggedRegistrationTimeout)
				{
					_loggedRegistrationTimeout = true;
					Logger.LogWarning((object)"[FishComp][DIAG] ServerTicker: RpcManager STILL UNREGISTERED 30s after joining a world. Inbound RPCs will not be received on this machine.");
				}
			}
		}

		private void Update()
		{
			RetryRpcRegistration();
			if (CompetitionManager.Instance == null)
			{
				return;
			}
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				if (CompetitionManager.Instance.IsRunning || CompetitionManager.Instance.Entries.Count > 0)
				{
					Logger.LogInfo((object)"[FishComp] Not in a world — clearing stale competition state");
					CompetitionManager.Instance.ResetCompetition();
				}
				return;
			}
			CompetitionManager.Instance.Tick(Time.deltaTime);
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && CompetitionManager.Instance.IsRunning)
			{
				_syncTimer -= Time.deltaTime;
				if (_syncTimer <= 0f)
				{
					_syncTimer = Plugin.ConfigSyncInterval.Value;
					Logger.LogDebug((object)"[FishComp][DIAG] ServerTicker periodic broadcast");
					RpcManager.Instance.BroadcastState();
				}
			}
		}
	}
}
namespace FishComp.Commands
{
	public class FishCompCommands
	{
		private class StartCommand : ConsoleCommand
		{
			public override string Name => "fishcomp_start";

			public override string Help => "Start a fishing competition. Usage: fishcomp_start <minutes>";

			public override bool IsCheat => false;

			public override bool IsSecret => false;

			public override bool OnlyServer => false;

			public override void Run(string[] args)
			{
				//IL_0083: Unknown result type (might be due to invalid IL or missing references)
				//IL_0089: Expected O, but got Unknown
				Logger.LogInfo((object)("[FishComp][DIAG] " + ((ConsoleCommand)this).Name + ".Run args=[" + string.Join(",", args) + "]"));
				if (CheckAdmin())
				{
					if (args.Length < 1 || !int.TryParse(args[0], out var result) || result < 1)
					{
						Console.instance.Print(((ConsoleCommand)this).Help);
						return;
					}
					Logger.LogInfo((object)$"fishcomp_start: requesting start for {result} minutes");
					ZPackage val = new ZPackage();
					val.Write(result);
					RpcManager.Instance.SendToServer("FishComp_RequestStart", val);
					Console.instance.Print($"Competition start request sent ({result} min). Announcement will appear when the server confirms.");
				}
			}
		}

		private class EndCommand : ConsoleCommand
		{
			public override string Name => "fishcomp_end";

			public override string Help => "End the current fishing competition and announce winners.";

			public override bool IsCheat => false;

			public override bool IsSecret => false;

			public override bool OnlyServer => false;

			public override void Run(string[] args)
			{
				//IL_005b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0065: Expected O, but got Unknown
				Logger.LogInfo((object)("[FishComp][DIAG] " + ((ConsoleCommand)this).Name + ".Run args=[" + string.Join(",", args) + "]"));
				if (CheckAdmin())
				{
					Logger.LogInfo((object)"fishcomp_end: sending request to server");
					RpcManager.Instance.SendToServer("FishComp_RequestEnd", new ZPackage());
					Console.instance.Print("Competition end request sent.");
				}
			}
		}

		private class ResetCommand : ConsoleCommand
		{
			public override string Name => "fishcomp_reset";

			public override string Help => "Reset and hide the leaderboard UI.";

			public override bool IsCheat => false;

			public override bool IsSecret => false;

			public override bool OnlyServer => false;

			public override void Run(string[] args)
			{
				//IL_005b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0065: Expected O, but got Unknown
				Logger.LogInfo((object)("[FishComp][DIAG] " + ((ConsoleCommand)this).Name + ".Run args=[" + string.Join(",", args) + "]"));
				if (CheckAdmin())
				{
					Logger.LogInfo((object)"fishcomp_reset: sending request to server");
					RpcManager.Instance.SendToServer("FishComp_RequestReset", new ZPackage());
					Console.instance.Print("Competition reset request sent.");
				}
			}
		}

		private class SetPosCommand : ConsoleCommand
		{
			public override string Name => "fishcomp_setpos";

			public override string Help => "Set leaderboard panel position. Usage: fishcomp_setpos <x> <y>";

			public override bool IsCheat => false;

			public override bool IsSecret => false;

			public override bool OnlyServer => false;

			public override void Run(string[] args)
			{
				Logger.LogInfo((object)("[FishComp][DIAG] " + ((ConsoleCommand)this).Name + ".Run args=[" + string.Join(",", args) + "]"));
				if (args.Length < 2 || !float.TryParse(args[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || !float.TryParse(args[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
				{
					Console.instance.Print(((ConsoleCommand)this).Help);
					return;
				}
				Logger.LogInfo((object)$"fishcomp_setpos: x={result}, y={result2}");
				Plugin.ConfigPositionX.Value = result;
				Plugin.ConfigPositionY.Value = result2;
				if (Plugin.Instance.UI != null)
				{
					Plugin.Instance.UI.SetPosition(result, result2);
				}
				Console.instance.Print($"Panel position set to ({result}, {result2})");
			}
		}

		private class TestUICommand : ConsoleCommand
		{
			public override string Name => "fishcomp_testui";

			public override string Help => "Toggle the leaderboard test panel (dummy data for positioning). Run again to hide.";

			public override bool IsCheat => false;

			public override bool IsSecret => false;

			public override bool OnlyServer => false;

			public override void Run(string[] args)
			{
				Logger.LogInfo((object)("[FishComp][DIAG] " + ((ConsoleCommand)this).Name + ".Run args=[" + string.Join(",", args) + "]"));
				Logger.LogInfo((object)"fishcomp_testui called");
				if (Plugin.Instance.UI == null)
				{
					Console.instance.Print("LeaderboardUI not initialised yet — load a world first.");
					return;
				}
				bool flag = Plugin.Instance.UI.ToggleTestUI();
				Console.instance.Print(flag ? "Test UI shown. Use fishcomp_setpos <x> <y> to reposition. Run fishcomp_testui again to hide." : "Test UI hidden.");
			}
		}

		public FishCompCommands()
		{
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new StartCommand());
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new EndCommand());
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new ResetCommand());
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new SetPosCommand());
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new TestUICommand());
		}

		private static bool CheckAdmin()
		{
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				return true;
			}
			if (!SynchronizationManager.Instance.PlayerIsAdmin)
			{
				Console.instance.Print("You must be a server admin to use this command.");
				return false;
			}
			return true;
		}
	}
}