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.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using BepInEx; using BepInEx.Logging; using FishNet; using FishNet.Authenticating; using FishNet.Broadcast; using FishNet.Connection; using FishNet.Managing; using FishNet.Managing.Client; using FishNet.Managing.Logging; using FishNet.Managing.Object; using FishNet.Managing.Server; using FishNet.Object; using FishNet.Serializing; using FishNet.Transporting; using HarmonyLib; using HowToFish.ExpansionKit.Packs; using HowToFish.ExpansionKit.Runtime.Characters; using HowToFish.ExpansionKit.Runtime.Commerce; using HowToFish.ExpansionKit.Runtime.Content; using HowToFish.ExpansionKit.Runtime.Networking; using HowToFish.ExpansionKit.Runtime.Persistence; using HowToFish.ExpansionKit.Runtime.Rendering; using HowToFish.ExpansionKit.Runtime.Shops; using HowToFish.ExpansionKit.Runtime.World; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Steamworks; using Unity.Mathematics; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.SceneManagement; [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("HowToFish.ExpansionKit.Runtime")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Version-pinned native runtime for independent How to Fish content packs.")] [assembly: AssemblyFileVersion("0.7.2.0")] [assembly: AssemblyInformationalVersion("0.7.2")] [assembly: AssemblyProduct("HowToFish.ExpansionKit.Runtime")] [assembly: AssemblyTitle("HowToFish.ExpansionKit.Runtime")] [assembly: AssemblyVersion("0.7.2.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 HowToFish.ExpansionKit.Runtime { [BepInPlugin("howtofish.expansionkit.runtime", "How to Fish ExpansionKit", "0.7.2")] public sealed class ExpansionKitPlugin : BaseUnityPlugin { public const string Id = "howtofish.expansionkit.runtime"; public const string Name = "How to Fish ExpansionKit"; public const string Version = "0.7.2"; public const string GameBuild = "25127368"; public static readonly Guid SupportedGame = new Guid("91a1729d-8ab8-4d3b-afcf-c6edfa435491"); private Harmony? patches; internal static ManualLogSource Log { get; private set; } = null; private void Awake() { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; ExpansionPlatform expansionPlatform = ((Component)this).gameObject.AddComponent(); try { Guid moduleVersionId = typeof(Item).Assembly.ManifestModule.ModuleVersionId; if (moduleVersionId != SupportedGame) { Guid guid = moduleVersionId; throw new NotSupportedException("ExpansionKit requires How to Fish 1.0.12, build 25127368. The current game assembly is " + guid.ToString() + "."); } patches = new Harmony("howtofish.expansionkit.runtime"); patches.PatchAll(typeof(ExpansionKitPlugin).Assembly); NativeContentSelfCheck.RequireClean(NativeContentSelfCheck.InspectSharedHooks()); } catch (Exception error) { expansionPlatform.Fail(error); throw; } Log.LogInfo((object)"ExpansionKit 0.7.2: native build 25127368 verified."); } } public sealed class ExpansionPlatform : MonoBehaviour { private sealed class AssetFactory : IPackAssetFactory { private readonly PackAssets assets; internal AssetFactory(PackAssets assets) { this.assets = assets; } public GameObject Instantiate(string packKey, ArtRecipe art) { return assets.CreateVisual(packKey, art); } } private sealed class RegistryState { public string RuntimeVersion { get; set; } = ""; public PackRegistryManifest Registry { get; set; } = new PackRegistryManifest(); public Dictionary> ExtensionCode { get; set; } = new Dictionary>(StringComparer.Ordinal); } private readonly Dictionary extensions = new Dictionary(StringComparer.Ordinal); private readonly Dictionary sources = new Dictionary(StringComparer.Ordinal); private readonly Dictionary content = new Dictionary(StringComparer.Ordinal); private readonly List policies = new List(); private readonly NativeSceneMaterials sceneMaterials = new NativeSceneMaterials(); private PackAssets? assets; private NativeTemplateCache? templates; private PackWorldRuntime? world; private QuestProgressStore? progress; private NativeSaveBridge? saves; private bool registrationClosed; private bool disposed; private long publishedRevision = -1L; private long remoteRevision = -1L; private readonly HashSet policiesByShop = new HashSet(StringComparer.Ordinal); public static ExpansionPlatform Current { get; private set; } public PackRegistry Registry { get; private set; } public PackHandshake? Handshake { get; private set; } public PackChannels Channels { get; private set; } public BoatEquipment Boats { get; private set; } public PackWorldRuntime World => world ?? throw new InvalidOperationException("This pack set has no world modules."); public bool HasWorld => world != null; public NativeSaveBridge Saves => saves ?? throw new InvalidOperationException("Pack saves are still preparing."); public bool CoreReady { get; private set; } public bool Ready { get { if (CoreReady && Failure == null) { return extensions.Values.All((PackExtension extension) => extension.GameplayReady?.Invoke() ?? true); } return false; } } public Exception? Failure { get; private set; } public string Status { get; private set; } = "Preparing expansion packs"; public string Fingerprint { get; private set; } = ""; public string ConnectionProblem { get; private set; } = ""; public IReadOnlyCollection InstalledPacks => (IReadOnlyCollection)(object)content.Values.ToArray(); private void Awake() { if (Object.op_Implicit((Object)(object)Current)) { throw new InvalidOperationException("Only one expansion platform may be active."); } Current = this; } public void RegisterExtension(PackExtension extension) { if (extension == null) { throw new ArgumentNullException("extension"); } if (registrationClosed) { throw new InvalidOperationException("Register extensions during plugin Awake, before platform preparation."); } if (extensions.ContainsKey(extension.Key)) { throw new InvalidOperationException("Duplicate pack extension: " + extension.Key); } extensions.Add(extension.Key, extension); } public PackContentInstaller GetContent(string packKey) { if (!content.TryGetValue(packKey, out PackContentInstaller value)) { throw new KeyNotFoundException("No installed content pack: " + packKey); } return value; } private IEnumerator Start() { if (Failure == null) { yield return Guarded(Prepare()); } } private IEnumerator Prepare() { while (true) { if (Object.op_Implicit((Object)(object)InstanceFinder.NetworkManager) && NativeAccess.Field(typeof(GameInfo), "_allItems").GetValue(null) is IDictionary { Count: not 0 }) { break; } yield return null; } registrationClosed = true; foreach (string item in FindManifests(Paths.PluginPath)) { AddSource(PackSource.Load(item)); } foreach (PackExtension item2 in extensions.Values.OrderBy((PackExtension packExtension) => packExtension.Key, StringComparer.Ordinal)) { PackSource packSource = item2.PrepareSource(); if (packSource.Key != item2.Key || packSource.CodeFingerprints.Count == 0) { throw new InvalidOperationException("An extension must supply its matching pack and code fingerprints: " + item2.Key); } AddSource(packSource); } string[] source = extensions.Keys.SelectMany((string key) => sources[key].Definition.ExtensionHooks).Distinct(StringComparer.Ordinal).ToArray(); Registry = PackRegistry.Build(sources.Values.Select((PackSource packSource2) => packSource2.Definition), new PackRegistryOptions { SupportedExtensionHooks = source.ToList() }); foreach (PackDefinition pack in Registry.Packs) { if (pack.ExtensionHooks.Count != 0 && !extensions.ContainsKey(pack.Key)) { throw new InvalidOperationException("The required code extension was not registered for " + pack.Key + "."); } } assets = new PackAssets(sources); assets.Verify(Registry); Fingerprint = ComputeFingerprint(); foreach (PackDefinition pack2 in Registry.Packs) { Status = "Registering " + pack2.Title; PackContentOptions packContentOptions = new PackContentOptions { Assets = new AssetFactory(assets) }; extensions.TryGetValue(pack2.Key, out PackExtension value); value?.ConfigureContent(packContentOptions); PackContentInstaller packContentInstaller = new PackContentInstaller(Registry, pack2, packContentOptions); content.Add(pack2.Key, packContentInstaller); packContentInstaller.Install(); value?.ContentInstalled?.Invoke(packContentInstaller); } progress = new QuestProgressStore(CommitQuest); if (Registry.Packs.Any((PackDefinition pack) => pack.Npcs.Count + pack.Shops.Count + pack.Islands.Count + pack.Quests.Count != 0)) { Status = "Preparing native world services"; templates = new NativeTemplateCache(IsConnected, CloneTemplate); world = new PackWorldRuntime(Registry, templates, progress, ResolveItem, ResolveLure, assets.ResolveScene, IsConnected, AuthorizeActor, BindAmmunition); world.Faulted += Fail; world.SceneMounted += VerifyScene; foreach (PackExtension value2 in extensions.Values) { value2.BindWorld?.Invoke(this); } world.Configure(Object.FindAnyObjectByType() ?? throw new InvalidOperationException("The native island manager is unavailable.")); yield return templates.Prepare(); } saves = new NativeSaveBridge(CaptureRegistry, ValidateRegistry, ValidateNativeSave, Fail); saves.RegisterParticipant("expansionkit_world", () => (JToken)(object)JObject.FromObject((object)progress.CaptureState()), delegate(JToken? token) { MountedPackState mountedPackState = ((token == null) ? new MountedPackState() : (token.ToObject() ?? throw new InvalidDataException("The saved world module is empty."))); if (world != null) { world.RestoreState(mountedPackState); } else { progress.RestoreState(mountedPackState); } }); saves.SaveSelected += delegate(int count) { byte nativeUnlockedCount = checked((byte)Math.Min(5, count)); if (world != null) { world.SelectSave(nativeUnlockedCount); } else { progress.SelectSave(nativeUnlockedCount); } }; policies.Add(AmmunitionPurchasePolicies.Register("expansionkit_progression", MaximumProgressionTier)); if (Registry.Packs.Count > 0) { Handshake = new PackHandshake(InstanceFinder.NetworkManager, Fingerprint, delegate(string message) { ConnectionProblem = message; ExpansionKitPlugin.Log.LogWarning((object)message); }); Handshake.StateRequested += delegate(NetworkConnection connection) { Handshake.SendState(connection, JsonConvert.SerializeObject((object)progress.CaptureState())); }; Handshake.StateReceived += ReceiveState; Channels = new PackChannels(InstanceFinder.NetworkManager, Handshake, Registry.Packs.Select((PackDefinition pack) => pack.Key), () => Ready, Fail); } InstanceFinder.NetworkManager.ClientManager.OnClientConnectionState += ClientStateChanged; Boats = new BoatEquipment(); foreach (PackExtension value3 in extensions.Values) { if (value3.PrepareGameplay != null) { yield return value3.PrepareGameplay(); } } foreach (PackContentInstaller value4 in content.Values) { value4.Validate(); } saves.Ready = true; CoreReady = true; Status = ((Registry.Packs.Count == 0) ? "ExpansionKit ready" : (Registry.Packs.Count + " expansion pack(s) ready")); ExpansionKitPlugin.Log.LogInfo((object)(Status + "; registry " + Fingerprint + ".")); } private void AddSource(PackSource source) { if (sources.ContainsKey(source.Key)) { throw new InvalidDataException("Two installed sources declare pack " + source.Key + "."); } sources.Add(source.Key, source); } private static GameObject CloneTemplate(GameObject source, Transform parent) { //IL_008a: 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) try { return NativeSceneMeshes.Clone(source, parent); } catch (InvalidOperationException innerException) { string text = string.Join("; ", from filter in source.GetComponentsInChildren(true) where !Object.op_Implicit((Object)(object)filter.sharedMesh) || !Object.op_Implicit((Object)(object)((Component)filter).GetComponent()) select ((Object)filter).name + " parent=" + ((Object)((Component)filter).transform.parent).name + " active=" + ((Component)filter).gameObject.activeInHierarchy + " reward=" + Object.op_Implicit((Object)(object)((Component)filter).GetComponentInParent()) + " shop=" + Object.op_Implicit((Object)(object)((Component)filter).GetComponentInParent())); string[] obj = new string[6] { "Native template '", ((Object)source).name, "' in ", null, null, null }; Scene scene = source.scene; obj[3] = ((Scene)(ref scene)).name; obj[4] = " could not be cloned. Empty geometry: "; obj[5] = text; throw new InvalidOperationException(string.Concat(obj), innerException); } } private static IEnumerable FindManifests(string root) { Stack directories = new Stack(); directories.Push(root); int count = 0; while (directories.Count != 0) { string directory = directories.Pop(); foreach (string item in Directory.EnumerateFiles(directory).OrderBy((string value) => value, StringComparer.Ordinal)) { string fileName = Path.GetFileName(item); if (fileName.EndsWith(".pack.json", StringComparison.OrdinalIgnoreCase) || fileName.Equals("pack.json", StringComparison.OrdinalIgnoreCase)) { int num = count + 1; count = num; if (num > 64) { throw new InvalidDataException("More than 64 pack manifests are installed."); } yield return item; } } foreach (string item2 in Directory.EnumerateDirectories(directory).OrderByDescending((string value) => value, StringComparer.Ordinal)) { if ((File.GetAttributes(item2) & FileAttributes.ReparsePoint) == 0) { directories.Push(item2); } } } } private string ComputeFingerprint() { StringBuilder stringBuilder = new StringBuilder("25127368").Append('|').Append(Registry.Fingerprint).Append('|') .Append(PackSource.FileHash(typeof(ExpansionKitPlugin).Assembly.Location)) .Append('|') .Append(PackSource.FileHash(typeof(PackDefinition).Assembly.Location)); foreach (PackSource item in sources.Values.OrderBy((PackSource value) => value.Key, StringComparer.Ordinal)) { foreach (KeyValuePair item2 in item.CodeFingerprints.OrderBy, string>((KeyValuePair pair) => pair.Key, StringComparer.Ordinal)) { stringBuilder.Append('\n').Append(item.Key).Append(':') .Append(item2.Key) .Append('=') .Append(item2.Value); } } using SHA256 sHA = SHA256.Create(); return BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(stringBuilder.ToString()))).Replace("-", ""); } public Item ResolveItem(string reference) { ContentReference reference2 = ContentReference.Parse(reference); if (reference2.IsNative) { return NativeItemRegistrar.NativeDonor(reference2.NativeId); } return GetContent(reference2.PackKey).ResolveItem(reference2); } public BaitInfo ResolveLure(string reference) { ContentReference reference2 = ContentReference.Parse(reference); if (reference2.IsNative) { return NativeLureCatalog.NativeLure(reference2.NativeId); } return GetContent(reference2.PackKey).ResolveLure(reference2); } private JObject CaptureRegistry() { return JObject.FromObject((object)new RegistryState { RuntimeVersion = "0.7.2", Registry = Registry.CreateManifest(), ExtensionCode = sources.Values.ToDictionary>((PackSource source) => source.Key, (PackSource source) => source.CodeFingerprints.ToDictionary, string, string>((KeyValuePair pair) => pair.Key, (KeyValuePair pair) => pair.Value, StringComparer.Ordinal), StringComparer.Ordinal) }); } private void ValidateRegistry(JObject json) { //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Expected O, but got Unknown RegistryState saved = ((JToken)json).ToObject() ?? throw new InvalidDataException("Missing saved registry."); if ((saved.RuntimeVersion != "0.7.2" && saved.RuntimeVersion != "0.4.0" && saved.RuntimeVersion != "0.5.0" && saved.RuntimeVersion != "0.5.1" && saved.RuntimeVersion != "0.6.0" && saved.RuntimeVersion != "0.7.0" && saved.RuntimeVersion != "0.7.1") || saved.Registry == null || saved.ExtensionCode == null) { throw new InvalidDataException("The save requires a different ExpansionKit runtime or has incomplete registry metadata."); } Registry.RequireCompatible(saved.Registry, (Func)((SavedPack _, SavedPack _) => true)); HashSet acceptedRevisions = new HashSet(StringComparer.Ordinal); foreach (SavedPack pack in saved.Registry.Packs) { if (!saved.ExtensionCode.TryGetValue(pack.Key, out Dictionary value) || value == null) { throw new InvalidDataException("The save is missing extension-code identity for " + pack.Key + "."); } if (!sources.TryGetValue(pack.Key, out PackSource value2)) { continue; } if (extensions.TryGetValue(pack.Key, out PackExtension value3)) { Func, bool>? acceptSavedRevision = value3.AcceptSavedRevision; if (acceptSavedRevision != null && acceptSavedRevision(new SavedPack { Key = pack.Key, Version = pack.Version, Fingerprint = pack.Fingerprint }, new ReadOnlyDictionary(value))) { acceptedRevisions.Add(pack.Key); } } IReadOnlyDictionary current2 = value2.CodeFingerprints; if (!acceptedRevisions.Contains(pack.Key) && (value.Count != current2.Count || value.Any((KeyValuePair pair) => !current2.TryGetValue(pair.Key, out string value4) || pair.Value != value4))) { throw new InvalidDataException("The save requires different extension code for " + pack.Key + "."); } } Registry.RequireCompatible(saved.Registry, (Func)((SavedPack old, SavedPack _) => acceptedRevisions.Contains(old.Key))); if (saved.ExtensionCode.Keys.Any((string key) => !saved.Registry.Packs.Any((SavedPack pack) => pack.Key == key))) { throw new InvalidDataException("The saved extension identities contain an undeclared pack."); } } private void ValidateNativeSave(JObject root) { foreach (JObject item in from value in ((JContainer)root).Descendants().OfType() where (bool?)value["Exists"] == true && value["ItemID"] != null select value) { int num = (int)item["ItemID"]; if (num < 0 || num > 255 || !Object.op_Implicit((Object)(object)GameInfo.GetSpawnable((byte)num))) { throw new InvalidDataException("The crew save requires unavailable item " + num + ". Restore its content pack before loading."); } } foreach (JArray item2 in ((JToken)root).SelectTokens("$.Players[*].OwnedBaits").OfType()) { if (((JContainer)item2).Count > GameInfo.AllBaits.Count - 1) { throw new InvalidDataException("The crew save requires a larger lure registry; no inventory indices were discarded."); } int index; for (index = 0; index < ((JContainer)item2).Count; index++) { int num2 = (int)item2[index]; if (num2 < 0) { throw new InvalidDataException("The saved lure quantity is negative."); } if (num2 > 0 && index + 1 >= 17 && !Registry.Content.Any((ResolvedContent entry) => (int)entry.Domain == 1 && entry.Id == index + 1)) { throw new InvalidDataException("The crew owns an unavailable lure at slot " + (index + 1) + "."); } } } int island = ((int?)root["SpawnedIsland"]).GetValueOrDefault(); if (island >= 5 && !Registry.Content.Any((ResolvedContent entry) => (int)entry.Domain == 3 && entry.Id == island)) { throw new InvalidDataException("The crew save requires unavailable island " + island + "."); } } private void CommitQuest(QuestCheckpoint checkpoint, Action apply) { if (checkpoint.Kind == "select-save") { apply(); return; } try { if (!Object.op_Implicit((Object)(object)Server.Instance) || !((NetworkBehaviour)Server.Instance).IsServerInitialized) { throw new InvalidOperationException("Only the host can commit a quest mutation."); } Saves.Checkpoint(); apply(); Saves.Checkpoint(); } catch (Exception error) { saves?.Fail(error); throw; } } private static bool IsConnected() { if (Object.op_Implicit((Object)(object)InstanceFinder.NetworkManager)) { if (!InstanceFinder.NetworkManager.IsClientStarted) { return InstanceFinder.NetworkManager.IsServerStarted; } return true; } return false; } private bool AuthorizeActor(Player player) { if (Ready && Object.op_Implicit((Object)(object)player) && ((NetworkBehaviour)player).IsServerInitialized) { return Handshake?.IsConfirmed(((NetworkBehaviour)player).Owner) ?? (Registry.Packs.Count == 0); } return false; } private void BindAmmunition(BulletPurchasable stand, string key) { string packKey = key.Substring(0, key.IndexOf(':')); string shopKey = key.Substring(key.IndexOf(':') + 1); ShopRecipe val = Registry.Packs.Single((PackDefinition pack) => pack.Key == packKey).Shops.Single((ShopRecipe shop) => ((PackRecipe)shop).Key == shopKey); byte island = EngineIsland(val.Island); byte maximum = ContentReference.Parse(val.Content).NativeId; if (!policiesByShop.Contains(key)) { policies.Add(AmmunitionPurchasePolicies.Register(key, (Weapon _) => (!Object.op_Implicit((Object)(object)OnlineIslandManager.Instance) || OnlineIslandManager.CurIsland != island) ? ((byte?)null) : new byte?(maximum))); policiesByShop.Add(key); } AmmunitionPurchasePolicies.Bind(stand, key); } private byte EngineIsland(string reference) { ContentReference contentReference = ContentReference.Parse(reference); checked { if (contentReference.IsNative) { return (byte)(contentReference.NativeId - 1); } return (byte)(Registry.Content.Single((ResolvedContent entry) => entry.Reference == reference && unchecked((int)entry.Domain) == 3).Id ?? throw new InvalidDataException("An island has no engine ID.")); } } private byte? MaximumProgressionTier(Weapon weapon) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Invalid comparison between Unknown and I4 if (!Object.op_Implicit((Object)(object)OnlineIslandManager.Instance)) { return null; } if (OwnedContentIdentity.TryGet((Item?)(object)weapon, out OwnedContentIdentity identity) && (int)((PackRecipe)Registry.Packs.Single((PackDefinition pack) => pack.Key == identity.PackKey).Items.Single((ItemRecipe item) => ((PackRecipe)item).Key == identity.Key)).Implementation == 1) { return null; } byte island = OnlineIslandManager.CurIsland; checked { if (island < 5) { return (byte)(island * 3); } IslandRecipe val = Registry.Packs.SelectMany((PackDefinition pack) => pack.Islands).FirstOrDefault((Func)((IslandRecipe value) => value.Id == island)); if (val == null) { return null; } if (val.Route != null) { PackWorldRuntime? packWorldRuntime = world; if (packWorldRuntime != null && packWorldRuntime.CanVisit((byte)(ContentReference.Parse(val.Route.BeforeIsland).NativeId - 1))) { return (byte)((ContentReference.Parse(val.Route.BeforeIsland).NativeId - 1) * 3); } } return (byte)Math.Max(0, Math.Min(12, (val.UnlockThreshold - 2) * 3)); } } private void VerifyScene(MountedIsland scene) { if (!scene.Reference.StartsWith("native:", StringComparison.Ordinal) && Registry.Packs.SelectMany((PackDefinition pack) => pack.Islands).Any((IslandRecipe island) => island.Id == scene.NativeEngineId && (int)((PackRecipe)island).Implementation == 0)) { sceneMaterials.Apply(((Component)scene.Root).gameObject); } foreach (PackExtension value in extensions.Values) { value.VerifyScene?.Invoke(scene); } } private void ReceiveState(string json) { if (Object.op_Implicit((Object)(object)Server.Instance) && ((NetworkBehaviour)Server.Instance).IsServerInitialized) { return; } MountedPackState mountedPackState = JsonConvert.DeserializeObject(json) ?? throw new InvalidDataException("The host sent an empty world snapshot."); if (mountedPackState.Revision > remoteRevision) { if (world != null) { world.RestoreState(mountedPackState); } else { progress.RestoreState(mountedPackState); } remoteRevision = mountedPackState.Revision; } } private void ClientStateChanged(ClientConnectionStateArgs state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 if ((int)state.ConnectionState == 1) { remoteRevision = -1L; } if ((int)state.ConnectionState == 8) { ConnectionProblem = ""; } } private void Update() { if (disposed || !CoreReady || Failure != null) { return; } try { Handshake?.Tick(); Boats?.Tick(); world?.Tick(); if (Object.op_Implicit((Object)(object)Server.Instance) && ((NetworkBehaviour)Server.Instance).IsServerInitialized && Handshake != null && progress != null) { MountedPackState mountedPackState = progress.CaptureState(); if (mountedPackState.Revision != publishedRevision) { Handshake.PublishState(JsonConvert.SerializeObject((object)mountedPackState)); publishedRevision = mountedPackState.Revision; } } } catch (Exception error) { Fail(error); } } public void Fail(Exception error) { if (Failure == null) { Failure = error; Status = "ExpansionKit stopped: " + error.Message; ExpansionKitPlugin.Log.LogError((object)(Status + Environment.NewLine + error)); if (saves != null && saves.Failure == null) { saves.Fail(error); } } } private IEnumerator Guarded(IEnumerator routine) { Stack stack = new Stack(); stack.Push(routine); while (stack.Count != 0) { IEnumerator enumerator = stack.Peek(); bool flag = false; object obj = null; Exception ex = null; try { flag = enumerator.MoveNext(); if (flag) { obj = enumerator.Current; } } catch (Exception ex2) { ex = ex2; } if (ex != null) { Fail(ex); while (stack.Count != 0) { if (stack.Pop() is IDisposable disposable) { disposable.Dispose(); } } break; } if (!flag) { stack.Pop(); if (enumerator is IDisposable disposable2) { disposable2.Dispose(); } } else if (obj is IEnumerator item) { stack.Push(item); } else { yield return obj; } } } private void OnGUI() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (Failure != null) { GUI.Box(new Rect(16f, 16f, (float)Math.Max(300, Screen.width - 32), 100f), Status + "\nFurther SDK saves and new connections are blocked. Check the BepInEx log, correct the pack set, and restart."); } else if (ConnectionProblem.Length != 0) { GUI.Box(new Rect(16f, 16f, (float)Math.Max(300, Screen.width - 32), 80f), ConnectionProblem); } } private void OnDestroy() { disposed = true; if (Object.op_Implicit((Object)(object)InstanceFinder.NetworkManager)) { InstanceFinder.NetworkManager.ClientManager.OnClientConnectionState -= ClientStateChanged; } Channels?.Dispose(); Boats?.Dispose(); Handshake?.Dispose(); saves?.Dispose(); world?.Dispose(); templates?.Dispose(); foreach (IDisposable policy in policies) { policy.Dispose(); } foreach (PackContentInstaller item in content.Values.Reverse()) { item.Dispose(); } assets?.Dispose(); sceneMaterials.Dispose(); if (Current == this) { Current = null; } } } [HarmonyPatch] internal static class WaitForPackPreparation { [HarmonyPatch] internal static class WaitForPackLobbyPreparation { private static IEnumerable TargetMethods() { return new string[4] { "CreateOfflineLobby", "JoinOfflineLobby", "CreateOnlineLobby", "JoinOnlineLobby" }.Select((string name) => AccessTools.Method(typeof(ConnectionManager), name, (Type[])null, (Type[])null)); } private static bool Prefix() { ExpansionPlatform current = ExpansionPlatform.Current; if (!Object.op_Implicit((Object)(object)current) || current.Ready) { return true; } ExpansionKitPlugin.Log.LogWarning((object)("Lobby not opened: " + current.Status)); return false; } } private static IEnumerable TargetMethods() { return from method in new Type[2] { typeof(ServerManager), typeof(ClientManager) }.SelectMany((Type type) => type.GetMethods(BindingFlags.Instance | BindingFlags.Public)) where method.Name == "StartConnection" && method.ReturnType == typeof(bool) select method; } private static bool Prefix(MethodBase __originalMethod, ref bool __result) { ExpansionPlatform current = ExpansionPlatform.Current; if (!Object.op_Implicit((Object)(object)current) || (current.Ready && (__originalMethod.DeclaringType != typeof(ServerManager) || SaveManager.CurServerSave != null))) { return true; } ExpansionKitPlugin.Log.LogWarning((object)("Connection not started: " + current.Status)); __result = false; return false; } } public static class NativeAccess { private static readonly Dictionary<(Type Type, string Name), FieldInfo> Fields = new Dictionary<(Type, string), FieldInfo>(); public static FieldInfo Field(Type type, string name) { if (type == null) { throw new ArgumentNullException("type"); } (Type, string) key = (type, name); lock (Fields) { if (!Fields.TryGetValue(key, out FieldInfo value)) { value = AccessTools.Field(type, name) ?? throw new MissingFieldException(type.FullName, name); Fields.Add(key, value); } return value; } } public static bool HasField(Type type, string name) { return AccessTools.Field(type, name) != null; } public static T Get(object target, string name) { return Read(Field((target ?? throw new ArgumentNullException("target")).GetType(), name), target); } public static T Static(Type type, string name) { return Read(Field(type, name), null); } public static T? Optional(object target, string name) where T : class { return Field((target ?? throw new ArgumentNullException("target")).GetType(), name).GetValue(target) as T; } public static void Set(object target, string name, object? value) { Field((target ?? throw new ArgumentNullException("target")).GetType(), name).SetValue(target, value); } public static void SetStatic(Type type, string name, object? value) { Field(type, name).SetValue(null, value); } private static T Read(FieldInfo field, object? target) { object value = field.GetValue(target); if (value is T) { return (T)value; } throw new InvalidOperationException("Native " + field.DeclaringType?.Name + "." + field.Name + " is not an initialized " + typeof(T).Name + "."); } public static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } public static bool Positive(float value) { if (Finite(value)) { return value > 0f; } return false; } public static void PrepareNetworkClone(GameObject clone, GameObject source) { if (!Object.op_Implicit((Object)(object)clone)) { throw new ArgumentNullException("clone"); } if (!Object.op_Implicit((Object)(object)source)) { throw new ArgumentNullException("source"); } NetworkBehaviour[] componentsInChildren = source.GetComponentsInChildren(true); NetworkBehaviour[] componentsInChildren2 = clone.GetComponentsInChildren(true); if (componentsInChildren2.Length != componentsInChildren.Length) { throw new InvalidOperationException("Cloning changed the native network behaviour layout."); } for (int i = 0; i < componentsInChildren2.Length; i++) { if (((object)componentsInChildren2[i]).GetType() != ((object)componentsInChildren[i]).GetType()) { throw new InvalidOperationException("Cloning changed the native network behaviour order."); } string[] array = new string[4] { "_syncTypes", "_serverRpcDelegates", "_observersRpcDelegates", "_targetRpcDelegates" }; foreach (string text in array) { IDictionary dictionary = Get(componentsInChildren2[i], text); if (dictionary == Get(componentsInChildren[i], text)) { throw new InvalidOperationException("The clone shares its " + text + " with the native prefab."); } dictionary.Clear(); } Type type = ((object)componentsInChildren2[i]).GetType(); while (type != null && type != typeof(MonoBehaviour)) { FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(bool) && fieldInfo.Name.StartsWith("NetworkInitialize___", StringComparison.Ordinal) && fieldInfo.Name.EndsWith("_Excuted", StringComparison.Ordinal)) { fieldInfo.SetValue(componentsInChildren2[i], false); } } type = type.BaseType; } } NetworkObject target = clone.GetComponent() ?? throw new InvalidOperationException("The clone has no root NetworkObject."); Set(target, "_initializedValusSet", false); Set(target, "_disabledNetworkBehavioursInitialized", false); } } public static class NativeSceneMeshes { public static GameObject Clone(GameObject source, Transform parent) { if (!Object.op_Implicit((Object)(object)source) || !Object.op_Implicit((Object)(object)parent) || ((Component)parent).gameObject.activeInHierarchy) { throw new ArgumentException("Capture a native object into an inactive template parent."); } MeshFilter[] componentsInChildren = source.GetComponentsInChildren(true); HashSet deferred = DeferredSlots(source); Mesh[] array = componentsInChildren.Select((MeshFilter filter) => OwnedMesh(filter, deferred)).ToArray(); GameObject val = Object.Instantiate(source, parent, false); val.SetActive(false); bool flag = false; try { MeshFilter[] componentsInChildren2 = val.GetComponentsInChildren(true); if (componentsInChildren2.Length != componentsInChildren.Length) { throw new InvalidOperationException("Native clone mesh layout changed."); } for (int num = 0; num < componentsInChildren2.Length; num++) { componentsInChildren2[num].sharedMesh = array[num]; MeshRenderer component = ((Component)componentsInChildren2[num]).GetComponent(); if (Object.op_Implicit((Object)(object)component) && ((Renderer)component).isPartOfStaticBatch) { (AccessTools.Method(typeof(Renderer), "SetStaticBatchInfo", (Type[])null, (Type[])null) ?? throw new MissingMethodException("Renderer", "SetStaticBatchInfo")).Invoke(component, new object[2] { 0, 0 }); (AccessTools.Property(typeof(Renderer), "staticBatchRootTransform") ?? throw new MissingMemberException("Renderer", "staticBatchRootTransform")).SetValue(component, null); if (((Renderer)component).isPartOfStaticBatch) { throw new InvalidOperationException("The native clone retained another scene's static batch."); } } } Transform[] componentsInChildren3 = val.GetComponentsInChildren(true); for (int num2 = 0; num2 < componentsInChildren3.Length; num2++) { ((Component)componentsInChildren3[num2]).gameObject.isStatic = false; } flag = true; return val; } finally { if (!flag) { Object.Destroy((Object)(object)val); } } } private static Mesh? OwnedMesh(MeshFilter filter, HashSet deferred) { Mesh mesh = filter.sharedMesh; MeshRenderer renderer = ((Component)filter).GetComponent(); if (!Object.op_Implicit((Object)(object)mesh)) { if (Object.op_Implicit((Object)(object)renderer) && ((Renderer)renderer).isPartOfStaticBatch) { throw new InvalidOperationException("Missing native geometry on a statically batched renderer: " + Path(((Component)filter).transform)); } if (Object.op_Implicit((Object)(object)renderer) && ((Renderer)renderer).enabled && ((Component)filter).gameObject.activeInHierarchy && !deferred.Contains(filter)) { throw new InvalidOperationException("Missing native geometry on a visible renderer that is not a native deferred slot: " + Path(((Component)filter).transform)); } return null; } if (((Object)mesh).name.StartsWith("Combined Mesh", StringComparison.OrdinalIgnoreCase) && (!Object.op_Implicit((Object)(object)renderer) || !((Renderer)renderer).isPartOfStaticBatch)) { throw new InvalidOperationException("An unowned combined-scene mesh was supplied for " + Path(((Component)filter).transform)); } if (!Object.op_Implicit((Object)(object)renderer) || !((Renderer)renderer).isPartOfStaticBatch) { return mesh; } MeshCollider component = ((Component)filter).GetComponent(); if (Object.op_Implicit((Object)(object)component) && Object.op_Implicit((Object)(object)component.sharedMesh) && (Object)(object)component.sharedMesh != (Object)(object)mesh && Fits(component.sharedMesh, renderer)) { return component.sharedMesh; } Mesh[] array = (from candidate in Resources.FindObjectsOfTypeAll() where Object.op_Implicit((Object)(object)candidate) && (Object)(object)candidate != (Object)(object)mesh && !((Object)candidate).name.StartsWith("Combined Mesh", StringComparison.OrdinalIgnoreCase) && candidate.subMeshCount == ((Renderer)renderer).sharedMaterials.Length && Fits(candidate, renderer) select candidate).Distinct().ToArray(); if (array.Length != 1) { throw new InvalidOperationException("Cannot safely recover native geometry '" + Path(((Component)filter).transform) + "': " + $"{array.Length} originals match; refusing to clone combined scene geometry."); } return array[0]; } public static HashSet DeferredSlots(GameObject source) { HashSet hashSet = new HashSet(); QuestInteractable[] componentsInChildren = source.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { MeshFilter val = NativeAccess.Optional(componentsInChildren[i], "_meshToUpdate"); if (Object.op_Implicit((Object)(object)val)) { hashSet.Add(val); } } return hashSet; } private static string Path(Transform node) { List list = new List(); Transform val = node; while (Object.op_Implicit((Object)(object)val)) { list.Add(((Object)val).name); val = val.parent; } list.Reverse(); return string.Join("/", list); } private static bool Fits(Mesh mesh, MeshRenderer renderer) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: 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_005d: 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) Bounds val = TransformBounds(mesh.bounds, ((Component)renderer).transform.localToWorldMatrix); Bounds bounds = ((Renderer)renderer).bounds; Vector3 size = ((Bounds)(ref bounds)).size; float num = Mathf.Max(0.0005f, ((Vector3)(ref size)).magnitude * 0.002f); Vector3 center = ((Bounds)(ref val)).center; bounds = ((Renderer)renderer).bounds; if (Vector3.Distance(center, ((Bounds)(ref bounds)).center) <= num) { Vector3 size2 = ((Bounds)(ref val)).size; bounds = ((Renderer)renderer).bounds; return Vector3.Distance(size2, ((Bounds)(ref bounds)).size) <= num; } return false; } public 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; } } public sealed class PackAssets : IDisposable { private readonly IReadOnlyDictionary sources; private readonly Dictionary bundles = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary verified = new Dictionary(StringComparer.OrdinalIgnoreCase); private bool disposed; public PackAssets(IReadOnlyDictionary sources) { this.sources = sources ?? throw new ArgumentNullException("sources"); } public void Verify(PackRegistry registry) { if (disposed) { throw new ObjectDisposedException("PackAssets"); } foreach (PackDefinition pack in registry.Packs) { foreach (ArtRecipe item in from art in pack.Items.Select((ItemRecipe item) => item.Art).Concat(pack.Lures.Select((LureRecipe lure) => lure.Art)).Concat(pack.Npcs.Select((NpcRecipe npc) => npc.Art)) where art != null select (art)) { VerifyFile(pack.Key, item.Bundle, item.Sha256); } foreach (IslandRecipe island in pack.Islands) { VerifyFile(pack.Key, island.SceneBundle, island.SceneSha256); } } } public GameObject CreateVisual(string packKey, ArtRecipe art) { if (art == null) { throw new ArgumentNullException("art"); } GameObject val = Load(packKey, art.Bundle, art.Sha256).LoadAsset(art.Prefab); if (!Object.op_Implicit((Object)(object)val) || val.GetComponentsInChildren(true).Length != 0 || val.GetComponentsInChildren(true).Length != 0 || val.GetComponentsInChildren(true).Length == 0) { throw new InvalidDataException("An authored item visual must contain renderable art, without runtime scripts or collision components: " + art.Prefab); } GameObject obj = Object.Instantiate(val); obj.SetActive(false); return obj; } public string ResolveScene(string packKey, IslandRecipe island) { string[] array = (from path in Load(packKey, island.SceneBundle, island.SceneSha256).GetAllScenePaths() where string.Equals(Path.GetFileNameWithoutExtension(path), island.SceneName, StringComparison.Ordinal) select path).ToArray(); if (array.Length != 1) { throw new InvalidDataException("The pack bundle must contain exactly one scene matching " + island.SceneName + "."); } return array[0]; } private AssetBundle Load(string packKey, string bundleName, string expectedHash) { string text = VerifyFile(packKey, bundleName, expectedHash); if (bundles.TryGetValue(text, out AssetBundle value)) { return value; } AssetBundle val = AssetBundle.LoadFromFile(text); if (!Object.op_Implicit((Object)(object)val)) { throw new InvalidDataException("Unity could not load the verified pack asset bundle: " + bundleName); } bundles.Add(text, val); return val; } private string VerifyFile(string packKey, string name, string expectedHash) { if (disposed) { throw new ObjectDisposedException("PackAssets"); } if (!sources.TryGetValue(packKey, out PackSource value)) { throw new InvalidDataException("No asset source exists for pack " + packKey + "."); } string text = value.ResolveFile(name); if (verified.TryGetValue(text, out string value2)) { if (!string.Equals(value2, expectedHash, StringComparison.OrdinalIgnoreCase)) { throw new InvalidDataException("Two recipes disagree about the same asset hash: " + name); } return text; } FileInfo fileInfo = new FileInfo(text); if (!fileInfo.Exists || fileInfo.Length == 0L || fileInfo.Length > 536870912) { throw new InvalidDataException("The required pack bundle is missing, empty or larger than 512 MiB: " + text); } string text2 = PackSource.FileHash(text); if (!string.Equals(text2, expectedHash, StringComparison.OrdinalIgnoreCase)) { throw new InvalidDataException("The installed pack bundle differs from its manifest: " + name); } verified.Add(text, text2); return text; } public void Dispose() { if (disposed) { return; } disposed = true; foreach (AssetBundle value in bundles.Values) { if (Object.op_Implicit((Object)(object)value)) { value.Unload(false); } } bundles.Clear(); verified.Clear(); } } public sealed class PackExtension { public string Key { get; } public Func PrepareSource { get; } public Action ConfigureContent { get; } public Action? ContentInstalled { get; set; } public Action? BindWorld { get; set; } public Func? PrepareGameplay { get; set; } public Func? GameplayReady { get; set; } public Action? VerifyScene { get; set; } public Func, bool>? AcceptSavedRevision { get; set; } public PackExtension(string key, Func prepareSource, Action configureContent) { if (!ContentReference.IsKey(key)) { throw new ArgumentException("A pack extension requires a valid key.", "key"); } Key = key; PrepareSource = prepareSource ?? throw new ArgumentNullException("prepareSource"); ConfigureContent = configureContent ?? throw new ArgumentNullException("configureContent"); } } public sealed class PackSource { private static readonly JsonSerializerSettings Json = new JsonSerializerSettings { MissingMemberHandling = (MissingMemberHandling)1, MaxDepth = 64 }; private readonly PackDefinition definition; public string Key => definition.Key; public string Root { get; } public IReadOnlyDictionary CodeFingerprints { get; } internal PackDefinition Definition => JsonConvert.DeserializeObject(JsonConvert.SerializeObject((object)definition), Json) ?? throw new InvalidDataException("The pack definition could not be copied."); public PackSource(PackDefinition definition, string root, params Assembly[] extensionAssemblies) { if (definition == null) { throw new ArgumentNullException("definition"); } definition.Validate(); if (string.IsNullOrWhiteSpace(root) || !Path.IsPathRooted(root) || !Directory.Exists(root)) { throw new ArgumentException("The pack needs an existing, absolute asset directory.", "root"); } this.definition = JsonConvert.DeserializeObject(JsonConvert.SerializeObject((object)definition), Json) ?? throw new InvalidDataException("The pack definition could not be copied."); Root = Path.GetFullPath(root); if (extensionAssemblies == null || extensionAssemblies.Any((Assembly assembly) => assembly == null)) { throw new ArgumentNullException("extensionAssemblies"); } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (Assembly item in extensionAssemblies.OrderBy((Assembly assembly) => assembly.GetName().Name, StringComparer.Ordinal)) { string text = item.GetName().Name ?? throw new InvalidDataException("An extension assembly has no identity."); if (dictionary.ContainsKey(text)) { throw new InvalidDataException("Duplicate extension assembly: " + text); } dictionary.Add(text, FileHash(item.Location)); } CodeFingerprints = new ReadOnlyDictionary(dictionary); } public static PackSource Load(string path) { string fullPath = Path.GetFullPath(path); FileInfo fileInfo = new FileInfo(fullPath); if (!fileInfo.Exists || fileInfo.Length > 2097152) { throw new InvalidDataException("A pack manifest must exist and be no larger than 2 MiB: " + fullPath); } return new PackSource(JsonConvert.DeserializeObject(File.ReadAllText(fullPath), Json) ?? throw new InvalidDataException("The pack manifest is empty: " + fullPath), fileInfo.DirectoryName); } internal string ResolveFile(string name) { if (string.IsNullOrWhiteSpace(name) || Path.IsPathRooted(name)) { throw new InvalidDataException("A pack asset must use a relative local filename."); } string fullPath = Path.GetFullPath(Path.Combine(Root, name)); string value = Root.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; if (!fullPath.StartsWith(value, StringComparison.OrdinalIgnoreCase)) { throw new InvalidDataException("A pack asset leaves its owning directory."); } return fullPath; } public static string FileHash(string path) { using FileStream inputStream = File.OpenRead(path); using SHA256 sHA = SHA256.Create(); return BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", ""); } } } namespace HowToFish.ExpansionKit.Runtime.World { public sealed class BoatMount { public sealed class BoatSupport : IDisposable { private readonly GameObject root; private readonly Collider collider; private readonly Boat boat; internal BoatSupport(GameObject root, Collider collider, Boat boat) { this.root = root; this.collider = collider; this.boat = boat; } public void SetActive(bool active) { if (Object.op_Implicit((Object)(object)root)) { root.SetActive(active); } } public void Dispose() { if (BoatManager.ColToBoat.TryGetValue(collider, out var value) && (Object)(object)value == (Object)(object)boat) { BoatManager.ColToBoat.Remove(collider); } if (Object.op_Implicit((Object)(object)root)) { Object.Destroy((Object)(object)root); } } } public Boat Boat { get; } public Transform Root { get; } public Transform VisualRoot => Boat.VisualBoat; public Rigidbody Physics { get { if (!IsServer) { return Boat.VisualPhysicsRig; } return Boat.HiddenPhysicsRig; } } public bool IsServer => ((NetworkBehaviour)Boat).IsServerInitialized; private Transform CollisionRoot => NativeAccess.Get(Boat, "_dynamicObjectColsHolder"); private Transform PhysicsFrame { get { if (!IsServer) { return CollisionRoot; } return ((Component)Physics).transform; } } private Vector3 Velocity { get { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!IsServer) { return Boat.Velocity; } return Physics.linearVelocity; } } public float Speed { get { //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) Vector3 velocity = Velocity; return ((Vector3)(ref velocity)).magnitude; } } public float HorizontalSpeed { get { //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_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) Vector3 val = Vector3.ProjectOnPlane(Velocity, Vector3.up); return ((Vector3)(ref val)).magnitude; } } public Quaternion PhysicsRotation => PhysicsFrame.rotation * Quaternion.Inverse(((Component)Boat.VisualPhysicsRig).transform.rotation) * Root.rotation; internal BoatMount(Boat boat, Transform root) { Boat = boat; Root = root; } public Vector3 PhysicsPoint(Vector3 local) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) return PhysicsFrame.TransformPoint(((Component)Boat.VisualPhysicsRig).transform.InverseTransformPoint(Root.TransformPoint(local))); } public Vector3 CollisionPoint(Vector3 local) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) return CollisionRoot.TransformPoint(((Component)Boat.VisualPhysicsRig).transform.InverseTransformPoint(Root.TransformPoint(local))); } public bool TryDeck(Vector3 local, out Vector3 surface) { //IL_0022: 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_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_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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_0066: 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_0111: 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_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013d: 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_00c7: 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) //IL_00ef: 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) Physics.SyncTransforms(); Transform collisionRoot = CollisionRoot; Vector3 val = ((Component)Boat.VisualPhysicsRig).transform.InverseTransformPoint(Root.TransformPoint(local + Vector3.up * 3f)); Vector3 up = collisionRoot.up; Ray val2 = default(Ray); ((Ray)(ref val2))..ctor(collisionRoot.TransformPoint(val), -up); float num = float.PositiveInfinity; Vector3 val3 = default(Vector3); Collider[] componentsInChildren = ((Component)collisionRoot).GetComponentsInChildren(); RaycastHit val5 = default(RaycastHit); foreach (Collider val4 in componentsInChildren) { if (val4.enabled && !val4.isTrigger && BoatManager.ColToBoat.TryGetValue(val4, out var value) && !((Object)(object)value != (Object)(object)Boat) && val4.Raycast(val2, ref val5, 6f) && !(Vector3.Dot(((RaycastHit)(ref val5)).normal, up) < 0.6f) && !(((RaycastHit)(ref val5)).distance >= num)) { num = ((RaycastHit)(ref val5)).distance; val3 = ((RaycastHit)(ref val5)).point; } } if (num < float.PositiveInfinity) { Vector3 val6 = collisionRoot.InverseTransformPoint(val3); surface = Root.InverseTransformPoint(((Component)Boat.VisualPhysicsRig).transform.TransformPoint(val6)); return true; } surface = default(Vector3); return false; } public bool Near(Player player, Vector3 local, float range) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)player) || player.Dying.IsDead || !Object.op_Implicit((Object)(object)Boat) || !((NetworkBehaviour)Boat).IsSpawned || !NativeAccess.Positive(range)) { return false; } return Vector3.Distance(player.Transform.position, PhysicsPoint(local)) <= range; } public BoatSupport SupportBox(string name, Vector3 localCenter, Vector3 size) { //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) //IL_0027: 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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: 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_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: 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_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00be: 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_00d9: 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) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: 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_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Expected O, but got Unknown if (!NativeAccess.Positive(size.x) || !NativeAccess.Positive(size.y) || !NativeAccess.Positive(size.z) || !NativeAccess.Finite(localCenter.x) || !NativeAccess.Finite(localCenter.y) || !NativeAccess.Finite(localCenter.z)) { throw new ArgumentException("Boat support dimensions must be positive and its center finite."); } int num = LayerMask.NameToLayer("BoatDynamic"); if (num < 0) { throw new InvalidOperationException("The native dynamic-boat collision layer is unavailable."); } GameObject val = new GameObject(name); val.SetActive(false); val.transform.SetParent(CollisionRoot, false); val.transform.localPosition = ((Component)Boat.VisualPhysicsRig).transform.InverseTransformPoint(Root.TransformPoint(localCenter)); val.transform.localRotation = Quaternion.Inverse(((Component)Boat.VisualPhysicsRig).transform.rotation) * Root.rotation; val.layer = num; val.tag = "Boat"; BoxCollider val2 = val.AddComponent(); val2.size = size; BoatManager.ColToBoat.Add((Collider)(object)val2, Boat); return new BoatSupport(val, (Collider)(object)val2, Boat); } } public sealed class BoatEquipment : IDisposable { private sealed class Registration : IDisposable { internal BoatEquipment Owner; internal string Key = ""; internal Func Attach; internal Func BlockTravel; internal Func? BlockDriving; internal Action Notify; internal GameObject? Root; internal IDisposable? Lifetime; public void Dispose() { Owner.Remove(this); } } private readonly Dictionary registrations = new Dictionary(StringComparer.Ordinal); private Boat? currentBoat; private bool disposed; internal static BoatEquipment? Current { get; private set; } public BoatEquipment() { if (Current != null) { throw new InvalidOperationException("Only one shared boat-equipment registry is supported."); } Current = this; } public IDisposable Register(string packKey, string key, Func attach, Func blockTravel, Action notify) { return Register(packKey, key, attach, blockTravel, notify, null); } public IDisposable Register(string packKey, string key, Func attach, Func blockTravel, Action notify, Func? blockDriving) { if (disposed) { throw new ObjectDisposedException("BoatEquipment"); } if (!ContentReference.IsKey(packKey) || !ContentReference.IsKey(key)) { throw new ArgumentException("Boat equipment needs a namespaced pack identity."); } string text = packKey + ":" + key; if (registrations.ContainsKey(text)) { throw new InvalidOperationException("Duplicate boat equipment: " + text); } Registration registration = new Registration { Owner = this, Key = text, Attach = (attach ?? throw new ArgumentNullException("attach")), BlockTravel = (blockTravel ?? throw new ArgumentNullException("blockTravel")), BlockDriving = blockDriving, Notify = (notify ?? throw new ArgumentNullException("notify")) }; registrations.Add(text, registration); return registration; } public void Tick() { //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Expected O, but got Unknown if (disposed) { return; } Boat boat = BoatManager.Boat; if (!Object.op_Implicit((Object)(object)boat) || !((NetworkBehaviour)boat).IsSpawned || !((NetworkBehaviour)boat).IsClientInitialized || !Object.op_Implicit((Object)(object)boat.VisualBoat) || !Object.op_Implicit((Object)(object)boat.VisualPhysicsRig) || !Object.op_Implicit((Object)(object)boat.HiddenPhysicsRig) || !BoatManager.ColToBoat.Values.Any((Boat owner) => (Object)(object)owner == (Object)(object)boat)) { boat = null; } if (currentBoat != boat) { foreach (Registration value in registrations.Values) { Detach(value); } currentBoat = boat; } if (!Object.op_Implicit((Object)(object)currentBoat)) { return; } foreach (Registration item in registrations.Values.Where((Registration value) => !Object.op_Implicit((Object)(object)value.Root))) { GameObject val = new GameObject("ExpansionKit boat equipment " + item.Key); val.SetActive(false); val.transform.SetParent(currentBoat.VisualBoat, false); item.Root = val; try { item.Lifetime = item.Attach(new BoatMount(currentBoat, val.transform)) ?? throw new InvalidOperationException("The boat equipment did not return an owned lifetime: " + item.Key); val.SetActive(true); } catch { Detach(item); throw; } } if (DrivingBlockReason(currentBoat) != null) { Boat.ToggleWantToDrive(false); if (((NetworkBehaviour)currentBoat).IsServerInitialized && Object.op_Implicit((Object)(object)currentBoat.Driver)) { AllowDriving(currentBoat); currentBoat.TrySetDriver((Player)null); } } } public string? DrivingBlockReason(Boat boat) { FindDrivingBlock(boat, out string reason); return reason; } private Registration? FindDrivingBlock(Boat boat, out string? reason) { reason = null; if (!Object.op_Implicit((Object)(object)boat) || (Object)(object)boat != (Object)(object)currentBoat || !((NetworkBehaviour)boat).IsSpawned) { return null; } foreach (Registration value in registrations.Values) { if (value.Lifetime != null) { string text = value.BlockDriving?.Invoke(); if (!string.IsNullOrEmpty(text)) { reason = text; return value; } } } return null; } internal bool AllowDriving(Boat boat) { string reason; Registration registration = FindDrivingBlock(boat, out reason); if (registration == null) { return true; } registration.Notify(reason); ExpansionKitPlugin.Log.LogWarning((object)("Boat driving paused: " + reason)); return false; } internal bool AllowTravel(byte island) { if (island == byte.MaxValue || !Object.op_Implicit((Object)(object)currentBoat) || !((NetworkBehaviour)currentBoat).IsSpawned) { return true; } foreach (Registration value in registrations.Values) { string text = value.BlockTravel(); if (!string.IsNullOrEmpty(text)) { value.Notify(text); ExpansionKitPlugin.Log.LogWarning((object)("Boat travel paused: " + text)); return false; } } return true; } private static void Detach(Registration registration) { registration.Lifetime?.Dispose(); registration.Lifetime = null; if (Object.op_Implicit((Object)(object)registration.Root)) { Object.Destroy((Object)(object)registration.Root); } registration.Root = null; } private void Remove(Registration registration) { if (registrations.TryGetValue(registration.Key, out Registration value) && value == registration) { Detach(registration); registrations.Remove(registration.Key); } } public void Dispose() { if (!disposed) { Registration[] array = registrations.Values.ToArray(); for (int i = 0; i < array.Length; i++) { array[i].Dispose(); } disposed = true; currentBoat = null; if (Current == this) { Current = null; } } } } [HarmonyPatch(typeof(OnlineIslandManager), "SpawnIsland")] internal static class BoatEquipmentTravelGuard { private static bool Prefix(byte islandIndex) { return BoatEquipment.Current?.AllowTravel(islandIndex) ?? true; } } [HarmonyPatch(typeof(BoatInteractable), "Hover")] internal static class BoatEquipmentHelmLabel { private static void Postfix(BoatInteractable __instance) { //IL_0040: 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) Boat boat = BoatManager.Boat; string text = BoatEquipment.Current?.DrivingBlockReason(boat); if (text != null) { PlayerUI.UpdateLookAtText(text); NativeAccess.Set(__instance, "_hoverText", text); } PlayerUI.SetLookAtColor((text != null || !boat.BoatUnlocked) ? GameInfo.RedColor : Color.white); } } [HarmonyPatch(typeof(BoatInteractable), "Interact")] internal static class BoatEquipmentHelmGuard { private static bool Prefix() { BoatEquipment? current = BoatEquipment.Current; int num; if (current == null) { num = 1; } else { num = (current.AllowDriving(BoatManager.Boat) ? 1 : 0); if (num == 0) { Boat.ToggleWantToDrive(false); } } return (byte)num != 0; } } [HarmonyPatch(typeof(Boat), "TrySetDriver")] internal static class BoatEquipmentDriverGuard { private static bool Prefix(Boat __instance, Player newDriver) { if (Object.op_Implicit((Object)(object)newDriver)) { return BoatEquipment.Current?.AllowDriving(__instance) ?? true; } return true; } } [HarmonyPatch(typeof(Boat), "ServerSetInput")] internal static class BoatEquipmentThrottleGuard { private static void Prefix(Boat __instance, ref half x, ref half y) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (BoatEquipment.Current?.DrivingBlockReason(__instance) != null) { x = (y = (half)0f); } } } public readonly struct IslandRouteAccess { public bool Completed { get; } public bool? PreservedContinuationAccess { get; } public bool? IntroductionPreservedAccess { get; } public IslandRouteAccess(bool completed, bool? preservedContinuationAccess = null, bool? introductionPreservedAccess = null) { Completed = completed; PreservedContinuationAccess = preservedContinuationAccess; IntroductionPreservedAccess = introductionPreservedAccess; } } public static class IslandRoutePolicy { public static bool PreservesNativeAccess(int nativeOrdinal, MountedPackState selectedSave) { if (nativeOrdinal < 1 || nativeOrdinal > 5) { throw new ArgumentOutOfRangeException("nativeOrdinal"); } if (selectedSave == null) { throw new ArgumentNullException("selectedSave"); } if (selectedSave.SaveSelectionInitialized) { return selectedSave.NativeUnlockedAtSelection >= nativeOrdinal; } return false; } public static bool CanVisitNative(int nativeOrdinal, int nativeUnlockedCount, MountedPackState selectedSave, IEnumerable insertedIslandsCompleted) { if (insertedIslandsCompleted == null) { throw new ArgumentNullException("insertedIslandsCompleted"); } return CanVisitNative(nativeOrdinal, nativeUnlockedCount, selectedSave, insertedIslandsCompleted.Select((bool completed) => new IslandRouteAccess(completed))); } public static bool RouteSatisfied(IslandRouteAccess interlude, bool globalPreserved) { if (!interlude.Completed) { if (interlude.IntroductionPreservedAccess ?? globalPreserved) { return interlude.PreservedContinuationAccess != false; } return false; } return true; } public static bool CanVisitNative(int nativeOrdinal, int nativeUnlockedCount, MountedPackState selectedSave, IEnumerable insertedIslands) { bool preserved = PreservesNativeAccess(nativeOrdinal, selectedSave); if (insertedIslands == null) { throw new ArgumentNullException("insertedIslands"); } IslandRouteAccess[] source = insertedIslands.ToArray(); if (preserved || source.Any((IslandRouteAccess interlude) => interlude.IntroductionPreservedAccess == true) || nativeUnlockedCount >= nativeOrdinal) { return source.All((IslandRouteAccess interlude) => RouteSatisfied(interlude, preserved)); } return false; } public static bool CanVisitCustom(int minimumNativeUnlockCount, int nativeUnlockedCount, int? beforeNativeOrdinal, MountedPackState selectedSave, IEnumerable precedingIslandsCompleted) { if (precedingIslandsCompleted == null) { throw new ArgumentNullException("precedingIslandsCompleted"); } return CanVisitCustom(minimumNativeUnlockCount, nativeUnlockedCount, beforeNativeOrdinal, selectedSave, precedingIslandsCompleted.Select((bool completed) => new IslandRouteAccess(completed))); } public static bool CanVisitCustom(int minimumNativeUnlockCount, int nativeUnlockedCount, int? beforeNativeOrdinal, MountedPackState selectedSave, IEnumerable precedingIslands) { if (minimumNativeUnlockCount < 0 || minimumNativeUnlockCount > 5) { throw new ArgumentOutOfRangeException("minimumNativeUnlockCount"); } if (selectedSave == null || precedingIslands == null) { throw new ArgumentNullException("selectedSave"); } if (nativeUnlockedCount < minimumNativeUnlockCount) { return false; } if (!beforeNativeOrdinal.HasValue) { return true; } int value = beforeNativeOrdinal.Value; bool preserved = PreservesNativeAccess(value, selectedSave); if (value < 2) { throw new ArgumentException("An interlude must follow an existing mandatory native stage."); } if (nativeUnlockedCount >= value - 1) { return precedingIslands.All((IslandRouteAccess interlude) => RouteSatisfied(interlude, preserved)); } return false; } } public readonly struct MountPose { public Vector3 Position { get; } public Quaternion Rotation { get; } public MountPose(Vector3 position, Quaternion rotation) { //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) //IL_0008: 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) Position = position; Rotation = rotation; } } public static class MountPlacement { public static MountPose Resolve(Transform root, PlacementRecipe placement) { //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: 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_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: 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_0100: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0130: 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) if (!Object.op_Implicit((Object)(object)root) || placement == null) { throw new ArgumentException("A live root and placement are required."); } Transform val = root; if (!string.IsNullOrWhiteSpace(placement.Marker)) { string marker = placement.Marker; Transform[] array = (from candidate in ((Component)root).GetComponentsInChildren(true) where (marker.IndexOf('/') < 0) ? (((Object)candidate).name == marker) : (RelativePath(root, candidate) == marker) select candidate).ToArray(); if (array.Length != 1) { throw new InvalidOperationException($"Mount marker '{marker}' under '{((Object)root).name}' must resolve exactly once; found {array.Length}."); } val = array[0]; } else if (placement.Pose == null) { throw new InvalidOperationException("A placement needs an explicit local pose or an unambiguous marker."); } PoseRecipe val2 = (PoseRecipe)(((object)placement.Pose) ?? ((object)new PoseRecipe())); Vector3 val3 = Vector(val2.Position); if (!WorldFields.Finite(val3) || !WorldFields.Finite(val2.Yaw)) { throw new InvalidOperationException("Mount coordinates and yaw must be finite."); } return new MountPose(root.InverseTransformPoint(val.TransformPoint(val3)), Quaternion.Inverse(root.rotation) * val.rotation * Quaternion.Euler(0f, val2.Yaw, 0f)); } public static void Apply(Transform target, Transform root, PlacementRecipe placement) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) MountPose mountPose = Resolve(root, placement); target.SetParent(root, false); target.localPosition = mountPose.Position; target.localRotation = mountPose.Rotation; } internal static Vector3 Vector(VectorRecipe value) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (value != null) { return new Vector3(value.X, value.Y, value.Z); } throw new ArgumentException("Missing position."); } private static string RelativePath(Transform root, Transform target) { if ((Object)(object)target == (Object)(object)root) { return ""; } if (!((Object)(object)target.parent == (Object)(object)root)) { return RelativePath(root, target.parent) + "/" + ((Object)target).name; } return ((Object)target).name; } } internal static class MountReferences { internal static string Key(string pack, string key) { return pack + ":" + key; } internal static string Qualify(string pack, string reference) { if (reference.IndexOf(':') >= 0) { return reference; } return Key(pack, reference); } internal static byte NativeId(string reference) { if (!reference.StartsWith("native:", StringComparison.Ordinal) || !byte.TryParse(reference.Substring(7), NumberStyles.None, CultureInfo.InvariantCulture, out var result)) { throw new ArgumentException("Expected native:, got " + reference); } return result; } } public static class NativePlayerGeometry { public static float StandingClearance() { //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) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0072: 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) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0098: 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_00a8: 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_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) Player playerPrefab = GameInfo.PlayerPrefab; CapsuleCollider val = NativeAccess.Get(playerPrefab.Movement, "_col"); SphereCollider val2 = NativeAccess.Get(playerPrefab.Movement, "_footCol"); if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val2) || val.direction != 1) { throw new InvalidOperationException("The native player's vertical collision geometry is unavailable."); } float y = ((Component)playerPrefab).transform.InverseTransformPoint(((Component)val).transform.TransformPoint(val.center - Vector3.up * val.height * 0.5f)).y; float y2 = ((Component)playerPrefab).transform.InverseTransformPoint(((Component)val2).transform.TransformPoint(val2.center - Vector3.up * val2.radius)).y; float num = 0f - Math.Min(y, y2) + 0.15f; if (!NativeAccess.Finite(num) || num < 0.2f || num > 3f) { throw new InvalidOperationException("The native player clearance is outside supported bounds."); } return num; } } public sealed class NativeTemplateCache : IDisposable { private readonly GameObject nursery; private readonly Dictionary> shops = new Dictionary>(); private readonly Dictionary npcs = new Dictionary(StringComparer.Ordinal); private readonly List quests = new List(); private readonly Func connected; private readonly Func cloneOwned; private bool disposed; private bool preparing; private int capturingBuildIndex; public bool Ready { get; private set; } public Exception? Failure { get; private set; } public Boat BoatPrefab { get; private set; } public QuestInteractable RewardHand { get; private set; } public IReadOnlyList QuestTemplates => quests.AsReadOnly(); public NativeTemplateCache(Func isConnected, Func cloneSceneObject) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown connected = isConnected ?? throw new ArgumentNullException("isConnected"); cloneOwned = cloneSceneObject ?? throw new ArgumentNullException("cloneSceneObject"); nursery = new GameObject("ExpansionKit inactive native templates"); nursery.SetActive(false); nursery.AddComponent(); ((Object)nursery).hideFlags = (HideFlags)52; Object.DontDestroyOnLoad((Object)(object)nursery); } public IEnumerator Prepare() { ThrowIfDisposed(); if (Ready) { yield break; } if (preparing) { throw new InvalidOperationException("Template preparation is already running; wait for Ready."); } if (Failure != null) { throw new InvalidOperationException("Template preparation previously failed.", Failure); } if (connected()) { throw new InvalidOperationException("Capture native templates before starting any network connection."); } Patches patchInfo = Harmony.GetPatchInfo((MethodBase)AccessTools.Method(typeof(NPC), "OnDestroy", (Type[])null, (Type[])null)); if (patchInfo == null || !patchInfo.Prefixes.Any((Patch patch) => patch.PatchMethod.DeclaringType == typeof(MountedNpcDestroy))) { throw new InvalidOperationException("Install the runtime assembly's Harmony patches before native template preparation."); } preparing = true; try { while (IslandManager.IsLoading) { yield return null; } for (int build = 1; build <= 5; build++) { if (connected()) { throw new InvalidOperationException("A network connection started during template capture."); } Scene sceneByBuildIndex = SceneManager.GetSceneByBuildIndex(build); if (((Scene)(ref sceneByBuildIndex)).IsValid() && ((Scene)(ref sceneByBuildIndex)).isLoaded) { throw new InvalidOperationException("Native template scene is already loaded: " + build); } capturingBuildIndex = build; yield return SceneManager.LoadSceneAsync(build, (LoadSceneMode)1); sceneByBuildIndex = SceneManager.GetSceneByBuildIndex(build); try { if (connected()) { throw new InvalidOperationException("A network connection started during template capture."); } Capture(sceneByBuildIndex, build); } catch (Exception failure) { Failure = failure; } if (((Scene)(ref sceneByBuildIndex)).IsValid() && ((Scene)(ref sceneByBuildIndex)).isLoaded) { yield return SceneManager.UnloadSceneAsync(sceneByBuildIndex); } capturingBuildIndex = 0; if (Failure != null) { throw new InvalidOperationException("Native template capture failed.", Failure); } } if (!Object.op_Implicit((Object)(object)BoatPrefab) || !Object.op_Implicit((Object)(object)RewardHand)) { throw new InvalidOperationException("Native boat and held-reward templates were not found."); } Ready = true; } finally { NativeTemplateCache nativeTemplateCache = this; nativeTemplateCache.preparing = false; if (!nativeTemplateCache.Ready && nativeTemplateCache.Failure == null) { nativeTemplateCache.Failure = new InvalidOperationException("Native template preparation was interrupted."); } if (nativeTemplateCache.capturingBuildIndex != 0) { Scene sceneByBuildIndex2 = SceneManager.GetSceneByBuildIndex(nativeTemplateCache.capturingBuildIndex); if (((Scene)(ref sceneByBuildIndex2)).IsValid() && ((Scene)(ref sceneByBuildIndex2)).isLoaded) { SceneManager.UnloadSceneAsync(sceneByBuildIndex2); } nativeTemplateCache.capturingBuildIndex = 0; } } } private void Capture(Scene scene, int island) { //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded) { throw new InvalidOperationException("Missing native template scene."); } GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); if (!Object.op_Implicit((Object)(object)BoatPrefab)) { SpawnManager instance = rootGameObjects.SelectMany((GameObject root) => root.GetComponentsInChildren(true)).Single(); BoatPrefab = WorldFields.Get(instance, "_boatPrefab"); } List list = new List(); shops.Add(island, list); foreach (Purchasable item in rootGameObjects.SelectMany((GameObject root) => root.GetComponentsInChildren(true)).OrderBy((Purchasable source) => HierarchyPath(((Component)source).transform), StringComparer.Ordinal)) { if (((Component)item).GetComponentsInChildren(true).Length == 1 && ((Component)item).GetComponentsInChildren(true).Length == 0) { Purchasable component = Clone(((Component)item).gameObject, nursery.transform).GetComponent(); ((Object)component).name = ((Object)item).name; ((Component)component).gameObject.SetActive(false); list.Add(component); } } foreach (NPC item2 in from source in rootGameObjects.SelectMany((GameObject root) => root.GetComponentsInChildren(true)) where !WorldFields.Get(source, "_forScreenshot") orderby source.ID select source) { string text = NpcKey(island, item2.ID); if (npcs.ContainsKey(text)) { throw new InvalidOperationException("Ambiguous native NPC donor " + text); } GameObject obj = Clone(((Component)item2).gameObject, nursery.transform); NPC component2 = obj.GetComponent(); obj.SetActive(false); ((Object)obj).name = ((Object)item2).name; obj.transform.localPosition = Vector3.zero; obj.transform.localRotation = Quaternion.identity; obj.transform.localScale = ((Component)item2).transform.lossyScale; obj.AddComponent().BindTemplate(component2); WorldFields.Set(component2, "_forScreenshot", true); ((Behaviour)component2).enabled = false; npcs.Add(text, component2); List list2 = new List(); foreach (NPCQuest quest in item2.Quests) { if (!Object.op_Implicit((Object)(object)quest)) { throw new InvalidOperationException("Native donor has a missing quest: " + text); } NPCQuest val = Object.Instantiate(quest); ((Object)val).hideFlags = (HideFlags)52; list2.Add(val); quests.Add(val); } WorldFields.Set(component2, "_quests", list2); if (!Object.op_Implicit((Object)(object)RewardHand)) { QuestInteractable val2 = WorldFields.Optional(item2, "questInteractable"); if (val2 != null && (Object)(object)((Component)val2).gameObject != (Object)(object)((Component)item2).gameObject && !Object.op_Implicit((Object)(object)((Component)val2).GetComponentInChildren(true))) { RewardHand = Clone(((Component)val2).gameObject, nursery.transform).GetComponent(); } } } } public NPC GetNpc(byte donorIslandId, byte donorNpcId) { RequireReady(); if (!npcs.TryGetValue(NpcKey(donorIslandId, donorNpcId), out NPC value) || !Object.op_Implicit((Object)(object)value)) { throw new InvalidOperationException($"Missing native NPC donor island {donorIslandId}, NPC {donorNpcId}."); } return value; } public IReadOnlyList GetShops(byte donorIslandId) { RequireReady(); if (!shops.TryGetValue(donorIslandId, out List value)) { throw new InvalidOperationException("Missing native shop donor island " + donorIslandId); } return value.AsReadOnly(); } public void RequireReady() { ThrowIfDisposed(); if (!Ready) { throw new InvalidOperationException("Wait for NativeTemplateCache.Ready before mounting content.", Failure); } } internal GameObject Clone(GameObject source, Transform inactiveParent) { ThrowIfDisposed(); if (!Object.op_Implicit((Object)(object)source) || !Object.op_Implicit((Object)(object)inactiveParent) || ((Component)inactiveParent).gameObject.activeInHierarchy) { throw new ArgumentException("Native clones require a source and an inactive owned parent."); } GameObject val = cloneOwned(source, inactiveParent); if (!Object.op_Implicit((Object)(object)val) || (Object)(object)val == (Object)(object)source || val.activeInHierarchy || (Object)(object)val.transform.parent != (Object)(object)inactiveParent) { throw new InvalidOperationException("The native clone adapter returned an active, unowned or aliased source."); } if (val.GetComponentsInChildren(true).Any((NetworkObject network) => network.IsSceneObject)) { Object.Destroy((Object)(object)val); throw new InvalidOperationException("The native clone adapter retained donor scene/network IDs."); } val.SetActive(false); return val; } private static string NpcKey(int island, byte npc) { return island + ":" + npc; } internal static string HierarchyPath(Transform transform) { if (!Object.op_Implicit((Object)(object)transform.parent)) { return ((Object)transform).name; } return HierarchyPath(transform.parent) + "/" + ((Object)transform).name; } private void ThrowIfDisposed() { if (disposed) { throw new ObjectDisposedException("NativeTemplateCache"); } } public void Dispose() { if (disposed) { return; } if (preparing) { throw new InvalidOperationException("Stop and dispose the capture coroutine before disposing its templates."); } disposed = true; Ready = false; foreach (NPCQuest quest in quests) { if (Object.op_Implicit((Object)(object)quest)) { Object.Destroy((Object)(object)quest); } } quests.Clear(); npcs.Clear(); shops.Clear(); if (Object.op_Implicit((Object)(object)nursery)) { Object.Destroy((Object)(object)nursery); } } } internal sealed class NativeTemplateRoot : MonoBehaviour { } internal static class WorldFields { internal static FieldInfo Field(Type type, string name) { return AccessTools.Field(type, name) ?? throw new MissingFieldException(type.FullName, name); } internal static T Get(object instance, string name) { object value = Field(instance.GetType(), name).GetValue(instance); if (value is T) { return (T)value; } throw new InvalidOperationException("Missing native field value " + instance.GetType().Name + "." + name); } internal static T? Optional(object instance, string name) where T : class { return Field(instance.GetType(), name).GetValue(instance) as T; } internal static void Set(object instance, string name, object value) { Field(instance.GetType(), name).SetValue(instance, value); } internal static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } internal 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 (Finite(value.x) && Finite(value.y)) { return Finite(value.z); } return false; } } public sealed class PackControl : Interactable { private Func? label; private Action? pressed; private Func? available; private string shown = ""; private bool registered; public static PackControl Create(Transform parent, string name, Vector3 position, Vector3 size, GameObject[] outlines, Func label, Action pressed, Func? available = null) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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) //IL_0056: 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_0075: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: 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) if (!Object.op_Implicit((Object)(object)parent) || ((Component)parent).gameObject.activeInHierarchy || !NativeAccess.Positive(size.x) || !NativeAccess.Positive(size.y) || !NativeAccess.Positive(size.z) || !NativeAccess.Finite(position.x) || !NativeAccess.Finite(position.y) || !NativeAccess.Finite(position.z)) { throw new ArgumentException("Create controls under an inactive owned root with positive bounds."); } GameObject val = new GameObject(name); val.SetActive(false); val.transform.SetParent(parent, false); val.transform.localPosition = position; val.layer = LayerMask.NameToLayer("Interactable"); val.tag = "Interactable"; BoxCollider val2 = val.AddComponent(); val2.size = size; ((Collider)val2).isTrigger = true; GameObject val3 = new GameObject("Label"); val3.transform.SetParent(val.transform, false); val3.transform.localPosition = Vector3.up * (size.y * 0.5f + 0.15f); PackControl packControl = val.AddComponent(); packControl.label = label ?? throw new ArgumentNullException("label"); packControl.pressed = pressed ?? throw new ArgumentNullException("pressed"); packControl.available = available; NativeAccess.Set(packControl, "_interactCol", val2); NativeAccess.Set(packControl, "_textTarget", val3.transform); NativeAccess.Set(packControl, "_modelsToOutline", outlines ?? throw new ArgumentNullException("outlines")); val.SetActive(true); return packControl; } protected override void Awake() { if (label == null || pressed == null) { throw new InvalidOperationException("The pack control was activated before configuration."); } ((Interactable)this).Awake(); registered = true; } public override void Hover() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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) //IL_007e: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.LocalPlayer; if (Object.op_Implicit((Object)(object)localPlayer)) { string text = label(localPlayer) + "\n" + SpriteManager.GetPickUpInput(); if (!base._isHovering) { PlayerUI.SetLookAtText(text, ((Interactable)this).TextTarget, default(Vector3), false); } else if (text != shown) { PlayerUI.UpdateLookAtText(text); } shown = text; Func? func = available; PlayerUI.SetLookAtColor((func != null && !func(localPlayer)) ? GameInfo.RedColor : Color.white); ((Interactable)this).Hover(); } } public override void UnHover() { PlayerUI.HideLookAtText(shown); ((Interactable)this).UnHover(); } public override void Interact(Player player) { if (!Object.op_Implicit((Object)(object)player) || (Object)(object)player != (Object)(object)Player.LocalPlayer) { throw new InvalidOperationException("Pack controls send local input to the authoritative channel."); } ((Interactable)this).Interact(player); pressed(player); } protected override void OnDestroy() { if (registered) { PlayerUI.HideLookAtText(shown); ((Interactable)this).OnDestroy(); } } } public sealed class MountedIsland { public string Reference { get; } public byte NativeEngineId { get; } public Scene Scene { get; } public Transform Root { get; } public Transform Additions { get; } internal MountedIsland(string reference, byte id, Scene scene, Transform root, Transform additions) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) Reference = reference; NativeEngineId = id; Scene = scene; Root = root; Additions = additions; } } public sealed class PackWorldRuntime : IDisposable { private sealed class Location { internal string Pack = ""; internal string Reference = ""; internal IslandRecipe Recipe; internal IslandInfo? Info; internal GameObject? Approach; internal GameObject? Warning; internal string? ScenePath; } private readonly PackDefinition[] packs; private readonly Dictionary locations = new Dictionary(); private readonly Dictionary mounted = new Dictionary(); private readonly Dictionary> externalLocations = new Dictionary>(StringComparer.Ordinal); private readonly Dictionary> externalCompletion = new Dictionary>(StringComparer.Ordinal); private readonly Dictionary> externalPreservedAccess = new Dictionary>(StringComparer.Ordinal); private readonly Dictionary> externalLoaders = new Dictionary>(StringComparer.Ordinal); private readonly NativeTemplateCache templates; private readonly NativeShopMounts shops; private readonly NativeNpcMounts npcs; private readonly QuestProgressStore progress; private readonly Func connected; private readonly Func sceneResolver; private IslandManager? manager; private NativeSaveBridge? saveBridge; private bool disposed; internal static PackWorldRuntime? Active { get; private set; } public IReadOnlyCollection Mounted => (IReadOnlyCollection)(object)mounted.Values.ToArray(); public Exception? Failure { get; private set; } private byte NativeUnlockCount { get { if (!Object.op_Implicit((Object)(object)OnlineIslandManager.Instance)) { return SaveManager.CurServerSave?.MaxIsland ?? 1; } return (byte)Math.Min(5, (int)OnlineIslandManager.MaxIslandUnlocked); } } public event Action? SceneMounted; public event Action? SceneUnmounting; public event Action? Faulted; public PackWorldRuntime(PackRegistry registry, NativeTemplateCache cache, QuestProgressStore state, Func resolveItem, Func resolveLure, Func resolveVerifiedScenePath, Func isConnected, Func authorizeActor, Action bindAmmunitionPolicy) { if (Active != null) { throw new InvalidOperationException("One shared pack-world dispatcher is already installed."); } if (registry == null) { throw new ArgumentNullException("registry"); } templates = cache ?? throw new ArgumentNullException("cache"); progress = state ?? throw new ArgumentNullException("state"); sceneResolver = resolveVerifiedScenePath ?? throw new ArgumentNullException("resolveVerifiedScenePath"); connected = isConnected ?? throw new ArgumentNullException("isConnected"); packs = registry.Packs.ToArray(); foreach (ShopRecipe item in packs.SelectMany((PackDefinition pack) => pack.Shops)) { NativeShopMounts.ValidateRecipeSupport(item); } if (packs.SelectMany((PackDefinition pack) => pack.Npcs).Any((NpcRecipe npc) => (int)((PackRecipe)npc).Implementation != 1 && (npc.Art != null || (int)npc.Role == 2))) { throw new NotSupportedException("Authored NPC rigs and external roles require an External recipe adapter."); } shops = new NativeShopMounts(cache, resolveItem, resolveLure, bindAmmunitionPolicy); npcs = new NativeNpcMounts(cache, packs, state, resolveItem, resolveLure, authorizeActor, GrantCoordinates); PackDefinition[] array = packs; foreach (PackDefinition val in array) { foreach (IslandRecipe island in val.Islands) { if (island.Id < 5 || island.Id == byte.MaxValue || locations.ContainsKey(island.Id)) { throw new InvalidOperationException("A custom island requires a unique engine byte ID 5..254; 255 is the native unload sentinel."); } locations.Add(island.Id, new Location { Pack = val.Key, Reference = val.Key + ":" + ((PackRecipe)island).Key, Recipe = island }); progress.RegisterIsland(val.Key + ":" + ((PackRecipe)island).Key, (island.Route == null) ? ((int?)null) : new int?(MountReferences.NativeId(island.Route.BeforeIsland))); } } Active = this; SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; } public PackWorldRuntime(PackRegistry registry, NativeTemplateCache cache, NativeSaveBridge persistence, Func resolveItem, Func resolveLure, Func resolveVerifiedScenePath, Func isConnected, Func authorizeActor, Action bindAmmunitionPolicy) : this(registry, cache, CreateNativeCheckpointStore(persistence), resolveItem, resolveLure, resolveVerifiedScenePath, isConnected, authorizeActor, bindAmmunitionPolicy) { try { AttachSaveBridge(persistence); } catch { Dispose(); throw; } } public static QuestProgressStore CreateNativeCheckpointStore(NativeSaveBridge persistence) { if (persistence == null) { throw new ArgumentNullException("persistence"); } return new QuestProgressStore(delegate(QuestCheckpoint request, Action apply) { if (request.Kind == "select-save") { apply(); } else { persistence.Checkpoint(); apply(); persistence.Checkpoint(); } }); } public void AttachSaveBridge(NativeSaveBridge persistence, string participantKey = "world") { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_0061: Expected O, but got Unknown ThrowIfDisposed(); if (persistence == null) { throw new ArgumentNullException("persistence"); } if (saveBridge != null) { throw new InvalidOperationException("World persistence is already attached."); } JsonSerializer serializer = JsonSerializer.Create(new JsonSerializerSettings { TypeNameHandling = (TypeNameHandling)0, MetadataPropertyHandling = (MetadataPropertyHandling)2, MissingMemberHandling = (MissingMemberHandling)1, MaxDepth = 32 }); persistence.RegisterParticipant(participantKey, () => (JToken)(object)JObject.FromObject((object)CaptureState(), serializer), delegate(JToken? token) { if (token == null) { RestoreState(new MountedPackState()); } else { ValidateStateToken(token); RestoreState(token.ToObject(serializer) ?? throw new InvalidOperationException("The world checkpoint did not contain a state object.")); } }); saveBridge = persistence; persistence.SaveSelected += OnSaveSelected; } private static void ValidateStateToken(JToken token) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Invalid comparison between Unknown and I4 JObject val = (JObject)(object)((token is JObject) ? token : null); if (val == null) { throw new InvalidOperationException("The world checkpoint must be an object."); } Require(val, "Version", (JTokenType)6); Require(val, "Revision", (JTokenType)6); Require(val, "SaveSelectionInitialized", (JTokenType)9); Require(val, "NativeUnlockedAtSelection", (JTokenType)6); Require(val, "Quests", (JTokenType)2); Require(val, "IslandFlags", (JTokenType)1); foreach (JToken item in (JArray)val["Quests"]) { JToken value = ((item is JObject) ? item : null) ?? throw new InvalidOperationException("Invalid world quest checkpoint."); Require((JObject)(object)value, "Key", (JTokenType)8); Require((JObject)(object)value, "Delivered", (JTokenType)6); Require((JObject)(object)value, "RewardClaimed", (JTokenType)9); } foreach (JProperty item2 in ((JObject)val["IslandFlags"]).Properties()) { if ((int)item2.Value.Type != 9) { throw new InvalidOperationException("Invalid island completion flag."); } } static void Require(JObject val2, string property, JTokenType kind) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) JToken obj = val2[property]; if (obj == null || obj.Type != kind) { throw new InvalidOperationException("Missing or invalid world checkpoint field: " + property); } } } private void OnSaveSelected(int nativeUnlockedCount) { if (nativeUnlockedCount < 1 || nativeUnlockedCount > 5) { throw new ArgumentOutOfRangeException("nativeUnlockedCount", "Native island count must be 1..5."); } SelectSave((byte)nativeUnlockedCount); } public void RegisterExternalIsland(string reference, Func location, Func completed, Func? loadScene = null, Func? preservedContinuationAccess = null) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Invalid comparison between Unknown and I4 ThrowIfDisposed(); if ((int)((PackRecipe)(locations.Values.SingleOrDefault((Location value) => value.Reference == reference) ?? throw new ArgumentException("Unregistered external island: " + reference)).Recipe).Implementation != 1) { throw new ArgumentException("Only an External island may use an existing scene adapter."); } externalLocations.Add(reference, location ?? throw new ArgumentNullException("location")); externalCompletion.Add(reference, completed ?? throw new ArgumentNullException("completed")); if (loadScene != null) { externalLoaders.Add(reference, loadScene); } if (preservedContinuationAccess != null) { externalPreservedAccess.Add(reference, preservedContinuationAccess); } } public void Configure(IslandManager instance) { //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Invalid comparison between Unknown and I4 //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Expected O, but got Unknown //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Expected O, but got Unknown //IL_02fb: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Unknown result type (might be due to invalid IL or missing references) //IL_0330: Expected O, but got Unknown ThrowIfDisposed(); if (Object.op_Implicit((Object)(object)manager) && (Object)(object)manager != (Object)(object)instance) { throw new InvalidOperationException("Dispose the world dispatcher before replacing the native island manager."); } if (Object.op_Implicit((Object)(object)manager)) { return; } if (!Object.op_Implicit((Object)(object)instance)) { throw new ArgumentNullException("instance"); } manager = instance; IslandInfo[] array = WorldFields.Get(instance, "_islandInfos"); if (array.Length != 5 || array.Any((IslandInfo value) => value == null)) { throw new InvalidOperationException("The expected five original island locations are unavailable."); } foreach (Location location in locations.Values.OrderBy((Location value) => value.Recipe.Id)) { if ((int)((PackRecipe)location.Recipe).Implementation != 1) { Vector3 position = MountPlacement.Vector(location.Recipe.WorldMapPosition); if (!WorldFields.Finite(position)) { throw new InvalidOperationException("Invalid custom island map coordinates."); } if (array.Any((IslandInfo original) => Vector2.Distance(new Vector2(position.x, position.z), new Vector2(original.IslandPosition.x, original.IslandPosition.z)) < 120f) || locations.Values.Where((Location other) => other != location).Any((Location other) => HorizontalDistance(position, MapPosition(other)) < 120f)) { throw new InvalidOperationException("Island map approach regions overlap: " + location.Reference); } location.Approach = new GameObject("ExpansionKit approach " + location.Reference); location.Approach.SetActive(false); location.Approach.transform.SetParent(((Component)instance).transform, false); location.Approach.transform.position = position; SphereCollider obj = location.Approach.AddComponent(); obj.radius = 50f; ((Collider)obj).isTrigger = true; PackIslandApproach packIslandApproach = location.Approach.AddComponent(); packIslandApproach.World = this; packIslandApproach.IslandId = location.Recipe.Id; location.Warning = new GameObject("ExpansionKit active location " + location.Reference); location.Warning.transform.SetParent(((Component)instance).transform, false); location.Warning.transform.position = position; location.Warning.SetActive(false); location.Info = new IslandInfo(); WorldFields.Set(location.Info, "_spawnPosition", location.Approach); WorldFields.Set(location.Info, "_warning", location.Warning); } } RefreshTravel(); } private Vector3 MapPosition(Location location) { //IL_0035: 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_003a: Unknown result type (might be due to invalid IL or missing references) Func value; IslandInfo val = (externalLocations.TryGetValue(location.Reference, out value) ? value() : null); Vector3 val2 = ((val != null) ? val.IslandPosition : MountPlacement.Vector(location.Recipe.WorldMapPosition)); if (!WorldFields.Finite(val2)) { throw new InvalidOperationException("Invalid island map position: " + location.Reference); } return val2; } private static float HorizontalDistance(Vector3 left, Vector3 right) { //IL_0000: 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_000c: 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_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) return Vector2.Distance(new Vector2(left.x, left.z), new Vector2(right.x, right.z)); } public MountedPackState CaptureState() { ThrowIfDisposed(); return progress.CaptureState(); } public void RestoreState(MountedPackState state) { ThrowIfDisposed(); progress.RestoreState(state); npcs.RefreshMountedState(); RefreshTravel(); } public void SelectSave(byte nativeUnlockedCount) { ThrowIfDisposed(); progress.SelectSave(nativeUnlockedCount); RefreshTravel(); } public void MarkIslandCompleted(string reference) { if (!Object.op_Implicit((Object)(object)Server.Instance) || !((NetworkBehaviour)Server.Instance).IsServerInitialized) { throw new InvalidOperationException("Only the verified authority may complete an island."); } if (!locations.Values.Any((Location value) => value.Reference == reference)) { throw new ArgumentException("Unknown island: " + reference); } progress.SetIslandFlag(reference + ":completed", value: true); RefreshTravel(); } public void CompleteIslandOnClaim(string questReference, string islandReference) { ThrowIfDisposed(); if (!locations.Values.Any((Location location) => location.Reference == islandReference)) { throw new ArgumentException("Unknown completion island: " + islandReference); } progress.CompleteIslandOnClaim(questReference, islandReference); } private bool Completed(Location location) { if (externalCompletion.TryGetValue(location.Reference, out Func value)) { return value(); } bool value2; return progress.CaptureState().IslandFlags.TryGetValue(location.Reference + ":completed", out value2) && value2; } private IslandRouteAccess RouteAccess(Location location) { bool value; bool? introductionPreservedAccess = (progress.CaptureState().IslandFlags.TryGetValue(location.Reference + ":preserve_native_access", out value) ? new bool?(value) : ((bool?)null)); Func value2; return new IslandRouteAccess(Completed(location), externalPreservedAccess.TryGetValue(location.Reference, out value2) ? new bool?(value2()) : ((bool?)null), introductionPreservedAccess); } private bool RouteSatisfied(Location location, int nativeOrdinal) { return IslandRoutePolicy.RouteSatisfied(RouteAccess(location), PreserveNativeAccess(nativeOrdinal)); } private IEnumerable Gap(int beforeNativeOrdinal) { return (from value in locations.Values where value.Recipe.Route != null && MountReferences.NativeId(value.Recipe.Route.BeforeIsland) == beforeNativeOrdinal orderby value.Recipe.Route.Priority select value).ThenBy((Location value) => value.Reference, StringComparer.Ordinal); } private bool PreserveNativeAccess(int nativeOrdinal) { return IslandRoutePolicy.PreservesNativeAccess(nativeOrdinal, SelectionForRouting()); } private MountedPackState SelectionForRouting() { MountedPackState mountedPackState = progress.CaptureState(); foreach (Location item in locations.Values.Where((Location location) => location.Recipe.Route != null)) { if (mountedPackState.IslandFlags.TryGetValue(item.Reference + ":preserve_native_access", out var value) && value) { mountedPackState.NativeUnlockedAtSelection = Math.Max(mountedPackState.NativeUnlockedAtSelection, MountReferences.NativeId(item.Recipe.Route.BeforeIsland)); } } return mountedPackState; } public bool CanVisit(byte engineIslandId) { if (disposed || Failure != null || !templates.Ready || progress.Fault != null) { return false; } if (engineIslandId < 5) { int num = engineIslandId + 1; return IslandRoutePolicy.CanVisitNative(num, NativeUnlockCount, SelectionForRouting(), Gap(num).Select(RouteAccess)); } if (!locations.TryGetValue(engineIslandId, out Location location)) { return false; } if (NativeUnlockCount < location.Recipe.UnlockThreshold) { return false; } string[] array = packs.SelectMany((PackDefinition pack) => from quest in pack.Quests where (int)((PackRecipe)quest).Implementation != 1 && quest.Rewards.Any((QuestRewardRecipe reward) => (int)reward.Kind == 2 && MountReferences.Qualify(pack.Key, reward.Content ?? "") == location.Reference) select pack.Key + ":" + ((PackRecipe)quest).Key).ToArray(); if (array.Length != 0 && !array.Any((string key) => progress.Get(key).RewardClaimed)) { return false; } int? num2 = ((location.Recipe.Route == null) ? ((int?)null) : new int?(MountReferences.NativeId(location.Recipe.Route.BeforeIsland))); int unlockThreshold = location.Recipe.UnlockThreshold; byte nativeUnlockCount = NativeUnlockCount; int? beforeNativeOrdinal = num2; MountedPackState selectedSave = SelectionForRouting(); IEnumerable precedingIslands; if (!num2.HasValue) { IEnumerable enumerable = Array.Empty(); precedingIslands = enumerable; } else { precedingIslands = Gap(num2.Value).TakeWhile((Location value) => value != location).Select(RouteAccess); } return IslandRoutePolicy.CanVisitCustom(unlockThreshold, nativeUnlockCount, beforeNativeOrdinal, selectedSave, precedingIslands); } public IslandInfo? FindLocation(int engineId) { if (engineId < 0 || engineId > 255) { return null; } if (engineId < 5) { Location location = Gap(engineId + 1).FirstOrDefault((Location location2) => !RouteSatisfied(location2, engineId + 1)); if (location != null) { return Info(location); } } if (!locations.TryGetValue((byte)engineId, out Location value)) { return null; } return Info(value); } private IslandInfo Info(Location location) { IslandInfo obj = location.Info; if (obj == null) { if (!externalLocations.TryGetValue(location.Reference, out Func value)) { throw new InvalidOperationException("Island location adapter is not ready: " + location.Reference); } obj = value(); } return obj; } public void RefreshTravel() { if (!Object.op_Implicit((Object)(object)manager)) { return; } byte b = (Object.op_Implicit((Object)(object)OnlineIslandManager.Instance) ? OnlineIslandManager.CurIsland : byte.MaxValue); foreach (Location value in locations.Values) { if (value.Info != null) { value.Info.Toggle(b != value.Recipe.Id); if (b != value.Recipe.Id && !CanVisit(value.Recipe.Id)) { value.Approach.SetActive(false); value.Warning.SetActive(false); } } } IslandInfo[] array = WorldFields.Get(manager, "_islandInfos"); int engineId; for (engineId = 0; engineId < 5; engineId++) { if (Gap(engineId + 1).All((Location location) => RouteSatisfied(location, engineId + 1))) { array[engineId].Toggle(b != engineId); continue; } WorldFields.Get(array[engineId], "_spawnPosition").SetActive(false); WorldFields.Get(array[engineId], "_warning").SetActive(false); } } private void GrantCoordinates(string reference) { if (reference.StartsWith("native:", StringComparison.Ordinal)) { byte b = MountReferences.NativeId(reference); if (!Object.op_Implicit((Object)(object)OnlineIslandManager.Instance) || b < 1 || b > 5 || b > NativeUnlockCount + 1) { throw new InvalidOperationException("Coordinates cannot skip native island stages."); } OnlineIslandManager.Instance.UnlockIsland(b); } else if (!locations.Values.Any((Location value) => value.Reference == reference)) { throw new InvalidOperationException("Coordinate reward references an unmounted island: " + reference); } } internal bool OwnsScene(int buildIndex) { if (buildIndex > 5 && buildIndex <= 256) { return locations.ContainsKey((byte)(buildIndex - 1)); } return false; } internal AsyncOperation? LoadScene(int buildIndex, LoadSceneMode mode) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Invalid comparison between Unknown and I4 //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) templates.RequireReady(); if ((int)mode != 1) { throw new InvalidOperationException("Custom islands must be loaded additively."); } Location location = locations[(byte)(buildIndex - 1)]; if ((int)((PackRecipe)location.Recipe).Implementation == 1) { if (!externalLoaders.TryGetValue(location.Reference, out Func value)) { throw new InvalidOperationException("External island has no registered scene loader: " + location.Reference); } return value(mode); } if (location.ScenePath != null) { Scene sceneByPath = SceneManager.GetSceneByPath(location.ScenePath); if (((Scene)(ref sceneByPath)).isLoaded) { throw new InvalidOperationException("The requested custom island is already loaded."); } } location.ScenePath = sceneResolver(location.Pack, location.Recipe); if (string.IsNullOrWhiteSpace(location.ScenePath) || Path.GetFileNameWithoutExtension(location.ScenePath) != location.Recipe.SceneName) { throw new InvalidOperationException("The scene resolver did not provide a verified authored scene path."); } return SceneManager.LoadSceneAsync(location.ScenePath, mode); } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (!templates.Ready || !connected() || disposed) { return; } try { MountLoadedScene(scene); } catch (Exception ex) { Exception obj = (Failure = ex); this.Faulted?.Invoke(obj); throw; } } private void MountLoadedScene(Scene scene) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Expected O, but got Unknown //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0139: 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_014f: Expected O, but got Unknown //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_02a5: Unknown result type (might be due to invalid IL or missing references) Location custom = locations.Values.SingleOrDefault((Location value) => (int)((PackRecipe)value.Recipe).Implementation != 1 && value.ScenePath != null && ((Scene)(ref scene)).path == value.ScenePath); if (custom != null) { GameObject[] array = (from root in ((Scene)(ref scene)).GetRootGameObjects() where ((Object)root).name == custom.Recipe.SceneRoot select root).ToArray(); if (array.Length != 1) { throw new InvalidOperationException("Authored island must contain exactly one declared root: " + custom.Reference); } GameObject val = array[0]; val.SetActive(false); ValidateAuthoredRoot(val, custom); val.transform.position = MountPlacement.Vector(custom.Recipe.WorldMapPosition); if (!Object.op_Implicit((Object)(object)val.GetComponent())) { val.AddComponent(); } MountPose mountPose = MountPlacement.Resolve(val.transform, custom.Recipe.PlayerSpawn); MountPose mountPose2 = MountPlacement.Resolve(val.transform, custom.Recipe.BoatSpawn); GameObject val2 = new GameObject("ExpansionKit player arrival"); val2.transform.SetParent(val.transform, false); val2.transform.localPosition = mountPose.Position; val2.transform.localRotation = mountPose.Rotation; GameObject val3 = new GameObject("ExpansionKit boat arrival"); val3.transform.SetParent(val.transform, false); val3.transform.localPosition = mountPose2.Position; val3.transform.localRotation = mountPose2.Rotation; SpawnManager instance = val.AddComponent(); WorldFields.Set(instance, "_playerSpawnPoint", val2.transform); WorldFields.Set(instance, "_boatSpawnPoint", val3.transform); WorldFields.Set(instance, "_boatPrefab", templates.BoatPrefab); MountScene(custom.Reference, custom.Recipe.Id, scene, val.transform); val.SetActive(true); } else if (((Scene)(ref scene)).buildIndex >= 1 && ((Scene)(ref scene)).buildIndex <= 5) { Island[] array2 = ((Scene)(ref scene)).GetRootGameObjects().SelectMany((GameObject root) => root.GetComponentsInChildren(true)).ToArray(); if (array2.Length != 1) { throw new InvalidOperationException("Native island root is missing or ambiguous: " + ((Scene)(ref scene)).name); } MountScene("native:" + ((Scene)(ref scene)).buildIndex, (byte)(((Scene)(ref scene)).buildIndex - 1), scene, ((Component)array2[0]).transform); } } private static void ValidateAuthoredRoot(GameObject root, Location location) { if (root.GetComponentsInChildren(true).Length != 0 || root.GetComponentsInChildren(true).Length != 0 || root.GetComponentsInChildren(true).Length != 0 || root.GetComponentsInChildren(true).Length != 0) { throw new InvalidOperationException("Authored island roots contain art/colliders/markers, not serialized native runtime services."); } Collider[] array = (from collider in root.GetComponentsInChildren(true) where !collider.isTrigger select collider).ToArray(); if (array.Length == 0 || array.Any((Collider collider) => !collider.enabled)) { throw new InvalidOperationException("An authored island needs enabled solid landing/collision geometry."); } int num = LayerMask.NameToLayer("Level"); if (num < 0) { throw new InvalidOperationException("The native Level layer is missing."); } Collider[] array2 = array; foreach (Collider obj in array2) { ((Component)obj).gameObject.layer = num; ((Component)obj).gameObject.tag = "Level"; } MountPlacement.Resolve(root.transform, location.Recipe.PlayerSpawn); MountPlacement.Resolve(root.transform, location.Recipe.BoatSpawn); } public MountedIsland MountScene(string islandReference, byte engineId, Scene scene, Transform islandRoot) { //IL_02fa: 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_0045: 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_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Expected O, but got Unknown //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_02c3: Unknown result type (might be due to invalid IL or missing references) ThrowIfDisposed(); templates.RequireReady(); if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded || !Object.op_Implicit((Object)(object)islandRoot) || ((Component)islandRoot).gameObject.scene != scene) { throw new ArgumentException("Mounting requires an actual loaded island root in the supplied scene."); } Location value; if (islandReference.StartsWith("native:", StringComparison.Ordinal)) { byte b = MountReferences.NativeId(islandReference); if (b < 1 || b > 5 || engineId != b - 1 || ((Scene)(ref scene)).buildIndex != b) { throw new ArgumentException("Native mounting reference, engine ID and actual scene do not agree."); } } else if (!locations.TryGetValue(engineId, out value) || value.Reference != islandReference) { throw new ArgumentException("Custom mounting reference and registered engine ID do not agree."); } if (mounted.ContainsKey(scene)) { throw new InvalidOperationException("Scene was mounted twice: " + ((Scene)(ref scene)).name); } GameObject val = new GameObject("ExpansionKit additions " + islandReference); val.SetActive(false); val.transform.SetParent(islandRoot, false); MountedIsland mountedIsland = new MountedIsland(islandReference, engineId, scene, islandRoot, val.transform); try { PackDefinition[] array = packs; foreach (PackDefinition pack in array) { foreach (NpcRecipe item in pack.Npcs.Where((NpcRecipe val2) => (int)((PackRecipe)val2).Implementation != 1 && MountReferences.Qualify(pack.Key, val2.Island) == islandReference).OrderBy((NpcRecipe val2) => ((PackRecipe)val2).Key, StringComparer.Ordinal)) { npcs.Mount(pack.Key, item, islandRoot, val.transform); } foreach (ShopRecipe item2 in pack.Shops.Where((ShopRecipe val2) => (int)((PackRecipe)val2).Implementation != 1 && MountReferences.Qualify(pack.Key, val2.Island) == islandReference).OrderBy((ShopRecipe val2) => ((PackRecipe)val2).Key, StringComparer.Ordinal)) { shops.Mount(pack.Key, item2, islandRoot, val.transform); } } mounted.Add(scene, mountedIsland); val.SetActive(true); npcs.RefreshMountedState(); this.SceneMounted?.Invoke(mountedIsland); return mountedIsland; } catch { mounted.Remove(scene); MountedNpc[] componentsInChildren = val.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { componentsInChildren[i].Retire(); } Object.Destroy((Object)(object)val); throw; } } public void UnmountScene(Scene scene) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!mounted.TryGetValue(scene, out MountedIsland value)) { return; } mounted.Remove(scene); try { this.SceneUnmounting?.Invoke(value); } finally { if (Object.op_Implicit((Object)(object)value.Additions)) { MountedNpc[] componentsInChildren = ((Component)value.Additions).GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { componentsInChildren[i].Retire(); } ((Component)value.Additions).gameObject.SetActive(false); Object.Destroy((Object)(object)((Component)value.Additions).gameObject); } } } private void OnSceneUnloaded(Scene scene) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) UnmountScene(scene); } public void Tick() { ThrowIfDisposed(); if (templates.Ready) { npcs.RefreshMountedState(); RefreshTravel(); } } private void ThrowIfDisposed() { if (disposed) { throw new ObjectDisposedException("PackWorldRuntime"); } } public void Dispose() { //IL_0072: Unknown result type (might be due to invalid IL or missing references) if (disposed) { return; } SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.sceneUnloaded -= OnSceneUnloaded; if (saveBridge != null) { saveBridge.SaveSelected -= OnSaveSelected; saveBridge = null; } List list = new List(); MountedIsland[] array = mounted.Values.ToArray(); foreach (MountedIsland mountedIsland in array) { try { UnmountScene(mountedIsland.Scene); } catch (Exception item) { list.Add(item); } } foreach (Location value in locations.Values) { if (Object.op_Implicit((Object)(object)value.Approach)) { Object.Destroy((Object)(object)value.Approach); } if (Object.op_Implicit((Object)(object)value.Warning)) { Object.Destroy((Object)(object)value.Warning); } } if (Object.op_Implicit((Object)(object)manager)) { IslandInfo[] array2 = WorldFields.Get(manager, "_islandInfos"); byte b = (Object.op_Implicit((Object)(object)OnlineIslandManager.Instance) ? OnlineIslandManager.CurIsland : byte.MaxValue); for (int j = 0; j < array2.Length; j++) { array2[j].Toggle(b != j); } } disposed = true; if (Active == this) { Active = null; } if (list.Count == 0) { return; } throw new AggregateException("Some scene-unmount callbacks failed after world cleanup.", list); } } internal sealed class PackIslandApproach : MonoBehaviour { internal PackWorldRuntime World; internal byte IslandId; private void OnTriggerEnter(Collider other) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)Server.Instance) && ((NetworkBehaviour)Server.Instance).IsServerInitialized && World.CanVisit(IslandId) && !IslandManager.IsLoading && !OnlineIslandManager.TeleportPlayers && !(Time.time - OnlineIslandManager.TimeWhenSwappingIsland < 5f) && ((Component)other).CompareTag("Player")) { Player playerFromBodyPart = PlayerManager.GetPlayerFromBodyPart(((Component)other).transform); if (Object.op_Implicit((Object)(object)playerFromBodyPart) && DazedUtils.AllPlayersNearbyCheck(playerFromBodyPart.Transform.position, IslandManager.NearPlayerRange)) { OnlineIslandManager.Instance.SpawnIsland(IslandId); } } } } [HarmonyPatch(typeof(IslandManager), "Awake")] internal static class PackWorldConfigure { private static void Postfix(IslandManager __instance) { PackWorldRuntime.Active?.Configure(__instance); } } [HarmonyPatch(typeof(IslandManager), "GetIslandInfo")] internal static class PackWorldLocation { private static bool Prefix(int index, ref IslandInfo __result) { IslandInfo val = PackWorldRuntime.Active?.FindLocation(index); if (val == null) { return true; } __result = val; return false; } } [HarmonyPatch(typeof(IslandManager), "OnIslandChange")] internal static class PackWorldTravelChanged { private static void Postfix() { PackWorldRuntime.Active?.RefreshTravel(); } } [HarmonyPatch(typeof(OnlineIslandManager), "SpawnIsland")] internal static class PackWorldTravelGuard { private static bool Prefix(byte islandIndex) { return PackWorldRuntime.Active?.CanVisit(islandIndex) ?? true; } } [HarmonyPatch] internal static class PackWorldSceneLoading { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(IslandManager).GetNestedTypes(BindingFlags.NonPublic).Single((Type type) => type.Name.StartsWith("", StringComparison.Ordinal)), "MoveNext", (Type[])null, (Type[])null); } private static AsyncOperation? Load(int buildIndex, LoadSceneMode mode) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) PackWorldRuntime? active = PackWorldRuntime.Active; if (active == null || !active.OwnsScene(buildIndex)) { return SceneManager.LoadSceneAsync(buildIndex, mode); } return PackWorldRuntime.Active.LoadScene(buildIndex, mode); } private static IEnumerable Transpiler(IEnumerable instructions) { MethodInfo source = AccessTools.Method(typeof(SceneManager), "LoadSceneAsync", new Type[2] { typeof(int), typeof(LoadSceneMode) }, (Type[])null); MethodInfo operand = AccessTools.Method(typeof(PackWorldSceneLoading), "Load", (Type[])null, (Type[])null); List list = instructions.ToList(); CodeInstruction[] array = list.Where((CodeInstruction code) => code.opcode == OpCodes.Call && object.Equals(code.operand, source)).ToArray(); if (array.Length != 1) { throw new InvalidOperationException("Native island loader is not the validated game build, or another loader replaced it."); } array[0].operand = operand; return list; } } } namespace HowToFish.ExpansionKit.Runtime.Shops { public sealed class NativeShopMounts { private readonly NativeTemplateCache templates; private readonly Func item; private readonly Func lure; private readonly Action bindAmmunition; public NativeShopMounts(NativeTemplateCache cache, Func resolveItem, Func resolveLure, Action bindAmmunitionPolicy) { templates = cache ?? throw new ArgumentNullException("cache"); item = resolveItem ?? throw new ArgumentNullException("resolveItem"); lure = resolveLure ?? throw new ArgumentNullException("resolveLure"); bindAmmunition = bindAmmunitionPolicy ?? throw new ArgumentNullException("bindAmmunitionPolicy"); } public Purchasable Mount(string packKey, ShopRecipe recipe, Transform islandRoot, Transform ownedRoot) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Invalid comparison between Unknown and I4 //IL_00bf: 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_00f9: Invalid comparison between Unknown and I4 //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Invalid comparison between Unknown and I4 //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Invalid comparison between Unknown and I4 //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: 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_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_0319: 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_04b5: Unknown result type (might be due to invalid IL or missing references) templates.RequireReady(); if (!Object.op_Implicit((Object)(object)ownedRoot) || ((Component)ownedRoot).gameObject.activeInHierarchy) { throw new ArgumentException("Configure shops under an inactive owned additions root."); } if ((int)((PackRecipe)recipe).Implementation == 1) { throw new InvalidOperationException("External shops must be mounted by their registered adapter."); } ValidateRecipeSupport(recipe); MountPose mountPose = MountPlacement.Resolve(islandRoot, recipe.Placement); string reference = MountReferences.Qualify(packKey, recipe.Content); string text = MountReferences.Key(packKey, ((PackRecipe)recipe).Key); Purchasable val = null; try { Item merchandise = (((int)recipe.Kind == 0) ? item(reference) : null); BaitInfo bait = (((int)recipe.Kind == 1) ? lure(reference) : null); Purchasable[] source = Enumerable.Range(1, 5).SelectMany((int index) => templates.GetShops((byte)index)).ToArray(); Purchasable val2 = ((IEnumerable)source).FirstOrDefault((Func)((Purchasable source2) => Matches(source2, recipe.Kind, reference, merchandise, bait))); if (!Object.op_Implicit((Object)(object)val2) && (int)recipe.Kind == 5) { val2 = (Purchasable)(object)source.OfType().FirstOrDefault(); } if (!Object.op_Implicit((Object)(object)val2) && (int)recipe.Kind == 6) { val2 = (Purchasable)(object)source.OfType().FirstOrDefault(); } if (Object.op_Implicit((Object)(object)val2)) { val = templates.Clone(((Component)val2).gameObject, ownedRoot).GetComponent(); float y = ((Component)val2).transform.localEulerAngles.y; ((Component)val).transform.localRotation = mountPose.Rotation * Quaternion.Inverse(Quaternion.Euler(0f, y, 0f)) * ((Component)val2).transform.localRotation; } else if (Object.op_Implicit((Object)(object)merchandise)) { val = (Purchasable)(object)CreateItemStand(merchandise, ownedRoot); ((Component)val).transform.localRotation = mountPose.Rotation; } else { if (!Object.op_Implicit((Object)(object)bait)) { throw new InvalidOperationException("No native shelf with the requested actual content identity: " + text); } BaitPurchasable val3 = Enumerable.Range(1, 5).SelectMany((int index) => templates.GetShops((byte)index)).OfType() .FirstOrDefault() ?? throw new InvalidOperationException("No native lure display template."); val = (Purchasable)(object)templates.Clone(((Component)val3).gameObject, ownedRoot).GetComponent(); WorldFields.Set(val, "_bait", bait); ReplaceLurePreview(val, bait); ((Component)val).transform.localRotation = mountPose.Rotation; } ((Object)val).name = "ExpansionKit shop " + text; ((Component)val).transform.localPosition = mountPose.Position; WorldFields.Set(val, "_isFree", false); WorldFields.Set(val, "_customCanBuy", true); WorldFields.Set(val, "_isHovering", false); if (Object.op_Implicit((Object)(object)merchandise)) { if (recipe.Cost.HasValue && recipe.Cost != merchandise.Cost) { throw new NotSupportedException("Item shelf price must equal the registered item Cost: " + text); } WorldFields.Set(val, "_customCost", merchandise.Cost); } else if (Object.op_Implicit((Object)(object)bait)) { if (recipe.Cost.HasValue && recipe.Cost != bait.Cost) { throw new NotSupportedException("Lure shelf price must equal the registered lure Cost: " + text); } WorldFields.Set(val, "_customCost", bait.Cost); } else if (recipe.Cost.HasValue) { throw new NotSupportedException("Upgrade and service prices are determined by native server rules; fixed overrides are not supported: " + text); } ValidateStand(val); SeatOnSurface(val, ownedRoot, mountPose.Position.y); if (val is SharpnessPurchasable) { WorldFields.Set(val, "_maxSharpnessUpgrade", MountReferences.NativeId(reference)); } if (val is SlotPurchasable) { WorldFields.Set(val, "_maxSlot", MountReferences.NativeId(reference)); } BulletPurchasable val4 = (BulletPurchasable)(object)((val is BulletPurchasable) ? val : null); if (val4 != null) { byte b = MountReferences.NativeId(reference); if (b < 1 || b > 12) { throw new InvalidOperationException("Ammunition tier must be 1..12."); } WorldFields.Set(val4, "_maxBulletUpgrade", b); bindAmmunition(val4, text); } ((Component)val).gameObject.SetActive(true); return val; } catch { if (Object.op_Implicit((Object)(object)val)) { Object.Destroy((Object)(object)((Component)val).gameObject); } throw; } } public static void ValidateRecipeSupport(ShopRecipe recipe) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 //IL_0019: 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_006e: Invalid comparison between Unknown and I4 //IL_004c: 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_0095: Invalid comparison between Unknown and I4 //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Invalid comparison between Unknown and I4 //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Invalid comparison between Unknown and I4 //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Invalid comparison between Unknown and I4 //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Invalid comparison between Unknown and I4 if (recipe == null) { throw new ArgumentNullException("recipe"); } if ((int)((PackRecipe)recipe).Implementation == 1) { return; } if ((int)recipe.CapPolicy != 0 || recipe.Cap.HasValue) { throw new NotSupportedException("Stock quantity caps need a transactional commerce adapter; native mounts cannot silently enforce them locally."); } if (recipe.Cost.HasValue && (int)recipe.Kind != 0 && (int)recipe.Kind != 1) { throw new NotSupportedException("Native upgrade/service prices are authoritative and cannot use a per-stand fixed override."); } if ((int)recipe.Kind == 5 || (int)recipe.Kind == 6 || (int)recipe.Kind == 3) { byte b = MountReferences.NativeId(recipe.Content); int num = (((int)recipe.Kind == 5) ? 15 : (((int)recipe.Kind == 6) ? 5 : 12)); if (b < 1 || b > num) { throw new ArgumentException("Native service tier is outside the supported range."); } } } private unsafe static bool Matches(Purchasable source, PackShopKind kind, string reference, Item? target, BaitInfo? bait) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected I4, but got Unknown switch ((int)kind) { case 0: if (source is ItemPurchasable && Object.op_Implicit((Object)(object)target)) { return WorldFields.Get(source, "_itemToPurchase").ID == target.ID; } return false; case 1: if (source is BaitPurchasable && Object.op_Implicit((Object)(object)bait)) { return (Object)(object)WorldFields.Get(source, "_bait") == (Object)(object)bait; } return false; case 2: if (source is AttachmentPurchasable) { return GameInfo.GetIndexOfAttachment(WorldFields.Get(source, "_info")) == MountReferences.NativeId(reference); } return false; case 3: return source is BulletPurchasable; case 4: return source is BoatRadarPurchasable; case 5: if (source is SharpnessPurchasable) { return WorldFields.Get(source, "_maxSharpnessUpgrade") == MountReferences.NativeId(reference); } return false; case 6: if (source is SlotPurchasable) { return WorldFields.Get(source, "_maxSlot") == MountReferences.NativeId(reference); } return false; case 7: if (source is MotorPurchasable) { return WorldFields.Get(source, "_motorIndex") == MountReferences.NativeId(reference); } return false; default: throw new NotSupportedException("Unsupported native shop kind: " + ((object)(*(PackShopKind*)(&kind))/*cast due to .constrained prefix*/).ToString()); } } private static ItemPurchasable CreateItemStand(Item merchandise, Transform parent) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: 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_0172: 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_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0227: 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_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0274: 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_02b9: Expected O, but got Unknown //IL_02d3: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Unknown result type (might be due to invalid IL or missing references) //IL_02f0: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)merchandise.Mesh) || merchandise.Mesh.vertexCount == 0) { throw new InvalidOperationException("Item has no usable physical shop-preview mesh."); } MeshRenderer val = ((IEnumerable)((Component)merchandise).GetComponentsInChildren(true)).FirstOrDefault((Func)delegate(MeshRenderer renderer) { MeshFilter component2 = ((Component)renderer).GetComponent(); return (Object)(object)((component2 != null) ? component2.sharedMesh : null) == (Object)(object)merchandise.Mesh; }); GameObject val2 = new GameObject("ExpansionKit item display"); val2.SetActive(false); val2.transform.SetParent(parent, false); try { GameObject val3 = new GameObject("Preview", new Type[2] { typeof(MeshFilter), typeof(MeshRenderer) }); val3.transform.SetParent(val2.transform, false); val3.GetComponent().sharedMesh = merchandise.Mesh; MeshRenderer component = val3.GetComponent(); bool flag = NativeContentDirectory.ApplyItemPreview((Renderer)(object)component, merchandise); if (flag) { val3.AddComponent(); } else { if (!Object.op_Implicit((Object)(object)val)) { throw new InvalidOperationException("The registered item has no usable shop-preview palette."); } ((Renderer)component).sharedMaterials = ((Renderer)val).sharedMaterials; } val3.transform.localRotation = (flag ? Quaternion.identity : Quaternion.Euler(-90f, 0f, 0f)); Bounds bounds = merchandise.Mesh.bounds; float num = Mathf.Max(new float[3] { ((Bounds)(ref bounds)).size.x, ((Bounds)(ref bounds)).size.y, ((Bounds)(ref bounds)).size.z }); if (!WorldFields.Finite(num) || num <= 0f) { throw new InvalidOperationException("Invalid preview dimensions."); } float num2 = (flag ? Mathf.Min(1f, 1.1f / num) : (1.1f / num)); val3.transform.localScale = Vector3.one * num2; val3.transform.localPosition = -(val3.transform.localRotation * ((Bounds)(ref bounds)).center * num2); BoxCollider val4 = val2.AddComponent(); Bounds val5 = NativeSceneMeshes.TransformBounds(bounds, Matrix4x4.TRS(val3.transform.localPosition, val3.transform.localRotation, val3.transform.localScale)); val4.center = ((Bounds)(ref val5)).center; val4.size = ((Bounds)(ref val5)).size + Vector3.one * 0.1f; val2.layer = LayerMask.NameToLayer("Interactable"); if (val2.layer < 0) { throw new InvalidOperationException("Native interaction layer is missing."); } val2.tag = "Interactable"; GameObject val6 = new GameObject("TextTarget"); val6.transform.SetParent(val2.transform, false); val6.transform.localPosition = Vector3.up * (val4.size.y * 0.5f + 0.15f); ItemPurchasable val7 = val2.AddComponent(); WorldFields.Set(val7, "_itemToPurchase", merchandise); WorldFields.Set(val7, "_modelsToOutline", new GameObject[1] { val3 }); WorldFields.Set(val7, "_interactCol", val4); WorldFields.Set(val7, "_textTarget", val6.transform); return val7; } catch { Object.Destroy((Object)(object)val2); throw; } } private static void ReplaceLurePreview(Purchasable stand, BaitInfo bait) { if (!Object.op_Implicit((Object)(object)bait.MeshForNpc)) { throw new InvalidOperationException("Registered lure has no physical reward/display mesh."); } MeshFilter[] array = WorldFields.Get(stand, "_modelsToOutline").SelectMany((GameObject model) => model.GetComponentsInChildren(true)).Distinct() .ToArray(); if (array.Length != 1) { throw new InvalidOperationException("A replacement lure requires a single-mesh native display donor."); } array[0].sharedMesh = bait.MeshForNpc; } private static void ValidateStand(Purchasable stand) { Collider val = WorldFields.Get(stand, "_interactCol"); GameObject[] array = WorldFields.Get(stand, "_modelsToOutline"); if (!Object.op_Implicit((Object)(object)val) || !((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))) { throw new InvalidOperationException("Native shelf has external or missing interaction, outline or text references."); } } private static void SeatOnSurface(Purchasable stand, Transform parent, float height) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0141: 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_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: 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_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) MeshFilter[] array = (from mesh in WorldFields.Get(stand, "_modelsToOutline").SelectMany((GameObject model) => model.GetComponentsInChildren(true)) where Object.op_Implicit((Object)(object)mesh.sharedMesh) select mesh).Distinct().ToArray(); if (array.Length == 0) { throw new InvalidOperationException("Native display has no measurable mesh."); } float num = float.PositiveInfinity; MeshFilter[] array2 = array; foreach (MeshFilter val in array2) { Bounds bounds = val.sharedMesh.bounds; Matrix4x4 val2 = parent.worldToLocalMatrix * ((Component)val).transform.localToWorldMatrix; for (int num3 = 0; num3 < 8; num3++) { Vector3 val3 = ((Bounds)(ref bounds)).center + Vector3.Scale(((Bounds)(ref bounds)).extents, new Vector3((float)(((num3 & 1) != 0) ? 1 : (-1)), (float)(((num3 & 2) != 0) ? 1 : (-1)), (float)(((num3 & 4) != 0) ? 1 : (-1)))); num = Mathf.Min(num, ((Matrix4x4)(ref val2)).MultiplyPoint3x4(val3).y); } } if (!WorldFields.Finite(num)) { throw new InvalidOperationException("Invalid native display bounds."); } Transform transform = ((Component)stand).transform; transform.localPosition += Vector3.up * (height - num); } } internal sealed class ShopPreviewLifetime : MonoBehaviour { private void OnDestroy() { NativeContentDirectory.RestoreItemPreview(((Component)this).GetComponent()); } } public sealed class PackPurchaseOffer { public string Key { get; } public string Title { get; } public int Cost { get; } public string Description { get; } public string UnavailableReason { get; } public bool Available => UnavailableReason.Length == 0; public PackPurchaseOffer(string key, string title, int cost, string description, string unavailableReason = "") { if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(title) || cost < 0 || description == null || unavailableReason == null) { throw new ArgumentException("A purchase offer needs an identity, title, non-negative cost and display text."); } Key = key; Title = title; Cost = cost; Description = description; UnavailableReason = unavailableReason; } } public sealed class PackPurchaseControl : Purchasable { private Func? quote; private Action? purchase; private PackPurchaseOffer? displayed; private bool registered; public override bool InteractableWhenHoldingItem => true; public static PackPurchaseControl Create(Transform parent, string name, Vector3 position, Vector3 size, GameObject[] outlines, Func quote, Action purchase) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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) //IL_0056: 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_0075: Expected O, but got Unknown //IL_008f: 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) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Expected O, but got Unknown //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: 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) if (!Object.op_Implicit((Object)(object)parent) || ((Component)parent).gameObject.activeInHierarchy || !NativeAccess.Positive(size.x) || !NativeAccess.Positive(size.y) || !NativeAccess.Positive(size.z) || !NativeAccess.Finite(position.x) || !NativeAccess.Finite(position.y) || !NativeAccess.Finite(position.z)) { throw new ArgumentException("Configure purchase controls under an inactive owned root with finite positive bounds."); } GameObject val = new GameObject(name); val.SetActive(false); val.transform.SetParent(parent, false); val.transform.localPosition = position; val.layer = LayerMask.NameToLayer("Interactable"); if (val.layer < 0) { throw new InvalidOperationException("The native interaction layer is unavailable."); } val.tag = "Interactable"; BoxCollider val2 = val.AddComponent(); val2.size = size; ((Collider)val2).isTrigger = true; GameObject val3 = new GameObject("Price label"); val3.transform.SetParent(val.transform, false); val3.transform.localPosition = Vector3.up * (size.y * 0.5f + 0.15f); PackPurchaseControl packPurchaseControl = val.AddComponent(); packPurchaseControl.quote = quote ?? throw new ArgumentNullException("quote"); packPurchaseControl.purchase = purchase ?? throw new ArgumentNullException("purchase"); NativeAccess.Set(packPurchaseControl, "_interactCol", val2); NativeAccess.Set(packPurchaseControl, "_textTarget", val3.transform); NativeAccess.Set(packPurchaseControl, "_modelsToOutline", outlines ?? throw new ArgumentNullException("outlines")); val.SetActive(true); return packPurchaseControl; } protected override void Awake() { if (quote == null || purchase == null) { throw new InvalidOperationException("A purchase control was activated before configuration."); } ((Interactable)this).Awake(); registered = true; } public override void Hover() { if (Object.op_Implicit((Object)(object)Player.LocalPlayer)) { displayed = quote(Player.LocalPlayer) ?? throw new InvalidOperationException("The equipment quote is missing."); base._customCost = displayed.Cost; base._customCanBuy = displayed.Available; base._hoverString = displayed.Title + "\n$" + displayed.Cost + "\n" + displayed.Description + "\n" + (displayed.Available ? SpriteManager.GetPickUpInput() : displayed.UnavailableReason); ((Purchasable)this).Hover(); } } public override void Interact(Player player) { if (!Object.op_Implicit((Object)(object)player) || (Object)(object)player != (Object)(object)Player.LocalPlayer || displayed == null) { throw new InvalidOperationException("Purchases require a locally displayed offer."); } ((Interactable)this).Interact(player); if (!displayed.Available || !MoneyManager.CanAfford(displayed.Cost)) { ((Purchasable)this).CantBuyEffects(); } else { purchase(player, displayed.Key); } } protected override void OnDestroy() { if (registered) { ((Purchasable)this).OnDestroy(); } } } } namespace HowToFish.ExpansionKit.Runtime.Rendering { public sealed class NativeSceneMaterials : IDisposable { private readonly Dictionary copies = new Dictionary(); public void Apply(GameObject root) { if (!Object.op_Implicit((Object)(object)root)) { throw new ArgumentNullException("root"); } Shader lit = Shader.Find("Universal Render Pipeline/Lit"); if (!Object.op_Implicit((Object)(object)lit) || !lit.isSupported) { throw new InvalidOperationException("The game's lit scene shader is unavailable."); } Renderer[] componentsInChildren = root.GetComponentsInChildren(true); foreach (Renderer obj in componentsInChildren) { obj.sharedMaterials = obj.sharedMaterials.Select((Material original) => Convert(original, lit)).ToArray(); } } private Material Convert(Material original, Shader lit) { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)original) || !Object.op_Implicit((Object)(object)original.shader)) { throw new InvalidDataException("The authored scene has a missing material."); } if (((Object)original.shader).name != "Standard") { if (!original.shader.isSupported) { throw new InvalidDataException("Unsupported authored scene shader: " + ((Object)original.shader).name); } return original; } if (copies.TryGetValue(original, out Material value)) { return value; } if (!original.HasProperty("_Color") || (original.HasProperty("_Mode") && original.GetFloat("_Mode") != 0f)) { throw new InvalidDataException("The native Standard-material adapter requires an opaque color material: " + ((Object)original).name); } value = new Material(lit) { name = ((Object)original).name + " - game lighting" }; value.SetColor("_BaseColor", original.GetColor("_Color")); value.SetFloat("_Metallic", original.HasProperty("_Metallic") ? original.GetFloat("_Metallic") : 0f); value.SetFloat("_Smoothness", original.HasProperty("_Glossiness") ? original.GetFloat("_Glossiness") : 0.15f); if (original.HasProperty("_MainTex") && Object.op_Implicit((Object)(object)original.GetTexture("_MainTex"))) { value.SetTexture("_BaseMap", original.GetTexture("_MainTex")); value.SetTextureScale("_BaseMap", original.GetTextureScale("_MainTex")); value.SetTextureOffset("_BaseMap", original.GetTextureOffset("_MainTex")); } copies.Add(original, value); return value; } public void Dispose() { foreach (Material value in copies.Values) { if (Object.op_Implicit((Object)(object)value)) { Object.Destroy((Object)(object)value); } } copies.Clear(); } } } namespace HowToFish.ExpansionKit.Runtime.Persistence { internal static class AutomaticCheckpointScope { [ThreadStatic] private static int depth; internal static bool Active => depth != 0; internal static IEnumerator Wrap(IEnumerator routine) { if (routine == null) { throw new ArgumentNullException("routine"); } try { while (true) { checked { depth++; } bool flag; try { flag = routine.MoveNext(); } finally { depth--; } if (flag) { yield return routine.Current; continue; } break; } } finally { (routine as IDisposable)?.Dispose(); } } } public static class ExpansionSaveProfiles { private static readonly ManualLogSource Log = Logger.CreateLogSource("ExpansionKit.Saves"); private static string? overrideFolder; private static bool initialized; public static string Folder { get; private set; } = ""; public static void ConfigureIsolatedFolder(string folder) { if (initialized) { throw new InvalidOperationException("Choose the isolated profile before native save initialization."); } if (string.IsNullOrWhiteSpace(folder) || !Path.IsPathRooted(folder)) { throw new ArgumentException("An isolated save profile requires an absolute path.", "folder"); } string fullPath = Path.GetFullPath(folder); if (overrideFolder != null && !string.Equals(overrideFolder, fullPath, StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("Two different isolated save profiles were requested."); } overrideFolder = fullPath; } public static void Initialize() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (initialized) { return; } string text = null; if (overrideFolder != null) { Folder = overrideFolder; } else { text = SteamUser.GetSteamID().m_SteamID.ToString(CultureInfo.InvariantCulture); if (text == "0") { throw new InvalidOperationException("Steam identity is not ready; no save profile was opened."); } Folder = Path.Combine(Application.persistentDataPath, "ExpansionKitProfiles", text); } Directory.CreateDirectory(Folder); string text2 = Path.Combine(Folder, "local.txt"); AccessTools.Field(typeof(SaveSystem), "_saveFolder").SetValue(null, Folder); AccessTools.Field(typeof(SaveSystem), "_fullLocalSavePath").SetValue(null, text2); AccessTools.Field(typeof(SaveSystem), "_localBackupPath").SetValue(null, text2 + ".backup"); AccessTools.Field(typeof(SaveSystem), "_localTempPath").SetValue(null, text2 + ".tmp"); if (text != null) { ImportOriginalsOnce(text); } initialized = true; Log.LogInfo((object)("Using the isolated expansion save profile: " + Folder)); } private static void ImportOriginalsOnce(string user) { string path = Path.Combine(Folder, "import-complete.marker"); if (File.Exists(path)) { return; } int num = 0; string[] array = new string[2] { Path.Combine(Application.persistentDataPath, "GamblersReachProfiles", user), Path.Combine(Application.persistentDataPath, "Saves", user) }; foreach (string path2 in array) { if (!Directory.Exists(path2)) { continue; } foreach (string item in Directory.EnumerateFiles(path2)) { string fileName = Path.GetFileName(item); string extension = Path.GetExtension(item); if (!(extension != ".txt") || !(extension != ".backup") || (fileName.StartsWith("reach-", StringComparison.Ordinal) && extension == ".json")) { string text = Path.Combine(Folder, fileName); if (!File.Exists(text)) { File.Copy(item, text, overwrite: false); num++; } } } } File.WriteAllText(path, "Existing saves were copied without changing their originals. ExpansionKit saves remain separate.\n"); Log.LogInfo((object)$"Copied {num} existing save files to the shared expansion profile. Originals were not modified."); } } [HarmonyPatch(typeof(SaveSystem), "Init")] internal static class InitializeExpansionSaveProfile { [HarmonyPriority(800)] private static bool Prefix() { ExpansionSaveProfiles.Initialize(); return false; } } public sealed class NativeSaveBridge : IDisposable { internal sealed class WriteReceipt { internal string Name { get; } internal JObject? Envelope { get; } internal bool Recovered { get; } internal string? ExactJson { get; } internal WriteReceipt(string name, JObject? envelope, bool recovered, string? exactJson = null) { Name = name; Envelope = envelope; Recovered = recovered; ExactJson = exactJson; } } private sealed class Participant { internal Func Capture { get; } internal Action Restore { get; } internal Participant(Func capture, Action restore) { Capture = capture; Restore = restore; } } public const string PropertyName = "ExpansionKit"; private const int MaximumEnvelopeBytes = 524288; private static readonly Func nativeCanSaveWorldItem = (Func)Delegate.CreateDelegate(typeof(Func), AccessTools.Method(typeof(SaveManager), "CanSaveWorldItem", (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(SaveManager).FullName, "CanSaveWorldItem")); private static readonly Dictionary Loaded = new Dictionary(StringComparer.Ordinal); private static readonly HashSet DuplicateNames = new HashSet(StringComparer.Ordinal); private readonly Dictionary participants = new Dictionary(StringComparer.Ordinal); private readonly Func captureRegistry; private readonly Action validateRegistry; private readonly Action validateNativeIdentities; private readonly Action reportFailure; private string selectedName = ""; private long revision; private string? replayJson; [ThreadStatic] private static int moduleCheckpointDepth; internal static NativeSaveBridge? Current { get; private set; } public long VerifiedWrites { get; private set; } public bool Ready { get; set; } public static bool IsModuleCheckpoint => moduleCheckpointDepth > 0; public static bool IsAutomaticCheckpoint => AutomaticCheckpointScope.Active; public Exception? Failure { get; private set; } public event Action? SaveSelected; public static bool IsWorldItemSaveEligible(Item item) { return nativeCanSaveWorldItem(item); } public NativeSaveBridge(Func captureRegistry, Action validateRegistry, Action validateNativeIdentities, Action reportFailure) { if (Current != null) { throw new InvalidOperationException("Another native pack checkpoint bridge is active."); } this.captureRegistry = captureRegistry ?? throw new ArgumentNullException("captureRegistry"); this.validateRegistry = validateRegistry ?? throw new ArgumentNullException("validateRegistry"); this.validateNativeIdentities = validateNativeIdentities ?? throw new ArgumentNullException("validateNativeIdentities"); this.reportFailure = reportFailure ?? throw new ArgumentNullException("reportFailure"); Current = this; } public void RegisterParticipant(string key, Func capture, Action restore) { if (Ready) { throw new InvalidOperationException("Register save participants before opening crew saves."); } if (string.IsNullOrWhiteSpace(key) || key.Length > 128 || participants.ContainsKey(key)) { throw new ArgumentException("A save participant needs a unique, bounded key.", "key"); } participants.Add(key, new Participant(capture ?? throw new ArgumentNullException("capture"), restore ?? throw new ArgumentNullException("restore"))); } internal static List RecordLoaded(IEnumerable saves) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Invalid comparison between Unknown and I4 Loaded.Clear(); DuplicateNames.Clear(); foreach (string safe in saves) { try { JObject obj = Parse(safe); JToken obj2 = obj["Name"]; if (obj2 == null || (int)obj2.Type != 8) { throw new InvalidDataException("A native crew save is missing its text name."); } string text = (string)obj["Name"]; ValidateName(text); if (!DuplicateNames.Contains(text)) { if (Loaded.ContainsKey(text)) { Loaded.Remove(text); DuplicateNames.Add(text); throw new InvalidDataException("Duplicate native crew save name: " + text); } Loaded.Add(text, safe); } } catch (Exception ex) when (ex is JsonException || ex is InvalidDataException) { ExpansionKitPlugin.Log.LogWarning((object)("A crew entry was excluded from the save list: " + ex.Message)); } } return Loaded.Values.ToList(); } internal bool Select(string name, bool create) { try { if (!Ready) { throw new InvalidOperationException("Expansion packs are still preparing. Wait before selecting a crew save."); } ValidateName(name); if (DuplicateNames.Contains(name)) { throw new InvalidDataException("Duplicate native crew save name: " + name); } JObject val = null; if (!create) { val = Parse(Loaded.TryGetValue(name, out string value) ? value : File.ReadAllText(NativePath(name))); if ((string)val["Name"] != name) { throw new InvalidDataException("The native crew save name does not match its selected profile."); } validateNativeIdentities(val); } JToken obj = ((val != null) ? val["ExpansionKit"] : null); JObject val2 = (JObject)(object)((obj is JObject) ? obj : null); if (((val != null) ? val["ExpansionKit"] : null) != null && val2 == null) { throw new InvalidDataException("The expansion checkpoint has an invalid shape."); } if (val2 != null) { ValidateEnvelope(val2); } selectedName = name; Restore(val2); Failure = null; this.SaveSelected?.Invoke((val == null) ? 2 : (((int?)val["MaxIsland"]) ?? throw new InvalidDataException("The native crew save has no progression count."))); return true; } catch (Exception ex) when (ex is IOException || ex is InvalidDataException || ex is UnauthorizedAccessException || ex is JsonException || ex is InvalidOperationException || ex is ArgumentException || ex is FormatException || ex is OverflowException) { selectedName = ""; AccessTools.Field(typeof(SaveManager), "k__BackingField").SetValue(null, null); reportFailure(ex); return false; } } internal WriteReceipt? BeforeWrite(string name, ref string json) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Expected O, but got Unknown //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0156: 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_017a: Expected O, but got Unknown if (name != selectedName || !Ready) { return null; } if (Failure != null) { throw new InvalidOperationException("Reload the verified crew checkpoint before saving again.", Failure); } JObject val = Parse(json); if (replayJson != null) { if (!string.Equals(json, replayJson, StringComparison.Ordinal)) { throw new InvalidDataException("Another write changed the prepared native checkpoint during replay."); } JObject envelope = ReadEnvelope(val); return new WriteReceipt(name, envelope, recovered: true, json); } JToken val2 = default(JToken); bool flag = val.TryGetValue("ExpansionKit", ref val2); JObject val3; if (flag) { val3 = (JObject)(object)(((val2 is JObject) ? val2 : null) ?? throw new InvalidDataException("Invalid prepared expansion checkpoint.")); ValidateEnvelope(val3); } else { JObject val4 = new JObject(); foreach (KeyValuePair item in participants.OrderBy, string>((KeyValuePair pair) => pair.Key, StringComparer.Ordinal)) { val4.Add(item.Key, item.Value.Capture().DeepClone()); } val3 = new JObject { ["schemaVersion"] = JToken.op_Implicit(1), ["revision"] = JToken.op_Implicit(checked(revision + 1)), ["registry"] = (JToken)(object)captureRegistry(), ["modules"] = (JToken)(object)val4 }; ValidateEnvelope(val3); val.Add("ExpansionKit", (JToken)(object)val3); json = ((JToken)val).ToString((Formatting)0, Array.Empty()); } return new WriteReceipt(name, (JObject)((JToken)val3).DeepClone(), flag); } internal void AfterWrite(WriteReceipt? receipt) { if (receipt != null) { string text = File.ReadAllText(NativePath(receipt.Name)); JObject val = Parse(text); if ((receipt.ExactJson != null) ? (!string.Equals(text, receipt.ExactJson, StringComparison.Ordinal)) : (!JToken.DeepEquals(val["ExpansionKit"], (JToken)(object)receipt.Envelope))) { throw new IOException("The native save did not persist the expansion checkpoint. Crew progress has not been committed."); } Loaded[receipt.Name] = text; revision = ((receipt.Envelope == null) ? 0 : ((long)receipt.Envelope["revision"])); if (receipt.Recovered && receipt.ExactJson == null) { Restore(receipt.Envelope); } VerifiedWrites++; } } public ServerSaveObject RecoverPreparedCheckpoint(string name, string json, string beforeHash, string afterHash) { try { return RecoverPrepared(name, json, beforeHash, afterHash); } catch (Exception error) { Fail(error); throw; } } private ServerSaveObject RecoverPrepared(string name, string json, string beforeHash, string afterHash) { //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Invalid comparison between Unknown and I4 //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_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Expected O, but got Unknown //IL_01c8: Unknown result type (might be due to invalid IL or missing references) if (!Ready || Failure != null || name != selectedName || SaveManager.CurServerSave?.Name != name || replayJson != null || PlayerManager.Players.Count != 0 || (Object.op_Implicit((Object)(object)Server.Instance) && ((NetworkBehaviour)Server.Instance).IsServerInitialized)) { throw new InvalidOperationException("Prepared checkpoint recovery requires the selected healthy crew before gameplay."); } string path = NativePath(name); string text = (File.Exists(path) ? ReadBounded(path) : null); bool flag = PreparedCheckpointReplay.Validate(json, beforeHash, afterHash, (text == null) ? "" : PreparedCheckpointReplay.Hash(text)); JObject val = Parse(json); if ((string)val["Name"] != name) { throw new InvalidDataException("The prepared native checkpoint belongs to a different crew."); } if (val["Players"] is JArray && val["WorldItems"] is JArray) { JToken obj = val["MaxIsland"]; if (obj != null && (int)obj.Type == 6) { validateNativeIdentities(val); JObject envelope = ReadEnvelope(val); if (text != null) { JObject val2 = Parse(text); if ((string)val2["Name"] != name) { throw new InvalidDataException("The native checkpoint on disk belongs to a different crew."); } validateNativeIdentities(val2); JObject val3 = ReadEnvelope(val2); if (val3 != null && (envelope == null || (long)envelope["revision"] < (long)val3["revision"] || ((JObject)val3["modules"]).Properties().Any((JProperty module) => envelope["modules"][(object)module.Name] == null))) { throw new InvalidDataException("Prepared recovery cannot discard or roll back existing expansion metadata."); } } val.Remove("ExpansionKit"); ServerSaveObject val4 = ((JToken)val).ToObject(JsonSerializer.Create(new JsonSerializerSettings { TypeNameHandling = (TypeNameHandling)0, MetadataPropertyHandling = (MetadataPropertyHandling)2, MissingMemberHandling = (MissingMemberHandling)1, MaxDepth = 48 })) ?? throw new InvalidDataException("The prepared native checkpoint is empty."); if (val4.Players == null || val4.WorldItems == null || val4.MaxIsland < 1) { throw new InvalidDataException("The prepared native checkpoint has incomplete native state."); } Restore(envelope); try { if (!flag) { long verifiedWrites = VerifiedWrites; replayJson = json; try { SaveSystem.SaveServer(name, json); } finally { replayJson = null; } if (VerifiedWrites == verifiedWrites || !string.Equals(ReadBounded(path), json, StringComparison.Ordinal)) { throw new IOException("The prepared native checkpoint did not reach disk unchanged."); } } else { Loaded[name] = json; } this.SaveSelected?.Invoke(val4.MaxIsland); return val4; } catch (Exception error) { Fail(error); throw; } } } throw new InvalidDataException("The prepared native checkpoint has incomplete native state."); } private JObject? ReadEnvelope(JObject root) { JToken val = default(JToken); if (!root.TryGetValue("ExpansionKit", ref val)) { return null; } JObject val2 = (JObject)(object)(((val is JObject) ? val : null) ?? throw new InvalidDataException("The expansion checkpoint has an invalid shape.")); ValidateEnvelope(val2); return val2; } private static string ReadBounded(string path) { if (new FileInfo(path).Length > 4194304) { throw new InvalidDataException("The prepared native checkpoint exceeds its safety limit."); } return File.ReadAllText(path, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true)); } public void Checkpoint() { if (!Ready || selectedName.Length == 0 || SaveManager.CurServerSave?.Name != selectedName || !Object.op_Implicit((Object)(object)Server.Instance) || !((NetworkBehaviour)Server.Instance).IsServerInitialized) { throw new InvalidOperationException("A shared checkpoint needs a selected, authoritative crew."); } long verifiedWrites = VerifiedWrites; moduleCheckpointDepth++; try { SaveManager.SaveServer(true); } finally { moduleCheckpointDepth--; } if (VerifiedWrites == verifiedWrites) { throw new IOException("The native checkpoint was cancelled or failed to reach disk."); } } private void ValidateEnvelope(JObject envelope) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Invalid comparison between Unknown and I4 if ((int?)envelope["schemaVersion"] == 1 && !((long?)envelope["revision"] < 0)) { JToken obj = envelope["revision"]; if (obj != null && (int)obj.Type == 6) { JToken obj2 = envelope["registry"]; JObject val = (JObject)(object)((obj2 is JObject) ? obj2 : null); if (val != null) { JToken obj3 = envelope["modules"]; JObject val2 = (JObject)(object)((obj3 is JObject) ? obj3 : null); if (val2 != null) { if (Encoding.UTF8.GetByteCount(((JToken)envelope).ToString((Formatting)0, Array.Empty())) > 524288) { throw new InvalidDataException("The expansion save metadata exceeds its bounded size."); } validateRegistry(val); { foreach (JProperty item in val2.Properties()) { if (!participants.ContainsKey(item.Name)) { throw new InvalidDataException("A required save participant is unavailable: " + item.Name); } } return; } } } } } throw new InvalidDataException("Unsupported or invalid expansion save metadata."); } private void Restore(JObject? envelope) { revision = ((envelope == null) ? 0 : ((long)envelope["revision"])); JToken obj = ((envelope != null) ? envelope["modules"] : null); JObject val = (JObject)(object)((obj is JObject) ? obj : null); foreach (KeyValuePair item in participants.OrderBy, string>((KeyValuePair pair) => pair.Key, StringComparer.Ordinal)) { Action restore = item.Value.Restore; object obj2; if (val == null) { obj2 = null; } else { JToken obj3 = val[item.Key]; obj2 = ((obj3 != null) ? obj3.DeepClone() : null); } restore((JToken)obj2); } } internal void Fail(Exception error) { if (Failure == null) { Failure = error; reportFailure(error); } } private static JObject Parse(string json) { //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_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown return JObject.Parse(json, new JsonLoadSettings { DuplicatePropertyNameHandling = (DuplicatePropertyNameHandling)2, CommentHandling = (CommentHandling)0 }); } private static void ValidateName(string name) { if (string.IsNullOrWhiteSpace(name) || name.Length > 128 || name != Path.GetFileName(name) || name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 || name == "." || name == "..") { throw new InvalidDataException("The crew save name is not a safe local filename."); } } private static string NativePath(string name) { ValidateName(name); return Path.Combine((AccessTools.Field(typeof(SaveSystem), "_saveFolder").GetValue(null) as string) ?? throw new InvalidOperationException("The native save profile has not been initialized."), name + ".txt"); } public void Dispose() { if (Current == this) { Current = null; } } } [HarmonyPatch(typeof(SaveSystem), "LoadAllServers")] internal static class ReadNativePackCheckpoints { private static void Postfix(ref List __result) { __result = NativeSaveBridge.RecordLoaded(__result); } } [HarmonyPatch(typeof(AutoSaver), "AutoSave")] internal static class IdentifyNativeAutomaticCheckpoint { private static void Postfix(ref IEnumerator __result) { __result = AutomaticCheckpointScope.Wrap(__result); } } [HarmonyPatch(typeof(Creature), "LoadFromSave")] internal static class PreserveSavedCreatureWeight { private static void Postfix(Creature __instance, SavedItem savedItem) { NativeSaveBridge? current = NativeSaveBridge.Current; if (current != null && current.Ready && savedItem.Weight == 1f) { NativeAccess.Set(__instance, "_skipRandomizedWeight", true); } } } [HarmonyPatch(typeof(SaveManager), "SelectServer")] internal static class SelectNativePackCheckpoint { [HarmonyPriority(800)] private static bool Prefix(string name) { return NativeSaveBridge.Current?.Select(name, create: false) ?? true; } } [HarmonyPatch(typeof(SaveManager), "CreateServer")] internal static class CreateNativePackCheckpoint { [HarmonyPriority(800)] private static bool Prefix(string serverName) { return NativeSaveBridge.Current?.Select(serverName, create: true) ?? true; } } [HarmonyPatch(typeof(SaveSystem), "SaveServer")] internal static class WriteNativePackCheckpoint { [HarmonyPriority(800)] private static void Prefix(string name, ref string saveString, out NativeSaveBridge.WriteReceipt? __state) { __state = NativeSaveBridge.Current?.BeforeWrite(name, ref saveString); } [HarmonyPriority(0)] private static void Postfix(NativeSaveBridge.WriteReceipt? __state) { NativeSaveBridge.Current?.AfterWrite(__state); } private static Exception? Finalizer(Exception? __exception) { if (__exception != null) { NativeSaveBridge.Current?.Fail(__exception); } return __exception; } } internal static class PreparedCheckpointReplay { internal const int MaximumBytes = 4194304; internal static bool Validate(string json, string beforeHash, string afterHash, string currentHash) { if (json == null || Encoding.UTF8.GetByteCount(json) > 4194304 || !IsHash(afterHash) || (beforeHash != "" && !IsHash(beforeHash)) || (currentHash != "" && !IsHash(currentHash)) || Hash(json) != afterHash) { throw new InvalidDataException("The prepared native checkpoint has invalid bytes or hashes."); } if (currentHash != beforeHash && currentHash != afterHash) { throw new InvalidDataException("The journal and native save are from different checkpoints. Restore their matching backups."); } return currentHash == afterHash; } internal static string Hash(string json) { using SHA256 sHA = SHA256.Create(); return BitConverter.ToString(sHA.ComputeHash(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetBytes(json))).Replace("-", "").ToLowerInvariant(); } private static bool IsHash(string value) { if (value != null && value.Length == 64) { return value.All((char character) => (character >= '0' && character <= '9') || (character >= 'a' && character <= 'f')); } return false; } } } namespace HowToFish.ExpansionKit.Runtime.Networking { public readonly struct PackCommandResult { public bool Accepted { get; } public string Message { get; } private PackCommandResult(bool accepted, string message) { Accepted = accepted; Message = message; } public static PackCommandResult Done() { return new PackCommandResult(accepted: true, ""); } public static PackCommandResult Reject(string message) { if (string.IsNullOrWhiteSpace(message)) { throw new ArgumentException("A rejected action needs an explanation."); } return new PackCommandResult(accepted: false, message); } } public sealed class PackChannel : IDisposable { private readonly PackChannels owner; internal uint StateSequence; internal uint ReceivedSequence; internal bool Disposed; internal Func Handle { get; } internal Func Capture { get; } internal Action Receive { get; } public string Key { get; } public event Action? Rejected; internal PackChannel(PackChannels owner, string key, Func handle, Func capture, Action receive) { this.owner = owner; Key = key; Handle = handle; Capture = capture; Receive = receive; } public void Send(string verb, string payload = "") { owner.Send(this, verb, payload); } public void Publish() { owner.Publish(this); } internal void Reject(string message) { this.Rejected?.Invoke(message); } public void Dispose() { if (!Disposed) { Disposed = true; owner.Remove(this); this.Rejected = null; } } } internal enum ChannelMessage : byte { Action, State, Rejected } internal struct ChannelPacket : IBroadcast { internal ChannelMessage Kind; internal uint Sequence; internal string Channel; internal string Verb; internal string Payload; } public sealed class PackChannels : IDisposable { private const int MaximumPayload = 16000; private readonly NetworkManager manager; private readonly PackHandshake handshake; private readonly Func ready; private readonly Action fail; private readonly HashSet packs; private readonly Dictionary channels = new Dictionary(StringComparer.Ordinal); private readonly Dictionary<(int Client, string Channel), uint> requests = new Dictionary<(int, string), uint>(); private readonly Dictionary nextRequest = new Dictionary(); private uint sequence; private bool disposed; public PackChannels(NetworkManager manager, PackHandshake handshake, IEnumerable packKeys, Func ready, Action fail) { if (!Object.op_Implicit((Object)(object)manager)) { throw new ArgumentNullException("manager"); } this.manager = manager; this.handshake = handshake ?? throw new ArgumentNullException("handshake"); this.ready = ready ?? throw new ArgumentNullException("ready"); this.fail = fail ?? throw new ArgumentNullException("fail"); packs = new HashSet(packKeys ?? throw new ArgumentNullException("packKeys"), StringComparer.Ordinal); if (manager.IsClientStarted || manager.IsServerStarted) { throw new InvalidOperationException("Register pack channels before networking starts."); } GenericWriter.SetWrite((Action)Write); GenericReader.SetRead((Func)Read); manager.ServerManager.RegisterBroadcast((Action)OnRequest, true); manager.ClientManager.RegisterBroadcast((Action)OnResponse); manager.ClientManager.OnClientConnectionState += OnLocalState; manager.ServerManager.OnRemoteConnectionState += OnRemoteState; handshake.StateRequested += SendSnapshot; } public PackChannel Register(string packKey, string key, Func handle, Func capture, Action receive) { if (disposed) { throw new ObjectDisposedException("PackChannels"); } if (!packs.Contains(packKey) || !ContentReference.IsKey(key)) { throw new ArgumentException("A channel needs a registered pack and valid local key."); } if (ready() || manager.IsClientStarted || manager.IsServerStarted) { throw new InvalidOperationException("Register channels during pack preparation."); } string text = packKey + ":" + key; if (channels.ContainsKey(text)) { throw new InvalidOperationException("Duplicate pack channel: " + text); } if (channels.Count >= 128) { throw new InvalidOperationException("The pack channel limit is 128."); } PackChannel packChannel = new PackChannel(this, text, handle ?? throw new ArgumentNullException("handle"), capture ?? throw new ArgumentNullException("capture"), receive ?? throw new ArgumentNullException("receive")); channels.Add(text, packChannel); return packChannel; } internal void Send(PackChannel channel, string verb, string payload) { Require(channel); if (!ready() || !handshake.ClientConfirmed) { throw new InvalidOperationException("Wait for the host to accept the pack set."); } if (!ContentReference.IsKey(verb) || verb.Length > 32) { throw new ArgumentException("Invalid pack action.", "verb"); } ValidateText(payload, 16000); manager.ClientManager.Broadcast(new ChannelPacket { Kind = ChannelMessage.Action, Sequence = checked(++sequence), Channel = channel.Key, Verb = verb, Payload = payload }, (Channel)0); } private void OnRequest(NetworkConnection connection, ChannelPacket packet, Channel transport) { if (packet.Kind != ChannelMessage.Action || !handshake.IsConfirmed(connection) || !ready()) { Reject(connection, packet, "This action requires a ready, verified crew."); return; } if (!channels.TryGetValue(packet.Channel, out PackChannel value)) { Reject(connection, packet, "This pack action is unavailable."); return; } (int, string) key = (connection.ClientId, packet.Channel); if (packet.Sequence == 0 || (requests.TryGetValue(key, out var value2) && packet.Sequence <= value2)) { Reject(connection, packet, "This action has already been processed."); return; } requests[key] = packet.Sequence; float realtimeSinceStartup = Time.realtimeSinceStartup; if (nextRequest.TryGetValue(connection.ClientId, out var value3) && realtimeSinceStartup < value3) { Reject(connection, packet, "Wait for the previous action."); return; } nextRequest[connection.ClientId] = realtimeSinceStartup + 0.1f; Player val = ((IEnumerable)PlayerManager.Players).FirstOrDefault((Func)((Player val2) => Object.op_Implicit((Object)(object)val2) && ((NetworkBehaviour)val2).Owner == connection && ((NetworkBehaviour)val2).IsServerInitialized && !((NetworkBehaviour)val2).IsDeinitializing && !val2.Dying.IsDead && val2.Vitals.Health > 0)); if (!Object.op_Implicit((Object)(object)val)) { Reject(connection, packet, "A living crew member must operate this equipment."); return; } try { PackCommandResult packCommandResult = value.Handle(val, packet.Verb, packet.Payload); if (!packCommandResult.Accepted) { Reject(connection, packet, packCommandResult.Message); } Publish(value); } catch (Exception obj) { fail(obj); Reject(connection, packet, "The equipment stopped after an error. Reload the last verified save."); } } private void Reject(NetworkConnection connection, ChannelPacket request, string message) { ExpansionKitPlugin.Log.LogWarning((object)(request.Channel + ": " + message)); manager.ServerManager.Broadcast(connection, new ChannelPacket { Kind = ChannelMessage.Rejected, Sequence = request.Sequence, Channel = request.Channel, Verb = "", Payload = message }, true, (Channel)0); } internal void Publish(PackChannel channel) { Require(channel); if (!manager.IsServerStarted) { throw new InvalidOperationException("Only the host publishes pack state."); } string text = channel.Capture(); ValidateText(text, 16000); ChannelPacket channelPacket = new ChannelPacket { Kind = ChannelMessage.State, Sequence = checked(++channel.StateSequence), Channel = channel.Key, Verb = "", Payload = text }; foreach (NetworkConnection value in manager.ServerManager.Clients.Values) { if (value.IsActive && handshake.IsConfirmed(value)) { manager.ServerManager.Broadcast(value, channelPacket, true, (Channel)0); } } } private void SendSnapshot(NetworkConnection connection) { foreach (PackChannel value in channels.Values) { string text = value.Capture(); ValidateText(text, 16000); manager.ServerManager.Broadcast(connection, new ChannelPacket { Kind = ChannelMessage.State, Sequence = checked(++value.StateSequence), Channel = value.Key, Verb = "", Payload = text }, true, (Channel)0); } } private void OnResponse(ChannelPacket packet, Channel transport) { if (!handshake.ClientConfirmed || !channels.TryGetValue(packet.Channel, out PackChannel value)) { throw new InvalidDataException("State arrived for an unavailable or unverified pack channel."); } if (packet.Kind == ChannelMessage.Rejected) { value.Reject(packet.Payload); return; } if (packet.Kind != ChannelMessage.State) { throw new InvalidDataException("Unexpected host pack channel message."); } if (manager.IsServerStarted || packet.Sequence <= value.ReceivedSequence) { return; } try { value.Receive(packet.Payload); value.ReceivedSequence = packet.Sequence; } catch (Exception obj) { fail(obj); throw; } } private void OnLocalState(ClientConnectionStateArgs state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)state.ConnectionState != 1) { return; } sequence = 0u; foreach (PackChannel value in channels.Values) { value.ReceivedSequence = 0u; } } private void OnRemoteState(NetworkConnection connection, RemoteConnectionStateArgs state) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if ((int)state.ConnectionState == 0) { nextRequest.Remove(connection.ClientId); (int, string)[] array = requests.Keys.Where<(int, string)>(((int Client, string Channel) tuple) => tuple.Client == connection.ClientId).ToArray(); foreach ((int, string) key in array) { requests.Remove(key); } } } private void Require(PackChannel channel) { if (disposed || channel.Disposed || !channels.TryGetValue(channel.Key, out PackChannel value) || value != channel) { throw new ObjectDisposedException("PackChannel"); } } internal void Remove(PackChannel channel) { channels.Remove(channel.Key); } private static void ValidateText(string text, int limit) { if (text == null || Encoding.UTF8.GetByteCount(text) > limit) { throw new InvalidDataException("A pack channel field exceeds its size limit."); } } private static void Text(Writer writer, string text, int limit) { ValidateText(text, limit); byte[] bytes = Encoding.UTF8.GetBytes(text); writer.WriteUInt16Unpacked((ushort)bytes.Length); writer.WriteUInt8Array(bytes, 0, bytes.Length); } private static string Text(Reader reader, int limit) { if (reader.Remaining < 2) { throw new InvalidDataException("Truncated pack channel field."); } int num = reader.ReadUInt16Unpacked(); if (num > limit || num > reader.Remaining) { throw new InvalidDataException("Invalid pack channel field length."); } return new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(reader.ReadUInt8ArrayAllocated(num)); } private static void Write(Writer writer, ChannelPacket packet) { writer.WriteUInt8Unpacked((byte)packet.Kind); writer.WriteUInt32Unpacked(packet.Sequence); Text(writer, packet.Channel, 130); Text(writer, packet.Verb, 32); Text(writer, packet.Payload, 16000); } private static ChannelPacket Read(Reader reader) { if (reader.Remaining < 5) { throw new InvalidDataException("Truncated pack channel packet."); } byte b = reader.ReadUInt8Unpacked(); if (b > 2) { throw new InvalidDataException("Unknown pack channel packet."); } return new ChannelPacket { Kind = (ChannelMessage)b, Sequence = reader.ReadUInt32Unpacked(), Channel = Text(reader, 130), Verb = Text(reader, 32), Payload = Text(reader, 16000) }; } public void Dispose() { if (!disposed) { PackChannel[] array = channels.Values.ToArray(); for (int i = 0; i < array.Length; i++) { array[i].Dispose(); } disposed = true; handshake.StateRequested -= SendSnapshot; manager.ServerManager.UnregisterBroadcast((Action)OnRequest); manager.ClientManager.UnregisterBroadcast((Action)OnResponse); manager.ClientManager.OnClientConnectionState -= OnLocalState; manager.ServerManager.OnRemoteConnectionState -= OnRemoteState; } } } internal enum PackMessage : byte { Hello, Rejected, State, StateRequest } internal struct PackPacket : IBroadcast { internal PackMessage Kind; internal string Body; } public sealed class PackHandshake : IDisposable { private sealed class PendingAuthentication { internal SteamLobbyAuthenticator Native { get; } internal float Deadline { get; } internal PendingAuthentication(SteamLobbyAuthenticator native, float deadline) { Native = native; Deadline = deadline; } } private const int MaximumBodyBytes = 60000; private readonly NetworkManager manager; private readonly Action reportFailure; private readonly HashSet matched = new HashSet(); private readonly HashSet authenticated = new HashSet(); private readonly Dictionary pending = new Dictionary(); private bool disposed; internal static PackHandshake? Current { get; private set; } public string Fingerprint { get; } public bool ClientConfirmed { get; private set; } public event Action? PeerConfirmed; public event Action? StateRequested; public event Action? PeerRejected; public event Action? StateReceived; public PackHandshake(NetworkManager manager, string fingerprint, Action reportFailure) { if (!Object.op_Implicit((Object)(object)manager)) { throw new ArgumentNullException("manager"); } if (manager.IsClientStarted || manager.IsServerStarted) { throw new InvalidOperationException("Install pack authentication before starting a connection."); } if (Current != null) { throw new InvalidOperationException("A pack handshake is already installed."); } if (fingerprint == null || fingerprint.Length != 64 || fingerprint.Any((char character) => (character < '0' || character > '9') && (character < 'A' || character > 'F'))) { throw new ArgumentException("The pack fingerprint must be an uppercase SHA-256.", "fingerprint"); } this.manager = manager; this.reportFailure = reportFailure ?? throw new ArgumentNullException("reportFailure"); Fingerprint = fingerprint; GenericWriter.SetWrite((Action)Write); GenericReader.SetRead((Func)Read); manager.ServerManager.RegisterBroadcast((Action)OnServerPacket, false); manager.ClientManager.RegisterBroadcast((Action)OnClientPacket); manager.ServerManager.OnAuthenticationResult += OnAuthenticationResult; manager.ServerManager.OnRemoteConnectionState += OnRemoteState; manager.ClientManager.OnClientConnectionState += OnClientState; manager.ClientManager.OnAuthenticated += OnClientAuthenticated; Current = this; } public bool IsConfirmed(NetworkConnection connection) { return authenticated.Contains(connection); } internal bool BeginAuthentication(SteamLobbyAuthenticator native, NetworkConnection connection) { if (matched.Contains(connection)) { return true; } if (!pending.ContainsKey(connection)) { pending.Add(connection, new PendingAuthentication(native, Time.realtimeSinceStartup + 15f)); } return false; } private void OnServerPacket(NetworkConnection connection, PackPacket packet, Channel channel) { PendingAuthentication value; if (packet.Kind == PackMessage.StateRequest && authenticated.Contains(connection) && packet.Body.Length == 0) { this.StateRequested?.Invoke(connection); } else if (packet.Kind != PackMessage.Hello || packet.Body != Fingerprint) { Reject(connection, "The installed expansion packs or runtime differ from the host. Install the same pack set before joining."); } else if (matched.Add(connection) && pending.TryGetValue(connection, out value)) { pending.Remove(connection); ((Authenticator)value.Native).OnRemoteConnection(connection); } } private void OnAuthenticationResult(NetworkConnection connection, bool success) { if (!success) { matched.Remove(connection); pending.Remove(connection); authenticated.Remove(connection); } else if (!matched.Contains(connection)) { Reject(connection, "Native authentication completed without a matching expansion registry."); } else { authenticated.Add(connection); this.PeerConfirmed?.Invoke(connection); } } private void OnClientAuthenticated() { ClientConfirmed = true; manager.ClientManager.Broadcast(new PackPacket { Kind = PackMessage.StateRequest, Body = "" }, (Channel)0); } private void OnClientState(ClientConnectionStateArgs state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Invalid comparison between Unknown and I4 if ((int)state.ConnectionState == 8) { manager.ClientManager.Broadcast(new PackPacket { Kind = PackMessage.Hello, Body = Fingerprint }, (Channel)0); } else if ((int)state.ConnectionState == 1) { ClientConfirmed = false; } } private void OnRemoteState(NetworkConnection connection, RemoteConnectionStateArgs state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) if ((int)state.ConnectionState == 0) { matched.Remove(connection); authenticated.Remove(connection); pending.Remove(connection); } } private void OnClientPacket(PackPacket packet, Channel channel) { if (packet.Kind == PackMessage.Rejected) { reportFailure(packet.Body); manager.ClientManager.StopConnection(); return; } if (packet.Kind == PackMessage.State) { if (!ClientConfirmed) { throw new InvalidDataException("Pack state arrived before native and registry authentication completed."); } this.StateReceived?.Invoke(packet.Body); return; } throw new InvalidDataException("Unexpected host pack message."); } public void SendState(NetworkConnection connection, string json) { if (!authenticated.Contains(connection)) { throw new InvalidOperationException("Do not send pack state to an unverified peer."); } manager.ServerManager.Broadcast(connection, new PackPacket { Kind = PackMessage.State, Body = json }, true, (Channel)0); } public void PublishState(string json) { NetworkConnection[] array = authenticated.ToArray(); foreach (NetworkConnection val in array) { if (val.IsActive) { SendState(val, json); } } } private void Reject(NetworkConnection connection, string message) { pending.Remove(connection); matched.Remove(connection); authenticated.Remove(connection); this.PeerRejected?.Invoke(connection, message); manager.ServerManager.Broadcast(connection, new PackPacket { Kind = PackMessage.Rejected, Body = message }, false, (Channel)0); manager.ServerManager.Kick(connection, (KickReason)5, (LoggingType)3, message); } public void Tick() { NetworkConnection[] array = (from pair in pending where !pair.Key.IsActive || Time.realtimeSinceStartup >= pair.Value.Deadline select pair.Key).ToArray(); foreach (NetworkConnection val in array) { if (val.IsActive) { Reject(val, "Expansion registry handshake timed out. Install the host's ExpansionKit runtime and packs."); } else { pending.Remove(val); } } } private static void Write(Writer writer, PackPacket packet) { byte[] bytes = Encoding.UTF8.GetBytes(packet.Body ?? ""); if (bytes.Length > 60000) { throw new InvalidDataException("The pack network message exceeds its limit."); } writer.WriteUInt8Unpacked((byte)packet.Kind); writer.WriteUInt16Unpacked((ushort)bytes.Length); writer.WriteUInt8Array(bytes, 0, bytes.Length); } private static PackPacket Read(Reader reader) { if (reader.Remaining < 3) { throw new InvalidDataException("Truncated pack network message."); } byte b = reader.ReadUInt8Unpacked(); int num = reader.ReadUInt16Unpacked(); if (b > 3 || num > 60000 || num > reader.Remaining) { throw new InvalidDataException("Invalid pack network message."); } return new PackPacket { Kind = (PackMessage)b, Body = Encoding.UTF8.GetString(reader.ReadUInt8ArrayAllocated(num)) }; } public void Dispose() { if (!disposed) { disposed = true; manager.ServerManager.UnregisterBroadcast((Action)OnServerPacket); manager.ClientManager.UnregisterBroadcast((Action)OnClientPacket); manager.ServerManager.OnAuthenticationResult -= OnAuthenticationResult; manager.ServerManager.OnRemoteConnectionState -= OnRemoteState; manager.ClientManager.OnClientConnectionState -= OnClientState; manager.ClientManager.OnAuthenticated -= OnClientAuthenticated; if (Current == this) { Current = null; } } } } [HarmonyPatch(typeof(SteamLobbyAuthenticator), "OnRemoteConnection")] internal static class AuthenticatePacksBeforeNative { private static bool Prefix(SteamLobbyAuthenticator __instance, NetworkConnection connection) { return PackHandshake.Current?.BeginAuthentication(__instance, connection) ?? true; } } } namespace HowToFish.ExpansionKit.Runtime.Content { public readonly struct ContentReference : IEquatable { public const string NativePack = "native"; public string PackKey { get; } public string Key { get; } public bool IsNative => PackKey == "native"; public byte NativeId { get { if (!IsNative) { throw new InvalidOperationException(ToString() + " is not a native reference."); } return byte.Parse(Key, CultureInfo.InvariantCulture); } } private ContentReference(string pack, string key) { PackKey = pack; Key = key; } public static ContentReference Native(byte id) { return new ContentReference("native", id.ToString(CultureInfo.InvariantCulture)); } public static ContentReference Pack(string packKey, string key) { if (!IsKey(packKey) || packKey == "native") { throw new ArgumentException("Invalid pack key.", "packKey"); } if (!IsKey(key)) { throw new ArgumentException("Invalid recipe key.", "key"); } return new ContentReference(packKey, key); } public static ContentReference Parse(string? reference) { if (!TryParse(reference, out var result)) { throw new FormatException("Malformed content reference: " + (reference ?? "")); } return result; } public static bool TryParse(string? reference, out ContentReference result) { result = default(ContentReference); if (reference == null || reference.Length > 130) { return false; } string[] array = reference.Split(':'); if (array.Length != 2) { return false; } if (array[0] == "native") { if (!byte.TryParse(array[1], NumberStyles.None, CultureInfo.InvariantCulture, out var result2) || array[1] != result2.ToString(CultureInfo.InvariantCulture)) { return false; } result = new ContentReference("native", array[1]); return true; } if (!IsKey(array[0]) || !IsKey(array[1])) { return false; } result = new ContentReference(array[0], array[1]); return true; } public static bool IsKey(string? value) { if (string.IsNullOrEmpty(value) || value.Length > 64) { return false; } if (value[0] < 'a' || value[0] > 'z') { return false; } foreach (char c in value) { if ((c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '_' && c != '-') { return false; } } return true; } public bool Equals(ContentReference other) { if (string.Equals(PackKey, other.PackKey, StringComparison.Ordinal)) { return string.Equals(Key, other.Key, StringComparison.Ordinal); } return false; } public override bool Equals(object? obj) { if (obj is ContentReference other) { return Equals(other); } return false; } public override int GetHashCode() { return ((PackKey?.GetHashCode() ?? 0) * 397) ^ (Key?.GetHashCode() ?? 0); } public override string ToString() { return PackKey + ":" + Key; } public static bool operator ==(ContentReference left, ContentReference right) { return left.Equals(right); } public static bool operator !=(ContentReference left, ContentReference right) { return !left.Equals(right); } public static ContentReference Of(ResolvedContent content) { if (content == null) { throw new ArgumentNullException("content"); } return Parse(content.Reference); } } public sealed class AuthoredArt : IDisposable { private bool disposed; public GameObject Root { get; } public Mesh Preview { get; } public Material[] Palette { get; } private AuthoredArt(GameObject root, Mesh preview, Material[] palette) { Root = root; Preview = preview; Palette = palette; } public static AuthoredArt Take(GameObject art, string label) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)art)) { throw new InvalidOperationException("The asset factory returned no visual for " + label + "."); } if (art.GetComponentsInChildren(true).Length != 0 || art.GetComponentsInChildren(true).Length != 0 || art.GetComponentsInChildren(true).Length != 0) { Object.Destroy((Object)(object)art); throw new InvalidOperationException("Visual " + label + " must contain only non-networked, non-physical art."); } art.SetActive(false); Mesh val = null; try { val = Combine(art, Matrix4x4.identity, out Material[] palette, label + " - preview"); return new AuthoredArt(art, val, palette); } catch { if (Object.op_Implicit((Object)(object)val)) { Object.Destroy((Object)(object)val); } Object.Destroy((Object)(object)art); throw; } } public Vector3 Marker(string name) { //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) Transform val = FindMarker(name) ?? throw new InvalidOperationException(((Object)Root).name + " is missing marker_" + name + "."); return Root.transform.InverseTransformPoint(val.position); } public bool HasMarker(string name) { return Object.op_Implicit((Object)(object)FindMarker(name)); } private Transform? FindMarker(string name) { Transform[] array = (from node in Root.GetComponentsInChildren(true) where ((Object)node).name == "marker_" + name select node).ToArray(); if (array.Length > 1) { throw new InvalidOperationException($"{((Object)Root).name} declares marker_{name} {array.Length} times."); } if (array.Length != 1) { return null; } return array[0]; } public Mesh Combine(Matrix4x4 artToTarget, out Material[] palette, string name) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return Combine(Root, artToTarget, out palette, name); } public static Mesh Combine(GameObject art, Matrix4x4 artToTarget, out Material[] palette, string name) { //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Expected O, but got Unknown //IL_0110: 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_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) List list = new List(); List list2 = new List(); MeshFilter[] componentsInChildren = art.GetComponentsInChildren(true); foreach (MeshFilter val in componentsInChildren) { MeshRenderer component = ((Component)val).GetComponent(); Mesh sharedMesh = val.sharedMesh; if (!Object.op_Implicit((Object)(object)component) || !Object.op_Implicit((Object)(object)sharedMesh) || !sharedMesh.isReadable) { throw new InvalidOperationException("Visual " + ((Object)art).name + " contains an unreadable or unrendered mesh on " + ((Object)val).name + "."); } Material[] sharedMaterials = ((Renderer)component).sharedMaterials; if (sharedMaterials.Length != sharedMesh.subMeshCount) { throw new InvalidOperationException("Material/submesh count differs on " + ((Object)val).name + "."); } for (int j = 0; j < sharedMesh.subMeshCount; j++) { if (!Object.op_Implicit((Object)(object)sharedMaterials[j])) { throw new InvalidOperationException("Visual " + ((Object)art).name + " has a missing material on " + ((Object)val).name + "."); } CombineInstance item = default(CombineInstance); ((CombineInstance)(ref item)).mesh = sharedMesh; ((CombineInstance)(ref item)).subMeshIndex = j; ((CombineInstance)(ref item)).transform = artToTarget * art.transform.worldToLocalMatrix * ((Component)val).transform.localToWorldMatrix; list.Add(item); list2.Add(sharedMaterials[j]); } } if (list.Count == 0) { throw new InvalidOperationException("Visual " + ((Object)art).name + " has no mesh."); } Mesh val2 = new Mesh { name = name, indexFormat = (IndexFormat)1 }; try { val2.CombineMeshes(list.ToArray(), false, true); val2.RecalculateBounds(); palette = list2.ToArray(); return val2; } catch { Object.Destroy((Object)(object)val2); throw; } } public void Dispose() { if (!disposed) { disposed = true; if (Object.op_Implicit((Object)(object)Root)) { Object.Destroy((Object)(object)Root); } } } } public static class NativeCookingMaterials { public static bool DonorSupports(Item donor) { return (Object)(object)Template(donor) != (Object)null; } private static Material? Template(Item donor) { return ((Component)donor).GetComponentsInChildren(true).SelectMany((Renderer renderer) => renderer.sharedMaterials).FirstOrDefault((Func)((Material material) => Object.op_Implicit((Object)(object)material) && material.HasProperty(ShaderManager.CooknessID) && ((Object)material.shader).name == "Shader Graphs/DefaultShader" && material.HasProperty("_Colors"))); } public static Material[] Create(Item donor, Material[] original, ICollection owned) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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_00a3: Expected O, but got Unknown //IL_0071: 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_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Expected O, but got Unknown //IL_0127: Unknown result type (might be due to invalid IL or missing references) Material val = Template(donor) ?? throw new InvalidOperationException("The native donor has no verified cooking/palette shader: " + ((Object)donor).name); Dictionary mapped = new Dictionary(); foreach (Material item in original.Distinct()) { Color val2 = (item.HasProperty("_BaseColor") ? item.GetColor("_BaseColor") : (item.HasProperty("_Color") ? item.GetColor("_Color") : Color.gray)); Material val3 = new Material(val) { name = ((Object)item).name + " - native cooking" }; owned.Add((Object)(object)val3); val3.SetFloat(ShaderManager.CooknessID, 0f); if (val3.HasProperty(ShaderManager.UseSkinID)) { val3.SetFloat(ShaderManager.UseSkinID, 0f); } if (val3.HasProperty(ShaderManager.RainbowSkinID)) { val3.SetFloat(ShaderManager.RainbowSkinID, 0f); } Texture2D val4 = new Texture2D(1, 1, (TextureFormat)4, false, true) { name = ((Object)item).name + " - pigment", wrapMode = (TextureWrapMode)1 }; val4.SetPixel(0, 0, val2); val4.Apply(false, true); owned.Add((Object)(object)val4); val3.SetTexture("_Colors", (Texture)(object)val4); if (val3.HasProperty("_Emission")) { val3.SetFloat("_Emission", 0f); } float num = (item.HasProperty("_Metallic") ? item.GetFloat("_Metallic") : 0f); float num2 = (item.HasProperty("_Smoothness") ? item.GetFloat("_Smoothness") : 0.5f); val3.SetFloat(ShaderManager.MetallicMetallicnessID, num); val3.SetFloat(ShaderManager.PlasticMetallicnessID, num); val3.SetFloat(ShaderManager.MetallicSmoothness, num2); val3.SetFloat(ShaderManager.PlasticSmoothnessID, num2); mapped.Add(item, val3); } return original.Select((Material material) => mapped[material]).ToArray(); } } public sealed class AuthoredCookSurface : MonoBehaviour { [SerializeField] private bool managed; private MaterialPropertyBlock? previous; private MaterialPropertyBlock? current; public bool Managed => managed; public static void Bind(Renderer renderer) { (((Component)renderer).GetComponent() ?? ((Component)renderer).gameObject.AddComponent()).managed = true; } public static bool TryApply(Renderer renderer, float cookness) { if (!Object.op_Implicit((Object)(object)renderer)) { return false; } AuthoredCookSurface component = ((Component)renderer).GetComponent(); if (!Object.op_Implicit((Object)(object)component) || !component.managed) { return false; } component.Apply(renderer, cookness); return true; } private void Apply(Renderer renderer, float cookness) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown if (previous == null) { previous = new MaterialPropertyBlock(); renderer.GetPropertyBlock(previous); } if (current == null) { current = new MaterialPropertyBlock(); } renderer.GetPropertyBlock(current); current.SetFloat(ShaderManager.CooknessID, cookness); renderer.SetPropertyBlock(current); } public static void Restore(Renderer renderer) { if (!Object.op_Implicit((Object)(object)renderer)) { return; } AuthoredCookSurface component = ((Component)renderer).GetComponent(); if (Object.op_Implicit((Object)(object)component) && component.managed) { if (component.previous != null) { renderer.SetPropertyBlock(component.previous); } component.previous = null; component.current = null; component.managed = false; } } } public sealed class ContentVisuals : IDisposable { private readonly Dictionary normalMaterials = new Dictionary(); private readonly Dictionary shinyMaterials = new Dictionary(); private readonly Dictionary skinPalettes = new Dictionary(); private readonly Dictionary originalPreviewMaterials = new Dictionary(); private readonly List ownedAssets = new List(); private readonly List ownedDisposables = new List(); public ICollection OwnedAssets => ownedAssets; public void Own(Object asset) { if (Object.op_Implicit(asset)) { ownedAssets.Add(asset); } } public void Own(IDisposable disposable) { if (disposable != null) { ownedDisposables.Add(disposable); } } public void RegisterPalette(byte itemId, Material[] normal, Material[]? shiny = null) { if (normal == null || normal.Length == 0) { throw new ArgumentException("A palette needs at least one material.", "normal"); } if (normalMaterials.ContainsKey(itemId) || skinPalettes.ContainsKey(itemId)) { throw new InvalidOperationException("A preview palette is already registered for item " + itemId + "."); } normalMaterials.Add(itemId, normal); shinyMaterials.Add(itemId, shiny ?? normal); } public void RegisterSkinPalettes(byte itemId, Material[][] palettes) { if (palettes == null || palettes.Length == 0 || palettes.Any((Material[] palette) => palette == null || palette.Length == 0)) { throw new ArgumentException("Skin palettes need at least one non-empty entry.", "palettes"); } if (normalMaterials.ContainsKey(itemId) || skinPalettes.ContainsKey(itemId)) { throw new InvalidOperationException("A preview palette is already registered for item " + itemId + "."); } skinPalettes.Add(itemId, palettes); } public bool HasPalette(byte itemId) { if (!normalMaterials.ContainsKey(itemId)) { return skinPalettes.ContainsKey(itemId); } return true; } public bool ApplyPreview(Renderer renderer, Item item, bool showShiny, byte? skinIndex) { if (!Object.op_Implicit((Object)(object)renderer)) { throw new ArgumentNullException("renderer"); } if (!Object.op_Implicit((Object)(object)item)) { return false; } if (skinPalettes.TryGetValue(item.ID, out Material[][] value)) { AuthoredCookSurface.Restore(renderer); int num = skinIndex ?? item.CurSkin; if (num >= value.Length) { throw new ArgumentOutOfRangeException("skinIndex", "Unknown owned skin finish."); } Remember(renderer); renderer.sharedMaterials = value[num]; return true; } Material[] sharedMaterials; if (normalMaterials.TryGetValue(item.ID, out Material[] value2)) { Remember(renderer); if (!showShiny) { Creature val = (Creature)(object)((item is Creature) ? item : null); if (val == null || !val.IsDrip) { sharedMaterials = value2; goto IL_00c5; } } sharedMaterials = shinyMaterials[item.ID]; goto IL_00c5; } return false; IL_00c5: renderer.sharedMaterials = sharedMaterials; AuthoredCookSurface.Bind(renderer); ShaderManager.SetItemCookness(renderer, item.Cookness); return true; } private void Remember(Renderer renderer) { if (!originalPreviewMaterials.ContainsKey(renderer)) { originalPreviewMaterials.Add(renderer, renderer.sharedMaterials); } } public void RestorePreview(Renderer renderer) { if (Object.op_Implicit((Object)(object)renderer) && originalPreviewMaterials.TryGetValue(renderer, out Material[] value)) { AuthoredCookSurface.Restore(renderer); renderer.sharedMaterials = value; originalPreviewMaterials.Remove(renderer); } } public void Dispose() { foreach (IDisposable ownedDisposable in ownedDisposables) { try { ownedDisposable.Dispose(); } catch (Exception ex) { Debug.LogException(ex); } } ownedDisposables.Clear(); foreach (KeyValuePair originalPreviewMaterial in originalPreviewMaterials) { if (Object.op_Implicit((Object)(object)originalPreviewMaterial.Key)) { originalPreviewMaterial.Key.sharedMaterials = originalPreviewMaterial.Value; } } foreach (Object ownedAsset in ownedAssets) { if (Object.op_Implicit(ownedAsset)) { Object.Destroy(ownedAsset); } } ownedAssets.Clear(); originalPreviewMaterials.Clear(); normalMaterials.Clear(); shinyMaterials.Clear(); skinPalettes.Clear(); } } public static class InventoryPreview { public static void Fit(Item item, Item donor, Mesh preview, Vector3? rotationOverride = null) { //IL_004c: 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_0055: 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) //IL_006a: 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_00d1: 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_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: 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_011a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_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_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)item) || !Object.op_Implicit((Object)(object)donor) || !Object.op_Implicit((Object)(object)preview)) { throw new ArgumentNullException(); } if (!Object.op_Implicit((Object)(object)donor.Mesh)) { throw new InvalidOperationException("Native donor " + ((Object)donor).name + " has no inventory mesh to derive framing from."); } Bounds bounds = donor.Mesh.bounds; float num = Largest(((Bounds)(ref bounds)).size); bounds = preview.bounds; float num2 = Largest(((Bounds)(ref bounds)).size); float num3 = donor.InventoryMeshScale * num / num2; if (!NativeAccess.Finite(num3) || num3 <= 0f || num2 <= 0.0001f) { throw new InvalidOperationException("Invalid inventory dimensions for " + ((Object)item).name + "."); } num3 = Mathf.Clamp(num3, 0.01f, 100f); Vector3 val = (Vector3)(((??)rotationOverride) ?? donor.InventoryMeshRot); Vector3 inventoryMeshPos = donor.InventoryMeshPos; Quaternion val2 = Quaternion.Euler(donor.InventoryMeshRot); bounds = donor.Mesh.bounds; Vector3 val3 = inventoryMeshPos + val2 * (((Bounds)(ref bounds)).center * donor.InventoryMeshScale); NativeAccess.Set(item, "_mesh", preview); NativeAccess.Set(item, "_inventoryMeshScale", num3); NativeAccess.Set(item, "_inventoryMeshRot", val); Quaternion val4 = Quaternion.Euler(val); bounds = preview.bounds; NativeAccess.Set(item, "_inventoryMeshPos", val3 - val4 * (((Bounds)(ref bounds)).center * num3)); } private static float Largest(Vector3 size) { //IL_0000: 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_000c: Unknown result type (might be due to invalid IL or missing references) return Mathf.Max(size.x, Mathf.Max(size.y, size.z)); } } public static class NativeBirdVisuals { private enum PartRegion { Body, LeftInner, LeftOuter, RightInner, RightOuter } private sealed class Part { internal string Name = ""; internal Mesh Mesh; internal Material[] Materials = Array.Empty(); internal PartRegion Region; } [Serializable] internal sealed class Presentation { [SerializeField] private GameObject root; [SerializeField] private Transform leftInner; [SerializeField] private Transform leftOuter; [SerializeField] private Transform rightInner; [SerializeField] private Transform rightOuter; [SerializeField] private MeshRenderer[] renderers = Array.Empty(); internal GameObject Root => root; internal Transform LeftInner => leftInner; internal Transform LeftOuter => leftOuter; internal Transform RightInner => rightInner; internal Transform RightOuter => rightOuter; internal MeshRenderer[] Renderers => renderers; internal Presentation(GameObject root, Transform leftInner, Transform leftOuter, Transform rightInner, Transform rightOuter, MeshRenderer[] renderers) { this.root = root; this.leftInner = leftInner; this.leftOuter = leftOuter; this.rightInner = rightInner; this.rightOuter = rightOuter; this.renderers = renderers; } } public static NativeBirdVisual Apply(Bird clone, Item donor, AuthoredArt art, ContentVisuals visuals) { //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0213: 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_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0225: 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_0237: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_023b: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0324: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_032a: Unknown result type (might be due to invalid IL or missing references) //IL_0346: Unknown result type (might be due to invalid IL or missing references) //IL_0348: Unknown result type (might be due to invalid IL or missing references) //IL_034a: Unknown result type (might be due to invalid IL or missing references) //IL_034c: Unknown result type (might be due to invalid IL or missing references) //IL_0385: Unknown result type (might be due to invalid IL or missing references) //IL_0387: Unknown result type (might be due to invalid IL or missing references) //IL_03ac: Unknown result type (might be due to invalid IL or missing references) //IL_03ce: Unknown result type (might be due to invalid IL or missing references) //IL_03d3: Unknown result type (might be due to invalid IL or missing references) //IL_03d5: Unknown result type (might be due to invalid IL or missing references) //IL_03da: Unknown result type (might be due to invalid IL or missing references) //IL_03df: Unknown result type (might be due to invalid IL or missing references) //IL_03e9: Unknown result type (might be due to invalid IL or missing references) //IL_03ee: Unknown result type (might be due to invalid IL or missing references) //IL_0405: Unknown result type (might be due to invalid IL or missing references) //IL_040a: Unknown result type (might be due to invalid IL or missing references) //IL_0419: Unknown result type (might be due to invalid IL or missing references) //IL_041e: Unknown result type (might be due to invalid IL or missing references) //IL_0494: Unknown result type (might be due to invalid IL or missing references) //IL_04a1: Unknown result type (might be due to invalid IL or missing references) //IL_04a6: Unknown result type (might be due to invalid IL or missing references) //IL_0544: Unknown result type (might be due to invalid IL or missing references) //IL_0566: Unknown result type (might be due to invalid IL or missing references) //IL_0577: Unknown result type (might be due to invalid IL or missing references) //IL_05a2: Unknown result type (might be due to invalid IL or missing references) //IL_05e5: Unknown result type (might be due to invalid IL or missing references) //IL_0673: Unknown result type (might be due to invalid IL or missing references) //IL_0675: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)clone) || !Object.op_Implicit((Object)(object)donor) || art == null || visuals == null) { throw new ArgumentNullException(); } if (Object.op_Implicit((Object)(object)((Component)clone).GetComponent())) { throw new InvalidOperationException(((Object)clone).name + " already has an authored bird visual."); } string[] array = new string[7] { "wing_hinge_left", "wing_hinge_right", "wing_joint_left", "wing_joint_right", "body_grip", "foot_left", "foot_right" }; foreach (string text in array) { if (!art.HasMarker(text)) { throw new InvalidOperationException(((Object)clone).name + " art is missing marker_" + text + "."); } } Animator val = NativeAccess.Get(clone, "_anim"); GameObject val2 = NativeAccess.Get(clone, "_disableOnDeath"); GameObject val3 = NativeAccess.Get(clone, "_enableOnDeath"); if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val.runtimeAnimatorController) || !((Component)val).transform.IsChildOf(val2.transform) || (Object)(object)val2 == (Object)(object)val3) { throw new InvalidOperationException(((Object)clone).name + " does not match the supported native Bird visual contract."); } Collider[] array2 = NativeAccess.Get(clone, "_worldColliders"); if (array2.Length != 2 || array2.Any((Collider val7) => !Object.op_Implicit((Object)(object)val7) || val7.isTrigger)) { throw new InvalidOperationException(((Object)clone).name + " must preserve its two native non-trigger bird colliders."); } BoxCollider collider = RequireBox(array2, val2, ((Object)clone).name + " live"); BoxCollider collider2 = RequireBox(array2, val3, ((Object)clone).name + " corpse"); Material[] array3 = NativeCookingMaterials.Create(donor, art.Palette, visuals.OwnedAssets); Dictionary dictionary = new Dictionary(); for (int num = 0; num < art.Palette.Length; num++) { dictionary[art.Palette[num]] = array3[num]; } Vector3 leftHinge = art.Marker("wing_hinge_left"); Vector3 rightHinge = art.Marker("wing_hinge_right"); Vector3 leftJoint = art.Marker("wing_joint_left"); Vector3 rightJoint = art.Marker("wing_joint_right"); Vector3 val4 = art.Marker("body_grip"); List list = BuildParts(art, dictionary, leftHinge, rightHinge, leftJoint, rightJoint, visuals); foreach (PartRegion region in Enum.GetValues(typeof(PartRegion))) { if (!list.Any((Part part) => part.Region == region)) { throw new InvalidOperationException($"{((Object)clone).name} art has no {region} visual part."); } } foreach (Renderer item in val2.GetComponentsInChildren(true).Concat(val3.GetComponentsInChildren(true))) { item.enabled = false; } Presentation presentation = BuildPresentation(clone, ((Object)clone).name + " Visual - Live", list, leftHinge, rightHinge, leftJoint, rightJoint); Presentation presentation2 = BuildPresentation(clone, ((Object)clone).name + " Visual - Corpse", list, leftHinge, rightHinge, leftJoint, rightJoint); Pose(presentation, 0f, 0f, dead: false); Pose(presentation2, 0f, 0f, dead: true); presentation.Root.transform.localPosition = -val4; presentation2.Root.transform.localRotation = Quaternion.Euler(0f, 0f, 88f); presentation2.Root.transform.localPosition = -(presentation2.Root.transform.localRotation * val4) + Vector3.up * 0.13f; Bounds flightBounds = BoundsIn(((Component)clone).transform, presentation.Renderers); Bounds corpseBounds = BoundsIn(((Component)clone).transform, presentation2.Renderers); Resize(collider, presentation.Renderers, 0.025f); Resize(collider2, presentation2.Renderers, 0.025f); Collider val5 = NativeAccess.Get(clone, "_pickUpCollider"); Collider obj = ((val5 is SphereCollider) ? val5 : null); if (obj == null || (Object)(object)((Component)val5).transform != (Object)(object)((Component)clone).transform || array2.Contains(val5)) { throw new InvalidOperationException(((Object)clone).name + " must preserve its independent root pickup sphere."); } ((SphereCollider)obj).center = ((Bounds)(ref corpseBounds)).center; Vector3 extents = ((Bounds)(ref corpseBounds)).extents; ((SphereCollider)obj).radius = ((Vector3)(ref extents)).magnitude + 0.035f; obj.isTrigger = true; foreach (MeshRenderer item2 in presentation.Renderers.Concat(presentation2.Renderers)) { AuthoredCookSurface.Bind((Renderer)(object)item2); } NativeAccess.Set(clone, "_renderers", presentation2.Renderers.Cast().ToList()); NativeAccess.Set(clone, "_skinPreset", null); NativeAccess.Set(clone, "_skinRenderers", new List()); NativeAccess.Set(clone, "_modelHeight", Mathf.Max(0.02f, 0f - ((Bounds)(ref corpseBounds)).min.y)); NativeAccess.Set(clone, "_modelForwardLength", Mathf.Max(Mathf.Abs(((Bounds)(ref flightBounds)).min.z), Mathf.Abs(((Bounds)(ref flightBounds)).max.z))); NativeAccess.Set(clone, "_groundCheckDist", Mathf.Max(0.25f, ((Bounds)(ref corpseBounds)).extents.y + 0.05f)); NativeAccess.Set(clone, "_hoverTextTarget", presentation2.Root.transform); NativeAccess.Set(clone, "_hoverTextOffset", Mathf.Max(0.15f, ((Bounds)(ref corpseBounds)).extents.y + 0.1f)); Mesh val6 = Object.Instantiate(art.Preview); ((Object)val6).name = ((Object)clone).name + " - bird inventory"; visuals.Own((Object)(object)val6); NativeAccess.Set(clone, "_dripMesh", val6); InventoryPreview.Fit((Item)(object)clone, donor, val6); visuals.RegisterPalette(((Item)clone).ID, array3); NativeBirdVisual nativeBirdVisual = ((Component)clone).gameObject.AddComponent(); nativeBirdVisual.Initialize(clone, val, val2, val3, presentation, presentation2, flightBounds, corpseBounds, list.Sum((Part part) => part.Mesh.triangles.Length / 3), list.Select((Part part) => part.Name).ToArray()); return nativeBirdVisual; } private static BoxCollider RequireBox(IEnumerable colliders, GameObject holder, string label) { Collider[] array = colliders.Where((Collider collider) => ((Component)collider).transform.IsChildOf(holder.transform)).ToArray(); if (array.Length == 1) { Collider obj = array[0]; BoxCollider val = (BoxCollider)(object)((obj is BoxCollider) ? obj : null); if (val != null) { return val; } } throw new InvalidOperationException(label + " holder must own exactly one native box collider."); } private static List BuildParts(AuthoredArt art, IReadOnlyDictionary materials, Vector3 leftHinge, Vector3 rightHinge, Vector3 leftJoint, Vector3 rightJoint, ContentVisuals visuals) { //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: 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_00e3: 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_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0104: 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_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0123: 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_00ee: 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) List list = new List(); MeshFilter[] componentsInChildren = art.Root.GetComponentsInChildren(true); foreach (MeshFilter filter in componentsInChildren) { MeshRenderer component = ((Component)filter).GetComponent(); Mesh sharedMesh = filter.sharedMesh; if (!Object.op_Implicit((Object)(object)component) || !Object.op_Implicit((Object)(object)sharedMesh) || !sharedMesh.isReadable || ((Renderer)component).sharedMaterials.Length != sharedMesh.subMeshCount) { throw new InvalidOperationException("Bird art part " + ((Object)filter).name + " has an invalid rendered mesh."); } PartRegion partRegion = Region(((Object)filter).name); Vector3 pivot = (Vector3)(partRegion switch { PartRegion.LeftInner => leftHinge, PartRegion.LeftOuter => leftJoint, PartRegion.RightInner => rightHinge, PartRegion.RightOuter => rightJoint, _ => Vector3.zero, }); Matrix4x4 matrix = art.Root.transform.worldToLocalMatrix * ((Component)filter).transform.localToWorldMatrix; Mesh val = Rebase(sharedMesh, matrix, pivot, ((Object)filter).name + " - bird visual"); visuals.Own((Object)(object)val); list.Add(new Part { Name = ((Object)filter).name, Mesh = val, Materials = ((Renderer)component).sharedMaterials.Select(delegate(Material material) { if (!materials.TryGetValue(material, out Material value)) { throw new InvalidOperationException("Bird art part " + ((Object)filter).name + " uses an unregistered material."); } return value; }).ToArray(), Region = partRegion }); } return list; } private static PartRegion Region(string name) { string text = name.ToLowerInvariant(); if (text.Contains("wing_l_outer")) { return PartRegion.LeftOuter; } if (text.Contains("wing_r_outer")) { return PartRegion.RightOuter; } if (text.Contains("wing_l_inner")) { return PartRegion.LeftInner; } if (text.Contains("wing_r_inner")) { return PartRegion.RightInner; } return PartRegion.Body; } private static Mesh Rebase(Mesh source, Matrix4x4 matrix, Vector3 pivot, string name) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: 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_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_005a: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0075: 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_0089: Expected O, but got Unknown Vector3[] array = source.vertices.Select((Vector3 vertex) => ((Matrix4x4)(ref matrix)).MultiplyPoint3x4(vertex) - pivot).ToArray(); Vector3[] normals = source.normals.Select(delegate(Vector3 normal) { //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_000c: 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) Vector3 val2 = ((Matrix4x4)(ref matrix)).MultiplyVector(normal); return ((Vector3)(ref val2)).normalized; }).ToArray(); Mesh val = new Mesh { name = name, indexFormat = (IndexFormat)(array.Length > 65535), vertices = array, normals = normals, uv = source.uv }; val.subMeshCount = source.subMeshCount; for (int num = 0; num < source.subMeshCount; num++) { val.SetTriangles(source.GetTriangles(num), num, false); } val.RecalculateBounds(); return val; } private static Presentation BuildPresentation(Bird bird, string name, IEnumerable parts, Vector3 leftHinge, Vector3 rightHinge, Vector3 leftJoint, Vector3 rightJoint) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_005b: 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_005f: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) GameObject val = Node(name, ((Component)bird).transform, Vector3.zero); GameObject val2 = Node("wing_hinge_left", val.transform, leftHinge); GameObject val3 = Node("wing_hinge_right", val.transform, rightHinge); GameObject val4 = Node("wing_joint_left", val2.transform, leftJoint - leftHinge); GameObject val5 = Node("wing_joint_right", val3.transform, rightJoint - rightHinge); List list = new List(); foreach (Part part in parts) { Transform parent = (Transform)(part.Region switch { PartRegion.LeftInner => val2.transform, PartRegion.LeftOuter => val4.transform, PartRegion.RightInner => val3.transform, PartRegion.RightOuter => val5.transform, _ => val.transform, }); GameObject obj = Node(part.Name, parent, Vector3.zero); obj.AddComponent().sharedMesh = part.Mesh; MeshRenderer val6 = obj.AddComponent(); ((Renderer)val6).sharedMaterials = part.Materials; list.Add(val6); } return new Presentation(val, val2.transform, val4.transform, val3.transform, val5.transform, list.ToArray()); } private static GameObject Node(string name, Transform parent, Vector3 localPosition) { //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_0017: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown GameObject val = new GameObject(name) { layer = ((Component)parent).gameObject.layer }; val.transform.SetParent(parent, false); val.transform.localPosition = localPosition; return val; } internal static void Pose(Presentation presentation, float flap, float sweep, bool dead) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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_0075: Unknown result type (might be due to invalid IL or missing references) if (dead) { presentation.LeftInner.localRotation = Quaternion.Euler(8f, 69f, -18f); presentation.RightInner.localRotation = Quaternion.Euler(8f, -69f, 18f); presentation.LeftOuter.localRotation = Quaternion.Euler(-5f, 24f, -17f); presentation.RightOuter.localRotation = Quaternion.Euler(-5f, -24f, 17f); } else { presentation.LeftInner.localRotation = Quaternion.Euler(0f, sweep, flap); presentation.RightInner.localRotation = Quaternion.Euler(0f, 0f - sweep, 0f - flap); presentation.LeftOuter.localRotation = Quaternion.Euler(0f, sweep * 0.45f, flap * 0.72f); presentation.RightOuter.localRotation = Quaternion.Euler(0f, (0f - sweep) * 0.45f, (0f - flap) * 0.72f); } } private static void Resize(BoxCollider collider, IReadOnlyList renderers, float padding) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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) Bounds val = BoundsIn(((Component)collider).transform, renderers); collider.center = ((Bounds)(ref val)).center; collider.size = ((Bounds)(ref val)).size + Vector3.one * padding; } internal static Bounds BoundsIn(Transform target, IReadOnlyList renderers) { //IL_0004: 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_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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_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) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) bool flag = false; Bounds result = default(Bounds); foreach (MeshRenderer renderer in renderers) { Mesh sharedMesh = ((Component)renderer).GetComponent().sharedMesh; Matrix4x4 val = target.worldToLocalMatrix * ((Component)renderer).transform.localToWorldMatrix; Vector3[] vertices = sharedMesh.vertices; foreach (Vector3 val2 in vertices) { Vector3 val3 = ((Matrix4x4)(ref val)).MultiplyPoint3x4(val2); if (!flag) { result = new Bounds(val3, Vector3.zero); flag = true; } else { ((Bounds)(ref result)).Encapsulate(val3); } } } if (!flag) { throw new InvalidOperationException("The authored bird presentation has no vertices."); } return result; } } public sealed class NativeBirdVisual : MonoBehaviour { [SerializeField] private Bird bird; [SerializeField] private Animator nativeAnimator; [SerializeField] private GameObject liveGate; [SerializeField] private GameObject corpseGate; [SerializeField] private NativeBirdVisuals.Presentation live; [SerializeField] private NativeBirdVisuals.Presentation dead; [SerializeField] private Bounds flightBounds; [SerializeField] private Bounds corpseBounds; [SerializeField] private int triangleCount; [SerializeField] private string[] partNames = Array.Empty(); private bool boundToInstance; private float wingPhase; private float flapCenter = 4f; private float flapAmplitude = 5f; private float flapFrequency = 2.1f; private float wingSweep; private int posedFrame = -1; public GameObject LiveRoot => live.Root; public GameObject CorpseRoot => dead.Root; public Animator NativeAnimator => nativeAnimator; public Bounds FlightBounds => flightBounds; public Bounds CorpseBounds => corpseBounds; public int TriangleCount => triangleCount; public IReadOnlyList PartNames => partNames; public bool ShowingCorpse => dead.Root.activeSelf; public IReadOnlyList LiveRenderers => (IReadOnlyList)(object)live.Renderers; public IReadOnlyList CorpseRenderers => (IReadOnlyList)(object)dead.Renderers; internal void Initialize(Bird owner, Animator animator, GameObject aliveHolder, GameObject deadHolder, NativeBirdVisuals.Presentation alive, NativeBirdVisuals.Presentation corpse, Bounds flightBounds, Bounds corpseBounds, int triangles, IReadOnlyList names) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) bird = owner; nativeAnimator = animator; liveGate = aliveHolder; corpseGate = deadHolder; live = alive; dead = corpse; this.flightBounds = flightBounds; this.corpseBounds = corpseBounds; triangleCount = triangles; partNames = names.ToArray(); boundToInstance = true; RefreshNow(); } public void RefreshNow() { if (!boundToInstance || !Object.op_Implicit((Object)(object)bird) || (Object)(object)((Component)bird).gameObject != (Object)(object)((Component)this).gameObject) { BindInstance(); } if (!Object.op_Implicit((Object)(object)bird) || !Object.op_Implicit((Object)(object)liveGate) || !Object.op_Implicit((Object)(object)corpseGate) || live == null || dead == null) { throw new InvalidOperationException("The authored bird visual lost its native instance bindings."); } bool flag = corpseGate.activeSelf && !liveGate.activeSelf; live.Root.SetActive(!flag); dead.Root.SetActive(flag); if (!flag && posedFrame != Time.frameCount) { posedFrame = Time.frameCount; float num; float num2; float num3; float num4; switch (bird.AnimState) { case 2: num = -18f; num2 = 2f; num3 = 2.1f; num4 = 31f; break; case 3: num = 0f; num2 = 34f; num3 = 7.2f; num4 = 2f; break; case 4: num = 8f; num2 = 43f; num3 = 8.4f; num4 = -4f; break; default: num = 4f; num2 = 5f; num3 = 2.1f; num4 = 0f; break; } float num5 = 1f - Mathf.Exp((0f - Time.deltaTime) * 9f); flapCenter = Mathf.Lerp(flapCenter, num, num5); flapAmplitude = Mathf.Lerp(flapAmplitude, num2, num5); flapFrequency = Mathf.Lerp(flapFrequency, num3, num5); wingSweep = Mathf.Lerp(wingSweep, num4, num5); wingPhase = Mathf.Repeat(wingPhase + Time.deltaTime * flapFrequency, MathF.PI * 2f); NativeBirdVisuals.Pose(live, flapCenter + Mathf.Sin(wingPhase) * flapAmplitude, wingSweep, dead: false); } } private void BindInstance() { //IL_00c6: 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_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) bird = ((Component)this).GetComponent(); if (!Object.op_Implicit((Object)(object)bird)) { throw new InvalidOperationException("An authored bird clone has no native Bird."); } nativeAnimator = NativeAccess.Get(bird, "_anim"); liveGate = NativeAccess.Get(bird, "_disableOnDeath"); corpseGate = NativeAccess.Get(bird, "_enableOnDeath"); live = ResolvePresentation(" Visual - Live"); dead = ResolvePresentation(" Visual - Corpse"); NativeBirdVisuals.Pose(live, 0f, 0f, dead: false); NativeBirdVisuals.Pose(dead, 0f, 0f, dead: true); flightBounds = NativeBirdVisuals.BoundsIn(((Component)this).transform, live.Renderers); corpseBounds = NativeBirdVisuals.BoundsIn(((Component)this).transform, dead.Renderers); partNames = live.Renderers.Select((MeshRenderer renderer) => ((Object)renderer).name).ToArray(); triangleCount = live.Renderers.Sum((MeshRenderer renderer) => ((Component)renderer).GetComponent().sharedMesh.triangles.Length / 3); boundToInstance = true; } private NativeBirdVisuals.Presentation ResolvePresentation(string suffix) { Transform[] array = (from Transform child in (IEnumerable)((Component)this).transform where ((Object)child).name.EndsWith(suffix, StringComparison.Ordinal) select child).ToArray(); if (array.Length != 1) { throw new InvalidOperationException("A cloned bird is missing its unique authored presentation: " + suffix); } Transform val = array[0]; Transform val2 = val.Find("wing_hinge_left"); Transform val3 = val.Find("wing_hinge_right"); if (!Object.op_Implicit((Object)(object)val2) || !Object.op_Implicit((Object)(object)val3) || !Object.op_Implicit((Object)(object)val2.Find("wing_joint_left")) || !Object.op_Implicit((Object)(object)val3.Find("wing_joint_right"))) { throw new InvalidOperationException("A cloned bird lost an original wing articulation."); } return new NativeBirdVisuals.Presentation(((Component)val).gameObject, val2, val2.Find("wing_joint_left"), val3, val3.Find("wing_joint_right"), ((Component)val).GetComponentsInChildren(true)); } private void LateUpdate() { RefreshNow(); } } [HarmonyPatch(typeof(Item), "GetName")] internal static class OwnedItemNamePatch { private static bool Prefix(Item __instance, ref string __result) { if (!NativeContentDirectory.TryDescribeItem(__instance, out string title, out string _)) { return true; } __result = title; return false; } } [HarmonyPatch(typeof(BaitInfo), "get_NameLocalized")] internal static class OwnedLureNamePatch { private static bool Prefix(BaitInfo __instance, ref string __result) { if (!NativeContentDirectory.TryDescribeLure(__instance, out string title, out string _)) { return true; } __result = title; return false; } } [HarmonyPatch(typeof(BaitInfo), "get_Description")] internal static class OwnedLureDescriptionPatch { private static bool Prefix(BaitInfo __instance, ref string __result) { if (!NativeContentDirectory.TryDescribeLure(__instance, out string _, out string description)) { return true; } __result = description; return false; } } [HarmonyPatch(typeof(InventorySlot), "ApplySkin")] internal static class OwnedInventoryPreviewPatch { private static bool Prefix(InventorySlot __instance, Item item) { return !NativeContentDirectory.ApplyItemPreview(NativeAccess.Get(__instance, "_renderer"), item); } } [HarmonyPatch(typeof(JournalSlot), "SetCreature")] internal static class OwnedJournalPreviewPatch { private static void Postfix(JournalSlot __instance, Creature creature) { Renderer component = ((Component)NativeAccess.Get(__instance, "_filter")).GetComponent(); if (Object.op_Implicit((Object)(object)component)) { bool showShiny = Object.op_Implicit((Object)(object)creature) && (SaveManager.GetSavedCreature(creature)?.KilledDrip ?? false); NativeContentDirectory.ApplyItemPreview(component, (Item?)(object)creature, showShiny); } } } [HarmonyPatch(typeof(MachineSlot), "SetIndex")] internal static class OwnedMachinePreviewPatch { private static void Prefix(MachineSlot __instance) { NativeContentDirectory.RestoreItemPreview(NativeAccess.Get(__instance, "_renderer")); } private static void Postfix(MachineSlot __instance, byte itemIndex, byte skinIndex) { if (itemIndex != byte.MaxValue) { NativeContentDirectory.ApplyItemPreview(NativeAccess.Get(__instance, "_renderer"), GameInfo.IDToItem(itemIndex), showShiny: false, skinIndex); } } } [HarmonyPatch(typeof(BaitSlot), "SetBait")] internal static class OwnedBaitSlotPreviewPatch { private static void Postfix(BaitSlot __instance, BaitInfo bait) { NativeContentDirectory.ApplyLurePreview(NativeAccess.Get(__instance, "_renderer"), bait); } } [HarmonyPatch(typeof(Bait), "SetBaitInfo")] internal static class OwnedBaitVisualPatch { private static void Postfix(Bait __instance) { NativeContentDirectory.ApplyBaitVisual(__instance); } } [HarmonyPatch(typeof(PlayerInventory), "LoadFromSave")] internal static class OwnedInventoryCapacityPatch { private static void Prefix(PlayerInventory __instance) { NativeContentDirectory.EnsureInventoryBaitCapacity(__instance); } } [HarmonyPatch(typeof(CreatureManager), "GetRandomItem", new Type[] { typeof(Vector3), typeof(List) })] internal static class OwnedCatchPatchRouting { private static void Prefix(CreatureManager __instance, ref List weights) { if (((NetworkBehaviour)__instance).IsServerInitialized && NativeContentDirectory.TryPatchCatchTable(weights, out List patched)) { weights = patched; } } } [HarmonyPatch(typeof(ShaderManager), "SetItemCookness", new Type[] { typeof(Renderer), typeof(float) })] internal static class OwnedCooknessPatch { private static bool Prefix(Renderer rend, float to) { return !AuthoredCookSurface.TryApply(rend, to); } } [HarmonyPatch(typeof(Creature), "ServerChangeHp")] internal static class OwnedLootCombatPatch { private static void Prefix(Creature __instance, int damage) { if (((NetworkBehaviour)__instance).IsServerInitialized && damage > 0 && __instance._hp.Value > 0) { NativeContentDirectory.ObserveCombat(__instance); } } } [HarmonyPatch(typeof(Creature), "OnDeath")] internal static class OwnedLootDeathPatch { private static void Postfix(Creature __instance) { NativeContentDirectory.AutoLoot(__instance); } } [HarmonyPatch(typeof(Server), "RpcLogic___BuyBait___4169050770")] internal static class ReservedLurePurchaseGuard { private static bool Prefix(byte __1) { if (NativeLureTable.IsSelectable(__1)) { return true; } ExpansionKitPlugin.Log.LogWarning((object)("Rejected purchase of unavailable lure slot " + __1 + ".")); return false; } } [HarmonyPatch(typeof(Server), "RpcLogic___ChangeBait___1382779195")] internal static class ReservedLureSelectGuard { private static bool Prefix(byte __1) { if (NativeLureTable.IsSelectable(__1)) { return true; } ExpansionKitPlugin.Log.LogWarning((object)("Rejected selection of unavailable lure slot " + __1 + ".")); return false; } } public static class NativeContentSelfCheck { public static IReadOnlyList InspectNativeTables() { List list = new List(); try { Dictionary dictionary = NativeAccess.Static>(typeof(GameInfo), "_allItems"); Dictionary dictionary2 = NativeAccess.Static>(typeof(GameInfo), "_idToSpawnable"); Dictionary dictionary3 = NativeAccess.Static>(typeof(GameInfo), "_nameToSpawnable"); List list2 = NativeAccess.Static>(typeof(GameInfo), "_allCreatures"); List baits = NativeAccess.Static>(typeof(GameInfo), "_allBaits"); if (dictionary.Count == 0) { list.Add("GameInfo item table is empty."); } foreach (byte item in dictionary.Keys.Where((byte id) => !NativePackCatalog.Contains((ContentDomain)0, (int)id) && !NativeContentDirectory.LiveRegistrars.Any((NativeItemRegistrar r) => r.Items.Any((RegisteredItem i) => i.ItemId == id)))) { list.Add($"Unowned non-native item ID {item} is present in GameInfo."); } foreach (KeyValuePair item2 in dictionary) { if (!Object.op_Implicit((Object)(object)item2.Value) || item2.Value.ID != item2.Key) { list.Add($"GameInfo item {item2.Key} is missing or mislabelled."); } } foreach (KeyValuePair item3 in dictionary2) { if (!dictionary.TryGetValue(item3.Key, out var value) || value != item3.Value) { list.Add($"Spawnable {item3.Key} disagrees with the item table."); } } foreach (KeyValuePair item4 in dictionary3) { if (!Object.op_Implicit((Object)(object)item4.Value) || !dictionary2.TryGetValue(item4.Value.ID, out var value2) || value2 != item4.Value) { list.Add("Spawn name " + item4.Key + " disagrees with the spawnable table."); } } if (list2.Any((Creature creature) => !Object.op_Implicit((Object)(object)creature) || creature.ExcludeFromJournal)) { list.Add("The journal lists a missing or excluded creature."); } if (list2.Select((Creature creature) => (!Object.op_Implicit((Object)(object)creature)) ? (-1) : ((Item)creature).ID).Distinct().Count() != list2.Count) { list.Add("The journal lists a creature twice."); } if (baits.Count < 17) { list.Add($"Bait table has {baits.Count} entries; expected at least {17}."); } else if ((Object)(object)baits[0] != (Object)(object)baits[1]) { list.Add("Bait slots 0 and 1 no longer alias the empty bait."); } for (int num = 0; num < baits.Count; num++) { if (!Object.op_Implicit((Object)(object)baits[num]) || baits[num].ItemWeights == null) { list.Add($"Bait {num} is missing or has no catch table."); } } int index; for (index = 17; index < baits.Count; index++) { if (!NativeLureTable.IsPlaceholder(baits[index]) && !NativeContentDirectory.LiveLureCatalogs.Any((NativeLureCatalog catalog) => catalog.Owns(baits[index]))) { list.Add($"Bait {index} ({((Object)baits[index]).name}) is neither a live pack lure nor a reserved placeholder."); } } NetworkManager networkManager = InstanceFinder.NetworkManager; if (!Object.op_Implicit((Object)(object)networkManager)) { list.Add("FishNet NetworkManager is unavailable."); } else { foreach (KeyValuePair pair in networkManager.RuntimeSpawnablePrefabs) { if (pair.Key >= 48000 && NativeContentDirectory.LiveRegistrars.All((NativeItemRegistrar r) => r.CollectionId != pair.Key)) { list.Add($"Reserved FishNet collection {pair.Key} exists without a live registrar."); } } } } catch (Exception ex) { list.Add("Native table inspection failed: " + ex.Message); } return list.AsReadOnly(); } public static IReadOnlyList InspectLiveContent() { List list = new List(); foreach (NativeItemRegistrar liveRegistrar in NativeContentDirectory.LiveRegistrars) { try { liveRegistrar.Validate(); } catch (Exception ex) { list.Add(ex.Message); } } foreach (NativeLureCatalog liveLureCatalog in NativeContentDirectory.LiveLureCatalogs) { try { liveLureCatalog.Validate(); } catch (Exception ex2) { list.Add(ex2.Message); } } return list.AsReadOnly(); } public static IReadOnlyList InspectSharedHooks() { List list = new List(); foreach (var (text, methodBase) in SharedHookTargets()) { if (methodBase == null) { list.Add("Shared hook target is missing from the native assembly: " + text); continue; } Patches patchInfo = Harmony.GetPatchInfo(methodBase); if (patchInfo == null || !patchInfo.Prefixes.Concat(patchInfo.Postfixes).Any((Patch patch) => patch.PatchMethod.DeclaringType?.Assembly == typeof(NativeContentSelfCheck).Assembly)) { list.Add("Shared hook is not applied (run Harmony.PatchAll over the ExpansionKit runtime assembly): " + text); } } return list.AsReadOnly(); } private static IEnumerable<(string Label, MethodBase? Method)> SharedHookTargets() { yield return (Label: "Item.GetName", Method: AccessTools.Method(typeof(Item), "GetName", (Type[])null, (Type[])null)); yield return (Label: "BaitInfo.get_NameLocalized", Method: AccessTools.PropertyGetter(typeof(BaitInfo), "NameLocalized")); yield return (Label: "BaitInfo.get_Description", Method: AccessTools.PropertyGetter(typeof(BaitInfo), "Description")); yield return (Label: "InventorySlot.ApplySkin", Method: AccessTools.Method(typeof(InventorySlot), "ApplySkin", (Type[])null, (Type[])null)); yield return (Label: "JournalSlot.SetCreature", Method: AccessTools.Method(typeof(JournalSlot), "SetCreature", (Type[])null, (Type[])null)); yield return (Label: "MachineSlot.SetIndex", Method: AccessTools.Method(typeof(MachineSlot), "SetIndex", (Type[])null, (Type[])null)); yield return (Label: "BaitSlot.SetBait", Method: AccessTools.Method(typeof(BaitSlot), "SetBait", (Type[])null, (Type[])null)); yield return (Label: "Bait.SetBaitInfo", Method: AccessTools.Method(typeof(Bait), "SetBaitInfo", (Type[])null, (Type[])null)); yield return (Label: "PlayerInventory.LoadFromSave", Method: AccessTools.Method(typeof(PlayerInventory), "LoadFromSave", (Type[])null, (Type[])null)); yield return (Label: "CreatureManager.GetRandomItem", Method: AccessTools.Method(typeof(CreatureManager), "GetRandomItem", new Type[2] { typeof(Vector3), typeof(List) }, (Type[])null)); yield return (Label: "ShaderManager.SetItemCookness(Renderer,float)", Method: AccessTools.Method(typeof(ShaderManager), "SetItemCookness", new Type[2] { typeof(Renderer), typeof(float) }, (Type[])null)); yield return (Label: "Creature.ServerChangeHp", Method: AccessTools.Method(typeof(Creature), "ServerChangeHp", (Type[])null, (Type[])null)); yield return (Label: "Creature.OnDeath", Method: AccessTools.Method(typeof(Creature), "OnDeath", (Type[])null, (Type[])null)); yield return (Label: "Server.RpcLogic___BuyBait", Method: AccessTools.Method(typeof(Server), "RpcLogic___BuyBait___4169050770", (Type[])null, (Type[])null)); yield return (Label: "Server.RpcLogic___ChangeBait", Method: AccessTools.Method(typeof(Server), "RpcLogic___ChangeBait___1382779195", (Type[])null, (Type[])null)); } public static void RequireClean(IReadOnlyList problems) { if (problems.Count > 0) { throw new InvalidOperationException(string.Join(Environment.NewLine, problems)); } } } public class StandardItemTuning { public int? Worth { get; set; } public int? Cost { get; set; } public bool? IgnoredByMoneyNpc { get; set; } public bool? IgnoredBySeagulls { get; set; } public bool? QuestItem { get; set; } } public sealed class FoodTuning : StandardItemTuning { public int Fullness { get; set; } public int Heal { get; set; } } public sealed class FishTuning : StandardItemTuning { public int? Health { get; set; } public int? Fullness { get; set; } public int? Heal { get; set; } public bool Journal { get; set; } = true; public bool? Endangered { get; set; } public float? BodyDamageFactor { get; set; } public float? CrewHealthFactor { get; set; } } public sealed class WeaponTuning { public float? DamageScale { get; set; } public IReadOnlyList UpgradeDamageScales { get; set; } = Array.Empty(); public MagazinePolicy MagazinePolicy { get; set; } public int? MagazineSize { get; set; } public IReadOnlyList UpgradeMagazineSizes { get; set; } = Array.Empty(); public ReloadPolicy ReloadPolicy { get; set; } public float? ReloadSeconds { get; set; } public float? ShotDelaySeconds { get; set; } public IReadOnlyList? AllowedAttachmentIds { get; set; } public int? Worth { get; set; } public int? Cost { get; set; } } public sealed class RodTuning { public float? LineStrengthScale { get; set; } public float? ReelingSpeedScale { get; set; } public float? SpoolLengthScale { get; set; } public float? MaximumLineLength { get; set; } public int? Worth { get; set; } public int? Cost { get; set; } } public static class NativeItemFactories { public const string UnsupportedPrefix = "Unsupported by the native runtime: "; public static PreparedItem PrepareFood(NativeItemRegistrar registrar, string key, byte itemId, ushort slot, byte donorId, string title, FoodTuning tuning, string description = "", string? cloneName = null) { if (registrar == null) { throw new ArgumentNullException("registrar"); } PreparedItem preparedItem = registrar.Prepare(key, itemId, slot, NativeItemRegistrar.NativeDonor(donorId), title, description, cloneName); try { ConfigureFood(preparedItem, tuning); return preparedItem; } catch { registrar.Discard(preparedItem); throw; } } public static void ConfigureStandardItem(PreparedItem item, StandardItemTuning? tuning = null) { Require(item); if (tuning == null) { tuning = new StandardItemTuning(); } item.Kind = (PackItemKind)((item.Clone is Creature) ? 3 : 0); ApplyCommon(item, tuning); } public static void ConfigureFood(PreparedItem item, FoodTuning tuning) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) Require(item); if (tuning == null) { throw new ArgumentNullException("tuning"); } Creature target = item.Require(); Item donor = item.Donor; Creature val = (Creature)(object)((donor is Creature) ? donor : null); if (val == null) { throw new InvalidOperationException($"{item.Reference}: food needs a Creature donor; {((Object)item.Donor).name} is {((object)item.Donor).GetType().Name}."); } if ((int)val.BossType != 0 || NativeAccess.Get(val, "_maxHp") != 0) { throw new InvalidOperationException($"{item.Reference}: food donors must be native inert food (boss-free, 0 max HP); {((Object)val).name} is not."); } if (tuning.Fullness < 0 || tuning.Fullness > 1000000 || tuning.Heal < 0 || tuning.Heal > 1000000 || tuning.Fullness + tuning.Heal == 0) { throw new ArgumentOutOfRangeException("tuning", "Food needs bounded, non-zero fullness/heal values."); } item.Kind = (PackItemKind)1; item.Catchable = false; ApplyCommon(item, tuning); NativeAccess.Set(target, "_excludeFromJournal", true); NativeAccess.Set(target, "_isEndangered", false); NativeAccess.Set(target, "_maxHp", 0); NativeAccess.Set(target, "_fullnessToRestore", tuning.Fullness); NativeAccess.Set(target, "_hpToRestore", tuning.Heal); NativeAccess.Set(target, "_bossType", (object)(BossType)0); } public static void ConfigureFish(PreparedItem item, FishTuning tuning) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Unknown result type (might be due to invalid IL or missing references) Require(item); if (tuning == null) { throw new ArgumentNullException("tuning"); } Creature val = item.Require(); Item donor = item.Donor; Creature val2 = (Creature)(object)((donor is Creature) ? donor : null); if (val2 == null) { throw new InvalidOperationException($"{item.Reference}: fish need a Creature donor."); } if ((int)val2.BossType != 0 || ((Item)val2).IsQuestItem) { throw new InvalidOperationException($"{item.Reference}: only ordinary, non-quest native creatures serve as fish donors; {((Object)val2).name} is not."); } int num = tuning.Health ?? NativeAccess.Get(val2, "_maxHp"); if (num < 1 || num > 100000000) { throw new ArgumentOutOfRangeException("tuning", "Fish health must be 1..100000000."); } int num2 = tuning.Fullness ?? val2.FullnessToRestore; int num3 = tuning.Heal ?? val2.HpToRestore; if (num2 < 0 || num3 < 0 || num2 > 1000000 || num3 > 1000000) { throw new ArgumentOutOfRangeException("tuning", "Fish food values are out of bounds."); } item.Kind = (PackItemKind)((val is Fish) ? 2 : 3); item.Catchable = true; ApplyCommon(item, tuning); NativeAccess.Set(val, "_excludeFromJournal", !tuning.Journal); NativeAccess.Set(val, "_isEndangered", tuning.Endangered ?? val2.IsEndangered); NativeAccess.Set(val, "_maxHp", num); NativeAccess.Set(val, "_fullnessToRestore", num2); NativeAccess.Set(val, "_hpToRestore", num3); if (tuning.BodyDamageFactor.HasValue) { Creature obj = ((val is AttackingFish) ? val : null); if (obj == null || !NativeAccess.HasField(typeof(AttackingFish), "_onHitDamage")) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: body damage factor needs a native contact-attacking donor."); } int val3 = Mathf.RoundToInt((float)NativeAccess.Get(item.Donor, "_onHitDamage") * Scale(tuning.BodyDamageFactor.Value, "body damage factor")); NativeAccess.Set(obj, "_onHitDamage", Math.Max(0, val3)); } if (tuning.CrewHealthFactor.HasValue) { if ((int)val.BossType == 0) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: crew health scaling only applies to boss-typed creatures."); } NativeAccess.Set(val, "_bossHpMultiplier", NativeAccess.Get(val2, "_bossHpMultiplier") * Scale(tuning.CrewHealthFactor.Value, "crew health factor")); } } public static NativeWeaponUpgrades ConfigureWeapon(PreparedItem item, WeaponTuning tuning) { //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: 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_02de: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_02fa: 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) //IL_0314: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Expected O, but got Unknown //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Expected O, but got Unknown //IL_038d: Unknown result type (might be due to invalid IL or missing references) //IL_0392: Unknown result type (might be due to invalid IL or missing references) //IL_0394: Unknown result type (might be due to invalid IL or missing references) //IL_03a7: Expected I4, but got Unknown //IL_05ed: Unknown result type (might be due to invalid IL or missing references) //IL_060a: Unknown result type (might be due to invalid IL or missing references) Require(item); if (tuning == null) { throw new ArgumentNullException("tuning"); } Weapon val = item.Require(); Item donor = item.Donor; Weapon val2 = (Weapon)(object)((donor is Weapon) ? donor : null); if (val2 == null) { throw new InvalidOperationException($"{item.Reference}: weapons need a Weapon donor."); } if (!Object.op_Implicit((Object)(object)val.Attachments) || (Object)(object)val.Attachments == (Object)(object)val2.Attachments || (!((Component)val.Attachments).transform.IsChildOf(((Component)val).transform) && (Object)(object)((Component)val.Attachments).transform != (Object)(object)((Component)val).transform)) { throw new InvalidOperationException($"{item.Reference}: the clone does not own its Attachments component."); } if (!NativePackCatalog.IsRangedWeapon((int)((Item)val2).ID)) { throw new InvalidOperationException($"{item.Reference}: native item {((Item)val2).ID} is not a ranged weapon donor."); } item.Kind = (PackItemKind)4; ApplyCommon(item, new StandardItemTuning { Worth = tuning.Worth, Cost = tuning.Cost }); Attachments attachments = val.Attachments; NativeWeaponUpgrades nativeWeaponUpgrades = NativeWeaponUpgrades.Describe(val2); BulletUpgrade[] array = NativeWeaponUpgrades.Bullets(val2); float num = (tuning.DamageScale.HasValue ? Scale(tuning.DamageScale.Value, "damage scale") : 1f); if (tuning.UpgradeDamageScales.Count != 0 && tuning.UpgradeDamageScales.Count != array.Length) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: the donor has {array.Length} ammunition tiers (including base); {tuning.UpgradeDamageScales.Count} per-tier scales were given (count must match, never clamped)."); } BulletUpgrade[] array2 = (BulletUpgrade[])(object)new BulletUpgrade[array.Length]; for (int i = 0; i < array.Length; i++) { float num2 = ((tuning.UpgradeDamageScales.Count > 0) ? Scale(tuning.UpgradeDamageScales[i], "per-tier damage scale") : num); int num3 = Mathf.Max(1, Mathf.RoundToInt((float)array[i].Damage * num2)); if (i > 0 && num3 <= NativeAccess.Get(array2[i - 1], "_damage")) { throw new InvalidOperationException($"{item.Reference}: scaled ammunition tiers must keep strictly increasing damage (tier {i})."); } array2[i] = new BulletUpgrade(); NativeAccess.Set(array2[i], "_damage", num3); NativeAccess.Set(array2[i], "_cost", array[i].Cost); } NativeAccess.Set(attachments, "_bulletUpgrades", array2); WeaponInfo val3 = NativeAccess.Get(val2, "_weaponInfo") ?? throw new InvalidOperationException("The donor has no WeaponInfo."); NativeAccess.Set(val, "_weaponInfo", (object?)new WeaponInfo { Weapon = val, ProjectileType = val3.ProjectileType, ProjectileDamage = array2[0].Damage, ProjectileForce = val3.ProjectileForce, ProjectileGravity = val3.ProjectileGravity, ShootVFX = val3.ShootVFX, BoatForceOverride = val3.BoatForceOverride }); int num4 = NativeAccess.Get(val2.Attachments, "_defaultAmmoPerMag"); int num5 = NativeAccess.Get(val2.Attachments, "_extendedAmmoPerMag"); bool flag = Object.op_Implicit((Object)(object)NativeAccess.Optional(val2.Attachments, "_extendedMagInfo")) && num5 > num4; if (num4 < 1) { throw new InvalidOperationException($"{item.Reference}: the donor magazine is invalid."); } MagazinePolicy magazinePolicy = tuning.MagazinePolicy; switch ((int)magazinePolicy) { case 0: if (tuning.MagazineSize.HasValue || tuning.UpgradeMagazineSizes.Count != 0) { throw new ArgumentException($"{item.Reference}: Donor magazine policy must omit MagazineSize/UpgradeMagazineSizes.", "tuning"); } break; case 1: { if (tuning.UpgradeMagazineSizes.Count != 0) { throw new ArgumentException($"{item.Reference}: Fixed magazine policy must omit UpgradeMagazineSizes.", "tuning"); } int num6 = tuning.MagazineSize ?? throw new ArgumentException($"{item.Reference}: Fixed magazine policy needs MagazineSize.", "tuning"); if (num6 < 1 || num6 > 10000) { throw new ArgumentOutOfRangeException("tuning", "Magazine size must be 1..10000."); } NativeAccess.Set(attachments, "_defaultAmmoPerMag", num6); if (flag) { NativeAccess.Set(attachments, "_extendedAmmoPerMag", Math.Max(num6 + 1, Mathf.RoundToInt((float)num6 * ((float)num5 / (float)num4)))); } break; } case 2: if (tuning.MagazineSize.HasValue) { throw new ArgumentException($"{item.Reference}: PerUpgrade magazine policy must omit MagazineSize.", "tuning"); } if (!flag) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: the donor has no extended magazine; per-upgrade magazine sizes have no native slot."); } if (tuning.UpgradeMagazineSizes.Count != 2) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: native weapons have exactly two magazine states (default, extended); {tuning.UpgradeMagazineSizes.Count} sizes were given."); } if (tuning.UpgradeMagazineSizes[0] < 1 || tuning.UpgradeMagazineSizes[1] <= tuning.UpgradeMagazineSizes[0] || tuning.UpgradeMagazineSizes[1] > 10000) { throw new ArgumentOutOfRangeException("tuning", "Magazine sizes must be increasing within 1..10000."); } NativeAccess.Set(attachments, "_defaultAmmoPerMag", tuning.UpgradeMagazineSizes[0]); NativeAccess.Set(attachments, "_extendedAmmoPerMag", tuning.UpgradeMagazineSizes[1]); break; default: throw new ArgumentOutOfRangeException("tuning", "Unknown magazine policy."); } if ((int)tuning.ReloadPolicy != 0) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: reload policy {tuning.ReloadPolicy} has no native equivalent (reload timing is animation-driven)."); } if (tuning.ReloadSeconds.HasValue) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: reload duration is driven by the native legacy animation clip and is not retimed."); } if (tuning.ShotDelaySeconds.HasValue) { float value = tuning.ShotDelaySeconds.Value; if (!NativeAccess.Finite(value) || value <= 0f || value > 3600f) { throw new ArgumentOutOfRangeException("tuning", "Shot delay must be within (0, 3600] seconds."); } NativeAccess.Set(val, "_timeBetweenShots", value); } if (tuning.AllowedAttachmentIds != null) { HashSet hashSet = new HashSet(tuning.AllowedAttachmentIds); foreach (byte item2 in hashSet) { if (item2 >= GameInfo.AllAttachments.Count) { throw new ArgumentOutOfRangeException("tuning", $"Unknown native attachment index {item2}."); } } foreach (Attachment item3 in NativeWeaponUpgrades.Sights(val).Cast().Concat((IEnumerable)(object)NativeWeaponUpgrades.Barrels(val)) .Concat((IEnumerable)(object)new Attachment[1] { (Attachment)NativeWeaponUpgrades.Laser(val) })) { if (Object.op_Implicit((Object)(object)item3) && Object.op_Implicit((Object)(object)item3.Info) && ((Component)item3).transform.IsChildOf(((Component)val).transform) && !hashSet.Contains(GameInfo.GetIndexOfAttachment(item3.Info))) { NativeAccess.Set(item3, "_info", null); } } AttachmentInfo val4 = NativeAccess.Optional(attachments, "_extendedMagInfo"); if (Object.op_Implicit((Object)(object)val4) && !hashSet.Contains(GameInfo.GetIndexOfAttachment(val4))) { NativeAccess.Set(attachments, "_extendedMagInfo", null); } } NativeWeaponUpgrades nativeWeaponUpgrades2 = NativeWeaponUpgrades.Describe(val); if (tuning.AllowedAttachmentIds == null && !(from offer in nativeWeaponUpgrades2.Offers where offer.Kind != WeaponUpgradeKind.Ammunition select offer.Identity).SequenceEqual(from offer in nativeWeaponUpgrades.Offers where offer.Kind != WeaponUpgradeKind.Ammunition select offer.Identity)) { throw new InvalidOperationException($"{item.Reference}: the weapon must retain native attachment identities, order and prices."); } if (nativeWeaponUpgrades2.DamageTierCount != nativeWeaponUpgrades.DamageTierCount || !(from offer in nativeWeaponUpgrades2.Offers where offer.Kind == WeaponUpgradeKind.Ammunition select offer.Cost).SequenceEqual(from offer in nativeWeaponUpgrades.Offers where offer.Kind == WeaponUpgradeKind.Ammunition select offer.Cost)) { throw new InvalidOperationException($"{item.Reference}: ammunition tier count and prices must stay native (only damage is scaled)."); } return nativeWeaponUpgrades2; } public static void ConfigureRod(PreparedItem item, RodTuning tuning) { Require(item); if (tuning == null) { throw new ArgumentNullException("tuning"); } FishingRod val = item.Require(); Item donor = item.Donor; FishingRod val2 = (FishingRod)(object)((donor is FishingRod) ? donor : null); if (val2 == null) { throw new InvalidOperationException($"{item.Reference}: rods need a FishingRod donor."); } if (!Object.op_Implicit((Object)(object)val.Bait) || (Object)(object)val.Bait == (Object)(object)val2.Bait || !((Component)val.Bait).transform.IsChildOf(((Component)val).transform)) { throw new InvalidOperationException($"{item.Reference}: the rod clone does not own an embedded bait."); } item.Kind = (PackItemKind)5; ApplyCommon(item, new StandardItemTuning { Worth = tuning.Worth, Cost = tuning.Cost }); SpringJoint val3 = NativeAccess.Get(val.Bait, "_joint"); SpringJoint val4 = NativeAccess.Get(val2.Bait, "_joint"); if (!Object.op_Implicit((Object)(object)val3) || (Object)(object)val3 == (Object)(object)val4 || !NativeAccess.Positive(val4.spring) || !NativeAccess.Finite(val4.damper) || val4.damper < 0f) { throw new InvalidOperationException($"{item.Reference}: the rod must own its native bait spring."); } if (tuning.LineStrengthScale.HasValue) { float num = Scale(tuning.LineStrengthScale.Value, "line strength scale"); val3.spring = val4.spring * num; val3.damper = val4.damper * num; } if (tuning.ReelingSpeedScale.HasValue) { float num2 = NativeAccess.Get(val2, "_holdReelSpeed"); if (!NativeAccess.Positive(num2)) { throw new InvalidOperationException($"{item.Reference}: the donor reel cadence is invalid."); } NativeAccess.Set(val, "_holdReelSpeed", num2 * Scale(tuning.ReelingSpeedScale.Value, "reeling speed scale")); } if (tuning.MaximumLineLength.HasValue || tuning.SpoolLengthScale.HasValue) { float num3 = tuning.MaximumLineLength ?? (NativeAccess.Get(val2, "_maxLineLength") * Scale(tuning.SpoolLengthScale.Value, "spool length scale")); if (!NativeAccess.Positive(num3) || num3 > 10000f || num3 <= NativeAccess.Get(val, "_minLineLength")) { throw new InvalidOperationException($"{item.Reference}: the spool length must be finite, <= 10000 m and exceed the native minimum line length."); } NativeAccess.Set(val, "_maxLineLength", num3); } } public static void ApplyRecipe(PreparedItem item, ItemRecipe recipe) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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_0049: Expected I4, but got Unknown //IL_0376: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) Require(item); if (recipe == null) { throw new ArgumentNullException("recipe"); } item.Recipe = recipe; RefuseUnsupported(item, recipe); PackItemKind kind = recipe.Kind; switch ((int)kind) { case 0: ConfigureStandardItem(item, new StandardItemTuning { Worth = recipe.Worth, Cost = recipe.Cost }); break; case 1: { Item donor = item.Donor; Creature val3 = (Creature)(object)(((donor is Creature) ? donor : null) ?? throw new InvalidOperationException($"{item.Reference}: food needs a Creature donor.")); int fullness = (recipe.FoodValue.HasValue ? Mathf.RoundToInt(recipe.FoodValue.Value) : val3.FullnessToRestore); int heal = (recipe.HealthRestored.HasValue ? Mathf.RoundToInt(recipe.HealthRestored.Value) : val3.HpToRestore); ConfigureFood(item, new FoodTuning { Worth = recipe.Worth, Cost = recipe.Cost, Fullness = fullness, Heal = heal }); break; } case 2: case 3: ConfigureFish(item, new FishTuning { Worth = recipe.Worth, Cost = recipe.Cost, Health = (recipe.Health.HasValue ? new int?(Mathf.Max(1, Mathf.RoundToInt(recipe.Health.Value))) : ((int?)null)), Fullness = (recipe.FoodValue.HasValue ? new int?(Mathf.RoundToInt(recipe.FoodValue.Value)) : ((int?)null)), Heal = (recipe.HealthRestored.HasValue ? new int?(Mathf.RoundToInt(recipe.HealthRestored.Value)) : ((int?)null)), BodyDamageFactor = recipe.BodyDamageFactor, CrewHealthFactor = recipe.CrewHealthFactor }); break; case 4: if (!(item.Clone is Weapon)) { if (recipe.Weapon != null) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: WeaponRecipe policies need a ranged Weapon donor; {((Object)item.Donor).name} is {((object)item.Donor).GetType().Name}."); } ConfigureStandardItem(item, new StandardItemTuning { Worth = recipe.Worth, Cost = recipe.Cost }); item.Kind = (PackItemKind)4; } else { WeaponRecipe val2 = (WeaponRecipe)(((object)recipe.Weapon) ?? ((object)new WeaponRecipe())); ConfigureWeapon(item, new WeaponTuning { Worth = recipe.Worth, Cost = recipe.Cost, DamageScale = val2.DamageScale, UpgradeDamageScales = val2.UpgradeDamageScales.ToArray(), MagazinePolicy = val2.MagazinePolicy, MagazineSize = val2.MagazineSize, UpgradeMagazineSizes = val2.UpgradeMagazineSizes.ToArray(), ReloadPolicy = val2.ReloadPolicy, ReloadSeconds = val2.ReloadSeconds, ShotDelaySeconds = val2.ShotDelaySeconds, AllowedAttachmentIds = val2.AllowedAttachmentIds?.ToArray() }); } break; case 5: { RodRecipe val = (RodRecipe)(((object)recipe.Rod) ?? ((object)new RodRecipe())); if (val.UpgradeLineStrengthScales.Count != 0 || val.UpgradeReelingSpeedScales.Count != 0) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: native rods have no upgrade tiers; upgrade scale lists cannot be honoured."); } ConfigureRod(item, new RodTuning { Worth = recipe.Worth, Cost = recipe.Cost, LineStrengthScale = val.LineStrengthScale, ReelingSpeedScale = val.ReelingSpeedScale, MaximumLineLength = val.MaximumLineLength }); break; } default: throw new ArgumentOutOfRangeException("recipe", "Unknown item kind."); } item.SetPresentation(recipe.Title, item.Description); } private static void RefuseUnsupported(PreparedItem item, ItemRecipe recipe) { //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Invalid comparison between Unknown and I4 //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Invalid comparison between Unknown and I4 //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Invalid comparison between Unknown and I4 //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Invalid comparison between Unknown and I4 bool flag = item.Clone is Creature; if (recipe.Edible == false && flag) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: native creatures are always edible; Edible=false cannot be honoured."); } if (recipe.Edible == true && !flag) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: only Creature donors are edible natively."); } if (recipe.Cookable == false) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: every native item accepts cookness; Cookable=false cannot be honoured."); } if (recipe.Cooked == true) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: pre-cooked spawn state is a synced runtime value, not prefab data."); } if (recipe.CookedItem != null) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: native cooking never transforms one item into another."); } if (recipe.CrewDamageFactor.HasValue) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: native creatures have no crew-scaled damage."); } if ((int)recipe.Kind == 1 && recipe.Health.HasValue) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: food spawns inert; health does not apply."); } if ((recipe.FoodValue.HasValue || recipe.HealthRestored.HasValue) && !flag) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: FoodValue/HealthRestored need an edible Creature donor."); } if ((int)recipe.Kind != 1 && (int)recipe.Kind != 2 && (int)recipe.Kind != 3 && (recipe.Health.HasValue || recipe.HealthRestored.HasValue || recipe.FoodValue.HasValue || recipe.BodyDamageFactor.HasValue || recipe.CrewHealthFactor.HasValue)) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{item.Reference}: Health/HealthRestored/FoodValue/BodyDamageFactor/CrewHealthFactor are creature overrides; use Kind=Fish/Creature/Food (never silently ignored)."); } } private static void ApplyCommon(PreparedItem item, StandardItemTuning tuning) { if (tuning.Worth.HasValue) { NativeAccess.Set(item.Clone, "_worth", Bounded(tuning.Worth.Value, "worth")); } if (tuning.Cost.HasValue) { NativeAccess.Set(item.Clone, "_cost", Bounded(tuning.Cost.Value, "cost")); } if (tuning.IgnoredByMoneyNpc.HasValue) { NativeAccess.Set(item.Clone, "_ignoredByMoneyNPC", tuning.IgnoredByMoneyNpc.Value); } if (tuning.IgnoredBySeagulls.HasValue) { NativeAccess.Set(item.Clone, "_ignoredBySeagulls", tuning.IgnoredBySeagulls.Value); } if (tuning.QuestItem.HasValue) { NativeAccess.Set(item.Clone, "k__BackingField", tuning.QuestItem.Value); } } private static int Bounded(int value, string label) { if (value < 0 || value > 100000000) { throw new ArgumentOutOfRangeException(label, label + " must be 0..100000000."); } return value; } private static float Scale(float value, string label) { if (!NativeAccess.Finite(value) || !(value >= 0.001f) || !(value <= 1000f)) { throw new ArgumentOutOfRangeException(label, label + " must be finite within 0.001..1000."); } return value; } private static void Require(PreparedItem item) { if (item == null) { throw new ArgumentNullException("item"); } if (item.Committed) { throw new InvalidOperationException($"{item.Reference} is already committed."); } if (!Object.op_Implicit((Object)(object)item.Clone) || ((Component)item.Clone).gameObject.activeInHierarchy) { throw new InvalidOperationException($"{item.Reference}: configure the inactive prepared clone."); } } } public enum WeaponUpgradeKind { Sight, Barrel, ExtendedMagazine, Laser, Ammunition } public sealed class WeaponUpgradeOffer { public WeaponUpgradeKind Kind { get; } public byte Index { get; } public int Cost { get; } public int Damage { get; } public AttachmentInfo? Information { get; } public byte? GameInfoIndex { get { if (!Object.op_Implicit((Object)(object)Information)) { return null; } return GameInfo.GetIndexOfAttachment(Information); } } public string Identity => $"{Kind}:{Index}:{Cost}:{(Object.op_Implicit((Object)(object)Information) ? ((Object)Information).name : Damage.ToString())}"; internal WeaponUpgradeOffer(WeaponUpgradeKind kind, byte index, int cost, AttachmentInfo? information, int damage) { Kind = kind; Index = index; Cost = cost; Information = information; Damage = damage; } } public sealed class NativeWeaponUpgrades { private readonly WeaponUpgradeOffer[] offers; public IReadOnlyList Offers => offers; public int BaseDamage { get; } public int DamageTierCount => offers.Count((WeaponUpgradeOffer offer) => offer.Kind == WeaponUpgradeKind.Ammunition); public int DefaultMagazine { get; } public int ExtendedMagazine { get; } public bool HasExtendedMagazine { get; } private NativeWeaponUpgrades(Weapon weapon) { Attachments target = weapon.Attachments ?? throw new InvalidOperationException(((Object)weapon).name + " has no Attachments."); List result = new List(); Sight[] array = Sights(weapon); BarrelAttachment[] array2 = Barrels(weapon); if (array.Length == 0 || array2.Length == 0) { throw new InvalidOperationException(((Object)weapon).name + " has no native sight/barrel tables."); } for (int i = 0; i < array.Length; i++) { if (Object.op_Implicit((Object)(object)array[i]) && Object.op_Implicit((Object)(object)((Attachment)array[i]).Info)) { Add(WeaponUpgradeKind.Sight, (byte)i, ((Attachment)array[i]).Cost, ((Attachment)array[i]).Info); } } for (int j = 0; j < array2.Length; j++) { if (Object.op_Implicit((Object)(object)array2[j]) && Object.op_Implicit((Object)(object)((Attachment)array2[j]).Info)) { Add(WeaponUpgradeKind.Barrel, (byte)j, ((Attachment)array2[j]).Cost, ((Attachment)array2[j]).Info); } } LaserSight val = Laser(weapon); if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)((Attachment)val).Info)) { Add(WeaponUpgradeKind.Laser, 0, ((Attachment)val).Cost, ((Attachment)val).Info); } AttachmentInfo val2 = NativeAccess.Optional(target, "_extendedMagInfo"); DefaultMagazine = NativeAccess.Get(target, "_defaultAmmoPerMag"); ExtendedMagazine = NativeAccess.Get(target, "_extendedAmmoPerMag"); HasExtendedMagazine = Object.op_Implicit((Object)(object)val2); if (Object.op_Implicit((Object)(object)val2)) { Add(WeaponUpgradeKind.ExtendedMagazine, 0, NativeAccess.Get(target, "_extendedMagCost"), val2); } BulletUpgrade[] array3 = Bullets(weapon); if (array3.Length < 1 || array3.Length > 256) { throw new InvalidOperationException(((Object)weapon).name + ": the ammunition tier table is missing or exceeds the saved AmmoType range."); } BaseDamage = array3[0].Damage; for (int k = 1; k < array3.Length; k++) { if (array3[k].Damage <= array3[k - 1].Damage) { throw new InvalidOperationException(((Object)weapon).name + ": ammunition tiers must have strictly increasing damage."); } result.Add(new WeaponUpgradeOffer(WeaponUpgradeKind.Ammunition, (byte)k, array3[k].Cost, null, array3[k].Damage)); } offers = result.ToArray(); void Add(WeaponUpgradeKind kind, byte index, int cost, AttachmentInfo information) { RequireNativeIdentity(information); if (cost < 0 || result.Any((WeaponUpgradeOffer offer) => (Object)(object)offer.Information == (Object)(object)information)) { throw new InvalidOperationException("Invalid or duplicate native attachment price/identity: " + ((Object)information).name); } result.Add(new WeaponUpgradeOffer(kind, index, cost, information, 0)); } } public static NativeWeaponUpgrades Describe(Weapon weapon) { if (!Object.op_Implicit((Object)(object)weapon)) { throw new ArgumentNullException("weapon"); } return new NativeWeaponUpgrades(weapon); } public static Sight[] Sights(Weapon weapon) { return NativeAccess.Get>(weapon.Attachments, "_sights").ToArray(); } public static BarrelAttachment[] Barrels(Weapon weapon) { return NativeAccess.Get>(weapon.Attachments, "_barrelAttachments").ToArray(); } public static BulletUpgrade[] Bullets(Weapon weapon) { return NativeAccess.Get(weapon.Attachments, "_bulletUpgrades"); } public static LaserSight Laser(Weapon weapon) { return NativeAccess.Optional(weapon.Attachments, "_laserSight"); } public static void RequireNativeIdentity(AttachmentInfo? information) { if (!Object.op_Implicit((Object)(object)information)) { throw new InvalidOperationException("A native attachment identity is missing."); } byte indexOfAttachment = GameInfo.GetIndexOfAttachment(information); if (indexOfAttachment >= GameInfo.AllAttachments.Count || (Object)(object)GameInfo.AllAttachments[indexOfAttachment] != (Object)(object)information) { throw new InvalidOperationException("AttachmentInfo is not a native GameInfo identity: " + ((Object)information).name); } } public void ValidateLiveWeapon(Weapon weapon) { if (!Object.op_Implicit((Object)(object)weapon)) { throw new ArgumentNullException("weapon"); } BulletUpgrade[] bullets = Bullets(weapon); if (bullets.Length != DamageTierCount + 1 || bullets[0].Damage != BaseDamage || offers.Where((WeaponUpgradeOffer offer) => offer.Kind == WeaponUpgradeKind.Ammunition).Any((WeaponUpgradeOffer offer) => bullets[offer.Index].Damage != offer.Damage || bullets[offer.Index].Cost != offer.Cost)) { throw new InvalidOperationException(((Object)weapon).name + " lost its owned ammunition table."); } foreach (WeaponUpgradeOffer item in offers.Where((WeaponUpgradeOffer offer) => Object.op_Implicit((Object)(object)offer.Information))) { if (!weapon.Attachments.CanAttach(item.Information) || weapon.Attachments.GetAttachmentCost(item.Information) != item.Cost) { throw new InvalidOperationException($"Native Awake/InitAttachments did not initialize {item.Kind} {((Object)item.Information).name} on {((Object)weapon).name}."); } } if (weapon.Attachments.AmmoPerMag <= 0) { throw new InvalidOperationException(((Object)weapon).name + " has a zero-capacity magazine."); } } } public sealed class PreparedItem { public NativeItemRegistrar Registrar { get; } public string Key { get; } public byte ItemId { get; } public ushort Slot { get; } public string Title { get; private set; } public string Description { get; private set; } public Item Clone { get; } public Item Donor { get; } public OwnedContentIdentity Identity { get; } public ItemRecipe? Recipe { get; internal set; } public PackItemKind Kind { get; internal set; } public MeshAdapterKind Visual { get; internal set; } public bool Catchable { get; set; } public bool Committed { get; internal set; } public NetworkObject Network => ((Component)Clone).GetComponent(); public ContentReference Reference => ContentReference.Pack(Registrar.PackKey, Key); public void MarkCustomVisual() { Visual = MeshAdapterKind.Custom; } internal PreparedItem(NativeItemRegistrar registrar, string key, byte id, ushort slot, string title, string description, Item clone, Item donor, OwnedContentIdentity identity) { Registrar = registrar; Key = key; ItemId = id; Slot = slot; Title = title; Description = description; Clone = clone; Donor = donor; Identity = identity; Catchable = clone is Creature; } public void SetPresentation(string title, string? description = null) { if (Committed) { throw new InvalidOperationException("Presentation is fixed after commit."); } if (string.IsNullOrWhiteSpace(title) || title.Length > 160) { throw new ArgumentException("Invalid title.", "title"); } Title = title; Description = description ?? Description; Identity.SetPresentation(Title, Description); } public T Require() where T : Item { Item clone = Clone; return ((T)(object)((clone is T) ? clone : null)) ?? throw new InvalidOperationException($"{Reference} is a {((object)Clone).GetType().Name}, not {typeof(T).Name}."); } } public sealed class RegisteredItem { public string Key { get; } public byte ItemId { get; } public ushort Slot { get; } public string Title { get; } public string Description { get; } public Item Prefab { get; } public Item Donor { get; } public PackItemKind Kind { get; } public MeshAdapterKind Visual { get; } public Fishable? Fishable { get; } public bool InJournal { get; } public ItemRecipe? Recipe { get; } public ContentReference Reference { get; } internal RegisteredItem(PreparedItem prepared, Fishable? fishable, bool inJournal) { //IL_005c: 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) Key = prepared.Key; ItemId = prepared.ItemId; Slot = prepared.Slot; Title = prepared.Title; Description = prepared.Description; Prefab = prepared.Clone; Donor = prepared.Donor; Kind = prepared.Kind; Visual = prepared.Visual; Fishable = fishable; InJournal = inJournal; Recipe = prepared.Recipe; Reference = prepared.Reference; } } public sealed class NativeItemRegistrar : IDisposable { public const byte FirstCustomItemId = 86; public const ushort FirstReservedCollectionId = 48000; private readonly Dictionary prepared = new Dictionary(StringComparer.Ordinal); private readonly Dictionary registered = new Dictionary(StringComparer.Ordinal); private readonly Dictionary byId = new Dictionary(); private readonly Dictionary addedNames = new Dictionary(StringComparer.Ordinal); private readonly List addedJournal = new List(); private readonly List fishables = new List(); private readonly Dictionary allItems; private readonly Dictionary spawnables; private readonly Dictionary namedSpawnables; private readonly List journal; private readonly NetworkManager manager; private GameObject? nursery; private PrefabObjects? collection; private bool disposed; public string PackKey { get; } public ushort CollectionId { get; } public int Capacity { get; } public ManualLogSource Log { get; } public ContentVisuals Visuals { get; } = new ContentVisuals(); public bool IsReady { get; private set; } public bool IsCommitted { get; private set; } public Transform Nursery { get { if (!Object.op_Implicit((Object)(object)nursery)) { throw new ObjectDisposedException("NativeItemRegistrar"); } return nursery.transform; } } public IReadOnlyCollection Pending => prepared.Values; public IReadOnlyList Items => registered.Values.OrderBy((RegisteredItem item) => item.Slot).ToArray(); public event Action? BeforeCommit; public NativeItemRegistrar(string packKey, ushort collectionId, int capacity = 256, ManualLogSource? log = null) { //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Expected O, but got Unknown if (!ContentReference.IsKey(packKey) || packKey == "native") { throw new ArgumentException("A registrar needs a valid, non-native pack key.", "packKey"); } if (collectionId < 48000) { throw new ArgumentOutOfRangeException("collectionId", "Pack collections use reserved IDs >= 48000."); } if (capacity < 1 || capacity > 65536) { throw new ArgumentOutOfRangeException("capacity"); } PackKey = packKey; CollectionId = collectionId; Capacity = capacity; Log = log ?? Logger.CreateLogSource("ExpansionKit.Content." + packKey); manager = InstanceFinder.NetworkManager; if (!Object.op_Implicit((Object)(object)manager)) { throw new InvalidOperationException("The FishNet NetworkManager is not available yet."); } RequireOffline(); allItems = NativeAccess.Static>(typeof(GameInfo), "_allItems"); spawnables = NativeAccess.Static>(typeof(GameInfo), "_idToSpawnable"); namedSpawnables = NativeAccess.Static>(typeof(GameInfo), "_nameToSpawnable"); journal = NativeAccess.Static>(typeof(GameInfo), "_allCreatures"); if (allItems.Count == 0) { throw new InvalidOperationException("GameInfo has not loaded the native item prefabs yet."); } if (manager.RuntimeSpawnablePrefabs.ContainsKey(collectionId)) { throw new InvalidOperationException($"FishNet collection {collectionId} already exists; it will not be overwritten."); } NativeContentDirectory.Attach(this); try { nursery = new GameObject($"ExpansionKit_{packKey}_{collectionId}"); nursery.SetActive(false); Object.DontDestroyOnLoad((Object)(object)nursery); } catch { NativeContentDirectory.Detach(this); throw; } } private void RequireOffline() { if (manager.IsServerStarted || manager.IsClientStarted) { throw new InvalidOperationException("Register content after game setup and before any host or client starts."); } } private void RequireOpen() { if (disposed) { throw new ObjectDisposedException("NativeItemRegistrar"); } if (IsCommitted) { throw new InvalidOperationException("The registrar is already committed."); } } public static string SpawnName(string packKey, string key) { return (packKey + "_" + key).ToLowerInvariant(); } public static Item NativeDonor(byte id) { Item spawnable = GameInfo.GetSpawnable(id); if (!Object.op_Implicit((Object)(object)spawnable) || spawnable.ID != id) { throw new InvalidOperationException($"Native item {id} is not a registered spawnable."); } if (!NativePackCatalog.Contains((ContentDomain)0, (int)id)) { throw new InvalidOperationException($"Item {id} is outside the native catalog and cannot serve as a donor."); } RequireCompatibleDonor(spawnable); return spawnable; } public static void RequireCompatibleDonor(Item donor) { if (!Object.op_Implicit((Object)(object)donor)) { throw new ArgumentNullException("donor"); } NetworkObject component = ((Component)donor).GetComponent(); if (!Object.op_Implicit((Object)(object)((Component)donor).GetComponent()) || !Object.op_Implicit((Object)(object)((Component)donor).GetComponent()) || !Object.op_Implicit((Object)(object)component) || component.IsSceneObject || ((Component)donor).GetComponentsInChildren(true).Length != 1 || ((Component)donor).gameObject.activeInHierarchy || Object.op_Implicit((Object)(object)((Component)donor).GetComponent())) { throw new InvalidOperationException($"{((Object)donor).name} ({donor.ID}) is not a native networked item prefab usable as a donor."); } } public PreparedItem Prepare(string key, byte itemId, ushort slot, Item donor, string title, string description = "", string? cloneName = null) { RequireOpen(); RequireOffline(); if (!ContentReference.IsKey(key)) { throw new ArgumentException("Invalid recipe key.", "key"); } if (string.IsNullOrWhiteSpace(title) || title.Length > 160) { throw new ArgumentException("Invalid title.", "title"); } if (description == null || description.Length > 2048) { throw new ArgumentException("Invalid description.", "description"); } if (itemId < 86) { throw new ArgumentOutOfRangeException("itemId", $"Custom item IDs start at {(byte)86}; {itemId} overlaps the native table."); } if (slot >= Capacity) { throw new ArgumentOutOfRangeException("slot", $"Slot {slot} exceeds collection capacity {Capacity}."); } RequireCompatibleDonor(donor); if (prepared.ContainsKey(key)) { throw new InvalidOperationException(PackKey + ":" + key + " is already prepared."); } if (prepared.Values.Any((PreparedItem item) => item.ItemId == itemId)) { throw new InvalidOperationException($"Item ID {itemId} is already prepared by {PackKey}."); } if (prepared.Values.Any((PreparedItem item) => item.Slot == slot)) { throw new InvalidOperationException($"Slot {CollectionId}:{slot} is already prepared by {PackKey}."); } string text = SpawnName(PackKey, key); if (allItems.ContainsKey(itemId) || spawnables.ContainsKey(itemId) || journal.Any((Creature creature) => Object.op_Implicit((Object)(object)creature) && ((Item)creature).ID == itemId) || namedSpawnables.Values.Any((Item item) => Object.op_Implicit((Object)(object)item) && item.ID == itemId)) { throw new InvalidOperationException($"Item ID {itemId} ({PackKey}:{key}) is already occupied in GameInfo."); } if (namedSpawnables.ContainsKey(text)) { throw new InvalidOperationException("Spawn name " + text + " is already occupied."); } foreach (NativeItemRegistrar liveRegistrar in NativeContentDirectory.LiveRegistrars) { if (liveRegistrar != this && (liveRegistrar.prepared.Values.Any((PreparedItem item) => item.ItemId == itemId) || liveRegistrar.byId.ContainsKey(itemId))) { throw new InvalidOperationException($"Item ID {itemId} is already claimed by pack {liveRegistrar.PackKey}."); } } if (manager.RuntimeSpawnablePrefabs.ContainsKey(CollectionId)) { throw new InvalidOperationException($"FishNet collection {CollectionId} was claimed by another party during preparation."); } if (cloneName != null && (cloneName.Trim().Length == 0 || cloneName.Length > 128)) { throw new ArgumentException("Invalid clone name.", "cloneName"); } GameObject val = Object.Instantiate(((Component)donor).gameObject, nursery.transform, false); try { ((Object)val).name = cloneName ?? (PackKey + "_" + key); val.SetActive(true); Item val2 = val.GetComponent() ?? throw new InvalidOperationException("The clone lost its root Item."); NativeAccess.PrepareNetworkClone(val, ((Component)donor).gameObject); NativeAccess.Set(val2, "_id", itemId); OwnedContentIdentity ownedContentIdentity = val.AddComponent(); ownedContentIdentity.Configure(PackKey, key, itemId, title, description, CollectionId, slot); PreparedItem preparedItem = new PreparedItem(this, key, itemId, slot, title, description, val2, donor, ownedContentIdentity); prepared.Add(key, preparedItem); Log.LogInfo((object)$"{PackKey}:{key}: prepared item {itemId} from native {((Object)donor).name} ({donor.ID}, {((object)donor).GetType().Name}) for {CollectionId}:{slot}."); return preparedItem; } catch { Object.Destroy((Object)(object)val); throw; } } public void Discard(PreparedItem item) { RequireOpen(); if (item == null || item.Registrar != this || !prepared.Remove(item.Key)) { throw new ArgumentException("Unknown prepared item.", "item"); } if (Object.op_Implicit((Object)(object)item.Clone)) { Object.Destroy((Object)(object)((Component)item.Clone).gameObject); } } public IReadOnlyList Commit() { //IL_0426: Unknown result type (might be due to invalid IL or missing references) //IL_0430: Expected O, but got Unknown //IL_043d: Unknown result type (might be due to invalid IL or missing references) //IL_0447: Expected O, but got Unknown RequireOpen(); try { RequireOffline(); if (prepared.Count == 0) { throw new InvalidOperationException($"{PackKey}: nothing is prepared for collection {CollectionId}."); } PreparedItem[] array = prepared.Values.OrderBy((PreparedItem item) => item.Slot).ToArray(); for (int num = 0; num < array.Length; num++) { if (array[num].Slot != num) { throw new InvalidOperationException($"{PackKey}: FishNet slots must be contiguous 0..{array.Length - 1}; slot {num} is missing (holes are not registered as prefabs)."); } } PreparedItem[] array2 = array; foreach (PreparedItem preparedItem in array2) { if (!Object.op_Implicit((Object)(object)preparedItem.Clone) || ((Component)preparedItem.Clone).gameObject.activeInHierarchy || (Object)(object)((Component)preparedItem.Clone).transform.parent != (Object)(object)nursery.transform) { throw new InvalidOperationException($"{preparedItem.Reference}: the prepared clone left the inactive nursery."); } if (preparedItem.Clone.ID != preparedItem.ItemId) { throw new InvalidOperationException($"{preparedItem.Reference}: a customization changed the reserved item ID."); } if (!Object.op_Implicit((Object)(object)preparedItem.Clone.Mesh) || preparedItem.Clone.Mesh.vertexCount == 0) { throw new InvalidOperationException($"{preparedItem.Reference}: the item has no inventory mesh."); } if (((Component)preparedItem.Clone).GetComponentsInChildren(true).Length == 0) { throw new InvalidOperationException($"{preparedItem.Reference}: the item has no renderer."); } if (((Component)preparedItem.Clone).GetComponentsInChildren(true).Length != 1) { throw new InvalidOperationException($"{preparedItem.Reference}: a customization added or removed a NetworkObject."); } if (allItems.ContainsKey(preparedItem.ItemId) || spawnables.ContainsKey(preparedItem.ItemId) || namedSpawnables.ContainsKey(SpawnName(PackKey, preparedItem.Key))) { throw new InvalidOperationException($"{preparedItem.Reference}: ID or spawn name became occupied before commit."); } this.BeforeCommit?.Invoke(preparedItem); } if (manager.RuntimeSpawnablePrefabs.ContainsKey(CollectionId)) { throw new InvalidOperationException($"FishNet collection {CollectionId} was claimed during content preparation."); } collection = manager.GetPrefabObjects(CollectionId, true); if (!Object.op_Implicit((Object)(object)collection) || collection.GetObjectCount() != 0) { throw new InvalidOperationException("The reserved FishNet collection could not be created empty."); } collection.AddObjects(array.Select((PreparedItem item) => item.Network).ToList(), false, true); array2 = array; foreach (PreparedItem preparedItem2 in array2) { Fishable val = null; if (preparedItem2.Catchable && preparedItem2.Clone is Creature) { val = ScriptableObject.CreateInstance(); ((Object)val).name = ((Object)preparedItem2.Clone).name + "_Catch"; NativeAccess.Set(val, "_itemToSpawn", preparedItem2.Clone); Object.DontDestroyOnLoad((Object)(object)val); fishables.Add(val); } Item clone = preparedItem2.Clone; Creature val2 = (Creature)(object)((clone is Creature) ? clone : null); bool flag = val2 != null && !val2.ExcludeFromJournal; allItems.Add(preparedItem2.ItemId, preparedItem2.Clone); spawnables.Add(preparedItem2.ItemId, preparedItem2.Clone); string key = SpawnName(PackKey, preparedItem2.Key); namedSpawnables.Add(key, preparedItem2.Clone); addedNames.Add(key, preparedItem2.Clone); if (flag) { journal.Add((Creature)preparedItem2.Clone); addedJournal.Add((Creature)preparedItem2.Clone); } preparedItem2.Committed = true; RegisteredItem value = new RegisteredItem(preparedItem2, val, flag); registered.Add(preparedItem2.Key, value); byId.Add(preparedItem2.ItemId, value); } prepared.Clear(); IsCommitted = true; IsReady = true; Validate(); Log.LogInfo((object)($"{PackKey}: {registered.Count} items committed to FishNet collection {CollectionId} (slots 0..{registered.Count - 1}): " + string.Join(", ", array.Select((PreparedItem item) => $"{item.Key}={item.ItemId}")) + ". Native donors unchanged.")); return Items; } catch (Exception arg) { Log.LogError((object)$"{PackKey}: content commit failed; owned registry changes are being rolled back: {arg}"); Dispose(); throw; } } public bool Validate() { if (disposed) { throw new ObjectDisposedException("NativeItemRegistrar"); } if (!IsCommitted) { throw new InvalidOperationException("Validate after commit."); } if (!Object.op_Implicit((Object)(object)collection) || !Object.op_Implicit((Object)(object)nursery) || collection.GetObjectCount() != registered.Count || byId.Count != registered.Count) { throw new InvalidOperationException(PackKey + ": the owned prefab collection is incomplete."); } if (!manager.RuntimeSpawnablePrefabs.TryGetValue(CollectionId, out var value) || value != collection) { throw new InvalidOperationException($"{PackKey}: FishNet no longer owns collection {CollectionId}."); } foreach (RegisteredItem value2 in registered.Values) { Item prefab = value2.Prefab; NetworkObject val = (Object.op_Implicit((Object)(object)prefab) ? ((Component)prefab).GetComponent() : null); if (!Object.op_Implicit((Object)(object)prefab) || prefab.ID != value2.ItemId || !Object.op_Implicit((Object)(object)val) || val.IsSceneObject || GameInfo.IDToItem(value2.ItemId) != prefab || GameInfo.GetSpawnable(value2.ItemId) != prefab || GameInfo.GetSpawnable(SpawnName(PackKey, value2.Key)) != prefab || !Object.op_Implicit((Object)(object)prefab.Mesh) || prefab.Mesh.vertexCount == 0 || !((Component)prefab).gameObject.activeSelf || ((Component)prefab).gameObject.activeInHierarchy || val.PrefabId != value2.Slot || val.SpawnableCollectionId != CollectionId || collection.GetObject(true, (int)value2.Slot) != val || collection.GetObject(false, (int)value2.Slot) != val) { throw new InvalidOperationException($"{PackKey}:{value2.Key}: item {value2.ItemId} / prefab {CollectionId}:{value2.Slot} mapping is invalid."); } if (val.NetworkBehaviours.Count == 0) { throw new InvalidOperationException(PackKey + ":" + value2.Key + ": no network behaviour registered."); } for (int i = 0; i < val.NetworkBehaviours.Count; i++) { NetworkBehaviour val2 = val.NetworkBehaviours[i]; if (!Object.op_Implicit((Object)(object)val2) || (Object)(object)val2.NetworkObject != (Object)(object)val || val2.ComponentIndex != i) { throw new InvalidOperationException(PackKey + ":" + value2.Key + ": invalid network behaviour owner."); } } OwnedContentIdentity component = ((Component)prefab).GetComponent(); if (!Object.op_Implicit((Object)(object)component) || component.ItemId != value2.ItemId || component.Key != value2.Key || component.PackKey != PackKey || component.CollectionId != CollectionId || component.Slot != value2.Slot) { throw new InvalidOperationException(PackKey + ":" + value2.Key + ": owned identity mismatch."); } Creature creature = (Creature)(object)((prefab is Creature) ? prefab : null); if (creature != null) { bool flag = journal.Count((Creature val3) => (Object)(object)val3 == (Object)(object)creature) == 1; if (value2.InJournal != flag || value2.InJournal == creature.ExcludeFromJournal) { throw new InvalidOperationException(PackKey + ":" + value2.Key + ": journal registration is inconsistent."); } if (Object.op_Implicit((Object)(object)value2.Fishable) && value2.Fishable.ItemToSpawn != prefab) { throw new InvalidOperationException(PackKey + ":" + value2.Key + ": the owned catch record points elsewhere."); } } } return true; } public RegisteredItem Get(string key) { if (!TryGetRegistered(key, out RegisteredItem entry)) { throw new KeyNotFoundException("Unknown registered item " + PackKey + ":" + key + "."); } return entry; } public bool TryGetRegistered(string key, out RegisteredItem entry) { entry = null; if (!disposed && key != null) { return registered.TryGetValue(key, out entry); } return false; } public bool TryGetPrepared(string key, out PreparedItem entry) { entry = null; if (!disposed && key != null) { return prepared.TryGetValue(key, out entry); } return false; } public Item GetItem(string key) { return Get(key).Prefab; } public Fishable GetFishable(string key) { RegisteredItem registeredItem = Get(key); if (!Object.op_Implicit((Object)(object)registeredItem.Fishable)) { throw new InvalidOperationException(PackKey + ":" + key + " is not a catchable creature."); } return registeredItem.Fishable; } public bool TryDescribe(Item? item, out RegisteredItem entry) { entry = null; if (Owns(item)) { return byId.TryGetValue(item.ID, out entry); } return false; } public bool Owns(Item? item) { if (disposed || !IsReady || !Object.op_Implicit((Object)(object)item) || !byId.TryGetValue(item.ID, out RegisteredItem value)) { return false; } OwnedContentIdentity component = ((Component)item).GetComponent(); if (Object.op_Implicit((Object)(object)component) && component.Configured && component.PackKey == PackKey && component.Key == value.Key) { return component.ItemId == item.ID; } return false; } public void Dispose() { if (disposed) { return; } disposed = true; IsReady = false; this.BeforeCommit = null; NativeContentDirectory.Detach(this); foreach (RegisteredItem value4 in registered.Values) { if (allItems.TryGetValue(value4.ItemId, out Item value) && value == value4.Prefab) { allItems.Remove(value4.ItemId); } if (spawnables.TryGetValue(value4.ItemId, out value) && value == value4.Prefab) { spawnables.Remove(value4.ItemId); } } foreach (KeyValuePair addedName in addedNames) { if (namedSpawnables.TryGetValue(addedName.Key, out Item value2) && value2 == addedName.Value) { namedSpawnables.Remove(addedName.Key); } } foreach (Creature item in addedJournal) { journal.Remove(item); } if (Object.op_Implicit((Object)(object)collection)) { Dictionary dictionary = NativeAccess.Get>(manager, "_runtimeSpawnablePrefabs"); if (dictionary.TryGetValue(CollectionId, out var value3) && value3 == collection) { dictionary.Remove(CollectionId); } collection.Clear(); Object.Destroy((Object)(object)collection); } foreach (Fishable fishable in fishables) { if (Object.op_Implicit((Object)(object)fishable)) { Object.Destroy((Object)(object)fishable); } } Visuals.Dispose(); foreach (PreparedItem value5 in prepared.Values) { if (Object.op_Implicit((Object)(object)value5.Clone)) { Object.Destroy((Object)(object)((Component)value5.Clone).gameObject); } } if (Object.op_Implicit((Object)(object)nursery)) { Object.Destroy((Object)(object)nursery); } prepared.Clear(); registered.Clear(); byId.Clear(); addedNames.Clear(); addedJournal.Clear(); fishables.Clear(); collection = null; nursery = null; } } public sealed class LootDrop { public Item Prefab { get; } public int FixedQuantity { get; } public int PerPlayerQuantity { get; } public ContentReference Reference { get; } public LootDrop(Item prefab, int fixedQuantity, int perPlayerQuantity, ContentReference reference) { if (!Object.op_Implicit((Object)(object)prefab) || !Object.op_Implicit((Object)(object)((Component)prefab).GetComponent()) || !Object.op_Implicit((Object)(object)((Component)prefab).GetComponent()) || ((Component)prefab).gameObject.activeInHierarchy) { throw new ArgumentException("A loot drop needs an inactive networked item prefab.", "prefab"); } if (fixedQuantity < 0 || fixedQuantity > 10000 || perPlayerQuantity < 0 || perPlayerQuantity > 10000 || fixedQuantity + perPlayerQuantity == 0) { throw new ArgumentOutOfRangeException("fixedQuantity", "Loot quantities must be 0..10000 with a non-zero total."); } Prefab = prefab; FixedQuantity = fixedQuantity; PerPlayerQuantity = perPlayerQuantity; Reference = reference; } public int QuantityFor(int players) { if (players < 0) { throw new ArgumentOutOfRangeException("players"); } long num = FixedQuantity + (long)PerPlayerQuantity * (long)players; if (num > 256) { throw new InvalidOperationException($"{Reference}: {num} drops exceed the per-death spawn bound."); } return (int)num; } } public sealed class NativeLootTable { public const int MaximumSpawnsPerDeath = 256; private readonly LootDrop[] drops; public string Key { get; } public IReadOnlyList Drops => drops; public ManualLogSource Log { get; } public int IssuedCount { get; private set; } public event Action>? Issued; public NativeLootTable(string key, IEnumerable drops, ManualLogSource? log = null) { if (!ContentReference.IsKey(key)) { throw new ArgumentException("Invalid loot key.", "key"); } this.drops = (drops ?? throw new ArgumentNullException("drops")).ToArray(); if (this.drops.Length == 0 || this.drops.Length > 64) { throw new ArgumentException("A loot table needs 1..64 drops.", "drops"); } if (this.drops.Select((LootDrop drop) => drop.Prefab).Distinct().Count() != this.drops.Length) { throw new ArgumentException("Duplicate loot drop items.", "drops"); } Key = key; Log = log ?? Logger.CreateLogSource("ExpansionKit.Loot"); } public bool TryIssue(Creature creature) { //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_021c: 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_0226: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_023a: 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_0254: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)creature)) { throw new ArgumentNullException("creature"); } if (!((NetworkBehaviour)creature).IsServerInitialized || !((NetworkBehaviour)creature).IsSpawned || ((NetworkBehaviour)creature).IsDeinitializing) { return false; } if (creature._hp.Value != 0 || NativeAccess.Get(creature, "<_spawnedByDynamite>k__BackingField")) { return false; } LootIssuedMarker lootIssuedMarker = ((Component)creature).GetComponent() ?? ((Component)creature).gameObject.AddComponent(); if (lootIssuedMarker.Issued.Contains(Key)) { return false; } if (!Object.op_Implicit((Object)(object)ItemManager.Instance)) { throw new InvalidOperationException("The native ItemManager is unavailable; cannot spawn loot."); } object? value = NativeAccess.Field(typeof(BossManager), "_instance").GetValue(null); object? target = ((value is BossManager) ? value : null) ?? throw new InvalidOperationException("The native boss reward controller is unavailable."); int players = PlayerManager.Players.Count; int num = drops.Sum((LootDrop drop) => drop.QuantityFor(players)); if (num > 256) { throw new InvalidOperationException($"{Key}: {num} drops exceed {256} per death."); } lootIssuedMarker.Issued.Add(Key); float num2 = NativeAccess.Get(target, "_trophyVel"); float num3 = NativeAccess.Get(target, "_trophyAngVel"); Vector3 val = (Object.op_Implicit((Object)(object)((Item)creature).Rig) ? ((Item)creature).Rig.worldCenterOfMass : ((Component)creature).transform.position); List list = new List(num); LootDrop[] array = drops; foreach (LootDrop lootDrop in array) { int num5 = lootDrop.QuantityFor(players); float num6 = ((num5 > 1 || lootDrop.PerPlayerQuantity > 0) ? 0.5f : 1f); for (int num7 = 0; num7 < num5; num7++) { Item val2 = ItemManager.Instance.SpawnNewItem(lootDrop.Prefab, val + Vector3.up * 0.5f, Quaternion.identity); if (!Object.op_Implicit((Object)(object)val2)) { throw new InvalidOperationException($"{lootDrop.Reference}: the native spawner returned no item."); } ResetModifiers(val2); val2.RigidbodySync.StartSimulateLocal(default(Vector3), default(Quaternion)); val2.Rig.linearVelocity = Vector3.up * (num2 * num6); val2.Rig.angularVelocity = Vector3.one * (num3 * num6); list.Add(val2); } } IssuedCount++; Log.LogInfo((object)$"Loot {Key}: {list.Count} items for {players} players from {((Object)creature).name} ({((Item)creature).ID})."); this.Issued?.Invoke(creature, list); return true; } public static void ResetModifiers(Item item) { if (!Object.op_Implicit((Object)(object)item) || !((NetworkBehaviour)item).IsServerInitialized) { return; } if (item._syncedRandomWeight.Value != 1f) { Creature val = (Creature)(object)((item is Creature) ? item : null); if (val == null || NativeAccess.Get(val, "_skipRandomizedWeight")) { item._syncedRandomWeight.Value = 1f; } } if (item._cookness.Value != 0f) { item._cookness.Value = 0f; } if (item._killScoreMultiplier.Value != 1f) { item._killScoreMultiplier.Value = 1f; } if (item._bettingMultiplier.Value != 1f) { item._bettingMultiplier.Value = 1f; } } } public sealed class LootIssuedMarker : MonoBehaviour { public HashSet Issued { get; } = new HashSet(StringComparer.Ordinal); public bool CombatObserved { get; set; } } public sealed class NativeLootBindings { private readonly Dictionary owned = new Dictionary(StringComparer.Ordinal); private readonly Dictionary native = new Dictionary(); public IReadOnlyCollection Tables => (IReadOnlyCollection)(object)owned.Values.Select<(NativeLootTable, bool), NativeLootTable>(((NativeLootTable Table, bool Automatic) pair) => pair.Table).Concat(native.Values.Select<(NativeLootTable, bool), NativeLootTable>(((NativeLootTable Table, bool Automatic) pair) => pair.Table)).Distinct() .ToArray(); public void Bind(ContentReference creature, NativeLootTable table, bool automatic = true) { if (table == null) { throw new ArgumentNullException("table"); } if (creature.IsNative) { if (!NativePackCatalog.IsCreature((int)creature.NativeId)) { throw new ArgumentException($"{creature} is not a native creature.", "creature"); } if (native.ContainsKey(creature.NativeId)) { throw new InvalidOperationException($"{creature} already has a loot table."); } native.Add(creature.NativeId, (table, automatic)); } else { string key = creature.ToString(); if (owned.ContainsKey(key)) { throw new InvalidOperationException($"{creature} already has a loot table."); } owned.Add(key, (table, automatic)); } } public bool TryGet(Creature creature, out NativeLootTable table) { bool automatic; return TryGet(creature, out table, out automatic); } public bool TryGet(Creature creature, out NativeLootTable table, out bool automatic) { table = null; automatic = false; if (!Object.op_Implicit((Object)(object)creature)) { return false; } (NativeLootTable, bool) value = default((NativeLootTable, bool)); bool num; if (!OwnedContentIdentity.TryGet((Item?)(object)creature, out OwnedContentIdentity identity)) { if (Object.op_Implicit((Object)(object)((Component)creature).GetComponent())) { goto IL_0072; } num = native.TryGetValue(((Item)creature).ID, out value); } else { num = owned.TryGetValue(identity.Reference.ToString(), out value); } if (num && value.Item1 != null) { (table, automatic) = value; return true; } goto IL_0072; IL_0072: return false; } public bool OnDeath(Creature creature) { if (TryGet(creature, out NativeLootTable table)) { return table.TryIssue(creature); } return false; } internal bool AutoOnDeath(Creature creature) { if (!TryGet(creature, out NativeLootTable table, out bool automatic) || !automatic) { return false; } LootIssuedMarker component = ((Component)creature).GetComponent(); if (!Object.op_Implicit((Object)(object)component) || !component.CombatObserved) { return false; } return table.TryIssue(creature); } public void Clear() { owned.Clear(); native.Clear(); } } public readonly struct CatchEntry { public Fishable Fishable { get; } public float Weight { get; } public CatchEntry(Fishable fishable, float weight) { if (!Object.op_Implicit((Object)(object)fishable) || !Object.op_Implicit((Object)(object)fishable.ItemToSpawn)) { throw new ArgumentException("A catch entry needs a Fishable with an item.", "fishable"); } if (!NativeAccess.Finite(weight) || weight < 1E-06f || weight > 1000000f) { throw new ArgumentOutOfRangeException("weight"); } Fishable = fishable; Weight = weight; } } public sealed class LureDefinition { public string Key { get; set; } = ""; public byte Id { get; set; } public string Title { get; set; } = ""; public string Description { get; set; } = ""; public int Cost { get; set; } public float LossPercent { get; set; } public float CatchTimeMin { get; set; } = 1f; public float CatchTimeMax { get; set; } = 3f; public bool? RequireReeling { get; set; } public List Catches { get; } = new List(); public BaitInfo? VisualDonor { get; set; } public AuthoredArt? Art { get; set; } public LureRecipe? Recipe { get; set; } } public sealed class CatchPatchDefinition { public string Key { get; set; } = ""; public BaitInfo Target { get; set; } public List Catches { get; } = new List(); public Func? Condition { get; set; } } public sealed class RegisteredLure { public string Key { get; } public byte Id { get; } public string Title { get; } public string Description { get; } public BaitInfo Bait { get; } public bool Authored { get; } public ContentReference Reference { get; } public bool Purchasable { get { if (Object.op_Implicit((Object)(object)Bait)) { return Bait.Cost > 0; } return false; } } internal RegisteredLure(string packKey, LureDefinition definition, BaitInfo bait) { Key = definition.Key; Id = definition.Id; Title = definition.Title; Description = definition.Description; Bait = bait; Authored = definition.Art != null; Reference = ContentReference.Pack(packKey, definition.Key); } } public static class NativeLureTable { private static readonly Dictionary Placeholders = new Dictionary(); public static IReadOnlyList ReservedIndices { get { IReadOnlyList allBaits = GameInfo.AllBaits; List list = new List(); for (int i = 17; i < allBaits.Count; i++) { if (IsPlaceholder(allBaits[i])) { list.Add(i); } } return list; } } public static bool IsPlaceholder(BaitInfo? bait) { if (Object.op_Implicit((Object)(object)bait)) { return Placeholders.ContainsKey(bait); } return false; } public static bool IsSelectable(int index) { if (index >= 0 && index < GameInfo.AllBaits.Count && Object.op_Implicit((Object)(object)GameInfo.AllBaits[index])) { return !IsPlaceholder(GameInfo.AllBaits[index]); } return false; } public static bool TryDescribePlaceholder(BaitInfo? bait, out string title, out string description) { title = ""; description = ""; if (!Object.op_Implicit((Object)(object)bait) || !Placeholders.TryGetValue(bait, out var value)) { return false; } title = "Reserved lure slot " + value; description = "This expansion lure slot is not filled by the installed packs."; return true; } public static IReadOnlyList InspectSavedInventory(IReadOnlyList? ownedBaits) { List list = new List(); if (ownedBaits == null) { return list; } IReadOnlyList allBaits = GameInfo.AllBaits; for (int i = 0; i < ownedBaits.Count; i++) { int num = i + 1; if (ownedBaits[i] > 0) { if (num >= allBaits.Count) { list.Add($"Saved lure counter {ownedBaits[i]} at table index {num} exceeds the installed table ({allBaits.Count} entries)."); } else if (IsPlaceholder(allBaits[num])) { list.Add($"Saved lure counter {ownedBaits[i]} at reserved index {num} has no installed lure; install the owning pack instead of remapping."); } } } return list; } internal static BaitInfo CreatePlaceholder(int index, BaitInfo empty) { if (!Object.op_Implicit((Object)(object)empty)) { throw new InvalidOperationException("The native empty bait is unavailable for reserved slots."); } BaitInfo val = Object.Instantiate(empty); ((Object)val).name = "ExpansionKit_ReservedLureSlot_" + index; NativeAccess.Set(val, "_name", "Reserved lure slot " + index); NativeAccess.Set(val, "_description", "Unfilled expansion lure slot."); NativeAccess.Set(val, "_cost", 0); Object.DontDestroyOnLoad((Object)(object)val); Placeholders.Add(val, index); return val; } internal static void Release(BaitInfo placeholder) { if (Object.op_Implicit((Object)(object)placeholder)) { Placeholders.Remove(placeholder); Object.Destroy((Object)(object)placeholder); } } internal static void TrimTrailing(List allBaits) { while (allBaits.Count > 17 && IsPlaceholder(allBaits[allBaits.Count - 1])) { BaitInfo placeholder = allBaits[allBaits.Count - 1]; allBaits.RemoveAt(allBaits.Count - 1); Release(placeholder); } } } public sealed class NativeLureCatalog : IDisposable { public const int NativeBaitCount = 17; private readonly List pending = new List(); private readonly List patches = new List(); private readonly Dictionary registered = new Dictionary(StringComparer.Ordinal); private readonly Dictionary byBait = new Dictionary(); private readonly Dictionary palettes = new Dictionary(); private readonly Dictionary previewOriginals = new Dictionary(); private readonly Dictionary baitPoses = new Dictionary(); private readonly Dictionary baitScales = new Dictionary(); private readonly Dictionary, List> patchedTables = new Dictionary, List>(); private readonly Dictionary replacedPlaceholders = new Dictionary(); private readonly List owned = new List(); private readonly List allBaits; private readonly BaitInfo empty; private BaitInfo[] nativeOriginals = Array.Empty(); private ItemInfoWeight[][] nativeEntries = Array.Empty(); private bool disposed; public string PackKey { get; } public ManualLogSource Log { get; } public bool IsReady { get; private set; } public bool IsCommitted { get; private set; } public string RegistrationHash { get; private set; } = ""; public IReadOnlyList Lures => registered.Values.OrderBy((RegisteredLure lure) => lure.Id).ToArray(); public IReadOnlyList CatchPatches => patches; public NativeLureCatalog(string packKey, ManualLogSource? log = null) { if (!ContentReference.IsKey(packKey) || packKey == "native") { throw new ArgumentException("Invalid pack key.", "packKey"); } PackKey = packKey; Log = log ?? Logger.CreateLogSource("ExpansionKit.Lures." + packKey); allBaits = NativeAccess.Static>(typeof(GameInfo), "_allBaits"); if (allBaits.Count < 17) { throw new InvalidOperationException("GameInfo has not loaded the native bait table yet."); } GameInfo target = NativeAccess.Static(typeof(GameInfo), "_instance"); empty = NativeAccess.Get(target, "_emptyBait"); if ((Object)(object)allBaits[0] != (Object)(object)empty || (Object)(object)allBaits[1] != (Object)(object)empty) { throw new InvalidOperationException("The native bait table no longer aliases the empty bait at slots 0 and 1; refusing to extend an unknown layout."); } if (NativeContentDirectory.FindLureCatalog(packKey) != null) { throw new InvalidOperationException("Pack " + packKey + " already has a lure catalog."); } NativeContentDirectory.Attach(this); } private void RequireOpen() { if (disposed) { throw new ObjectDisposedException("NativeLureCatalog"); } if (IsCommitted) { throw new InvalidOperationException("The lure catalog is already committed."); } } public static BaitInfo NativeLure(byte index) { if (!NativePackCatalog.Contains((ContentDomain)1, (int)index)) { throw new ArgumentOutOfRangeException("index", $"Native lure indices are 0..{16}."); } IReadOnlyList readOnlyList = GameInfo.AllBaits; if (index >= readOnlyList.Count || !Object.op_Implicit((Object)(object)readOnlyList[index])) { throw new InvalidOperationException($"Native bait {index} is unavailable."); } return readOnlyList[index]; } public void Add(LureDefinition lure) { RequireOpen(); if (lure == null) { throw new ArgumentNullException("lure"); } if (!ContentReference.IsKey(lure.Key)) { throw new ArgumentException("Invalid lure key.", "lure"); } if (lure.Id < 17) { throw new ArgumentOutOfRangeException("lure", $"Custom lure indices start at {17}."); } if (string.IsNullOrWhiteSpace(lure.Title) || lure.Title.Length > 160 || lure.Description == null || lure.Description.Length > 2048) { throw new ArgumentException("Invalid lure title/description.", "lure"); } if (lure.Cost < 0 || lure.Cost > 100000000) { throw new ArgumentOutOfRangeException("lure", "Lure cost is out of bounds."); } if (!NativeAccess.Finite(lure.LossPercent) || lure.LossPercent < 0f || lure.LossPercent > 100f) { throw new ArgumentOutOfRangeException("lure", "Loss percent must be 0..100."); } if (!NativeAccess.Positive(lure.CatchTimeMin) || !NativeAccess.Positive(lure.CatchTimeMax) || lure.CatchTimeMax < lure.CatchTimeMin || lure.CatchTimeMax > 86400f) { throw new ArgumentOutOfRangeException("lure", "Catch times must be positive with maximum >= minimum."); } if (lure.Catches.Count == 0 || lure.Catches.Count > 128) { throw new ArgumentException("A lure needs 1..128 catch entries.", "lure"); } if (lure.Catches.Select((CatchEntry entry) => entry.Fishable).Distinct().Count() != lure.Catches.Count) { throw new ArgumentException("Duplicate catch entries.", "lure"); } if (lure.Art == null && !Object.op_Implicit((Object)(object)lure.VisualDonor)) { throw new ArgumentException(PackKey + ":" + lure.Key + ": a lure needs authored art or a native visual donor.", "lure"); } if ((Object.op_Implicit((Object)(object)lure.VisualDonor) && !NativePackCatalog.Contains((ContentDomain)1, (int)GameInfo.GetIndexOfBait(lure.VisualDonor))) || (Object.op_Implicit((Object)(object)lure.VisualDonor) && (Object)(object)GameInfo.AllBaits[GameInfo.GetIndexOfBait(lure.VisualDonor)] != (Object)(object)lure.VisualDonor)) { throw new ArgumentException(PackKey + ":" + lure.Key + ": the visual donor is not a native bait.", "lure"); } if (Object.op_Implicit((Object)(object)lure.VisualDonor) && (!Object.op_Implicit((Object)(object)lure.VisualDonor.Mesh) || !Object.op_Implicit((Object)(object)lure.VisualDonor.MeshForNpc))) { throw new ArgumentException(PackKey + ":" + lure.Key + ": the native visual donor has no mesh.", "lure"); } if (pending.Any((LureDefinition other) => other.Key == lure.Key || other.Id == lure.Id)) { throw new InvalidOperationException($"{PackKey}: duplicate lure key or index {lure.Key}/{lure.Id}."); } string name = PackKey + "_" + lure.Key; if (allBaits.Any((BaitInfo bait) => Object.op_Implicit((Object)(object)bait) && string.Equals(((Object)bait).name, name, StringComparison.OrdinalIgnoreCase))) { throw new InvalidOperationException("Bait asset name " + name + " is already present in the table."); } pending.Add(lure); } public void AddCatchPatch(CatchPatchDefinition patch) { RequireOpen(); if (patch == null) { throw new ArgumentNullException("patch"); } if (!ContentReference.IsKey(patch.Key)) { throw new ArgumentException("Invalid patch key.", "patch"); } if (!Object.op_Implicit((Object)(object)patch.Target) || patch.Target.ItemWeights == null) { throw new ArgumentException("A catch patch needs a target bait with a catch table.", "patch"); } if (patch.Catches.Count == 0 || patch.Catches.Count > 128) { throw new ArgumentException("A catch patch needs 1..128 entries.", "patch"); } if (patches.Any((CatchPatchDefinition other) => other.Key == patch.Key)) { throw new InvalidOperationException(PackKey + ": duplicate catch patch " + patch.Key + "."); } patches.Add(patch); } public void Commit() { RequireOpen(); try { if (!Object.op_Implicit((Object)(object)InstanceFinder.NetworkManager) || InstanceFinder.NetworkManager.IsServerStarted || InstanceFinder.NetworkManager.IsClientStarted) { throw new InvalidOperationException("Register lures before any host or client starts."); } if (pending.Count == 0 && patches.Count == 0) { throw new InvalidOperationException(PackKey + ": no lures or catch patches to commit."); } if (((pending.Count == 0) ? (allBaits.Count - 1) : Math.Max(allBaits.Count - 1, ((IEnumerable)pending).Max((Func)((LureDefinition lure) => lure.Id)))) >= 255) { throw new InvalidOperationException("The bait table cannot exceed 255 entries (saved byte indices)."); } nativeOriginals = allBaits.Take(17).ToArray(); nativeEntries = nativeOriginals.Select((BaitInfo bait) => bait.ItemWeights?.ToArray() ?? Array.Empty()).ToArray(); foreach (LureDefinition item in pending) { if (item.Id < allBaits.Count && !NativeLureTable.IsPlaceholder(allBaits[item.Id])) { BaitInfo val = allBaits[item.Id]; string title; string description; string text = (NativeContentDirectory.TryDescribeLure(val, out title, out description) ? title : (Object.op_Implicit((Object)(object)val) ? ((Object)val).name : "")); throw new InvalidOperationException($"{PackKey}:{item.Key} declares bait index {item.Id}, which is occupied by '{text}'; lure indices are never renumbered."); } } foreach (LureDefinition item2 in pending.OrderBy((LureDefinition lure) => lure.Id)) { BaitInfo val2 = Create(item2); while (allBaits.Count < item2.Id) { allBaits.Add(NativeLureTable.CreatePlaceholder(allBaits.Count, empty)); } if (item2.Id == allBaits.Count) { allBaits.Add(val2); } else { replacedPlaceholders.Add(item2.Id, allBaits[item2.Id]); allBaits[item2.Id] = val2; } RegisteredLure value = new RegisteredLure(PackKey, item2, val2); registered.Add(item2.Key, value); byBait.Add(val2, value); } pending.Clear(); IsCommitted = true; IsReady = true; RegistrationHash = ComputeHash(); Validate(); IReadOnlyList reservedIndices = NativeLureTable.ReservedIndices; Log.LogInfo((object)(string.Format("{0}: {1} lures at indices {2}; ", PackKey, registered.Count, string.Join(", ", registered.Values.Select((RegisteredLure lure) => lure.Id))) + string.Format("{0} additive catch patches; reserved placeholders at [{1}]; table hash {2}.", patches.Count, string.Join(", ", reservedIndices), RegistrationHash.Substring(0, 16)))); } catch (Exception arg) { Log.LogError((object)$"{PackKey}: lure registration failed and is being rolled back: {arg}"); Dispose(); throw; } } private BaitInfo Create(LureDefinition lure) { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_0304: Unknown result type (might be due to invalid IL or missing references) //IL_0332: 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) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0100: 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_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_013f: 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_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: 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_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0214: 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_021d: Unknown result type (might be due to invalid IL or missing references) //IL_026c: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_027d: 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_0288: Unknown result type (might be due to invalid IL or missing references) //IL_028d: 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) BaitInfo val = ScriptableObject.CreateInstance(); owned.Add((Object)(object)val); ((Object)val).name = PackKey + "_" + lure.Key; Object.DontDestroyOnLoad((Object)(object)val); NativeAccess.Set(val, "_name", lure.Title); NativeAccess.Set(val, "_description", lure.Description); NativeAccess.Set(val, "_cost", lure.Cost); NativeAccess.Set(val, "_lostOnBaitChance", lure.LossPercent); NativeAccess.Set(val, "_catchTimeMinMax", (object)new Vector2(lure.CatchTimeMin, lure.CatchTimeMax)); NativeAccess.Set(val, "_itemWeights", ((IEnumerable)lure.Catches).Select((Func)delegate(CatchEntry entry) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_002f: Expected O, but got Unknown ItemInfoWeight val8 = new ItemInfoWeight(); NativeAccess.Set((object)val8, "fishable", entry.Fishable); NativeAccess.Set((object)val8, "_weight", entry.Weight); return val8; }).ToList()); if (lure.Art != null) { AuthoredArt? art = lure.Art; Vector3 val2 = art.Marker("line"); Vector3 val3 = art.Marker("hook"); Vector3 val4 = val3 - val2; if (((Vector3)(ref val4)).sqrMagnitude < 1E-08f) { throw new InvalidOperationException(PackKey + ":" + lure.Key + ": marker_line and marker_hook coincide."); } Quaternion val5 = Quaternion.FromToRotation(val3 - val2, Vector3.down); Matrix4x4 artToTarget = Matrix4x4.TRS(-(val5 * val2), val5, Vector3.one); Material[] palette; Mesh val6 = art.Combine(artToTarget, out palette, ((Object)val).name + " - lure"); owned.Add((Object)(object)val6); NativeAccess.Set(val, "_mesh", val6); NativeAccess.Set(val, "_meshForNpc", val6); NativeAccess.Set(val, "_requireReelingToCatch", lure.RequireReeling ?? true); NativeAccess.Set(val, "_hookPoint", ((Matrix4x4)(ref artToTarget)).MultiplyPoint3x4(val3)); NativeAccess.Set(val, "_inventoryRot", (object)new Vector3(-15f, 30f, -30f)); Bounds bounds = val6.bounds; val4 = ((Bounds)(ref bounds)).size; float num = 0.4f / ((Vector3)(ref val4)).magnitude; if (!NativeAccess.Positive(num)) { throw new InvalidOperationException(PackKey + ":" + lure.Key + ": degenerate lure geometry."); } NativeAccess.Set(val, "inventoryMeshScale", num); Quaternion val7 = Quaternion.Euler(val.InventoryMeshRot); bounds = val6.bounds; NativeAccess.Set(val, "inventoryMeshPos", -(val7 * (((Bounds)(ref bounds)).center * num))); palettes.Add(val, palette); art.Dispose(); } else { BaitInfo visualDonor = lure.VisualDonor; NativeAccess.Set(val, "_mesh", visualDonor.Mesh); NativeAccess.Set(val, "_meshForNpc", visualDonor.MeshForNpc); NativeAccess.Set(val, "_hookPoint", visualDonor.HookPoint); NativeAccess.Set(val, "_inventoryRot", visualDonor.InventoryMeshRot); NativeAccess.Set(val, "inventoryMeshScale", visualDonor.InventoryMeshScale); NativeAccess.Set(val, "inventoryMeshPos", visualDonor.InventoryMeshPos); NativeAccess.Set(val, "_requireReelingToCatch", lure.RequireReeling ?? visualDonor.RequireReelingToCatch); } return val; } public RegisteredLure Get(string key) { if (disposed || key == null || !registered.TryGetValue(key, out RegisteredLure value)) { throw new KeyNotFoundException("Unknown registered lure " + PackKey + ":" + key + "."); } return value; } public bool TryGet(string key, out RegisteredLure lure) { lure = null; if (!disposed && key != null) { return registered.TryGetValue(key, out lure); } return false; } public byte Index(string key) { RegisteredLure registeredLure = Get(key); byte indexOfBait = GameInfo.GetIndexOfBait(registeredLure.Bait); if (indexOfBait != registeredLure.Id) { throw new InvalidOperationException($"{registeredLure.Reference} drifted from index {registeredLure.Id} to {indexOfBait}."); } return indexOfBait; } public BaitInfo Resolve(ContentReference reference) { if (reference.IsNative) { return NativeLure(reference.NativeId); } if (reference.PackKey != PackKey) { throw new KeyNotFoundException($"{reference} belongs to another pack; resolve through that pack's catalog."); } return Get(reference.Key).Bait; } public bool Owns(BaitInfo? bait) { if (IsReady && Object.op_Implicit((Object)(object)bait)) { return byBait.ContainsKey(bait); } return false; } public bool TryDescribe(BaitInfo bait, out string title, out string description) { title = ""; description = ""; if (!Owns(bait)) { return false; } RegisteredLure registeredLure = byBait[bait]; title = registeredLure.Title; description = registeredLure.Description; return true; } public bool TryPatchCatchTable(List weights, out List patched) { //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Expected O, but got Unknown patched = weights; if (!IsReady || weights == null || patches.Count == 0) { return false; } List list = null; foreach (CatchPatchDefinition patch in patches) { if (!Object.op_Implicit((Object)(object)patch.Target) || patch.Target.ItemWeights != weights || (patch.Condition != null && !patch.Condition())) { continue; } if (list == null) { list = new List(weights); } if (!patchedTables.TryGetValue(weights, out List value)) { patchedTables.Add(weights, value = new List()); } foreach (CatchEntry entry in patch.Catches) { ItemInfoWeight val = ((IEnumerable)value).FirstOrDefault((Func)((ItemInfoWeight val2) => (Object)(object)val2.Fishable == (Object)(object)entry.Fishable && val2.Weight == entry.Weight)); if (val == null) { val = new ItemInfoWeight(); NativeAccess.Set(val, "fishable", entry.Fishable); NativeAccess.Set(val, "_weight", entry.Weight); value.Add(val); } list.Add(val); } } if (list == null) { return false; } double num = ((IEnumerable)list).Sum((Func)((ItemInfoWeight val2) => val2.Weight)); if (num <= 0.0 || num > 3.4028234663852886E+38) { throw new InvalidOperationException(PackKey + ": a patched catch table has an invalid total weight."); } patched = list; return true; } public bool ApplyPreview(Renderer renderer, BaitInfo bait) { if (!Object.op_Implicit((Object)(object)renderer)) { throw new ArgumentNullException("renderer"); } if (!Owns(bait) || !palettes.TryGetValue(bait, out Material[] value)) { return false; } if (!previewOriginals.ContainsKey(renderer)) { previewOriginals.Add(renderer, renderer.sharedMaterials); } renderer.sharedMaterials = value; return true; } public void RestorePreview(Renderer renderer) { if (Object.op_Implicit((Object)(object)renderer) && previewOriginals.TryGetValue(renderer, out Material[] value)) { renderer.sharedMaterials = value; previewOriginals.Remove(renderer); } } public void ApplyBait(Bait bait) { //IL_00e7: 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_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: 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_00b4: 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_00ca: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)bait) || !IsReady) { return; } MeshFilter obj = NativeAccess.Get(bait, "_meshFilter"); Transform transform = ((Component)obj).transform; Pose value; if (Owns(bait.Info) && palettes.ContainsKey(bait.Info) && (Object)(object)transform != (Object)(object)((Component)bait).transform) { if (!baitPoses.ContainsKey(transform)) { baitPoses.Add(transform, new Pose(transform.localPosition, transform.localRotation)); baitScales.Add(transform, transform.localScale); } if ((Object)(object)transform.parent != (Object)(object)((Component)bait).transform) { throw new InvalidOperationException("The native bait mesh is no longer a direct child."); } transform.localPosition = Vector3.zero; transform.localRotation = Quaternion.identity; transform.localScale = Vector3.one; } else if (baitPoses.TryGetValue(transform, out value)) { transform.SetLocalPositionAndRotation(value.position, value.rotation); transform.localScale = baitScales[transform]; baitPoses.Remove(transform); baitScales.Remove(transform); } Renderer component = ((Component)obj).GetComponent(); if (Object.op_Implicit((Object)(object)component) && !ApplyPreview(component, bait.Info)) { RestorePreview(component); } } public void EnsureInventoryCapacity(PlayerInventory inventory) { if (!IsReady || !Object.op_Implicit((Object)(object)inventory) || !((NetworkBehaviour)inventory).IsServerInitialized) { return; } SavedPlayer val = NativeAccess.Optional(inventory, "_toLoadFrom"); if (val != null) { IReadOnlyList readOnlyList = NativeLureTable.InspectSavedInventory(val.OwnedBaits); if (readOnlyList.Count > 0) { throw new InvalidOperationException("The saved lure layout does not match the installed packs: " + string.Join(" ", readOnlyList)); } } while (inventory._ownedBaits.Count < GameInfo.AllBaits.Count - 1) { inventory._ownedBaits.Add(0); } } public void ConfigureStand(BaitPurchasable stand, string key) { if (!Object.op_Implicit((Object)(object)stand)) { throw new ArgumentNullException("stand"); } RegisteredLure registeredLure = Get(key); if (!registeredLure.Purchasable) { throw new InvalidOperationException($"{registeredLure.Reference} is earned, not purchasable."); } BaitInfo source = NativeAccess.Get(stand, "_bait"); MeshFilter[] array = (from filter in ((Component)stand).GetComponentsInChildren(true) where (Object)(object)filter.sharedMesh == (Object)(object)source.Mesh || (Object)(object)filter.sharedMesh == (Object)(object)source.MeshForNpc select filter).ToArray(); if (array.Length == 0) { throw new InvalidOperationException("The native lure stand has no matching bait mesh."); } NativeAccess.Set(stand, "_bait", registeredLure.Bait); NativeAccess.Set(stand, "_isFree", false); NativeAccess.Set(stand, "_customCost", registeredLure.Bait.Cost); MeshFilter[] array2 = array; foreach (MeshFilter obj in array2) { obj.sharedMesh = registeredLure.Bait.MeshForNpc; Renderer component = ((Component)obj).GetComponent(); if (Object.op_Implicit((Object)(object)component) && !ApplyPreview(component, registeredLure.Bait)) { RestorePreview(component); } } } public void Validate() { //IL_0221: Unknown result type (might be due to invalid IL or missing references) if (disposed) { throw new ObjectDisposedException("NativeLureCatalog"); } if (!IsReady || allBaits != NativeAccess.Static>(typeof(GameInfo), "_allBaits")) { throw new InvalidOperationException(PackKey + ": lure registration is incomplete or the bait table was replaced."); } for (int i = 0; i < nativeOriginals.Length; i++) { if ((Object)(object)allBaits[i] != (Object)(object)nativeOriginals[i]) { throw new InvalidOperationException($"{PackKey}: a native bait reference changed at index {i}."); } if (!(nativeOriginals[i].ItemWeights?.ToArray() ?? Array.Empty()).SequenceEqual(nativeEntries[i])) { throw new InvalidOperationException($"{PackKey}: a native bait's catch table was mutated at index {i}."); } } for (int j = 17; j < allBaits.Count; j++) { if (!Object.op_Implicit((Object)(object)allBaits[j])) { throw new InvalidOperationException($"{PackKey}: bait table index {j} is empty; every slot must hold a lure or a reserved placeholder."); } } foreach (RegisteredLure value2 in registered.Values) { if (value2.Id >= allBaits.Count || (Object)(object)allBaits[value2.Id] != (Object)(object)value2.Bait || GameInfo.GetIndexOfBait(value2.Bait) != value2.Id || !Object.op_Implicit((Object)(object)value2.Bait.Mesh) || !Object.op_Implicit((Object)(object)value2.Bait.MeshForNpc) || value2.Bait.ItemWeights == null || value2.Bait.ItemWeights.Count == 0) { throw new InvalidOperationException($"{value2.Reference} lost its canonical index, mesh or catch table."); } if (palettes.TryGetValue(value2.Bait, out Material[] value) && (value2.Bait.Mesh.subMeshCount != value.Length || value2.Bait.HookPoint.y >= 0f)) { throw new InvalidOperationException($"{value2.Reference} lost its authored palette or hook."); } } } private string ComputeHash() { StringBuilder stringBuilder = new StringBuilder("ExpansionKit.lures.v2|").Append(PackKey); for (int i = 0; i < allBaits.Count; i++) { BaitInfo val = allBaits[i]; stringBuilder.Append('|').Append(i).Append(':'); if (NativeLureTable.IsPlaceholder(val)) { stringBuilder.Append(""); continue; } stringBuilder.Append(((Object)val).name).Append(':').Append(val.Cost); if (val.ItemWeights == null) { continue; } foreach (ItemInfoWeight itemWeight in val.ItemWeights) { stringBuilder.Append(';').Append((Object.op_Implicit((Object)(object)itemWeight.Fishable) && Object.op_Implicit((Object)(object)itemWeight.Fishable.ItemToSpawn)) ? itemWeight.Fishable.ItemToSpawn.ID : (-1)).Append('=') .Append(itemWeight.Weight.ToString("R", CultureInfo.InvariantCulture)); } } foreach (CatchPatchDefinition item in patches.OrderBy((CatchPatchDefinition patch) => patch.Key, StringComparer.Ordinal)) { stringBuilder.Append("|patch:").Append(item.Key).Append('@') .Append(((Object)item.Target).name); foreach (CatchEntry @catch in item.Catches) { stringBuilder.Append(';').Append(@catch.Fishable.ItemToSpawn.ID).Append('=') .Append(@catch.Weight.ToString("R", CultureInfo.InvariantCulture)); } } using SHA256 sHA = SHA256.Create(); return string.Concat(from value in sHA.ComputeHash(Encoding.UTF8.GetBytes(stringBuilder.ToString())) select value.ToString("x2")); } public void Dispose() { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) if (disposed) { return; } disposed = true; IsReady = false; NativeContentDirectory.Detach(this); foreach (KeyValuePair previewOriginal in previewOriginals) { if (Object.op_Implicit((Object)(object)previewOriginal.Key)) { previewOriginal.Key.sharedMaterials = previewOriginal.Value; } } foreach (KeyValuePair baitPose in baitPoses) { if (Object.op_Implicit((Object)(object)baitPose.Key)) { baitPose.Key.SetLocalPositionAndRotation(baitPose.Value.position, baitPose.Value.rotation); baitPose.Key.localScale = baitScales[baitPose.Key]; } } foreach (RegisteredLure item in registered.Values.OrderByDescending((RegisteredLure lure) => lure.Id)) { int num = allBaits.IndexOf(item.Bait); if (num >= 0) { if (replacedPlaceholders.TryGetValue(num, out BaitInfo value) && Object.op_Implicit((Object)(object)value) && NativeLureTable.IsPlaceholder(value)) { allBaits[num] = value; } else { allBaits[num] = NativeLureTable.CreatePlaceholder(num, empty); } } } replacedPlaceholders.Clear(); NativeLureTable.TrimTrailing(allBaits); foreach (LureDefinition item2 in pending) { item2.Art?.Dispose(); } foreach (Object item3 in owned) { if (Object.op_Implicit(item3)) { Object.Destroy(item3); } } owned.Clear(); pending.Clear(); patches.Clear(); registered.Clear(); byBait.Clear(); palettes.Clear(); previewOriginals.Clear(); baitPoses.Clear(); baitScales.Clear(); patchedTables.Clear(); } } public enum MeshAdapterKind { NativeDonor, Rigid, Articulated, Custom } public static class RigidMeshAdapter { public sealed class Options { public string GripMarker { get; set; } = "grip_right"; public Quaternion Presentation { get; set; } = Quaternion.identity; public Vector3? InventoryRotation { get; set; } public bool ConvertToNativeCookingMaterials { get; set; } = true; } public static Material[] Apply(Item item, Item donor, AuthoredArt art, ContentVisuals visuals, Options? options = null) { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: 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_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: 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_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0119: 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_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0120: 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_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013f: 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_0146: Unknown result type (might be due to invalid IL or missing references) //IL_025c: 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_0267: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Unknown result type (might be due to invalid IL or missing references) //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02d5: Unknown result type (might be due to invalid IL or missing references) //IL_02f7: Unknown result type (might be due to invalid IL or missing references) //IL_0305: Unknown result type (might be due to invalid IL or missing references) //IL_030a: Unknown result type (might be due to invalid IL or missing references) //IL_038f: Unknown result type (might be due to invalid IL or missing references) //IL_0394: Unknown result type (might be due to invalid IL or missing references) //IL_03a3: Unknown result type (might be due to invalid IL or missing references) //IL_0324: Unknown result type (might be due to invalid IL or missing references) //IL_0332: Unknown result type (might be due to invalid IL or missing references) //IL_0337: Unknown result type (might be due to invalid IL or missing references) //IL_0341: Unknown result type (might be due to invalid IL or missing references) //IL_0346: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)item) || !Object.op_Implicit((Object)(object)donor) || art == null || visuals == null) { throw new ArgumentNullException(); } if (item is Tool) { throw new NotSupportedException("Tool rigs (rods/weapons) need a custom binding; the rigid adapter only handles single-mesh items."); } if (options == null) { options = new Options(); } MeshRenderer[] array = NativeAccess.Get>(item, "_renderers").OfType().ToArray(); if (array.Length != 1 || ((Component)item).GetComponentsInChildren(true).Length != 0) { throw new InvalidOperationException(((Object)item).name + " is not a single rigid-mesh item; use the articulated adapter or a custom binding."); } MeshRenderer val = array[0]; MeshFilter component = ((Component)val).GetComponent(); if (!Object.op_Implicit((Object)(object)component)) { throw new InvalidOperationException("The native rigid renderer has no MeshFilter."); } HandTransforms handTransformsRight = item.HandTransformsRight; Vector3 val2 = ((handTransformsRight != null && handTransformsRight.Exists && Object.op_Implicit((Object)(object)handTransformsRight.Parent)) ? handTransformsRight.Parent.TransformPoint(handTransformsRight.HandPos) : ((Component)item).transform.position); Vector3 val3 = (art.HasMarker(options.GripMarker) ? art.Marker(options.GripMarker) : Vector3.zero); Quaternion val4 = ((Component)item).transform.rotation * Quaternion.Inverse(Quaternion.Euler(item.HeldRot)) * options.Presentation; Matrix4x4 val5 = Matrix4x4.TRS(val2 - val4 * val3, val4, Vector3.one); Material[] palette; Mesh val6 = art.Combine(((Component)val).transform.worldToLocalMatrix * val5, out palette, ((Object)item).name + " - rigid body"); visuals.Own((Object)(object)val6); Mesh val7 = Object.Instantiate(art.Preview); ((Object)val7).name = ((Object)item).name + " - inventory"; visuals.Own((Object)(object)val7); Material[] array2 = palette; if (options.ConvertToNativeCookingMaterials && NativeCookingMaterials.DonorSupports(donor)) { array2 = NativeCookingMaterials.Create(donor, palette, visuals.OwnedAssets); } component.sharedMesh = val6; ((Renderer)val).sharedMaterials = array2; ((Renderer)val).enabled = true; NativeAccess.Set(item, "_skinPreset", null); NativeAccess.Set(item, "_skinRenderers", new List()); if (item is Creature) { NativeAccess.Set(item, "_dripMesh", val7); } InventoryPreview.Fit(item, donor, val7, options.InventoryRotation); Collider[] array3 = NativeAccess.Get(item, "_worldColliders"); if (array3.Length == 1) { Collider obj = array3[0]; BoxCollider val8 = (BoxCollider)(object)((obj is BoxCollider) ? obj : null); if (val8 != null) { Bounds val9 = BoundsIn(val6, ((Component)val).transform, ((Component)val8).transform); val8.center = ((Bounds)(ref val9)).center; val8.size = ((Bounds)(ref val9)).size + Vector3.one * 0.015f; Collider val10 = NativeAccess.Get(item, "_pickUpCollider"); if ((Object)(object)val10 == (Object)(object)val8) { throw new InvalidOperationException(((Object)item).name + " aliases its pickup and body colliders."); } Bounds val11 = BoundsIn(val6, ((Component)val).transform, ((Component)val10).transform); SphereCollider val12 = (SphereCollider)(object)((val10 is SphereCollider) ? val10 : null); if (val12 == null) { BoxCollider val13 = (BoxCollider)(object)((val10 is BoxCollider) ? val10 : null); if (val13 == null) { throw new InvalidOperationException(((Object)item).name + ": unsupported native pickup collider " + ((object)val10).GetType().Name + "."); } val13.center = ((Bounds)(ref val11)).center; val13.size = ((Bounds)(ref val11)).size + Vector3.one * 0.04f; } else { val12.center = ((Bounds)(ref val11)).center; Vector3 extents = ((Bounds)(ref val11)).extents; val12.radius = ((Vector3)(ref extents)).magnitude + 0.03f; } val10.isTrigger = true; Bounds val14 = BoundsIn(val6, ((Component)val).transform, ((Component)item).transform); NativeAccess.Set(item, "_modelHeight", Mathf.Max(0.02f, 0f - ((Bounds)(ref val14)).min.y)); visuals.RegisterPalette(item.ID, array2); return array2; } } throw new InvalidOperationException(((Object)item).name + ": the rigid adapter expects one native box body collider."); } internal static Bounds BoundsIn(Mesh mesh, Transform source, Transform target) { //IL_0001: 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_000c: 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_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: 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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: 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_0070: Unknown result type (might be due to invalid IL or missing references) Matrix4x4 val = target.worldToLocalMatrix * source.localToWorldMatrix; Vector3[] vertices = mesh.vertices; if (vertices.Length == 0) { throw new InvalidOperationException("The authored mesh has no vertices."); } Bounds result = default(Bounds); ((Bounds)(ref result))..ctor(((Matrix4x4)(ref val)).MultiplyPoint3x4(vertices[0]), Vector3.zero); Vector3[] array = vertices; foreach (Vector3 val2 in array) { ((Bounds)(ref result)).Encapsulate(((Matrix4x4)(ref val)).MultiplyPoint3x4(val2)); } return result; } } public static class ArticulatedMeshAdapter { public sealed class Options { public Func? Anatomy { get; set; } public Color? ShinyAccent { get; set; } public bool ConvertToNativeCookingMaterials { get; set; } = true; } public static void Apply(Creature item, Item donor, AuthoredArt art, ContentVisuals visuals, Options? options = null) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: 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) if (!Object.op_Implicit((Object)(object)item) || !Object.op_Implicit((Object)(object)donor) || art == null || visuals == null) { throw new ArgumentNullException(); } if (options == null) { options = new Options(); } Mesh val = Object.Instantiate(art.Preview); ((Object)val).name = ((Object)item).name + " - articulated body"; visuals.Own((Object)(object)val); Material[] palette = art.Palette; Material[] array = palette; if ((int)item.BossType == 0) { Color val2 = (Color)(((??)options.ShinyAccent) ?? new Color(0.95f, 0.6f, 0.35f)); Dictionary changed = new Dictionary(); foreach (Material item2 in palette.Distinct()) { Material val3 = new Material(item2) { name = ((Object)item2).name + " - shiny" }; string[] array2 = new string[2] { "_BaseColor", "_Color" }; foreach (string text in array2) { if (val3.HasProperty(text)) { val3.SetColor(text, Color.Lerp(val3.GetColor(text), val2, 0.45f)); } } changed.Add(item2, val3); visuals.Own((Object)(object)val3); } array = palette.Select((Material material) => changed[material]).ToArray(); } Material[] array3 = palette; Material[] array4 = array; if (options.ConvertToNativeCookingMaterials) { array3 = NativeCookingMaterials.Create(donor, palette, visuals.OwnedAssets); array4 = (Material[])(((int)item.BossType == 0) ? ((Array)NativeCookingMaterials.Create(donor, array, visuals.OwnedAssets)) : ((Array)array3)); } Bind(item, art, val, array3, array4, options.Anatomy); SkinnedMeshRenderer[] componentsInChildren = ((Component)item).GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { AuthoredCookSurface.Bind((Renderer)(object)componentsInChildren[i]); } Mesh val4 = Object.Instantiate(art.Preview); ((Object)val4).name = ((Object)item).name + " - inventory"; visuals.Own((Object)(object)val4); InventoryPreview.Fit((Item)(object)item, donor, val4); visuals.RegisterPalette(((Item)item).ID, array3, array4); } private static void Bind(Creature item, AuthoredArt art, Mesh mesh, Material[] materials, Material[] dripMaterials, Func? anatomyFilter) { //IL_013a: 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_0155: 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_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_026b: 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_0274: Unknown result type (might be due to invalid IL or missing references) //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_0371: Unknown result type (might be due to invalid IL or missing references) //IL_037c: Unknown result type (might be due to invalid IL or missing references) //IL_0381: Unknown result type (might be due to invalid IL or missing references) //IL_0386: Unknown result type (might be due to invalid IL or missing references) //IL_03ae: Unknown result type (might be due to invalid IL or missing references) //IL_03b3: Unknown result type (might be due to invalid IL or missing references) //IL_03b8: Unknown result type (might be due to invalid IL or missing references) //IL_03be: Unknown result type (might be due to invalid IL or missing references) //IL_03d1: Unknown result type (might be due to invalid IL or missing references) //IL_03d6: Unknown result type (might be due to invalid IL or missing references) //IL_03e1: Unknown result type (might be due to invalid IL or missing references) //IL_03e3: Unknown result type (might be due to invalid IL or missing references) //IL_03e8: Unknown result type (might be due to invalid IL or missing references) //IL_0463: Unknown result type (might be due to invalid IL or missing references) //IL_03ff: Unknown result type (might be due to invalid IL or missing references) //IL_0404: Unknown result type (might be due to invalid IL or missing references) //IL_0409: Unknown result type (might be due to invalid IL or missing references) //IL_040d: Unknown result type (might be due to invalid IL or missing references) //IL_0412: Unknown result type (might be due to invalid IL or missing references) //IL_05b0: Unknown result type (might be due to invalid IL or missing references) //IL_05bb: Unknown result type (might be due to invalid IL or missing references) //IL_05cb: Unknown result type (might be due to invalid IL or missing references) //IL_05d0: Unknown result type (might be due to invalid IL or missing references) //IL_05d5: Unknown result type (might be due to invalid IL or missing references) //IL_05da: Unknown result type (might be due to invalid IL or missing references) //IL_05df: Unknown result type (might be due to invalid IL or missing references) //IL_05ec: Unknown result type (might be due to invalid IL or missing references) //IL_05f1: Unknown result type (might be due to invalid IL or missing references) //IL_05fb: Unknown result type (might be due to invalid IL or missing references) //IL_0600: Unknown result type (might be due to invalid IL or missing references) //IL_061c: Unknown result type (might be due to invalid IL or missing references) //IL_0621: Unknown result type (might be due to invalid IL or missing references) //IL_0630: Unknown result type (might be due to invalid IL or missing references) //IL_0657: Unknown result type (might be due to invalid IL or missing references) //IL_06ca: Unknown result type (might be due to invalid IL or missing references) //IL_06d6: Unknown result type (might be due to invalid IL or missing references) //IL_06db: Unknown result type (might be due to invalid IL or missing references) //IL_06e0: Unknown result type (might be due to invalid IL or missing references) //IL_06fb: Unknown result type (might be due to invalid IL or missing references) //IL_0707: Unknown result type (might be due to invalid IL or missing references) //IL_0723: Unknown result type (might be due to invalid IL or missing references) //IL_072f: Unknown result type (might be due to invalid IL or missing references) //IL_0734: Unknown result type (might be due to invalid IL or missing references) //IL_0739: Unknown result type (might be due to invalid IL or missing references) GameObject val = NativeAccess.Optional(item, "_disableOnShiny"); GameObject val2 = NativeAccess.Optional(item, "_enableOnShiny"); bool flag = !Object.op_Implicit((Object)(object)val) && !Object.op_Implicit((Object)(object)val2); SkinnedMeshRenderer[] componentsInChildren = ((Component)item).GetComponentsInChildren(true); object obj; if (!flag) { obj = (Object.op_Implicit((Object)(object)val) ? val.GetComponent() : null); } else { if (componentsInChildren.Length != 1) { throw new InvalidOperationException($"{((Object)item).name} has {componentsInChildren.Length} skinned renderers and no native normal/shiny holders."); } obj = componentsInChildren[0]; } SkinnedMeshRenderer normal = (SkinnedMeshRenderer)obj; SkinnedMeshRenderer val3 = (Object.op_Implicit((Object)(object)val2) ? val2.GetComponent() : null); Collider[] colliders = NativeAccess.Get(item, "_worldColliders"); if (!Object.op_Implicit((Object)(object)normal) || (!flag && !Object.op_Implicit((Object)(object)val3)) || !Object.op_Implicit((Object)(object)normal.sharedMesh) || colliders.Length == 0 || colliders.Any((Collider collider) => !Object.op_Implicit((Object)(object)collider) || collider.isTrigger)) { throw new InvalidOperationException(((Object)item).name + " does not match a supported articulated creature rig."); } Transform root = ((Component)item).transform; Bounds val4 = NativeSceneMeshes.TransformBounds(normal.sharedMesh.bounds, root.worldToLocalMatrix * ((Component)normal).transform.localToWorldMatrix); float z = ((Bounds)(ref val4)).size.z; Bounds bounds = mesh.bounds; float num = z / ((Bounds)(ref bounds)).size.z; if (!NativeAccess.Finite(num) || num <= 0f) { throw new InvalidOperationException("Invalid anatomical dimensions for " + ((Object)item).name + "."); } MeshFilter[] array = art.Root.GetComponentsInChildren(true).Where(anatomyFilter ?? ((Func)((MeshFilter _) => true))).ToArray(); if (array.Length == 0) { throw new InvalidOperationException(((Object)item).name + ": the anatomy filter selected no art parts."); } Vector3[] source = array.SelectMany(delegate(MeshFilter filter) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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) Matrix4x4 val12 = art.Root.transform.worldToLocalMatrix * ((Component)filter).transform.localToWorldMatrix; return ((IEnumerable)filter.sharedMesh.vertices).Select((Func)((Matrix4x4)(object)val12).MultiplyPoint3x4); }).ToArray(); Vector3 forward = Vector3.forward; bounds = mesh.bounds; Vector3 val5 = forward * ((((Bounds)(ref bounds)).max.z - source.Max((Vector3 point) => point.z)) * num); Vector3 center = ((Bounds)(ref val4)).center; bounds = mesh.bounds; Matrix4x4 val6 = Matrix4x4.TRS(center - ((Bounds)(ref bounds)).center * num + val5, Quaternion.identity, Vector3.one * num); Rigidbody[] bodies = colliders.Select(NativeBody).Distinct().ToArray(); Vector3[] centers = bodies.Select(delegate(Rigidbody body) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: 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) Bounds val12 = BodyBounds(root, colliders.Where((Collider collider) => (Object)(object)NativeBody(collider) == (Object)(object)body)); return ((Bounds)(ref val12)).center; }).ToArray(); int[] order = (from num4 in Enumerable.Range(0, bodies.Length) orderby centers[num4].z descending select num4).ToArray(); Transform[] array2 = bodies.Select((Rigidbody body) => ((Component)body).transform).ToArray(); Matrix4x4 val7 = ((Component)normal).transform.worldToLocalMatrix * root.localToWorldMatrix; Vector3[] vertices = mesh.vertices; Vector3[] normals = mesh.normals; BoneWeight[] array3 = (BoneWeight[])(object)new BoneWeight[vertices.Length]; Vector3 val9; for (int num2 = 0; num2 < vertices.Length; num2++) { Vector3 val8 = ((Matrix4x4)(ref val6)).MultiplyPoint3x4(vertices[num2]); array3[num2] = Weight(val8.z, centers, order); vertices[num2] = ((Matrix4x4)(ref val7)).MultiplyPoint3x4(val8); if (num2 < normals.Length) { int num3 = num2; val9 = ((Matrix4x4)(ref val7)).MultiplyVector(normals[num2]); normals[num3] = ((Vector3)(ref val9)).normalized; } } mesh.vertices = vertices; mesh.normals = normals; mesh.boneWeights = array3; mesh.bindposes = array2.Select((Transform bone) => bone.worldToLocalMatrix * ((Component)normal).transform.localToWorldMatrix).ToArray(); mesh.RecalculateBounds(); Vector3[] source2 = ((IEnumerable)source).Select((Func)((Matrix4x4)(object)val6).MultiplyPoint3x4).ToArray(); int index; for (index = 0; index < bodies.Length; index++) { Collider[] colliders2 = colliders.Where((Collider collider) => (Object)(object)NativeBody(collider) == (Object)(object)bodies[index]).ToArray(); Vector3[] array4 = source2.Where(delegate(Vector3 point) { //IL_0000: 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) BoneWeight val12 = Weight(point.z, centers, order); return (((BoneWeight)(ref val12)).boneIndex0 == index && ((BoneWeight)(ref val12)).weight0 >= 0.2f) || (((BoneWeight)(ref val12)).boneIndex1 == index && ((BoneWeight)(ref val12)).weight1 >= 0.2f); }).ToArray(); if (array4.Length < 2) { throw new InvalidOperationException($"The authored body does not reach native segment {index} of {((Object)item).name}."); } ResizeCompound(root, colliders2, array4); } Collider pickup = NativeAccess.Get(item, "_pickUpCollider"); Collider obj2 = pickup; Collider obj3 = ((obj2 is BoxCollider) ? obj2 : null); if (obj3 == null || (Object)(object)((Component)pickup).transform != (Object)(object)root || colliders.Any((Collider collider) => (Object)(object)((Component)collider).gameObject == (Object)(object)((Component)pickup).gameObject)) { throw new InvalidOperationException(((Object)item).name + " aliases its pickup and world-collision layers."); } Bounds val10 = NativeSceneMeshes.TransformBounds(mesh.bounds, root.worldToLocalMatrix * ((Component)normal).transform.localToWorldMatrix); ((BoxCollider)obj3).center = ((Bounds)(ref val10)).center; ((BoxCollider)obj3).size = ((Bounds)(ref val10)).size + Vector3.one * 0.04f; obj3.isTrigger = true; Bounds val11 = BodyBounds(root, colliders); NativeAccess.Set(item, "_modelHeight", Mathf.Max(0.02f, 0f - ((Bounds)(ref val11)).min.y)); NativeAccess.Set(item, "_groundCheckDist", Mathf.Max(0.25f, ((Bounds)(ref val11)).extents.y + 0.05f)); NativeAccess.Set(item, "_skinPreset", null); NativeAccess.Set(item, "_skinRenderers", new List()); NativeAccess.Set(item, "_dripMesh", mesh); SetRenderer(normal, mesh, array2, materials, root); if (!Object.op_Implicit((Object)(object)val3)) { return; } val9 = ((Component)normal).transform.position - ((Component)val3).transform.position; if (!(((Vector3)(ref val9)).sqrMagnitude > 1E-05f) && !(Quaternion.Angle(((Component)normal).transform.rotation, ((Component)val3).transform.rotation) > 0.1f)) { val9 = ((Component)normal).transform.lossyScale - ((Component)val3).transform.lossyScale; if (!(((Vector3)(ref val9)).sqrMagnitude > 1E-05f)) { SetRenderer(val3, mesh, array2, dripMaterials, root); val.SetActive(true); val2.SetActive(false); return; } } throw new InvalidOperationException(((Object)item).name + " has incompatible normal/shiny bind spaces."); } private static Rigidbody NativeBody(Collider collider) { Rigidbody componentInParent = ((Component)collider).GetComponentInParent(true); if (!Object.op_Implicit((Object)(object)componentInParent)) { throw new InvalidOperationException("The native collider has no owning rigidbody in its hierarchy."); } return componentInParent; } private static void SetRenderer(SkinnedMeshRenderer renderer, Mesh mesh, Transform[] bones, Material[] materials, Transform root) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) renderer.sharedMesh = mesh; renderer.bones = bones; renderer.rootBone = root; ((Renderer)renderer).sharedMaterials = materials; ((Renderer)renderer).localBounds = mesh.bounds; renderer.updateWhenOffscreen = true; ((Renderer)renderer).enabled = true; } private static BoneWeight Weight(float z, Vector3[] centers, int[] order) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: 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_009e: Unknown result type (might be due to invalid IL or missing references) BoneWeight result; if (z >= centers[order[0]].z) { result = default(BoneWeight); ((BoneWeight)(ref result)).boneIndex0 = order[0]; ((BoneWeight)(ref result)).weight0 = 1f; return result; } for (int i = 1; i < order.Length; i++) { int num = order[i - 1]; int num2 = order[i]; if (!(z < centers[num2].z)) { float num3 = Mathf.InverseLerp(centers[num2].z, centers[num].z, z); result = default(BoneWeight); ((BoneWeight)(ref result)).boneIndex0 = num; ((BoneWeight)(ref result)).boneIndex1 = num2; ((BoneWeight)(ref result)).weight0 = num3; ((BoneWeight)(ref result)).weight1 = 1f - num3; return result; } } result = default(BoneWeight); ((BoneWeight)(ref result)).boneIndex0 = order[^1]; ((BoneWeight)(ref result)).weight0 = 1f; return result; } private static void ResizeCompound(Transform root, Collider[] colliders, Vector3[] points) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0046: 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_0051: 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_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0088: 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_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00be: 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_00d7: 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_00f0: 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_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013e: 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_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: 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_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0202: 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) Transform frame = ((Component)colliders[0]).transform; if (colliders.Any((Collider collider) => (Object)(object)((Component)collider).transform != (Object)(object)frame)) { throw new InvalidOperationException("The native compound collider does not share its expected bone frame."); } Bounds val = BodyBounds(frame, colliders); Matrix4x4 val2 = frame.worldToLocalMatrix * root.localToWorldMatrix; Bounds val3 = default(Bounds); ((Bounds)(ref val3))..ctor(((Matrix4x4)(ref val2)).MultiplyPoint3x4(points[0]), Vector3.zero); foreach (Vector3 val4 in points) { ((Bounds)(ref val3)).Encapsulate(((Matrix4x4)(ref val2)).MultiplyPoint3x4(val4)); } ((Bounds)(ref val3)).Expand(0.015f); Vector3 val5 = default(Vector3); ((Vector3)(ref val5))..ctor(((Bounds)(ref val3)).size.x / ((Bounds)(ref val)).size.x, ((Bounds)(ref val3)).size.y / ((Bounds)(ref val)).size.y, ((Bounds)(ref val3)).size.z / ((Bounds)(ref val)).size.z); foreach (Collider val6 in colliders) { CapsuleCollider val7 = (CapsuleCollider)(object)((val6 is CapsuleCollider) ? val6 : null); if (val7 != null) { val7.center = ((Bounds)(ref val3)).center + Vector3.Scale(val7.center - ((Bounds)(ref val)).center, val5); int num2 = (val7.direction + 1) % 3; int num3 = (val7.direction + 2) % 3; val7.radius *= Mathf.Max(((Vector3)(ref val5))[num2], ((Vector3)(ref val5))[num3]); val7.height = Mathf.Max(val7.radius * 2f, val7.height * ((Vector3)(ref val5))[val7.direction]); continue; } BoxCollider val8 = (BoxCollider)(object)((val6 is BoxCollider) ? val6 : null); if (val8 != null) { val8.center = ((Bounds)(ref val3)).center + Vector3.Scale(val8.center - ((Bounds)(ref val)).center, val5); val8.size = Vector3.Scale(val8.size, val5); continue; } throw new InvalidOperationException("Unexpected native creature collider " + ((object)val6).GetType().Name + "."); } } public static Bounds BodyBounds(Transform root, IEnumerable colliders) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: 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_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: 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_00df: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: 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_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) bool flag = false; Bounds result = default(Bounds); Bounds bounds = default(Bounds); foreach (Collider collider in colliders) { CapsuleCollider val = (CapsuleCollider)(object)((collider is CapsuleCollider) ? collider : null); if (val != null) { Vector3 val2 = Vector3.one * val.radius * 2f; ((Vector3)(ref val2))[val.direction] = Mathf.Max(val.height, val.radius * 2f); ((Bounds)(ref bounds))..ctor(val.center, val2); } else { BoxCollider val3 = (BoxCollider)(object)((collider is BoxCollider) ? collider : null); if (val3 == null) { throw new InvalidOperationException("Unsupported native body collider " + ((object)collider).GetType().Name + "."); } ((Bounds)(ref bounds))..ctor(val3.center, val3.size); } Bounds val4 = NativeSceneMeshes.TransformBounds(bounds, root.worldToLocalMatrix * ((Component)collider).transform.localToWorldMatrix); if (!flag) { result = val4; flag = true; } else { ((Bounds)(ref result)).Encapsulate(val4); } } if (!flag || ((Bounds)(ref result)).size.x <= 0f || ((Bounds)(ref result)).size.y <= 0f || ((Bounds)(ref result)).size.z <= 0f) { throw new InvalidOperationException("The native physics body has no usable collision envelope."); } return result; } } public sealed class OwnedContentIdentity : MonoBehaviour { [SerializeField] private string packKey = ""; [SerializeField] private string key = ""; [SerializeField] private byte itemId; [SerializeField] private string title = ""; [SerializeField] private string description = ""; [SerializeField] private ushort collectionId; [SerializeField] private ushort slot; [SerializeField] private bool configured; public string PackKey => packKey; public string Key => key; public byte ItemId => itemId; public string Title => title; public string Description => description; public ushort CollectionId => collectionId; public ushort Slot => slot; public bool Configured => configured; public ContentReference Reference => ContentReference.Pack(packKey, key); internal void Configure(string pack, string recipeKey, byte id, string displayTitle, string displayDescription, ushort collection, ushort prefabSlot) { if (configured) { throw new InvalidOperationException("The owned identity is already configured."); } packKey = pack; key = recipeKey; itemId = id; title = displayTitle; description = displayDescription; collectionId = collection; slot = prefabSlot; configured = true; } internal void SetPresentation(string displayTitle, string displayDescription) { title = displayTitle; description = displayDescription; } public static bool TryGet(Item? item, out OwnedContentIdentity identity) { identity = null; if (!Object.op_Implicit((Object)(object)item)) { return false; } OwnedContentIdentity component = ((Component)item).GetComponent(); if (!Object.op_Implicit((Object)(object)component) || !component.configured || component.itemId != item.ID) { return false; } if (!NativeContentDirectory.IsLive(component.packKey, component.key, item.ID)) { return false; } identity = component; return true; } } public static class NativeContentDirectory { private static readonly List Registrars = new List(); private static readonly List LureCatalogs = new List(); private static readonly List LootBindings = new List(); public static IReadOnlyList LiveLootBindings { get { lock (LootBindings) { return LootBindings.ToArray(); } } } public static IReadOnlyList LiveRegistrars { get { lock (Registrars) { return Registrars.ToArray(); } } } public static IReadOnlyList LiveLureCatalogs { get { lock (LureCatalogs) { return LureCatalogs.ToArray(); } } } public static void Attach(NativeLootBindings bindings) { if (bindings == null) { throw new ArgumentNullException("bindings"); } lock (LootBindings) { if (!LootBindings.Contains(bindings)) { LootBindings.Add(bindings); } } } public static void Detach(NativeLootBindings bindings) { lock (LootBindings) { LootBindings.Remove(bindings); } } internal static void ObserveCombat(Creature creature) { if (!Object.op_Implicit((Object)(object)creature)) { return; } foreach (NativeLootBindings liveLootBinding in LiveLootBindings) { if (liveLootBinding.TryGet(creature, out NativeLootTable _)) { (((Component)creature).GetComponent() ?? ((Component)creature).gameObject.AddComponent()).CombatObserved = true; break; } } } internal static bool AutoLoot(Creature creature) { bool result = false; foreach (NativeLootBindings liveLootBinding in LiveLootBindings) { if (liveLootBinding.AutoOnDeath(creature)) { result = true; } } return result; } internal static void Attach(NativeItemRegistrar registrar) { lock (Registrars) { foreach (NativeItemRegistrar registrar2 in Registrars) { if (registrar2.CollectionId == registrar.CollectionId) { throw new InvalidOperationException($"FishNet collection {registrar.CollectionId} is already owned by pack {registrar2.PackKey}."); } } Registrars.Add(registrar); } } internal static void Detach(NativeItemRegistrar registrar) { lock (Registrars) { Registrars.Remove(registrar); } } internal static void Attach(NativeLureCatalog catalog) { lock (LureCatalogs) { LureCatalogs.Add(catalog); } } internal static void Detach(NativeLureCatalog catalog) { lock (LureCatalogs) { LureCatalogs.Remove(catalog); } } internal static bool IsLive(string packKey, string key, byte id) { lock (Registrars) { foreach (NativeItemRegistrar registrar in Registrars) { if (registrar.IsReady && registrar.PackKey == packKey && registrar.TryGetRegistered(key, out RegisteredItem entry) && entry.ItemId == id) { return true; } } } return false; } public static NativeItemRegistrar? FindRegistrar(string packKey) { lock (Registrars) { foreach (NativeItemRegistrar registrar in Registrars) { if (registrar.PackKey == packKey) { return registrar; } } } return null; } public static NativeLureCatalog? FindLureCatalog(string packKey) { lock (LureCatalogs) { foreach (NativeLureCatalog lureCatalog in LureCatalogs) { if (lureCatalog.PackKey == packKey) { return lureCatalog; } } } return null; } public static bool TryResolveItem(ContentReference reference, out Item item) { item = null; if (reference.IsNative) { Item spawnable = GameInfo.GetSpawnable(reference.NativeId); if (!Object.op_Implicit((Object)(object)spawnable) || spawnable.ID != reference.NativeId) { return false; } item = spawnable; return true; } foreach (NativeItemRegistrar liveRegistrar in LiveRegistrars) { if (liveRegistrar.PackKey == reference.PackKey && liveRegistrar.TryGetRegistered(reference.Key, out RegisteredItem entry)) { item = entry.Prefab; return true; } } return false; } public static bool TryResolveFishable(ContentReference reference, out Fishable fishable) { fishable = null; if (reference.IsNative) { return false; } foreach (NativeItemRegistrar liveRegistrar in LiveRegistrars) { if (liveRegistrar.PackKey == reference.PackKey && liveRegistrar.TryGetRegistered(reference.Key, out RegisteredItem entry) && Object.op_Implicit((Object)(object)entry.Fishable)) { fishable = entry.Fishable; return true; } } return false; } public static Item ResolveItem(string reference) { if (!TryResolveItem(ContentReference.Parse(reference), out Item item)) { throw new KeyNotFoundException("Unresolved item reference " + reference + "."); } return item; } public static BaitInfo ResolveLure(string reference) { if (!TryResolveLure(ContentReference.Parse(reference), out BaitInfo bait)) { throw new KeyNotFoundException("Unresolved lure reference " + reference + "."); } return bait; } public static bool TryResolveLure(ContentReference reference, out BaitInfo bait) { bait = null; if (reference.IsNative) { if (!NativePackCatalog.Contains((ContentDomain)1, (int)reference.NativeId) || reference.NativeId >= GameInfo.AllBaits.Count || !Object.op_Implicit((Object)(object)GameInfo.AllBaits[reference.NativeId])) { return false; } bait = GameInfo.AllBaits[reference.NativeId]; return true; } NativeLureCatalog nativeLureCatalog = FindLureCatalog(reference.PackKey); if (nativeLureCatalog == null || !nativeLureCatalog.TryGet(reference.Key, out RegisteredLure lure)) { return false; } bait = lure.Bait; return true; } public static bool TryPatchCatchTable(List weights, out List patched) { patched = weights; if (weights == null) { return false; } List list = null; foreach (NativeLureCatalog liveLureCatalog in LiveLureCatalogs) { if (liveLureCatalog.TryPatchCatchTable(weights, out List patched2)) { if (list == null) { list = new List(weights); } list.AddRange(patched2.Skip(weights.Count)); } } if (list == null) { return false; } patched = list; return true; } public static bool TryDescribeItem(Item? item, out string title, out string description) { title = ""; description = ""; if (!OwnedContentIdentity.TryGet(item, out OwnedContentIdentity identity)) { return false; } title = identity.Title; description = identity.Description; return true; } public static bool TryDescribeLure(BaitInfo? bait, out string title, out string description) { title = ""; description = ""; if (!Object.op_Implicit((Object)(object)bait)) { return false; } foreach (NativeLureCatalog liveLureCatalog in LiveLureCatalogs) { if (liveLureCatalog.TryDescribe(bait, out title, out description)) { return true; } } return NativeLureTable.TryDescribePlaceholder(bait, out title, out description); } public static bool ApplyItemPreview(Renderer renderer, Item? item, bool showShiny = false, byte? skinIndex = null) { if (!Object.op_Implicit((Object)(object)renderer)) { throw new ArgumentNullException("renderer"); } bool flag = false; foreach (NativeItemRegistrar liveRegistrar in LiveRegistrars) { if (!flag && liveRegistrar.IsReady && liveRegistrar.Owns(item) && liveRegistrar.Visuals.ApplyPreview(renderer, item, showShiny, skinIndex)) { flag = true; } else { liveRegistrar.Visuals.RestorePreview(renderer); } } return flag; } public static void RestoreItemPreview(Renderer renderer) { if (!Object.op_Implicit((Object)(object)renderer)) { return; } foreach (NativeItemRegistrar liveRegistrar in LiveRegistrars) { liveRegistrar.Visuals.RestorePreview(renderer); } } public static bool ApplyLurePreview(Renderer renderer, BaitInfo? bait) { if (!Object.op_Implicit((Object)(object)renderer)) { throw new ArgumentNullException("renderer"); } bool flag = false; foreach (NativeLureCatalog liveLureCatalog in LiveLureCatalogs) { if (!flag && liveLureCatalog.Owns(bait) && liveLureCatalog.ApplyPreview(renderer, bait)) { flag = true; } else { liveLureCatalog.RestorePreview(renderer); } } return flag; } public static void ApplyBaitVisual(Bait bait) { if (!Object.op_Implicit((Object)(object)bait)) { return; } foreach (NativeLureCatalog liveLureCatalog in LiveLureCatalogs) { liveLureCatalog.ApplyBait(bait); } } public static void EnsureInventoryBaitCapacity(PlayerInventory inventory) { if (!Object.op_Implicit((Object)(object)inventory)) { return; } foreach (NativeLureCatalog liveLureCatalog in LiveLureCatalogs) { liveLureCatalog.EnsureInventoryCapacity(inventory); } } } public interface IPackAssetFactory { GameObject Instantiate(string packKey, ArtRecipe art); } public static class PackAssetFactory { private sealed class Delegated : IPackAssetFactory { private readonly Func instantiate; internal Delegated(Func instantiate) { this.instantiate = instantiate; } public GameObject Instantiate(string packKey, ArtRecipe art) { return instantiate(packKey, art); } } public static IPackAssetFactory From(Func instantiate) { return new Delegated(instantiate ?? throw new ArgumentNullException("instantiate")); } } public sealed class ItemCustomization { public PackDefinition Pack { get; } public ItemRecipe Recipe { get; } public PreparedItem Item { get; } public Item Clone => Item.Clone; public Item Donor => Item.Donor; public NativeItemRegistrar Registrar => Item.Registrar; public ContentVisuals Visuals => Item.Registrar.Visuals; public ManualLogSource Log { get; } public AuthoredArt? Art { get; } public bool VisualBound { get; private set; } public PackContentInstaller Installer { get; } internal ItemCustomization(PackContentInstaller installer, PackDefinition pack, ItemRecipe recipe, PreparedItem item, AuthoredArt? art, ManualLogSource log) { Installer = installer; Pack = pack; Recipe = recipe; Item = item; Art = art; Log = log; } public void MarkVisualBound() { Item.Visual = MeshAdapterKind.Custom; VisualBound = true; } } public sealed class ExternalRecipeContext where TRecipe : PackRecipe { public PackDefinition Pack { get; } public TRecipe Recipe { get; } public PackContentInstaller Installer { get; } public ManualLogSource Log { get; } internal ExternalRecipeContext(PackContentInstaller installer, PackDefinition pack, TRecipe recipe, ManualLogSource log) { Installer = installer; Pack = pack; Recipe = recipe; Log = log; } } public delegate void ItemCustomizer(ItemCustomization context); public delegate PreparedItem ExternalItemBinder(ExternalRecipeContext context, NativeItemRegistrar registrar); public delegate LureDefinition ExternalLureBinder(ExternalRecipeContext context); public delegate NativeLootTable ExternalLootBinder(ExternalRecipeContext context); public sealed class PackContentOptions { public IPackAssetFactory? Assets { get; set; } public ManualLogSource? Log { get; set; } public ItemCustomizer? CustomizeItems { get; set; } public Dictionary ItemCustomizers { get; } = new Dictionary(StringComparer.Ordinal); public Dictionary ExternalItems { get; } = new Dictionary(StringComparer.Ordinal); public Dictionary ExternalLures { get; } = new Dictionary(StringComparer.Ordinal); public Dictionary ExternalLoot { get; } = new Dictionary(StringComparer.Ordinal); public Func? CatchPatchCondition { get; set; } } public sealed class PackContentInstaller : IDisposable { private readonly Dictionary registrars = new Dictionary(); private readonly List ownedFishables = new List(); private readonly List pendingArt = new List(); private readonly PackContentOptions options; private bool installed; private bool disposed; public PackRegistry Registry { get; } public PackDefinition Pack { get; } public string PackKey => Pack.Key; public ManualLogSource Log { get; } public IReadOnlyCollection Registrars => registrars.Values; public NativeLureCatalog? Lures { get; private set; } public NativeLootBindings Loot { get; } = new NativeLootBindings(); public bool IsReady { get; private set; } public PackContentInstaller(PackRegistry registry, PackDefinition pack, PackContentOptions? options = null) { Registry = registry ?? throw new ArgumentNullException("registry"); if (pack == null) { throw new ArgumentNullException("pack"); } Pack = ((IEnumerable)registry.Packs).FirstOrDefault((Func)((PackDefinition candidate) => candidate.Key == pack.Key)) ?? throw new ArgumentException("Pack " + pack.Key + " is not part of the composed registry.", "pack"); this.options = options ?? new PackContentOptions(); Log = this.options.Log ?? Logger.CreateLogSource("ExpansionKit.Pack." + Pack.Key); } public void Install() { if (disposed) { throw new ObjectDisposedException("PackContentInstaller"); } if (installed) { throw new InvalidOperationException("Install runs once per installer."); } installed = true; try { foreach (PackDependency dependency in Pack.Dependencies) { if (NativeContentDirectory.FindRegistrar(dependency.Key) == null && NativeContentDirectory.FindLureCatalog(dependency.Key) == null && Registry.Packs.First((PackDefinition candidate) => candidate.Key == dependency.Key).Items.Count + Registry.Packs.First((PackDefinition candidate) => candidate.Key == dependency.Key).Lures.Count > 0) { throw new InvalidOperationException(PackKey + ": install dependency pack " + dependency.Key + " first."); } } foreach (NetworkCollectionRecipe collection in Pack.Collections) { registrars.Add(collection.Id, new NativeItemRegistrar(PackKey, collection.Id, collection.Capacity, Log)); } InstallItems(); foreach (NativeItemRegistrar item in registrars.Values.Where((NativeItemRegistrar registrar) => registrar.Pending.Count > 0)) { item.Commit(); } NativeItemRegistrar[] array = registrars.Values.Where((NativeItemRegistrar registrar) => !registrar.IsCommitted).ToArray(); foreach (NativeItemRegistrar nativeItemRegistrar in array) { registrars.Remove(nativeItemRegistrar.CollectionId); nativeItemRegistrar.Dispose(); } InstallLures(); InstallLoot(); IsReady = true; Log.LogInfo((object)($"{PackKey} {Pack.Version}: {registrars.Values.Sum((NativeItemRegistrar registrar) => registrar.Items.Count)} items, " + $"{Lures?.Lures.Count ?? 0} lures, {Loot.Tables.Count} loot tables installed.")); } catch (Exception arg) { Log.LogError((object)$"{PackKey}: installation failed; rolling back: {arg}"); Dispose(); throw; } } private void InstallItems() { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Invalid comparison between Unknown and I4 //IL_011e: Unknown result type (might be due to invalid IL or missing references) foreach (ItemRecipe item in Pack.Items) { if (!registrars.TryGetValue(item.Network.CollectionId, out NativeItemRegistrar value)) { throw new InvalidOperationException($"{PackKey}:{((PackRecipe)item).Key}: network collection {item.Network.CollectionId} is not declared by the pack."); } if ((int)((PackRecipe)item).Implementation == 1) { if (!options.ExternalItems.TryGetValue(((PackRecipe)item).Key, out ExternalItemBinder value2)) { throw new InvalidOperationException(PackKey + ":" + ((PackRecipe)item).Key + " is External (hook " + ((PackRecipe)item).ExtensionHook + ") and has no host binder; it is not installed."); } PreparedItem obj = value2(new ExternalRecipeContext(this, Pack, item, Log), value) ?? throw new InvalidOperationException(PackKey + ":" + ((PackRecipe)item).Key + ": the external binder returned nothing."); VerifyBinding(obj, item, value); obj.Recipe = item; obj.Kind = item.Kind; continue; } Item donor = ResolveItem(ContentReference.Parse(item.NativeDonor ?? throw new InvalidOperationException(PackKey + ":" + ((PackRecipe)item).Key + ": native clone without donor."))); if (!ContentReference.Parse(item.NativeDonor).IsNative) { throw new InvalidOperationException(PackKey + ":" + ((PackRecipe)item).Key + ": donors must be native items."); } PreparedItem preparedItem = value.Prepare(((PackRecipe)item).Key, item.Id, item.Network.Slot, donor, item.Title); AuthoredArt authoredArt = null; try { NativeItemFactories.ApplyRecipe(preparedItem, item); if (item.Art != null) { authoredArt = LoadArt(item.Art, preparedItem.Reference.ToString()); } ItemCustomization itemCustomization = new ItemCustomization(this, Pack, item, preparedItem, authoredArt, Log); options.CustomizeItems?.Invoke(itemCustomization); if (options.ItemCustomizers.TryGetValue(((PackRecipe)item).Key, out ItemCustomizer value3)) { value3(itemCustomization); } if (authoredArt != null && !itemCustomization.VisualBound) { ApplyStandardArt(preparedItem, authoredArt); } if (preparedItem.Clone.ID != item.Id) { throw new InvalidOperationException($"{preparedItem.Reference}: a customization changed the item ID."); } } catch { if (value.TryGetPrepared(((PackRecipe)item).Key, out PreparedItem _)) { value.Discard(preparedItem); } throw; } finally { authoredArt?.Dispose(); } } } private void ApplyStandardArt(PreparedItem item, AuthoredArt art) { Item clone = item.Clone; if (!(clone is Tool)) { Creature val = (Creature)(object)((clone is Creature) ? clone : null); if (val != null && ((Component)val).GetComponentsInChildren(true).Length != 0) { ArticulatedMeshAdapter.Apply(val, item.Donor, art, item.Registrar.Visuals); item.Visual = MeshAdapterKind.Articulated; } else { RigidMeshAdapter.Apply(item.Clone, item.Donor, art, item.Registrar.Visuals); item.Visual = MeshAdapterKind.Rigid; } return; } throw new InvalidOperationException($"{item.Reference}: rod/weapon art needs an explicit custom binding (ItemCustomization.MarkVisualBound); the shared adapters only handle rigid items and articulated creatures."); } private AuthoredArt LoadArt(ArtRecipe recipe, string label) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Expected O, but got Unknown //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) if (options.Assets == null) { throw new InvalidOperationException(label + ": the recipe declares art but the host supplied no asset factory."); } GameObject val = options.Assets.Instantiate(PackKey, recipe); if (!Object.op_Implicit((Object)(object)val)) { throw new InvalidOperationException(label + ": the asset factory returned no prefab for " + recipe.Bundle + "/" + recipe.Prefab + "."); } val.transform.localPosition = new Vector3(recipe.Position.X, recipe.Position.Y, recipe.Position.Z); val.transform.localRotation = Quaternion.Euler(recipe.Rotation.X, recipe.Rotation.Y, recipe.Rotation.Z); val.transform.localScale = new Vector3(recipe.Scale.X, recipe.Scale.Y, recipe.Scale.Z); if (recipe.Grip != null && val.GetComponentsInChildren(true).All((Transform node) => ((Object)node).name != "marker_grip_right")) { GameObject val2 = new GameObject("marker_grip_right"); val2.transform.SetParent(val.transform, false); val2.transform.localPosition = new Vector3(recipe.Grip.Position.X, recipe.Grip.Position.Y, recipe.Grip.Position.Z); val2.transform.localRotation = Quaternion.Euler(0f, recipe.Grip.Yaw, 0f); } GameObject val3 = new GameObject(label + " art"); val3.SetActive(false); val.transform.SetParent(val3.transform, false); return AuthoredArt.Take(val3, label); } private static void VerifyBinding(PreparedItem bound, ItemRecipe recipe, NativeItemRegistrar registrar) { string text = registrar.PackKey + ":" + ((PackRecipe)recipe).Key; if (bound.Registrar != registrar || !registrar.TryGetPrepared(((PackRecipe)recipe).Key, out PreparedItem entry) || entry != bound) { throw new InvalidOperationException($"{text}: the external binder must prepare its clone through the pack's registrar for collection {registrar.CollectionId}."); } if (bound.Key != ((PackRecipe)recipe).Key || bound.ItemId != recipe.Id || bound.Slot != recipe.Network.Slot || bound.Clone.ID != recipe.Id || bound.Identity.CollectionId != recipe.Network.CollectionId) { throw new InvalidOperationException($"{text}: the external binding does not match the recipe (ID {recipe.Id}, prefab {recipe.Network.CollectionId}:{recipe.Network.Slot})."); } if (recipe.Art != null && bound.Visual == MeshAdapterKind.NativeDonor) { throw new InvalidOperationException(text + ": the recipe declares art but the external binder left the native donor visual."); } } private void InstallLures() { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Invalid comparison between Unknown and I4 //IL_03d4: Unknown result type (might be due to invalid IL or missing references) //IL_03da: Invalid comparison between Unknown and I4 //IL_041a: Unknown result type (might be due to invalid IL or missing references) //IL_043a: Unknown result type (might be due to invalid IL or missing references) //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) if (Pack.Lures.Count == 0 && Pack.CatchPatches.Count == 0) { return; } Lures = new NativeLureCatalog(PackKey, Log); foreach (LureRecipe lure in Pack.Lures) { LureDefinition lureDefinition; if ((int)((PackRecipe)lure).Implementation == 1) { if (!options.ExternalLures.TryGetValue(((PackRecipe)lure).Key, out ExternalLureBinder value)) { throw new InvalidOperationException(PackKey + ":" + ((PackRecipe)lure).Key + " is External (hook " + ((PackRecipe)lure).ExtensionHook + ") and has no host binder; it is not installed."); } lureDefinition = value(new ExternalRecipeContext(this, Pack, lure, Log)) ?? throw new InvalidOperationException(PackKey + ":" + ((PackRecipe)lure).Key + ": the external binder returned nothing."); if (lureDefinition.Key != ((PackRecipe)lure).Key || lureDefinition.Id != lure.Id) { throw new InvalidOperationException($"{PackKey}:{((PackRecipe)lure).Key}: the external lure binding does not match the recipe key/index {lure.Id}."); } } else { BaitInfo val = ((lure.NativeVisualDonor != null) ? Lures.Resolve(ContentReference.Parse(lure.NativeVisualDonor)) : null); if (lure.Art == null && !Object.op_Implicit((Object)(object)val)) { throw new InvalidOperationException(PackKey + ":" + ((PackRecipe)lure).Key + ": a native-clone lure needs art or a native visual donor."); } LureDefinition obj = new LureDefinition { Key = ((PackRecipe)lure).Key, Id = lure.Id, Title = lure.Title, Description = lure.Description, Cost = (lure.Cost ?? (Object.op_Implicit((Object)(object)val) ? val.Cost : 0)) }; float? lossPercent = lure.LossPercent; float lossPercent2; if (!lossPercent.HasValue) { if (!Object.op_Implicit((Object)(object)val)) { throw Explicit(((PackRecipe)lure).Key, "LossPercent"); } lossPercent2 = val.LostOnBaitChance; } else { lossPercent2 = lossPercent.GetValueOrDefault(); } obj.LossPercent = lossPercent2; lossPercent = lure.CatchTimeMin; float catchTimeMin; if (!lossPercent.HasValue) { if (!Object.op_Implicit((Object)(object)val)) { throw Explicit(((PackRecipe)lure).Key, "CatchTimeMin"); } catchTimeMin = val.CatchTimeMinMax.x; } else { catchTimeMin = lossPercent.GetValueOrDefault(); } obj.CatchTimeMin = catchTimeMin; lossPercent = lure.CatchTimeMax; float catchTimeMax; if (!lossPercent.HasValue) { if (!Object.op_Implicit((Object)(object)val)) { throw Explicit(((PackRecipe)lure).Key, "CatchTimeMax"); } catchTimeMax = val.CatchTimeMinMax.y; } else { catchTimeMax = lossPercent.GetValueOrDefault(); } obj.CatchTimeMax = catchTimeMax; obj.VisualDonor = val; obj.Recipe = lure; lureDefinition = obj; foreach (CatchRecipe @catch in lure.Catches) { lureDefinition.Catches.Add(new CatchEntry(ResolveFishable(ContentReference.Parse(@catch.Item)), @catch.Weight)); } if (lure.Art != null) { lureDefinition.Art = LoadArt(lure.Art, PackKey + ":" + ((PackRecipe)lure).Key); pendingArt.Add(lureDefinition.Art); } } lureDefinition.Recipe = lure; Lures.Add(lureDefinition); } foreach (CatchTablePatch catchPatch in Pack.CatchPatches) { if ((int)((PackRecipe)catchPatch).Implementation == 1) { throw new NotSupportedException("Unsupported by the native runtime: " + PackKey + ":" + ((PackRecipe)catchPatch).Key + ": external catch patches have no binder contract."); } if ((int)catchPatch.Mode != 0) { throw new NotSupportedException("Unsupported by the native runtime: " + $"{PackKey}:{((PackRecipe)catchPatch).Key}: {catchPatch.Mode} would replace a lure's native catch table; only Additive patches are applied."); } CatchPatchDefinition catchPatchDefinition = new CatchPatchDefinition { Key = ((PackRecipe)catchPatch).Key, Target = ResolveLure(ContentReference.Parse(catchPatch.TargetLure)), Condition = options.CatchPatchCondition }; foreach (CatchRecipe catch2 in catchPatch.Catches) { catchPatchDefinition.Catches.Add(new CatchEntry(ResolveFishable(ContentReference.Parse(catch2.Item)), catch2.Weight)); } Lures.AddCatchPatch(catchPatchDefinition); } Lures.Commit(); pendingArt.Clear(); } private static InvalidOperationException Explicit(string key, string field) { return new InvalidOperationException(key + ": authored lures must declare " + field + " explicitly (no native donor to inherit from)."); } private void InstallLoot() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Invalid comparison between Unknown and I4 foreach (LootRecipe item in Pack.Loot) { NativeLootTable nativeLootTable; if ((int)((PackRecipe)item).Implementation == 1) { if (!options.ExternalLoot.TryGetValue(((PackRecipe)item).Key, out ExternalLootBinder value)) { throw new InvalidOperationException(PackKey + ":" + ((PackRecipe)item).Key + " is External (hook " + ((PackRecipe)item).ExtensionHook + ") and has no host binder; it is not installed."); } nativeLootTable = value(new ExternalRecipeContext(this, Pack, item, Log)) ?? throw new InvalidOperationException(PackKey + ":" + ((PackRecipe)item).Key + ": the external binder returned nothing."); if (nativeLootTable.Key != ((PackRecipe)item).Key) { throw new InvalidOperationException(PackKey + ":" + ((PackRecipe)item).Key + ": the external loot table key does not match."); } } else { nativeLootTable = new NativeLootTable(((PackRecipe)item).Key, item.Drops.Select(delegate(LootDropRecipe drop) { ContentReference reference = ContentReference.Parse(drop.Item); return new LootDrop(ResolveItem(reference), drop.FixedQuantity, drop.PerPlayerQuantity, reference); }), Log); } ContentReference contentReference = ContentReference.Parse(item.Creature); if (!contentReference.IsNative && !(ResolveItem(contentReference) is Creature)) { throw new InvalidOperationException($"{PackKey}:{((PackRecipe)item).Key}: {contentReference} is not a creature."); } Loot.Bind(contentReference, nativeLootTable, (int)((PackRecipe)item).Implementation != 1); } if (Pack.Loot.Count > 0) { NativeContentDirectory.Attach(Loot); } } public Item ResolveItem(ContentReference reference) { if (reference.IsNative) { return NativeItemRegistrar.NativeDonor(reference.NativeId); } if (reference.PackKey != PackKey && !Pack.Dependencies.Any((PackDependency dependency) => dependency.Key == reference.PackKey)) { throw new KeyNotFoundException(PackKey + " has no declared dependency on " + reference.PackKey + "."); } if (reference.PackKey == PackKey) { foreach (NativeItemRegistrar value in registrars.Values) { if (value.TryGetRegistered(reference.Key, out RegisteredItem entry)) { return entry.Prefab; } if (value.TryGetPrepared(reference.Key, out PreparedItem entry2)) { return entry2.Clone; } } } if (NativeContentDirectory.TryResolveItem(reference, out Item item)) { return item; } throw new KeyNotFoundException($"Unresolved item reference {reference}."); } public Fishable ResolveFishable(ContentReference reference) { if (!reference.IsNative) { foreach (NativeItemRegistrar value in registrars.Values) { if (value.PackKey == reference.PackKey && value.TryGetRegistered(reference.Key, out RegisteredItem entry)) { if (!Object.op_Implicit((Object)(object)entry.Fishable)) { throw new InvalidOperationException($"{reference} is not a catchable creature."); } return entry.Fishable; } } if (NativeContentDirectory.TryResolveFishable(reference, out Fishable fishable)) { return fishable; } throw new KeyNotFoundException($"Unresolved creature reference {reference}."); } Item native = ResolveItem(reference); if (!(native is Creature)) { throw new InvalidOperationException($"{reference} is not a native creature."); } foreach (BaitInfo allBait in GameInfo.AllBaits) { if (!Object.op_Implicit((Object)(object)allBait) || allBait.ItemWeights == null) { continue; } foreach (ItemInfoWeight itemWeight in allBait.ItemWeights) { if (itemWeight != null && Object.op_Implicit((Object)(object)itemWeight.Fishable) && (Object)(object)itemWeight.Fishable.ItemToSpawn == (Object)(object)native) { return itemWeight.Fishable; } } } Fishable val = ((IEnumerable)ownedFishables).FirstOrDefault((Func)((Fishable val3) => Object.op_Implicit((Object)(object)val3) && (Object)(object)val3.ItemToSpawn == (Object)(object)native)); if (Object.op_Implicit((Object)(object)val)) { return val; } Fishable val2 = ScriptableObject.CreateInstance(); ((Object)val2).name = PackKey + "_native" + reference.NativeId + "_Catch"; NativeAccess.Set(val2, "_itemToSpawn", native); Object.DontDestroyOnLoad((Object)(object)val2); ownedFishables.Add(val2); return val2; } public BaitInfo ResolveLure(ContentReference reference) { if (reference.IsNative) { return NativeLureCatalog.NativeLure(reference.NativeId); } if (reference.PackKey == PackKey && Lures != null && Lures.TryGet(reference.Key, out RegisteredLure lure)) { return lure.Bait; } if (reference.PackKey != PackKey && !Pack.Dependencies.Any((PackDependency dependency) => dependency.Key == reference.PackKey)) { throw new KeyNotFoundException(PackKey + " has no declared dependency on " + reference.PackKey + "."); } NativeLureCatalog nativeLureCatalog = NativeContentDirectory.FindLureCatalog(reference.PackKey); if (nativeLureCatalog != null && nativeLureCatalog.TryGet(reference.Key, out lure)) { return lure.Bait; } throw new KeyNotFoundException($"Unresolved lure reference {reference}."); } public bool Validate() { if (disposed) { throw new ObjectDisposedException("PackContentInstaller"); } if (!IsReady) { throw new InvalidOperationException(PackKey + " is not installed."); } foreach (NativeItemRegistrar value in registrars.Values) { value.Validate(); } Lures?.Validate(); return true; } public object Describe() { if (disposed) { throw new ObjectDisposedException("PackContentInstaller"); } string packKey = PackKey; string version = Pack.Version; bool isReady = IsReady; var collections = (from registrar in registrars.Values orderby registrar.CollectionId select new { id = registrar.CollectionId, capacity = registrar.Capacity, committed = registrar.IsCommitted, items = registrar.Items.Select((RegisteredItem item) => new { key = item.Key, id = item.ItemId, slot = item.Slot, kind = ((object)item.Kind/*cast due to .constrained prefix*/).ToString(), visual = item.Visual.ToString(), donor = (Object.op_Implicit((Object)(object)item.Donor) ? new byte?(item.Donor.ID) : ((byte?)null)), donorType = (Object.op_Implicit((Object)(object)item.Donor) ? ((object)item.Donor).GetType().Name : null), worth = (Object.op_Implicit((Object)(object)item.Prefab) ? new int?(item.Prefab.DefaultWorth) : ((int?)null)), cost = (Object.op_Implicit((Object)(object)item.Prefab) ? new int?(item.Prefab.Cost) : ((int?)null)), journal = item.InJournal, catchable = item.Fishable }).ToArray() }).ToArray(); object[] lures; if (Lures != null) { object[] array = Lures.Lures.Select((RegisteredLure lure) => new { key = lure.Key, index = lure.Id, cost = (Object.op_Implicit((Object)(object)lure.Bait) ? new int?(lure.Bait.Cost) : ((int?)null)), authored = lure.Authored, catches = ((Object.op_Implicit((Object)(object)lure.Bait) && lure.Bait.ItemWeights != null) ? lure.Bait.ItemWeights.Select((ItemInfoWeight entry) => new { item = ((Object.op_Implicit((Object)(object)entry.Fishable) && Object.op_Implicit((Object)(object)entry.Fishable.ItemToSpawn)) ? new byte?(entry.Fishable.ItemToSpawn.ID) : ((byte?)null)), weight = entry.Weight }).ToArray() : null) }).ToArray(); lures = array; } else { lures = Array.Empty(); } return new { pack = packKey, version = version, ready = isReady, collections = collections, lures = lures, lureTableHash = Lures?.RegistrationHash, catchPatches = Lures?.CatchPatches.Select((CatchPatchDefinition patch) => new { key = patch.Key, target = (Object.op_Implicit((Object)(object)patch.Target) ? ((Object)patch.Target).name : null), entries = patch.Catches.Count }).ToArray(), loot = Loot.Tables.Select((NativeLootTable table) => new { key = table.Key, issued = table.IssuedCount, drops = table.Drops.Select((LootDrop drop) => new { item = drop.Reference.ToString(), FixedQuantity = drop.FixedQuantity, PerPlayerQuantity = drop.PerPlayerQuantity }).ToArray() }).ToArray() }; } public void Dispose() { if (disposed) { return; } disposed = true; IsReady = false; NativeContentDirectory.Detach(Loot); Loot.Clear(); Lures?.Dispose(); Lures = null; foreach (AuthoredArt item in pendingArt) { item.Dispose(); } pendingArt.Clear(); foreach (NativeItemRegistrar value in registrars.Values) { value.Dispose(); } registrars.Clear(); foreach (Fishable ownedFishable in ownedFishables) { if (Object.op_Implicit((Object)(object)ownedFishable)) { Object.Destroy((Object)(object)ownedFishable); } } ownedFishables.Clear(); } } } namespace HowToFish.ExpansionKit.Runtime.Commerce { public static class AmmunitionPurchasePolicies { private sealed class Registration : IDisposable { private readonly string key; private readonly Func policy; internal Registration(string key, Func policy) { this.key = key; this.policy = policy; } public void Dispose() { if (Policies.TryGetValue(key, out Func value) && (object)value == policy) { Policies.Remove(key); } } } private static readonly Dictionary> Policies = new Dictionary>(StringComparer.Ordinal); private static readonly ManualLogSource Log = Logger.CreateLogSource("ExpansionKit.Purchases"); private static readonly FieldInfo Maximum = AccessTools.Field(typeof(BulletPurchasable), "_maxBulletUpgrade"); private static readonly FieldInfo CanBuy = AccessTools.Field(typeof(Purchasable), "_customCanBuy"); private static readonly FieldInfo Cost = AccessTools.Field(typeof(Purchasable), "_customCost"); public static IDisposable Register(string key, Func maximumTier) { if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentException("A purchase policy needs an owner key.", "key"); } if (maximumTier == null) { throw new ArgumentNullException("maximumTier"); } if (Policies.ContainsKey(key)) { throw new InvalidOperationException("Duplicate ammunition policy: " + key); } Policies.Add(key, maximumTier); return new Registration(key, maximumTier); } public static void Bind(BulletPurchasable stand, string key) { if (!Object.op_Implicit((Object)(object)stand)) { throw new ArgumentNullException("stand"); } if (!Policies.ContainsKey(key)) { throw new InvalidOperationException("Register the ammunition policy before binding its stand: " + key); } if (Object.op_Implicit((Object)(object)((Component)stand).GetComponent())) { throw new InvalidOperationException("The ammunition stand already has a policy."); } AmmunitionPolicyBinding ammunitionPolicyBinding = ((Component)stand).gameObject.AddComponent(); ammunitionPolicyBinding.Key = key; ammunitionPolicyBinding.FallbackMaximum = (byte)Maximum.GetValue(stand); } public static byte? MaximumFor(Weapon weapon) { if (!Object.op_Implicit((Object)(object)weapon)) { throw new ArgumentNullException("weapon"); } byte? result = null; foreach (Func item in from pair in Policies.OrderBy>, string>((KeyValuePair> pair) => pair.Key, StringComparer.Ordinal) select pair.Value) { byte? b = item(weapon); if (b.HasValue) { result = (result.HasValue ? new byte?(Math.Min(result.Value, b.Value)) : b); } } return result; } internal static void Refresh(BulletPurchasable stand, Player? player, bool purchase) { AmmunitionPolicyBinding component = ((Component)stand).GetComponent(); if (!Object.op_Implicit((Object)(object)component)) { return; } if (!Policies.TryGetValue(component.Key, out Func value)) { throw new InvalidOperationException("An active ammunition stand lost its policy: " + component.Key); } Item val = (Object.op_Implicit((Object)(object)player) ? player.Holding.HeldItem : null); Weapon val2 = ((Object.op_Implicit((Object)(object)val) && (Object)(object)val.SyncedHolder == (Object)(object)player) ? val.Weapon : null); byte b = ((!Object.op_Implicit((Object)(object)val2)) ? component.FallbackMaximum : (value(val2) ?? component.FallbackMaximum)); if (Object.op_Implicit((Object)(object)val2)) { byte? b2 = MaximumFor(val2); if (b2.HasValue) { byte valueOrDefault = b2.GetValueOrDefault(); b = Math.Min(b, valueOrDefault); } } Maximum.SetValue(stand, b); if (purchase) { BulletUpgrade val3 = (Object.op_Implicit((Object)(object)val2) ? val2.Attachments.GetNextBulletUpgrade(b) : null); CanBuy.SetValue(stand, val3 != null); if (val3 != null) { Cost.SetValue(stand, val3.Cost); } } } internal static bool Authorize(Weapon? weapon) { if (!Object.op_Implicit((Object)(object)weapon) || ((NetworkBehaviour)weapon).IsDeinitializing) { return true; } byte? b = MaximumFor(weapon); if (!b.HasValue || weapon.Attachments.AmmoType < b.Value) { return true; } Log.LogWarning((object)$"Ammunition purchase denied for item {((Item)weapon).ID}: tier {weapon.Attachments.AmmoType}, allowed maximum {b.Value}."); return false; } } internal sealed class AmmunitionPolicyBinding : MonoBehaviour { internal string Key = ""; internal byte FallbackMaximum; } [HarmonyPatch(typeof(BulletPurchasable), "Hover")] internal static class RefreshAmmunitionPolicy { private static void Prefix(BulletPurchasable __instance) { AmmunitionPurchasePolicies.Refresh(__instance, Player.LocalPlayer, purchase: false); } } [HarmonyPatch(typeof(BulletPurchasable), "Interact")] internal static class ApplyAmmunitionPolicy { private static void Prefix(BulletPurchasable __instance, Player player) { AmmunitionPurchasePolicies.Refresh(__instance, player, purchase: true); } } [HarmonyPatch(typeof(Server), "RpcLogic___BuyBulletUpgrade___1996094309")] internal static class EnforceAmmunitionPolicy { private static bool Prefix(Weapon __0) { return AmmunitionPurchasePolicies.Authorize(__0); } } } namespace HowToFish.ExpansionKit.Runtime.Characters { internal sealed class NativeDeliveryQuest : IDisposable { private readonly MountedNpc owner; private readonly string key; private readonly string pack; private readonly QuestRecipe recipe; private readonly Item delivery; private readonly Func resolveItem; private readonly Func resolveLure; private readonly Func actorAllowed; private readonly Action unlockCoordinates; private readonly QuestInteractable hand; private bool disposed; internal QuestProgressStore Store { get; } internal NPCQuest Quest { get; } internal string RewardTitle => recipe.Title; private NPC Npc => owner.Native; private bool ServerReady { get { if (Object.op_Implicit((Object)(object)Server.Instance) && ((NetworkBehaviour)Server.Instance).IsServerInitialized && Object.op_Implicit((Object)(object)NPCManager.Instance) && owner.Initialized && !owner.Retired && !disposed && !IslandManager.IsLoading) { return Store.Fault == null; } return false; } } internal NativeDeliveryQuest(MountedNpc marker, string questKey, QuestRecipe definition, NativeTemplateCache templates, QuestProgressStore state, Func items, Func lures, Func authorizeActor, Action grantCoordinates) { //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Invalid comparison between Unknown and I4 owner = marker; key = questKey; pack = key.Substring(0, key.IndexOf(':')); recipe = definition; Store = state; resolveItem = items; resolveLure = lures; actorAllowed = authorizeActor; unlockCoordinates = grantCoordinates; delivery = items(MountReferences.Qualify(pack, recipe.Objectives[0].Item)); if (!Object.op_Implicit((Object)(object)delivery) || delivery.IgnoredByCloseDots) { throw new InvalidOperationException("Quest delivery item must be a registered native-save-eligible item: " + key); } NPCQuest val = ((IEnumerable)templates.QuestTemplates).FirstOrDefault((Func)((NPCQuest quest) => (int)quest.Type == 3 && Object.op_Implicit((Object)(object)quest.InteractableMesh))) ?? throw new InvalidOperationException("No native held-coordinates quest template."); Quest = Object.Instantiate(val); ((Object)Quest).name = "ExpansionKit quest " + key; ((Object)Quest).hideFlags = (HideFlags)52; try { WorldFields.Set(Quest, "_questItems", new List { delivery }); WorldFields.Set(Quest, "_onlyCreatures", false); WorldFields.Set(Quest, "_totalItems", checked((byte)recipe.Objectives[0].Count)); WorldFields.Set(Quest, "_type", (object)(QuestType)3); WorldFields.Set(Quest, "_islandToUnlock", (byte)0); WorldFields.Set(Quest, "_baitToReceive", null); ValidateRewardDefinitions(); QuestRewardRecipe val2 = recipe.Rewards.FirstOrDefault(); Mesh val3 = ((val2 != null && (int)val2.Kind == 0) ? items(Ref(val2)).Mesh : ((val2 != null && (int)val2.Kind == 1) ? lures(Ref(val2)).MeshForNpc : val.InteractableMesh)); if (!Object.op_Implicit((Object)(object)val3)) { throw new InvalidOperationException("Quest requires a physical held-reward mesh: " + key); } WorldFields.Set(Quest, "_interactableMesh", val3); WorldFields.Set(Npc, "_quests", new List { Quest }); QuestInteractable val4 = WorldFields.Optional(Npc, "questInteractable"); hand = (Object.op_Implicit((Object)(object)val4) ? val4 : templates.Clone(((Component)templates.RewardHand).gameObject, WorldFields.Get(Npc, "_handHoldingItem")).GetComponent()); if (!((Component)hand).transform.IsChildOf(((Component)Npc).transform)) { throw new InvalidOperationException("Native reward hand is not owned by the mounted NPC."); } WorldFields.Set(Npc, "questInteractable", hand); hand.LinkNpc(Npc); ((Behaviour)hand).enabled = true; Collider val5 = WorldFields.Get(hand, "_interactCol"); if (!Object.op_Implicit((Object)(object)val5) || !((Component)val5).transform.IsChildOf(((Component)hand).transform)) { throw new InvalidOperationException("Native reward hand collider is missing or external."); } val5.enabled = true; ((Component)val5).gameObject.layer = LayerMask.NameToLayer("Interactable"); ((Component)val5).gameObject.tag = "Interactable"; ((Component)hand).gameObject.SetActive(false); } catch { Object.Destroy((Object)(object)Quest); throw; } } internal bool CanAccept(NPCQuest quest, Item value) { if (Quest == quest && !disposed && !owner.Retired && owner.Initialized && Store.CanDeliver(key) && Object.op_Implicit((Object)(object)value) && value.ID == delivery.ID && !value.IsDestroying && !((NetworkBehaviour)value).IsDeinitializing) { if (Object.op_Implicit((Object)(object)value.Creature)) { return value.Creature.IsDead; } return true; } return false; } internal void Deliver(Item value) { //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) if (!ServerReady || !CanAccept(Quest, value) || !((NetworkBehaviour)value).IsSpawned || !value.IsInteractable || Object.op_Implicit((Object)(object)value.Holder) || Object.op_Implicit((Object)(object)value.SyncedHolder) || value.IsInInventory || !Object.op_Implicit((Object)(object)value.LastHolder) || Object.op_Implicit((Object)(object)value.DeadPlayer) || Object.op_Implicit((Object)(object)value.BirdHolder) || Object.op_Implicit((Object)(object)value.AttachedRod) || value.IgnoredByCloseDots || !Eligible(value.LastHolder) || Vector3.Distance(((Component)value).transform.position, Npc.MouthPosForItems.position) > 4f || NPCManager.Instance.NpcIsHoldingItem(Npc.ID)) { return; } int count = Store.Get(key).Delivered + 1; Store.Deliver(key, delegate { NPCManager.Instance.SendEatEffects(Npc.ID); value.DestroyItem((byte)2, Npc.ID); if (Object.op_Implicit((Object)(object)value) && !value.IsDestroying && ((NetworkBehaviour)value).IsSpawned) { throw new InvalidOperationException("Native quest intake did not remove the delivered item."); } ApplyPresentation(count, claimed: false); }); NPCManager.Instance.ServerSpeak(Npc.ID, (byte)0, checked((byte)count), (QuestLineType)((count != recipe.Objectives[0].Count) ? 1 : 2)); } internal void Claim(Player player) { if (!ServerReady || !Eligible(player) || !Store.PendingReward(key) || !NPCManager.Instance.NpcIsHoldingItem(Npc.ID) || NPCManager.Instance.GetShowingQuest(Npc.ID) != Quest) { return; } ValidateGrants(player); Store.Claim(key, delegate { foreach (QuestRewardRecipe reward in recipe.Rewards) { Grant(player, reward); } ApplyPresentation(recipe.Objectives[0].Count, claimed: true); }); } private bool Eligible(Player player) { //IL_003e: 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) if (Object.op_Implicit((Object)(object)player) && PlayerManager.Players.Contains(player) && !((NetworkBehaviour)player).IsDeinitializing && !player.Dying.IsDead && player.Vitals.Health > 0 && Vector3.Distance(player.Transform.position, ((Component)Npc).transform.position) <= 6f) { return actorAllowed(player); } return false; } private void ValidateRewardDefinitions() { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: 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_008e: Expected I4, but got Unknown //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) if (recipe.Rewards.Count == 0) { return; } if (recipe.Rewards.Count > 16) { throw new NotSupportedException("At most sixteen rewards are supported per claim."); } foreach (QuestRewardRecipe reward in recipe.Rewards) { if (reward.Amount < 1) { throw new InvalidOperationException("Reward amount must be positive: " + key); } QuestRewardKind kind = reward.Kind; switch ((int)kind) { case 0: { Item val = resolveItem(Ref(reward)); if (!Object.op_Implicit((Object)(object)val) || val.IgnoredByCloseDots || reward.Amount > 32) { throw new InvalidOperationException("Item rewards need a save-eligible prefab and quantity 1..32."); } break; } case 1: { BaitInfo val2 = resolveLure(Ref(reward)); byte indexOfBait = GameInfo.GetIndexOfBait(val2); if (!Object.op_Implicit((Object)(object)val2) || indexOfBait == 0 || indexOfBait >= GameInfo.AllBaits.Count || GameInfo.AllBaits[indexOfBait] != val2 || reward.Amount > 255) { throw new InvalidOperationException("Lure reward is not registered or quantity exceeds 255."); } break; } case 2: if (reward.Amount != 1 || string.IsNullOrWhiteSpace(reward.Content)) { throw new InvalidOperationException("Coordinate rewards require an island reference and amount one."); } break; default: throw new NotSupportedException("Unsupported quest reward: " + ((object)reward.Kind/*cast due to .constrained prefix*/).ToString()); case 3: break; } } } private void ValidateGrants(Player player) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Invalid comparison between Unknown and I4 //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Invalid comparison between Unknown and I4 //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Invalid comparison between Unknown and I4 ValidateRewardDefinitions(); long num = 0L; Dictionary dictionary = new Dictionary(); foreach (QuestRewardRecipe reward in recipe.Rewards) { if ((int)reward.Kind == 3) { num += reward.Amount; } if ((int)reward.Kind == 0 && !Object.op_Implicit((Object)(object)ItemManager.Instance)) { throw new InvalidOperationException("Native item spawn service is unavailable."); } if ((int)reward.Kind == 1) { byte indexOfBait = GameInfo.GetIndexOfBait(resolveLure(Ref(reward))); if (indexOfBait == 0 || indexOfBait > player.Inventory._ownedBaits.Count) { throw new InvalidOperationException("Player inventory has not registered the lure reward."); } if (!dictionary.ContainsKey(indexOfBait)) { dictionary[indexOfBait] = player.Inventory._ownedBaits[indexOfBait - 1]; } dictionary[indexOfBait] += reward.Amount; if (dictionary[indexOfBait] < 0 || dictionary[indexOfBait] > int.MaxValue) { throw new InvalidOperationException("Lure reward would overflow the native inventory."); } } if ((int)reward.Kind == 2 && Ref(reward).StartsWith("native:", StringComparison.Ordinal)) { byte b = MountReferences.NativeId(Ref(reward)); if (!Object.op_Implicit((Object)(object)OnlineIslandManager.Instance) || b < 1 || b > 5 || b > OnlineIslandManager.MaxIslandUnlocked + 1) { throw new InvalidOperationException("Coordinate rewards cannot skip mandatory native island progression."); } } } if (num > 0 && (!Object.op_Implicit((Object)(object)MoneyManager.Instance) || MoneyManager.Instance._money.Value + num > int.MaxValue)) { throw new InvalidOperationException("Money reward exceeds the native wallet limit."); } } private void Grant(Player player, QuestRewardRecipe reward) { //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_0008: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected I4, but got Unknown //IL_0049: 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_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) //IL_006d: 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) //IL_0081: Unknown result type (might be due to invalid IL or missing references) QuestRewardKind kind = reward.Kind; switch ((int)kind) { case 0: { Item val = resolveItem(Ref(reward)); for (int i = 0; i < reward.Amount; i++) { Item val2 = ItemManager.Instance.SpawnNewItem(val, Npc.MouthPosForItems.position + Vector3.up * 0.5f + ((Component)Npc).transform.forward * 0.8f, Quaternion.identity); if (!Object.op_Implicit((Object)(object)val2) || !((NetworkBehaviour)val2).IsSpawned) { throw new InvalidOperationException("Native item reward failed to spawn."); } } break; } case 1: { byte indexOfBait = GameInfo.GetIndexOfBait(resolveLure(Ref(reward))); int num = player.Inventory._ownedBaits[indexOfBait - 1]; for (int j = 0; j < reward.Amount; j++) { player.Inventory.ServerBoughtBait(indexOfBait); } if (player.Inventory._ownedBaits[indexOfBait - 1] != num + reward.Amount) { throw new InvalidOperationException("Native lure grant did not deliver the expected quantity."); } player.Inventory.ServerSetCurBait(indexOfBait); break; } case 2: unlockCoordinates(Ref(reward)); break; case 3: { int value = MoneyManager.Instance._money.Value; MoneyManager.AddMoney(reward.Amount, player); if (MoneyManager.Instance._money.Value != checked(value + reward.Amount)) { throw new InvalidOperationException("Native money grant did not deliver the expected amount."); } break; } } } private string Ref(QuestRewardRecipe reward) { return MountReferences.Qualify(pack, reward.Content ?? ""); } internal void Refresh() { if (!disposed && owner.Initialized && Object.op_Implicit((Object)(object)Npc) && Object.op_Implicit((Object)(object)NPCManager.Instance)) { QuestProgress questProgress = Store.Get(key); ApplyPresentation(questProgress.Delivered, questProgress.RewardClaimed); } } private void ApplyPresentation(int delivered, bool claimed) { byte b = (byte)((delivered != recipe.Objectives[0].Count || claimed) ? byte.MaxValue : 0); byte[] array = WorldFields.Get(Npc, "_serverProgressions"); if (array.Length != 1) { throw new InvalidOperationException("Mounted native quest progression changed shape."); } array[0] = checked((byte)delivered); byte b2 = default(byte); if (Object.op_Implicit((Object)(object)Server.Instance) && ((NetworkBehaviour)Server.Instance).IsServerInitialized && (!NPCManager.Instance._idToShowingQuestIndex.TryGetValue(Npc.ID, ref b2) || b2 != b)) { NPCManager.Instance.SetShowingNpcQuest(Npc.ID, b); } if (((Component)hand).gameObject.activeSelf != (b != byte.MaxValue)) { Npc.OnShowingQuestIndexChange(b); } } internal string? Dialogue(QuestLineType type) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Invalid comparison between Unknown and I4 if (owner.Dialogue.Length == 0) { return null; } QuestProgress questProgress = Store.Get(key); int val = (((int)type == 1) ? 1 : (((int)type == 2 || (int)type == 3) ? 2 : (questProgress.RewardClaimed ? 3 : 0))); return owner.Dialogue[Math.Min(val, owner.Dialogue.Length - 1)] + ((recipe.Objectives[0].Count > 1) ? $"\n{questProgress.Delivered}/{recipe.Objectives[0].Count}" : ""); } public void Dispose() { if (!disposed) { disposed = true; if (Object.op_Implicit((Object)(object)hand)) { ((Component)hand).gameObject.SetActive(false); } if (Object.op_Implicit((Object)(object)Quest)) { Object.Destroy((Object)(object)Quest); } } } } public sealed class NativeNpcMounts { private readonly NativeTemplateCache templates; private readonly QuestProgressStore progress; private readonly Func resolveItem; private readonly Func resolveLure; private readonly Func actorAllowed; private readonly Action unlockCoordinates; private readonly Dictionary quests; public NativeNpcMounts(NativeTemplateCache cache, IEnumerable packs, QuestProgressStore state, Func itemResolver, Func lureResolver, Func authorizeActor, Action grantCoordinates) { //IL_016a: Unknown result type (might be due to invalid IL or missing references) templates = cache ?? throw new ArgumentNullException("cache"); progress = state ?? throw new ArgumentNullException("state"); resolveItem = itemResolver ?? throw new ArgumentNullException("itemResolver"); resolveLure = lureResolver ?? throw new ArgumentNullException("lureResolver"); actorAllowed = authorizeActor ?? throw new ArgumentNullException("authorizeActor"); unlockCoordinates = grantCoordinates ?? throw new ArgumentNullException("grantCoordinates"); quests = packs.SelectMany((PackDefinition val) => from quest in val.Quests where (int)((PackRecipe)quest).Implementation != 1 select new KeyValuePair(MountReferences.Key(val.Key, ((PackRecipe)quest).Key), quest)).ToDictionary, string, QuestRecipe>((KeyValuePair pair) => pair.Key, (KeyValuePair pair) => pair.Value, StringComparer.Ordinal); foreach (KeyValuePair item in quests.OrderBy, string>((KeyValuePair pair) => pair.Key, StringComparer.Ordinal)) { QuestRecipe value = item.Value; if (value.Objectives.Count != 1 || (int)value.Objectives[0].Kind != 0 || value.Objectives[0].Count < 1 || value.Objectives[0].Count > 255) { throw new NotSupportedException("Native delivery quests require exactly one DeliverItem objective with count 1..255: " + item.Key); } string pack = item.Key.Substring(0, item.Key.IndexOf(':')); progress.Register(item.Key, value.Objectives[0].Count, value.Requires.Select((string reference) => MountReferences.Qualify(pack, reference))); } progress.ValidateDefinitions(); } public NPC Mount(string packKey, NpcRecipe recipe, Transform islandRoot, Transform ownedRoot) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Invalid comparison between Unknown and I4 //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) templates.RequireReady(); if ((int)((PackRecipe)recipe).Implementation == 1 || (int)recipe.Role == 2) { throw new InvalidOperationException("External NPCs require their pack adapter."); } if (recipe.Art != null) { throw new NotSupportedException("Authored NPC rig replacement requires an explicit adapter; donor rigs are mounted unchanged."); } NPC npc = templates.GetNpc(MountReferences.NativeId(recipe.NativeDonorIsland), MountReferences.NativeId(recipe.NativeDonor)); MountedNpc.RequireAvailable(recipe.Id); MountPose mountPose = MountPlacement.Resolve(islandRoot, recipe.Placement); GameObject val = templates.Clone(((Component)npc).gameObject, ownedRoot); val.SetActive(false); ((Object)val).name = "ExpansionKit NPC " + MountReferences.Key(packKey, ((PackRecipe)recipe).Key); try { NPC component = val.GetComponent(); MountedNpc mountedNpc = val.GetComponent() ?? val.AddComponent(); mountedNpc.Bind(component, recipe.Id, recipe.Dialogue, recipe.Title); val.transform.localPosition = mountPose.Position; val.transform.localRotation = mountPose.Rotation; val.transform.localScale = ((Component)npc).transform.localScale; WorldFields.Set(component, "_forScreenshot", false); WorldFields.Set(component, "_curQuestIndex", (byte)0); WorldFields.Set(component, "_curLineIndex", (byte)0); WorldFields.Set(component, "_eyePos", Vector3.zero); WorldFields.Set(component, "_targetLookingAt", null); WorldFields.Set(component, "_speakMovementsLeft", (byte)0); FieldInfo fieldInfo = WorldFields.Field(typeof(NPC), "_mouthState"); fieldInfo.SetValue(component, Enum.ToObject(fieldInfo.FieldType, 0)); ValidateInteraction(component); if ((int)recipe.Role == 0) { NPCQuest item = ((IEnumerable)npc.Quests).FirstOrDefault((Func)((NPCQuest quest) => Object.op_Implicit((Object)(object)quest) && (int)quest.Type == 1)) ?? ((IEnumerable)templates.QuestTemplates).FirstOrDefault((Func)((NPCQuest quest) => (int)quest.Type == 1 && quest.QuestItems.Count == 0)) ?? throw new InvalidOperationException("No captured native mouth-selling quest."); WorldFields.Set(component, "_quests", new List { item }); QuestInteractable val2 = WorldFields.Optional(component, "questInteractable"); if (Object.op_Implicit((Object)(object)val2)) { ((Component)val2).gameObject.SetActive(false); } } else { string text = MountReferences.Qualify(packKey, recipe.Quest ?? ""); if (!quests.TryGetValue(text, out QuestRecipe value)) { throw new InvalidOperationException("Missing mounted quest: " + text); } mountedNpc.Quest = new NativeDeliveryQuest(mountedNpc, text, value, templates, progress, resolveItem, resolveLure, actorAllowed, unlockCoordinates); } ((Behaviour)component).enabled = true; val.SetActive(true); mountedNpc.Quest?.Refresh(); return component; } catch { Object.Destroy((Object)(object)val); throw; } } private static void ValidateInteraction(NPC npc) { string[] array = new string[2] { "_characterInteractable", "_npcInteractable" }; foreach (string name in array) { Interactable val = WorldFields.Get(npc, name); Collider val2 = WorldFields.Get(val, "_interactCol"); if (!((Component)val).transform.IsChildOf(((Component)npc).transform) || !Object.op_Implicit((Object)(object)val2) || !((Component)val2).transform.IsChildOf(((Component)npc).transform) || !Object.op_Implicit((Object)(object)val.TextTarget) || !val.TextTarget.IsChildOf(((Component)npc).transform)) { throw new InvalidOperationException("NPC donor interaction references leave its owned hierarchy."); } ((Behaviour)val).enabled = true; val2.enabled = true; ((Component)val2).gameObject.layer = LayerMask.NameToLayer("Interactable"); ((Component)val2).gameObject.tag = "Interactable"; } if (!((Component)npc).GetComponents().Any((Collider collider) => collider.isTrigger)) { throw new InvalidOperationException("NPC donor has no physical mouth-intake trigger."); } } public void RefreshMountedState() { MountedNpc[] array = MountedNpc.Live.Where((MountedNpc marker) => marker.Quest?.Store == progress).ToArray(); for (int num = 0; num < array.Length; num++) { array[num].Quest.Refresh(); } } } internal sealed class MountedNpc : MonoBehaviour { private static readonly ConditionalWeakTable owners = new ConditionalWeakTable(); private static readonly Dictionary live = new Dictionary(); private static Dictionary Registry => (Dictionary)WorldFields.Field(typeof(NPCManager), "_idToNpc").GetValue(null); internal static IEnumerable Live => live.Values; internal NPC Native { get; private set; } internal bool Template { get; private set; } = true; internal bool Retired { get; private set; } internal string Title { get; private set; } = ""; internal string[] Dialogue { get; private set; } = Array.Empty(); internal NativeDeliveryQuest? Quest { get; set; } internal bool Initialized { get; set; } internal List OwnedMaterials { get; } = new List(); internal void BindTemplate(NPC npc) { Native = npc; Template = true; Retired = false; owners.Add(npc, this); } internal void Bind(NPC npc, byte id, IEnumerable dialogue, string title) { RequireAvailable(id); Native = npc; Template = false; Retired = false; Dialogue = dialogue.ToArray(); Title = title; WorldFields.Set(npc, "_id", id); owners.Add(npc, this); live.Add(id, this); } internal static MountedNpc? For(NPC npc) { if (!owners.TryGetValue(npc, out MountedNpc value)) { return null; } return value; } internal static MountedNpc? For(byte id) { if (!live.TryGetValue(id, out MountedNpc value)) { return null; } return value; } internal static void RequireAvailable(byte id) { if (id == byte.MaxValue) { throw new InvalidOperationException("NPC ID 255 is reserved by the native handoff protocol."); } if (live.ContainsKey(id) || (Registry.TryGetValue(id, out NPC value) && Object.op_Implicit((Object)(object)value))) { throw new InvalidOperationException("NPC byte ID is occupied: " + id); } } internal static void VerifyRegistration(NPC npc) { if (!Registry.TryGetValue(npc.ID, out NPC value) || value != npc) { throw new InvalidOperationException("Mounted NPC failed native registration: " + npc.ID); } } internal void Retire() { if (Retired) { return; } Retired = true; Quest?.Dispose(); if (Native == null) { return; } if (!Template && live.TryGetValue(Native.ID, out MountedNpc value) && value == this) { live.Remove(Native.ID); } if (Registry.TryGetValue(Native.ID, out NPC value2) && value2 == Native) { NPCManager.RemoveNpc(Native); } if (Object.op_Implicit((Object)(object)Native)) { ((MonoBehaviour)Native).StopAllCoroutines(); string[] array = new string[4] { "_mouthLower", "_mouthUpper", "_handHoldingItem", "_mouthSource" }; foreach (string name in array) { Component val = WorldFields.Optional(Native, name); if (val != null && Object.op_Implicit((Object)(object)val)) { LeanTween.cancel(val.gameObject); } } AudioSource val2 = WorldFields.Optional(Native, "_mouthSource"); if (Object.op_Implicit((Object)(object)val2)) { val2.Stop(); } ((Component)Native).gameObject.SetActive(false); } foreach (Material ownedMaterial in OwnedMaterials) { if (Object.op_Implicit((Object)(object)ownedMaterial)) { Object.Destroy((Object)(object)ownedMaterial); } } OwnedMaterials.Clear(); } private void OnDestroy() { Retire(); } } [HarmonyPatch(typeof(NPC), "Awake")] internal static class MountedNpcAwake { private static bool Prefix(NPC __instance) { MountedNpc mountedNpc = MountedNpc.For(__instance); if (!((Object)(object)mountedNpc == (Object)null)) { if (!mountedNpc.Template) { return !mountedNpc.Retired; } return false; } return true; } private static void Postfix(NPC __instance) { MountedNpc mountedNpc = MountedNpc.For(__instance); if (!((Object)(object)mountedNpc == (Object)null) && !mountedNpc.Template && !mountedNpc.Retired) { MountedNpc.VerifyRegistration(__instance); mountedNpc.Initialized = true; mountedNpc.Quest?.Refresh(); } } } [HarmonyPatch(typeof(NPC), "OnDestroy")] internal static class MountedNpcDestroy { private static bool Prefix(NPC __instance) { MountedNpc mountedNpc = MountedNpc.For(__instance); if ((Object)(object)mountedNpc == (Object)null) { if (!Object.op_Implicit((Object)(object)((Component)__instance).GetComponent())) { return !Object.op_Implicit((Object)(object)((Component)__instance).GetComponentInParent(true)); } return false; } mountedNpc.Retire(); return false; } } [HarmonyPatch(typeof(NPC), "SetSkinColor")] internal static class MountedNpcMaterialOwnership { private static bool Prefix(NPC __instance, out Material[]? __state) { MountedNpc mountedNpc = MountedNpc.For(__instance); __state = (((Object)(object)mountedNpc != (Object)null && !mountedNpc.Template && !mountedNpc.Retired) ? ((Component)__instance).GetComponentsInChildren(true).SelectMany((Renderer renderer) => renderer.sharedMaterials).Distinct() .ToArray() : null); if (!((Object)(object)mountedNpc == (Object)null)) { if (!mountedNpc.Template) { return !mountedNpc.Retired; } return false; } return true; } private static void Postfix(NPC __instance, Material[]? __state) { MountedNpc mountedNpc = MountedNpc.For(__instance); if ((Object)(object)mountedNpc == (Object)null || __state == null) { return; } foreach (Material item in (from material in ((Component)__instance).GetComponentsInChildren(true).SelectMany((Renderer renderer) => renderer.sharedMaterials) where Object.op_Implicit((Object)(object)material) && !__state.Contains(material) select material).Distinct()) { if (!mountedNpc.OwnedMaterials.Contains(item)) { mountedNpc.OwnedMaterials.Add(item); } } } } [HarmonyPatch(typeof(NPC), "CanEat")] internal static class MountedNpcIntake { private static void Postfix(NPC __instance, NPCQuest quest, Item item, ref bool __result) { MountedNpc mountedNpc = MountedNpc.For(__instance); if (!((Object)(object)mountedNpc == (Object)null)) { __result &= !mountedNpc.Template && !mountedNpc.Retired && mountedNpc.Initialized; if (mountedNpc.Quest != null) { __result &= mountedNpc.Quest.CanAccept(quest, item); } } } } [HarmonyPatch(typeof(NPC), "EatItem")] internal static class MountedNpcDelivery { private static bool Prefix(NPC __instance, NPCQuest quest, byte questIndex, Item item) { MountedNpc mountedNpc = MountedNpc.For(__instance); if ((Object)(object)mountedNpc == (Object)null) { return true; } if (mountedNpc.Template || mountedNpc.Retired) { return false; } if (mountedNpc.Quest == null) { return true; } if (questIndex != 0 || mountedNpc.Quest.Quest != quest) { throw new InvalidOperationException("Mounted delivery received a foreign quest identity."); } mountedNpc.Quest.Deliver(item); return false; } } [HarmonyPatch(typeof(Server), "RpcLogic___TakeItemFromNpc___1382779195")] internal static class MountedNpcClaim { private static bool Prefix(object[] __args) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown MountedNpc mountedNpc = MountedNpc.For((byte)__args[1]); if ((Object)(object)mountedNpc == (Object)null) { return true; } if (mountedNpc.Quest != null && !mountedNpc.Retired && !mountedNpc.Template) { mountedNpc.Quest.Claim((Player)__args[0]); } return false; } } [HarmonyPatch(typeof(NPC), "SetNpcText")] internal static class MountedNpcDialogue { private static void Postfix(NPC __instance, byte lineIndex, QuestLineType lineType, byte progression) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) MountedNpc mountedNpc = MountedNpc.For(__instance); if (!((Object)(object)mountedNpc == (Object)null) && !mountedNpc.Template && !mountedNpc.Retired && Object.op_Implicit((Object)(object)Player.LocalPlayer) && !(Vector3.Distance(((Component)__instance).transform.position, Player.LocalPlayer.Transform.position) >= WorldFields.Get(__instance, "_speakingDistance"))) { string text = mountedNpc.Quest?.Dialogue(lineType); if (text == null && mountedNpc.Dialogue.Length != 0) { text = mountedNpc.Dialogue[lineIndex % mountedNpc.Dialogue.Length]; } if (text != null) { Interactable val = WorldFields.Get(__instance, "_characterInteractable"); PlayerUI.SetNpcText(text, val.TextTarget); } } } } [HarmonyPatch(typeof(QuestInteractable), "SetQuest")] internal static class MountedNpcRewardLabel { private static void Postfix(QuestInteractable __instance) { NPC val = WorldFields.Optional(__instance, "_npc"); MountedNpc mountedNpc = (Object.op_Implicit((Object)(object)val) ? MountedNpc.For(val) : null); if (mountedNpc?.Quest != null) { WorldFields.Set(__instance, "_hoverString", "Take " + mountedNpc.Quest.RewardTitle + "\n" + SpriteManager.GetPickUpInput()); } } } public sealed class QuestProgress { public string Key { get; set; } = ""; public int Delivered { get; set; } public bool RewardClaimed { get; set; } } public sealed class MountedPackState { public int Version { get; set; } = 1; public long Revision { get; set; } public bool SaveSelectionInitialized { get; set; } public byte NativeUnlockedAtSelection { get; set; } public List Quests { get; set; } = new List(); public Dictionary IslandFlags { get; set; } = new Dictionary(StringComparer.Ordinal); } public sealed class QuestCheckpoint { public string OperationId { get; } public string QuestKey { get; } public string Kind { get; } public MountedPackState Before { get; } public MountedPackState After { get; } internal QuestCheckpoint(string key, string kind, MountedPackState before, MountedPackState after) { QuestKey = key; Kind = kind; Before = before; After = after; OperationId = key + ":" + after.Revision + ":" + kind; } } public delegate void CommitQuestCheckpoint(QuestCheckpoint checkpoint, Action apply); public sealed class QuestProgressStore { private sealed class Rule { internal int Total; internal string[] Dependencies = Array.Empty(); internal HashSet CompletionFlags = new HashSet(StringComparer.Ordinal); } public const string IslandIntroducedSuffix = ":introduced"; public const string IslandPreservedAccessSuffix = ":preserve_native_access"; private readonly Dictionary rules = new Dictionary(StringComparer.Ordinal); private readonly Dictionary islandContinuations = new Dictionary(StringComparer.Ordinal); private readonly CommitQuestCheckpoint checkpoint; private MountedPackState state = new MountedPackState(); private bool executing; private bool applying; public Exception? Fault { get; private set; } public QuestProgressStore(CommitQuestCheckpoint commit) { checkpoint = commit ?? throw new ArgumentNullException("commit"); } public void Register(string key, int total, IEnumerable dependencies) { if (executing || state.Revision != 0L) { throw new InvalidOperationException("Register quests before restoring or changing state."); } if (string.IsNullOrWhiteSpace(key) || total < 1 || total > 255 || dependencies == null) { throw new ArgumentException("A stable key, count 1..255 and dependency collection are required."); } if (rules.ContainsKey(key)) { throw new InvalidOperationException("Duplicate quest key: " + key); } string[] array = dependencies.ToArray(); if (array.Any(string.IsNullOrWhiteSpace) || array.Contains(key) || array.Distinct(StringComparer.Ordinal).Count() != array.Length) { throw new ArgumentException("Invalid quest dependencies: " + key); } rules.Add(key, new Rule { Total = total, Dependencies = array }); state.Quests.Add(new QuestProgress { Key = key }); } public void RegisterIsland(string reference, int? nativeContinuationOrdinal) { if (executing || state.Revision != 0L) { throw new InvalidOperationException("Register islands before restoring or changing state."); } RequireCustomIslandReference(reference); if (nativeContinuationOrdinal.HasValue && (nativeContinuationOrdinal < 2 || nativeContinuationOrdinal > 5)) { throw new ArgumentOutOfRangeException("nativeContinuationOrdinal"); } if (islandContinuations.ContainsKey(reference)) { throw new InvalidOperationException("Duplicate island reference: " + reference); } islandContinuations.Add(reference, nativeContinuationOrdinal); } public void ValidateDefinitions() { HashSet visiting = new HashSet(StringComparer.Ordinal); HashSet finished = new HashSet(StringComparer.Ordinal); foreach (string key3 in rules.Keys) { Visit(key3); } void Visit(string key) { if (!finished.Contains(key)) { if (!rules.ContainsKey(key)) { throw new InvalidOperationException("Missing quest dependency: " + key); } if (!visiting.Add(key)) { throw new InvalidOperationException("Cyclic quest dependencies: " + key); } string[] dependencies = rules[key].Dependencies; foreach (string key2 in dependencies) { Visit(key2); } visiting.Remove(key); finished.Add(key); } } } public void CompleteIslandOnClaim(string questKey, string islandReference) { if (executing || state.Revision != 0L) { throw new InvalidOperationException("Register completion rules before restoring or changing state."); } RequireCustomIslandReference(islandReference); RuleFor(questKey).CompletionFlags.Add(islandReference + ":completed"); } public MountedPackState CaptureState() { if (Fault != null) { throw new InvalidOperationException("Do not save native state after a failed quest checkpoint; restore the paired checkpoint first.", Fault); } if (applying) { throw new InvalidOperationException("Do not save native state halfway through a quest mutation."); } return Copy(state); } public QuestProgress Get(string key) { QuestProgress questProgress = Find(state, key); return new QuestProgress { Key = questProgress.Key, Delivered = questProgress.Delivered, RewardClaimed = questProgress.RewardClaimed }; } public bool PendingReward(string key) { if (Get(key).Delivered == RuleFor(key).Total) { return !Get(key).RewardClaimed; } return false; } public bool CanDeliver(string key) { if (Fault != null || executing) { return false; } Rule rule = RuleFor(key); QuestProgress questProgress = Find(state, key); if (questProgress.Delivered < rule.Total && !questProgress.RewardClaimed) { return rule.Dependencies.All((string dependency) => Find(state, dependency).RewardClaimed); } return false; } public void RestoreState(MountedPackState restored) { if (executing) { throw new InvalidOperationException("Cannot restore during a quest transaction."); } Validate(restored); state = Copy(restored); foreach (string key in rules.Keys) { if (!state.Quests.Any((QuestProgress quest) => quest.Key == key)) { state.Quests.Add(new QuestProgress { Key = key }); } } Fault = null; } public void SelectSave(byte nativeUnlockedCount) { RequireHealthy(); if (nativeUnlockedCount < 1 || nativeUnlockedCount > 5) { throw new ArgumentOutOfRangeException("nativeUnlockedCount"); } MountedPackState mountedPackState = Copy(state); bool flag = !mountedPackState.SaveSelectionInitialized; if (!mountedPackState.SaveSelectionInitialized) { mountedPackState.SaveSelectionInitialized = true; mountedPackState.NativeUnlockedAtSelection = nativeUnlockedCount; } foreach (KeyValuePair item in islandContinuations.OrderBy, string>((KeyValuePair pair) => pair.Key, StringComparer.Ordinal)) { if (!mountedPackState.IslandFlags.ContainsKey(item.Key + ":introduced")) { mountedPackState.IslandFlags.Add(item.Key + ":introduced", value: true); mountedPackState.IslandFlags.Add(item.Key + ":preserve_native_access", item.Value.HasValue && nativeUnlockedCount >= item.Value.Value); flag = true; } } if (flag) { Commit("world", "select-save", mountedPackState, delegate { }); } } public void Deliver(string key, Action consumeNativeItem) { RequireHealthy(); if (!CanDeliver(key)) { throw new InvalidOperationException("Quest cannot accept another delivery: " + key); } MountedPackState mountedPackState = Copy(state); Find(mountedPackState, key).Delivered++; Commit(key, "deliver", mountedPackState, consumeNativeItem); } public void Claim(string key, Action grantNativeReward) { RequireHealthy(); if (!PendingReward(key) || !RuleFor(key).Dependencies.All((string dependency) => Find(state, dependency).RewardClaimed)) { throw new InvalidOperationException("No claimable quest reward: " + key); } MountedPackState mountedPackState = Copy(state); Find(mountedPackState, key).RewardClaimed = true; foreach (string completionFlag in RuleFor(key).CompletionFlags) { mountedPackState.IslandFlags[completionFlag] = true; } Commit(key, "claim", mountedPackState, grantNativeReward); } public void SetIslandFlag(string key, bool value) { RequireHealthy(); if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentException("A stable island flag key is required."); } if (key.EndsWith(":introduced", StringComparison.Ordinal) || key.EndsWith(":preserve_native_access", StringComparison.Ordinal)) { throw new InvalidOperationException("Island introduction latches may only be initialized at save selection."); } if (!state.IslandFlags.TryGetValue(key, out var value2) || value2 != value) { MountedPackState mountedPackState = Copy(state); mountedPackState.IslandFlags[key] = value; Commit(key, "island-flag", mountedPackState, delegate { }); } } private void Commit(string key, string kind, MountedPackState next, Action nativeMutation) { if (nativeMutation == null) { throw new ArgumentNullException("nativeMutation"); } RequireHealthy(); next.Revision = checked(state.Revision + 1); QuestCheckpoint questCheckpoint = new QuestCheckpoint(key, kind, Copy(state), Copy(next)); int invoked = 0; bool applied = false; executing = true; try { checkpoint(questCheckpoint, delegate { if (++invoked != 1) { throw new InvalidOperationException("A checkpoint attempted to apply a mutation twice."); } applying = true; try { nativeMutation(); state = next; applied = true; } catch (Exception fault) { Fault = fault; throw; } finally { applying = false; } }); if (invoked != 1 || !applied) { throw new InvalidOperationException("The checkpoint did not successfully apply its native mutation exactly once."); } } catch (Exception ex) { Exception innerException = (Fault = ex); throw new InvalidOperationException("Quest checkpoint failed. Reload the paired native/pack checkpoint before any further delivery or claim.", innerException); } finally { executing = false; } } private void Validate(MountedPackState candidate) { ValidateDefinitions(); if (candidate == null || candidate.Version != 1 || candidate.Revision < 0 || candidate.Quests == null || candidate.IslandFlags == null || (candidate.SaveSelectionInitialized && (candidate.NativeUnlockedAtSelection < 1 || candidate.NativeUnlockedAtSelection > 5)) || (!candidate.SaveSelectionInitialized && candidate.NativeUnlockedAtSelection != 0)) { throw new ArgumentException("Invalid mounted-pack save state."); } HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (QuestProgress quest in candidate.Quests) { if (quest == null || string.IsNullOrEmpty(quest.Key) || !hashSet.Add(quest.Key)) { throw new ArgumentException("Duplicate or missing saved quest identity."); } Rule rule = RuleFor(quest.Key); if (quest.Delivered < 0 || quest.Delivered > rule.Total || (quest.RewardClaimed && quest.Delivered != rule.Total)) { throw new ArgumentException("Invalid saved quest progress: " + quest.Key); } } foreach (QuestProgress item in candidate.Quests.Where((QuestProgress quest) => quest.Delivered > 0)) { if (!RuleFor(item.Key).Dependencies.All((string key) => candidate.Quests.Any((QuestProgress questProgress) => questProgress.Key == key && questProgress.RewardClaimed))) { throw new ArgumentException("Saved quest progress violates dependencies: " + item.Key); } } if (candidate.IslandFlags.Keys.Any(string.IsNullOrWhiteSpace)) { throw new ArgumentException("Invalid saved island flag key."); } foreach (string key in islandContinuations.Keys) { bool value; bool flag = candidate.IslandFlags.TryGetValue(key + ":introduced", out value); bool flag2 = candidate.IslandFlags.ContainsKey(key + ":preserve_native_access"); if (flag != flag2 || (flag && (!value || !candidate.SaveSelectionInitialized))) { throw new ArgumentException("Invalid island introduction latch: " + key); } } } private static void RequireCustomIslandReference(string reference) { int num = reference?.IndexOf(':') ?? (-1); if (string.IsNullOrWhiteSpace(reference) || num <= 0 || num == reference.Length - 1 || reference.LastIndexOf(':') != num || reference.StartsWith("native:", StringComparison.Ordinal)) { throw new ArgumentException("A qualified custom island reference is required.", "reference"); } } private Rule RuleFor(string key) { if (!rules.TryGetValue(key, out Rule value)) { throw new InvalidOperationException("Unknown quest key: " + key); } return value; } private static QuestProgress Find(MountedPackState source, string key) { return source.Quests.SingleOrDefault((QuestProgress value) => value.Key == key) ?? throw new InvalidOperationException("Missing quest state: " + key); } private void RequireHealthy() { if (Fault != null) { throw new InvalidOperationException("Quest store requires paired checkpoint recovery.", Fault); } if (executing) { throw new InvalidOperationException("Nested quest mutations are not supported."); } } private static MountedPackState Copy(MountedPackState source) { return new MountedPackState { Version = source.Version, Revision = source.Revision, SaveSelectionInitialized = source.SaveSelectionInitialized, NativeUnlockedAtSelection = source.NativeUnlockedAtSelection, Quests = source.Quests.Select((QuestProgress value) => new QuestProgress { Key = value.Key, Delivered = value.Delivered, RewardClaimed = value.RewardClaimed }).ToList(), IslandFlags = new Dictionary(source.IslandFlags, StringComparer.Ordinal) }; } } }