Decompiled source of FishComp v1.0.0

FishComp.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
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 UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("FishComp")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("FishComp")]
[assembly: AssemblyTitle("FishComp")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace FishComp
{
	[BepInPlugin("com.fishcomp.valheim", "FishComp", "1.0.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.0.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 CompetitionManager CompetitionManager { get; private set; }

		public RpcManager RpcManager { get; private set; }

		public LeaderboardUI UI { get; private set; }

		public FishCompCommands Commands { get; private set; }

		private void Awake()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Expected O, but got Unknown
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Expected O, but got Unknown
			GameObject val = new GameObject("FishComp_UpdaterHost");
			Object.DontDestroyOnLoad((Object)(object)val);
			_updaterHost = val;
			Instance = this;
			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)");
			CompetitionManager = new CompetitionManager();
			RpcManager = new RpcManager(this);
			Commands = new FishCompCommands();
			PrefabManager.OnVanillaPrefabsAvailable += InitUI;
			harmony = new Harmony("com.fishcomp.valheim");
			harmony.PatchAll();
			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.0.0 loaded.");
		}

		private void InitUI()
		{
			Logger.LogInfo((object)"InitUI called — creating LeaderboardUI");
			UI = new LeaderboardUI(_updaterHost);
			PrefabManager.OnVanillaPrefabsAvailable -= InitUI;
		}
	}
}
namespace FishComp.UI
{
	public class LeaderboardUI
	{
		private class LeaderboardUpdater : MonoBehaviour
		{
			private LeaderboardUI leaderboardUI;

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

			private void Update()
			{
				if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
				{
					CompetitionManager.Instance?.Tick(Time.deltaTime);
				}
				leaderboardUI?.Refresh();
			}
		}

		private GameObject panel;

		private Text timerText;

		private Transform fishCaughtList;

		private bool _showTestUI;

		private bool PanelExists => (Object)(object)panel != (Object)null && Object.op_Implicit((Object)(object)panel);

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

