Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of FishingPond v2.1.7
BepInEx\plugins\FishingPond\PromotorFishFarming.dll
Decompiled 5 hours ago
The result has been truncated due to the large size, download it to view full contents!
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