Decompiled source of GamblersReach v0.8.4
BepInEx/plugins/GamblersReach/GamblersReach.Core.dll
Decompiled 2 hours agousing System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using HowToFish.ExpansionKit.Packs; using HowToFish.ExpansionKit.Progression; using Microsoft.CodeAnalysis; [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("GamblersReach.Core")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("GamblersReach.Core")] [assembly: AssemblyTitle("GamblersReach.Core")] [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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 GamblersReach.Core { internal static class Identifiers { internal static string Require(string value, string parameterName) { if (string.IsNullOrWhiteSpace(value) || value.Length > 128 || value != value.Trim()) { throw new ArgumentException("An identifier must contain 1–128 characters without surrounding whitespace.", parameterName); } for (int i = 0; i < value.Length; i++) { if (char.IsControl(value[i])) { throw new ArgumentException("Identifiers cannot contain control characters.", parameterName); } } return value; } } } namespace GamblersReach.Core.Wildlife { public readonly struct HawkFlightPose { public Vector3 Position { get; } public Vector3 Velocity { get; } public HawkFlightPose(Vector3 position, Vector3 velocity) { Position = position; Velocity = velocity; } } public sealed class HawkFlightCurve { private readonly Vector3 start; private readonly Vector3 startVelocity; private readonly Vector3 end; private readonly Vector3 endVelocity; public double Duration { get; } public HawkFlightCurve(Vector3 start, Vector3 startVelocity, Vector3 end, Vector3 endVelocity, double duration) { if (!Finite(start) || !Finite(startVelocity) || !Finite(end) || !Finite(endVelocity) || double.IsNaN(duration) || double.IsInfinity(duration) || duration < 1E-06 || duration > 3.4028234663852886E+38) { throw new ArgumentException("A flight curve requires finite positions, velocities and a positive duration."); } this.start = start; this.startVelocity = startVelocity; this.end = end; this.endVelocity = endVelocity; Duration = duration; } public HawkFlightPose Sample(double elapsed) { if (double.IsNaN(elapsed) || double.IsInfinity(elapsed) || elapsed < 0.0) { throw new ArgumentOutOfRangeException("elapsed"); } float num = (float)Duration; float num2 = (float)Math.Min(1.0, elapsed / Duration); float num3 = num2 * num2; float num4 = num3 * num2; Vector3 position = start * (2f * num4 - 3f * num3 + 1f) + startVelocity * (num * (num4 - 2f * num3 + num2)) + end * (-2f * num4 + 3f * num3) + endVelocity * (num * (num4 - num3)); Vector3 velocity = start * ((6f * num3 - 6f * num2) / num) + startVelocity * (3f * num3 - 4f * num2 + 1f) + end * ((-6f * num3 + 6f * num2) / num) + endVelocity * (3f * num3 - 2f * num2); return new HawkFlightPose(position, velocity); } internal static bool Finite(Vector3 value) { if (!float.IsNaN(value.X) && !float.IsInfinity(value.X) && !float.IsNaN(value.Y) && !float.IsInfinity(value.Y) && !float.IsNaN(value.Z)) { return !float.IsInfinity(value.Z); } return false; } } public sealed class HawkFlightHistory { public const double InterpolationDelay = 0.1; private readonly List<(Vector3 Position, double Time)> samples = new List<(Vector3, double)>(); public bool HasSamples => samples.Count != 0; public void Receive(Vector3 position, double receivedAt) { if (!HawkFlightCurve.Finite(position) || !Finite(receivedAt) || (samples.Count != 0 && receivedAt < samples[samples.Count - 1].Time)) { throw new ArgumentException("Hawk position samples require finite, monotonic arrival times."); } if (samples.Count != 0 && receivedAt == samples[samples.Count - 1].Time) { samples[samples.Count - 1] = (position, receivedAt); } else { samples.Add((position, receivedAt)); } if (samples.Count > 12) { samples.RemoveAt(0); } } public HawkFlightPose Sample(double now) { if (!HasSamples || !Finite(now)) { throw new InvalidOperationException("The hawk has no valid received flight history."); } double num = now - 0.1; if (samples.Count == 1 || num <= samples[0].Time) { return new HawkFlightPose(samples[0].Position, Vector3.Zero); } for (int i = 0; i < samples.Count - 1; i++) { (Vector3, double) tuple = samples[i]; (Vector3, double) tuple2 = samples[i + 1]; if (!(num > tuple2.Item2)) { return new HawkFlightCurve(tuple.Item1, Velocity(i), tuple2.Item1, Velocity(i + 1), tuple2.Item2 - tuple.Item2).Sample(num - tuple.Item2); } } (Vector3, double) tuple3 = samples[samples.Count - 1]; Vector3 vector = Velocity(samples.Count - 1); return new HawkFlightPose(tuple3.Item1 + vector * (float)Math.Min(0.1, num - tuple3.Item2), (num - tuple3.Item2 > 0.1) ? Vector3.Zero : vector); } private Vector3 Velocity(int index) { (Vector3, double) tuple = samples[Math.Max(0, index - 1)]; (Vector3, double) tuple2 = samples[Math.Min(samples.Count - 1, index + 1)]; Vector3 vector = (tuple2.Item1 - tuple.Item1) / (float)(tuple2.Item2 - tuple.Item2); if (!(vector.LengthSquared() > 2500f)) { return vector; } return Vector3.Normalize(vector) * 50f; } private static bool Finite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } } public static class HawkRules { public const int Health = 60; public const int BaseWorth = 20; public const int DiveDamage = 8; public const int MaximumAlive = 1; public const double NativeSeagullSpawnSeconds = 140.0; public const double SpawnSeconds = 420.0; public const double WarningSeconds = 1.2; public const double RecoverySeconds = 4.0; public const double CircleSeconds = 8.0; public const float StrikeRadius = 0.75f; public const float TargetRange = 35f; } public enum HawkStage { Circling, Telegraph, Diving, Recovering } public sealed class HawkSpawnClock { public double Elapsed { get; private set; } public bool Advance(double seconds, bool harborActive, int livingHawks) { if (!Finite(seconds) || seconds <= 0.0 || livingHawks < 0) { throw new ArgumentOutOfRangeException("seconds"); } if (!harborActive || livingHawks >= 1) { Elapsed = 0.0; return false; } Elapsed += seconds; if (Elapsed < 420.0) { return false; } Elapsed = 0.0; return true; } private static bool Finite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } } public sealed class HawkDiveCycle { private HawkFlightCurve? warning; private HawkFlightCurve? approach; private HawkFlightCurve? pullout; public HawkStage Stage { get; private set; } public double Elapsed { get; private set; } public Vector3 DiveOrigin { get; private set; } public Vector3 LockedAim { get; private set; } public Vector3 DiveEnd { get; private set; } public double DiveSeconds { get; private set; } public bool StrikeSpent { get; private set; } public bool CanWarn { get { if (Stage == HawkStage.Circling) { return Elapsed >= 8.0; } return false; } } public int DivesStarted { get; private set; } public void Warn(Vector3 origin, Vector3 target) { Warn(origin, target, Vector3.UnitX * 8f); } public void Warn(Vector3 origin, Vector3 target, Vector3 velocity) { if (!CanWarn) { throw new InvalidOperationException("The hawk must finish circling before another warning."); } if (!Finite(origin) || !Finite(target) || !Finite(velocity) || velocity.LengthSquared() < 1f || velocity.LengthSquared() > 400f || Vector3.DistanceSquared(origin, target) < 1f) { throw new ArgumentException("A dive needs finite, distinct origin and target positions."); } LockedAim = target; DiveOrigin = origin + velocity * 0.84f + Vector3.UnitY * 1.25f; Vector3 vector = Vector3.Normalize(target - DiveOrigin); Vector3 value = new Vector3(vector.X, 0f, vector.Z); value = ((value.LengthSquared() < 0.001f) ? Vector3.UnitZ : Vector3.Normalize(value)); DiveEnd = target + value * 2.5f + Vector3.UnitY * 1.5f; float num = Vector3.Distance(DiveOrigin, target); DiveSeconds = Math.Max(1.1, Math.Min(2.4, (num + 3f) / 15f)); double num2 = DiveSeconds * 0.78; Vector3 vector2 = vector * Math.Min(8f, num / (float)num2); Vector3 vector3 = value * Math.Max(8f, Math.Min(16f, num / (float)num2 * 0.75f)); warning = new HawkFlightCurve(origin, velocity, DiveOrigin, vector2, 1.2); approach = new HawkFlightCurve(DiveOrigin, vector2, target, vector3, num2); pullout = new HawkFlightCurve(target, vector3, DiveEnd, value * 6f + Vector3.UnitY * 7f, DiveSeconds - num2); StrikeSpent = false; Enter(HawkStage.Telegraph); } public void Advance(double seconds, bool targetValid = true) { if (double.IsNaN(seconds) || double.IsInfinity(seconds) || seconds <= 0.0) { throw new ArgumentOutOfRangeException("seconds"); } if (!targetValid && (Stage == HawkStage.Telegraph || Stage == HawkStage.Diving)) { Abort(); return; } Elapsed += seconds; if (Stage == HawkStage.Telegraph && Elapsed >= 1.2) { DivesStarted++; Enter(HawkStage.Diving); } else if (Stage == HawkStage.Diving && Elapsed >= DiveSeconds) { Enter(HawkStage.Recovering); } else if (Stage == HawkStage.Recovering && Elapsed >= 4.0) { Enter(HawkStage.Circling); } } public HawkFlightPose WarningPose(double elapsed) { if (Stage != HawkStage.Telegraph || warning == null) { throw new InvalidOperationException("Warning poses require an active wind-up."); } return warning.Sample(elapsed); } public HawkFlightPose DivePose(double elapsed) { if (Stage != HawkStage.Diving || approach == null || pullout == null) { throw new InvalidOperationException("Dive poses require an active dive."); } if (!(elapsed <= approach.Duration)) { return pullout.Sample(elapsed - approach.Duration); } return approach.Sample(elapsed); } public Vector3 DivePosition(double elapsed) { return DivePose(elapsed).Position; } public bool TryStrike(Vector3 previous, Vector3 next, Vector3 playerCenter) { if (!Finite(previous) || !Finite(next) || !Finite(playerCenter)) { throw new ArgumentException("Strike positions must be finite."); } if (Stage != HawkStage.Diving || StrikeSpent) { return false; } Vector3 vector = next - previous; float num = vector.LengthSquared(); float num2 = ((num < 1E-10f) ? 0f : Math.Max(0f, Math.Min(1f, Vector3.Dot(playerCenter - previous, vector) / num))); if (Vector3.DistanceSquared(playerCenter, previous + vector * num2) > 0.5625f) { return false; } StrikeSpent = true; return true; } public void Abort() { if (Stage != HawkStage.Circling && Stage != HawkStage.Recovering) { Enter(HawkStage.Recovering); } } private void Enter(HawkStage stage) { Stage = stage; Elapsed = 0.0; } private static bool Finite(Vector3 value) { if (Finite(value.X) && Finite(value.Y)) { return Finite(value.Z); } return false; } private static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } } namespace GamblersReach.Core.Progression { public enum ChapterReward { None, AnglerLure, IslandFiveCoordinates } public sealed class ChapterProgress { public int Version { get; set; } = 1; public bool Initialized { get; set; } public bool ExistingIslandFiveAccess { get; set; } public bool BarracudaTrophyDelivered { get; set; } public bool AnglerLureClaimed { get; set; } public bool AnglerTrophyDelivered { get; set; } public bool CoordinatesClaimed { get; set; } public ChapterReward PendingReward { get; set; } public bool CanVisitIslandFive { get { if (!ExistingIslandFiveAccess) { return CoordinatesClaimed; } return true; } } public void Initialize(int nativeUnlockCount, int continuationIslandId = 4) { Validate(); if (nativeUnlockCount < 1 || nativeUnlockCount > 255) { throw new ArgumentOutOfRangeException("nativeUnlockCount"); } if (!Initialized) { ExistingIslandFiveAccess = InterludeRoutePolicy.IsUnlocked(continuationIslandId, nativeUnlockCount); Initialized = true; } } public void DeliverBarracudaTrophy() { RequireReady(); if (PendingReward != ChapterReward.None) { throw new InvalidOperationException("Take the waiting reward before offering another trophy."); } BarracudaTrophyDelivered = true; PendingReward = ChapterReward.AnglerLure; } public void DeliverAnglerTrophy() { RequireReady(); if (!AnglerLureClaimed || CoordinatesClaimed || PendingReward != ChapterReward.None) { throw new InvalidOperationException("The angler trophy is not needed at this stage."); } AnglerTrophyDelivered = true; PendingReward = ChapterReward.IslandFiveCoordinates; } public void Claim(ChapterReward reward) { RequireReady(); if (reward == ChapterReward.None || reward != PendingReward) { throw new InvalidOperationException("That reward is not waiting to be collected."); } if (reward == ChapterReward.AnglerLure) { AnglerLureClaimed = true; } else { CoordinatesClaimed = true; } PendingReward = ChapterReward.None; Validate(); } public void Validate() { if (Version != 1 || !Enum.IsDefined(typeof(ChapterReward), PendingReward) || (!Initialized && (ExistingIslandFiveAccess || BarracudaTrophyDelivered || AnglerLureClaimed || AnglerTrophyDelivered || CoordinatesClaimed || PendingReward != ChapterReward.None)) || (AnglerLureClaimed && !BarracudaTrophyDelivered) || (AnglerTrophyDelivered && !AnglerLureClaimed) || (CoordinatesClaimed && !AnglerTrophyDelivered) || (BarracudaTrophyDelivered && !AnglerLureClaimed && PendingReward != ChapterReward.AnglerLure) || (AnglerTrophyDelivered && !CoordinatesClaimed && PendingReward != ChapterReward.IslandFiveCoordinates) || (PendingReward == ChapterReward.AnglerLure && !BarracudaTrophyDelivered) || (PendingReward == ChapterReward.IslandFiveCoordinates && (!AnglerTrophyDelivered || CoordinatesClaimed))) { throw new ArgumentException("The harbor chapter's trophy and reward state is inconsistent."); } } private void RequireReady() { Validate(); if (!Initialized) { throw new InvalidOperationException("Select a crew save before starting the harbor chapter."); } } } public static class HarborSaveRevision { private const string Release080Plugin = "B82A1AA7035E23D749A682CA415B03FC585DDBC32A4A4640C5D47EDC9D031D44"; private const string Release080Core = "1AB9465A0B9A43B699DCAA2BF61EBD760B2F9C9C9BCE100ECE8BE268EE765260"; private const string Release081Plugin = "A697A545B6D2305402175F0291F1706D49762CF7F9DBBC8C4B1EB863858E36DF"; private const string Release081Core = "4DB633424F0CC228C9EAB0B136C58707873328676F246875CA97F769436B5ABA"; private const string Release082Plugin = "5A5102CF45FE8D331A2D080B24BC7A51900BFD644F75F80F752BAB2A0EAF61F0"; private const string Release082Core = "B02C833A811D2CA9327B473CD870FA26E425FB9D7E4BF55DBD135BD2ABD4F274"; private const string Verified083Plugin = "10E193C337211F02D717B03BC36C9F97BBB93DE4C95886095C246E384236C66C"; private const string Verified083Core = "3F47EA9DC4A2668B1734376BA66BDCBB2D1F227B670277D13DE7E148E83A806F"; private const string Final083Plugin = "BCE48FC27C98F680208514B75665A791FC16CBD2C08F821135B4985D19CE219B"; private const string Final083Core = "FA2A9199DE7B242B93BF9CDC1C34B8BC2801AE8A7B54AC23DA7F70FAD4517F2B"; private const string Published083Plugin = "2B4DDB34D01147F397367362370178E4B70961FFA6FB2121126164ED3C1DB946"; private const string Published083Core = "70DE6B29B13E099814C8C64FE808D73B9DB448CF771B3FCDA7C5A6642A2FF4CC"; public static bool Accept(SavedPack saved, IReadOnlyDictionary<string, string> code) { if (saved == null || code == null || saved.Key != "gamblers_reach" || code.Count != 2 || !code.TryGetValue("GamblersReach", out string value) || !code.TryGetValue("GamblersReach.Core", out string value2)) { return false; } return saved.Version switch { "0.7.0" => saved.Fingerprint == "23737c25e8a8e86f7707f11d58c433a39f7c1217593500db09e41093c79c59c6" && value == "5226473DBAED2950D4C89AD542707E8EE60E82C963081C130A3A22FE636B78C4" && value2 == "6EE04CA3CF88F4ED9342B3A5E263404F96B722F4EBDC61A3668A96C383FEB7FD", "0.7.1" => saved.Fingerprint == "22918057faa48535942230ca43139bef87aecb8982e607c46630b4ccaa5e76ab" && value == "38FBA14506562EA94D8DC9E3D1B8B20557B9786BD40B276C37B07BE15FBA1242" && value2 == "1C684E478FD719ACEFA93AFBE6FA735E25135482780054DCAD5018028CDFD0DE", "0.7.2" => saved.Fingerprint == "ae798b2247b389e535e12004a9cce35eaa0102a9448485e577e5e0df401b9cfa" && value == "FB131E532CEEAAB58274836CADA223CD3A463D5C6DDA0CDAF4BCA26D7F78FF8B" && value2 == "7886CA9C0B52923355305DF503ECB8E71970BA3BF3A54C4CB4A7035862E71272", "0.7.3" => saved.Fingerprint == "68da1823c373e929acfdfe89029c6f0e53cd96db7065f030e8e5d5240acf875c" && value == "6B84E52DABA097A21AAF5A6721A91E9D93163423EDAEF30CACD861A8F74A2C7F" && value2 == "689ED1F4A5C7321B9F78E68F0EE059ED228186B8F47C97FDF58DD1EC6E942C68", "0.8.0" => saved.Fingerprint == "1f102a86294e76cd038a3e49cf09246ecca2648d90d2c4baeabd17e49e760ec3" && value == "B82A1AA7035E23D749A682CA415B03FC585DDBC32A4A4640C5D47EDC9D031D44" && value2 == "1AB9465A0B9A43B699DCAA2BF61EBD760B2F9C9C9BCE100ECE8BE268EE765260", "0.8.1" => saved.Fingerprint == "e9c47b6e0ef99e06d4082fb3204f3724b8d1e45f5ac58b1e4cc594a9413ef0d5" && value == "A697A545B6D2305402175F0291F1706D49762CF7F9DBBC8C4B1EB863858E36DF" && value2 == "4DB633424F0CC228C9EAB0B136C58707873328676F246875CA97F769436B5ABA", "0.8.2" => saved.Fingerprint == "c3c851a52ccd6fe58581e7dce2199935db86f70cc60bcbea2eba416fa171d41c" && value == "5A5102CF45FE8D331A2D080B24BC7A51900BFD644F75F80F752BAB2A0EAF61F0" && value2 == "B02C833A811D2CA9327B473CD870FA26E425FB9D7E4BF55DBD135BD2ABD4F274", "0.8.3" => saved.Fingerprint == "c9fcbdb01ece013997b6192aaeaa5285afdd63c2df8eea89ea801315ae484fa2" && IsVerified083Code(value, value2), _ => false, }; } public static bool AcceptWildlife(SavedPack saved, IReadOnlyDictionary<string, string> code) { if (saved == null || saved.Key != "gamblers_reach_wildlife" || code == null || code.Count != 2 || !code.TryGetValue("GamblersReach", out string value) || !code.TryGetValue("GamblersReach.Core", out string value2)) { return false; } return saved.Version switch { "0.8.0" => saved.Fingerprint == "a7e42f62b2dc0bbaee2ae29d747f9d5d24a1e50939840209a32b452d7a1a5fb2" && value == "B82A1AA7035E23D749A682CA415B03FC585DDBC32A4A4640C5D47EDC9D031D44" && value2 == "1AB9465A0B9A43B699DCAA2BF61EBD760B2F9C9C9BCE100ECE8BE268EE765260", "0.8.1" => saved.Fingerprint == "7b978088f9d3925a2dc2641aa80abcf6cd5d75643c9204f1c22ecb1d40a83287" && value == "A697A545B6D2305402175F0291F1706D49762CF7F9DBBC8C4B1EB863858E36DF" && value2 == "4DB633424F0CC228C9EAB0B136C58707873328676F246875CA97F769436B5ABA", "0.8.2" => saved.Fingerprint == "8b9a83bbd5db323bdfae408d38db3d03cc47a4019b0a9752179db84c738ca2eb" && value == "5A5102CF45FE8D331A2D080B24BC7A51900BFD644F75F80F752BAB2A0EAF61F0" && value2 == "B02C833A811D2CA9327B473CD870FA26E425FB9D7E4BF55DBD135BD2ABD4F274", "0.8.3" => saved.Fingerprint == "9e5f9f2c0588b1a61258770f2692c299fbad35b2c56d1bae0059ef85cc7f339e" && IsVerified083Code(value, value2), _ => false, }; } private static bool IsVerified083Code(string? plugin, string? core) { if ((!(plugin == "10E193C337211F02D717B03BC36C9F97BBB93DE4C95886095C246E384236C66C") || !(core == "3F47EA9DC4A2668B1734376BA66BDCBB2D1F227B670277D13DE7E148E83A806F")) && (!(plugin == "BCE48FC27C98F680208514B75665A791FC16CBD2C08F821135B4985D19CE219B") || !(core == "FA2A9199DE7B242B93BF9CDC1C34B8BC2801AE8A7B54AC23DA7F70FAD4517F2B"))) { if (plugin == "2B4DDB34D01147F397367362370178E4B70961FFA6FB2121126164ED3C1DB946") { return core == "70DE6B29B13E099814C8C64FE808D73B9DB448CF771B3FCDA7C5A6642A2FF4CC"; } return false; } return true; } } public sealed class HuntProgress { public int Version { get; set; } = 1; public bool MiniBossDefeated { get; set; } public bool BossDefeated { get; set; } public bool TrophyReturned { get; set; } public bool RifleClaimed { get; set; } public void Validate() { if (Version != 1 || (BossDefeated && !MiniBossDefeated) || (TrophyReturned && !BossDefeated) || (RifleClaimed && !TrophyReturned)) { throw new ArgumentException("The hunt progress is inconsistent."); } } public bool RecordMiniBoss() { Validate(); if (MiniBossDefeated) { return false; } MiniBossDefeated = true; return true; } public bool RecordBoss() { Validate(); if (!MiniBossDefeated) { throw new InvalidOperationException("Defeat the Reef Marauder before the Breakwater King."); } if (BossDefeated) { return false; } BossDefeated = true; return true; } public void ReturnTrophy() { Validate(); if (!BossDefeated || TrophyReturned) { throw new InvalidOperationException("The crown is not needed at this stage."); } TrophyReturned = true; } public void ClaimRifle() { Validate(); if (!TrophyReturned || RifleClaimed) { throw new InvalidOperationException("The hunt reward is not available."); } RifleClaimed = true; } } public sealed class VanillaProgress { public const int FinalVanillaIslandId = 4; public int HighestCompletedIslandId { get; } public bool HasCompletedGame { get; } public VanillaProgress(int highestCompletedIslandId, bool hasCompletedGame = false) { if (highestCompletedIslandId < -1 || highestCompletedIslandId > 4) { throw new ArgumentOutOfRangeException("highestCompletedIslandId"); } HighestCompletedIslandId = highestCompletedIslandId; HasCompletedGame = hasCompletedGame; } } public enum QuestStage { Locked, Introduction, CollectSixSpecies, ClaimGearReward, PostgameRareCatch, Complete, AwaitFinale } public enum QuestNpcRole { Host, Angler, Outfitter } public sealed class QuestDefinition { public const int RequiredSpeciesCount = 6; private readonly ReadOnlyCollection<string> speciesIds; public IReadOnlyList<string> CollectionSpeciesIds => speciesIds; public string RareSpeciesId { get; } public QuestDefinition(IEnumerable<string> collectionSpeciesIds, string rareSpeciesId) { if (collectionSpeciesIds == null) { throw new ArgumentNullException("collectionSpeciesIds"); } string[] array = (from id in collectionSpeciesIds.Take(7) select Identifiers.Require(id, "collectionSpeciesIds")).ToArray(); if (array.Length != 6 || array.Distinct<string>(StringComparer.Ordinal).Count() != 6) { throw new ArgumentException("Exactly six distinct species IDs are required.", "collectionSpeciesIds"); } Array.Sort(array, (IComparer<string>?)StringComparer.Ordinal); speciesIds = Array.AsReadOnly(array); RareSpeciesId = Identifiers.Require(rareSpeciesId, "rareSpeciesId"); if (!array.Contains<string>(RareSpeciesId, StringComparer.Ordinal)) { throw new ArgumentException("The rare-catch target must be one of the six collection species.", "rareSpeciesId"); } } } public sealed class ModProgress { public int Version => 1; public bool IslandUnlocked { get; } public bool IntroductionCompleted { get; } public IReadOnlyList<string> CollectedSpeciesIds { get; } public bool GearRewardClaimed { get; } public bool RareCatchQuestCompleted { get; } internal ModProgress(bool islandUnlocked, bool introductionCompleted, IEnumerable<string> collectedSpeciesIds, bool gearRewardClaimed, bool rareCatchQuestCompleted) { IslandUnlocked = islandUnlocked; IntroductionCompleted = introductionCompleted; CollectedSpeciesIds = Array.AsReadOnly(collectedSpeciesIds.OrderBy<string, string>((string id) => id, StringComparer.Ordinal).ToArray()); GearRewardClaimed = gearRewardClaimed; RareCatchQuestCompleted = rareCatchQuestCompleted; } public static ModProgress New() { return new ModProgress(islandUnlocked: false, introductionCompleted: false, Array.Empty<string>(), gearRewardClaimed: false, rareCatchQuestCompleted: false); } public ModProgressSnapshot CreateSnapshot() { return new ModProgressSnapshot { Version = Version, IslandUnlocked = IslandUnlocked, IntroductionCompleted = IntroductionCompleted, CollectedSpeciesIds = CollectedSpeciesIds.ToArray(), GearRewardClaimed = GearRewardClaimed, RareCatchQuestCompleted = RareCatchQuestCompleted }; } } public sealed class ModProgressSnapshot { public const int CurrentVersion = 1; public int Version { get; set; } = 1; public bool IslandUnlocked { get; set; } public bool IntroductionCompleted { get; set; } public string[] CollectedSpeciesIds { get; set; } = Array.Empty<string>(); public bool GearRewardClaimed { get; set; } public bool RareCatchQuestCompleted { get; set; } } public sealed class QuestProgression { public QuestDefinition Definition { get; } public QuestProgression(QuestDefinition definition) { Definition = definition ?? throw new ArgumentNullException("definition"); } public static bool IsVanillaEligible(VanillaProgress vanilla) { if (vanilla == null) { throw new ArgumentNullException("vanilla"); } if (!vanilla.HasCompletedGame) { return vanilla.HighestCompletedIslandId >= 3; } return true; } public bool CanVisitIsland(VanillaProgress vanilla, ModProgress progress) { Validate(progress); if (!IsVanillaEligible(vanilla)) { return progress.IslandUnlocked; } return true; } public ModProgress RefreshEligibility(VanillaProgress vanilla, ModProgress progress) { if (!CanVisitIsland(vanilla, progress) || progress.IslandUnlocked) { return progress; } return Copy(progress, true); } public QuestStage GetStage(VanillaProgress vanilla, ModProgress progress) { if (!CanVisitIsland(vanilla, progress)) { return QuestStage.Locked; } if (!progress.IntroductionCompleted) { return QuestStage.Introduction; } if (progress.CollectedSpeciesIds.Count < 6) { return QuestStage.CollectSixSpecies; } if (!progress.GearRewardClaimed) { return QuestStage.ClaimGearReward; } if (progress.RareCatchQuestCompleted) { return QuestStage.Complete; } if (!vanilla.HasCompletedGame) { return QuestStage.AwaitFinale; } return QuestStage.PostgameRareCatch; } public static QuestNpcRole? NpcFor(QuestStage stage) { switch (stage) { case QuestStage.Introduction: return QuestNpcRole.Host; case QuestStage.CollectSixSpecies: return QuestNpcRole.Angler; case QuestStage.ClaimGearReward: return QuestNpcRole.Outfitter; case QuestStage.PostgameRareCatch: return QuestNpcRole.Angler; case QuestStage.Locked: case QuestStage.Complete: case QuestStage.AwaitFinale: return null; default: throw new ArgumentOutOfRangeException("stage"); } } public ModProgress CompleteIntroduction(VanillaProgress vanilla, ModProgress progress) { RequireStage(vanilla, progress, QuestStage.Introduction); return Copy(progress, true, true); } public ModProgress RecordCatch(VanillaProgress vanilla, ModProgress progress, string speciesId) { Identifiers.Require(speciesId, "speciesId"); progress = RefreshEligibility(vanilla, progress); if (!progress.IntroductionCompleted || !Definition.CollectionSpeciesIds.Contains<string>(speciesId, StringComparer.Ordinal)) { return progress; } if (!progress.CollectedSpeciesIds.Contains<string>(speciesId, StringComparer.Ordinal)) { ModProgress progress2 = progress; IEnumerable<string> collectedSpeciesIds = progress.CollectedSpeciesIds.Concat(new string[1] { speciesId }); return Copy(progress2, null, null, collectedSpeciesIds); } if (vanilla.HasCompletedGame && progress.GearRewardClaimed && !progress.RareCatchQuestCompleted && speciesId == Definition.RareSpeciesId) { ModProgress progress3 = progress; bool? rareCatchQuestCompleted = true; return Copy(progress3, null, null, null, null, rareCatchQuestCompleted); } return progress; } public ModProgress ClaimGearReward(VanillaProgress vanilla, ModProgress progress) { RequireStage(vanilla, progress, QuestStage.ClaimGearReward); bool? gearRewardClaimed = true; return Copy(progress, null, null, null, gearRewardClaimed); } public ModProgress Restore(ModProgressSnapshot snapshot) { if (snapshot == null) { throw new ArgumentNullException("snapshot"); } if (snapshot.Version != 1) { throw new ArgumentException("Unsupported mod progress version.", "snapshot"); } if (snapshot.CollectedSpeciesIds == null || snapshot.CollectedSpeciesIds.Length > 6) { throw new ArgumentException("Invalid collection state.", "snapshot"); } string[] collectedSpeciesIds = snapshot.CollectedSpeciesIds; for (int i = 0; i < collectedSpeciesIds.Length; i++) { Identifiers.Require(collectedSpeciesIds[i], "snapshot"); } if (snapshot.CollectedSpeciesIds.Distinct<string>(StringComparer.Ordinal).Count() != snapshot.CollectedSpeciesIds.Length) { throw new ArgumentException("Duplicate collected species IDs.", "snapshot"); } ModProgress modProgress = new ModProgress(snapshot.IslandUnlocked, snapshot.IntroductionCompleted, snapshot.CollectedSpeciesIds, snapshot.GearRewardClaimed, snapshot.RareCatchQuestCompleted); Validate(modProgress); return modProgress; } private void Validate(ModProgress progress) { if (progress == null) { throw new ArgumentNullException("progress"); } if ((progress.IntroductionCompleted && !progress.IslandUnlocked) || (progress.CollectedSpeciesIds.Count > 0 && !progress.IntroductionCompleted) || (progress.GearRewardClaimed && progress.CollectedSpeciesIds.Count != 6) || (progress.RareCatchQuestCompleted && !progress.GearRewardClaimed) || progress.CollectedSpeciesIds.Any((string id) => !Definition.CollectionSpeciesIds.Contains<string>(id, StringComparer.Ordinal))) { throw new ArgumentException("Progress is inconsistent with the quest definition or its prerequisites.", "progress"); } } private void RequireStage(VanillaProgress vanilla, ModProgress progress, QuestStage stage) { QuestStage stage2 = GetStage(vanilla, progress); if (stage2 != stage) { throw new InvalidOperationException($"This operation requires {stage}; the quest is {stage2}."); } } private static ModProgress Copy(ModProgress progress, bool? islandUnlocked = null, bool? introductionCompleted = null, IEnumerable<string>? collectedSpeciesIds = null, bool? gearRewardClaimed = null, bool? rareCatchQuestCompleted = null) { return new ModProgress(islandUnlocked ?? progress.IslandUnlocked, introductionCompleted ?? progress.IntroductionCompleted, collectedSpeciesIds ?? progress.CollectedSpeciesIds, gearRewardClaimed ?? progress.GearRewardClaimed, rareCatchQuestCompleted ?? progress.RareCatchQuestCompleted); } } } namespace GamblersReach.Core.Content { public sealed class ManifestEntry { public string Id { get; } public string CanonicalData { get; } public ManifestEntry(string id, string canonicalData) { Id = Identifiers.Require(id, "id"); CanonicalData = canonicalData ?? throw new ArgumentNullException("canonicalData"); } } public static class ContentManifest { public const int FormatVersion = 1; public static string ComputeChecksum(IEnumerable<ManifestEntry> entries) { if (entries == null) { throw new ArgumentNullException("entries"); } ManifestEntry[] array = entries.ToArray(); if (array.Any((ManifestEntry entry) => entry == null)) { throw new ArgumentException("A manifest cannot contain null entries.", "entries"); } Array.Sort(array, (ManifestEntry left, ManifestEntry right) => StringComparer.Ordinal.Compare(left.Id, right.Id)); for (int num = 1; num < array.Length; num++) { if (array[num - 1].Id == array[num].Id) { throw new ArgumentException("Duplicate content ID: " + array[num].Id, "entries"); } } using MemoryStream memoryStream = new MemoryStream(); using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), leaveOpen: true)) { WriteText(binaryWriter, "GamblersReach.ContentManifest"); binaryWriter.Write(1); binaryWriter.Write(array.Length); ManifestEntry[] array2 = array; foreach (ManifestEntry manifestEntry in array2) { WriteText(binaryWriter, manifestEntry.Id); WriteText(binaryWriter, manifestEntry.CanonicalData); } } using SHA256 sHA = SHA256.Create(); return BitConverter.ToString(sHA.ComputeHash(memoryStream.ToArray())).Replace("-", "").ToLowerInvariant(); } private static void WriteText(BinaryWriter writer, string value) { byte[] bytes = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetBytes(value); writer.Write(bytes.Length); writer.Write(bytes); } } } namespace GamblersReach.Core.Blackjack { public enum PlayerAction { Hit, Stand } public enum BlackjackRoundState { PlayerTurn, Resolved, SettlementPending, Settled } public sealed class BlackjackRound { private readonly CardDeck deck; private readonly List<Card> playerCards = new List<Card>(); private readonly List<Card> dealerCards = new List<Card>(); private readonly List<PlayerAction> actions = new List<PlayerAction>(); private readonly ReadOnlyCollection<Card> playerView; private readonly ReadOnlyCollection<Card> dealerView; private int nextCard; public Guid RoundId { get; } public ItemWager Wager { get; } public BlackjackRoundState State { get; private set; } public IReadOnlyList<Card> PlayerCards => playerView; public IReadOnlyList<Card> DealerCards => dealerView; public Card DealerUpCard => dealerCards[0]; public HandScore PlayerScore => HandEvaluator.Score(playerCards); public HandScore DealerScore => HandEvaluator.Score(dealerCards); public int CardsRemaining => deck.Cards.Count - nextCard; public BlackjackResult? Result { get; private set; } public SettlementInstruction? Settlement { get; private set; } public BlackjackRound(Guid roundId, ItemWager wager, CardDeck deck) { if (roundId == Guid.Empty) { throw new ArgumentException("A globally unique nonempty round ID is required.", "roundId"); } RoundId = roundId; Wager = wager ?? throw new ArgumentNullException("wager"); this.deck = deck ?? throw new ArgumentNullException("deck"); playerView = playerCards.AsReadOnly(); dealerView = dealerCards.AsReadOnly(); playerCards.Add(Draw()); dealerCards.Add(Draw()); playerCards.Add(Draw()); dealerCards.Add(Draw()); if (PlayerScore.IsNatural || DealerScore.IsNatural) { Resolve((PlayerScore.IsNatural && DealerScore.IsNatural) ? BlackjackOutcome.Push : (PlayerScore.IsNatural ? BlackjackOutcome.Natural : BlackjackOutcome.Loss)); } } public void Play(PlayerAction action) { if (action != PlayerAction.Hit && action != PlayerAction.Stand) { throw new ArgumentOutOfRangeException("action"); } RequireState(BlackjackRoundState.PlayerTurn); if (action == PlayerAction.Hit) { playerCards.Add(Draw()); if (PlayerScore.IsBust) { Resolve(BlackjackOutcome.Loss); } else if (PlayerScore.Total == 21) { FinishDealer(); } } else { FinishDealer(); } actions.Add(action); } public SettlementInstruction PrepareSettlement() { RequireState(BlackjackRoundState.Resolved); Settlement = new SettlementInstruction(Wager, Result); State = BlackjackRoundState.SettlementPending; return Settlement; } public void AcknowledgeSettlement(Guid settlementId) { RequireState(BlackjackRoundState.SettlementPending); if (settlementId != RoundId) { throw new ArgumentException("The acknowledgement belongs to a different settlement.", "settlementId"); } State = BlackjackRoundState.Settled; } public RoundSnapshot CreateSnapshot() { return new RoundSnapshot { Version = 1, RoundId = RoundId, EscrowId = Wager.EscrowId, WagerValue = Wager.Value, Deck = deck.Cards.Select((Card card) => new CardSnapshot { Suit = card.Suit, Rank = card.Rank }).ToArray(), Actions = actions.ToArray(), State = State }; } public static BlackjackRound Restore(RoundSnapshot snapshot) { if (snapshot == null) { throw new ArgumentNullException("snapshot"); } if (snapshot.Version != 1) { throw new ArgumentException("Unsupported round snapshot version.", "snapshot"); } if (snapshot.Deck == null || snapshot.Deck.Length != 52 || snapshot.Deck.Any((CardSnapshot card) => card == null)) { throw new ArgumentException("A complete recovery deck is required.", "snapshot"); } if (snapshot.Actions == null || snapshot.Actions.Length > 48) { throw new ArgumentException("Invalid action history.", "snapshot"); } CardDeck cardDeck = new CardDeck(snapshot.Deck.Select((CardSnapshot card) => new Card(card.Suit, card.Rank))); BlackjackRound blackjackRound = new BlackjackRound(snapshot.RoundId, new ItemWager(snapshot.EscrowId, snapshot.WagerValue), cardDeck); try { PlayerAction[] array = snapshot.Actions; foreach (PlayerAction action in array) { blackjackRound.Play(action); } switch (snapshot.State) { case BlackjackRoundState.PlayerTurn: case BlackjackRoundState.Resolved: if (blackjackRound.State != snapshot.State) { throw new ArgumentException("Saved state disagrees with the replayed round.", "snapshot"); } break; case BlackjackRoundState.SettlementPending: blackjackRound.PrepareSettlement(); break; case BlackjackRoundState.Settled: blackjackRound.PrepareSettlement(); blackjackRound.AcknowledgeSettlement(blackjackRound.RoundId); break; default: throw new ArgumentException("Unknown saved round state.", "snapshot"); } } catch (InvalidOperationException innerException) { throw new ArgumentException("The recovery history contains an out-of-turn transition.", "snapshot", innerException); } return blackjackRound; } private Card Draw() { if (nextCard == deck.Cards.Count) { throw new InvalidOperationException("The deck is exhausted."); } return deck.Cards[nextCard++]; } private void FinishDealer() { while (DealerScore.Total < 17) { dealerCards.Add(Draw()); } Resolve((DealerScore.IsBust || PlayerScore.Total > DealerScore.Total) ? BlackjackOutcome.Win : ((PlayerScore.Total == DealerScore.Total) ? BlackjackOutcome.Push : BlackjackOutcome.Loss)); } private void Resolve(BlackjackOutcome outcome) { RequireState(BlackjackRoundState.PlayerTurn); if (Result != null) { throw new InvalidOperationException("The round has already resolved."); } Result = new BlackjackResult(RoundId, outcome, PlayerScore, DealerScore); State = BlackjackRoundState.Resolved; } private void RequireState(BlackjackRoundState required) { if (State != required) { throw new InvalidOperationException($"This operation requires {required}; the round is {State}."); } } } public sealed class CardSnapshot { public Suit Suit { get; set; } public Rank Rank { get; set; } } public sealed class RoundSnapshot { public const int CurrentVersion = 1; public int Version { get; set; } = 1; public Guid RoundId { get; set; } public string EscrowId { get; set; } = string.Empty; public float WagerValue { get; set; } public CardSnapshot[] Deck { get; set; } = Array.Empty<CardSnapshot>(); public PlayerAction[] Actions { get; set; } = Array.Empty<PlayerAction>(); public BlackjackRoundState State { get; set; } } public sealed class BlackjackTableGate { public long Revision { get; private set; } public bool IsLocked { get; private set; } public int ActorId { get; private set; } = -1; public Guid RoundId { get; private set; } public void RequireAvailable(long expectedRevision) { if (IsLocked) { throw new InvalidOperationException("The blackjack table is in use. Wait for the current hand to finish."); } if (expectedRevision != Revision) { throw new InvalidOperationException("The table changed before your deal arrived. Review the table and try again."); } } public void Claim(long expectedRevision, int actorId, Guid roundId) { RequireAvailable(expectedRevision); if (actorId < 0) { throw new ArgumentOutOfRangeException("actorId"); } if (roundId == Guid.Empty) { throw new ArgumentException("A hand must have a round identity.", "roundId"); } long revision = checked(Revision + 1); ActorId = actorId; RoundId = roundId; IsLocked = true; Revision = revision; } public void RequireActor(int actorId, Guid roundId) { if (!IsLocked || actorId != ActorId || roundId != RoundId) { throw new InvalidOperationException("Only the player who dealt this hand can control it."); } } public void Advance(int actorId, Guid roundId) { RequireActor(actorId, roundId); checked { Revision++; } } public void Release(int actorId, Guid roundId) { RequireActor(actorId, roundId); long revision = checked(Revision + 1); IsLocked = false; Revision = revision; } public void Reset() { long revision = checked(Revision + 1); IsLocked = false; ActorId = -1; RoundId = Guid.Empty; Revision = revision; } } public enum Suit { Clubs, Diamonds, Hearts, Spades } public enum Rank { Ace = 1, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King } public readonly struct Card : IEquatable<Card> { public Suit Suit { get; } public Rank Rank { get; } public bool IsValid { get { if (Suit >= Suit.Clubs && Suit <= Suit.Spades && Rank >= Rank.Ace) { return Rank <= Rank.King; } return false; } } public Card(Suit suit, Rank rank) { if (suit < Suit.Clubs || suit > Suit.Spades) { throw new ArgumentOutOfRangeException("suit"); } if (rank < Rank.Ace || rank > Rank.King) { throw new ArgumentOutOfRangeException("rank"); } Suit = suit; Rank = rank; } public bool Equals(Card other) { if (Suit == other.Suit) { return Rank == other.Rank; } return false; } public override bool Equals(object? obj) { if (obj is Card other) { return Equals(other); } return false; } public override int GetHashCode() { return (int)((int)Suit * 13 + Rank); } public override string ToString() { return $"{Rank} of {Suit}"; } public static bool operator ==(Card left, Card right) { return left.Equals(right); } public static bool operator !=(Card left, Card right) { return !left.Equals(right); } } public readonly struct HandScore { public int Total { get; } public bool IsSoft { get; } public int CardCount { get; } public bool IsBust => Total > 21; public bool IsNatural { get { if (CardCount == 2) { return Total == 21; } return false; } } internal HandScore(int total, bool isSoft, int cardCount) { Total = total; IsSoft = isSoft; CardCount = cardCount; } } public static class HandEvaluator { public static HandScore Score(IEnumerable<Card> cards) { if (cards == null) { throw new ArgumentNullException("cards"); } int num = 0; int num2 = 0; int num3 = 0; foreach (Card card in cards) { if (!card.IsValid) { throw new ArgumentException("A hand contains an invalid card.", "cards"); } checked { num += Math.Min(unchecked((int)card.Rank), 10); num3++; } if (card.Rank == Rank.Ace) { num2++; } } bool flag = num2 > 0 && num <= 11; return new HandScore(flag ? (num + 10) : num, flag, num3); } } public sealed class CardDeck { private readonly ReadOnlyCollection<Card> cards; public IReadOnlyList<Card> Cards => cards; public CardDeck(IEnumerable<Card> orderedCards) { if (orderedCards == null) { throw new ArgumentNullException("orderedCards"); } Card[] array = orderedCards.Take(53).ToArray(); if (array.Length != 52 || array.Any((Card card) => !card.IsValid) || array.Distinct().Count() != 52) { throw new ArgumentException("A deck must contain each of the 52 valid cards exactly once.", "orderedCards"); } cards = Array.AsReadOnly(array); } public static CardDeck Ordered() { List<Card> list = new List<Card>(52); for (int i = 0; i < 4; i++) { for (int j = 1; j <= 13; j++) { list.Add(new Card((Suit)i, (Rank)j)); } } return new CardDeck(list); } public static CardDeck Shuffled(Random random) { if (random == null) { throw new ArgumentNullException("random"); } Card[] array = Ordered().Cards.ToArray(); for (int num = array.Length - 1; num > 0; num--) { int num2 = random.Next(num + 1); if (num2 < 0 || num2 > num) { throw new ArgumentException("The random source returned an invalid index.", "random"); } ref Card reference = ref array[num]; ref Card reference2 = ref array[num2]; Card card = array[num2]; Card card2 = array[num]; reference = card; reference2 = card2; } return new CardDeck(array); } } public enum BlackjackOutcome { Loss, Push, Win, Natural } public enum SettlementKind { Forfeit, ReturnOriginalItems, AwardItems } public static class PayoutRules { public const float MaximumWagerValue = 1000000f; public const float MaximumReturnMultiplier = 2.5f; public static void ValidateWagerValue(float value) { if (float.IsNaN(value) || float.IsInfinity(value) || value <= 0f || value > 1000000f) { throw new ArgumentOutOfRangeException("value", "Wager value must be finite, positive, and at most 1,000,000."); } } public static float MultiplierFor(BlackjackOutcome outcome) { return outcome switch { BlackjackOutcome.Loss => 0f, BlackjackOutcome.Push => 1f, BlackjackOutcome.Win => 2f, BlackjackOutcome.Natural => 2.5f, _ => throw new ArgumentOutOfRangeException("outcome"), }; } public static void ValidateReturnMultiplier(float multiplier) { if (multiplier != 0f && multiplier != 1f && multiplier != 2f && multiplier != 2.5f) { throw new ArgumentOutOfRangeException("multiplier", "Only the four defined payout multipliers are supported."); } } public static float CalculateReturnValue(float wagerValue, float multiplier) { ValidateWagerValue(wagerValue); ValidateReturnMultiplier(multiplier); return wagerValue * multiplier; } } public sealed class ItemWager { public string EscrowId { get; } public float Value { get; } public ItemWager(string escrowId, float value) { EscrowId = Identifiers.Require(escrowId, "escrowId"); PayoutRules.ValidateWagerValue(value); Value = value; } } public sealed class BlackjackResult { public Guid RoundId { get; } public BlackjackOutcome Outcome { get; } public HandScore PlayerScore { get; } public HandScore DealerScore { get; } public float TotalReturnMultiplier => PayoutRules.MultiplierFor(Outcome); public bool ReturnOriginalItems => Outcome == BlackjackOutcome.Push; internal BlackjackResult(Guid roundId, BlackjackOutcome outcome, HandScore playerScore, HandScore dealerScore) { RoundId = roundId; Outcome = outcome; PlayerScore = playerScore; DealerScore = dealerScore; } } public sealed class SettlementInstruction { public Guid SettlementId { get; } public string EscrowId { get; } public float WagerValue { get; } public SettlementKind Kind { get; } public float TotalReturnMultiplier { get; } public float TotalReturnValue => PayoutRules.CalculateReturnValue(WagerValue, TotalReturnMultiplier); internal SettlementInstruction(ItemWager wager, BlackjackResult result) { SettlementId = result.RoundId; EscrowId = wager.EscrowId; WagerValue = wager.Value; TotalReturnMultiplier = result.TotalReturnMultiplier; Kind = ((result.Outcome != BlackjackOutcome.Loss) ? (result.ReturnOriginalItems ? SettlementKind.ReturnOriginalItems : SettlementKind.AwardItems) : SettlementKind.Forfeit); } } }
BepInEx/plugins/GamblersReach/GamblersReach.dll
Decompiled 2 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using BepInEx; using BepInEx.Logging; using FishNet; using FishNet.Broadcast; using FishNet.Connection; using FishNet.Managing; using FishNet.Managing.Logging; using FishNet.Managing.Server; using FishNet.Object; using FishNet.Serializing; using FishNet.Transporting; using GamblersReach.Art; using GamblersReach.Characters; using GamblersReach.Content; using GamblersReach.Core.Blackjack; using GamblersReach.Core.Progression; using GamblersReach.Core.Wildlife; using GamblersReach.Encounters; using GamblersReach.Gameplay; using GamblersReach.Interface; using GamblersReach.Networking; using GamblersReach.Wildlife; using GamblersReach.World; using HarmonyLib; using HowToFish.ExpansionKit; using HowToFish.ExpansionKit.Assets; using HowToFish.ExpansionKit.Packs; using HowToFish.ExpansionKit.Progression; using HowToFish.ExpansionKit.Runtime; using HowToFish.ExpansionKit.Runtime.Commerce; using HowToFish.ExpansionKit.Runtime.Content; using HowToFish.ExpansionKit.Runtime.Persistence; using HowToFish.ExpansionKit.Runtime.Rendering; using HowToFish.ExpansionKit.Runtime.World; using HowToFish.ExpansionKit.World; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using TMPro; using UnityEngine; using UnityEngine.Localization; using UnityEngine.Localization.Components; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("GamblersReach.Qa")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("GamblersReach")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.8.4.0")] [assembly: AssemblyInformationalVersion("0.8.4")] [assembly: AssemblyProduct("GamblersReach")] [assembly: AssemblyTitle("GamblersReach")] [assembly: AssemblyVersion("0.8.4.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 GamblersReach { internal static class ExpansionProfile { internal static string Folder => ExpansionSaveProfiles.Folder; } internal sealed class ExpansionRuntime : MonoBehaviour { private sealed class HarborPackAssets : IPackAssetFactory { private readonly AuthoredAssets assets; internal HarborPackAssets(AuthoredAssets assets) { this.assets = assets; } public GameObject Instantiate(string packKey, ArtRecipe art) { if (packKey != "gamblers_reach" || art.Bundle != "gamblersreach.models") { throw new InvalidOperationException("The harbor art adapter received another pack's asset."); } return assets.CreateVisual(Path.GetFileNameWithoutExtension(art.Prefab)); } } [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static Func<ItemDefinition, bool> <>9__65_2; public static ExternalLootBinder <>9__65_0; public static Func<NativeItemRegistrar, bool> <>9__66_0; public static Func<NativeItemRegistrar, bool> <>9__66_1; public static Func<NativeLootTable, bool> <>9__66_2; public static Func<LoadSceneMode, AsyncOperation?> <>9__67_2; internal bool <ConfigurePack>b__65_2(ItemDefinition item) { //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_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 ItemKind kind = item.Kind; if (kind - 3 <= 1) { return true; } return false; } internal NativeLootTable <ConfigurePack>b__65_0(ExternalRecipeContext<LootRecipe> context) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown <>c__DisplayClass65_0 CS$<>8__locals3 = new <>c__DisplayClass65_0 { context = context }; return new NativeLootTable(((PackRecipe)CS$<>8__locals3.context.Recipe).Key, ((IEnumerable<LootDropRecipe>)CS$<>8__locals3.context.Recipe.Drops).Select((Func<LootDropRecipe, LootDrop>)((LootDropRecipe drop) => new LootDrop(CS$<>8__locals3.context.Installer.ResolveItem(ContentReference.Parse(drop.Item)), drop.FixedQuantity, drop.PerPlayerQuantity, ContentReference.Parse(drop.Item)))), (ManualLogSource)null); } internal bool <ContentInstalled>b__66_0(NativeItemRegistrar registrar) { return registrar.CollectionId == 48187; } internal bool <ContentInstalled>b__66_1(NativeItemRegistrar registrar) { return registrar.CollectionId == 48188; } internal bool <ContentInstalled>b__66_2(NativeLootTable table) { return table.Key == "angler_loot"; } internal AsyncOperation? <BindPlatformWorld>b__67_2(LoadSceneMode mode) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return ReachWorld.LoadScene(7, mode); } } [CompilerGenerated] private sealed class <>c__DisplayClass65_0 { public ExternalRecipeContext<LootRecipe> context; internal LootDrop <ConfigurePack>b__4(LootDropRecipe drop) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown return new LootDrop(context.Installer.ResolveItem(ContentReference.Parse(drop.Item)), drop.FixedQuantity, drop.PerPlayerQuantity, ContentReference.Parse(drop.Item)); } } private bool prepared; private AuthoredAssets? authoredAssets; private HarborSceneAssets? harborAssets; private bool disposed; private ChapterQuests? chapterQuests; internal static ExpansionRuntime? Instance { get; private set; } internal bool Ready { get { if (prepared && Object.op_Implicit((Object)(object)ExpansionPlatform.Current)) { return ExpansionPlatform.Current.CoreReady; } return false; } } internal string Status { get; private set; } = "Preparing the extra island..."; internal ArtFactory Art { get; private set; } internal ContentRegistry Content { get; private set; } internal EncounterCatalog Encounters { get; private set; } internal HarborLureCatalog Lures { get; private set; } internal NpcService Characters { get; private set; } internal ReachWorld World { get; private set; } internal ReachNetwork Network { get; private set; } internal ReachSession Session { get; private set; } internal ReachPanel Panel { get; private set; } internal NativeWorldServices NativeServices { get; private set; } internal IslandArtLayout? ArtLayout { get; private set; } internal HawkContent Hawks { get; private set; } private void Awake() { //IL_00a6: 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_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Expected O, but got Unknown Instance = this; Characters = new NpcService(); NativeServices = new NativeWorldServices(); World = new ReachWorld(Characters.CaptureTemplates, BuildWorld, SpawnCharacters, ClearSceneServices, NativeServices.Capture); World.IslandCreated += SpawnGrass; ExpansionPlatform.Current.RegisterExtension(new PackExtension("gamblers_reach", (Func<PackSource>)PrepareSource, (Action<PackContentOptions>)ConfigurePack) { ContentInstalled = ContentInstalled, BindWorld = BindPlatformWorld, PrepareGameplay = PrepareGameplay, GameplayReady = () => prepared, VerifyScene = VerifyPlatformScene, AcceptSavedRevision = HarborPackDefinition.AcceptSavedRevision }); Hawks = new HawkContent(this); } private PackSource PrepareSource() { //IL_00c7: Unknown result type (might be due to invalid IL or missing references) IslandManager val = Object.FindAnyObjectByType<IslandManager>(); if (!Object.op_Implicit((Object)(object)val)) { throw new InvalidOperationException("The native island manager was not found."); } World.Configure(val); Art = new ArtFactory(); authoredAssets = new AuthoredAssets(); harborAssets = new HarborSceneAssets(); World.UseAuthoredScene(harborAssets.ScenePath, "GamblersReachHarbor"); Content = new ContentRegistry(); Content.Begin(authoredAssets.CreateVisual, authoredAssets.CreateVisual); Encounters = new EncounterCatalog(); Encounters.Begin(authoredAssets.CreateVisual); return HarborPackDefinition.Create(World.Position); } private void ConfigurePack(PackContentOptions options) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Expected O, but got Unknown //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Expected O, but got Unknown options.Assets = (IPackAssetFactory)(object)new HarborPackAssets(authoredAssets ?? throw new InvalidOperationException("The original harbor assets are unavailable.")); foreach (ContentDescriptor descriptor in Content.Descriptors) { options.ExternalItems.Add(descriptor.Key, (ExternalItemBinder)((ExternalRecipeContext<ItemRecipe> context, NativeItemRegistrar registrar) => Content.Prepare(context.Recipe, registrar))); } foreach (ItemDefinition item in HarborDefinition.Value.Items.Where(delegate(ItemDefinition item) { //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_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 ItemKind kind = item.Kind; return kind - 3 <= 1; })) { options.ExternalItems.Add(item.Key, (ExternalItemBinder)((ExternalRecipeContext<ItemRecipe> context, NativeItemRegistrar registrar) => Encounters.Prepare(context.Recipe, registrar))); } Dictionary<string, ExternalLootBinder> externalLoot = options.ExternalLoot; object obj = <>c.<>9__65_0; if (obj == null) { ExternalLootBinder val = (ExternalRecipeContext<LootRecipe> context) => new NativeLootTable(((PackRecipe)context.Recipe).Key, ((IEnumerable<LootDropRecipe>)context.Recipe.Drops).Select((Func<LootDropRecipe, LootDrop>)((LootDropRecipe drop) => new LootDrop(context.Installer.ResolveItem(ContentReference.Parse(drop.Item)), drop.FixedQuantity, drop.PerPlayerQuantity, ContentReference.Parse(drop.Item)))), (ManualLogSource)null); <>c.<>9__65_0 = val; obj = (object)val; } externalLoot.Add("angler_loot", (ExternalLootBinder)obj); } private void ContentInstalled(PackContentInstaller installer) { Content.Complete(installer.Registrars.Single((NativeItemRegistrar registrar) => registrar.CollectionId == 48187)); Encounters.Complete(installer.Registrars.Single((NativeItemRegistrar registrar) => registrar.CollectionId == 48188)); Encounters.BindLoot(installer.Loot.Tables.Single((NativeLootTable table) => table.Key == "angler_loot")); Lures = new HarborLureCatalog(); Lures.Bind(installer.Lures ?? throw new InvalidOperationException("The shared harbor lures did not register.")); NativeServices.PrepareHarpoonUpgrades(Content.HarpoonUpgrades); Network = new ReachNetwork(InstanceFinder.NetworkManager); Panel = ((Component)this).gameObject.AddComponent<ReachPanel>(); Panel.Initialize(SendAction); Session = new ReachSession(Content, World, Network, Characters, Panel); Session.ChapterChanged += World.RefreshChapterGate; } private void BindPlatformWorld(ExpansionPlatform platform) { platform.World.RegisterExternalIsland("gamblers_reach:harbor", (Func<IslandInfo>)(() => World.Location ?? throw new InvalidOperationException("The harbor approach was not configured.")), (Func<bool>)(() => Session.ChapterState.CoordinatesClaimed), (Func<LoadSceneMode, AsyncOperation>)((LoadSceneMode mode) => ReachWorld.LoadScene(7, mode)), (Func<bool>)(() => Session.ChapterState.ExistingIslandFiveAccess)); World.IslandCreated += delegate { //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (ArtLayout == null) { throw new InvalidOperationException("The harbor's authored scene did not bind."); } platform.World.MountScene("gamblers_reach:harbor", (byte)6, ArtLayout.Root.scene, ArtLayout.Root.transform); }; } private void VerifyPlatformScene(MountedIsland scene) { if (scene.Reference != "gamblers_reach:harbor") { return; } NPC[] componentsInChildren = ((Component)scene.Root).GetComponentsInChildren<NPC>(true); foreach (NpcDefinition resident in HarborDefinition.Value.Npcs) { if (componentsInChildren.Count((NPC npc) => npc.ID == resident.Id) != 1) { throw new InvalidOperationException("An authored harbor NPC failed to mount: " + resident.Key); } } if (((Component)scene.Root).GetComponentsInChildren<Purchasable>(true).Length >= HarborDefinition.Value.Shops.Count) { return; } throw new InvalidOperationException("The harbor did not mount all its declared shop services."); } private IEnumerator PrepareGameplay() { Status = "Preparing native character and boat behavior..."; yield return World.Prepare(); if (!Characters.HasTemplates) { throw new InvalidOperationException("No compatible native NPC rigs were captured. See the template diagnostics in the log."); } if (!Characters.HasSaleQuest) { throw new InvalidOperationException("The native merchant quest was not captured: " + Characters.SaleQuestOrigin); } if (!NativeServices.IsPrepared) { throw new InvalidOperationException("Native services, grass or the Reel of Fortune could not be prepared."); } prepared = true; Status = "Gambler's Reach ready. A new route opens after island four."; Plugin.Log.LogInfo((object)Status); } private WorldLayout BuildWorld(Transform parent) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0047: 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) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) ArtLayout = (harborAssets ?? throw new InvalidOperationException("The authored harbor is unavailable.")).Bind(((Component)parent).gameObject); return new WorldLayout { PlayerSpawn = ArtLayout.PlayerSpawn + Vector3.up * World.PlayerSpawnClearance, BoatSpawn = ArtLayout.BoatSpawn, PlayerHeading = ArtLayout.PlayerHeading, TablePosition = ArtLayout.TablePosition, NpcPositions = new Dictionary<string, Vector3>(StringComparer.Ordinal) { ["tackle"] = ArtLayout.TackleMakerPosition, ["collector"] = ArtLayout.MarineCollectorPosition, ["dealer"] = ArtLayout.DealerPosition } }; } private void OnNpcInteraction(string key) { SendAction(ReachAction.Talk, key); } private void SpawnCharacters(Transform parent, WorldLayout layout) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) if (ArtLayout == null) { throw new InvalidOperationException("The authored island layout is missing."); } NativeServices.Spawn(parent, Content, ArtLayout.ShopPositions, ArtLayout.ShopHeadings, ArtLayout.SlotMachinePosition, ArtLayout.SlotMachineHeading); NativeServices.Cooking.Spawn(parent, ArtLayout.NativeDecorPositions["grill"], ArtLayout.NativeDecorHeadings["grill"]); Characters.Spawn(parent, layout.NpcPositions, OnNpcInteraction); Characters.Targets["tackle"].localRotation = Quaternion.Euler(0f, ArtLayout.NpcHeadings["tackle_maker"], 0f); Characters.Targets["collector"].localRotation = Quaternion.Euler(0f, ArtLayout.NpcHeadings["marine_collector"], 0f); Characters.Targets["dealer"].localRotation = Quaternion.Euler(0f, ArtLayout.NpcHeadings["blackjack_dealer"], 0f); NPC component = ((Component)Characters.Targets["collector"]).GetComponent<NPC>(); ((Component)component).GetComponent<ReachNpcMarker>().NativeChapter = true; chapterQuests = new ChapterQuests(component, Lures.Get("angler_lure"), Encounters.Get(192), Encounters.Get(193)); Session.BindChapterQuests(chapterQuests); NativeBlackjackDisplay display = NativeServices.SpawnBlackjackPresentation(parent, layout.TablePosition, ArtLayout.BlackjackPlayerHeading); Panel.BindTable(parent, layout.TablePosition, ArtLayout.BlackjackPlayerHeading, display); Session.RefreshDeposit(); } private void SpawnGrass() { if (ArtLayout == null) { throw new InvalidOperationException("The active harbor has no authored grass placement surface."); } NativeServices.Grass.Spawn(ArtLayout.Root.transform); } private void ClearSceneServices() { chapterQuests?.Dispose(); chapterQuests = null; Characters.ClearScene(); NativeServices.ClearScene(); ArtLayout = null; } private void SendAction(ReachAction action, string argument) { try { Network.Send(action, argument); } catch (InvalidOperationException ex) { Plugin.Log.LogWarning((object)ex.Message); Panel.ShowError(ex.Message); } } private void Update() { if (Network != null) { Network.Tick(); } if (Session != null) { Session.Tick(); } } private void OnGUI() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (!Ready && MainMenuManager.IsInMenu) { GUI.Box(new Rect(16f, (float)(Screen.height - 80), (float)Math.Min(600, Screen.width - 32), 60f), "Gambler's Reach\n" + Status); } } private void OnDestroy() { if (!disposed) { disposed = true; Plugin.Log.LogInfo((object)"Disposing the expansion runtime."); prepared = false; if (World != null) { World.IslandCreated -= SpawnGrass; } if (Session != null && World != null) { Session.ChapterChanged -= World.RefreshChapterGate; } chapterQuests?.Dispose(); Session?.Dispose(); Network?.Dispose(); World?.Dispose(); Characters?.Dispose(); NativeServices?.Dispose(); Lures?.Dispose(); Encounters?.Dispose(); Content?.Dispose(); authoredAssets?.Dispose(); harborAssets?.Dispose(); Art?.Dispose(); Instance = null; } } } [BepInPlugin("friends.howtofish.gamblersreach", "Gambler's Reach", "0.8.4")] [BepInDependency("howtofish.expansionkit.runtime", "0.7.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string Id = "friends.howtofish.gamblersreach"; public const string Name = "Gambler's Reach"; public const string Version = "0.8.4"; internal static readonly Guid SupportedGame = new Guid("91a1729d-8ab8-4d3b-afcf-c6edfa435491"); private Harmony? patches; internal static ManualLogSource Log { get; private set; } = null; internal static Plugin Instance { get; private set; } = null; private void Awake() { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; Guid moduleVersionId = typeof(Item).Assembly.ManifestModule.ModuleVersionId; if (moduleVersionId != SupportedGame) { string text = $"Unsupported How to Fish build ({moduleVersionId}). Expected game 1.0.12. No expansion content was loaded."; Log.LogError((object)text); throw new NotSupportedException(text); } patches = new Harmony("friends.howtofish.gamblersreach"); patches.PatchAll(typeof(Plugin).Assembly); RegisterContent(); Log.LogInfo((object)("Gambler's Reach 0.8.4 loaded on Unity " + Application.unityVersion + "; game assembly verified.")); } private void RegisterContent() { ((Component)this).gameObject.AddComponent<ExpansionRuntime>(); } } } namespace GamblersReach.World { internal sealed class BlackjackDeposit : MonoBehaviour { internal const int MaximumItems = 12; private BoxCollider region; internal static BlackjackDeposit? Current { get; private set; } internal Vector3 Position => ((Component)region).transform.TransformPoint(region.center); internal bool Ready { get { if (Object.op_Implicit((Object)(object)region)) { return ((Component)region).gameObject.activeInHierarchy; } return false; } } internal void Configure(BoxCollider volume) { if (!Object.op_Implicit((Object)(object)volume) || !((Collider)volume).isTrigger) { throw new ArgumentException("The blackjack deposit needs a dedicated trigger volume.", "volume"); } if (Object.op_Implicit((Object)(object)Current) && (Object)(object)Current != (Object)(object)this) { throw new InvalidOperationException("Only one shared blackjack deposit may be active."); } int num = LayerMask.NameToLayer("Ignore Raycast"); if (num < 0) { throw new InvalidOperationException("The wager sensor's non-interactive layer is unavailable."); } ((Component)volume).gameObject.layer = num; ((Component)volume).gameObject.tag = "Untagged"; region = volume; Current = this; } internal Item[] ItemsSnapshot() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) if (!Ready) { return Array.Empty<Item>(); } Physics.SyncTransforms(); Vector3 lossyScale = ((Component)region).transform.lossyScale; Vector3 val = Vector3.Scale(region.size * 0.5f, new Vector3(Mathf.Abs(lossyScale.x), Mathf.Abs(lossyScale.y), Mathf.Abs(lossyScale.z))); Collider[] array = Physics.OverlapBox(Position, val, ((Component)region).transform.rotation, LayerMask.op_Implicit(GameInfo.ItemLayer), (QueryTriggerInteraction)2); HashSet<Item> hashSet = new HashSet<Item>(); Collider[] array2 = array; foreach (Collider val2 in array2) { Item value = null; if (!ItemManager.Items.TryGetValue(((Component)val2).transform, out value) && Object.op_Implicit((Object)(object)val2.attachedRigidbody)) { ItemManager.Items.TryGetValue(((Component)val2.attachedRigidbody).transform, out value); } if (Object.op_Implicit((Object)(object)value)) { hashSet.Add(value); } } return hashSet.OrderBy((Item item) => ((NetworkBehaviour)item).ObjectId).ToArray(); } private void OnDestroy() { if ((Object)(object)Current == (Object)(object)this) { Current = null; } } } internal enum CumulativeShopKind { Weapon, Melee, Explosive, Sharpening, Rod, Utility, Lure, Pocket, Motor } internal sealed class CumulativeShopOffer { internal string Key { get; } internal string Reference => "native_" + Key.Substring("legacy_".Length); internal CumulativeShopKind Kind { get; } internal byte? NativeItemId { get; } internal byte? NativeLureIndex { get; } internal byte? NativeTier { get; } internal string NativeName { get; } internal string? SourceName { get; } internal int? FixedPrice { get; } internal CumulativeShopOffer(string key, CumulativeShopKind kind, byte? nativeItemId, string nativeName, int? fixedPrice, byte? nativeLureIndex = null, byte? nativeTier = null, string? sourceName = null) { Key = key; Kind = kind; NativeItemId = nativeItemId; NativeName = nativeName; FixedPrice = fixedPrice; NativeLureIndex = nativeLureIndex; NativeTier = nativeTier; SourceName = sourceName; } } internal readonly struct CumulativeShopPlacement { internal Vector3 Surface { get; } internal float Heading { get; } internal CumulativeShopPlacement(Vector3 surface, float heading) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) Surface = surface; Heading = heading; } } internal readonly struct CumulativeShopFootprint { internal float Width { get; } internal float Height { get; } internal float Depth { get; } internal CumulativeShopFootprint(Vector3 size) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) Width = size.x; Height = size.y; Depth = size.z; } public override string ToString() { return $"{Width:0.00}w x {Depth:0.00}d x {Height:0.00}h m"; } } internal sealed class CumulativeHarborShops : IDisposable { private sealed class Template { internal Purchasable Stand; internal Quaternion Tilt; internal CumulativeShopFootprint Footprint; internal string Source = ""; } private static readonly CumulativeShopOffer[] Island4CarryForward; internal static readonly IReadOnlyList<string> ExistingUpgradeCoverage; private readonly GameObject nursery; private readonly Dictionary<string, Item> nativeItems = new Dictionary<string, Item>(StringComparer.Ordinal); private readonly Dictionary<string, BaitInfo> nativeLures = new Dictionary<string, BaitInfo>(StringComparer.Ordinal); private readonly Dictionary<string, Template> templates = new Dictionary<string, Template>(StringComparer.Ordinal); private readonly Dictionary<string, Purchasable> stands = new Dictionary<string, Purchasable>(StringComparer.Ordinal); private readonly Dictionary<string, float> headings = new Dictionary<string, float>(StringComparer.Ordinal); private GameObject? spawnedRoot; private bool disposed; internal IReadOnlyList<CumulativeShopOffer> Offers => Array.AsReadOnly(Island4CarryForward); internal IReadOnlyList<string> RequiredMarkerKeys => Array.AsReadOnly(Island4CarryForward.Select((CumulativeShopOffer offer) => offer.Key).ToArray()); internal IReadOnlyDictionary<string, Purchasable> Stands => stands; internal IReadOnlyDictionary<string, string> Sources => new ReadOnlyDictionary<string, string>(templates.ToDictionary<KeyValuePair<string, Template>, string, string>((KeyValuePair<string, Template> pair) => pair.Key, (KeyValuePair<string, Template> pair) => pair.Value.Source, StringComparer.Ordinal)); internal IReadOnlyDictionary<string, CumulativeShopFootprint> Footprints => new ReadOnlyDictionary<string, CumulativeShopFootprint>(templates.ToDictionary<KeyValuePair<string, Template>, string, CumulativeShopFootprint>((KeyValuePair<string, Template> pair) => pair.Key, (KeyValuePair<string, Template> pair) => pair.Value.Footprint, StringComparer.Ordinal)); internal IReadOnlyList<string> Missing { get { Template value; return (from offer in Island4CarryForward where !templates.TryGetValue(offer.Key, out value) || !Object.op_Implicit((Object)(object)value.Stand) select offer.Key).ToArray(); } } internal bool IsPrepared { get { if (!disposed) { return Missing.Count == 0; } return false; } } internal CumulativeHarborShops() { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown ValidateCatalogShape(); nursery = new GameObject("Inactive Island4 cumulative shop templates"); nursery.SetActive(false); Object.DontDestroyOnLoad((Object)(object)nursery); } internal object DescribeTemplates() { return new { Footprints = Enumerable.ToDictionary(Footprints, (KeyValuePair<string, CumulativeShopFootprint> pair) => pair.Key, (KeyValuePair<string, CumulativeShopFootprint> pair) => new { pair.Value.Width, pair.Value.Depth, pair.Value.Height }, StringComparer.Ordinal), Sources = Sources, Offers = Offers.Select((CumulativeShopOffer offer) => new { Key = offer.Key, Kind = offer.Kind.ToString(), NativeItemId = offer.NativeItemId, NativeLureIndex = offer.NativeLureIndex, NativeTier = offer.NativeTier, NativeName = offer.NativeName, FixedPrice = offer.FixedPrice }).ToArray() }; } internal static IReadOnlyList<CumulativeHarborShops> FindSpawned(Transform parent) { if (!Object.op_Implicit((Object)(object)parent)) { throw new ArgumentNullException("parent"); } return (from binding in ((Component)parent).GetComponentsInChildren<CumulativeShopBinding>(true) where binding.Shops != null && !binding.Shops.disposed select binding.Shops).Distinct().ToArray(); } internal void Capture(Scene scene) { //IL_0101: Unknown result type (might be due to invalid IL or missing references) ThrowIfDisposed(); if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded) { throw new ArgumentException("Cumulative shop capture requires a loaded native scene.", "scene"); } if (!string.Equals(((Scene)(ref scene)).name, "Island4", StringComparison.Ordinal) || IsPrepared) { return; } if (templates.Count != 0) { throw new InvalidOperationException("The native Island4 cumulative shops were captured more than once."); } ResolveNativeAssets(); Purchasable[] source = ((Scene)(ref scene)).GetRootGameObjects().SelectMany((GameObject root) => root.GetComponentsInChildren<Purchasable>(true)).ToArray(); bool flag = false; try { CumulativeShopOffer[] island4CarryForward = Island4CarryForward; foreach (CumulativeShopOffer offer in island4CarryForward) { Purchasable[] array = source.Where((Purchasable candidate) => Matches(candidate, offer) && (offer.SourceName == null || ((Object)((Component)candidate).gameObject).name == offer.SourceName)).ToArray(); if (array.Length != 1) { throw new InvalidOperationException($"Native Island4 must contain exactly one {offer.Key} display; found {array.Length}."); } CaptureTemplate(scene, offer, array[0]); } ValidateNativeCatalog(); flag = true; } finally { if (!flag) { foreach (Template value in templates.Values) { if (Object.op_Implicit((Object)(object)value.Stand)) { Object.Destroy((Object)(object)((Component)value.Stand).gameObject); } } templates.Clear(); nativeItems.Clear(); nativeLures.Clear(); } } } internal void Spawn(Transform parent, IReadOnlyDictionary<string, CumulativeShopPlacement> placements) { //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Expected O, but got Unknown //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) ThrowIfDisposed(); if (!IsPrepared) { throw new InvalidOperationException("Missing verified Island4 cumulative shops: " + string.Join(", ", Missing)); } if (!Object.op_Implicit((Object)(object)parent) || placements == null) { throw new ArgumentException("Cumulative shops require a parent and placement catalog."); } if (Object.op_Implicit((Object)(object)spawnedRoot) || stands.Values.Any((Purchasable stand) => Object.op_Implicit((Object)(object)stand))) { throw new InvalidOperationException("Clear cumulative shops before rebuilding them."); } CumulativeShopOffer[] island4CarryForward = Island4CarryForward; foreach (CumulativeShopOffer cumulativeShopOffer in island4CarryForward) { if (!placements.TryGetValue(cumulativeShopOffer.Key, out var value) || !Finite(value.Surface) || !NativeContentAccess.Finite(value.Heading)) { throw new InvalidOperationException("Missing or invalid cumulative shop marker: " + cumulativeShopOffer.Key); } } ValidateNativeCatalog(); spawnedRoot = new GameObject("Island4 cumulative harbor shops"); spawnedRoot.SetActive(false); spawnedRoot.transform.SetParent(parent, false); spawnedRoot.AddComponent<CumulativeShopBinding>().Shops = this; bool flag = false; try { island4CarryForward = Island4CarryForward; foreach (CumulativeShopOffer cumulativeShopOffer2 in island4CarryForward) { Template template = templates[cumulativeShopOffer2.Key]; Purchasable component = NativeSceneMeshes.Clone(((Component)template.Stand).gameObject, spawnedRoot.transform).GetComponent<Purchasable>(); CumulativeShopPlacement cumulativeShopPlacement = placements[cumulativeShopOffer2.Key]; ((Object)component).name = "Native Island4 " + cumulativeShopOffer2.Key; ((Component)component).transform.localPosition = cumulativeShopPlacement.Surface; ((Component)component).transform.localRotation = Quaternion.Euler(0f, cumulativeShopPlacement.Heading, 0f) * template.Tilt; ValidateReferences(component); if (!Matches(component, cumulativeShopOffer2)) { throw new InvalidOperationException("A placed cumulative shop lost its native identity: " + cumulativeShopOffer2.Key); } SeatOnSurface(component, spawnedRoot.transform, cumulativeShopPlacement.Surface, cumulativeShopOffer2.Kind == CumulativeShopKind.Motor); stands.Add(cumulativeShopOffer2.Key, component); headings.Add(cumulativeShopOffer2.Key, cumulativeShopPlacement.Heading); ((Component)component).gameObject.SetActive(true); } spawnedRoot.SetActive(true); flag = true; } finally { if (!flag) { Clear(); } } } internal Vector3 ApproachDirection(string key) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) ThrowIfDisposed(); if (!Object.op_Implicit((Object)(object)spawnedRoot) || !headings.TryGetValue(key, out var value)) { throw new InvalidOperationException("The cumulative shop is not spawned: " + key); } Vector3 val = Vector3.ProjectOnPlane(spawnedRoot.transform.TransformDirection(Quaternion.Euler(0f, value, 0f) * Vector3.forward), Vector3.up); return ((Vector3)(ref val)).normalized; } internal void ValidateLiveStand(CumulativeShopOffer offer) { ValidateNativeCatalog(); if (!Island4CarryForward.Contains(offer) || !stands.TryGetValue(offer.Key, out Purchasable value) || !Object.op_Implicit((Object)(object)value) || !((Behaviour)value).isActiveAndEnabled || !Matches(value, offer)) { throw new InvalidOperationException("The cumulative native display is missing or inactive: " + offer.Key); } ValidateReferences(value); if (NativeContentAccess.Get<bool>(value, "_isFree")) { throw new InvalidOperationException("A cumulative native display became free: " + offer.Key); } int? fixedPrice = offer.FixedPrice; if (fixedPrice.HasValue) { int valueOrDefault = fixedPrice.GetValueOrDefault(); if (NativeContentAccess.Get<int>(value, "_customCost") != valueOrDefault) { throw new InvalidOperationException("A cumulative native display has the wrong price: " + offer.Key); } } if (offer.Kind == CumulativeShopKind.Sharpening && NativeContentAccess.Get<byte>(value, "_maxSharpnessUpgrade") != 12) { throw new InvalidOperationException("The sharpening stand no longer has Island4's tier-12 cap."); } if (offer.Kind == CumulativeShopKind.Pocket && NativeContentAccess.Get<byte>(value, "_maxSlot") != offer.NativeTier) { throw new InvalidOperationException("The pocket stand no longer has Island4's three-slot cap."); } if (offer.Kind == CumulativeShopKind.Motor && NativeContentAccess.Get<byte>(value, "_motorIndex") != offer.NativeTier) { throw new InvalidOperationException("A motor stand no longer has its native boat tier: " + offer.Key); } } internal void ValidateNativeCatalog() { ThrowIfDisposed(); if (nativeItems.Count != Island4CarryForward.Count((CumulativeShopOffer offer) => offer.NativeItemId.HasValue)) { throw new InvalidOperationException("The native cumulative item catalog is incomplete."); } if (nativeLures.Count != Island4CarryForward.Count((CumulativeShopOffer offer) => offer.NativeLureIndex.HasValue)) { throw new InvalidOperationException("The native cumulative lure catalog is incomplete."); } foreach (CumulativeShopOffer item in Island4CarryForward.Where((CumulativeShopOffer offer) => offer.NativeItemId.HasValue)) { Item val = nativeItems[item.Key]; if ((Object)(object)GameInfo.IDToItem(item.NativeItemId.Value) != (Object)(object)val || ((Object)val).name != item.NativeName || val.Cost != item.FixedPrice) { throw new InvalidOperationException("Native item identity changed after capture: " + item.Key); } RequireItemFamily(val, item); } foreach (CumulativeShopOffer item2 in Island4CarryForward.Where((CumulativeShopOffer offer) => offer.NativeLureIndex.HasValue)) { BaitInfo val2 = nativeLures[item2.Key]; byte? nativeLureIndex = item2.NativeLureIndex; if (nativeLureIndex.HasValue) { byte valueOrDefault = nativeLureIndex.GetValueOrDefault(); if (valueOrDefault < GameInfo.AllBaits.Count && !((Object)(object)GameInfo.AllBaits[valueOrDefault] != (Object)(object)val2) && GameInfo.GetIndexOfBait(val2) == valueOrDefault && !(((Object)val2).name != item2.NativeName) && val2.Cost == item2.FixedPrice) { RequireLure(val2, item2); continue; } } throw new InvalidOperationException("Native lure identity changed after capture: " + item2.Key); } } internal void Clear() { if (Object.op_Implicit((Object)(object)spawnedRoot)) { spawnedRoot.GetComponent<CumulativeShopBinding>().Shops = null; spawnedRoot.SetActive(false); Object.Destroy((Object)(object)spawnedRoot); } spawnedRoot = null; stands.Clear(); headings.Clear(); } private void ResolveNativeAssets() { HashSet<Item> hashSet = new HashSet<Item>(Resources.LoadAll<Item>("Items")); HashSet<BaitInfo> hashSet2 = new HashSet<BaitInfo>(Resources.LoadAll<BaitInfo>("Baits")); if (hashSet.Count == 0 || hashSet2.Count == 0) { throw new InvalidOperationException("Initialize native GameInfo before capturing cumulative shops."); } foreach (CumulativeShopOffer item in Island4CarryForward.Where((CumulativeShopOffer offer) => offer.NativeItemId.HasValue)) { Item val = GameInfo.IDToItem(item.NativeItemId.Value); if (!Object.op_Implicit((Object)(object)val) || !hashSet.Contains(val) || ((Object)val).name != item.NativeName || val.Cost != item.FixedPrice || val.IsQuestItem || val is DeadPlayer || (val is Creature && (item.Key != "legacy_beer" || ((object)val).GetType() != typeof(Creature)))) { throw new InvalidOperationException($"Native item {item.NativeItemId} differs from '{item.NativeName}' at ${item.FixedPrice}: " + $"name={((val != null) ? ((Object)val).name : null)}, type={((object)val)?.GetType().Name}, cost={((val != null) ? new int?(val.Cost) : ((int?)null))}, quest={((val != null) ? new bool?(val.IsQuestItem) : ((bool?)null))}, " + $"resource={Object.op_Implicit((Object)(object)val) && hashSet.Contains(val)}, creature={val is Creature}, player={val is DeadPlayer}."); } RequireItemFamily(val, item); nativeItems.Add(item.Key, val); } foreach (CumulativeShopOffer item2 in Island4CarryForward.Where((CumulativeShopOffer offer) => offer.NativeLureIndex.HasValue)) { byte value = item2.NativeLureIndex.Value; if (value >= GameInfo.AllBaits.Count) { throw new InvalidOperationException("Native lure index is unavailable: " + item2.Key); } BaitInfo val2 = GameInfo.AllBaits[value]; if (!Object.op_Implicit((Object)(object)val2) || !hashSet2.Contains(val2) || GameInfo.GetIndexOfBait(val2) != value || ((Object)val2).name != item2.NativeName || val2.Cost != item2.FixedPrice) { throw new InvalidOperationException($"Native lure {value} is not Island4 stock '{item2.NativeName}' at ${item2.FixedPrice}."); } RequireLure(val2, item2); nativeLures.Add(item2.Key, val2); } } private void CaptureTemplate(Scene scene, CumulativeShopOffer offer, Purchasable source) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) ValidateReferences(source); if (NativeContentAccess.Get<bool>(source, "_isFree") || !NativeContentAccess.Get<bool>(source, "_customCanBuy")) { throw new InvalidOperationException("Island4 source is not an enabled paid purchase: " + offer.Key); } Purchasable component = NativeSceneMeshes.Clone(((Component)source).gameObject, nursery.transform).GetComponent<Purchasable>(); bool flag = false; try { ((Object)component).name = "Native Island4 template " + offer.Key; ((Component)component).transform.localPosition = Vector3.zero; ((Component)component).transform.localScale = ((Component)source).transform.lossyScale; Quaternion val = Quaternion.Inverse(Quaternion.Euler(0f, ((Component)source).transform.eulerAngles.y, 0f)) * ((Component)source).transform.rotation; ((Component)component).transform.localRotation = val; NativeContentAccess.Set(component, "_isHovering", false); NativeContentAccess.Set(component, "_customCanBuy", true); ValidateReferences(component); if (!Matches(component, offer)) { throw new InvalidOperationException("Cloning changed the Island4 purchase identity: " + offer.Key); } Bounds val2 = VisibleBounds(component, nursery.transform); templates.Add(offer.Key, new Template { Stand = component, Tilt = val, Footprint = new CumulativeShopFootprint(((Bounds)(ref val2)).size), Source = ((Scene)(ref scene)).name + ":" + Hierarchy(((Component)source).transform) }); flag = true; } finally { if (!flag && Object.op_Implicit((Object)(object)component)) { Object.Destroy((Object)(object)((Component)component).gameObject); } } } private bool Matches(Purchasable stand, CumulativeShopOffer offer) { return offer.Kind switch { CumulativeShopKind.Sharpening => ((object)stand).GetType() == typeof(SharpnessPurchasable) && NativeContentAccess.Get<byte>(stand, "_maxSharpnessUpgrade") == 12, CumulativeShopKind.Lure => ((object)stand).GetType() == typeof(BaitPurchasable) && (Object)(object)NativeContentAccess.Optional<BaitInfo>(stand, "_bait") == (Object)(object)nativeLures[offer.Key], CumulativeShopKind.Pocket => ((object)stand).GetType() == typeof(SlotPurchasable) && NativeContentAccess.Get<byte>(stand, "_maxSlot") == offer.NativeTier, CumulativeShopKind.Motor => ((object)stand).GetType() == typeof(MotorPurchasable) && NativeContentAccess.Get<byte>(stand, "_motorIndex") == offer.NativeTier && NativeContentAccess.Get<int>(stand, "_customCost") == offer.FixedPrice, _ => ((object)stand).GetType() == typeof(ItemPurchasable) && (Object)(object)NativeContentAccess.Optional<Item>(stand, "_itemToPurchase") == (Object)(object)nativeItems[offer.Key], }; } private static void RequireItemFamily(Item item, CumulativeShopOffer offer) { bool flag; switch (offer.Kind) { case CumulativeShopKind.Weapon: flag = item is Weapon; break; case CumulativeShopKind.Melee: flag = item is Melee; break; case CumulativeShopKind.Explosive: flag = item is Explosive; break; case CumulativeShopKind.Rod: flag = ((offer.Key == "legacy_cast_rod") ? (item is FishingRodCast) : (offer.Key == "legacy_crab_rod" && item is FishingRodCrab)); break; case CumulativeShopKind.Utility: { bool flag2 = !(item is Weapon) && !(item is Melee) && !(item is FishingRod) && !(item is Explosive); if (flag2) { flag2 = offer.Key switch { "legacy_radio" => item is Radio, "legacy_disc" => item is Disc, "legacy_map" => item is Map, "legacy_beer" => ((object)item).GetType() == typeof(Creature), "legacy_badball" => true, _ => false, }; } flag = flag2; break; } default: flag = false; break; } if (!flag) { throw new InvalidOperationException($"Native item {offer.NativeName} does not match {offer.Kind}."); } } private static void RequireLure(BaitInfo lure, CumulativeShopOffer offer) { if (offer.Kind != CumulativeShopKind.Lure || !Object.op_Implicit((Object)(object)lure.Mesh) || !Object.op_Implicit((Object)(object)lure.MeshForNpc) || lure.ItemWeights == null || lure.ItemWeights.Count == 0 || !lure.ItemWeights.Any((ItemInfoWeight entry) => entry != null && entry.Weight > 0f && Object.op_Implicit((Object)(object)entry.Fishable) && entry.Fishable.ItemToSpawn is Creature)) { throw new InvalidOperationException("Native lure " + offer.NativeName + " has no usable native catch table."); } } private static void ValidateCatalogShape() { //IL_034c: Unknown result type (might be due to invalid IL or missing references) //IL_0351: Unknown result type (might be due to invalid IL or missing references) if (Island4CarryForward.Length != 25 || Island4CarryForward.Select((CumulativeShopOffer cumulativeShopOffer) => cumulativeShopOffer.Key).Distinct<string>(StringComparer.Ordinal).Count() != 25 || Island4CarryForward.Count((CumulativeShopOffer cumulativeShopOffer) => cumulativeShopOffer.Kind == CumulativeShopKind.Weapon) != 4 || Island4CarryForward.Count((CumulativeShopOffer cumulativeShopOffer) => cumulativeShopOffer.Kind == CumulativeShopKind.Melee) != 2 || Island4CarryForward.Count((CumulativeShopOffer cumulativeShopOffer) => cumulativeShopOffer.Kind == CumulativeShopKind.Explosive) != 1 || Island4CarryForward.Count((CumulativeShopOffer cumulativeShopOffer) => cumulativeShopOffer.Kind == CumulativeShopKind.Sharpening) != 1 || Island4CarryForward.Count((CumulativeShopOffer cumulativeShopOffer) => cumulativeShopOffer.Kind == CumulativeShopKind.Rod) != 2 || Island4CarryForward.Count((CumulativeShopOffer cumulativeShopOffer) => cumulativeShopOffer.Kind == CumulativeShopKind.Utility) != 5 || Island4CarryForward.Count((CumulativeShopOffer cumulativeShopOffer) => cumulativeShopOffer.Kind == CumulativeShopKind.Lure) != 7 || Island4CarryForward.Count((CumulativeShopOffer cumulativeShopOffer) => cumulativeShopOffer.Kind == CumulativeShopKind.Pocket) != 1 || Island4CarryForward.Count((CumulativeShopOffer cumulativeShopOffer) => cumulativeShopOffer.Kind == CumulativeShopKind.Motor) != 2 || Island4CarryForward.Any((CumulativeShopOffer cumulativeShopOffer) => cumulativeShopOffer.NativeName.Contains("Scientific") || cumulativeShopOffer.NativeName == "Coconut")) { throw new InvalidOperationException("The Island4 cumulative shop catalog is malformed."); } CumulativeShopOffer[] island4CarryForward = Island4CarryForward; foreach (CumulativeShopOffer offer in island4CarryForward) { ShopDefinition val = HarborDefinition.Value.Shops.Single((ShopDefinition shop) => shop.Key == offer.Key); string text; switch (offer.Kind) { case CumulativeShopKind.Weapon: case CumulativeShopKind.Melee: case CumulativeShopKind.Explosive: case CumulativeShopKind.Rod: case CumulativeShopKind.Utility: text = "NativeItem"; break; case CumulativeShopKind.Lure: text = "NativeLure"; break; case CumulativeShopKind.Sharpening: text = "Sharpening"; break; case CumulativeShopKind.Pocket: text = "Pocket"; break; case CumulativeShopKind.Motor: text = "Motor"; break; default: throw new InvalidOperationException("Unknown cumulative definition kind: " + offer.Kind); } string text2 = text; int num2 = ((int?)offer.NativeItemId) ?? ((int?)offer.NativeLureIndex) ?? (-1); string text3 = ((num2 >= 0) ? offer.NativeName : ""); if (((object)val.Kind/*cast due to .constrained prefix*/).ToString() != text2 || val.Reference != offer.Reference || val.NativeId != num2 || val.NativeName != text3) { throw new InvalidOperationException("The SDK definition differs from the verified native shop: " + offer.Key); } } } private static void ValidateReferences(Purchasable stand) { //IL_010f: Unknown result type (might be due to invalid IL or missing references) Collider val = NativeContentAccess.Get<Collider>(stand, "_interactCol"); GameObject[] array = NativeContentAccess.Get<GameObject[]>(stand, "_modelsToOutline"); if (!Object.op_Implicit((Object)(object)val) || !val.enabled || !((Component)val).transform.IsChildOf(((Component)stand).transform) || !((Component)val).CompareTag("Interactable") || !Object.op_Implicit((Object)(object)((Interactable)stand).TextTarget) || !((Interactable)stand).TextTarget.IsChildOf(((Component)stand).transform) || array.Length == 0 || array.Any((GameObject model) => !Object.op_Implicit((Object)(object)model) || !model.transform.IsChildOf(((Component)stand).transform)) || ((Component)stand).GetComponentsInChildren<Purchasable>(true).Length != 1 || ((Component)stand).GetComponentsInChildren<NetworkBehaviour>(true).Length != 0 || ((Component)stand).GetComponentsInChildren<NPC>(true).Length != 0) { throw new InvalidOperationException("Native cumulative shop has invalid or external references: " + ((Object)stand).name); } VisibleBounds(stand, ((Component)stand).transform); } private static void SeatOnSurface(Purchasable stand, Transform parent, Vector3 surface, bool centerFootprint) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) Bounds val = VisibleBounds(stand, parent); Transform transform = ((Component)stand).transform; transform.localPosition += new Vector3(centerFootprint ? (surface.x - ((Bounds)(ref val)).center.x) : 0f, surface.y - ((Bounds)(ref val)).min.y, centerFootprint ? (surface.z - ((Bounds)(ref val)).center.z) : 0f); } private static Bounds VisibleBounds(Purchasable stand, Transform relativeTo) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) MeshRenderer val3 = default(MeshRenderer); MeshFilter[] array = (from filter in ((Component)stand).GetComponentsInChildren<MeshFilter>(true) where Object.op_Implicit((Object)(object)filter.sharedMesh) && ActiveBelow(((Component)filter).transform, ((Component)stand).transform) && ((Component)filter).TryGetComponent<MeshRenderer>(ref val3) && ((Renderer)val3).enabled select filter).ToArray(); if (array.Length == 0) { throw new InvalidOperationException("Native cumulative shop has no visible owned geometry: " + ((Object)stand).name); } Bounds? val = null; MeshFilter[] array2 = array; foreach (MeshFilter val2 in array2) { Bounds value = NativeSceneMeshes.TransformBounds(val2.sharedMesh.bounds, relativeTo.worldToLocalMatrix * ((Component)val2).transform.localToWorldMatrix); if (!Finite(((Bounds)(ref value)).min) || !Finite(((Bounds)(ref value)).max)) { throw new InvalidOperationException("Native cumulative shop has invalid geometry bounds: " + ((Object)stand).name); } if (val.HasValue) { Bounds valueOrDefault = val.GetValueOrDefault(); ((Bounds)(ref valueOrDefault)).Encapsulate(((Bounds)(ref value)).min); ((Bounds)(ref valueOrDefault)).Encapsulate(((Bounds)(ref value)).max); val = valueOrDefault; } else { val = value; } } return val.Value; } private static bool ActiveBelow(Transform node, Transform root) { while ((Object)(object)node != (Object)(object)root) { if (!Object.op_Implicit((Object)(object)node) || !((Component)node).gameObject.activeSelf) { return false; } node = node.parent; } return true; } private static string Hierarchy(Transform node) { List<string> list = new List<string>(); while (Object.op_Implicit((Object)(object)node)) { list.Add(((Object)node).name); node = node.parent; } list.Reverse(); return string.Join("/", list); } private static bool Finite(Vector3 value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (NativeContentAccess.Finite(value.x) && NativeContentAccess.Finite(value.y)) { return NativeContentAccess.Finite(value.z); } return false; } private void ThrowIfDisposed() { if (disposed) { throw new ObjectDisposedException("CumulativeHarborShops"); } } public void Dispose() { if (disposed) { return; } Clear(); disposed = true; foreach (Template value in templates.Values) { if (Object.op_Implicit((Object)(object)value.Stand)) { Object.Destroy((Object)(object)((Component)value.Stand).gameObject); } } templates.Clear(); nativeItems.Clear(); Object.Destroy((Object)(object)nursery); } static CumulativeHarborShops() { CumulativeShopOffer[] obj = new CumulativeShopOffer[25] { new CumulativeShopOffer("legacy_brass_knuckles", CumulativeShopKind.Melee, (byte)57, "Brass Knuckles", 24), new CumulativeShopOffer("legacy_pistol", CumulativeShopKind.Weapon, (byte)66, "Pistol", 50), new CumulativeShopOffer("legacy_shotgun", CumulativeShopKind.Weapon, (byte)68, "Shotgun", 150), new CumulativeShopOffer("legacy_smg", CumulativeShopKind.Weapon, (byte)69, "Smg", 650), new CumulativeShopOffer("legacy_sniper_rifle", CumulativeShopKind.Weapon, (byte)70, "Sniper Rifle", 3800), new CumulativeShopOffer("legacy_knife", CumulativeShopKind.Melee, (byte)63, "Knife", 45), new CumulativeShopOffer("legacy_dynamite", CumulativeShopKind.Explosive, (byte)60, "Dynamite", 25), new CumulativeShopOffer("legacy_sharpening", CumulativeShopKind.Sharpening, null, "Sharpness (Purchasable)", null, null, null, "Sharpness (Purchasable)"), new CumulativeShopOffer("legacy_cast_rod", CumulativeShopKind.Rod, (byte)61, "Fishing Rod", 3), new CumulativeShopOffer("legacy_crab_rod", CumulativeShopKind.Rod, (byte)59, "Crab Fishing Rod", 3), new CumulativeShopOffer("legacy_badball", CumulativeShopKind.Utility, (byte)55, "Badball", 3), new CumulativeShopOffer("legacy_beer", CumulativeShopKind.Utility, (byte)56, "Beer", 12), new CumulativeShopOffer("legacy_radio", CumulativeShopKind.Utility, (byte)67, "Radio", 10), new CumulativeShopOffer("legacy_disc", CumulativeShopKind.Utility, (byte)62, "Disc", 10), new CumulativeShopOffer("legacy_map", CumulativeShopKind.Utility, (byte)65, "Map", 10), new CumulativeShopOffer("legacy_hotdog_bait", CumulativeShopKind.Lure, null, "HotDog", 1, (byte)4), new CumulativeShopOffer("legacy_beginner_lure", CumulativeShopKind.Lure, null, "Beginner Lure", 3, (byte)6), new CumulativeShopOffer("legacy_standard_lure", CumulativeShopKind.Lure, null, "Standard Lure", 15, (byte)8), new CumulativeShopOffer("legacy_professional_lure", CumulativeShopKind.Lure, null, "Professional Lure", 50, (byte)10), new CumulativeShopOffer("legacy_beginner_boss_lure", CumulativeShopKind.Lure, null, "Beginner Boss Lure", 40, (byte)9), new CumulativeShopOffer("legacy_standard_boss_lure", CumulativeShopKind.Lure, null, "Standard Boss Lure", 280, (byte)12), new CumulativeShopOffer("legacy_professional_boss_lure", CumulativeShopKind.Lure, null, "Professional Boss Lure", 1200, (byte)15), null, null, null }; byte? nativeTier = (byte)3; obj[22] = new CumulativeShopOffer("legacy_pocket_slot", CumulativeShopKind.Pocket, null, "Slot3 (Purchasable)", null, null, nativeTier, "Slot3 (Purchasable)"); int? fixedPrice = 230; nativeTier = (byte)1; obj[23] = new CumulativeShopOffer("legacy_big_motor", CumulativeShopKind.Motor, null, "BigMotor (Purchasable)", fixedPrice, null, nativeTier, "BigMotor (Purchasable)"); int? fixedPrice2 = 860; nativeTier = (byte)2; obj[24] = new CumulativeShopOffer("legacy_dual_motors", CumulativeShopKind.Motor, null, "DualMotors (Purchasable)", fixedPrice2, null, nativeTier, "DualMotors (Purchasable)"); Island4CarryForward = obj; ExistingUpgradeCoverage = Array.AsReadOnly(new string[7] { "harpoon_red_dot_sight", "harpoon_sniper_scope", "harpoon_compensator", "harpoon_suppressor", "harpoon_laser_sight", "harpoon_extended_mag", "harpoon_ammunition" }); } } internal sealed class CumulativeShopBinding : MonoBehaviour { internal CumulativeHarborShops? Shops { get; set; } } internal readonly struct HarpoonUpgradePlacement { internal Vector3 Surface { get; } internal float Heading { get; } internal HarpoonUpgradePlacement(Vector3 surface, float heading) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) Surface = surface; Heading = heading; } } internal sealed class HarpoonUpgradeShops : IDisposable { private const string AmmunitionPolicy = "gamblers_reach:ammunition"; private readonly HarpoonUpgradeCatalog catalog; private readonly IDisposable ammunitionPolicy; private readonly GameObject nursery; private readonly Dictionary<AttachmentInfo, AttachmentPurchasable> attachments = new Dictionary<AttachmentInfo, AttachmentPurchasable>(); private readonly Dictionary<string, Purchasable> stands = new Dictionary<string, Purchasable>(StringComparer.Ordinal); private readonly Dictionary<string, string> sources = new Dictionary<string, string>(StringComparer.Ordinal); private BulletPurchasable? ammunition; private bool disposed; internal IReadOnlyDictionary<string, Purchasable> Stands => stands; internal IReadOnlyDictionary<string, string> Sources => sources; internal IReadOnlyList<string> MissingFamilies => (from offer in catalog.Offers where Object.op_Implicit((Object)(object)offer.Information) && !attachments.ContainsKey(offer.Information) select offer.Key).Concat(Object.op_Implicit((Object)(object)ammunition) ? ((IEnumerable<string>)Array.Empty<string>()) : ((IEnumerable<string>)new string[1] { "harpoon_ammunition" })).ToArray(); internal bool IsPrepared { get { if (!disposed) { return MissingFamilies.Count == 0; } return false; } } internal HarpoonUpgradeShops(HarpoonUpgradeCatalog catalog) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown this.catalog = catalog ?? throw new ArgumentNullException("catalog"); nursery = new GameObject("Reach inactive native harpoon upgrade templates"); nursery.SetActive(false); Object.DontDestroyOnLoad((Object)(object)nursery); ammunitionPolicy = AmmunitionPurchasePolicies.Register("gamblers_reach:ammunition", (Func<Weapon, byte?>)MaximumAmmunitionTier); } private static byte? MaximumAmmunitionTier(Weapon weapon) { ReachWorld? current = ReachWorld.Current; if (current == null || !current.IsActive) { return null; } if (((Item)weapon).ID == 188) { return (byte)12; } if (((Item)weapon).ID >= 86) { return null; } ReachSession? current2 = ReachSession.Current; return (byte)((current2 != null && current2.CanVisitIslandFive) ? 12u : 9u); } internal void Capture(Scene scene) { //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Expected O, but got Unknown //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Expected O, but got Unknown ThrowIfDisposed(); if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded) { throw new ArgumentException("Harpoon upgrade capture requires a loaded native scene.", "scene"); } Purchasable[] array = ((Scene)(ref scene)).GetRootGameObjects().SelectMany((GameObject root) => root.GetComponentsInChildren<Purchasable>(true)).ToArray(); foreach (Purchasable val in array) { AttachmentPurchasable val2 = (AttachmentPurchasable)(object)((val is AttachmentPurchasable) ? val : null); if (val2 != null) { AttachmentInfo information = NativeContentAccess.Get<AttachmentInfo>(val2, "_info"); HarpoonUpgradeOffer harpoonUpgradeOffer = catalog.Offers.SingleOrDefault((HarpoonUpgradeOffer harpoonUpgradeOffer2) => (Object)(object)harpoonUpgradeOffer2.Information == (Object)(object)information); if (harpoonUpgradeOffer != null && !attachments.ContainsKey(information)) { HarpoonUpgradeCatalog.RequireNativeIdentity(information); AttachmentPurchasable value = (AttachmentPurchasable)CloneOwned(val); attachments.Add(information, value); sources.Add(harpoonUpgradeOffer.Key, ((Scene)(ref scene)).name + ":" + ((Object)val).name); } } else if (val is BulletPurchasable && !Object.op_Implicit((Object)(object)ammunition)) { ammunition = (BulletPurchasable)CloneOwned(val); NativeContentAccess.Set(ammunition, "_maxBulletUpgrade", (byte)9); sources.Add("harpoon_ammunition", ((Scene)(ref scene)).name + ":" + ((Object)val).name); } } } internal void Spawn(Transform parent, Weapon target, IReadOnlyDictionary<string, HarpoonUpgradePlacement> layout) { //IL_00be: 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) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) ThrowIfDisposed(); if (!IsPrepared) { throw new InvalidOperationException("Missing native harpoon purchasable families: " + string.Join(", ", MissingFamilies) + ". Capture native Island4 or Island5 before spawning."); } if (!Object.op_Implicit((Object)(object)parent) || !Object.op_Implicit((Object)(object)target) || ((Item)target).ID != 188) { throw new ArgumentException("Harpoon upgrade stands require an authored parent and the registered weapon-188 prefab."); } if (stands.Values.Any((Purchasable val3) => Object.op_Implicit((Object)(object)val3))) { throw new InvalidOperationException("Harpoon upgrade stands have already been spawned; call ClearSpawned before rebuilding."); } foreach (string standKey in catalog.StandKeys) { if (!layout.TryGetValue(standKey, out var value) || !Finite(value.Surface) || !NativeContentAccess.Finite(value.Heading)) { throw new InvalidOperationException("Missing or invalid harpoon shelf marker: " + standKey); } } stands.Clear(); bool flag = false; try { foreach (string key in catalog.StandKeys) { HarpoonUpgradeOffer harpoonUpgradeOffer = catalog.Offers.First((HarpoonUpgradeOffer harpoonUpgradeOffer2) => harpoonUpgradeOffer2.StandKey == key); Purchasable val = Object.Instantiate<Purchasable>((Purchasable)(Object.op_Implicit((Object)(object)harpoonUpgradeOffer.Information) ? ((object)attachments[harpoonUpgradeOffer.Information]) : ((object)ammunition)), parent, false); stands.Add(key, val); ((Object)val).name = "Reach native " + key; HarpoonUpgradePlacement harpoonUpgradePlacement = layout[key]; ((Component)val).transform.localPosition = harpoonUpgradePlacement.Surface; ((Component)val).transform.localRotation = Quaternion.Euler(0f, harpoonUpgradePlacement.Heading, 0f); ValidateOwnedReferences(val); SeatOnSurface(val, parent, harpoonUpgradePlacement.Surface.y); BulletPurchasable val2 = (BulletPurchasable)(object)((val is BulletPurchasable) ? val : null); if (val2 != null) { AmmunitionPurchasePolicies.Bind(val2, "gamblers_reach:ammunition"); } ((Component)val).gameObject.SetActive(true); } flag = true; } finally { if (!flag) { ClearSpawned(); } } } internal void ClearSpawned() { foreach (Purchasable value in stands.Values) { if (Object.op_Implicit((Object)(object)value)) { Object.Destroy((Object)(object)((Component)value).gameObject); } } stands.Clear(); } private Purchasable CloneOwned(Purchasable source) { ValidateOwnedReferences(source); Purchasable component = NativeSceneMeshes.Clone(((Component)source).gameObject, nursery.transform).GetComponent<Purchasable>(); NativeContentAccess.Set(component, "_isFree", false); NativeContentAccess.Set(component, "_customCanBuy", true); NativeContentAccess.Set(component, "_customCost", 0); NativeContentAccess.Set(component, "_isHovering", false); ValidateOwnedReferences(component); return component; } private static void ValidateOwnedReferences(Purchasable shop) { Collider val = NativeContentAccess.Get<Collider>(shop, "_interactCol"); GameObject[] array = NativeContentAccess.Get<GameObject[]>(shop, "_modelsToOutline"); if (!Object.op_Implicit((Object)(object)val) || !((Component)val).transform.IsChildOf(((Component)shop).transform) || !((Component)val).CompareTag("Interactable") || !Object.op_Implicit((Object)(object)((Interactable)shop).TextTarget) || !((Interactable)shop).TextTarget.IsChildOf(((Component)shop).transform) || array.Length == 0 || array.Any((GameObject model) => !Object.op_Implicit((Object)(object)model) || !model.transform.IsChildOf(((Component)shop).transform))) { throw new InvalidOperationException("Native upgrade stand has external or invalid interaction/card/outline references: " + ((Object)shop).name); } if (((Component)shop).GetComponentsInChildren<Purchasable>(true).Length != 1 || ((Component)shop).GetComponentsInChildren<NetworkBehaviour>(true).Length != 0) { throw new InvalidOperationException("Expected one non-networked native purchase display; actual purchases use Server's native RPCs."); } } private static void SeatOnSurface(Purchasable stand, Transform parent, float height) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) MeshFilter[] array = (from filter in ((Component)stand).GetComponentsInChildren<MeshFilter>(true) where ActiveBelow(((Component)filter).transform, ((Component)stand).transform) select filter).ToArray(); if (array.Length == 0) { throw new InvalidOperationException("Native upgrade shelf display contains no visible geometry: " + ((Object)stand).name); } float num = array.Min(delegate(MeshFilter filter) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) Bounds val = TransformBounds(filter.sharedMesh.bounds, parent.worldToLocalMatrix * ((Component)filter).transform.localToWorldMatrix); return ((Bounds)(ref val)).min.y; }); Transform transform = ((Component)stand).transform; transform.localPosition += Vector3.up * (height - num); } private static bool ActiveBelow(Transform child, Transform root) { Transform val = child; while ((Object)(object)val != (Object)(object)root) { if (!((Component)val).gameObject.activeSelf) { return false; } val = val.parent; } return true; } private static Bounds TransformBounds(Bounds bounds, Matrix4x4 matrix) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: 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_005d: Unknown result type (might be due to invalid IL or missing references) Bounds result = default(Bounds); ((Bounds)(ref result))..ctor(((Matrix4x4)(ref matrix)).MultiplyPoint3x4(((Bounds)(ref bounds)).center), Vector3.zero); for (int i = 0; i < 8; i++) { ((Bounds)(ref result)).Encapsulate(((Matrix4x4)(ref matrix)).MultiplyPoint3x4(((Bounds)(ref bounds)).center + Vector3.Scale(((Bounds)(ref bounds)).extents, new Vector3((float)(((i & 1) != 0) ? 1 : (-1)), (float)(((i & 2) != 0) ? 1 : (-1)), (float)(((i & 4) != 0) ? 1 : (-1)))))); } return result; } private static bool Finite(Vector3 value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (NativeContentAccess.Finite(value.x) && NativeContentAccess.Finite(value.y)) { return NativeContentAccess.Finite(value.z); } return false; } private void ThrowIfDisposed() { if (disposed) { throw new ObjectDisposedException("HarpoonUpgradeShops"); } } public void Dispose() { if (!disposed) { disposed = true; ClearSpawned(); ammunitionPolicy.Dispose(); Object.Destroy((Object)(object)nursery); } } } internal sealed class NativeCasinoPresentation : IDisposable { private readonly GameObject nursery; private GameObject? canvasTemplate; private GameObject? highlightTemplate; private Vector3 canvasPosition; private Quaternion canvasRotation; private Vector3 highlightPosition; private Quaternion highlightRotation; private Color winColor; private float pulse; internal bool Ready { get { if (Object.op_Implicit((Object)(object)canvasTemplate)) { return Object.op_Implicit((Object)(object)highlightTemplate); } return false; } } internal NativeCasinoPresentation() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown nursery = new GameObject("Inactive native casino presentation"); nursery.SetActive(false); Object.DontDestroyOnLoad((Object)(object)nursery); } internal void Capture(Scene scene) { //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Expected O, but got Unknown //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_02d7: Unknown result type (might be due to invalid IL or missing references) //IL_02e4: Unknown result type (might be due to invalid IL or missing references) //IL_0302: Unknown result type (might be due to invalid IL or missing references) //IL_0307: Unknown result type (might be due to invalid IL or missing references) if (Ready) { return; } LocalCasino val = ((Scene)(ref scene)).GetRootGameObjects().SelectMany((GameObject root) => root.GetComponentsInChildren<LocalCasino>(true)).FirstOrDefault(); if (Object.op_Implicit((Object)(object)val)) { Transform val2 = NativeContentAccess.Get<Transform>(val, "_rouletteTableHolder"); TextMeshProUGUI val3 = NativeContentAccess.Get<TextMeshProUGUI>(val, "_curWorthText"); CasinoBox componentInChildren = ((Component)val2).GetComponentInChildren<CasinoBox>(true); if (!Object.op_Implicit((Object)(object)((Graphic)val3).canvas) || !Object.op_Implicit((Object)(object)componentInChildren)) { throw new InvalidOperationException("The native roulette presentation is incomplete."); } BoxCollider val4 = NativeContentAccess.Get<BoxCollider>(componentInChildren, "_col"); MeshFilter component = ((Component)componentInChildren).GetComponent<MeshFilter>(); MeshRenderer component2 = ((Component)componentInChildren).GetComponent<MeshRenderer>(); if (!Object.op_Implicit((Object)(object)val4) || !Object.op_Implicit((Object)(object)component) || !Object.op_Implicit((Object)(object)component2) || !Object.op_Implicit((Object)(object)component.sharedMesh) || component.sharedMesh.vertexCount != 24) { throw new InvalidOperationException("The native roulette wager highlight is not the verified standalone cube."); } Quaternion val5 = Quaternion.Euler(0f, 90f, 0f); canvasPosition = val5 * val2.InverseTransformPoint(((Component)((Graphic)val3).canvas).transform.position); canvasRotation = val5 * Quaternion.Inverse(val2.rotation) * ((Component)((Graphic)val3).canvas).transform.rotation; highlightPosition = val5 * val2.InverseTransformPoint(((Component)componentInChildren).transform.position); highlightRotation = val5 * Quaternion.Inverse(val2.rotation) * ((Component)componentInChildren).transform.rotation; canvasTemplate = Object.Instantiate<GameObject>(((Component)((Graphic)val3).canvas).gameObject, nursery.transform, false); canvasTemplate.SetActive(false); LocalizeStringEvent[] componentsInChildren = canvasTemplate.GetComponentsInChildren<LocalizeStringEvent>(true); for (int num = 0; num < componentsInChildren.Length; num++) { ((Behaviour)componentsInChildren[num]).enabled = false; } highlightTemplate = new GameObject("Native tabletop wager highlight", new Type[3] { typeof(MeshFilter), typeof(MeshRenderer), typeof(BoxCollider) }); highlightTemplate.transform.SetParent(nursery.transform, false); highlightTemplate.transform.localScale = Vector3.Scale(((Component)componentInChildren).transform.lossyScale, new Vector3(1f / val2.lossyScale.x, 1f / val2.lossyScale.y, 1f / val2.lossyScale.z)); highlightTemplate.GetComponent<MeshFilter>().sharedMesh = component.sharedMesh; ((Renderer)highlightTemplate.GetComponent<MeshRenderer>()).sharedMaterials = ((Renderer)component2).sharedMaterials; BoxCollider component3 = highlightTemplate.GetComponent<BoxCollider>(); component3.size = val4.size; component3.center = val4.center; ((Collider)component3).isTrigger = true; ((Collider)component3).enabled = false; winColor = NativeContentAccess.Get<Color>(val, "_wonColor"); pulse = NativeContentAccess.Get<float>(val, "_scaleMultiplier"); Plugin.Log.LogInfo((object)("Captured native roulette typography, floating amount and tabletop highlight from " + ((Scene)(ref scene)).name + ".")); } } internal NativeBlackjackDisplay Spawn(Transform parent, Vector3 tableSurface, float heading) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) if (!Ready) { throw new InvalidOperationException("Native roulette presentation was not captured before island creation."); } GameObject val = new GameObject("Blackjack native presentation"); val.SetActive(false); val.transform.SetParent(parent, false); val.transform.localPosition = tableSurface - Vector3.up; val.transform.localRotation = Quaternion.Euler(0f, heading, 0f); GameObject obj = Object.Instantiate<GameObject>(canvasTemplate, val.transform, false); obj.transform.localPosition = canvasPosition; obj.transform.localRotation = canvasRotation; GameObject val2 = Object.Instantiate<GameObject>(highlightTemplate, val.transform, false); ((Object)val2).name = "Blackjack deposited fish volume"; val2.transform.localPosition = highlightPosition; val2.transform.localRotation = highlightRotation; FitHighlight(parent, val.transform, val2.transform); BoxCollider component = val2.GetComponent<BoxCollider>(); val2.AddComponent<BlackjackDeposit>().Configure(component); TextMeshProUGUI[] componentsInChildren = obj.GetComponentsInChildren<TextMeshProUGUI>(true); NativeBlackjackDisplay nativeBlackjackDisplay = obj.AddComponent<NativeBlackjackDisplay>(); nativeBlackjackDisplay.Configure(componentsInChildren.Single((TextMeshProUGUI text) => ((Object)text).name == "CurWorthText"), componentsInChildren.Single((TextMeshProUGUI text) => ((Object)text).name == "PlaceBetsText"), winColor, pulse); obj.SetActive(true); val.SetActive(true); return nativeBlackjackDisplay; } private static void FitHighlight(Transform island, Transform table, Transform highlight) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0135: 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_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0184: 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_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: 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_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_02a9: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02e3: Unknown result type (might be due to invalid IL or missing references) //IL_02fc: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_031f: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) Bounds val = default(Bounds); bool flag = false; MeshFilter[] componentsInChildren = ((Component)island).GetComponentsInChildren<MeshFilter>(true); foreach (MeshFilter val2 in componentsInChildren) { MeshRenderer component = ((Component)val2).GetComponent<MeshRenderer>(); if (!Object.op_Implicit((Object)(object)component) || !Object.op_Implicit((Object)(object)val2.sharedMesh)) { continue; } Material[] sharedMaterials = ((Renderer)component).sharedMaterials; for (int j = 0; j < sharedMaterials.Length; j++) { if (!Object.op_Implicit((Object)(object)sharedMaterials[j]) || !((Object)sharedMaterials[j]).name.StartsWith("felt_green", StringComparison.Ordinal)) { continue; } Mesh sharedMesh = val2.sharedMesh; Matrix4x4 val3 = table.worldToLocalMatrix * ((Component)val2).transform.localToWorldMatrix; Vector3[] vertices = sharedMesh.vertices; foreach (int item in sharedMesh.GetTriangles(j).Distinct()) { Vector3 val4 = ((Matrix4x4)(ref val3)).MultiplyPoint3x4(vertices[item]); if (!flag) { val = new Bounds(val4, Vector3.zero); flag = true; } else { ((Bounds)(ref val)).Encapsulate(val4); } } } } if (!flag || ((Bounds)(ref val)).size.x < 1f || ((Bounds)(ref val)).size.z < 0.5f || ((Bounds)(ref val)).size.y > 0.1f) { throw new InvalidOperationException("The authored blackjack felt has no valid surface bounds."); } Mesh sharedMesh2 = ((Component)highlight).GetComponent<MeshFilter>().sharedMesh; Bounds val5 = NativeSceneMeshes.TransformBounds(sharedMesh2.bounds, Matrix4x4.TRS(Vector3.zero, highlight.localRotation, highlight.localScale)); float num = Mathf.Min(new float[3] { 1f, (((Bounds)(ref val)).size.x - 0.03f) / ((Bounds)(ref val5)).size.x, (((Bounds)(ref val)).size.z - 0.03f) / ((Bounds)(ref val5)).size.z }); Vector3 localScale = highlight.localScale; highlight.localScale = new Vector3(localScale.x * num, localScale.y, localScale.z * num); val5 = NativeSceneMeshes.TransformBounds(sharedMesh2.bounds, Matrix4x4.TRS(Vector3.zero, highlight.localRotation, highlight.localScale)); Vector3 localPosition = highlight.localPosition; localPosition.x = Mathf.Clamp(localPosition.x, ((Bounds)(ref val)).min.x + 0.015f - ((Bounds)(ref val5)).min.x, ((Bounds)(ref val)).max.x - 0.015f - ((Bounds)(ref val5)).max.x); localPosition.z = Mathf.Clamp(localPosition.z, ((Bounds)(ref val)).min.z + 0.015f - ((Bounds)(ref val5)).min.z, ((Bounds)(ref val)).max.z - 0.015f - ((Bounds)(ref val5)).max.z); localPosition.y = ((Bounds)(ref val)).max.y + 0.005f - ((Bounds)(ref val5)).min.y; highlight.localPosition = localPosition; } public void Dispose() { Object.Destroy((Object)(object)nursery); } } internal sealed class NativeBlackjackDisplay : MonoBehaviour { private TextMeshProUGUI worth; private TextMeshProUGUI prompt; private Color winColor; private float pulse; private double displayed; private long target; private string lastRound = ""; private bool wasLocked; private float pulseRemaining; internal long TargetValue => target; internal void Configure(TextMeshProUGUI amount, TextMeshProUGUI label, Color winnerColor, float nativePulse) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) worth = amount; prompt = label; winColor = winnerColor; pulse = nativePulse; ((TMP_Text)worth).text = "$0"; ((Graphic)worth).color = Color.white; ((TMP_Text)prompt).text = "Place items here"; } internal void Show(PanelView state) { //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) bool num = wasLocked && !state.TableLocked && state.Round == lastRound; target = state.DepositValue; if (num) { bool flag = state.PayoutMultiplier > 1f; ((Graphic)worth).color = (flag ? winColor : Color.white); pulseRemaining = (flag ? 0.5f : 0f); } else if (state.TableLocked || state.Round != lastRound) { ((Graphic)worth).color = Color.white; } string text = state.ActingPlayerName.Replace("<", "").Replace(">", ""); if (text.Length > 24) { text = text.Substring(0, 24); } ((TMP_Text)prompt).text = ((state.DepositMessage.Length > 0) ? state.DepositMessage : (state.TableLocked ? (text + " is playing") : ((state.Round.Length == 0) ? "Place items here" : ((state.DepositItemCount == 0) ? (state.Outcome + "\nPlace another bet") : (state.Outcome + "\nCollect or DEAL again"))))); lastRound = state.Round; wasLocked = state.TableLocked; } private void Update() { //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)worth)) { double num = (double)target - displayed; displayed = ((Math.Abs(num) < 1.0) ? ((double)target) : (displayed + num * (1.0 - Math.Exp(-12f * Time.unscaledDeltaTime)))); ((TMP_Text)worth).text = "$" + Math.Round(displayed); if (pulseRemaining > 0f) { pulseRemaining = Mathf.Max(0f, pulseRemaining - Time.unscaledDeltaTime); ((TMP_Text)worth).transform.localScale = Vector3.one * (1f + pulse * Mathf.Sin(pulseRemaining * MathF.PI * 2f)); } else { ((TMP_Text)worth).transform.localScale = Vector3.one; } } } } internal readonly struct NativeGrassTuning { internal readonly float MeshSize; internal readonly float Density; internal readonly float PositionRandomness; internal readonly float RotationRandomness; internal readonly Vector2 ScaleRange; internal float Step => MeshSize / Density; internal NativeGrassTuning(float meshSize, float density, float positionRandomness, float rotationRandomness, Vector2 scaleRange) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) if (meshSize <= 0f || density <= 0f || positionRandomness < 0f || positionRandomness > 1f || rotationRandomness < 0f || rotationRandomness > 360f || scaleRange.x <= 0f || scaleRange.y < scaleRange.x) { throw new InvalidOperationException($"The native grass decoration tuning is invalid: size {meshSize}, density {density}, " + $"jitter {positionRandomness}, roll {rotationRandomness}, scale {scaleRange}."); } MeshSize = meshSize; Density = density; PositionRandomness = positionRandomness; RotationRandomness = rotationRandomness; ScaleRange = scaleRange; } public override string ToString() { return $"size {MeshSize}, density {Density} ({Step:0.###}m grid), jitter {PositionRandomness}, roll +/-{RotationRandomness} degrees, scale {ScaleRange.x}-{ScaleRange.y}"; } } internal sealed class GrassPlacementStats { internal int Candidates; internal int OffGrass; internal int Steep; internal int Low; internal int Edge; internal int Covered; internal int Blocked; internal int Thinned; internal int Accepted; public override string ToString() { return $"{Accepted} accepted of {Candidates} candidates (off grass {OffGrass}, steep {Steep}, low {Low}, path/edge {Edge}, " + $"covered {Covered}, blocked {Blocked}, thinned {Thinned})"; } } internal sealed class NativeGrassPlacement { internal const float MinNormalY = 0.78f; internal co