		private void CreatePanel()
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Expected O, but got Unknown
			//IL_008a: 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_00d9: Expected O, but got Unknown
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: 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_016a: 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_01c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cc: Expected O, but got Unknown
			//IL_02c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_02dc: 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)
			Logger.LogInfo((object)"CreatePanel() entered");
			GameObject val = new GameObject("FishCompCanvasRoot", new Type[4]
			{
				typeof(RectTransform),
				typeof(Canvas),
				typeof(CanvasScaler),
				typeof(GraphicRaycaster)
			});
			Object.DontDestroyOnLoad((Object)(object)val);
			Canvas component = val.GetComponent<Canvas>();
			component.renderMode = (RenderMode)0;
			component.sortingOrder = 100;
			CanvasScaler component2 = val.GetComponent<CanvasScaler>();
			component2.uiScaleMode = (ScaleMode)1;
			component2.referenceResolution = new Vector2(1920f, 1080f);
			component2.screenMatchMode = (ScreenMatchMode)0;
			component2.matchWidthOrHeight = 0.5f;
			panel = new GameObject("FishCompPanel", new Type[2]
			{
				typeof(RectTransform),
				typeof(Image)
			});
			panel.transform.SetParent(val.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 val2 = panel.AddComponent<VerticalLayoutGroup>();
			((LayoutGroup)val2).padding = new RectOffset(10, 10, 8, 10);
			((HorizontalOrVerticalLayoutGroup)val2).spacing = 4f;
			((LayoutGroup)val2).childAlignment = (TextAnchor)0;
			((HorizontalOrVerticalLayoutGroup)val2).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)val2).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)val2).childForceExpandWidth = true;
			((HorizontalOrVerticalLayoutGroup)val2).childForceExpandHeight = false;
			ContentSizeFitter val3 = panel.AddComponent<ContentSizeFitter>();
			val3.horizontalFit = (FitMode)0;
			val3.verticalFit = (FitMode)2;
			Font font = (((Object)(object)GUIManager.Instance.AveriaSerifBold != (Object)null) ? GUIManager.Instance.AveriaSerifBold : GUIManager.Instance.AveriaSerif);
			CreateText("Title", panel.transform, "\ud83c\udfa3 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 — canvas.renderMode: {component.renderMode}, rect.anchoredPosition: {component3.anchoredPosition}, anchorMin: {component3.anchorMin}, pivot: {component3.pivot}");
		}

		private static Text CreateText(string name, Transform parent, string text, int fontSize, FontStyle style, Font font, TextAnchor alignment = (TextAnchor)0)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			//IL_0064: 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_0079: 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_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			//IL_00a7: 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;
			RectTransform component3 = val.GetComponent<RectTransform>();
			component3.sizeDelta = new Vector2(240f, 20f);
			return val.transform;
		}

		public void SetPosition(float x, float y)
		{
			//IL_0054: 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_0070: 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_0097: 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)
			Logger.LogInfo((object)$"SetPosition called: ({x}, {y}), PanelExists: {PanelExists}");
			if (PanelExists)
			{
				RectTransform component = panel.GetComponent<RectTransform>();
				Logger.LogInfo((object)$"Before: anchoredPosition={component.anchoredPosition}, anchorMin={component.anchorMin}, anchorMax={component.anchorMax}, pivot={component.pivot}");
				component.anchoredPosition = new Vector2(x, y);
				Logger.LogInfo((object)$"After: anchoredPosition={component.anchoredPosition}");
			}
		}

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

		public void Refresh()
		{
			if (!PanelExists)
			{
				if (GUIManager.Instance == null)
				{
					return;
				}
				CreatePanel();
			}
			if (!PanelExists)
			{
				return;
			}
			CompetitionManager instance = CompetitionManager.Instance;
			bool isRunning = instance.IsRunning;
			if (isRunning)
			{
				_showTestUI = false;
			}
			panel.SetActive(isRunning || _showTestUI);
			if (!isRunning && !_showTestUI)
			{
				return;
			}
			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
					}
				});
				LayoutRebuilder.ForceRebuildLayoutImmediate(panel.GetComponent<RectTransform>());
			}
			else
			{
				int num = Mathf.Max(0, Mathf.CeilToInt(instance.RemainingSeconds));
				timerText.text = $"Time Remaining: {num / 60:00}:{num % 60:00}";
				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)
		{
			if (!__result)
			{
				return;
			}
			Fish component = go.GetComponent<Fish>();
			if (!((Object)(object)component == (Object)null) && CompetitionManager.Instance != null && CompetitionManager.Instance.IsRunning && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				Player val = (Player)(object)((__instance is Player) ? __instance : null);
				if (!((Object)(object)val == (Object)null))
				{
					string playerId = val.GetPlayerID().ToString();
					string playerName = val.GetPlayerName();
					Logger.LogInfo((object)("Recording catch — player: " + playerName));
					CompetitionManager.Instance.RecordCatch(playerId, playerName);
				}
			}
		}
	}
}
namespace FishComp.Networking
{
	public class RpcManager
	{
		public const string RpcSyncState = "FishComp_SyncState";

		public const string RpcAnnounceWinner = "FishComp_AnnounceWinner";

		private readonly Plugin plugin;

		private bool registered;

		private bool isApplyingRemoteState;

		public static RpcManager Instance { get; private set; }

