Decompiled source of FishingPond v2.1.7

BepInEx\plugins\FishingPond\PromotorFishFarming.dll

Decompiled 5 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using Promotor.FishFarming.Core;
using UnityEngine;
using UnityEngine.Rendering;

[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("PromotorFishFarming")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+64063242d95185b23c75d8f11a2ab490a426d6ea")]
[assembly: AssemblyProduct("PromotorFishFarming")]
[assembly: AssemblyTitle("PromotorFishFarming")]
[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 Promotor.FishFarming.Core
{
	public sealed class SpeciesStock
	{
		public string SpeciesId = "";

		public int Eggs;

		public int Fry;

		public int Juveniles;

		public int Adults;

		public SpeciesStock Clone()
		{
			return (SpeciesStock)MemberwiseClone();
		}
	}
	public sealed class PondState
	{
		public string PondId = "";

		public long Revision;

		public int Feed;

		public long LastSimulatedUtcTicks;

		public Dictionary<string, SpeciesStock> Species = new Dictionary<string, SpeciesStock>(StringComparer.Ordinal);

		public Dictionary<string, HarvestTicket> Harvests = new Dictionary<string, HarvestTicket>(StringComparer.Ordinal);

		public PondState Clone()
		{
			PondState pondState = new PondState
			{
				PondId = PondId,
				Revision = Revision,
				Feed = Feed,
				LastSimulatedUtcTicks = LastSimulatedUtcTicks
			};
			foreach (KeyValuePair<string, SpeciesStock> item in Species)
			{
				pondState.Species.Add(item.Key, item.Value.Clone());
			}
			foreach (KeyValuePair<string, HarvestTicket> harvest in Harvests)
			{
				pondState.Harvests.Add(harvest.Key, harvest.Value.Clone());
			}
			return pondState;
		}
	}
	public enum HarvestStatus
	{
		Reserved,
		Delivered,
		Cancelled
	}
	public sealed class HarvestTicket
	{
		public string RequestId = "";

		public string PlayerId = "";

		public string SpeciesId = "";

		public int Count;

		public HarvestStatus Status;

		public long CreatedUtcTicks;

		public HarvestTicket Clone()
		{
			return (HarvestTicket)MemberwiseClone();
		}
	}
	public sealed class HarvestResult
	{
		public bool Ok;

		public string Code = "";

		public string Message = "";

		public int AdultsRemaining;

		public int HarvestableNow;

		public string RequestId = "";

		public long Revision;

		public static HarvestResult Reject(string code, string message)
		{
			return new HarvestResult
			{
				Ok = false,
				Code = code,
				Message = message
			};
		}
	}
	public interface IPondStore
	{
		T Execute<T>(string pondId, Func<PondState, T> operation);
	}
	public interface IAuthoritativeInventory
	{
		bool CanAccept(string playerId, string itemId, int amount);

		bool GrantExactlyOnce(string playerId, string itemId, int amount, string requestId);

		bool ConsumeExactlyOnce(string playerId, string itemId, int amount, string requestId);
	}
	public sealed class PondRules
	{
		public int MinimumAdultBreeders = 2;

		public int MaxHarvestPerRequest = 100;

		public int MaxPondAdultsPerSpecies = 500;
	}
	public sealed class PondService
	{
		private readonly IPondStore store;

		private readonly IAuthoritativeInventory inventory;

		private readonly PondRules rules;

		public PondService(IPondStore store, IAuthoritativeInventory inventory, PondRules rules)
		{
			this.store = store ?? throw new ArgumentNullException("store");
			this.inventory = inventory ?? throw new ArgumentNullException("inventory");
			this.rules = rules ?? throw new ArgumentNullException("rules");
		}

		public HarvestResult ReserveHarvest(string serverResolvedPlayerId, string pondId, string speciesId, int requestedCount, string requestId, bool withinRange, bool permitted)
		{
			if (string.IsNullOrWhiteSpace(serverResolvedPlayerId) || string.IsNullOrWhiteSpace(pondId) || string.IsNullOrWhiteSpace(speciesId) || string.IsNullOrWhiteSpace(requestId) || requestId.Length > 128)
			{
				return HarvestResult.Reject("BAD_REQUEST", "Invalid request fields");
			}
			if (!withinRange || !permitted)
			{
				return HarvestResult.Reject("NOT_AUTHORIZED", "Move closer or check access");
			}
			if (requestedCount < 1 || requestedCount > rules.MaxHarvestPerRequest)
			{
				return HarvestResult.Reject("BAD_COUNT", "Invalid harvest quantity");
			}
			return store.Execute(pondId, delegate(PondState pond)
			{
				if (pond.Harvests.TryGetValue(requestId, out var value))
				{
					if (value.PlayerId != serverResolvedPlayerId || value.SpeciesId != speciesId || value.Count != requestedCount)
					{
						return HarvestResult.Reject("ID_CONFLICT", "Request ID reused with different payload");
					}
					return Response(pond, value, "ALREADY_PROCESSED", "Previous request returned");
				}
				if (!pond.Species.TryGetValue(speciesId, out var value2))
				{
					return HarvestResult.Reject("UNKNOWN_SPECIES", "This species is not stocked");
				}
				if (value2.Adults < 0 || value2.Adults > rules.MaxPondAdultsPerSpecies)
				{
					return HarvestResult.Reject("CORRUPT_STATE", "Invalid population count");
				}
				int num = 0;
				foreach (HarvestTicket value3 in pond.Harvests.Values)
				{
					if (value3.SpeciesId == speciesId && value3.Status == HarvestStatus.Reserved)
					{
						num = checked(num + value3.Count);
					}
				}
				int num2 = Math.Max(0, value2.Adults - rules.MinimumAdultBreeders - num);
				if (requestedCount > num2)
				{
					return HarvestResult.Reject("BREEDERS_PROTECTED", "At least two adults must stay in the pond");
				}
				HarvestTicket harvestTicket = new HarvestTicket
				{
					RequestId = requestId,
					PlayerId = serverResolvedPlayerId,
					SpeciesId = speciesId,
					Count = requestedCount,
					Status = HarvestStatus.Reserved,
					CreatedUtcTicks = DateTime.UtcNow.Ticks
				};
				pond.Harvests.Add(requestId, harvestTicket);
				pond.Revision++;
				return Response(pond, harvestTicket, "RESERVED", "Harvest reserved for delivery");
			});
		}

		public HarvestResult Deliver(string serverResolvedPlayerId, string pondId, string requestId, string fishItemId, bool withinRange, bool permitted)
		{
			if (!withinRange || !permitted)
			{
				return HarvestResult.Reject("NOT_AUTHORIZED", "Not allowed to collect");
			}
			if (string.IsNullOrWhiteSpace(requestId))
			{
				return HarvestResult.Reject("BAD_REQUEST", "Missing request ID");
			}
			return store.Execute(pondId, delegate(PondState pond)
			{
				if (!pond.Harvests.TryGetValue(requestId, out var value) || value.PlayerId != serverResolvedPlayerId)
				{
					return HarvestResult.Reject("NOT_FOUND", "Harvest reservation missing");
				}
				if (value.Status == HarvestStatus.Delivered)
				{
					return Response(pond, value, "ALREADY_DELIVERED", "Already delivered");
				}
				if (value.Status != HarvestStatus.Reserved)
				{
					return HarvestResult.Reject("CANCELLED", "Harvest was cancelled");
				}
				if (!inventory.CanAccept(serverResolvedPlayerId, fishItemId, value.Count))
				{
					return HarvestResult.Reject("NO_SPACE", "Inventory has insufficient capacity");
				}
				if (!inventory.GrantExactlyOnce(serverResolvedPlayerId, fishItemId, value.Count, requestId))
				{
					return HarvestResult.Reject("DELIVERY_FAILED", "Inventory grant failed; reservation retained");
				}
				SpeciesStock speciesStock = pond.Species[value.SpeciesId];
				if (speciesStock.Adults - value.Count < rules.MinimumAdultBreeders)
				{
					throw new InvalidOperationException("Protected breeder invariant violated. Quarantine pond for repair.");
				}
				speciesStock.Adults -= value.Count;
				value.Status = HarvestStatus.Delivered;
				pond.Revision++;
				return Response(pond, value, "DELIVERED", "Harvest collected");
			});
		}

		public HarvestResult DepositFeed(string playerId, string pondId, string feedItemId, int amount, string requestId, bool withinRange, bool permitted, int maxFeed = 100)
		{
			if (!withinRange || !permitted)
			{
				return HarvestResult.Reject("NOT_AUTHORIZED", "Not allowed to feed");
			}
			if (amount < 1 || amount > 100 || string.IsNullOrWhiteSpace(requestId))
			{
				return HarvestResult.Reject("BAD_REQUEST", "Invalid deposit request");
			}
			return HarvestResult.Reject("NOT_IMPLEMENTED", "Feed deposit requires a durable receipt ledger");
		}

		private HarvestResult Response(PondState pond, HarvestTicket ticket, string code, string message)
		{
			int num = 0;
			foreach (HarvestTicket value in pond.Harvests.Values)
			{
				if (value.SpeciesId == ticket.SpeciesId && value.Status == HarvestStatus.Reserved)
				{
					num += value.Count;
				}
			}
			int adults = pond.Species[ticket.SpeciesId].Adults;
			return new HarvestResult
			{
				Ok = true,
				Code = code,
				Message = message,
				RequestId = ticket.RequestId,
				AdultsRemaining = adults,
				HarvestableNow = Math.Max(0, adults - rules.MinimumAdultBreeders - num),
				Revision = pond.Revision
			};
		}
	}
	public static class SpeciesCatalog
	{
		public static readonly Dictionary<string, string[]> BaitSpecies = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
		{
			{
				"FishingBait",
				new string[2] { "Fish1", "Fish2" }
			},
			{
				"MossyFishingBait",
				new string[1] { "Fish3" }
			},
			{
				"StickyFishingBait",
				new string[1] { "Fish4" }
			},
			{
				"StingyFishingBait",
				new string[1] { "Fish5" }
			},
			{
				"HeavyFishingBait",
				new string[2] { "Fish6", "Fish7" }
			},
			{
				"MistyFishingBait",
				new string[2] { "Fish8", "Fish9" }
			},
			{
				"HotFishingBait",
				new string[1] { "Fish10" }
			},
			{
				"FrostyFishingBait",
				new string[1] { "Fish11" }
			}
		};

		public static bool IsEligible(string baitPrefab, string speciesPrefab)
		{
			if (!BaitSpecies.TryGetValue(baitPrefab ?? "", out var value))
			{
				return false;
			}
			string[] array = value;
			for (int i = 0; i < array.Length; i++)
			{
				if (string.Equals(array[i], speciesPrefab, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}
	}
	public sealed class FishingReservation
	{
		public string CastId = "";

		public string PeerId = "";

		public string SpeciesId = "";

		public string BaitId = "";

		public long ExpiresUtcTicks;

		public FishingReservation Clone()
		{
			return (FishingReservation)MemberwiseClone();
		}
	}
	public sealed class FishCohort
	{
		public int Count;

		public double AgeDays;

		public int Quality;

		public FishCohort(int count, double ageDays, int quality = 1)
		{
			Count = count;
			AgeDays = ageDays;
			Quality = FishInheritance.ClampQuality(quality);
		}
	}
	public sealed class PondPopulation
	{
		public string PondId = "";

		public long Revision;

		public long LastSimulatedUtcTicks;

		public int Feed;

		public int Capacity = 20;

		public double LastWorldSeconds = -1.0;

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

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

		public readonly Dictionary<string, List<FishCohort>> OffspringCohorts = new Dictionary<string, List<FishCohort>>(StringComparer.Ordinal);

		public readonly Dictionary<string, List<int>> MatureOffspringQualities = new Dictionary<string, List<int>>(StringComparer.Ordinal);

		public bool BalancedModelInitialized;

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

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

		public readonly Dictionary<string, HarvestTicket> BasketReservations = new Dictionary<string, HarvestTicket>(StringComparer.Ordinal);
	}
	public static class PondEcology
	{
		public static double PolygonArea(IList<double[]> vertices)
		{
			if (vertices == null || vertices.Count < 3)
			{
				return 0.0;
			}
			double num = 0.0;
			double num2 = vertices[0][0];
			double num3 = vertices[0][1];
			for (int i = 1; i + 1 < vertices.Count; i++)
			{
				num += (vertices[i][0] - num2) * (vertices[i + 1][1] - num3) - (vertices[i + 1][0] - num2) * (vertices[i][1] - num3);
			}
			return Math.Abs(num) / 2.0;
		}

		public static int CapacityFromArea(double areaSquareMeters, double areaPerFish, int hardMaximum)
		{
			if (double.IsNaN(areaSquareMeters) || double.IsInfinity(areaSquareMeters) || areaSquareMeters <= 0.0 || double.IsNaN(areaPerFish) || areaPerFish <= 0.0 || double.IsInfinity(areaPerFish))
			{
				return 0;
			}
			return Math.Max(2, Math.Min(Math.Max(2, hardMaximum), (int)Math.Min(2147483647.0, Math.Floor(areaSquareMeters / areaPerFish))));
		}

		public static Dictionary<string, int> Occupancy(PondPopulation pond, IDictionary<string, int> physicalFish)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			if (physicalFish != null)
			{
				foreach (KeyValuePair<string, int> item in physicalFish)
				{
					if (item.Value > 0)
					{
						dictionary[item.Key] = item.Value;
					}
				}
			}
			foreach (KeyValuePair<string, SpeciesStock> item2 in pond.Species)
			{
				int value = 0;
				dictionary.TryGetValue(item2.Key, out value);
				int num = ((value >= 2) ? Math.Max(0, item2.Value.Adults - 2) : 0);
				int num2 = Math.Max(0, item2.Value.Eggs) + Math.Max(0, item2.Value.Fry) + Math.Max(0, item2.Value.Juveniles);
				int num3 = value + num + num2;
				if (num3 > 0)
				{
					dictionary[item2.Key] = num3;
				}
			}
			return dictionary;
		}

		public static Dictionary<string, int> SpeciesQuotas(int capacity, IEnumerable<string> species)
		{
			List<string> list = new List<string>();
			foreach (string item in species)
			{
				if (!string.IsNullOrEmpty(item) && !list.Contains(item))
				{
					list.Add(item);
				}
			}
			list.Sort(StringComparer.Ordinal);
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			if (list.Count == 0)
			{
				return dictionary;
			}
			int num = Math.Max(0, capacity) / list.Count;
			int num2 = Math.Max(0, capacity) % list.Count;
			for (int i = 0; i < list.Count; i++)
			{
				dictionary[list[i]] = num + ((i < num2) ? 1 : 0);
			}
			return dictionary;
		}
	}
	public sealed class PondEngine
	{
		public const int ProtectedBreeders = 2;

		public const int MaxSimulatedCyclesPerCall = 4320;

		public readonly TimeSpan Cycle = TimeSpan.FromMinutes(30.0);

		public readonly TimeSpan CastTtl = TimeSpan.FromMinutes(3.0);

		private readonly object sync = new object();

		private readonly Random random;

		private static readonly Dictionary<string, double[]> BalancedDays = new Dictionary<string, double[]>(StringComparer.Ordinal)
		{
			{
				"Fish1",
				new double[2] { 0.5, 1.5 }
			},
			{
				"Fish2",
				new double[2] { 0.5, 1.5 }
			},
			{
				"Fish3",
				new double[2] { 1.0, 2.25 }
			},
			{
				"Fish4",
				new double[2] { 1.25, 2.5 }
			},
			{
				"Fish5",
				new double[2] { 0.75, 1.75 }
			},
			{
				"Fish6",
				new double[2] { 0.75, 2.0 }
			},
			{
				"Fish7",
				new double[2] { 1.0, 2.5 }
			},
			{
				"Fish8",
				new double[2] { 1.0, 2.25 }
			},
			{
				"Fish9",
				new double[2] { 1.25, 3.0 }
			},
			{
				"Fish10",
				new double[2] { 1.75, 4.0 }
			},
			{
				"Fish11",
				new double[2] { 1.5, 3.5 }
			},
			{
				"Fish12",
				new double[2] { 1.25, 3.0 }
			}
		};

		public static int BreedingPairs(int adultCount)
		{
			return Math.Max(0, adultCount / 2);
		}

		public PondEngine(int seed = 512)
		{
			random = new Random(seed);
		}

		public int Available(PondPopulation pond, string speciesId, long nowTicks)
		{
			lock (sync)
			{
				if (!pond.Species.TryGetValue(speciesId, out var value))
				{
					return 0;
				}
				int num = 0;
				foreach (FishingReservation value2 in pond.FishingReservations.Values)
				{
					if (value2.SpeciesId == speciesId && value2.ExpiresUtcTicks > nowTicks)
					{
						num++;
					}
				}
				foreach (HarvestTicket value3 in pond.BasketReservations.Values)
				{
					if (value3.SpeciesId == speciesId && value3.Status == HarvestStatus.Reserved)
					{
						num += value3.Count;
					}
				}
				return Math.Max(0, value.Adults - 2 - num);
			}
		}

		public string ReserveCast(PondPopulation pond, string peerId, string baitPrefab, string castId, long nowTicks)
		{
			lock (sync)
			{
				if (string.IsNullOrWhiteSpace(peerId) || string.IsNullOrWhiteSpace(castId) || castId.Length > 96)
				{
					return "BAD_REQUEST";
				}
				Expire(pond, nowTicks);
				if (pond.FishingReservations.TryGetValue(castId, out var value))
				{
					return (value.PeerId == peerId) ? value.SpeciesId : "CAST_ID_CONFLICT";
				}
				foreach (FishingReservation value2 in pond.FishingReservations.Values)
				{
					if (value2.PeerId == peerId && value2.ExpiresUtcTicks > nowTicks)
					{
						return "ALREADY_FISHING";
					}
				}
				List<string> list = new List<string>();
				foreach (SpeciesStock value3 in pond.Species.Values)
				{
					if (SpeciesCatalog.IsEligible(baitPrefab, value3.SpeciesId) && Available(pond, value3.SpeciesId, nowTicks) > 0)
					{
						list.Add(value3.SpeciesId);
					}
				}
				if (list.Count == 0)
				{
					return "NO_ELIGIBLE_SURPLUS";
				}
				string text = list[random.Next(list.Count)];
				pond.FishingReservations.Add(castId, new FishingReservation
				{
					CastId = castId,
					PeerId = peerId,
					BaitId = baitPrefab,
					SpeciesId = text,
					ExpiresUtcTicks = nowTicks + CastTtl.Ticks
				});
				pond.Revision++;
				return text;
			}
		}

		public bool ReleaseCast(PondPopulation pond, string peerId, string castId)
		{
			lock (sync)
			{
				if (!pond.FishingReservations.TryGetValue(castId, out var value) || value.PeerId != peerId)
				{
					return false;
				}
				pond.FishingReservations.Remove(castId);
				pond.Revision++;
				return true;
			}
		}

		public bool CommitVerifiedCatch(PondPopulation pond, string peerId, string castId, long nowTicks, Func<string, string, string, bool> durableExactlyOnceFishDelivery)
		{
			lock (sync)
			{
				if (!pond.FishingReservations.TryGetValue(castId, out var value) || value.PeerId != peerId || value.ExpiresUtcTicks <= nowTicks)
				{
					return false;
				}
				if (!pond.Species.TryGetValue(value.SpeciesId, out var value2) || value2.Adults <= 2)
				{
					return false;
				}
				if (durableExactlyOnceFishDelivery == null || !durableExactlyOnceFishDelivery(peerId, value.SpeciesId, castId))
				{
					return false;
				}
				value2.Adults--;
				pond.FishingReservations.Remove(castId);
				pond.Revision++;
				return true;
			}
		}

		public void Expire(PondPopulation pond, long nowTicks)
		{
			lock (sync)
			{
				List<string> list = new List<string>();
				foreach (KeyValuePair<string, FishingReservation> fishingReservation in pond.FishingReservations)
				{
					if (fishingReservation.Value.ExpiresUtcTicks <= nowTicks)
					{
						list.Add(fishingReservation.Key);
					}
				}
				foreach (string item in list)
				{
					pond.FishingReservations.Remove(item);
				}
				if (list.Count > 0)
				{
					pond.Revision++;
				}
			}
		}

		public static double EggIntervalDays(string speciesId)
		{
			if (!BalancedDays.TryGetValue(speciesId ?? "", out var value))
			{
				return 1.0;
			}
			return value[0];
		}

		public static double MaturityDays(string speciesId)
		{
			if (!BalancedDays.TryGetValue(speciesId ?? "", out var value))
			{
				return 3.0;
			}
			return value[1];
		}

		public static int CycleSecondsFor(string speciesId)
		{
			return (int)Math.Round(EggIntervalDays(speciesId) * 1800.0);
		}

		private static List<FishCohort> GetCohorts(PondPopulation pond, string speciesId)
		{
			if (!pond.OffspringCohorts.TryGetValue(speciesId, out var value))
			{
				value = new List<FishCohort>();
				pond.OffspringCohorts[speciesId] = value;
			}
			return value;
		}

		private static void RefreshStages(SpeciesStock stock, List<FishCohort> cohorts, double maturityMultiplier = 1.0)
		{
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			double num4 = MaturityDays(stock.SpeciesId) * maturityMultiplier;
			foreach (FishCohort cohort in cohorts)
			{
				if (cohort.AgeDays < num4 / 3.0)
				{
					num += cohort.Count;
				}
				else if (cohort.AgeDays < num4 * 2.0 / 3.0)
				{
					num2 += cohort.Count;
				}
				else
				{
					num3 += cohort.Count;
				}
			}
			stock.Eggs = num;
			stock.Fry = num2;
			stock.Juveniles = num3;
		}

		private static void AgeCohorts(PondPopulation pond, SpeciesStock stock, double days, double maturityMultiplier = 1.0)
		{
			List<FishCohort> cohorts = GetCohorts(pond, stock.SpeciesId);
			double num = MaturityDays(stock.SpeciesId) * maturityMultiplier;
			for (int num2 = cohorts.Count - 1; num2 >= 0; num2--)
			{
				FishCohort fishCohort = cohorts[num2];
				fishCohort.AgeDays += days;
				if (fishCohort.AgeDays + 1E-09 >= num)
				{
					stock.Adults += fishCohort.Count;
					if (!pond.MatureOffspringQualities.TryGetValue(stock.SpeciesId, out var value))
					{
						value = (pond.MatureOffspringQualities[stock.SpeciesId] = new List<int>());
					}
					for (int i = 0; i < fishCohort.Count; i++)
					{
						value.Add(FishInheritance.ClampQuality(fishCohort.Quality));
					}
					cohorts.RemoveAt(num2);
					pond.Revision++;
				}
			}
			int eggs = stock.Eggs;
			int fry = stock.Fry;
			int juveniles = stock.Juveniles;
			RefreshStages(stock, cohorts, maturityMultiplier);
			if (eggs != stock.Eggs || fry != stock.Fry || juveniles != stock.Juveniles)
			{
				pond.Revision++;
			}
		}

		private static void EnsureBalancedModel(PondPopulation pond)
		{
			if (pond.BalancedModelInitialized)
			{
				return;
			}
			foreach (SpeciesStock value in pond.Species.Values)
			{
				List<FishCohort> cohorts = GetCohorts(pond, value.SpeciesId);
				if (cohorts.Count == 0)
				{
					double num = MaturityDays(value.SpeciesId);
					if (value.Eggs > 0)
					{
						cohorts.Add(new FishCohort(value.Eggs, 0.0));
					}
					if (value.Fry > 0)
					{
						cohorts.Add(new FishCohort(value.Fry, num / 3.0));
					}
					if (value.Juveniles > 0)
					{
						cohorts.Add(new FishCohort(value.Juveniles, num * 2.0 / 3.0));
					}
				}
				RefreshStages(value, cohorts);
			}
			pond.SpeciesProgressSeconds.Clear();
			pond.BalancedModelInitialized = true;
		}

		public void AdvanceWorld(PondPopulation pond, double nowSeconds, IDictionary<string, int> liveBreeders, double dayLengthSeconds = 1800.0, IDictionary<string, int> observedPhysicalFish = null, double eggIntervalMultiplier = 1.0, int feedUnitsPerEgg = 1, double maturityMultiplier = 1.0, IDictionary<string, int> breederQualities = null)
		{
			lock (sync)
			{
				if (double.IsNaN(nowSeconds) || double.IsInfinity(nowSeconds) || nowSeconds < 0.0 || double.IsNaN(dayLengthSeconds) || dayLengthSeconds <= 0.0 || double.IsInfinity(dayLengthSeconds))
				{
					return;
				}
				eggIntervalMultiplier = Math.Max(0.25, Math.Min(10.0, eggIntervalMultiplier));
				maturityMultiplier = Math.Max(0.25, Math.Min(10.0, maturityMultiplier));
				feedUnitsPerEgg = Math.Max(1, Math.Min(20, feedUnitsPerEgg));
				EnsureBalancedModel(pond);
				if (pond.LastWorldSeconds < 0.0 || nowSeconds < pond.LastWorldSeconds)
				{
					pond.LastWorldSeconds = nowSeconds;
					return;
				}
				double num = Math.Min(nowSeconds - pond.LastWorldSeconds, 8.0 * dayLengthSeconds);
				if (num < 1E-07)
				{
					return;
				}
				pond.LastWorldSeconds += num;
				double num2 = num / dayLengthSeconds;
				List<string> list = new List<string>(pond.Species.Keys);
				list.Sort(StringComparer.Ordinal);
				double num3 = num2;
				int num4 = 256;
				while (num3 > 1E-09 && num4 > 0)
				{
					double num5 = num3;
					foreach (string item in list)
					{
						pond.SpawnProgressDays.TryGetValue(item, out var value);
						num5 = Math.Min(num5, Math.Max(0.0, EggIntervalDays(item) * eggIntervalMultiplier - value));
					}
					if (num5 > 0.0)
					{
						foreach (string item2 in list)
						{
							AgeCohorts(pond, pond.Species[item2], num5, maturityMultiplier);
							pond.SpawnProgressDays.TryGetValue(item2, out var value2);
							pond.SpawnProgressDays[item2] = value2 + num5;
						}
						num3 = Math.Max(0.0, num3 - num5);
					}
					List<string> list2 = new List<string>();
					foreach (string item3 in list)
					{
						pond.SpawnProgressDays.TryGetValue(item3, out var value3);
						if (value3 + 1E-09 >= EggIntervalDays(item3) * eggIntervalMultiplier)
						{
							list2.Add(item3);
						}
					}
					if (list2.Count == 0)
					{
						break;
					}
					foreach (string item4 in list2)
					{
						pond.SpawnProgressDays[item4] = Math.Max(0.0, pond.SpawnProgressDays[item4] - EggIntervalDays(item4) * eggIntervalMultiplier);
					}
					num4 -= list2.Count;
					IDictionary<string, int> physicalFish = observedPhysicalFish ?? liveBreeders;
					Dictionary<string, int> dictionary = PondEcology.Occupancy(pond, physicalFish);
					int num6 = 0;
					foreach (int value8 in dictionary.Values)
					{
						num6 += value8;
					}
					Dictionary<string, int> dictionary2 = PondEcology.SpeciesQuotas(pond.Capacity, dictionary.Keys);
					Dictionary<string, int> dictionary3 = new Dictionary<string, int>(StringComparer.Ordinal);
					foreach (string item5 in list2)
					{
						int value4 = 0;
						liveBreeders?.TryGetValue(item5, out value4);
						dictionary3[item5] = BreedingPairs(value4);
					}
					if (list2.Count > 1)
					{
						int num7 = (int)Math.Abs(pond.Revision % list2.Count);
						if (num7 > 0)
						{
							List<string> list3 = new List<string>();
							for (int i = 0; i < list2.Count; i++)
							{
								list3.Add(list2[(i + num7) % list2.Count]);
							}
							list2 = list3;
						}
					}
					bool flag = true;
					while (flag && pond.Feed >= feedUnitsPerEgg && num6 < pond.Capacity)
					{
						flag = false;
						foreach (string item6 in list2)
						{
							int num8 = dictionary3[item6];
							int value5 = 0;
							int value6 = 0;
							dictionary.TryGetValue(item6, out value5);
							dictionary2.TryGetValue(item6, out value6);
							if (num8 > 0 && value5 < value6 && num6 < pond.Capacity && pond.Feed >= feedUnitsPerEgg)
							{
								SpeciesStock stock = pond.Species[item6];
								int value7 = 1;
								breederQualities?.TryGetValue(item6, out value7);
								GetCohorts(pond, item6).Add(new FishCohort(1, 0.0, FishInheritance.ClampQuality(value7)));
								dictionary3[item6]--;
								dictionary[item6] = value5 + 1;
								num6++;
								pond.Feed -= feedUnitsPerEgg;
								pond.Revision++;
								RefreshStages(stock, GetCohorts(pond, item6), maturityMultiplier);
								flag = true;
							}
						}
					}
				}
				if (num3 > 1E-09 && num4 <= 0)
				{
					pond.LastWorldSeconds -= num3 * dayLengthSeconds;
				}
			}
		}

		public void Advance(PondPopulation pond, long nowTicks, IDictionary<string, int> authorizedLiveBreeders = null)
		{
			lock (sync)
			{
				if (pond.LastSimulatedUtcTicks == 0L)
				{
					pond.LastSimulatedUtcTicks = nowTicks;
				}
				else
				{
					if (nowTicks <= pond.LastSimulatedUtcTicks)
					{
						return;
					}
					long num = Math.Min(4320L, (nowTicks - pond.LastSimulatedUtcTicks) / Cycle.Ticks);
					if (num == 0L)
					{
						return;
					}
					for (long num2 = 0L; num2 < num; num2++)
					{
						int num3 = 0;
						foreach (SpeciesStock value2 in pond.Species.Values)
						{
							num3 += value2.Adults + value2.Juveniles + value2.Fry + value2.Eggs;
						}
						foreach (SpeciesStock value3 in pond.Species.Values)
						{
							int juveniles = value3.Juveniles;
							int fry = value3.Fry;
							int eggs = value3.Eggs;
							value3.Adults += juveniles;
							value3.Juveniles = fry;
							value3.Fry = eggs;
							value3.Eggs = 0;
						}
						foreach (SpeciesStock value4 in pond.Species.Values)
						{
							int value;
							int val = ((authorizedLiveBreeders == null) ? BreedingPairs(value4.Adults) : (authorizedLiveBreeders.TryGetValue(value4.SpeciesId, out value) ? BreedingPairs(value) : 0));
							int val2 = Math.Max(0, pond.Capacity - num3);
							int num4 = (value4.Eggs = Math.Min(Math.Min(val, val2), Math.Max(0, pond.Feed)));
							pond.Feed -= num4;
							num3 += num4;
						}
					}
					pond.LastSimulatedUtcTicks += num * Cycle.Ticks;
					pond.Revision++;
				}
			}
		}
	}
	public static class PondContainment
	{
		public static bool IsAboveWater(float fishY, float waterY, float tolerance)
		{
			if (!float.IsNaN(fishY) && !float.IsInfinity(fishY) && !float.IsNaN(waterY) && !float.IsInfinity(waterY))
			{
				return fishY > waterY + Math.Max(0f, tolerance);
			}
			return false;
		}

		public static float SafeReturnHeight(float anchorY, float waterY, float depth)
		{
			if (float.IsNaN(waterY) || float.IsInfinity(waterY))
			{
				return anchorY;
			}
			float num = waterY - Math.Max(0.25f, depth);
			if (!float.IsNaN(anchorY) && !float.IsInfinity(anchorY))
			{
				return Math.Min(anchorY, num);
			}
			return num;
		}
	}
	public static class FishInheritance
	{
		public static int ClampQuality(int quality)
		{
			return Math.Max(1, Math.Min(5, quality));
		}

		public static int ParentPairQuality(int parentA, int parentB)
		{
			return Math.Min(ClampQuality(parentA), ClampQuality(parentB));
		}

		public static bool ProtectBreederReserve(int liveFishCount, int pendingCatches)
		{
			if (liveFishCount > 0)
			{
				return liveFishCount - Math.Max(0, pendingCatches) <= 2;
			}
			return false;
		}
	}
}
namespace Promotor.FishFarming.Valheim
{
	internal static class FeedPresentation
	{
		private static AssetBundle visualBundle;

		private static bool attemptedBundle;

		private static readonly List<Object> retainedAssets = new List<Object>();

		public static void Apply(CustomItem item, string pluginDllPath, string assetName, Color tint)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			//IL_0097: 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_016b: 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_01c8: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				string text = Path.Combine(Path.GetDirectoryName(pluginDllPath), "Assets");
				string path = Path.Combine(text, "icons", assetName + ".png");
				if (File.Exists(path))
				{
					byte[] array = File.ReadAllBytes(path);
					Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
					if (ImageConversion.LoadImage(val, array))
					{
						((Object)val).name = "promotor_ff_" + assetName + "_icon";
						((Texture)val).wrapMode = (TextureWrapMode)1;
						((Texture)val).filterMode = (FilterMode)1;
						Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f);
						((Object)val2).name = ((Object)val).name + "_sprite";
						item.ItemDrop.m_itemData.m_shared.m_icons = (Sprite[])(object)new Sprite[1] { val2 };
						retainedAssets.Add((Object)(object)val);
						retainedAssets.Add((Object)(object)val2);
					}
				}
				if (assetName == "pondkeepers_guide")
				{
					return;
				}
				Transform val3 = item.ItemPrefab.transform.Find("model") ?? item.ItemPrefab.transform;
				Renderer[] componentsInChildren = ((Component)val3).GetComponentsInChildren<Renderer>(true);
				Material vanillaMaterial = ((componentsInChildren.Length != 0) ? componentsInChildren[0].sharedMaterial : null);
				GameObject val4 = GetBundleVisual(text, assetName);
				if ((Object)(object)val4 == (Object)null)
				{
					val4 = CreateSackModel(assetName, tint, vanillaMaterial);
				}
				if (!((Object)(object)val4 == (Object)null))
				{
					Renderer[] array2 = componentsInChildren;
					for (int i = 0; i < array2.Length; i++)
					{
						array2[i].enabled = false;
					}
					val4.transform.SetParent(val3, false);
					val4.transform.localPosition = Vector3.zero;
					val4.transform.localRotation = Quaternion.identity;
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("PROMOTOR Fish Farming visuals failed (vanilla fallback): " + ex));
			}
		}

		public static Sprite LoadIconOrNull(string pluginDllPath, string assetName)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			//IL_0092: 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)
			try
			{
				string path = Path.Combine(Path.GetDirectoryName(pluginDllPath), "Assets", "icons", assetName + ".png");
				if (!File.Exists(path))
				{
					return null;
				}
				Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
				if (!ImageConversion.LoadImage(val, File.ReadAllBytes(path)))
				{
					Object.Destroy((Object)(object)val);
					return null;
				}
				((Object)val).name = "promotor_ff_" + assetName + "_texture";
				((Texture)val).wrapMode = (TextureWrapMode)1;
				((Texture)val).filterMode = (FilterMode)1;
				Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f);
				retainedAssets.Add((Object)(object)val);
				retainedAssets.Add((Object)(object)val2);
				return val2;
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("PROMOTOR optional piece icon unavailable: " + ex.Message));
				return null;
			}
		}

		public static void DecorateFeedBin(GameObject piecePrefab)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: 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_00ac: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)piecePrefab == (Object)null)
			{
				return;
			}
			try
			{
				Renderer[] componentsInChildren = piecePrefab.GetComponentsInChildren<Renderer>(true);
				Material vanillaMaterial = ((componentsInChildren.Length != 0) ? componentsInChildren[0].sharedMaterial : null);
				GameObject obj = CreateSackModel("feed_bin_display", new Color(0.54f, 0.36f, 0.21f), vanillaMaterial);
				((Object)obj).name = "PROMOTOR_FeedBin_VisualOnly";
				obj.transform.SetParent(piecePrefab.transform, false);
				obj.transform.localPosition = new Vector3(0f, 0.48f, 0f);
				obj.transform.localScale = Vector3.one * 0.72f;
				obj.transform.localRotation = Quaternion.Euler(0f, 25f, -8f);
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("PROMOTOR Feed Bin decoration skipped: " + ex.Message));
			}
		}

		private static GameObject GetBundleVisual(string directory, string name)
		{
			if (!attemptedBundle)
			{
				attemptedBundle = true;
				string text = Path.Combine(directory, "promotor_fishfarming_assets");
				if (File.Exists(text))
				{
					visualBundle = AssetBundle.LoadFromFile(text);
				}
			}
			if ((Object)(object)visualBundle == (Object)null)
			{
				return null;
			}
			GameObject val = visualBundle.LoadAsset<GameObject>(name + "_model");
			if (!((Object)(object)val != (Object)null))
			{
				return null;
			}
			return Object.Instantiate<GameObject>(val);
		}

		private static GameObject CreateSackModel(string name, Color tint, Material vanillaMaterial)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			//IL_0049: 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_00b3: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("PROMOTOR_" + name + "_visual");
			float[] y = new float[7] { -0.21f, -0.18f, -0.1f, 0.04f, 0.14f, 0.23f, 0.26f };
			float[] radius = new float[7] { 0.1f, 0.17f, 0.19f, 0.18f, 0.15f, 0.12f, 0.15f };
			AttachMesh(val, "woven_sack", RingMesh(y, radius, 14), tint, vanillaMaterial);
			AttachMesh(val, "rope_collar", TorusMesh(0.135f, 0.018f, 0.205f, 20, 5), new Color(0.73f, 0.57f, 0.31f), vanillaMaterial);
			AttachMesh(val, "seed_surface", TorusMesh(0.085f, 0.07f, 0.245f, 14, 4), new Color(0.36f, 0.25f, 0.12f), vanillaMaterial);
			return val;
		}

		private static void AttachMesh(GameObject parent, string name, Mesh mesh, Color tint, Material vanillaMaterial)
		{
			//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_0018: 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_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Expected O, but got Unknown
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name);
			val.transform.SetParent(parent.transform, false);
			val.AddComponent<MeshFilter>().sharedMesh = mesh;
			MeshRenderer val2 = val.AddComponent<MeshRenderer>();
			Shader val3 = Shader.Find("Standard") ?? Shader.Find("Diffuse");
			if (!((Object)(object)val3 == (Object)null))
			{
				Material val4 = (((Object)(object)vanillaMaterial != (Object)null) ? new Material(vanillaMaterial) : new Material(val3));
				if (val4.HasProperty("_Color"))
				{
					val4.color = tint;
				}
				((Renderer)val2).sharedMaterial = val4;
			}
		}

		private static Mesh RingMesh(float[] y, float[] radius, int sides)
		{
			//IL_0095: 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_00b0: 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_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: 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_018a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: 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_01a5: Expected O, but got Unknown
			int num = y.Length;
			Vector3[] array = (Vector3[])(object)new Vector3[num * sides];
			Vector2[] array2 = (Vector2[])(object)new Vector2[array.Length];
			int[] array3 = new int[(num - 1) * sides * 6];
			for (int i = 0; i < num; i++)
			{
				for (int j = 0; j < sides; j++)
				{
					float num2 = (float)j * (MathF.PI * 2f / (float)sides);
					int num3 = i * sides + j;
					float num4 = 1f + 0.035f * Mathf.Sin((float)j * 2.3f + (float)i * 0.8f);
					array[num3] = new Vector3(Mathf.Cos(num2) * radius[i] * num4, y[i], Mathf.Sin(num2) * radius[i] * num4);
					array2[num3] = new Vector2((float)j / (float)sides, (float)i / (float)(num - 1));
				}
			}
			int num5 = 0;
			for (int k = 0; k < num - 1; k++)
			{
				for (int l = 0; l < sides; l++)
				{
					int num6 = k * sides + l;
					int num7 = k * sides + (l + 1) % sides;
					int num8 = (k + 1) * sides + l;
					int num9 = (k + 1) * sides + (l + 1) % sides;
					array3[num5++] = num6;
					array3[num5++] = num8;
					array3[num5++] = num7;
					array3[num5++] = num7;
					array3[num5++] = num8;
					array3[num5++] = num9;
				}
			}
			Mesh val = new Mesh
			{
				name = "PROMOTOR hand-shaped sack",
				vertices = array,
				uv = array2,
				triangles = array3
			};
			val.RecalculateNormals();
			val.RecalculateBounds();
			return val;
		}

		private static Mesh TorusMesh(float major, float minor, float y, int sectors, int tube)
		{
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: 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_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: 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_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Expected O, but got Unknown
			Vector3[] array = (Vector3[])(object)new Vector3[sectors * tube];
			Vector2[] array2 = (Vector2[])(object)new Vector2[array.Length];
			int[] array3 = new int[sectors * tube * 6];
			for (int i = 0; i < sectors; i++)
			{
				for (int j = 0; j < tube; j++)
				{
					float num = (float)i * 2f * MathF.PI / (float)sectors;
					float num2 = (float)j * 2f * MathF.PI / (float)tube;
					float num3 = major + minor * Mathf.Cos(num2);
					int num4 = i * tube + j;
					array[num4] = new Vector3(num3 * Mathf.Cos(num), y + minor * Mathf.Sin(num2), num3 * Mathf.Sin(num));
					array2[num4] = new Vector2((float)i / (float)sectors, (float)j / (float)tube);
				}
			}
			int num5 = 0;
			for (int k = 0; k < sectors; k++)
			{
				for (int l = 0; l < tube; l++)
				{
					int num6 = k * tube + l;
					int num7 = (k + 1) % sectors * tube + l;
					int num8 = k * tube + (l + 1) % tube;
					int num9 = (k + 1) % sectors * tube + (l + 1) % tube;
					array3[num5++] = num6;
					array3[num5++] = num7;
					array3[num5++] = num8;
					array3[num5++] = num8;
					array3[num5++] = num7;
					array3[num5++] = num9;
				}
			}
			Mesh val = new Mesh
			{
				name = "PROMOTOR woven collar",
				vertices = array,
				triangles = array3,
				uv = array2
			};
			val.RecalculateNormals();
			val.RecalculateBounds();
			return val;
		}
	}
	[BepInPlugin("promotor.valheim.fishfarming", "FishingPond", "2.1.7")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class FishFarmingPlugin : BaseUnityPlugin
	{
		private sealed class CatchLease
		{
			internal long Peer;

			internal string Species;

			internal float ExpiresAt;
		}

		private ConfigEntry<string> allowedPlayerIds;

		private ConfigEntry<bool> allowValheimAdmins;

		private bool managerPermissionGranted;

		private bool managerPermissionPending;

		private const string ManagerAccessRequest = "FISH_POND_v5_AccessRequest";

		private const string ManagerAccessReply = "FISH_POND_v5_AccessReply";

		private float lastAccessDeniedNotice = -100f;

		private const string SnapshotRequest = "PROMOTOR_FISH_v2_GetSnapshot";

		private const string SnapshotResponse = "PROMOTOR_FISH_v2_Snapshot";

		private const string CatchPermitRequest = "PROMOTOR_FISH_v4_CatchPermitRequest";

		private const string CatchPermitReply = "PROMOTOR_FISH_v4_CatchPermitReply";

		private readonly Dictionary<string, CatchLease> catchLeases = new Dictionary<string, CatchLease>(StringComparer.Ordinal);

		private readonly Dictionary<string, float> clientCatchPermits = new Dictionary<string, float>(StringComparer.Ordinal);

		private readonly Dictionary<string, int> permittedCounts = new Dictionary<string, int>(StringComparer.Ordinal);

		private readonly Dictionary<string, float> pendingCatchRequests = new Dictionary<string, float>(StringComparer.Ordinal);

		private readonly Dictionary<string, bool> pendingCatchFromRod = new Dictionary<string, bool>(StringComparer.Ordinal);

		private float nextCatchLeasePrune;

		private const string ZoneEditRequest = "PROMOTOR_FISH_v3_EditZone";

		private const string ZoneEditReply = "PROMOTOR_FISH_v3_EditZoneReply";

		private readonly Dictionary<long, float> pendingResetConfirmations = new Dictionary<long, float>();

		private float localResetExpiresAt = -1f;

		private bool resetAwaitingConfirmation;

		private string resetStatus = "Reset requires a confirmation and a world-state backup.";

		private bool resetExecutionPending;

		private long resetRequesterPeer;

		private float resetPendingDeadline;

		private readonly HashSet<ZDOID> resetDeleteTargets = new HashSet<ZDOID>();

		private bool resetDeletionVerificationPending;

		private float nextResetDeletionVerification;

		private float resetDeletionDeadline;

		private bool resetRescanRequested;

		private int resetFishTargetCount;

		private string resetBackupDirectory = "";

		private long resetFinalRequesterPeer;

		private readonly Dictionary<long, float> nextAdminEdit = new Dictionary<long, float>();

		private readonly Dictionary<long, float> nextSnapshotReply = new Dictionary<long, float>();

		private string remoteZoneStatus = "Mark polygon corners by walking along the shore.";

		private Harmony harmony;

		private const string OriginPondKey = "promotor.ff.origin.pond";

		private const string OriginWorldKey = "promotor.ff.origin.world";

		private string currentWorldKey;

		private string zonePath;

		private bool worldZoneLoaded;

		private string zonePersistenceStatus = "No world-specific boundary loaded.";

		private ConfigEntry<float> uiScale;

		private ConfigEntry<bool> showTechnicalDiagnostics;

		private ConfigEntry<bool> showPondHud;

		private ConfigEntry<bool> showPopulationToasts;

		private ConfigEntry<float> pondHudDistance;

		private ConfigEntry<float> messageCooldown;

		private readonly Dictionary<string, int> hudCounts = new Dictionary<string, int>(StringComparer.Ordinal);

		private readonly Dictionary<string, int> lastNotifiedCounts = new Dictionary<string, int>(StringComparer.Ordinal);

		private bool populationBaselineReady;

		private int hudFeed = -1;

		private int hudFeedCapacity = -1;

		private int hudPondCapacity = -1;

		private ConfigEntry<float> pondHudScale;

		private ConfigEntry<float> pondHudGap;

		private bool hudZoneKnown;

		private Vector3 hudZoneCenter;

		private float hudZoneRadius;

		private float hudZoneHeight;

		private readonly List<Vector2> pondVertices = new List<Vector2>();

		private readonly List<Vector2> hudVertices = new List<Vector2>();

		private readonly HashSet<string> hudBreedingSpecies = new HashSet<string>(StringComparer.Ordinal);

		private readonly HashSet<string> hudActiveBreederSpecies = new HashSet<string>(StringComparer.Ordinal);

		private readonly Dictionary<string, string> hudGrowthStages = new Dictionary<string, string>(StringComparer.Ordinal);

		private readonly Dictionary<string, int> hudRegisteredBreederCounts = new Dictionary<string, int>(StringComparer.Ordinal);

		private int hudSecondsToCycle = -1;

		private ConfigEntry<bool> showPondBoundary;

		private ConfigEntry<float> boundaryLineWidth;

		private ConfigEntry<float> boundaryLineHeight;

		private LineRenderer pondBoundaryLine;

		private GameObject pondBoundaryObject;

		private Material pondBoundaryMaterial;

		private float nextBoundaryVisualTick;

		private string lastBoundaryVisualShape = "";

		private ConfigEntry<bool> containFish;

		private ConfigEntry<float> containmentInset;

		private ConfigEntry<float> containmentAboveWaterTolerance;

		private ConfigEntry<float> containmentStrandingGrace;

		private ConfigEntry<float> containmentTickSeconds;

		private ConfigEntry<float> fishListRefreshSeconds;

		private Fish[] cachedFishList = (Fish[])(object)new Fish[0];

		private float nextFishListRefresh;

		private float nextZoneSignatureRefresh;

		private readonly Dictionary<string, Vector3> lastSafeFishPositions = new Dictionary<string, Vector3>(StringComparer.Ordinal);

		private readonly Dictionary<string, float> strandedFishSince = new Dictionary<string, float>(StringComparer.Ordinal);

		private float nextContainmentCheck;

		private float nextContainmentPrune;

		private string lastContainmentZoneSignature = "";

		private readonly HashSet<string> containmentSeen = new HashSet<string>(StringComparer.Ordinal);

		private readonly Dictionary<string, float> changePendingSince = new Dictionary<string, float>(StringComparer.Ordinal);

		private readonly Dictionary<string, int> changePendingCounts = new Dictionary<string, int>(StringComparer.Ordinal);

		private ConfigEntry<float> noticeStabilitySeconds;

		private float lastReserveNotice = -100f;

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

		private float nextPondMessageAt;

		private float reserveMessagePriorityUntil;

		private GUIStyle hudHeadingStyle;

		private GUIStyle hudLineStyle;

		private GUIStyle hudHintStyle;

		private GUIStyle hudBoxStyle;

		private Texture2D hudBackground;

		private string diagnosticStatus = "No diagnostics exported yet.";

		private ZNet registeredZNet;

		private ConfigEntry<bool> allowLegacyMigration;

		private int ownedFishPresent;

		private int removedOwnedFishSinceLoad;

		private readonly HashSet<string> ownedSeenPreviously = new HashSet<string>(StringComparer.Ordinal);

		private bool pickupGuardInstalled;

		private ConfigEntry<KeyCode> hotkey;

		private ConfigEntry<string> pondId;

		private ConfigEntry<bool> zoneEnabled;

		private ConfigEntry<float> zoneX;

		private ConfigEntry<float> zoneY;

		private ConfigEntry<float> zoneZ;

		private ConfigEntry<float> zoneRadius;

		private ConfigEntry<float> zoneHeight;

		private bool registered;

		private bool visible;

		private bool cursorStateCaptured;

		private bool jotunnInputBlocked;

		private CursorLockMode previousCursorLockMode;

		private bool previousCursorVisible;

		private bool isServer;

		private float nextRegistrationTry;

		private float saveCheck;

		private float nextWorldGrowthTick;

		private float nextScan;

		private ConfigEntry<float> dedicatedZdoScanInterval;

		private float nextDedicatedFeedWarning;

		private int lastNetworkFishCandidates;

		private int lastNetworkFeedCandidates;

		private bool zdoScanReady;

		private float nextZdoDiagnosticLog;

		private readonly List<ZDO> networkZdos = new List<ZDO>();

		private readonly List<ZDO> pondSectorZdos = new List<ZDO>();

		private readonly List<ZDO> pondSectorScratch = new List<ZDO>();

		private readonly List<ZDO> pendingSectorZdos = new List<ZDO>();

		private bool sectorBuildInProgress;

		private int sectorNextX;

		private int sectorNextY;

		private int sectorMinY;

		private int sectorMaxX;

		private int sectorMaxY;

		private int lastSectorQueryFrame = -1;

		private float sectorBuildWorstMs;

		private ConfigEntry<int> sectorsPerFrame;

		private readonly Dictionary<int, List<ZDO>> pondSectorPrefabs = new Dictionary<int, List<ZDO>>();

		private readonly HashSet<int> pondWantedPrefabs = new HashSet<int>();

		private float nextPondSectorRefresh;

		private FishingFloat[] cachedFishingFloats = (FishingFloat[])(object)new FishingFloat[0];

		private float nextFishingFloatRefresh;

		private readonly HashSet<int> inspectedLocalCastFloats = new HashSet<int>();

		private float nextCastReserveProbe;

		private int pondSectorCandidateCount;

		private bool pondSectorCacheValid;

		private float lastPondSectorScanMs;

		private float worstPondSectorScanMs;

		private int pondSectorScanCount;

		private ConfigEntry<float> pondSectorRefreshSeconds;

		private ConfigEntry<float> slowSectorQueryThresholdMs;

		private float nextSlowQueryNotice;

		private ConfigEntry<bool> skipDistantPondScans;

		private ConfigEntry<float> pondScanActivationDistance;

		private static readonly string[] NetworkFishPrefabs = new string[12]
		{
			"Fish1", "Fish2", "Fish3", "Fish4", "Fish5", "Fish6", "Fish7", "Fish8", "Fish9", "Fish10",
			"Fish11", "Fish12"
		};

		private float nextFeedScan;

		private ConfigEntry<float> feedScanInterval;

		private ConfigEntry<float> feedSettleSeconds;

		private ConfigEntry<bool> floatFeedItems;

		private float nextVisualFeedRefresh;

		private ItemDrop[] cachedVisualFeed = (ItemDrop[])(object)new ItemDrop[0];

		private float nextVisualFeedTick;

		private int lastNotifiedFood = -1;

		private float nextSnapshotBroadcast;

		private bool itemsRegistered;

		private bool feedBinRegistered;

		private const string FeedBinPrefab = "promotor_ff_feed_bin";

		private readonly Dictionary<string, float> feedDropFirstSeen = new Dictionary<string, float>(StringComparer.Ordinal);

		private readonly HashSet<string> creditedDropIds = new HashSet<string>(StringComparer.Ordinal);

		private string feedLedgerPath;

		private bool feedLedgerReady;

		private int floatingFeedDrops;

		private ConfigEntry<float> surfaceOffset;

		private const string MixedFeedPrefab = "promotor_ff_balanced_fish_feed";

		private const string FeedGuidePrefab = "promotor_ff_pondkeepers_guide";

		private const string CarrotPowderPrefab = "promotor_ff_carrot_powder";

		private const string TurnipPowderPrefab = "promotor_ff_turnip_powder";

		private const string OnionPowderPrefab = "promotor_ff_onion_powder";

		private float nextClientSnapshot;

		private float snapshotRequestSentAt = -1f;

		private float lastSnapshotReceivedAt = -1f;

		private readonly Dictionary<string, float> remoteFeedOwnershipAttempts = new Dictionary<string, float>(StringComparer.Ordinal);

		private float nextOffspringRelease;

		private string lastReleasedSpecies = "";

		private ConfigEntry<int> maxPhysicalReleasesPerSpeciesPerHour;

		private readonly Dictionary<string, List<double>> recentPhysicalReleases = new Dictionary<string, List<double>>(StringComparer.Ordinal);

		private ConfigEntry<bool> autoReleaseOffspring;

		private ConfigEntry<int> maxLiveFish;

		private ConfigEntry<float> areaPerFish;

		private ConfigEntry<int> maximumPondCapacity;

		private int calculatedPondCapacity;

		private int pondOccupiedSpaces;

		private readonly Dictionary<string, int> speciesSpaceQuotas = new Dictionary<string, int>(StringComparer.Ordinal);

		private ConfigEntry<bool> requireLiveBreedersForRelease;

		private ConfigEntry<float> offspringInterval;

		private string offspringStatus = "No offspring released yet.";

		private bool vanillaBitePatchInstalled;

		private static FishFarmingPlugin activeInstance;

		private float nextRegistrySave;

		private bool registryDirty;

		private readonly Dictionary<string, string> presentRegisteredFish = new Dictionary<string, string>(StringComparer.Ordinal);

		private readonly Dictionary<string, int> observedFishQuality = new Dictionary<string, int>(StringComparer.Ordinal);

		private readonly Dictionary<string, string> presentUnregisteredFish = new Dictionary<string, string>(StringComparer.Ordinal);

		private readonly Dictionary<string, int> presentManagedCounts = new Dictionary<string, int>(StringComparer.Ordinal);

		private string lastPopulationSignature = "";

		private string lastZoneSignature = "";

		private string breederPath;

		private readonly HashSet<string> activatedBreeders = new HashSet<string>(StringComparer.Ordinal);

		private bool breederRecordsLoaded;

		private string feedStatus = "Deposit feed locally using actual inventory items (server-host pilot).";

		private ConfigEntry<int> maxFeed;

		private ConfigEntry<double> eggIntervalMultiplier;

		private ConfigEntry<int> feedUnitsPerEgg;

		private ConfigEntry<double> maturityMultiplier;

		private string breedingStatus = "No active physical breeding pairs. Stock two living fish of the same species.";

		private Rect window = new Rect(90f, 70f, 1080f, 740f);

		private long simulationOffsetTicks;

		private string simulationMessage = "World time controls growth (sleep / server skiptime).";

		private readonly PondEngine engine = new PondEngine();

		private PondPopulation serverPond;

		private string savePath;

		private string snapshot = "Open a world and click Refresh to request server population.";

		private string observed = "Physical fish scan not yet available.";

		private string zoneInfo = "Pond zone not defined.";

		private string status = "Waiting for world connection";

		private int tab;

		private GUIStyle boxStyle;

		private GUIStyle windowStyle;

		private GUIStyle menuPanelStyle;

		private GUIStyle cardStyle;

		private GUIStyle groupTitleStyle;

		private GUIStyle activeTabStyle;

		private GUIStyle actionButtonStyle;

		private GUIStyle subtleStyle;

		private Texture2D panelTexture;

		private Texture2D menuTexture;

		private Texture2D buttonTexture;

		private Texture2D activeTexture;

		private GUIStyle headingStyle;

		private GUIStyle detailStyle;

		private GUIStyle tabStyle;

		private GUIStyle titleStyle;

		private Texture2D backgroundTexture;

		private Texture2D ornateWindowTexture;

		private Texture2D ornateHudTexture;

		private readonly Dictionary<string, string> lastStablePresentFish = new Dictionary<string, string>(StringComparer.Ordinal);

		private readonly Dictionary<string, int> missingFishScans = new Dictionary<string, int>(StringComparer.Ordinal);

		private readonly Dictionary<string, string> registeredFish = new Dictionary<string, string>(StringComparer.Ordinal);

		private string registeredSummary = "No registered physical fish yet.";

		private string registrationStatus = "Not registered";

		private string registryPath;

		private string registryPondId;

		private bool registryLoaded;

		private Vector2 contentScroll;

		private readonly Dictionary<string, int> physicalCounts = new Dictionary<string, int>(StringComparer.Ordinal);

		private int EffectiveFeedCapacity => Mathf.Clamp(maxFeed.Value, 1, 10000);

		private void Awake()
		{
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Expected O, but got Unknown
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Expected O, but got Unknown
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Expected O, but got Unknown
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Expected O, but got Unknown
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e6: Expected O, but got Unknown
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_0220: Expected O, but got Unknown
			//IL_04d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_04dd: Expected O, but got Unknown
			//IL_06b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_06c3: Invalid comparison between Unknown and I4
			//IL_087f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0889: Expected O, but got Unknown
			activeInstance = this;
			allowedPlayerIds = ((BaseUnityPlugin)this).Config.Bind<string>("Access", "AllowedPlayerIDs", "", "SERVER CONFIG ONLY: comma/semicolon separated SteamID64 or platform identity (Steam_765...). Use the peer identity from the dedicated-server log; never use character names as proof of identity. Empty = adminlist-only.");
			allowValheimAdmins = ((BaseUnityPlugin)this).Config.Bind<bool>("Access", "AllowValheimAdmins", true, "Permit authenticated Valheim adminlist.txt users to administer the pond in addition to AllowedPlayerIDs.");
			eggIntervalMultiplier = ((BaseUnityPlugin)this).Config.Bind<double>("Breeding", "EggIntervalMultiplier", 1.0, new ConfigDescription("All species retain their unique egg intervals; multiply each by this factor. 0.5 = twice as frequent; 2 = half as frequent.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.25, 10.0), Array.Empty<object>()));
			maturityMultiplier = ((BaseUnityPlugin)this).Config.Bind<double>("Breeding", "MaturityDaysMultiplier", 1.0, new ConfigDescription("Scale each species' egg-to-adult time; existing cohorts keep their accumulated age.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.25, 10.0), Array.Empty<object>()));
			feedUnitsPerEgg = ((BaseUnityPlugin)this).Config.Bind<int>("Feeding", "FeedUnitsPerEgg", 1, new ConfigDescription("Prepaid feed units consumed per newly laid egg; existing offspring never need a second payment.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>()));
			allowLegacyMigration = ((BaseUnityPlugin)this).Config.Bind<bool>("Persistence", "ImportLegacyFilesOnce", false, "Only enable ONCE in the existing world to copy v0.6.2 saves to this world's isolated folder. Automatically reverts to false.");
			autoReleaseOffspring = ((BaseUnityPlugin)this).Config.Bind<bool>("Offspring", "AutoRelease", true, "Release one mature virtual surplus adult as a real fish at a time, on the authoritative host only.");
			requireLiveBreedersForRelease = ((BaseUnityPlugin)this).Config.Bind<bool>("Safety", "RequireTwoLiveBreedersBeforeRelease", true, "Do not create physical offspring unless two living registered parent fish of the species are currently observed.");
			maxLiveFish = ((BaseUnityPlugin)this).Config.Bind<int>("Offspring", "MaxLoadedPondFishV2", 100, new ConfigDescription("Physical swimming-fish limit. Existing fish are never deleted. Use cautiously on busy servers.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2, 256), Array.Empty<object>()));
			areaPerFish = ((BaseUnityPlugin)this).Config.Bind<float>("Ecology", "AreaSquareMetersPerFishV2", 1f, new ConfigDescription("Horizontal pond surface area in m2 per ecological slot (live fish, eggs, fry and mature pending).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 100f), Array.Empty<object>()));
			maximumPondCapacity = ((BaseUnityPlugin)this).Config.Bind<int>("Ecology", "MaximumTotalFishV2", 256, new ConfigDescription("Hard cap on total ecological slots across all stages, limited further by pond surface area.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2, 256), Array.Empty<object>()));
			offspringInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Offspring", "ReleaseIntervalSeconds", 5f, "Delay between physical adult release checks; 5 seconds by default. World-time species caps still apply.");
			maxPhysicalReleasesPerSpeciesPerHour = ((BaseUnityPlugin)this).Config.Bind<int>("Offspring", "MaxReleasesPerSpeciesPerHour", 6, "Maximum physical mature offspring released per species per Valheim world hour; existing saved config values take precedence.");
			containFish = ((BaseUnityPlugin)this).Config.Bind<bool>("Containment", "Enabled", true, "Keep networked fish already observed inside this pond from crossing its boundary. Install the same mod on every fish-owning client.");
			containmentInset = ((BaseUnityPlugin)this).Config.Bind<float>("Containment", "InsideMarginMeters", 0.65f, "Minimum shoreline clearance for recording safe underwater return points.");
			containmentAboveWaterTolerance = ((BaseUnityPlugin)this).Config.Bind<float>("Containment", "AboveWaterToleranceMeters", 0.75f, "Still-water clearance before considering a fish stranded. 0.25..3 meters.");
			containmentStrandingGrace = ((BaseUnityPlugin)this).Config.Bind<float>("Containment", "StrandingGraceSeconds", 1.5f, "Time above water before rescuing a fish; brief jumps and waves are allowed. 0.5..10 seconds.");
			containmentTickSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "ContainmentTickSeconds", 0.2f, "Seconds between fish containment passes. Default 0.20 (5 Hz), instead of the previous 0.05 (20 Hz). Range 0.10..1.0.");
			sectorsPerFrame = ((BaseUnityPlugin)this).Config.Bind<int>("Performance", "MaxSectorsPerFrame", 1, "Max pond sectors queried per Unity frame (1..3). Default one protects building/terrain streaming.");
			pondSectorRefreshSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "PondSectorRefreshSeconds", 2f, "Minimum time between one bounded ZDO sector query for ALL pond fish and feed. Range 1-10 seconds.");
			slowSectorQueryThresholdMs = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "SlowSectorQueryThresholdMs", 8f, "If a pond sector query takes longer than this, use a longer cache interval (5 seconds) to protect world loading.");
			skipDistantPondScans = ((BaseUnityPlugin)this).Config.Bind<bool>("Performance", "SkipScansWhenNoPlayersNearPond", true, "Avoid pond ZDO scans when nobody is within the pond's observer range. Helps remote-base streaming.");
			pondScanActivationDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "PondObserverDistanceMeters", 140f, "Distance from the pond boundary to a connected player required to scan networked pond objects.");
			dedicatedZdoScanInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "FishScanIntervalSeconds", 2f, "Server fish ZDO scan interval (0.75-10 sec). Lower values update counters faster but cost more CPU.");
			feedScanInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "FeedScanIntervalSeconds", 0.75f, "Server feed ZDO scan interval (0.5-10 sec), separate from fish scanning.");
			feedSettleSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Feeding", "FeedSettleSeconds", 0.75f, "Seconds after server first sees a dropped feed object before it is consumed (0.5-5).");
			floatFeedItems = ((BaseUnityPlugin)this).Config.Bind<bool>("Feeding", "FloatFeedItems", true, "Keep owned dropped Balanced Fish Feed visible near the water surface while awaiting consumption.");
			fishListRefreshSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "FishListRefreshSeconds", 2.5f, "Seconds between expensive Unity-wide fish enumeration calls. Range 0.5..5.0.");
			surfaceOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Feeding", "SurfaceOffsetY", 0.2f, "Floating feed position relative to pond center Y. Mark pond center near water level.");
			maxFeed = ((BaseUnityPlugin)this).Config.Bind<int>("Feeding", "MaxFeedUnits", 100, new ConfigDescription("Server pond-food storage capacity in units; default 100.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10000), Array.Empty<object>()));
			uiScale = ((BaseUnityPlugin)this).Config.Bind<float>("Client", "PanelScale", 1f, "F7 panel size / font scale: 0.8 to 1.4. Can also change from F7 Diagnostics.");
			showPondHud = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "ShowPondHUD", true, "Show compact fish totals when close to the pond.");
			showPopulationToasts = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "ShowPopulationNotifications", true, "Use Valheim notifications for changes in nearby fish counts.");
			pondHudDistance = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "ProximityExtraMeters", 8f, "Additional distance beyond pond radius to show the mini HUD.");
			pondHudScale = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "PondHudScale", 1f, "Scale the larger proximity panel from 0.8 to 1.5.");
			pondHudGap = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "BelowMinimapGap", 12f, "Screen pixels between the minimap and pond panel.");
			showPondBoundary = ((BaseUnityPlugin)this).Config.Bind<bool>("BoundaryVisual", "ShowNearbyOutline", true, "Highlight the authoritative pond polygon for nearby players. Purely visual; does not change fishing or stocking rules.");
			boundaryLineWidth = ((BaseUnityPlugin)this).Config.Bind<float>("BoundaryVisual", "LineWidthMeters", 0.16f, "Thickness of the shoreline outline, in world meters (0.05..0.4).");
			boundaryLineHeight = ((BaseUnityPlugin)this).Config.Bind<float>("BoundaryVisual", "HeightAboveWaterMeters", 0.35f, "Height of the outline above the game's water plane (0.05..1.2).");
			messageCooldown = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "ReserveNoticeCooldownSeconds", 3f, "Minimum interval between protected-fish messages.");
			noticeStabilitySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "PopulationNoticeDelaySeconds", 0.75f, "Minimum stable observation delay before a population notification (0.25-5 sec). Higher values reduce shoreline noise.");
			showTechnicalDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Client", "ShowTechnicalDiagnostics", true, "Show host-only diagnostics and export controls in F7.");
			hotkey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Client", "PanelKey", (KeyCode)288, "Toggle the fish pond management panel.");
			if ((int)hotkey.Value == 289)
			{
				hotkey.Value = (KeyCode)288;
			}
			pondId = ((BaseUnityPlugin)this).Config.Bind<string>("Pond", "PondId", "HarborPond", "Pond identifier. MVP supports one zone per server world.");
			zoneEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("PondZone", "Enabled", false, "Enable the bounded physical-fish pond zone.");
			zoneX = ((BaseUnityPlugin)this).Config.Bind<float>("PondZone", "CenterX", 0f, "World center X; set at local host from F7 panel or edit server config.");
			zoneY = ((BaseUnityPlugin)this).Config.Bind<float>("PondZone", "CenterY", 0f, "World center Y.");
			zoneZ = ((BaseUnityPlugin)this).Config.Bind<float>("PondZone", "CenterZ", 0f, "World center Z.");
			zoneRadius = ((BaseUnityPlugin)this).Config.Bind<float>("PondZone", "Radius", 15f, "Horizontal pond radius in meters.");
			zoneHeight = ((BaseUnityPlugin)this).Config.Bind<float>("PondZone", "VerticalTolerance", 8f, "Maximum vertical distance from pond center.");
			savePath = Path.Combine(Paths.ConfigPath, "PROMOTOR_FishFarming_PondState.txt");
			registryPath = Path.Combine(Paths.ConfigPath, "PROMOTOR_FishFarming_RegisteredFish.txt");
			breederPath = Path.Combine(Paths.ConfigPath, "PROMOTOR_FishFarming_ActivatedBreeders.txt");
			feedLedgerPath = Path.Combine(Paths.ConfigPath, "PROMOTOR_FishFarming_ConsumedDrops.txt");
			string[] networkFishPrefabs = NetworkFishPrefabs;
			foreach (string text in networkFishPrefabs)
			{
				pondWantedPrefabs.Add(StringExtensionMethods.GetStableHashCode(text));
			}
			pondWantedPrefabs.Add(StringExtensionMethods.GetStableHashCode("promotor_ff_balanced_fish_feed"));
			PrefabManager.OnVanillaPrefabsAvailable += RegisterFeedItems;
			harmony = new Harmony("promotor.valheim.fishfarming");
			harmony.PatchAll(typeof(FishFarmingPlugin).Assembly);
			InstallVanillaBiteGuard();
			InstallProtectedBaitMessageFilter();
			InstallPickupGuard();
			InstallRodCatchGuard();
			InstallInventoryFishPickupGuard();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"FishingPond v2.1.7 loaded. Waiting for Jotunn vanilla prefabs to register fish feed items.");
		}

		private void OnDisable()
		{
			if (visible)
			{
				SetPanelVisible(open: false);
			}
			else
			{
				ReleaseCursor();
			}
		}

		private void OnDestroy()
		{
			PrefabManager.OnVanillaPrefabsAvailable -= RegisterFeedItems;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			if ((Object)(object)activeInstance == (Object)(object)this)
			{
				activeInstance = null;
			}
			if (isServer && serverPond != null)
			{
				SaveState();
			}
			if (isServer && registryDirty)
			{
				SaveRegistry();
			}
			if (isServer && worldZoneLoaded)
			{
				SaveWorldZone();
			}
			ReleaseCursor();
			if ((Object)(object)backgroundTexture != (Object)null)
			{
				Object.Destroy((Object)(object)backgroundTexture);
			}
			if ((Object)(object)panelTexture != (Object)null)
			{
				Object.Destroy((Object)(object)panelTexture);
			}
			if ((Object)(object)menuTexture != (Object)null)
			{
				Object.Destroy((Object)(object)menuTexture);
			}
			if ((Object)(object)buttonTexture != (Object)null)
			{
				Object.Destroy((Object)(object)buttonTexture);
			}
			if ((Object)(object)activeTexture != (Object)null)
			{
				Object.Destroy((Object)(object)activeTexture);
			}
			if ((Object)(object)hudBackground != (Object)null)
			{
				Object.Destroy((Object)(object)hudBackground);
			}
			if ((Object)(object)ornateWindowTexture != (Object)null)
			{
				Object.Destroy((Object)(object)ornateWindowTexture);
			}
			if ((Object)(object)ornateHudTexture != (Object)null)
			{
				Object.Destroy((Object)(object)ornateHudTexture);
			}
			DestroyPondBoundaryOutline();
		}

		private void Update()
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			if (!Application.isBatchMode && Input.GetKeyDown(hotkey.Value))
			{
				if (visible)
				{
					SetPanelVisible(open: false);
				}
				else
				{
					RequestPondManagerAccess();
				}
			}
			if (isServer && sectorBuildInProgress && ShouldScanNetworkPond())
			{
				RefreshPondSectorCache();
			}
			if (Time.unscaledTime >= nextContainmentCheck)
			{
				nextContainmentCheck = Time.unscaledTime + Mathf.Clamp(containmentTickSeconds.Value, 0.1f, 1f);
				EnforcePondContainment();
			}
			if (isServer && Time.unscaledTime >= nextCatchLeasePrune)
			{
				nextCatchLeasePrune = Time.unscaledTime + 1f;
				PruneCatchLeases();
			}
			DrainPondMessages();
			CheckProtectedPairOnCast();
			if (visible && !CanManagePond())
			{
				SetPanelVisible(open: false);
			}
			if (registered && ((Object)(object)ZNet.instance == (Object)null || (Object)(object)ZNet.instance != (Object)(object)registeredZNet))
			{
				if (visible)
				{
					SetPanelVisible(open: false);
				}
				ResetWorldSession();
			}
			if (Time.unscaledTime >= nextRegistrationTry)
			{
				nextRegistrationTry = Time.unscaledTime + 2f;
				TryRegister();
			}
			if (!isServer && registered && Time.unscaledTime >= nextClientSnapshot)
			{
				nextClientSnapshot = Time.unscaledTime + (IsPlayerNearPond() ? 2f : (visible ? 3f : 12f));
				Refresh();
			}
			if (!isServer && snapshotRequestSentAt >= 0f && lastSnapshotReceivedAt < snapshotRequestSentAt && Time.unscaledTime - snapshotRequestSentAt > 8f && !status.StartsWith("Snapshot timeout", StringComparison.Ordinal))
			{
				status = "Snapshot timeout: check matching mod versions and PondId on server/client.";
			}
			if (!isServer || serverPond == null)
			{
				return;
			}
			if (resetDeletionVerificationPending && Time.unscaledTime >= nextResetDeletionVerification)
			{
				VerifyAndFinishPondReset();
			}
			if (resetExecutionPending)
			{
				if (Time.unscaledTime > resetPendingDeadline)
				{
					resetExecutionPending = false;
					NotifyResetRequester("DENIED: fresh pond scan timed out; nothing reset");
				}
				else if (RefreshPondSectorCache() && pondSectorCacheValid && !sectorBuildInProgress)
				{
					resetExecutionPending = false;
					NotifyResetRequester(PerformPondReset());
				}
			}
			if (!resetExecutionPending && !resetDeletionVerificationPending && Time.unscaledTime >= nextScan)
			{
				nextScan = Time.unscaledTime + Mathf.Clamp(dedicatedZdoScanInterval.Value, 0.75f, 10f);
				if (ShouldScanNetworkPond())
				{
					ScanPhysicalFish();
				}
				if (registryDirty && Time.unscaledTime >= nextRegistrySave)
				{
					nextRegistrySave = Time.unscaledTime + 5f;
					SaveRegistry();
				}
			}
			if (!resetExecutionPending && !resetDeletionVerificationPending && Time.unscaledTime >= nextFeedScan)
			{
				nextFeedScan = Time.unscaledTime + Mathf.Clamp(feedScanInterval.Value, 0.5f, 10f);
				if (ShouldScanNetworkPond())
				{
					ProcessThrownFeed();
				}
			}
			if (!resetExecutionPending && !resetDeletionVerificationPending && Time.unscaledTime >= nextOffspringRelease)
			{
				nextOffspringRelease = Time.unscaledTime + Mathf.Max(5f, offspringInterval.Value);
				if (autoReleaseOffspring.Value)
				{
					ReleaseOneGrownFish();
				}
			}
			if (!resetExecutionPending && !resetDeletionVerificationPending && Time.unscaledTime >= nextWorldGrowthTick)
			{
				nextWorldGrowthTick = Time.unscaledTime + 2f;
				AdvanceWorldBreeding();
			}
			if (Time.unscaledTime >= saveCheck)
			{
				saveCheck = Time.unscaledTime + 30f;
				SaveState();
			}
		}

		private void LateUpdate()
		{
			if (!Application.isBatchMode)
			{
				if (visible)
				{
					EnforceCursorVisible();
				}
				UpdateFloatingFeedVisuals();
				UpdatePondBoundaryOutline();
			}
		}

		private bool CanManagePond()
		{
			if ((Object)(object)Player.m_localPlayer != (Object)null)
			{
				return managerPermissionGranted;
			}
			return false;
		}

		private void RequestPondManagerAccess()
		{
			if (!registered || (Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null)
			{
				return;
			}
			if (isServer)
			{
				managerPermissionGranted = true;
				SetPanelVisible(open: true);
			}
			else if (managerPermissionGranted)
			{
				SetPanelVisible(open: true);
			}
			else if (!managerPermissionPending)
			{
				long serverPeerId = GetServerPeerId();
				if (serverPeerId != 0L)
				{
					managerPermissionPending = true;
					ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerId, "FISH_POND_v5_AccessRequest", new object[1] { "request" });
				}
			}
		}

		private void OnManagerAccessRequest(long sender, string ignored)
		{
			if (isServer && ZRoutedRpc.instance != null)
			{
				bool flag = IsAuthorizedPondPeer(sender);
				ZRoutedRpc.instance.InvokeRoutedRPC(sender, "FISH_POND_v5_AccessReply", new object[1] { flag ? "ALLOW" : "DENY" });
			}
		}

		private void OnManagerAccessReply(long sender, string response)
		{
			if (!isServer && sender == GetServerPeerId())
			{
				managerPermissionPending = false;
				managerPermissionGranted = response == "ALLOW";
				if (managerPermissionGranted)
				{
					SetPanelVisible(open: true);
				}
				else if (Time.unscaledTime - lastAccessDeniedNotice >= 3f)
				{
					lastAccessDeniedNotice = Time.unscaledTime;
					NotifyPlayer("Pond manager: your account is not authorized on the server", center: true);
				}
			}
		}

		private void SetPanelVisible(bool open)
		{
			//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)
			if ((open && !CanManagePond()) || visible == open)
			{
				return;
			}
			visible = open;
			if (open)
			{
				previousCursorLockMode = Cursor.lockState;
				previousCursorVisible = Cursor.visible;
				cursorStateCaptured = true;
				try
				{
					GUIManager.BlockInput(true);
					jotunnInputBlocked = true;
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not block camera input for F7: " + ex.Message));
				}
				Cursor.lockState = (CursorLockMode)0;
				Cursor.visible = true;
				nextClientSnapshot = 0f;
			}
			else
			{
				ReleaseCursor();
			}
		}

		private void EnforceCursorVisible()
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			if (visible)
			{
				if ((int)Cursor.lockState != 0)
				{
					Cursor.lockState = (CursorLockMode)0;
				}
				if (!Cursor.visible)
				{
					Cursor.visible = true;
				}
			}
		}

		private void ReleaseCursor()
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			if (jotunnInputBlocked)
			{
				jotunnInputBlocked = false;
				try
				{
					GUIManager.BlockInput(false);
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not release F7 input block: " + ex.Message));
				}
			}
			if (cursorStateCaptured)
			{
				cursorStateCaptured = false;
				Cursor.lockState = previousCursorLockMode;
				Cursor.visible = previousCursorVisible;
			}
		}

		private void TryRegister()
		{
			if (registered || ZRoutedRpc.instance == null || (Object)(object)ZNet.instance == (Object)null)
			{
				return;
			}
			isServer = ZNet.instance.IsServer();
			if (isServer && !PrepareWorldStorage())
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"Cannot identify world. Pond state loading disabled rather than sharing saves across worlds.");
				isServer = false;
				return;
			}
			registeredZNet = ZNet.instance;
			ZRoutedRpc.instance.Register<string>("PROMOTOR_FISH_v2_GetSnapshot", (Action<long, string>)OnSnapshotRequest);
			ZRoutedRpc.instance.Register<string>("PROMOTOR_FISH_v2_Snapshot", (Action<long, string>)OnSnapshotResponse);
			ZRoutedRpc.instance.Register<string>("PROMOTOR_FISH_v4_CatchPermitRequest", (Action<long, string>)OnCatchPermitRequest);
			ZRoutedRpc.instance.Register<string>("PROMOTOR_FISH_v4_CatchPermitReply", (Action<long, string>)OnCatchPermitReply);
			ZRoutedRpc.instance.Register<string>("PROMOTOR_FISH_v3_EditZone", (Action<long, string>)OnZoneEditRequest);
			ZRoutedRpc.instance.Register<string>("PROMOTOR_FISH_v3_EditZoneReply", (Action<long, string>)OnZoneEditReply);
			ZRoutedRpc.instance.Register<string>("FISH_POND_v5_AccessRequest", (Action<long, string>)OnManagerAccessRequest);
			ZRoutedRpc.instance.Register<string>("FISH_POND_v5_AccessReply", (Action<long, string>)OnManagerAccessReply);
			registered = true;
			if (isServer)
			{
				serverPond = LoadState();
				LoadRegistry();
				LoadBreederRecords();
				LoadFeedLedger();
				if (serverPond != null)
				{
					ScanPhysicalFish();
					RecalculateEcologyCapacity();
					AdvanceWorldBreeding();
					SaveState();
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Fish pond state initialized at " + savePath));
				}
			}
		}

		private void ResetWorldSession()
		{
			if (isServer && serverPond != null)
			{
				SaveState();
			}
			if (isServer && registryDirty)
			{
				SaveRegistry();
			}
			registered = false;
			managerPermissionGranted = false;
			managerPermissionPending = false;
			registeredZNet = null;
			isServer = false;
			serverPond = null;
			registryLoaded = false;
			registryDirty = false;
			breederRecordsLoaded = false;
			feedLedgerReady = false;
			currentWorldKey = null;
			zonePath = null;
			worldZoneLoaded = false;
			zonePersistenceStatus = "World zone not loaded.";
			lastSafeFishPositions.Clear();
			lastContainmentZoneSignature = "";
			nextZoneSignatureRefresh = 0f;
			cachedFishList = (Fish[])(object)new Fish[0];
			nextFishListRefresh = 0f;
			nextContainmentCheck = 0f;
			lastStablePresentFish.Clear();
			missingFishScans.Clear();
			pendingResetConfirmations.Clear();
			catchLeases.Clear();
			clientCatchPermits.Clear();
			pendingCatchRequests.Clear();
			pendingCatchFromRod.Clear();
			resetExecutionPending = false;
			resetDeletionVerificationPending = false;
			resetRescanRequested = false;
			resetDeleteTargets.Clear();
			resetRescanRequested = false;
			resetFinalRequesterPeer = 0L;
			resetRequesterPeer = 0L;
			resetAwaitingConfirmation = false;
			registeredFish.Clear();
			nextWorldGrowthTick = 0f;
			activatedBreeders.Clear();
			creditedDropIds.Clear();
			ownedSeenPreviously.Clear();
			removedOwnedFishSinceLoad = 0;
			simulationOffsetTicks = 0L;
			lastPopulationSignature = "";
			recentPhysicalReleases.Clear();
			lastNotifiedCounts.Clear();
			hudCounts.Clear();
			lastNotifiedFood = -1;
			hudPondCapacity = -1;
			nextSnapshotBroadcast = 0f;
			nextVisualFeedRefresh = 0f;
			cachedVisualFeed = (ItemDrop[])(object)new ItemDrop[0];
			feedDropFirstSeen.Clear();
			pondVertices.Clear();
			hudVertices.Clear();
			hudBreedingSpecies.Clear();
			hudActiveBreederSpecies.Clear();
			hudGrowthStages.Clear();
			hudRegisteredBreederCounts.Clear();
			hudSecondsToCycle = -1;
			DestroyPondBoundaryOutline();
			changePendingSince.Clear();
			changePendingCounts.Clear();
			hudZoneKnown = false;
			populationBaselineReady = false;
			nextClientSnapshot = 0f;
			InvalidatePondSectorCache();
			snapshotRequestSentAt = -1f;
			lastSnapshotReceivedAt = -1f;
			remoteFeedOwnershipAttempts.Clear();
			nextAdminEdit.Clear();
			nextSnapshotReply.Clear();
			remoteZoneStatus = "Mark polygon corners by walking along the shore.";
			snapshot = "World changed. Waiting for pond state...";
		}

		private static string WorldIdentity(ZNet net)
		{
			if ((Object)(object)net == (Object)null)
			{
				return null;
			}
			try
			{
				object obj = null;
				MethodInfo methodInfo = AccessTools.Method(((object)net).GetType(), "GetWorldUID", (Type[])null, (Type[])null);
				if (methodInfo != null && methodInfo.GetParameters().Length == 0)
				{
					obj = methodInfo.Invoke(net, null);
				}
				if (obj == null || obj.ToString() == "0")
				{
					FieldInfo fieldInfo = AccessTools.Field(((object)net).GetType(), "m_world");
					object obj2 = ((fieldInfo == null) ? null : fieldInfo.GetValue(net));
					if (obj2 != null)
					{
						FieldInfo fieldInfo2 = AccessTools.Field(obj2.GetType(), "m_uid");
						if (fieldInfo2 != null)
						{
							obj = fieldInfo2.GetValue(obj2);
						}
					}
				}
				if (obj == null || obj.ToString() == "0")
				{
					return null;
				}
				return obj.ToString();
			}
			catch
			{
				return null;
			}
		}

		private static string SafeWorldKey(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return null;
			}
			return BitConverter.ToString(Encoding.UTF8.GetBytes(value)).Replace("-", "");
		}

		private bool PrepareWorldStorage()
		{
			string text = WorldIdentity(ZNet.instance);
			string text2 = SafeWorldKey(text);
			if (string.IsNullOrEmpty(text2))
			{
				return false;
			}
			currentWorldKey = text2;
			string text3 = Path.Combine(Paths.ConfigPath, "PROMOTOR_FishFarming_Worlds", text2);
			try
			{
				Directory.CreateDirectory(text3);
				string oldFile = savePath;
				string oldFile2 = registryPath;
				string oldFile3 = breederPath;
				string oldFile4 = feedLedgerPath;
				savePath = Path.Combine(text3, "PondState.txt");
				registryPath = Path.Combine(text3, "RegisteredFish.txt");
				breederPath = Path.Combine(text3, "ActivatedBreeders.txt");
				feedLedgerPath = Path.Combine(text3, "ConsumedDrops.txt");
				zonePath = Path.Combine(text3, "PondZone.txt");
				LoadWorldZone();
				if (allowLegacyMigration.Value)
				{
					CopyLegacyIfNew(oldFile, savePath);
					CopyLegacyIfNew(oldFile2, registryPath);
					CopyLegacyIfNew(oldFile3, breederPath);
					CopyLegacyIfNew(oldFile4, feedLedgerPath);
					allowLegacyMigration.Value = false;
					((BaseUnityPlugin)this).Config.Save();
					((BaseUnityPlugin)this).Logger.LogWarning((object)("One-time legacy migration was requested for world " + text));
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)("World-isolated pond storage: " + text3));
				return true;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("World storage initialization failed: " + ex));
				return false;
			}
		}

		private void LoadWorldZone()
		{
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			worldZoneLoaded = false;
			pondVertices.Clear();
			if (string.IsNullOrEmpty(zonePath))
			{
				throw new InvalidOperationException("Missing world-specific zone path");
			}
			if (!File.Exists(zonePath))
			{
				if (!allowLegacyMigration.Value)
				{
					zoneEnabled.Value = false;
					zonePersistenceStatus = "New world: boundary disabled. Mark pond center in F7.";
				}
				else
				{
					zonePersistenceStatus = "Imported existing pond boundary from server config.";
				}
				worldZoneLoaded = true;
				SaveWorldZone();
				return;
			}
			string[] array = File.ReadAllLines(zonePath);
			if (array.Length < 7 || array[0] != "POND|" + pondId.Value || !array[1].StartsWith("ENABLED|", StringComparison.Ordinal))
			{
				throw new InvalidDataException("World pond-zone metadata invalid; refusing to use another pond's coordinates");
			}
			zoneEnabled.Value = array[1] == "ENABLED|1";
			zoneX.Value = ParseZoneNumber(array[2], "X");
			zoneY.Value = ParseZoneNumber(array[3], "Y");
			zoneZ.Value = ParseZoneNumber(array[4], "Z");
			zoneRadius.Value = Mathf.Clamp(ParseZoneNumber(array[5], "RADIUS"), 2f, 100f);
			zoneHeight.Value = Mathf.Clamp(ParseZoneNumber(array[6], "HEIGHT"), 1f, 100f);
			pondVertices.Clear();
			for (int i = 7; i < array.Length; i++)
			{
				if (!array[i].StartsWith("VERTEX|", StringComparison.Ordinal))
				{
					throw new InvalidDataException("Invalid polygon vertex entry");
				}
				string[] array2 = array[i].Split('|');
				if (array2.Length != 3 || pondVertices.Count >= 48)
				{
					throw new InvalidDataException("Invalid polygon vertex count");
				}
				pondVertices.Add(new Vector2(ParseZoneNumber("X|" + array2[1], "X"), ParseZoneNumber("Z|" + array2[2], "Z")));
			}
			if (pondVertices.Count > 0 && pondVertices.Count < 3)
			{
				throw new InvalidDataException("Polygon needs at least three points");
			}
			worldZoneLoaded = true;
			if (pondVertices.Count >= 3 && Mathf.Abs(zoneX.Value) < 0.01f && Mathf.Abs(zoneZ.Value) < 0.01f)
			{
				UpdatePolygonCenter(force: true);
				SaveWorldZone();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Migrated polygon center from shoreline vertices for HUD proximity.");
			}
			zonePersistenceStatus = "Boundary loaded from this world's PondZone.txt.";
		}

		private void UpdatePolygonCenter(bool force = false)
		{
			//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_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			if (pondVertices.Count < 3 || (!force && !zoneEnabled.Value))
			{
				return;
			}
			float num = 0f;
			float num2 = 0f;
			foreach (Vector2 pondVertex in pondVertices)
			{
				num += pondVertex.x;
				num2 += pondVertex.y;
			}
			zoneX.Value = num / (float)pondVertices.Count;
			zoneZ.Value = num2 / (float)pondVertices.Count;
		}

		private static float ParseZoneNumber(string line, string label)
		{
			if (!line.StartsWith(label + "|", StringComparison.Ordinal))
			{
				throw new InvalidDataException("Missing " + label);
			}
			float num = float.Parse(line.Substring(label.Length + 1), CultureInfo.InvariantCulture);
			if (float.IsNaN(num) || float.IsInfinity(num))
			{
				throw new InvalidDataException("Invalid " + label);
			}
			return num;
		}

		private void SaveWorldZone()
		{
			//IL_0158: 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)
			if (!isServer || !worldZoneLoaded || string.IsNullOrEmpty(zonePath))
			{
				return;
			}
			List<string> list = new List<string>(new string[7]
			{
				"POND|" + pondId.Value,
				"ENABLED|" + (zoneEnabled.Value ? "1" : "0"),
				"X|" + zoneX.Value.ToString("R", CultureInfo.InvariantCulture),
				"Y|" + zoneY.Value.ToString("R", CultureInfo.InvariantCulture),
				"Z|" + zoneZ.Value.ToString("R", CultureInfo.InvariantCulture),
				"RADIUS|" + zoneRadius.Value.ToString("R", CultureInfo.InvariantCulture),
				"HEIGHT|" + zoneHeight.Value.ToString("R", CultureInfo.InvariantCulture)
			});
			if (pondVertices.Count >= 3)
			{
				foreach (Vector2 pondVertex in pondVertices)
				{
					list.Add("VERTEX|" + pondVertex.x.ToString("R", CultureInfo.InvariantCulture) + "|" + pondVertex.y.ToString("R", CultureInfo.InvariantCulture));
				}
			}
			string text = zonePath + ".tmp";
			try
			{
				File.WriteAllLines(text, list.ToArray());
				if (File.Exists(zonePath))
				{
					File.Replace(text, zonePath, zonePath + ".bak");
				}
				else
				{
					File.Move(text, zonePath);
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("World zone write failed: " + ex));
			}
		}

		private static void CopyLegacyIfNew(string oldFile, string newFile)
		{
			if (File.Exists(oldFile) && !File.Exists(newFile))
			{
				File.Copy(oldFile, newFile);
			}
		}

		private void InstallPickupGuard()
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Expected O, but got Unknown
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(Fish), "Pickup", (Type[])null, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.Method(typeof(FishFarmingPlugin), "BeforeFishPickup", (Type[])null, (Type[])null);
				if (methodInfo == null || methodInfo2 == null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Fish.Pickup not found; hand/rod pickup cannot be blocked by this pilot.");
					return;
				}
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				pickupGuardInstalled = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Fish.Pickup reserve guard installed (host and snapshot-aware client pilot).");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Fish.Pickup guard unavailable: " + ex));
			}
		}

		private void InstallInventoryFishPickupGuard()
		{
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Expected O, but got Unknown
			try
			{
				int num = 0;
				MethodInfo[] methods = typeof(Humanoid).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo in methods)
				{
					ParameterInfo[] parameters = methodInfo.GetParameters();
					if (methodInfo.Name == "Pickup" && parameters.Length != 0 && parameters[0].ParameterType == typeof(GameObject))
					{
						harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(FishFarmingPlugin), "BeforeInventoryFishPickup", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						num++;
					}
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Server-authorized inventory-fish pickup gates: " + num));
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not install inventory-fish pickup gate: " + ex));
			}
		}

		private static bool BeforeInventoryFishPickup(GameObject __0)
		{
			FishFarmingPlugin fishFarmingPlugin = activeInstance;
			if ((Object)(object)fishFarmingPlugin == (Object)null || (Object)(object)__0 == (Object)null)
			{
				return true;
			}
			Fish component = __0.GetComponent<Fish>();
			if (!((Object)(object)component == (Object)null))
			{
				return fishFarmingPlugin.CheckCatchPermission(component, rod: false);
			}
			return true;
		}

		private void InstallRodCatchGuard()
		{
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Expected O, but got Unknown
			try
			{
				MethodInfo methodInfo = null;
				MethodInfo[] methods = typeof(FishingFloat).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo2 in methods)
				{
					ParameterInfo[] parameters = methodInfo2.GetParameters();
					if (methodInfo2.Name == "Catch" && parameters.Length >= 2 && parameters[0].ParameterType == typeof(Fish))
					{
						methodInfo = methodInfo2;
						break;
					}
				}
				if (methodInfo == null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"FishingFloat.Catch signature not found: fail-safe pond pickup still active, rod catch needs validation.");
					return;
				}
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(FishFarmingPlugin), "BeforeRodCatch", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Server-permitted vanilla rod-catch gate installed.");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not install rod catch gate: " + ex));
			}
		}

		private static bool BeforeRodCatch(Fish __0)
		{
			FishFarmingPlugin fishFarmingPlugin = activeInstance;
			if (!((Object)(object)fishFarmingPlugin == (Object)null))
			{
				return fishFarmingPlugin.CheckCatchPermission(__0, rod: true);
			}
			return true;
		}

		private static bool BeforeFishPickup(Fish __instance)
		{
			FishFarmingPlugin fishFarmingPlugin = activeInstance;
			if (!((Object)(object)fishFarmingPlugin == (Object)null))
			{
				return fishFarmingPlugin.CheckCatchPermission(__instance, rod: false);
			}
			return true;
		}

		private bool CheckCatchPermission(Fish fish, bool rod)
		{
			//IL_0129: 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)
			if ((Object)(object)fish == (Object)null)
			{
				return true;
			}
			if (!isServer && registered && lastSnapshotReceivedAt < 0f)
			{
				return false;
			}
			if (isServer ? (!zoneEnabled.Value) : (!hudZoneKnown))
			{
				return true;
			}
			if (isServer)
			{
				string text = NormalizePrefabName(((Object)((Component)fish).gameObject).name);
				bool flag = ShouldBlockFishRemoval(fish, text);
				int value = 0;
				int num = 0;
				physicalCounts.TryGetValue(text, out value);
				foreach (KeyValuePair<string, CatchLease> catchLease in catchLeases)
				{
					if (catchLease.Value.Species == text && catchLease.Value.ExpiresAt > Time.unscaledTime)
					{
						num++;
					}
				}
				if (IsInsidePond(((Component)fish).transform.position) && FishInheritance.ProtectBreederReserve(value, num))
				{
					flag = true;
				}
				if (flag)
				{
					NotifyReserveDenied(text);
				}
				return !flag;
			}
			if (!hudZoneKnown || !IsInsideHudZone(((Component)fish).transform.position))
			{
				return true;
			}
			string persistentFishId = GetPersistentFishId(fish);
			if (string.IsNullOrEmpty(persistentFishId) || ZRoutedRpc.instance == null)
			{
				return false;
			}
			if (clientCatchPermits.TryGetValue(persistentFishId, out var value2) && value2 > Time.unscaledTime)
			{
				string key = NormalizePrefabName(((Object)((Component)fish).gameObject).name);
				if (hudCounts.TryGetValue(key, out var value3) && permittedCounts.TryGetValue(persistentFishId, out var value4) && value3 >= value4 && value3 > 2)
				{
					return true;
				}
				clientCatchPermits.Remove(persistentFishId);
				permittedCounts.Remove(persistentFishId);
			}
			if (!pendingCatchRequests.TryGetValue(persistentFishId, out var value5) || Time.unscaledTime - value5 > 0.8f)
			{
				long serverPeerId = GetServerPeerId();
				if (serverPeerId != 0L)
				{
					pendingCatchRequests[persistentFishId] = Time.unscaledTime;
					pendingCatchFromRod[persistentFishId] = rod;
					ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerId, "PROMOTOR_FISH_v4_CatchPermitRequest", new object[1] { persistentFishId });
				}
			}
			return false;
		}

		private void PruneCatchLeases()
		{
			foreach (KeyValuePair<string, CatchLease> item in new Dictionary<string, CatchLease>(catchLeases))
			{
				if (item.Value.ExpiresAt <= Time.unscaledTime || !presentRegisteredFish.ContainsKey(item.Key))
				{
					catchLeases.Remove(item.Key);
				}
			}
		}

		private void OnCatchPermitRequest(long sender, string fishId)
		{
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: 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_0153: 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_015b: 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_00f6: Unknown result type (might be due to invalid IL or missing references)
			if (!isServer || serverPond == null || !zoneEnabled.Value || ZRoutedRpc.instance == null || resetExecutionPending || resetDeletionVerificationPending)
			{
				return;
			}
			string text = "DENY";
			ZNetPeer val = (((Object)(object)ZNet.instance == (Object)null) ? null : ZNet.instance.GetPeer(sender));
			if (val != null && val.IsReady() && !string.IsNullOrEmpty(fishId) && fishId.Length < 100 && presentRegisteredFish.TryGetValue(fishId, out var value))
			{
				ZDO val2 = null;
				if (pondSectorPrefabs.TryGetValue(StringExtensionMethods.GetStableHashCode(value), out var value2))
				{
					foreach (ZDO item in value2)
					{
						if (item != null && item.IsValid() && ((object)Unsafe.As<ZDOID, ZDOID>(ref item.m_uid)/*cast due to .constrained prefix*/).ToString() == fishId)
						{
							ZDO val3 = ((ZDOMan.instance == null) ? null : ZDOMan.instance.GetZDO(item.m_uid));
							if (val3 != null && val3.IsValid())
							{
								val2 = val3;
							}
							break;
						}
					}
				}
				if (val2 != null && IsInsidePond(val2.GetPosition()))
				{
					Vector3 position = val2.GetPosition();
					Vector3 refPos = val.m_refPos;
					float num = position.x - refPos.x;
					float num2 = position.z - refPos.z;
					if