Decompiled source of AmorLib v1.2.1
AmorLib.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using AIGraph; using Agents; using AmorLib.API; using AmorLib.Dependencies; using AmorLib.Events; using AmorLib.Utils; using AmorLib.Utils.Extensions; using AmorLib.Utils.JsonElementConverters; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using CullingSystem; using GTFO.API; using GTFO.API.Extensions; using GTFO.API.JSON; using GTFO.API.Utilities; using GameData; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using InjectLib.JsonNETInjection.Supports; using LevelGeneration; using Localization; using MTFO.Ext.PartialData; using Microsoft.CodeAnalysis; using Player; using SNetwork; using SemanticVersioning; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("AmorLib")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+b41318f3ac2a7407b947876732694101ea94314d")] [assembly: AssemblyProduct("AmorLib")] [assembly: AssemblyTitle("AmorLib")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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; } } } namespace AmorLib { [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] internal sealed class CallConstructorOnLoadAttribute : Attribute { } [BepInPlugin("Amor.AmorLib", "AmorLib", "1.2.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] internal sealed class EntryPoint : BasePlugin { public override void Load() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) new Harmony("Amor.AmorLib").PatchAll(); CallAllAutoConstructors(); Logger.Info("AmorLib is done loading!"); } private void CallAllAutoConstructors() { foreach (Type item in from t in AccessTools.GetTypesFromAssembly(((object)this).GetType().Assembly) where t.IsDefined(typeof(CallConstructorOnLoadAttribute), inherit: false) select t) { RuntimeHelpers.RunClassConstructor(item.TypeHandle); } } } internal static class Logger { private static readonly ManualLogSource MLS; static Logger() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown MLS = new ManualLogSource("AmorLib"); Logger.Sources.Add((ILogSource)(object)MLS); } public static void Info(BepInExInfoLogInterpolatedStringHandler handler) { MLS.LogInfo(handler); } public static void Info(string str) { MLS.LogMessage((object)str); } public static void Debug(BepInExDebugLogInterpolatedStringHandler handler) { MLS.LogDebug(handler); } public static void Debug(string str) { MLS.LogDebug((object)str); } public static void Error(BepInExErrorLogInterpolatedStringHandler handler) { MLS.LogError(handler); } public static void Error(string str) { MLS.LogError((object)str); } public static void Warn(BepInExWarningLogInterpolatedStringHandler handler) { MLS.LogWarning(handler); } public static void Warn(string str) { MLS.LogWarning((object)str); } } } namespace AmorLib.Utils { [CallConstructorOnLoad] public static class CourseNodeUtil { private class DimensionMap { private const float IndexSize = 4f; public List<AIG_CourseNode>?[,] BuildNodeMap; public AIG_CourseNode[,][] NodeMap; public (int x, int z) MinCellBound; public (int x, int z) MaxCellBound; public (int x, int z) MapSize; internal DimensionMap() { BuildNodeMap = null; NodeMap = null; MapSize = (0, 0); MinCellBound = (int.MaxValue, int.MaxValue); MaxCellBound = (int.MinValue, int.MinValue); } internal void UpdateBounds(Vector3 position) { //IL_0000: 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) int val = (int)(position.x / 4f); int val2 = (int)(position.z / 4f); MinCellBound = (Math.Min(val, MinCellBound.x), Math.Min(val2, MinCellBound.z)); MaxCellBound = (Math.Max(val, MaxCellBound.x), Math.Max(val2, MaxCellBound.z)); } internal void CreateNodeMap() { MapSize = (MaxCellBound.x - MinCellBound.x + 1, MaxCellBound.z - MinCellBound.z + 1); BuildNodeMap = new List<AIG_CourseNode>[MapSize.x, MapSize.z]; NodeMap = new AIG_CourseNode[MapSize.x, MapSize.z][]; } internal (int x, int z) GetMapPos(Vector3 position) { //IL_0000: 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) return (Math.Clamp((int)(position.x / 4f) - MinCellBound.x, 0, MapSize.x - 1), Math.Clamp((int)(position.z / 4f) - MinCellBound.z, 0, MapSize.z - 1)); } internal List<AIG_CourseNode> GetBuildNodeList(Vector3 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) (int x, int z) mapPos = GetMapPos(position); int item = mapPos.x; int item2 = mapPos.z; List<AIG_CourseNode> list = BuildNodeMap[item, item2]; if (list == null) { list = (BuildNodeMap[item, item2] = new List<AIG_CourseNode>()); } return list; } internal void FinishBuild() { for (int i = 0; i < MapSize.x; i++) { for (int j = 0; j < MapSize.z; j++) { NodeMap[i, j] = BuildNodeMap[i, j]?.ToArray(); } } BuildNodeMap = null; } public AIG_CourseNode[] GetNodes(Vector3 position) { //IL_0001: 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_015d: Expected O, but got Unknown //IL_016f: Unknown result type (might be due to invalid IL or missing references) var (num, num2) = GetMapPos(position); if (NodeMap[num, num2] != null) { return NodeMap[num, num2]; } float num3 = Math.Max(MapSize.x, MapSize.z); for (int i = 1; (float)i < num3; i++) { int num4 = Math.Max(num - i, 0); int num5 = Math.Min(num + i, MapSize.x - 1); int num6 = Math.Max(num2 - i, 0); int num7 = Math.Min(num2 + i, MapSize.z - 1); for (int j = num4; j <= num5; j++) { if (NodeMap[j, num6] != null) { return NodeMap[j, num6]; } if (NodeMap[j, num7] != null) { return NodeMap[j, num7]; } } for (int k = num6 + 1; k <= num7 - 1; k++) { if (NodeMap[num4, k] != null) { return NodeMap[num4, k]; } if (NodeMap[num5, k] != null) { return NodeMap[num5, k]; } } } bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(66, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Unable to get any node for ("); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Vector3>(position); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(")! How are you even playing the game?!"); } Logger.Error(val); return null; } } private static readonly Dictionary<eDimensionIndex, DimensionMap> _maps; static CourseNodeUtil() { _maps = new Dictionary<eDimensionIndex, DimensionMap>(); LevelAPI.OnAfterBuildBatch += OnAfterBuildBatch; } private static void OnAfterBuildBatch(BatchName batch) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0051: 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_0099: 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_0177: Unknown result type (might be due to invalid IL or missing references) if ((int)batch != 38) { return; } _maps.Clear(); Enumerator<AIG_NodeCluster> enumerator = AIG_NodeCluster.AllNodeClusters.GetEnumerator(); while (enumerator.MoveNext()) { AIG_NodeCluster current = enumerator.Current; object obj; if (current == null) { obj = null; } else { AIG_CourseNode courseNode = current.m_courseNode; obj = ((courseNode != null) ? courseNode.m_dimension : null); } if (obj != null) { if (!_maps.TryGetValue(current.m_courseNode.m_dimension.DimensionIndex, out DimensionMap value)) { _maps.Add(current.m_courseNode.m_dimension.DimensionIndex, value = new DimensionMap()); } Enumerator<AIG_INode> enumerator2 = current.m_nodes.GetEnumerator(); while (enumerator2.MoveNext()) { AIG_INode current2 = enumerator2.Current; value.UpdateBounds(current2.Position); } } } foreach (DimensionMap value2 in _maps.Values) { value2.CreateNodeMap(); } enumerator = AIG_NodeCluster.AllNodeClusters.GetEnumerator(); while (enumerator.MoveNext()) { AIG_NodeCluster current3 = enumerator.Current; object obj2; if (current3 == null) { obj2 = null; } else { AIG_CourseNode courseNode2 = current3.m_courseNode; obj2 = ((courseNode2 != null) ? courseNode2.m_dimension : null); } if (obj2 == null) { continue; } DimensionMap dimensionMap = _maps[current3.m_courseNode.m_dimension.DimensionIndex]; int id = current3.CourseNode.NodeID; Enumerator<AIG_INode> enumerator2 = current3.m_nodes.GetEnumerator(); while (enumerator2.MoveNext()) { AIG_INode current4 = enumerator2.Current; List<AIG_CourseNode> buildNodeList = dimensionMap.GetBuildNodeList(current4.Position); if (!buildNodeList.Any((AIG_CourseNode node) => id == node.NodeID)) { buildNodeList.Add(current3.CourseNode); } } } foreach (DimensionMap value3 in _maps.Values) { value3.FinishBuild(); } } private static bool TryGetClosestNodeInCluster(AIG_NodeCluster cluster, Vector3 position, out AIG_INode node) { //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_001a: 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_00b1: 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_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) AIG_VoxelNodeVolume voxelNodeVolume = cluster.m_nodeVolume.m_voxelNodeVolume; if (voxelNodeVolume.TryGetCloseVoxelNode_Safe(position, position.y - 0.5f, position.y + 0.5f, ref node, false)) { return true; } int num = default(int); int num2 = default(int); voxelNodeVolume.GetGridPosition(position, ref num, ref num2); num = Mathf.Clamp(num, 0, voxelNodeVolume.m_countX - 1); num2 = Mathf.Clamp(num2, 0, voxelNodeVolume.m_countZ - 1); float num3 = float.MaxValue; AIG_VoxelNode val = null; AIG_VoxelNodePillar val2 = default(AIG_VoxelNodePillar); for (int i = -1; i < 2; i++) { for (int j = -1; j < 2; j++) { if (!voxelNodeVolume.TryGetPillar(num + i, num2 + j, ref val2)) { continue; } Enumerator<AIG_VoxelNode> enumerator = val2.m_nodes.GetEnumerator(); while (enumerator.MoveNext()) { AIG_VoxelNode current = enumerator.Current; if (current.ClusterID != cluster.ID) { continue; } Vector3 val3 = current.Position - position; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; if (sqrMagnitude < num3) { val = current; num3 = sqrMagnitude; if (num3 < 0.55f) { node = ((Il2CppObjectBase)val).Cast<AIG_INode>(); return true; } } } } } if (val != null) { node = ((Il2CppObjectBase)val).Cast<AIG_INode>(); return true; } return false; } public static AIG_CourseNode ResolveCourseNode(AIG_CourseNode[] list, Vector3 position) { //IL_0030: 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_00b1: 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_00c1: Unknown result type (might be due to invalid IL or missing references) //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) if (list == null) { return null; } if (list.Length == 1) { return list[0]; } (AIG_CourseNode, AIG_INode)[] array = new(AIG_CourseNode, AIG_INode)[list.Length]; int num = 0; foreach (AIG_CourseNode val in list) { if (!TryGetClosestNodeInCluster(val.m_nodeCluster, position, out AIG_INode node)) { node = val.m_nodeCluster.m_nodes[0]; } array[num++] = (val, node); } bool flag = false; float num2 = float.MaxValue; AIG_CourseNode result = null; (AIG_CourseNode, AIG_INode)[] array2 = array; for (int i = 0; i < array2.Length; i++) { var (val2, val3) = array2[i]; if (val3 != null) { bool flag2 = val3.Position.y - 0.25f <= position.y; Vector3 val4 = position - val3.Position; float sqrMagnitude = ((Vector3)(ref val4)).sqrMagnitude; if ((!flag && flag2) || (flag == flag2 && sqrMagnitude < num2)) { flag = flag2; num2 = sqrMagnitude; result = val2; } } } return result; } public static AIG_CourseNode GetCourseNode(Vector3 position) { //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_000e: Unknown result type (might be due to invalid IL or missing references) ? val = position; Dimension dimensionFromPos = Dimension.GetDimensionFromPos(position); int num; if (dimensionFromPos == null) { num = 0; val = num; num = (int)val; } else { val = dimensionFromPos.DimensionIndex; num = (int)val; } return GetCourseNode((Vector3)num, (eDimensionIndex)val); } public static AIG_CourseNode GetCourseNode(Vector3 position, eDimensionIndex dimensionIndex) { //IL_0005: 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_0049: 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_001a: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (!_maps.TryGetValue(dimensionIndex, out DimensionMap value)) { bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(39, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("No Position-To-Node map for dimension "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<eDimensionIndex>(dimensionIndex); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("!"); } Logger.Error(val); return null; } return ResolveCourseNode(value.GetNodes(position), position); } public static AIG_CourseNode[] GetCourseNodes(Vector3 position, eDimensionIndex dimensionIndex) { //IL_0005: 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_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (!_maps.TryGetValue(dimensionIndex, out DimensionMap value)) { bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(39, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("No Position-To-Node map for dimension "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<eDimensionIndex>(dimensionIndex); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("!"); } Logger.Error(val); return null; } return value.GetNodes(position); } } public static class DataBlockUtil { public static bool TryGetBlock<T>(string name, [MaybeNullWhen(false)] out T block) where T : GameDataBlockBase<T> { if (PData_Wrapper.TryGetGUID(name, out var guid)) { return DataBlockUtil.TryGetBlock<T>(guid, out block); } block = GameDataBlockBase<T>.GetBlock(name); if (block != null) { return ((GameDataBlockBase<T>)block).internalEnabled; } return false; } public static bool TryGetBlock<T>(uint id, [MaybeNullWhen(false)] out T block) where T : GameDataBlockBase<T> { block = GameDataBlockBase<T>.GetBlock(id); if (block != null) { return ((GameDataBlockBase<T>)block).internalEnabled; } return false; } } public abstract class GlobalBase { private eDimensionIndex _dimIndex; private LG_LayerType _layerType; private eLocalZoneIndex _localIndex; private GlobalZoneIndex _struct; private (int dim, int layer, int zone) _tuple; public Dimension? Dimension; public LG_Zone? Zone; private bool _isDirty = true; [JsonPropertyOrder(-10)] public eDimensionIndex DimensionIndex { get { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return _dimIndex; } set { //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) _dimIndex = value; MarkDirty(); } } [JsonPropertyOrder(-10)] public LG_LayerType Layer { get { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return _layerType; } set { //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) _layerType = value; MarkDirty(); } } [JsonPropertyOrder(-10)] public eLocalZoneIndex LocalIndex { get { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return _localIndex; } set { //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) _localIndex = value; MarkDirty(); } } [JsonIgnore] public GlobalZoneIndex GlobalZoneIndex { get { //IL_0007: Unknown result type (might be due to invalid IL or missing references) Refresh(); return _struct; } } [JsonIgnore] public (int dimension, int layer, int zone) IntTuple { get { Refresh(); return _tuple; } } protected GlobalBase() { Refresh(); LevelAPI.OnAfterBuildBatch += OnAfterBuildBatch; LevelAPI.OnLevelCleanup += OnLevelCleanup; } private void OnAfterBuildBatch(BatchName batch) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0006: Unknown result type (might be due to invalid IL or missing references) if ((int)batch == 8) { Dimension val = default(Dimension); Dimension = (Dimension.GetDimension(_dimIndex, ref val) ? val : null); Zone = (_tuple.TryGetZone(out LG_Zone zone) ? zone : null); } } private void OnLevelCleanup() { Dimension = null; Zone = null; } private void MarkDirty() { _isDirty = true; } private void Refresh() { //IL_000b: 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_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_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) //IL_0034: Unknown result type (might be due to invalid IL or missing references) if (_isDirty) { _struct = GlobalIndexUtil.ToStruct(DimensionIndex, Layer, LocalIndex); _tuple = GlobalIndexUtil.ToIntTuple(DimensionIndex, Layer, LocalIndex); _isDirty = false; } } public override string ToString() { //IL_0018: 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_004a: Unknown result type (might be due to invalid IL or missing references) return $"({DimensionIndex}, {Layer}, {LocalIndex})"; } } public static class GlobalIndexUtil { public static (int dimension, int layer, int zone) ToIntTuple(this LG_Zone zone) { //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_0012: Unknown result type (might be due to invalid IL or missing references) return ToIntTuple(zone.DimensionIndex, zone.Layer.m_type, zone.LocalIndex); } public static (int dimension, int layer, int zone) ToIntTuple(this GlobalZoneIndex globalIndex) { //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_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_000d: Unknown result type (might be due to invalid IL or missing references) return ToIntTuple(globalIndex.Dimension, globalIndex.Layer, globalIndex.Zone); } public static (int dimension, int layer, int zone) ToIntTuple(eDimensionIndex dimension, LG_LayerType layer, eLocalZoneIndex zone) { //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_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected I4, but got Unknown //IL_0008: Expected I4, but got Unknown //IL_0008: Expected I4, but got Unknown return ((int)dimension, (int)layer, (int)zone); } public static GlobalZoneIndex ToStruct(this LG_Zone zone) { //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_0012: 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) return ToStruct(zone.DimensionIndex, zone.Layer.m_type, zone.LocalIndex); } public static GlobalZoneIndex ToStruct(eDimensionIndex dimension, LG_LayerType layer, eLocalZoneIndex zone) { //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_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) return new GlobalZoneIndex(dimension, layer, zone); } public static bool TryGetZone(this (int, int, int) index, [MaybeNullWhen(false)] out LG_Zone zone) { return TryGetZone((eDimensionIndex)index.Item1, (LG_LayerType)(byte)index.Item2, (eLocalZoneIndex)index.Item3, out zone); } public static bool TryGetZone(this GlobalZoneIndex index, [MaybeNullWhen(false)] out LG_Zone zone) { //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_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_000d: Unknown result type (might be due to invalid IL or missing references) return TryGetZone(index.Dimension, index.Layer, index.Zone, out zone); } public static bool TryGetZone(eDimensionIndex dimension, LG_LayerType layer, eLocalZoneIndex localIndex, out LG_Zone? zone) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown //IL_002a: 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_004e: Unknown result type (might be due to invalid IL or missing references) if (!Builder.CurrentFloor.TryGetZoneByLocalIndex(dimension, layer, localIndex, ref zone)) { bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(66, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Unable to find zone in level! (Dimension: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<eDimensionIndex>(dimension); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", Layer: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<LG_LayerType>(layer); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", LocalIndex: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<eLocalZoneIndex>(localIndex); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(")"); } Logger.Error(val); return false; } return true; } } public static class JsonSerializerUtil { public static JsonSerializerOptions CreateDefaultSettings(bool useLocalizedText = true, bool usePartialData = false, bool useInjectLib = false) { JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(useLocalizedText ? JsonSerializer.DefaultSerializerSettingsWithLocalizedText : JsonSerializer.DefaultSerializerSettings); if (usePartialData && PData_Wrapper.IsLoaded) { jsonSerializerOptions.Converters.Add(PData_Wrapper.PersistentIDConverter); } if (useInjectLib && InjectLib_Wrapper.IsLoaded) { jsonSerializerOptions.Converters.Add(InjectLib_Wrapper.InjectLibConverter); } return jsonSerializerOptions; } } public sealed class ZoneNode { public readonly LG_Zone Zone; private readonly Dictionary<ushort, int> _groups = new Dictionary<ushort, int>(); public AreaNode[] Areas { get; private set; } public IReadOnlyCollection<ushort> Groups => _groups.Keys; public ZoneNode(LG_Zone zone) { Zone = zone; } internal void OnNodesCreated() { List<AreaNode> list = new List<AreaNode>(Zone.m_areas.Count); Enumerator<LG_Area> enumerator = Zone.m_areas.GetEnumerator(); while (enumerator.MoveNext()) { LG_Area current = enumerator.Current; list.Add(ZoneGraphUtil.GetAreaNode(current)); } Areas = list.ToArray(); } public bool IsReachable(ushort group) { return _groups.ContainsKey(group); } public bool IsReachable() { return _groups.Count > 0; } internal void Reset() { _groups.Clear(); } internal void OnAreaReachable(ushort newGroup, ushort oldGroup) { if (_groups.TryGetValue(oldGroup, out var value) && --value == 0) { _groups.Remove(oldGroup); } if (newGroup != 0) { value = _groups.GetValueOrDefault(newGroup); _groups[newGroup] = value + 1; } } } public sealed class AreaNode { public class AreaEdge { public readonly LG_Gate Gate; public readonly AreaNode Neighbor; public bool IsOpen { get; private set; } public AreaEdge(LG_Gate gate, AreaNode area) { Gate = gate; Neighbor = area; UpdateOpen(); } internal void UpdateOpen() { IsOpen = Gate.IsTraversable; } } public readonly LG_Area Area; public ushort Group { get; private set; } public ZoneNode Zone { get; private set; } public AreaEdge[] Edges { get; private set; } public AreaNode(LG_Area area) { Area = area; } public bool IsReachable(ushort group) { if (Group == group) { return group > 0; } return false; } public bool IsReachable() { return Group != 0; } internal void OnNodesCreated() { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Invalid comparison between Unknown and I4 Zone = ZoneGraphUtil.GetZoneNode(Area.m_zone); List<AreaEdge> list = new List<AreaEdge>(); Enumerator<LG_Gate> enumerator = Area.m_gates.GetEnumerator(); while (enumerator.MoveNext()) { LG_Gate current = enumerator.Current; LG_Area val = ((LG_ZoneExpander)current).m_linksFrom; if (!((Object)(object)val == (Object)null)) { if (val.UID == Area.UID) { val = ((LG_ZoneExpander)current).m_linksTo; } if (!((Object)(object)val == (Object)null) && (int)((LG_ZoneExpander)current).ExpanderStatus != 2) { list.Add(new AreaEdge(current, ZoneGraphUtil.GetAreaNode(val))); } } } Edges = list.ToArray(); } internal void Reset() { Group = 0; } internal void SetGroup(ushort group) { Zone.OnAreaReachable(group, Group); Group = group; } internal void UpdateEdges() { AreaEdge[] edges = Edges; for (int i = 0; i < edges.Length; i++) { edges[i].UpdateOpen(); } } } [CallConstructorOnLoad] public sealed class ZoneGraphUtil { public const ushort NoGroup = 0; internal static readonly ZoneGraphUtil Current; private readonly Dictionary<int, ZoneNode> _zoneToNode = new Dictionary<int, ZoneNode>(); private readonly Dictionary<int, AreaNode> _areaToNode = new Dictionary<int, AreaNode>(); private readonly Dictionary<int, ushort> _playerToGroup = new Dictionary<int, ushort>(); private ushort _lastGroup; public static bool IsReady { get; private set; } public static event Action? OnReachableUpdate; public static ZoneNode GetZoneNode(Agent agent) { return GetZoneNode(agent.CourseNode.m_zone); } public static ZoneNode GetZoneNode(AIG_CourseNode courseNode) { return GetZoneNode(courseNode.m_zone); } public static ZoneNode GetZoneNode(LG_Zone zone) { return Current._zoneToNode[zone.ID]; } public static AreaNode GetAreaNode(Agent agent) { return GetAreaNode(agent.CourseNode.m_area); } public static AreaNode GetAreaNode(AIG_CourseNode courseNode) { return GetAreaNode(courseNode.m_area); } public static AreaNode GetAreaNode(LG_Area area) { return Current._areaToNode[area.UID]; } public static ushort GetPlayerGroup(PlayerAgent player) { return Current._playerToGroup.GetValueOrDefault(player.PlayerSlotIndex); } public static bool IsReachable(PlayerAgent player, AIG_CourseNode courseNode) { return GetAreaNode(courseNode).IsReachable(GetPlayerGroup(player)); } public static bool IsReachable(PlayerAgent player, LG_Area area) { return GetAreaNode(area).IsReachable(GetPlayerGroup(player)); } public static bool IsReachable(PlayerAgent player, LG_Zone zone) { return GetZoneNode(zone).IsReachable(GetPlayerGroup(player)); } public static bool IsReachable(AIG_CourseNode courseNode) { return GetAreaNode(courseNode).IsReachable(); } public static bool IsReachable(LG_Area area) { return GetAreaNode(area).IsReachable(); } public static bool IsReachable(LG_Zone zone) { return GetZoneNode(zone).IsReachable(); } static ZoneGraphUtil() { IsReady = false; Current = new ZoneGraphUtil(); LevelAPI.OnBuildStart += delegate { LG_Area.s_areaUIDCounter = 0; }; LevelAPI.OnBuildDone += Current.BuildZoneGraph; LevelAPI.OnLevelCleanup += Current.Cleanup; SNetEvents.OnCheckpointReload += Current.RefreshOnCheckpoint; } private void BuildZoneGraph() { Enumerator<LG_Zone> enumerator = Builder.CurrentFloor.allZones.GetEnumerator(); while (enumerator.MoveNext()) { LG_Zone current = enumerator.Current; _zoneToNode.TryAdd(current.ID, new ZoneNode(current)); Enumerator<LG_Area> enumerator2 = current.m_areas.GetEnumerator(); while (enumerator2.MoveNext()) { LG_Area current2 = enumerator2.Current; _areaToNode.TryAdd(current2.UID, new AreaNode(current2)); } } foreach (ZoneNode value in _zoneToNode.Values) { value.OnNodesCreated(); } foreach (AreaNode value2 in _areaToNode.Values) { value2.OnNodesCreated(); } IsReady = true; } private void Cleanup() { _zoneToNode.Clear(); _areaToNode.Clear(); _playerToGroup.Clear(); _lastGroup = 0; IsReady = false; } private void RefreshOnCheckpoint() { foreach (AreaNode value in _areaToNode.Values) { value.UpdateEdges(); } RefreshGraph(); } private void RefreshGraph() { foreach (ZoneNode value in _zoneToNode.Values) { value.Reset(); } foreach (AreaNode value2 in _areaToNode.Values) { value2.Reset(); } _playerToGroup.Clear(); _lastGroup = 0; Enumerator<PlayerAgent> enumerator3 = PlayerManager.PlayerAgentsInLevel.GetEnumerator(); while (enumerator3.MoveNext()) { PlayerAgent current = enumerator3.Current; if (((Agent)current).CourseNode != null) { UpdateOrCreateGroup(current); } } ZoneGraphUtil.OnReachableUpdate?.Invoke(); } private ushort UpdateOrCreateGroup(PlayerAgent player) { int playerSlotIndex = player.PlayerSlotIndex; AreaNode areaNode = GetAreaNode((Agent)(object)player); if (areaNode.IsReachable()) { return _playerToGroup[playerSlotIndex] = areaNode.Group; } _playerToGroup[playerSlotIndex] = ++_lastGroup; PropogateGroup(areaNode, _lastGroup); return _lastGroup; } private void PropogateGroup(AreaNode areaNode, ushort group) { areaNode.SetGroup(group); AreaNode.AreaEdge[] edges = areaNode.Edges; foreach (AreaNode.AreaEdge areaEdge in edges) { if (areaEdge.IsOpen && areaEdge.Neighbor.Group != group) { PropogateGroup(areaEdge.Neighbor, group); } } } internal void Internal_OnPlayerNodeChanged(PlayerAgent player, AIG_CourseNode oldNode) { if (!IsReady) { return; } ushort group = UpdateOrCreateGroup(player); if (oldNode != null) { AreaNode areaNode = GetAreaNode(oldNode.m_area); if (areaNode.IsReachable(group)) { return; } foreach (ushort value in _playerToGroup.Values) { if (areaNode.IsReachable(value)) { ZoneGraphUtil.OnReachableUpdate?.Invoke(); return; } } PropogateGroup(areaNode, 0); } ZoneGraphUtil.OnReachableUpdate?.Invoke(); } internal void Internal_OnDoorStateChanged(LG_Gate gate, bool isOpen) { if (!IsReady) { return; } AreaNode areaNode = GetAreaNode(((LG_ZoneExpander)gate).m_linksFrom); AreaNode areaNode2 = GetAreaNode(((LG_ZoneExpander)gate).m_linksTo); areaNode.UpdateEdges(); areaNode2.UpdateEdges(); int key; ushort value; if (!isOpen) { if (areaNode.IsReachable()) { RefreshGraph(); } } else if (areaNode.IsReachable()) { KeyValuePair<int, ushort>[] array = _playerToGroup.ToArray(); foreach (KeyValuePair<int, ushort> keyValuePair in array) { keyValuePair.Deconstruct(out key, out value); int key2 = key; if (value == areaNode2.Group) { _playerToGroup[key2] = areaNode.Group; } } PropogateGroup(areaNode2, areaNode.Group); ZoneGraphUtil.OnReachableUpdate?.Invoke(); } else { if (!areaNode2.IsReachable()) { return; } KeyValuePair<int, ushort>[] array = _playerToGroup.ToArray(); foreach (KeyValuePair<int, ushort> keyValuePair in array) { keyValuePair.Deconstruct(out key, out value); int key3 = key; if (value == areaNode.Group) { _playerToGroup[key3] = areaNode2.Group; } } PropogateGroup(areaNode, areaNode2.Group); ZoneGraphUtil.OnReachableUpdate?.Invoke(); } } } } namespace AmorLib.Utils.JsonElementConverters { [JsonConverter(typeof(BoolBaseConverter))] public struct BoolBase { public BoolMode Mode; public static readonly BoolBase False = new BoolBase(BoolMode.False); public static readonly BoolBase True = new BoolBase(BoolMode.True); public static readonly BoolBase Unchanged = new BoolBase(BoolMode.Unchanged); public BoolBase(bool mode) { Mode = (mode ? BoolMode.True : BoolMode.False); } public BoolBase(BoolMode mode) { Mode = mode; } public readonly bool GetValue(bool originalValue) { return Mode switch { BoolMode.True => true, BoolMode.False => false, _ => originalValue, }; } } public enum BoolMode { False, True, Unchanged } public sealed class BoolBaseConverter : JsonConverter<BoolBase> { public override bool HandleNull => false; public override bool CanConvert(Type objectType) { return objectType == typeof(BoolBase); } public override BoolBase Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { switch (reader.TokenType) { case JsonTokenType.String: { string text = reader.GetString()?.Trim(); if (string.IsNullOrEmpty(text)) { throw new JsonException("BoolBase string value is null or empty!"); } if (text.EqualsAny(true, "Unchanged", "Ignore", "Keep", "Original", "KeepOriginal")) { return BoolBase.Unchanged; } if (bool.TryParse(text, out var result)) { return new BoolBase(result); } throw new JsonException("Cannot parse BoolBase string: " + text + "! Are you sure it's in right format?"); } case JsonTokenType.True: return BoolBase.True; case JsonTokenType.False: return BoolBase.False; default: throw new JsonException($"BoolBaseJson type: {reader.TokenType} is not implemented!"); } } public override void Write(Utf8JsonWriter writer, BoolBase value, JsonSerializerOptions options) { switch (value.Mode) { case BoolMode.True: writer.WriteBooleanValue(value: true); break; case BoolMode.False: writer.WriteBooleanValue(value: false); break; case BoolMode.Unchanged: writer.WriteStringValue("Unchanged"); break; } writer.WriteCommentValue("BoolBase"); } } [JsonConverter(typeof(LocaleTextConverter))] public struct LocaleText : IEquatable<LocaleText> { public uint ID; public string RawText; public static readonly LocaleText Empty = new LocaleText(string.Empty); private readonly string TextFallback { get { if (ID != 0) { return Text.Get(ID); } return RawText; } } public LocaleText(LocalizedText baseText) { RawText = ((Object)baseText).ToString(); ID = baseText.Id; } public LocaleText(string text) { if (PData_Wrapper.TryGetGUID(text, out var guid)) { RawText = string.Empty; ID = guid; } else { RawText = text; ID = 0u; } } public LocaleText(uint id) { RawText = string.Empty; ID = id; } public readonly LocalizedText ToLocalizedText() { //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_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown return new LocalizedText { Id = ID, UntranslatedText = TextFallback }; } public static explicit operator LocaleText(LocalizedText localizedText) { return new LocaleText(localizedText); } public static explicit operator LocaleText(string text) { return new LocaleText(text); } public static implicit operator LocalizedText(LocaleText localeText) { return localeText.ToLocalizedText(); } public static implicit operator string(LocaleText localeText) { return localeText.ToString(); } public override readonly string ToString() { return TextFallback; } public readonly bool Equals(LocaleText other) { if (ID == other.ID) { return string.Equals(RawText, other.RawText, StringComparison.Ordinal); } return false; } public override readonly bool Equals(object? obj) { if (obj is LocaleText other) { return Equals(other); } return false; } public override readonly int GetHashCode() { return HashCode.Combine(ID, RawText); } public static bool operator ==(LocaleText left, LocaleText right) { return left.Equals(right); } public static bool operator !=(LocaleText left, LocaleText right) { return !(left == right); } } public class LocaleTextConverter : JsonConverter<LocaleText> { public override bool HandleNull => true; public override LocaleText Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.TokenType switch { JsonTokenType.String => new LocaleText(reader.GetString()), JsonTokenType.Number => new LocaleText(reader.GetUInt32()), JsonTokenType.Null => LocaleText.Empty, _ => throw new JsonException($"LocaleTextJson type: {reader.TokenType} is not implemented!"), }; } public override void Write(Utf8JsonWriter writer, LocaleText value, JsonSerializerOptions options) { if (value.ID != 0) { writer.WriteNumberValue(value.ID); } else { writer.WriteStringValue(value.RawText); } } } [JsonConverter(typeof(ValueBaseConverter))] public struct ValueBase { public float Value; public ValueMode Mode; public bool FromDefault; public static readonly ValueBase Unchanged = new ValueBase(1f, ValueMode.Rel, fromDefault: true); public static readonly ValueBase Zero = new ValueBase(0f, ValueMode.Abs, fromDefault: false); public ValueBase(float value = 1f, ValueMode mode = ValueMode.Rel, bool fromDefault = true) { Value = value; Mode = mode; FromDefault = fromDefault; } public readonly float GetAbsValue(float maxValue, float currentValue) { if (Mode == ValueMode.Rel) { if (!FromDefault) { return maxValue * Value; } return currentValue * Value; } return Value; } public readonly float GetAbsValue(float baseValue) { if (Mode != ValueMode.Abs) { return baseValue * Value; } return Value; } public readonly int GetAbsValue(int maxValue, int currentValue) { return (int)Math.Round(GetAbsValue((float)maxValue, (float)currentValue)); } public readonly int GetAbsValue(int baseValue) { return (int)Math.Round(GetAbsValue((float)baseValue)); } public override readonly string ToString() { return $"[Mode: {Mode}, Value: {Value}]"; } } public enum ValueMode { Rel, Abs } public sealed class ValueBaseConverter : JsonConverter<ValueBase> { public override bool HandleNull => false; public override bool CanConvert(Type objectType) { return objectType == typeof(ValueBase); } public override ValueBase Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { switch (reader.TokenType) { case JsonTokenType.Number: return new ValueBase(reader.GetSingle(), ValueMode.Abs); case JsonTokenType.StartObject: { ValueBase result3 = default(ValueBase); while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) { return result3; } if (reader.TokenType != JsonTokenType.PropertyName) { throw new JsonException("Expected PropertyName token"); } string? @string = reader.GetString(); reader.Read(); switch (@string.ToLowerInvariant()) { case "value": result3.Value = reader.GetSingle(); break; case "mode": { if (Enum.TryParse<ValueMode>(reader.GetString(), out var result4)) { result3.Mode = result4; } break; } case "fromdefault": result3.FromDefault = reader.GetBoolean(); break; } } throw new JsonException("Expected EndObject token"); } case JsonTokenType.String: { string text = reader.GetString()?.Trim(); if (string.IsNullOrEmpty(text)) { throw new JsonException("ValueBase string value is null or empty!"); } bool fromDefault = false; if (text.EndsWith("of default", StringComparison.OrdinalIgnoreCase)) { fromDefault = true; string text2 = text; text = text2.Substring(0, text2.Length - 10).TrimEnd(); } if (text.EndsWith("%", StringComparison.InvariantCulture)) { string text2 = text; if (float.TryParse(text2.Substring(0, text2.Length - 1).TrimEnd(), NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var result)) { return new ValueBase(result / 100f, ValueMode.Rel, fromDefault); } } if (text.EqualsAny(true, "Unchanged", "Ignore", "Keep", "Original", "KeepOriginal")) { return new ValueBase(1f, ValueMode.Rel, fromDefault: false); } if (float.TryParse(text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var result2)) { return new ValueBase(result2, ValueMode.Abs); } throw new JsonException("Cannot parse ValueBase string: " + text + "! Are you sure it's in right format?"); } default: throw new JsonException($"ValueBaseJson type: {reader.TokenType} is not implemented!"); } } public override void Write(Utf8JsonWriter writer, ValueBase value, JsonSerializerOptions options) { switch (value.Mode) { case ValueMode.Rel: { if (Mathf.Approximately(value.Value, 1f)) { writer.WriteStringValue("Unchanged"); break; } string format = (value.FromDefault ? "{0}% of default" : "{0}%"); writer.WriteStringValue(string.Format(format, value.Value * 100f)); break; } case ValueMode.Abs: writer.WriteStringValue(value.Value.ToString()); break; } writer.WriteCommentValue("ValueBase"); } } } namespace AmorLib.Utils.Extensions { public static class CollectionExtensions { public static TValue GetOrAddNew<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key) where TValue : new() { if (!dict.TryGetValue(key, out TValue value)) { value = (dict[key] = new TValue()); } return value; } public static void ForEachValue<TKey, TValue>(this IDictionary<TKey, TValue> dict, Action<TValue> action) where TKey : notnull { foreach (TValue value in dict.Values) { action(value); } } public static bool AnyAllValues<TKey, TCollection, TValue>(this IDictionary<TKey, TCollection> dict, Func<TValue, bool> predicate) where TCollection : IEnumerable<TValue> { foreach (TCollection value in dict.Values) { if (value.Any(predicate)) { return true; } } return false; } public static TValue? FirstOrDefaultAllValues<TKey, TCollection, TValue>(this IDictionary<TKey, TCollection> dict, Func<TValue, bool> predicate) where TCollection : IEnumerable<TValue> { foreach (TCollection value in dict.Values) { foreach (TValue item in value) { if (predicate(item)) { return item; } } } return default(TValue); } } public static class GameObjectPlusExtensions { public static bool TryAndGetComponent<T>(this GameObject go, out T component) { component = go.GetComponent<T>(); return component != null; } public static T AddOrGetComponent<T>(this GameObject go) where T : Component { if (!go.TryAndGetComponent<T>(out var component)) { return go.AddComponent<T>(); } return component; } public static string GetFullPath(this GameObject go) { StringBuilder stringBuilder = new StringBuilder(((Object)go).name); Transform parent = go.transform.parent; while ((Object)(object)parent != (Object)null) { stringBuilder.Insert(0, ((Object)parent).name + "/"); parent = parent.parent; } return stringBuilder.ToString(); } public static GameObject ClonePrefabSpawners(this GameObject original, Vector3 position, Quaternion rotation, Transform parent) { //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_002e: 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_009e: Unknown result type (might be due to invalid IL or missing references) GameObject val = Object.Instantiate<GameObject>(original, position, rotation, parent); foreach (LG_PrefabSpawner componentsInChild in val.GetComponentsInChildren<LG_PrefabSpawner>()) { try { GameObject val2 = Object.Instantiate<GameObject>(componentsInChild.m_prefab, ((Component)componentsInChild).transform.position, ((Component)componentsInChild).transform.rotation, ((Component)componentsInChild).transform.parent); if (componentsInChild.m_disableCollision) { foreach (Collider componentsInChild2 in val2.GetComponentsInChildren<Collider>()) { componentsInChild2.enabled = false; } } if (componentsInChild.m_applyScale) { val2.transform.localScale = ((Component)componentsInChild).transform.localScale; } val2.transform.SetParent(((Component)componentsInChild).transform); } catch { } } return val; } public static uint PostWithCleanup(this CellSoundPlayer soundPlayer, uint eventID, Vector3 pos, uint in_uFlags = 1u) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) return soundPlayer.Post(eventID, pos, in_uFlags, EventCallback.op_Implicit((Action<Object, AkCallbackType, AkCallbackInfo>)SoundDoneCallback), (Object)(object)soundPlayer); } private static void SoundDoneCallback(Object in_pCookie, AkCallbackType in_type, AkCallbackInfo callbackInfo) { CellSoundPlayer obj = ((Il2CppObjectBase)in_pCookie).Cast<CellSoundPlayer>(); if (obj != null) { obj.Recycle(); } } public static bool IsWithinSqrDistance(this Vector3 a, Vector3 b, float sqrThreshold, out float sqrDistance) { //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_0003: 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) Vector3 val = a - b; sqrDistance = ((Vector3)(ref val)).sqrMagnitude; return sqrDistance <= sqrThreshold; } public static bool IsWithinSqrDistance(this Vector3 a, Vector3 b, float sqrThreshold) { //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_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) Vector3 val = a - b; return ((Vector3)(ref val)).sqrMagnitude <= sqrThreshold; } } public static class PluginInfoExtensions { public static bool VersionEquals(this PluginInfo pluginInfo, string verison) { int? num = CompareVersion(pluginInfo, verison); if (num.HasValue) { return num.GetValueOrDefault() == 0; } return false; } public static bool VersionExceeds(this PluginInfo pluginInfo, string version) { int? num = CompareVersion(pluginInfo, version); if (num.HasValue) { return num.GetValueOrDefault() > 0; } return false; } public static bool VersionAtLeast(this PluginInfo pluginInfo, string version) { int? num = CompareVersion(pluginInfo, version); if (num.HasValue) { return num.GetValueOrDefault() >= 0; } return false; } private static int? CompareVersion(PluginInfo pluginInfo, string verison) { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Expected O, but got Unknown object obj; if (pluginInfo == null) { obj = null; } else { BepInPlugin metadata = pluginInfo.Metadata; obj = ((metadata != null) ? metadata.Version : null); } if ((Version)obj == (Version)null) { Logger.Error("PluginInfo or Version.Metadata is null"); return null; } Match match = Regex.Match(verison, "^(\\d+)\\.(\\d+)\\.(\\d+)$"); if (!match.Success) { Logger.Error("Version string format is incorrect"); return null; } int num = int.Parse(match.Groups[1].Value); int num2 = int.Parse(match.Groups[2].Value); int num3 = int.Parse(match.Groups[3].Value); return pluginInfo.Metadata.Version.CompareTo(new Version(num, num2, num3, (string)null, (string)null)); } } public static class StringExtensions { public static bool EqualsAny(this string input, bool ignoreCase = false, params string[] args) { StringComparison comparisonType = (ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); foreach (string value in args) { if (input.Equals(value, comparisonType)) { return true; } } return false; } public static LocaleText ToLocaleText(this string input) { return new LocaleText(input); } } } namespace AmorLib.Patches.SNet { [HarmonyPatch(typeof(CheckpointManager), "OnStateChange")] internal static class Patch_CheckpointState { [HarmonyPostfix] [HarmonyWrapSafe] public static void Post_CheckpointStateChange(pCheckpointState newState) { //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)newState.lastInteraction == 2) { SNetEvents.CheckpointReloaded(); } } } [HarmonyPatch(typeof(SNet_SyncManager), "OnRecallDone")] internal static class Patch_OnRecallDone { [HarmonyPostfix] [HarmonyWrapSafe] private static void Post_RecallDone() { SNetEvents.RecallDone(); } } [HarmonyPatch(typeof(SNet_Capture))] internal static class Patch_SNet_Capture { [HarmonyPatch("TriggerCapture")] [HarmonyPrefix] [HarmonyWrapSafe] private static void Pre_TriggerCapture(SNet_Capture __instance) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) SNetEvents.BufferCaptured(__instance.PrimedBufferType); } [HarmonyPatch("RecallBuffer")] [HarmonyPostfix] [HarmonyWrapSafe] private static void Post_RecallBuffer(SNet_Capture __instance, eBufferType bufferType) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (!__instance.IsRecalling) { SNetEvents.BufferRecalled(bufferType); } } } } namespace AmorLib.Patches.LevelGen { [HarmonyPatch] internal static class BuilderPatches { [HarmonyPatch(typeof(Builder), "BuildDone")] [HarmonyPostfix] [HarmonyBefore(new string[] { "dev.gtfomodding.gtfo-api" })] [HarmonyWrapSafe] private static void BuildDone_Early() { LevelEvents.BuildDoneEarly(); } [HarmonyPatch(typeof(Builder), "BuildDone")] [HarmonyPostfix] [HarmonyAfter(new string[] { "dev.gtfomodding.gtfo-api" })] [HarmonyWrapSafe] private static void BuildDone_Late() { LevelEvents.BuildDoneLate(); } } [HarmonyPatch(typeof(LG_BuildZoneLightsJob), "Build")] internal static class LightsPatches { [HarmonyPrefix] [HarmonyPriority(600)] [HarmonyWrapSafe] private static void Pre_ZoneBuild(LG_BuildZoneLightsJob __instance, out List<LightWorker>? __state) { LG_Zone zone = __instance.m_zone; if ((Object)(object)zone == (Object)null) { __state = null; return; } __state = new List<LightWorker>(); Enumerator<AIG_CourseNode> enumerator = zone.m_courseNodes.GetEnumerator(); while (enumerator.MoveNext()) { AIG_CourseNode current = enumerator.Current; foreach (LG_Light componentsInChild in ((Component)current.m_area).GetComponentsInChildren<LG_Light>(false)) { __state.Add(new LightWorker(zone, current, componentsInChild, ((Object)componentsInChild).GetInstanceID(), componentsInChild.m_intensity)); } } } [HarmonyPostfix] [HarmonyPriority(600)] [HarmonyWrapSafe] private static void Post_ZoneBuild(LG_BuildZoneLightsJob __instance, bool __result, List<LightWorker>? __state) { if ((Object)(object)__instance.m_zone == (Object)null || !__result || __state == null) { return; } foreach (LightWorker item in __state) { item.Setup(); LightAPI.AllLightsMap.TryAdd(item.InstanceID, item); } } } [HarmonyPatch] internal static class TerminalPatches { private static readonly Dictionary<(int, int, int), LG_ComputerTerminal> ReactorTerminals; static TerminalPatches() { ReactorTerminals = new Dictionary<(int, int, int), LG_ComputerTerminal>(); LevelAPI.OnAfterBuildBatch += OnAfterBatchBuild; LevelAPI.OnLevelCleanup += OnLevelCleanup; } private static void OnAfterBatchBuild(BatchName batch) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 if ((int)batch != 47) { return; } foreach (KeyValuePair<(int, int, int), LG_ComputerTerminal> kvp in ReactorTerminals) { if (kvp.Key.TryGetZone(out LG_Zone zone) && !ListExtensions.ToManaged<LG_ComputerTerminal>(zone.TerminalsSpawnedInZone).Any((LG_ComputerTerminal term) => term.SyncID == kvp.Value.SyncID)) { zone.TerminalsSpawnedInZone.Add(kvp.Value); Logger.Debug("Appended reactor terminal to its TerminalsSpawnedInZone"); } } } private static void OnLevelCleanup() { ReactorTerminals.Clear(); } [HarmonyPatch(typeof(LG_ComputerTerminalCommandInterpreter), "ReceiveCommand")] [HarmonyPrefix] [HarmonyPriority(700)] [HarmonyWrapSafe] private static void HiddenCommandExecutionFix(LG_ComputerTerminalCommandInterpreter __instance, ref TERM_Command cmd) { //IL_0010: 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_0013: Invalid comparison between Unknown and I4 //IL_0020: 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_0025: Invalid comparison between Unknown and I4 //IL_0015: 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_002a: Invalid comparison between Unknown and I4 //IL_0018: 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_001c: Invalid comparison between Unknown and I4 //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Invalid comparison between Unknown and I4 if (((Il2CppArrayBase<TERM_Command>)(object)LG_ComputerTerminalCommandInterpreter.m_alwaysHiddenCommands).Contains(cmd)) { return; } TERM_Command val = cmd; if ((int)val <= 6) { if ((int)val == 0 || val - 5 <= 1) { return; } } else if (val - 10 <= 1 || (int)val == 16 || (int)val == 31) { return; } if (__instance.m_terminal.CommandIsHidden(cmd)) { cmd = (TERM_Command)10; } } [HarmonyPatch(typeof(LG_WardenObjective_Reactor), "Start")] [HarmonyPostfix] [HarmonyWrapSafe] private static void CustomReactorTerminalFix(LG_WardenObjective_Reactor __instance) { if (!((BaseChainloader<BasePlugin>)(object)IL2CPPChainloader.Instance).Plugins.ContainsKey("FlowGeos") && !((Object)(object)__instance.m_terminalPrefab != (Object)null)) { Logger.Info("Resolving terminal prefab for reactor"); GameObject loadedAsset = AssetAPI.GetLoadedAsset<GameObject>("Assets/AssetPrefabs/Complex/Generic/FunctionMarkers/Terminal_Floor.prefab"); if ((Object)(object)loadedAsset == (Object)null) { Logger.Error("Failed to find terminal prefab loaded asset?"); } else { __instance.m_terminalPrefab = loadedAsset; } } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] private static bool ReactorTerminalSpawnNodeFix(LG_ComputerTerminal __instance, ref AIG_CourseNode __result) { if ((Object)(object)__instance.ConnectedReactor != (Object)null && __instance.m_terminalItem.SpawnNode == null) { __result = __instance.ConnectedReactor.SpawnNode; return false; } return true; } [HarmonyPatch(typeof(LG_WardenObjective_Reactor), "GenericObjectiveSetup")] [HarmonyPostfix] [HarmonyWrapSafe] private static void ReactorTerminalInZoneFix(LG_WardenObjective_Reactor __instance) { if (__instance.SpawnNode.m_zone.TerminalsSpawnedInZone != null && (Object)(object)__instance.m_terminal != (Object)null) { ReactorTerminals.Add(__instance.SpawnNode.m_zone.ToIntTuple(), __instance.m_terminal); } } } [HarmonyPatch] internal static class ZoneGraphPatches { [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] private static void Pre_PlayerNodeSet(PlayerAgent __instance, ref AIG_CourseNode __state) { __state = ((Agent)__instance).CourseNode; } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] private static void Post_PlayerNodeSet(PlayerAgent __instance, AIG_CourseNode __state) { AIG_CourseNode courseNode = ((Agent)__instance).CourseNode; if (courseNode != null && __state != courseNode) { ZoneGraphUtil.Current.Internal_OnPlayerNodeChanged(__instance, __state); } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] private static void Pre_GateIsTraversable(LG_Gate __instance, ref bool __state) { __state = __instance.IsTraversable; } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] private static void Post_GateIsTraversable(LG_Gate __instance, bool __state) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 if (__state != __instance.IsTraversable && (int)((LG_ZoneExpander)__instance).ExpanderStatus == 1) { ZoneGraphUtil.Current.Internal_OnDoorStateChanged(__instance, !__state); } } } } namespace AmorLib.Networking { public struct LowResColor { public byte r; public byte g; public byte b; public byte a; private static Color _color = Color.black; public static implicit operator Color(LowResColor lowResColor) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) _color.r = (float)(int)lowResColor.r / 255f; _color.g = (float)(int)lowResColor.g / 255f; _color.b = (float)(int)lowResColor.b / 255f; _color.a = (float)(int)lowResColor.a / 255f; return _color; } public static implicit operator LowResColor(Color color) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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) LowResColor result = default(LowResColor); result.r = (byte)(color.r * 255f); result.g = (byte)(color.g * 255f); result.b = (byte)(color.b * 255f); result.a = (byte)(color.a * 255f); return result; } } public static class StateReplicatorManager { internal static readonly Dictionary<Type, HashSet<uint>> _reservedReplicators = new Dictionary<Type, HashSet<uint>>(); public static void ReserveIDs<T>(params uint[] args) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown HashSet<uint> orAddNew = _reservedReplicators.GetOrAddNew(typeof(T)); bool flag = default(bool); foreach (uint num in args) { if (orAddNew.Contains(num)) { BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(41, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("StateReplicator "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Type>(typeof(T)); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" already has reserved ID "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<uint>(num); } Logger.Error(val); } else { orAddNew.Add(num); } } } public static void DeregisterIds<T>(params uint[] args) { if (_reservedReplicators.TryGetValue(typeof(T), out HashSet<uint> value)) { foreach (uint item in args) { value.Remove(item); } } } } public abstract class SyncedEvent<S> where S : struct { public delegate void ReceiveHandler(S packet); public virtual string Prefix => string.Empty; public abstract string GUID { get; } public bool IsSetup { get; private set; } public string EventName { get; private set; } = string.Empty; public event ReceiveHandler? OnReceive; public event ReceiveHandler? OnReceiveLocal; public void Setup() { if (!IsSetup) { EventName = (Utility.IsNullOrWhiteSpace(Prefix) ? ("SE-" + GUID) : ("SE-" + Prefix + "-" + GUID)); NetworkAPI.RegisterEvent<S>(EventName, (Action<ulong, S>)ReceiveClient_Callback); IsSetup = true; } } public void Send(S packetData, SNet_Player? target = null, SNet_ChannelType priority = 4) { //IL_0020: 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) if ((Object)(object)target != (Object)null) { NetworkAPI.InvokeEvent<S>(EventName, packetData, target, priority); } else { NetworkAPI.InvokeEvent<S>(EventName, packetData, priority); } ReceiveLocal_Callback(packetData); } private void ReceiveLocal_Callback(S packet) { ReceiveLocal(packet); this.OnReceiveLocal?.Invoke(packet); } private void ReceiveClient_Callback(ulong sender, S packet) { Receive(packet); this.OnReceive?.Invoke(packet); } protected virtual void ReceiveLocal(S packet) { } protected virtual void Receive(S packet) { } } public abstract class SyncedEventMasterOnly<S> : SyncedEvent<S> where S : struct { public void Send(S packet, SNet_ChannelType priority = 4) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!SNet.IsMaster) { Send(packet, SNet.Master, priority); } else { Receive(packet); } } } } namespace AmorLib.Networking.StateReplicators { public interface IStateReplicatorHolder<S> where S : struct { StateReplicator<S>? Replicator { get; } void OnStateChange(S oldState, S state, bool isRecall); } public struct Packet { public uint replicatorID; public PacketAction action; } public enum PacketAction : byte { Created, Destroyed, SyncRequest } internal sealed class ReplicatorHandshake { public delegate void ClientRequestedSyncDel(SNet_Player requestedPlayer); private readonly Dictionary<uint, (bool SetupOnHost, bool SetupOnClient)> _lookup = new Dictionary<uint, (bool, bool)>(); public string EventName { get; private set; } public bool IsReadyToSync { get; private set; } public event ClientRequestedSyncDel? OnClientSyncRequested; public ReplicatorHandshake(string eventName) { EventName = eventName; NetworkAPI.RegisterEvent<Packet>(EventName, (Action<ulong, Packet>)OnSyncAction); SNetEvents.OnRecallDone += OnRecallDone; } private void OnRecallDone() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(42, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Handshake :: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(EventName); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", Client sending sync request"); } Logger.Warn(val); ClientSyncRequest(); } private void ClientSyncRequest() { if (SNet.IsMaster) { return; } foreach (uint key in _lookup.Keys) { NetworkAPI.InvokeEvent<Packet>(EventName, new Packet { replicatorID = key, action = PacketAction.SyncRequest }, SNet.Master, (SNet_ChannelType)2); } } public void Reset() { _lookup.Clear(); } private void OnSyncAction(ulong sender, Packet packet) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown if (!SNet.IsMaster && sender == SNet.Master.Lookup) { if (packet.action != PacketAction.SyncRequest) { SetHostState(packet.replicatorID, packet.action == PacketAction.Created); } else { Logger.Warn("Handshake :: OnSyncAction host sync request has no implementation"); } } else { if (!SNet.IsMaster) { return; } bool flag = default(bool); switch (packet.action) { case PacketAction.Created: SetClientState(packet.replicatorID, isSetup: true); break; case PacketAction.Destroyed: SetClientState(packet.replicatorID, isSetup: false); break; case PacketAction.SyncRequest: { SNet_Player requestedPlayer = default(SNet_Player); if (!SNet.TryGetPlayer(sender, ref requestedPlayer)) { BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(45, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Handshake :: Cannot find player from sender: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<ulong>(sender); } Logger.Error(val); } else { this.OnClientSyncRequested?.Invoke(requestedPlayer); } break; } default: { BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(35, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Handshake :: Unknown packet action "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<PacketAction>(packet.action); } Logger.Error(val); break; } } } } public void UpdateCreated(uint id) { if (SNet.IsInLobby) { if (SNet.IsMaster) { SetHostState(id, isSetup: true); NetworkAPI.InvokeEvent<Packet>(EventName, new Packet { replicatorID = id, action = PacketAction.Created }, (SNet_ChannelType)2); } else if (SNet.HasMaster) { SetClientState(id, isSetup: true); NetworkAPI.InvokeEvent<Packet>(EventName, new Packet { replicatorID = id, action = PacketAction.Created }, SNet.Master, (SNet_ChannelType)2); } else { Logger.Error("Handshake :: MASTER is NULL in lobby; this should NOT happen!!"); } } else { Logger.Error("Handshake :: Session LifeTimeType StateReplicator cannot be created without lobby!"); } } public void UpdateDestroyed(uint id) { if (SNet.IsInLobby) { if (SNet.IsMaster) { SetHostState(id, isSetup: true); NetworkAPI.InvokeEvent<Packet>(EventName, new Packet { replicatorID = id, action = PacketAction.Destroyed }, (SNet_ChannelType)2); } else if (SNet.HasMaster) { SetClientState(id, isSetup: true); NetworkAPI.InvokeEvent<Packet>(EventName, new Packet { replicatorID = id, action = PacketAction.Destroyed }, SNet.Master, (SNet_ChannelType)2); } else { Logger.Error("Handshake :: MASTER is NULL in lobby; this should NOT happen!!"); } } else { Logger.Error("Handshake :: Session LifeTimeType StateReplicator cannot be created without lobby!"); } } private void SetHostState(uint id, bool isSetup) { if (_lookup.TryGetValue(id, out (bool, bool) value)) { value.Item1 = isSetup; } else { _lookup[id] = new(bool, bool) { Item1 = isSetup }; } UpdateSyncState(id); } private void SetClientState(uint id, bool isSetup) { if (_lookup.TryGetValue(id, out (bool, bool) value)) { value.Item2 = isSetup; } else { _lookup[id] = new(bool, bool) { Item2 = isSetup }; } UpdateSyncState(id); } private void UpdateSyncState(uint id) { bool isReadyToSync = IsReadyToSync; IsReadyToSync = _lookup.TryGetValue(id, out (bool, bool) value) && value.Item1 && value.Item2; if (IsReadyToSync && IsReadyToSync != isReadyToSync && SNet.HasMaster && !SNet.IsMaster) { NetworkAPI.InvokeEvent<Packet>(EventName, new Packet { replicatorID = id, action = PacketAction.SyncRequest }, SNet.Master, (SNet_ChannelType)2); } } } public enum Size { State4Byte = 4, State8Byte = 8, State16Byte = 16, State32Byte = 32, State48Byte = 48, State64Byte = 64, State80Byte = 80, State96Byte = 96, State128Byte = 128, State196Byte = 196, State256Byte = 256 } internal delegate void OnReceiveDel<S>(ulong sender, uint replicatorID, S newState) where S : struct; internal interface IReplicatorEvent<S> { string EventName { get; } bool IsRegistered { get; } void Invoke(uint replicatorID, S data, SNet_Player? target = null, SNet_ChannelType priority = 2); } internal interface IStatePayload { uint ID { get; set; } S Get<S>(Size size) where S : struct; void Set<S>(S stateData, Size size) where S : struct; } internal class ReplicatorPayload<S, P> : IReplicatorEvent<S> where S : struct where P : struct, IStatePayload { private readonly Size _payloadSize; public string EventName { get; private set; } = string.Empty; public bool IsRegistered => NetworkAPI.IsEventRegistered(EventName); public ReplicatorPayload(Size size, string eventName, OnReceiveDel<S> onReceiveCallback) { OnReceiveDel<S> onReceiveCallback2 = onReceiveCallback; base..ctor(); ReplicatorPayload<S, P> replicatorPayload = this; NetworkAPI.RegisterEvent<P>(eventName, (Action<ulong, P>)delegate(ulong sender, P payload) { onReceiveCallback2?.Invoke(sender, payload.ID, payload.Get<S>(replicatorPayload._payloadSize)); }); EventName = eventName; _payloadSize = size; } public void Invoke(uint replicatorID, S data, SNet_Player? target, SNet_ChannelType priority) { //IL_004a: 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) P val = new P { ID = replicatorID }; val.Set(data, _payloadSize); if ((Object)(object)target != (Object)null) { NetworkAPI.InvokeEvent<P>(EventName, val, target, priority); } else { NetworkAPI.InvokeEvent<P>(EventName, val, priority); } } } internal struct StatePayloadBytes : IStatePayload { public unsafe fixed byte PayloadBytes[256]; public uint ID { get; set; } public unsafe S Get<S>(Size size) where S : struct { byte[] array = new byte[(int)size]; fixed (byte* ptr = PayloadBytes) { Marshal.Copy((IntPtr)ptr, array, 0, (int)size); } return FromBytes<S>(array); } public unsafe void Set<S>(S stateData, Size size) where S : struct { byte[] array = ToBytes(stateData); if (array.Length > (int)size) { throw new ArgumentException($"Data size {array.Length} exceeds payload size {size}, unable to serialize {"S"}!"); } fixed (byte* ptr = PayloadBytes) { Marshal.Copy(array, 0, (IntPtr)ptr, array.Length); for (int i = array.Length; i < (int)size; i++) { ptr[i] = 0; } } } private static byte[] ToBytes<S>(S data) where S : struct { int num = Marshal.SizeOf<S>(); byte[] array = new byte[num]; IntPtr intPtr = Marshal.AllocHGlobal(num); try { Marshal.StructureToPtr(data, intPtr, fDeleteOld: false); Marshal.Copy(intPtr, array, 0, num); return array; } finally { Marshal.FreeHGlobal(intPtr); } } private static S FromBytes<S>(byte[] arr) where S : struct { IntPtr intPtr = Marshal.AllocHGlobal(arr.Length); try { Marshal.Copy(arr, 0, intPtr, arr.Length); return Marshal.PtrToStructure<S>(intPtr); } finally { Marshal.FreeHGlobal(intPtr); } } } public enum LifeTimeType { Permanent, Session } public sealed class StateReplicator<S> where S : struct { internal readonly Dictionary<eBufferType, S> _recallStateSnapshots = new Dictionary<eBufferType, S>(); public static readonly string Name; public static readonly string HashName; public static readonly string ClientRequestEventName; public static readonly string HostSetStateEventName; public static readonly string HostSetRecallStateEventName; public static readonly string HandshakeEventName; public static readonly int StateSize; public static readonly Size StateSizeType; private static readonly IReplicatorEvent<S>? _clientRequestEvent; private static readonly IReplicatorEvent<S>? _hostSetStateEvent; private static readonly IReplicatorEvent<S>? _hostSetRecallStateEvent; private static readonly ReplicatorHandshake? _handshake; internal static readonly Dictionary<uint, StateReplicator<S>> _replicators; public bool IsValid => ID != 0; public bool IsInvalid => ID == 0; public uint ID { get; private set; } public LifeTimeType LifeTime { get; private set; } public IStateReplicatorHolder<S>? Holder { get; private set; } public S State { get; private set; } public S LastState { get; private set; } public bool ClientSendStateAllowed { get; set; } = true; public bool CanSendToClient { get { if (SNet.IsInLobby) { return SNet.IsMaster; } return false; } } public bool CanSendToHost { get { if (SNet.IsInLobby && !SNet.IsMaster && SNet.HasMaster) { return ClientSendStateAllowed; } return false; } } public event Action<S, S, bool>? OnStateChanged; public void SetState(S state) { if (!IsInvalid) { DoSync(state); } } public void SetStateUnsynced(S state) { if (!IsInvalid) { LastState = state; State = state; } } public void Unload() { if (IsValid) { _replicators.Remove(ID); _recallStateSnapshots.Clear(); _handshake?.UpdateDestroyed(ID); ID = 0u; } } private void DoSync(S newState) { if (IsValid) { if (CanSendToClient) { _hostSetStateEvent?.Invoke(ID, newState, null, (SNet_ChannelType)2); Internal_ChangeState(newState, isRecall: false); } else if (CanSendToHost) { _clientRequestEvent?.Invoke(ID, newState, SNet.Master, (SNet_ChannelType)2); } } } private void Internal_ChangeState(S state, bool isRecall) { if (!IsInvalid) { S state2 = State; State = state; LastState = state2; this.OnStateChanged?.Invoke(state2, state, isRecall); Holder?.OnStateChange(state2, state, isRecall); } } private void SendDropInState(SNet_Player target) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown if (IsInvalid || (Object)(object)target == (Object)null) { bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(35, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("IsInvalid: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(IsInvalid); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("SendDropInState"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" :: Target was null? "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>((Object)(object)target == (Object)null); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("?"); } Logger.Error(val); } else { _hostSetRecallStateEvent?.Invoke(ID, State, target, (SNet_ChannelType)2); } } public void ClearAllRecallSnapshot() { if (!IsInvalid) { _recallStateSnapshots.Clear(); } } private void SaveSnapshot(eBufferType type) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (!IsInvalid) { _recallStateSnapshots[type] = State; } } private void RestoreSnapshot(eBufferType type) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_0067: Unknown result type (might be due to invalid IL or missing references) if (!IsValid || !CanSendToClient) { return; } if (_recallStateSnapshots.TryGetValue(type, out var value)) { _hostSetRecallStateEvent?.Invoke(ID, value, null, (SNet_ChannelType)2); Internal_ChangeState(value, isRecall: true); return; } bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(31, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("RestoreSnapshot"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" :: There was no snapshot for "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<eBufferType>(type); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("?"); } Logger.Error(val); } static StateReplicator() { _replicators = new Dictionary<uint, StateReplicator<S>>(); Name = typeof(S).Name; StateSize = Marshal.SizeOf(typeof(S)); StateSizeType = GetSizeType(StateSize); using MD5 mD = MD5.Create(); HashName = Convert.ToBase64String(mD.ComputeHash(Encoding.UTF8.GetBytes(typeof(S).FullName))); ClientRequestEventName = "SRcr-" + Name + "-" + HashName; HostSetStateEventName = "SRhs-" + Name + "-" + HashName; HostSetRecallStateEventName = "SRre-" + Name + "-" + HashName; HandshakeEventName = "RH-" + Name + "-" + HashName; _clientRequestEvent = CreatePayloadEvent(ClientRequestEventName, ClientRequestEventCallback); _hostSetStateEvent = CreatePayloadEvent(HostSetStateEventName, HostSetStateEventCallback); _hostSetRecallStateEvent = CreatePayloadEvent(HostSetRecallStateEventName, HostSetRecallStateEventCallback); _handshake = CreateHandshakeEvent(); SNetEvents.OnBufferCapture += BufferStored; SNetEvents.OnBufferRecall += BufferRecalled; LevelAPI.OnLevelCleanup += OnLevelCleanup; } public static StateReplicator<S>? Create(uint replicatorID, S startState, LifeTimeType lifeTime, IStateReplicatorHolder<S>? holder = null) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown if (replicatorID == 0) { Logger.Error("Replicator ID 0 is reserved for empty!"); return null; } if (_replicators.ContainsKey(replicatorID)) { Logger.Error("Replicator ID has already assigned!"); return null; } StateReplicator<S> stateReplicator = new StateReplicator<S> { ID = replicatorID, LifeTime = lifeTime, Holder = holder, State = startState }; switch (lifeTime) { case LifeTimeType.Permanent: Logger.Debug("LifeTime is Permanent :: Handshaking is disabled"); break; case LifeTimeType.Session: _handshake?.UpdateCreated(replicatorID); break; default: { bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(21, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("LifeTime "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<LifeTimeType>(lifeTime); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" is invalid!"); } Logger.Error(val); return null; } } _replicators[replicatorID] = stateReplicator; return stateReplicator; } public static Size GetSizeType(int stateSize) { Size size = Size.State8Byte; foreach (Size value in Enum.GetValues(typeof(Size))) { if (stateSize <= (int)value && size < value) { size = value; break; } } return size; } private static IReplicatorEvent<S>? CreatePayloadEvent(string name, OnReceiveDel<S> callback) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown if (NetworkAPI.IsEventRegistered(name)) { bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(77, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("ReplicatorPayload<"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(">."); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("callback"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" failed to initialize: Event name "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" is already registered!"); } Logger.Error(val); return null; } return new ReplicatorPayload<S, StatePayloadBytes>(StateSizeType, name, callback); } private static ReplicatorHandshake? CreateHandshakeEvent() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown if (NetworkAPI.IsEventRegistered(HandshakeEventName)) { bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(78, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("ReplicatorHandshake<"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("> failed to initialize: Event name "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(HandshakeEventName); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" is already registered!"); } Logger.Error(val); return null; } ReplicatorHandshake replicatorHandshake = new ReplicatorHandshake(HandshakeEventName); replicatorHandshake.OnClientSyncRequested += ClientSyncRequested; return replicatorHandshake; } private static void ClientRequestEventCallback(ulong sender, uint replicatorID, S newState) { if (SNet.IsMaster && _replicators.TryGetValue(replicatorID, out StateReplicator<S> value)) { value.SetState(newState); } } private static void HostSetStateEventCallback(ulong sender, uint replicatorID, S newState) { if (SNet.HasMaster && SNet.Master.Lookup == sender && _replicators.TryGetValue(replicatorID, out StateReplicator<S> value)) { value.Internal_ChangeState(newState, isRecall: false); } } private static void HostSetRecallStateEventCallback(ulong sender, uint replicatorID, S newState) { if (SNet.HasMaster && SNet.Master.Lookup == sender && _replicators.TryGetValue(replicatorID, out StateReplicator<S> value)) { value.Internal_ChangeState(newState, isRecall: true); } } private static void ClientSyncRequested(SNet_Player requestedPlayer) { foreach (StateReplicator<S> value in _replicators.Values) { if (value.IsValid) { value.SendDropInState(requestedPlayer); } } } private static void BufferStored(eBufferType type) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) foreach (StateReplicator<S> value in _replicators.Values) { if (value.IsValid) { value.SaveSnapshot(type); } } } private static void BufferRecalled(eBufferType type) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) foreach (StateReplicator<S> value in _replicators.Values) { if (value.IsValid) { value.RestoreSnapshot(type); } } } private static void OnLevelCleanup() { UnloadSessionReplicators(); } public static void UnloadSessionReplicators() { List<uint> list = new List<uint>(); foreach (StateReplicator<S> value in _replicators.Values) { if (value.LifeTime == LifeTimeType.Session) { list.Add(value.ID); value.Unload(); } } foreach (uint item in list) { _replicators.Remove(item); } _handshake?.Reset(); } } } namespace AmorLib.Events { public static class LevelEvents { public static event Action? OnBuildDoneEarly; public static event Action? OnBuildDoneLate; internal static void BuildDoneEarly() { SafeInvoke.Invoke(LevelEvents.OnBuildDoneEarly); } internal static void BuildDoneLate() { SafeInvoke.Invoke(LevelEvents.OnBuildDoneLate); } } public static class SNetEvents { public static event Action<eBufferType>? OnBufferCapture; public static event Action<eBufferType>? OnBufferRecall; public static event Action? OnCheckpointReload; public static event Action? OnRecallDone; internal static void BufferCaptured(eBufferType bufferType) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) SafeInvoke.Invoke<eBufferType>(SNetEvents.OnBufferCapture, bufferType); } internal static void BufferRecalled(eBufferType bufferType) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) SafeInvoke.Invoke<eBufferType>(SNetEvents.OnBufferRecall, bufferType); } internal static void CheckpointReloaded() { SafeInvoke.Invoke(SNetEvents.OnCheckpointReload); } internal static void RecallDone() { SafeInvoke.Invoke(SNetEvents.OnRecallDone); } } } namespace AmorLib.Dependencies { [CallConstructorOnLoad] public static class InjectLib_Wrapper { public const string PLUGIN_GUID = "GTFO.InjectLib"; public static bool IsLoaded { get; private set; } public static JsonConverter? InjectLibConverter { get; private set; } static InjectLib_Wrapper() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown if (((BaseChainloader<BasePlugin>)(object)IL2CPPChainloader.Instance).Plugins.ContainsKey("GTFO.InjectLib")) { IsLoaded = true; SetConverter(); } bool flag = default(bool); BepInExDebugLogInterpolatedStringHandler val = new BepInExDebugLogInterpolatedStringHandler(21, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("InjectLib is loaded: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(IsLoaded); } Logger.Debug(val); } [MethodImpl(MethodImplOptions.NoInlining)] private static void SetConverter() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected O, but got Unknown InjectLibConverter = (JsonConverter?)new InjectLibConnector(); } } [CallConstructorOnLoad] public static class PData_Wrapper { public const string PLUGIN_GUID = "MTFO.Extension.PartialBlocks"; public static bool IsLoaded { get; private set; } public static bool IsMainBranch { get; private set; } public static JsonConverter? PersistentIDConverter { get; private set; } static PData_Wrapper() { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown bool flag = default(bool); if (((BaseChainloader<BasePlugin>)(object)IL2CPPChainloader.Instance).Plugins.TryGetValue("MTFO.Extension.PartialBlocks", out var value)) { try { IsMainBranch = value.VersionAtLeast("1.5.2"); Type type = AccessTools.TypeByName("MTFO.Ext.PartialData.JsonConverters.PersistentIDConverter"); if (type != null && typeof(JsonConverter).IsAssignableFrom(type)) { PersistentIDConverter = (JsonConverter)Activator.CreateInstance(type); IsLoaded = PersistentIDConverter != null; } } catch (Exception ex) { IsLoaded = false; IsMainBranch = false; PersistentIDConverter = null; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(54, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Exception thrown while reading data from PartialData:\n"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex); } Logger.Error(val); } } BepInExDebugLogInterpolatedStringHandler val2 = new BepInExDebugLogInterpolatedStringHandler(57, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("PartialData is loaded: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<bool>(IsLoaded); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(", Version >= 1.5.2 (main branch): "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<bool>(IsMainBranch); } Logger.Debug(val2); if (IsLoaded && !IsMainBranch) { Logger.Warn("AWOPartialDataFixer (PartialDataModCompatible) is deprecated, or older version! It will still work but it's recommended to switch to the main branch of PartialData"); } } public static bool TryGetGUID(string text, out uint guid) { if (IsLoaded && IsMainBranch) { guid = GetGUID(text); return guid != 0; } guid = 0u; return false; } [MethodImpl(MethodImplOptions.NoInlining)] private static uint GetGUID(string text) { uint result = default(uint); if (PersistentIDManager.TryGetId(text, ref result)) { return result; } return 0u; } } } namespace AmorLib.API { public interface ILightModifier { Color Color { get; set; } float Intensity { get; set; } bool Enabled { get; set; } int Priority { get; } bool Active { get; } void Set(Color color, float intensity, bool enabled) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Color = color; Intensity = intensity; Enabled = enabled; } bool Register(); void Remove(); } [CallConstructorOnLoad] public static class LightAPI { internal static readonly ConcurrentDictionary<int, LightWorker> AllLightsMap; static LightAPI() { AllLightsMap = new ConcurrentDictionary<int, LightWorker>(); LevelAPI.OnAfterBuildBatch += OnAfterZoneLightsBatch; LevelAPI.OnLevelCleanup += OnLevelCleanup; } private static void OnLevelCleanup() { AllLightsMap.Clear(); } private static void OnAfterZoneLightsBatch(BatchName batch) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown if ((int)batch == 61) { bool flag = default(bool); BepInExDebugLogInterpolatedStringHandler val = new BepInExDebugLogInterpolatedStringHandler(35, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("All LightWorkers are setup. Count: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(AllLightsMap.Count); } Logger.Debug(val); } } public static LightWorker? GetSpecificLightWorker(int instanceID) { if (!TryGetSpecificLightWorker(instanceID, out LightWorker worker)) { return null; } return worker; } public static bool TryGetSpecificLightWorker(int instanceID, [NotNullWhen(true)] out LightWorker? worker) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown if (!AllLightsMap.TryGetValue(instanceID, out worker)) { bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(65, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to find LightWorker: no LG_Light exists with instance id "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(instanceID); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("!"); } Logger.Error(val); return false; } return true; } public static void ForEachWorker(this IEnumerable<LightWorker> lightWorkers, Action<LightWorker> action) { foreach (LightWorker lightWorker in lightWorkers) { action(lightWorker); } } public static IEnumerable<ILightModifier> AddLightModifiers(this IEnumerable<LightWorker> lightWorkers, Color color, float intensity, bool enabled, int priority = 1000) { //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) return lightWorkers.Select((LightWorker worker) => worker.AddModifier(color, intensity, enabled, priority)); } public static void ForEachMod(this IEnumerable<ILightModifier> lightModifiers, Action<ILightModifier> action) { foreac