		public RpcManager(Plugin plugin)
		{
			this.plugin = plugin;
			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);
				registered = true;
				Logger.LogInfo((object)"RpcManager RPCs registered (SyncState, AnnounceWinner)");
			}
		}

		public void BroadcastState()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: 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);
			}
			ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "FishComp_SyncState", new object[1] { val });
		}

		public void BroadcastWinner(CompetitionResult result)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: 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;
			}
			ZPackage val = new ZPackage();
			val.Write(result.TopByFishCaught?.PlayerName ?? string.Empty);
			val.Write(result.TopByFishCaught?.FishCaught ?? 0);
			Logger.LogInfo((object)string.Format("BroadcastWinner — most fish: {0} ({1} fish)", result.TopByFishCaught?.PlayerName ?? "none", result.TopByFishCaught?.FishCaught ?? 0));
			ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "FishComp_AnnounceWinner", new object[1] { val });
		}

		private void RPC_SyncState(long sender, ZPackage pkg)
		{
			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)"RPC_AnnounceWinner received");
			string text = pkg.ReadString();
			int num = pkg.ReadInt();
			string text2 = (string.IsNullOrEmpty(text) ? "—" : $"{text} ({num} fish)");
			string text3 = "\ud83c\udfc6 FishComp Results — Most Fish Caught: " + text2;
			if ((Object)(object)MessageHud.instance == (Object)null)
			{
				Logger.LogWarning((object)"RPC_AnnounceWinner — MessageHud.instance is null, cannot show message");
				return;
			}
			Logger.LogInfo((object)("RPC_AnnounceWinner — showing message: " + text3));
			MessageHud.instance.ShowMessage((MessageType)2, text3, 0, (Sprite)null, false);
		}

		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 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();
			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();
			RemainingSeconds = 0f;
			this.OnLeaderboardChanged?.Invoke();
		}

		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)
			{
				RemainingSeconds -= deltaTime;
				if (RemainingSeconds <= 0f)
				{
					RemainingSeconds = 0f;
					EndCompetition();
				}
			}
		}
	}
	[Serializable]
	public class LeaderboardEntry
	{
		public string PlayerId;

		public string PlayerName;

		public int FishCaught;

		public void RecordCatch()
		{
			FishCaught++;
		}
	}
}
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)
			{
				if (!CheckAdmin() || !CheckServer())
				{
					return;
				}
				if (args.Length < 1 || !int.TryParse(args[0], out var result) || result < 1)
				{
					Console.instance.Print(((ConsoleCommand)this).Help);
					return;
				}
				if (CompetitionManager.Instance.IsRunning)
				{
					Console.instance.Print("A competition is already running.");
					return;
				}
				CompetitionManager.Instance.StartCompetition(result);
				Console.instance.Print($"FishComp started for {result} minutes. Good luck!");
				Chat instance = Chat.instance;
				if (instance != null)
				{
					instance.SendText((Type)2, $"\ud83c\udfa3 A fishing competition has started! You have {result} minutes. Good luck!");
				}
			}
		}

		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)
			{
				if (CheckAdmin() && CheckServer())
				{
					if (!CompetitionManager.Instance.IsRunning)
					{
						Console.instance.Print("No competition is currently running.");
						return;
					}
					CompetitionManager.Instance.EndCompetition();
					Console.instance.Print("FishComp ended.");
				}
			}
		}

		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)
			{
				if (CheckAdmin() && CheckServer())
				{
					CompetitionManager.Instance.ResetCompetition();
					RpcManager.Instance.BroadcastState();
					Console.instance.Print("FishComp reset.");
				}
			}
		}

		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)
			{
				if (args.Length < 2 || !float.TryParse(args[0], out var result) || !float.TryParse(args[1], out var result2))
				{
					Console.instance.Print(((ConsoleCommand)this).Help);
					return;
				}
				Logger.LogInfo((object)$"fishcomp_setpos: parsed x={result}, y={result2}. Plugin.Instance.UI null: {Plugin.Instance.UI == null}");
				Plugin.ConfigPositionX.Value = result;
				Plugin.ConfigPositionY.Value = result2;
				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_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 (!SynchronizationManager.Instance.PlayerIsAdmin)
			{
				Console.instance.Print("You must be a server admin to use this command.");
				return false;
			}
			return true;
		}

		private static bool CheckServer()
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				Console.instance.Print("This command must be run on the server.");
				return false;
			}
			return true;
		}
	}
}