Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of BasedItemSaving v0.0.1
BasedItemSaving.dll
Decompiled 9 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BasedItemSaving.API; using BasedItemSaving.Saving; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Dawn; using Dawn.Internal; using HatLib; using HatLib.Tools; using Microsoft.CodeAnalysis; using Newtonsoft.Json.Linq; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("BasedItemSaving")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("0.0.1.0")] [assembly: AssemblyInformationalVersion("0.0.1")] [assembly: AssemblyProduct("BasedItemSaving")] [assembly: AssemblyTitle("BasedItemSaving")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.1.0")] [module: UnverifiableCode] [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 BasedItemSaving { [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("MrHat.BasedItemSaving", "BasedItemSaving", "0.0.1")] internal class Plugin : BaseUnityPlugin { internal static ManualLogSource? mls; private void Awake() { mls = Logger.CreateLogSource("MrHat.BasedItemSaving"); ConfigManager.Bind(((BaseUnityPlugin)this).Config); ShipItemSaving.Init(); ManualLogSource? obj = mls; if (obj != null) { obj.LogInfo((object)"BasedItemSaving 0.0.1 loaded"); } } } internal static class ConfigManager { private static readonly HashSet<NamespacedKey<DawnUnlockableItemInfo>> excludedUnlockables = new HashSet<NamespacedKey<DawnUnlockableItemInfo>>(); internal static void Bind(ConfigFile config) { ConfigEntry<string> exclusions = config.Bind<string>("Item Saving", "Excluded Unlockables", "misideitems:houseteleporter,\nmelaniepocketroom:melmstockroomzero, melaniepocketroom:melmbedroomzero, melaniepocketroom:melmbedroomone,\nunknown_lib:basement, unknown_lib:house, unknown_lib:poolroom, unknown_lib:garden,\nmelaniecrafting:workbench, melaniecrafting:crucible,\nmelaniemeliciouscooked:grinder, melaniemeliciouscooked:blender, melaniemeliciouscooked:crockpot, melaniemeliciouscooked:toasteroven, melaniemeliciouscooked:portablegrill, melaniemeliciouscooked:oven, melaniemeliciouscooked:minifridge,\nmelaniemeliciouscooked:plantera, melaniemeliciouscooked:planterb, melaniemeliciouscooked:planterc, melaniemeliciouscooked:trellisa, melaniemeliciouscooked:trellisb, melaniemeliciouscooked:plantershelfa,\nmelaniemeliciouscooked:barrela, melaniemeliciouscooked:barrelb, melaniemeliciouscooked:wineracka, melaniemeliciouscooked:beeboxa, melaniemeliciouscooked:pota, melaniemeliciouscooked:icecreammaker, melaniemeliciouscooked:seedmaker", "Unlockables that should be ignored and excluded from item reparenting"); ReadExclusions(exclusions.Value); exclusions.SettingChanged += delegate { ReadExclusions(exclusions.Value); }; } internal static bool IsExcluded(NamespacedKey<DawnUnlockableItemInfo> key) { return excludedUnlockables.Contains(key); } private static void ReadExclusions(string value) { excludedUnlockables.Clear(); string[] array = value.Split(',', StringSplitOptions.RemoveEmptyEntries); NamespacedKey val = default(NamespacedKey); foreach (string text in array) { string text2 = text.Trim(); if (NamespacedKey.TryParse(text2, ref val)) { excludedUnlockables.Add(val.AsTyped<DawnUnlockableItemInfo>()); continue; } ManualLogSource? mls = Plugin.mls; if (mls != null) { mls.LogError((object)("Excluded unlockable key " + text2 + " could not be read")); } } } } internal static class Log { internal static void Warn(string message) { ManualLogSource? mls = Plugin.mls; if (mls != null) { mls.LogWarning((object)message); } } } public static class MyPluginInfo { public const string PLUGIN_GUID = "BasedItemSaving"; public const string PLUGIN_NAME = "BasedItemSaving"; public const string PLUGIN_VERSION = "0.0.1"; } } namespace BasedItemSaving.Saving { internal class FurnitureRelationship(PlaceableObjectsSurface surface, PlaceableShipObject shipObject, Transform itemParent, NamespacedKey<DawnUnlockableItemInfo> furnitureKey, string parentPath) { internal PlaceableObjectsSurface Surface { get; } = surface; internal PlaceableShipObject ShipObject { get; } = shipObject; internal Transform ItemParent { get; } = itemParent; internal NamespacedKey<DawnUnlockableItemInfo> FurnitureKey { get; } = furnitureKey; internal string ParentPath { get; } = parentPath; internal static FurnitureRelationship? FromItem(GrabbableObject item, out bool surfaceFound) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) surfaceFound = false; Transform parent = ((Component)item).transform.parent; PlaceableObjectsSurface val = (((Object)(object)parent == (Object)null) ? null : SurfaceAt(parent, item, ((Component)item).transform.position, Object.FindObjectsByType<PlaceableObjectsSurface>((FindObjectsSortMode)0), out surfaceFound)); object obj; if (val == null) { obj = null; } else { NetworkObject parentTo = val.parentTo; obj = ((parentTo != null) ? ((Component)parentTo).GetComponentInChildren<PlaceableShipObject>(true) : null); } PlaceableShipObject val2 = (PlaceableShipObject)obj; StartOfRound instance = StartOfRound.Instance; if ((Object)(object)val2?.parentObject == (Object)null || (Object)(object)instance?.unlockablesList == (Object)null) { return null; } int unlockableID = val2.unlockableID; UnlockableItem val3 = ((unlockableID >= 0 && unlockableID < instance.unlockablesList.unlockables.Count) ? instance.unlockablesList.unlockables[unlockableID] : null); DawnUnlockableItemInfo val4 = ((val3 != null) ? UnlockableItemExtensions.GetDawnInfo(val3) : null); string text = Utility.FurnitureParentPath(((Component)val2.parentObject).transform, parent); return (val3 == null || val4 == null || val3.inStorage || text == null) ? null : new FurnitureRelationship(val, val2, parent, ((DawnBaseInfo<DawnUnlockableItemInfo>)(object)val4).TypedKey, text); } internal static bool RestoreParent(StartOfRound round, GrabbableObject item, SavedItemPose pose, out PlaceableShipObject? shipObject, out Transform? parent, out bool unresolved) { //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) shipObject = null; parent = null; unresolved = false; NamespacedKey val = default(NamespacedKey); if (!NamespacedKey.TryParse(pose.furnitureKey, ref val)) { unresolved = true; return false; } NamespacedKey<DawnUnlockableItemInfo> key = val.AsTyped<DawnUnlockableItemInfo>(); if (ConfigManager.IsExcluded(key)) { return false; } shipObject = ((IEnumerable<PlaceableShipObject>)Object.FindObjectsByType<PlaceableShipObject>((FindObjectsSortMode)0)).FirstOrDefault((Func<PlaceableShipObject, bool>)delegate(PlaceableShipObject candidate) { int unlockableID = candidate.unlockableID; int result; if (unlockableID >= 0 && unlockableID < round.unlockablesList.unlockables.Count) { UnlockableItem obj2 = round.unlockablesList.unlockables[unlockableID]; result = ((obj2 != null && ((object)((DawnBaseInfo<DawnUnlockableItemInfo>)(object)UnlockableItemExtensions.GetDawnInfo(obj2))?.TypedKey).Equals((object?)key) == true) ? 1 : 0); } else { result = 0; } return (byte)result != 0; }); PlaceableShipObject? obj = shipObject; if (obj != null) { AutoParentToShip parentObject = obj.parentObject; if (((parentObject != null) ? new bool?(((NetworkBehaviour)parentObject).IsSpawned) : ((bool?)null)) == true && !round.unlockablesList.unlockables[shipObject.unlockableID].inStorage && !((Object)(object)(parent = Utility.FurnitureParentFromPath(((Component)shipObject.parentObject).transform, pose.furnitureParentPath)) == (Object)null)) { bool found; PlaceableObjectsSurface val2 = SurfaceAt(parent, item, parent.TransformPoint(pose.localPosition), ((Component)shipObject.parentObject).GetComponentsInChildren<PlaceableObjectsSurface>(true), out found); PlaceableObjectsSurfaceRegistrationAPI registration; return (Object)(object)val2 == (Object)null || !PlaceableObjectsSurfaceRegistry.TryGet(val2, out registration) || registration.AllowAutomaticSaving; } } unresolved = true; return false; } private static PlaceableObjectsSurface? SurfaceAt(Transform parent, GrabbableObject item, Vector3 position, PlaceableObjectsSurface[] surfaces, out bool found) { //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) PlaceableObjectsSurface[] array = surfaces.Where((PlaceableObjectsSurface surface) => (Object)(object)ParentFor(surface) == (Object)(object)parent).ToArray(); found = array.Length != 0; if (array.Length < 2) { return array.FirstOrDefault(); } float offset = item.itemProperties?.verticalOffset ?? 0f; return array.Where((PlaceableObjectsSurface surface) => (Object)(object)surface.placeableBounds != (Object)null).OrderBy(delegate(PlaceableObjectsSurface surface) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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_0029: 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_002c: 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_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) Collider placeableBounds = surface.placeableBounds; Vector3 val = position - ((Component)placeableBounds).transform.up * offset; Vector3 val2 = placeableBounds.ClosestPoint(val) - val; return ((Vector3)(ref val2)).sqrMagnitude; }).FirstOrDefault(); } private static Transform? ParentFor(PlaceableObjectsSurface surface) { object obj; if (surface == null) { obj = null; } else { NetworkObject parentTo = surface.parentTo; obj = ((parentTo != null) ? ((Component)parentTo).transform : null); } Transform val = (Transform)obj; PlayerPhysicsRegion val2 = ((val != null) ? ((IEnumerable<PlayerPhysicsRegion>)((Component)val).GetComponentsInChildren<PlayerPhysicsRegion>()).FirstOrDefault((Func<PlayerPhysicsRegion, bool>)((PlayerPhysicsRegion region) => ((Behaviour)region).isActiveAndEnabled)) : null); return (val2 != null && val2.allowDroppingItems && (Object)(object)val2.physicsTransform != (Object)null) ? val2.physicsTransform : val; } } internal static class Utility { internal static string? FurnitureParentPath(Transform root, Transform parent) { if ((Object)(object)parent == (Object)(object)root) { return string.Empty; } List<int> list = new List<int>(); Transform val = parent; while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)root) { list.Add(val.GetSiblingIndex()); val = val.parent; } if ((Object)(object)val != (Object)(object)root) { return null; } list.Reverse(); return string.Join("/", list); } internal static Transform? FurnitureParentFromPath(Transform root, string path) { Transform val = root; if (string.IsNullOrWhiteSpace(path)) { return val; } string[] array = path.Split('/'); foreach (string s in array) { if (!int.TryParse(s, out var result) || result < 0 || result >= val.childCount) { return null; } val = val.GetChild(result); } return val; } internal static bool PlaceFurniturePose(GrabbableObject? item, Transform parent, Vector3 position, Quaternion rotation) { //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) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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_0039: 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_0042: 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_0049: 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) if ((Object)(object)item == (Object)null || !GrabbablePlacement.Place(item, parent, parent.TransformPoint(position), parent.rotation * rotation)) { return false; } ((Component)item).transform.SetLocalPositionAndRotation(position, rotation); item.targetFloorPosition = position; item.startFallingPosition = position + Vector3.up * 0.0001f; return true; } } internal static class ShipItemSaving { [CompilerGenerated] private static class <>O { public static Action<GrabbableObject, JObject> <0>__SaveItem; public static Action<GrabbableObject, JObject> <1>__ReadItem; public static Action<StartOfRound> <2>__RoundStarted; public static Action<StartOfRound> <3>__ItemsLoaded; public static Action<StartOfRound> <4>__UnlockablesSynced; public static Action <5>__GameSaved; public static Action<StartOfRound> <6>__RoundCleared; public static HatNetMessageHandler <7>__AssignmentsReceived; public static HatNetMessageHandler <8>__AssignmentsRequested; public static Action<GrabbableObject> <9>__MarkBasedItem; } private const float NetworkWaitSeconds = 8f; private static readonly NamespacedKey PoseKey = NamespacedKey.From("baseditemsaving", "exact_pose"); private static readonly NamespacedKey AssignmentKey = NamespacedKey.From("baseditemsaving", "item_assignments"); private static readonly NamespacedKey RequestKey = NamespacedKey.From("baseditemsaving", "item_assignments_request"); private static readonly Dictionary<GrabbableObject, SavedItemPose> pending = new Dictionary<GrabbableObject, SavedItemPose>(); private static readonly Dictionary<ulong, ItemAssignment> hostItems = new Dictionary<ulong, ItemAssignment>(); private static bool unlockablesSynced; internal static void Init() { //IL_011e: 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_0129: Expected O, but got Unknown //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Expected O, but got Unknown DawnItemSaveEvents.OnCollectExtraSaveData += SaveItem; DawnItemSaveEvents.OnLoadExtraSaveData += ReadItem; RoundEvents.OnRoundStart += RoundStarted; RoundEvents.OnShipItemsLoaded += ItemsLoaded; RoundEvents.OnShipUnlockablesSynced += UnlockablesSynced; RoundEvents.OnGameSaved += GameSaved; RoundEvents.OnLocalDisconnect += RoundCleared; RoundEvents.OnDestroy += RoundCleared; NamespacedKey assignmentKey = AssignmentKey; object obj = <>O.<7>__AssignmentsReceived; if (obj == null) { HatNetMessageHandler val = AssignmentsReceived; <>O.<7>__AssignmentsReceived = val; obj = (object)val; } HatNet.AddClientHandler(assignmentKey, (HatNetMessageHandler)obj); NamespacedKey requestKey = RequestKey; object obj2 = <>O.<8>__AssignmentsRequested; if (obj2 == null) { HatNetMessageHandler val2 = AssignmentsRequested; <>O.<8>__AssignmentsRequested = val2; obj2 = (object)val2; } HatNet.AddServerHandler(requestKey, (HatNetMessageHandler)obj2); } private static void SaveItem(GrabbableObject item, JObject extraData) { FurnitureRelationship furniture; SavedItemPose savedItemPose = PoseFor(item, out furniture); if (savedItemPose == null) { extraData.Remove(((object)PoseKey).ToString()); Forget(item); return; } extraData[((object)PoseKey).ToString()] = (JToken)(object)JObject.Parse(JsonUtility.ToJson((object)savedItemPose)); NetworkManager singleton = NetworkManager.Singleton; if (singleton != null && singleton.IsServer) { NetworkObject networkObject = ((NetworkBehaviour)item).NetworkObject; if (networkObject != null && networkObject.IsSpawned) { hostItems[((NetworkBehaviour)item).NetworkObjectId] = new ItemAssignment(item, savedItemPose, furniture); PlaceableObjectsSurfaceRegistry.MarkBasedItem(item); } } } private static void ReadItem(GrabbableObject item, JObject extraData) { NetworkManager singleton = NetworkManager.Singleton; if (singleton == null || !singleton.IsServer) { return; } JToken val = extraData[((object)PoseKey).ToString()]; if (val == null) { return; } try { SavedItemPose savedItemPose = JsonUtility.FromJson<SavedItemPose>(((object)val).ToString()); bool flag = savedItemPose == null; bool flag2 = flag; if (!flag2) { ItemParentKind parentKind = savedItemPose.parentKind; bool flag3 = ((parentKind < ItemParentKind.Ship || parentKind > ItemParentKind.Furniture) ? true : false); flag2 = flag3; } if (!flag2) { pending[item] = savedItemPose; PlaceableObjectsSurfaceRegistry.MarkBasedItem(item); } } catch (ArgumentException) { Log.Warn("Saved item pose could not be read"); } } private static void RoundStarted(StartOfRound round) { unlockablesSynced = false; if (!((NetworkBehaviour)round).IsServer) { RequestAssignments(); } } private static void GameSaved() { NetworkManager singleton = NetworkManager.Singleton; if (singleton != null && singleton.IsServer) { ManualLogSource? mls = Plugin.mls; if (mls != null) { mls.LogDebug((object)$"Saved {hostItems.Count} exact item poses"); } } } private static void ItemsLoaded(StartOfRound round) { if (((NetworkBehaviour)round).IsServer) { ((MonoBehaviour)round).StartCoroutine(RestoreHost(round)); } } private static IEnumerator RestoreHost(StartOfRound round) { for (int i = 0; i < 4; i++) { yield return null; } ItemAssignmentList restored = new ItemAssignmentList(); int unresolvedCount = 0; foreach (KeyValuePair<GrabbableObject, SavedItemPose> item2 in pending) { item2.Deconstruct(out var key, out var value); GrabbableObject item = key; SavedItemPose pose = value; bool unresolved = false; ItemAssignment assignment = ((pose.parentKind != ItemParentKind.Ship) ? RestoreFurniture(round, item, pose, out unresolved) : ((!PlaceableObjectsSurfaceRegistry.IsExplicitlyIgnored(item) && GrabbablePlacement.Place(item, round.elevatorTransform, pose.worldPosition, pose.worldRotation)) ? new ItemAssignment(item, pose) : null)); if (assignment == null) { if (pose.parentKind == ItemParentKind.Furniture && unresolved) { unresolvedCount++; } Forget(item); } else { hostItems[((NetworkBehaviour)item).NetworkObjectId] = assignment; PlaceableObjectsSurfaceRegistry.MarkBasedItem(item); restored.Items.Add(assignment); } } if (unresolvedCount > 0) { Log.Warn($"{unresolvedCount} furniture item relationships could not be resolved and Dawn poses were left unchanged"); } ManualLogSource? mls = Plugin.mls; if (mls != null) { mls.LogDebug((object)$"Host restored {restored.Items.Count} of {pending.Count} saved item poses"); } if (!HatNet.SendToClients(AssignmentKey, JsonUtility.ToJson((object)restored))) { Log.Warn("Restored item assignments could not be sent to clients"); } pending.Clear(); } private static ItemAssignment? RestoreFurniture(StartOfRound round, GrabbableObject item, SavedItemPose pose, out bool unresolved) { //IL_0028: 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) if (PlaceableObjectsSurfaceRegistry.IsExplicitlyIgnored(item)) { unresolved = false; return null; } if (!FurnitureRelationship.RestoreParent(round, item, pose, out PlaceableShipObject shipObject, out Transform parent, out unresolved) || !Utility.PlaceFurniturePose(item, parent, pose.localPosition, pose.localRotation)) { return null; } return new ItemAssignment(item, pose) { FurnitureNetworkId = ((NetworkBehaviour)shipObject.parentObject).NetworkObjectId }; } private static SavedItemPose? PoseFor(GrabbableObject item, out FurnitureRelationship? furniture) { //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_0048: 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_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: 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_0143: 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_014d: Unknown result type (might be due to invalid IL or missing references) furniture = null; if ((Object)(object)item == (Object)null || PlaceableObjectsSurfaceRegistry.IsExplicitlyIgnored(item)) { return null; } SavedItemPose savedItemPose = new SavedItemPose { parentKind = ItemParentKind.Ship, worldPosition = ((Component)item).transform.position, worldRotation = ((Component)item).transform.rotation }; if (item is ClipboardItem || (item is PhysicsProp && item.itemProperties?.itemName == "Sticky note")) { return savedItemPose; } bool surfaceFound; FurnitureRelationship furnitureRelationship = FurnitureRelationship.FromItem(item, out surfaceFound); if (!surfaceFound) { return savedItemPose; } if (furnitureRelationship == null || ConfigManager.IsExcluded(furnitureRelationship.FurnitureKey) || (PlaceableObjectsSurfaceRegistry.TryGet(furnitureRelationship.Surface, out var registration) && !registration.AllowAutomaticSaving)) { return null; } furniture = furnitureRelationship; savedItemPose.parentKind = ItemParentKind.Furniture; savedItemPose.furnitureKey = ((object)furnitureRelationship.FurnitureKey).ToString(); savedItemPose.furnitureParentPath = furnitureRelationship.ParentPath; savedItemPose.localPosition = furnitureRelationship.ItemParent.InverseTransformPoint(((Component)item).transform.position); savedItemPose.localRotation = Quaternion.Inverse(furnitureRelationship.ItemParent.rotation) * ((Component)item).transform.rotation; return savedItemPose; } private static void Forget(GrabbableObject? item) { if (!((Object)(object)item == (Object)null)) { NetworkManager singleton = NetworkManager.Singleton; if (singleton != null && singleton.IsServer) { hostItems.Remove(((NetworkBehaviour)item).NetworkObjectId); PlaceableObjectsSurfaceRegistry.UnmarkBasedItem(item); } } } private static void UnlockablesSynced(StartOfRound round) { if (!((NetworkBehaviour)round).IsServer) { unlockablesSynced = true; RequestAssignments(); } } private static void RequestAssignments() { if (!HatNet.SendToServer(RequestKey, string.Empty)) { Log.Warn("Current item assignments could not be requested"); } } private static void AssignmentsRequested(ulong clientId, string data) { ItemAssignmentList itemAssignmentList = new ItemAssignmentList { Items = hostItems.Values.ToList() }; if (!HatNet.SendToClient(AssignmentKey, clientId, JsonUtility.ToJson((object)itemAssignmentList))) { Log.Warn("Current item assignments could not be returned to a client"); } } private static void AssignmentsReceived(ulong clientId, string data) { NetworkManager singleton = NetworkManager.Singleton; if ((singleton != null && singleton.IsServer) || clientId != 0) { return; } try { ItemAssignmentList itemAssignmentList = JsonUtility.FromJson<ItemAssignmentList>(data); StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || itemAssignmentList?.Items == null) { return; } foreach (ItemAssignment item in itemAssignmentList.Items) { NetworkRefs.WhenReady<GrabbableObject>((MonoBehaviour)(object)instance, item.ItemNetworkId, (Action<GrabbableObject>)PlaceableObjectsSurfaceRegistry.MarkBasedItem, 8f, 0); } if (unlockablesSynced) { PlaceClientItems(instance, itemAssignmentList); } } catch (ArgumentException) { Log.Warn("Saved item assignments could not be read"); } } private static void PlaceClientItems(StartOfRound round, ItemAssignmentList assignments) { foreach (ItemAssignment assignment in assignments.Items) { if (assignment.Pose.parentKind == ItemParentKind.Ship) { NetworkRefs.WhenReady<GrabbableObject>((MonoBehaviour)(object)round, assignment.ItemNetworkId, (Action<GrabbableObject>)delegate(GrabbableObject item) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) assignments.PlacedCount += (GrabbablePlacement.Place((item.isHeld || (Object)(object)item.playerHeldBy != (Object)null || item.isHeldByEnemy) ? null : item, round.elevatorTransform, assignment.Pose.worldPosition, assignment.Pose.worldRotation) ? 1 : 0); }, 8f, 4); continue; } NetworkRefs.WhenReady((MonoBehaviour)(object)round, assignment.FurnitureNetworkId, (Action<NetworkObject>)delegate(NetworkObject root) { Transform parent = Utility.FurnitureParentFromPath(((Component)root).transform, assignment.Pose.furnitureParentPath); if (!((Object)(object)parent == (Object)null)) { NetworkRefs.WhenReady<GrabbableObject>((MonoBehaviour)(object)round, assignment.ItemNetworkId, (Action<GrabbableObject>)delegate(GrabbableObject item) { //IL_004e: 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) assignments.PlacedCount += (Utility.PlaceFurniturePose((item.isHeld || (Object)(object)item.playerHeldBy != (Object)null || item.isHeldByEnemy) ? null : item, parent, assignment.Pose.localPosition, assignment.Pose.localRotation) ? 1 : 0); }, 8f, 4); } }, 8f, 0); } ((MonoBehaviour)round).StartCoroutine(ClientPlacementSummary(assignments)); } private static IEnumerator ClientPlacementSummary(ItemAssignmentList assignments) { yield return (object)new WaitForSecondsRealtime(16.25f); ManualLogSource? mls = Plugin.mls; if (mls != null) { mls.LogDebug((object)$"Client restored {assignments.PlacedCount} item poses with {assignments.Items.Count - assignments.PlacedCount} unresolved"); } } private static void RoundCleared(StartOfRound round) { pending.Clear(); hostItems.Clear(); PlaceableObjectsSurfaceRegistry.ClearRoundItems(); } } internal enum ItemParentKind { Ship, Furniture } [Serializable] internal class SavedItemPose { public ItemParentKind parentKind; public Vector3 worldPosition; public Vector3 localPosition; public Quaternion worldRotation; public Quaternion localRotation; public string furnitureKey = string.Empty; public string furnitureParentPath = string.Empty; } [Serializable] internal class ItemAssignment { public ulong ItemNetworkId; public ulong FurnitureNetworkId; public SavedItemPose Pose = null; internal ItemAssignment(GrabbableObject item, SavedItemPose pose, FurnitureRelationship? furniture = null) { ItemNetworkId = ((NetworkBehaviour)item).NetworkObjectId; ulong? obj; if (furniture == null) { obj = null; } else { AutoParentToShip parentObject = furniture.ShipObject.parentObject; obj = ((parentObject != null) ? new ulong?(((NetworkBehaviour)parentObject).NetworkObjectId) : ((ulong?)null)); } ulong? num = obj; FurnitureNetworkId = num.GetValueOrDefault(); Pose = pose; } } [Serializable] internal class ItemAssignmentList { public List<ItemAssignment> Items = new List<ItemAssignment>(); internal int PlacedCount; } } namespace BasedItemSaving.API { public readonly struct PlaceableObjectsSurfaceRegistrationAPI { public string OwnerGuid { get; } public Type SurfaceType { get; } public bool AllowAutomaticSaving { get; } public PlaceableObjectsSurfaceRegistrationAPI(string ownerGuid, Type surfaceType, bool allowAutomaticSaving) { OwnerGuid = ownerGuid; SurfaceType = surfaceType; AllowAutomaticSaving = allowAutomaticSaving; } } public static class PlaceableObjectsSurfaceRegistry { private static readonly List<PlaceableObjectsSurfaceRegistrationAPI> registrations = new List<PlaceableObjectsSurfaceRegistrationAPI>(); private static readonly HashSet<GrabbableObject> ignoredItems = new HashSet<GrabbableObject>(); private static readonly HashSet<GrabbableObject> basedItems = new HashSet<GrabbableObject>(); public static void Register<TSurface>(string ownerGuid, bool allowAutomaticSaving) where TSurface : PlaceableObjectsSurface { Type surfaceType = typeof(TSurface); if (!registrations.Any((PlaceableObjectsSurfaceRegistrationAPI entry) => entry.OwnerGuid == ownerGuid && entry.SurfaceType == surfaceType)) { registrations.Add(new PlaceableObjectsSurfaceRegistrationAPI(ownerGuid, surfaceType, allowAutomaticSaving)); } } public static bool TryGet(PlaceableObjectsSurface surface, out PlaceableObjectsSurfaceRegistrationAPI registration) { registration = registrations.FirstOrDefault((PlaceableObjectsSurfaceRegistrationAPI entry) => entry.SurfaceType.IsInstanceOfType(surface)); return registration.SurfaceType != null; } public static void Ignore(GrabbableObject item) { ignoredItems.Add(item); } public static void Allow(GrabbableObject item) { ignoredItems.Remove(item); } public static bool IsIgnored(GrabbableObject item) { return (Object)(object)item != (Object)null && (ignoredItems.Contains(item) || basedItems.Contains(item)); } internal static bool IsExplicitlyIgnored(GrabbableObject item) { return (Object)(object)item != (Object)null && ignoredItems.Contains(item); } internal static void MarkBasedItem(GrabbableObject item) { basedItems.Add(item); } internal static void UnmarkBasedItem(GrabbableObject item) { basedItems.Remove(item); } internal static void ClearRoundItems() { ignoredItems.Clear(); basedItems.Clear(); } } }