Decompiled source of QuickSort v0.1.6
QuickSort.dll
Decompiled 11 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ChatCommandAPI; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Unity.Netcode; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: IgnoresAccessChecksTo("0Harmony")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: IgnoresAccessChecksTo("baer1.ChatCommandAPI")] [assembly: IgnoresAccessChecksTo("BepInEx")] [assembly: IgnoresAccessChecksTo("BepInEx.Harmony")] [assembly: IgnoresAccessChecksTo("Unity.InputSystem")] [assembly: IgnoresAccessChecksTo("Unity.Netcode.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.TextMeshPro")] [assembly: IgnoresAccessChecksTo("UnityEngine.IMGUIModule")] [assembly: IgnoresAccessChecksTo("UnityEngine.UI")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("QuickSort")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyDescription("made by Asta")] [assembly: AssemblyFileVersion("1.2.1.0")] [assembly: AssemblyInformationalVersion("1.2.1+066683a97cb60749303cd8f64e5af2e9d2e3aaac")] [assembly: AssemblyProduct("QuickSort")] [assembly: AssemblyTitle("QuickSort")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.2.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] 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; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace QuickSort { public static class Chat { [HarmonyPatch(typeof(HUDManager), "SubmitChat_performed")] [HarmonyPrefix] [HarmonyWrapSafe] public static bool OnChatSubmit(HUDManager __instance, CallbackContext context) { if (!((CallbackContext)(ref context)).performed || !Player.Local.isTypingChat) { return true; } string text = __instance.chatTextField.text; if (text == "/help") { string text2 = "Commands: "; text2 += string.Join(", ", ChatCommand.Commands); Log.Chat(text2); CloseChat(__instance); return false; } if (text.StartsWith("/")) { text = text.Substring(1).Trim(); foreach (ChatCommand command in ChatCommand.Commands) { if (text.StartsWith(command.keyword)) { CloseChat(__instance); try { command.action(command.GetArgs(text)); } catch (Exception e) { Log.Exception(e); } return false; } } } return true; } public static void CloseChat(HUDManager instance) { instance.localPlayer.isTypingChat = false; instance.chatTextField.text = ""; EventSystem.current.SetSelectedGameObject((GameObject)null); ((Behaviour)instance.typingIndicator).enabled = false; } } public abstract class Argument { public string name; } public class Argument<T> : Argument { public T value; public Argument(string name, T value) { base.name = name; this.value = value; } public static implicit operator T(Argument<T> arg) { return arg.value; } } public struct ChatArgs { public Argument[] arguments; public string help; public int Length => arguments.Length; public bool Empty => arguments.Length == 0; public bool this[string name] => Get<bool>(name); public string this[int name] => Get<string>(name); public T Get<T>(string name) { Argument[] array = arguments; Argument[] array2 = array; foreach (Argument argument in array2) { if (argument.name == name) { return ((Argument<T>)argument).value; } } return default(T); } public T Get<T>(int name) { Argument[] array = arguments; Argument[] array2 = array; foreach (Argument argument in array2) { if (argument.name == name.ToString()) { return ((Argument<T>)argument).value; } } return default(T); } } public class ChatCommand { public static List<ChatCommand> Commands = new List<ChatCommand>(); public string keyword; public Action<ChatArgs> action; public string help; public ChatCommand(string keyword, string help, Action<ChatArgs> action) { this.keyword = keyword; this.help = help; this.action = action; } public static ChatCommand New(string keyword, string help, Action<ChatArgs> action) { ChatCommand chatCommand = new ChatCommand(keyword, help, action); Commands.Add(chatCommand); return chatCommand; } public ChatArgs GetArgs(string raw) { List<Argument> list = new List<Argument>(); string[] array = raw.Split(' '); keyword = array[0]; for (int i = 1; i < array.Length; i++) { if (array[i].StartsWith("-")) { string name = array[i].Substring(1); list.Add(new Argument<bool>(name, value: true)); } else { list.Add(new Argument<string>((i - 1).ToString(), array[i])); } } ChatArgs result = default(ChatArgs); result.arguments = list.ToArray(); result.help = help; return result; } public override string ToString() { return keyword; } } public static class Extensions { public static string NormalizeName(string s) { if (string.IsNullOrWhiteSpace(s)) { return ""; } return s.ToLower().Replace(" ", "_").Replace("-", "_") .Trim(); } public static string Name(this GrabbableObject item) { return NormalizeName(item.itemProperties.itemName); } public static string Name(this Item item) { return NormalizeName(item.itemName); } } [HarmonyPatch(typeof(PlayerControllerB), "BeginGrabObject")] internal static class GrabPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator ilGenerator) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Expected O, but got Unknown //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected O, but got Unknown //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Expected O, but got Unknown //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Expected O, but got Unknown //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Expected O, but got Unknown //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Expected O, but got Unknown //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Expected O, but got Unknown //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(instructions); CodeMatcher val = new CodeMatcher((IEnumerable<CodeInstruction>)list, ilGenerator); CodeInstruction val2 = null; MethodInfo methodInfo = AccessTools.Method(typeof(NetworkBehaviour), "get_NetworkObject", (Type[])null, (Type[])null); for (int i = 0; i < list.Count - 1; i++) { CodeInstruction val3 = list[i]; if (val3.opcode == OpCodes.Callvirt && val3.operand is MethodInfo methodInfo2 && methodInfo2 == methodInfo) { CodeInstruction val4 = list[i + 1]; if (IsStloc(val4.opcode)) { val2 = val4.Clone(); break; } } } if (val2 == null) { val2 = new CodeInstruction(OpCodes.Stloc_0, (object)null); } Label label = default(Label); return val.MatchForward(true, (CodeMatch[])(object)new CodeMatch[1] { new CodeMatch((OpCode?)OpCodes.Callvirt, (object)AccessTools.Method(typeof(GrabbableObject), "InteractItem", (Type[])null, (Type[])null), (string)null) }).MatchBack(true, (CodeMatch[])(object)new CodeMatch[1] { new CodeMatch((OpCode?)OpCodes.Ldarg_0, (object)null, (string)null) }).Insert((CodeInstruction[])(object)new CodeInstruction[4] { new CodeInstruction(OpCodes.Ldarg_0, (object)null), new CodeInstruction(OpCodes.Ldfld, (object)AccessTools.Field(typeof(PlayerControllerB), "currentlyGrabbingObject")), new CodeInstruction(OpCodes.Callvirt, (object)methodInfo), val2 }) .ThrowIfInvalid("QuickSort: BeginGrabObject pattern not found (InteractItem)") .CreateLabel(ref label) .Start() .Insert((CodeInstruction[])(object)new CodeInstruction[2] { new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(Player), "OverrideGrabbingObject", (Type[])null, (Type[])null)), new CodeInstruction(OpCodes.Brtrue, (object)label) }) .InstructionEnumeration(); } private static bool IsStloc(OpCode op) { return op == OpCodes.Stloc || op == OpCodes.Stloc_S || op == OpCodes.Stloc_0 || op == OpCodes.Stloc_1 || op == OpCodes.Stloc_2 || op == OpCodes.Stloc_3; } } public class Log { private static ManualLogSource _log; public static void Init(ManualLogSource log) { _log = log; } public static void NotifyPlayer(string header, string body = "", bool isWarning = false) { if ((Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.DisplayTip(header, body, isWarning, false, "LC_Tip1"); } Debug(header); Debug(body); } public static void Chat(string body, string color = "FFFFFF") { if ((Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddChatMessage("<color=#" + color + ">[Pasta] " + body + "</color>", "", -1, false); } Debug(body); } public static void ConfirmSound() { if ((Object)(object)HUDManager.Instance != (Object)null && (Object)(object)GameNetworkManager.Instance != (Object)null) { HUDManager.Instance.UIAudio.PlayOneShot(GameNetworkManager.Instance.buttonTuneSFX); } } public static void Exception(Exception e) { string message = e.Message; string stackTrace = e.StackTrace; _log.LogError((object)message); _log.LogError((object)stackTrace); } public static void Error(params object[] objects) { _log.LogError((object)string.Join(" ", objects)); } public static void Warning(params object[] objects) { _log.LogWarning((object)string.Join(" ", objects)); } public static void Info(params object[] objects) { _log.LogInfo((object)string.Join(" ", objects)); } public static void Debug(params object[] objects) { _log.LogDebug((object)string.Join(" ", objects)); } } internal static class MoveUtils { public static bool MoveItemOnShip(GrabbableObject item, Vector3 worldPos, int floorYRot = -1) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)item == (Object)null) { return false; } PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val == (Object)null) { return false; } GameObject val2 = GameObject.Find("Environment/HangarShip"); if ((Object)(object)val2 == (Object)null) { return false; } Vector3 val3 = val2.transform.InverseTransformPoint(worldPos); ((Component)item).transform.position = worldPos; val.SetObjectAsNoLongerHeld(true, true, val3, item, floorYRot); val.ThrowObjectServerRpc(NetworkObjectReference.op_Implicit(((NetworkBehaviour)item).NetworkObject), true, true, val3, floorYRot); return true; } public static bool MoveItemOnShipLocal(GrabbableObject item, Vector3 shipLocalPos, int floorYRot = -1) { //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_008e: 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) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)item == (Object)null) { return false; } PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val == (Object)null) { return false; } GameObject val2 = GameObject.Find("Environment/HangarShip"); if ((Object)(object)val2 == (Object)null) { return false; } ((Component)item).transform.position = val2.transform.TransformPoint(shipLocalPos); try { val.SetObjectAsNoLongerHeld(true, true, shipLocalPos, item, floorYRot); } catch { } try { val.PlaceGrabbableObject(val2.transform, shipLocalPos, false, item); val.PlaceObjectServerRpc(NetworkObjectReference.op_Implicit(((NetworkBehaviour)item).NetworkObject), NetworkObjectReference.op_Implicit(val2), shipLocalPos, false); } catch { try { val.ThrowObjectServerRpc(NetworkObjectReference.op_Implicit(((NetworkBehaviour)item).NetworkObject), true, true, shipLocalPos, floorYRot); } catch { return false; } } return true; } public static bool TryTeleportToWorld(GameObject go, Vector3 worldPos, Quaternion worldRot) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) GrabbableObject val = (((Object)(object)go != (Object)null) ? go.GetComponent<GrabbableObject>() : null); if ((Object)(object)val != (Object)null) { return MoveItemOnShip(val, worldPos, val.floorYRot); } if ((Object)(object)go == (Object)null) { return false; } go.transform.SetPositionAndRotation(worldPos, worldRot); return true; } } public static class Player { public static GrabbableObject overrideObject; public static PlayerControllerB Local => StartOfRound.Instance?.localPlayerController; public static bool CanGrabObject(GrabbableObject item) { if ((Object)(object)item == (Object)null || !item.grabbable || item.deactivated || item.isHeld || item.isPocketed) { return false; } if ((Object)(object)Local == (Object)null || Local.isPlayerDead || Local.isTypingChat || Local.inTerminalMenu || Local.throwingObject || Local.IsInspectingItem || Local.isGrabbingObjectAnimation || (Object)(object)Local.inAnimationWithEnemy != (Object)null || Local.inSpecialInteractAnimation || Local.jetpackControls || Local.disablingJetpackControls || Local.activatingItem || Local.waitingToDropItem || Local.FirstEmptyItemSlot() == -1) { return false; } return true; } public static bool OverrideGrabbingObject() { if ((Object)(object)overrideObject == (Object)null) { return false; } Local.currentlyGrabbingObject = overrideObject; overrideObject = null; return true; } public static IEnumerator StartGrabbingObject(GrabbableObject grabbableObject) { if (CanGrabObject(grabbableObject)) { overrideObject = grabbableObject; Local.BeginGrabObject(); yield return Local.grabObjectCoroutine; } } public static IEnumerator StartMovingObject(GrabbableObject item, Vector3 position, NetworkObject? parent = null) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) yield return StartGrabbingObject(item); if ((Object)(object)Local == (Object)null) { yield break; } int frames = 0; while (frames < 30 && ((Object)(object)Local.currentlyHeldObjectServer == (Object)null || (Object)(object)Local.currentlyHeldObjectServer != (Object)(object)item)) { frames++; yield return null; } if ((Object)(object)Local.currentlyHeldObjectServer == (Object)null || (Object)(object)Local.currentlyHeldObjectServer != (Object)(object)item) { Log.Warning("Failed to grab " + (item?.itemProperties?.itemName ?? "item") + "; skipping move to avoid crash."); yield break; } try { item.floorYRot = -1; Local.DiscardHeldObject(true, parent, position, false); } catch (Exception e) { Log.Exception(e); } } } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("pasta.quicksort", "QuickSort", "1.0.0")] public class Plugin : BaseUnityPlugin { public static ManualLogSource Log; public static ConfigFile config; public static ConfigEntry<string> configVersion; private static Harmony harmony; public static GameObject sorterObject; public static Plugin Instance { get; private set; } private void Awake() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_02b0: Unknown result type (might be due to invalid IL or missing references) //IL_02ba: Expected O, but got Unknown //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; config = ((BaseUnityPlugin)this).Config; ConfigEntry<string> val = default(ConfigEntry<string>); bool flag = config.TryGetEntry<string>(new ConfigDefinition("General", "configVersion"), ref val); configVersion = config.Bind<string>("General", "configVersion", "0.1.5", "Config schema version (used for internal migrations)."); bool flag2 = false; string text = configVersion.Value ?? ""; if (string.IsNullOrWhiteSpace(text)) { text = "0.1.5"; configVersion.Value = text; flag2 = true; } if (!flag || IsVersionLessThan(text, "0.1.5")) { ConfigEntry<float> val2 = config.Bind<float>("Sorter", "sortOriginY", 0.1f, "Y coordinate of the origin position for sorting items (relative to ship)"); if (Mathf.Abs(val2.Value - 0.5f) < 0.0001f) { val2.Value = 0.1f; flag2 = true; } ConfigEntry<string> val3 = config.Bind<string>("Sorter", "skippedItems", "", "SCRAP skip list (comma-separated, substring match). Only applies to scrap items."); string text2 = AddTokensToCommaList(val3.Value, "shotgun", "ammo"); if (!string.Equals(text2, val3.Value, StringComparison.Ordinal)) { val3.Value = text2; flag2 = true; } configVersion.Value = "0.1.5"; flag2 = true; } if (flag2) { config.Save(); } QuickSort.Log.Init(((BaseUnityPlugin)this).Logger); QuickSort.Log.Info("QuickSort - Item Sorter loading..."); SortShortcuts.EnsureFileExists(); SortPositions.EnsureFileExists(); harmony = new Harmony("pasta.quicksort"); harmony.PatchAll(typeof(Ship)); harmony.PatchAll(typeof(Startup)); harmony.PatchAll(typeof(GrabPatch)); try { new SortCommand(); new SortBindCommand(); new SortSetCommand(); new SortResetCommand(); new SortPositionsCommand(); new SortBindingsListCommand(); new SortSkipCommand(); new PileCommand(); QuickSort.Log.Info("Sort command registered in Awake"); } catch (Exception ex) { QuickSort.Log.Error("Failed to register sort command in Awake: " + ex.Message); QuickSort.Log.Error("Stack trace: " + ex.StackTrace); } try { if ((Object)(object)sorterObject == (Object)null) { sorterObject = new GameObject("PastaSorter"); sorterObject.AddComponent<Sorter>(); Object.DontDestroyOnLoad((Object)(object)sorterObject); QuickSort.Log.Info("Sorter initialized in Awake"); } } catch (Exception ex2) { QuickSort.Log.Error("Failed to initialize Sorter in Awake: " + ex2.Message); QuickSort.Log.Error("Stack trace: " + ex2.StackTrace); } QuickSort.Log.Info("QuickSort - Item Sorter loaded!"); } private void OnDestroy() { if (harmony != null) { harmony.UnpatchSelf(); } if ((Object)(object)sorterObject != (Object)null) { Object.Destroy((Object)(object)sorterObject); } } private static bool IsVersionLessThan(string a, string b) { int[] array = Parse(a); int[] array2 = Parse(b); for (int i = 0; i < 3; i++) { if (array[i] < array2[i]) { return true; } if (array[i] > array2[i]) { return false; } } return false; static int[] Parse(string s) { if (string.IsNullOrWhiteSpace(s)) { return new int[3]; } string[] array3 = s.Trim().Split('.'); int[] array4 = new int[3]; for (int j = 0; j < 3; j++) { if (j < array3.Length && int.TryParse(array3[j], out var result)) { array4[j] = result; } else { array4[j] = 0; } } return array4; } } private static string AddTokensToCommaList(string? list, params string[] tokensToAdd) { List<string> tokens = new List<string>(); HashSet<string> seen = new HashSet<string>(); if (!string.IsNullOrWhiteSpace(list)) { string[] array = list.Split(','); foreach (string raw2 in array) { Add(raw2); } } if (tokensToAdd != null) { foreach (string raw3 in tokensToAdd) { Add(raw3); } } return string.Join(", ", tokens); void Add(string raw) { string text = (raw ?? "").Trim(); if (!string.IsNullOrWhiteSpace(text)) { text = Extensions.NormalizeName(text).Trim('_'); if (!string.IsNullOrWhiteSpace(text) && seen.Add(text)) { tokens.Add(text); } } } } } public static class Startup { private static bool commandRegistered; [HarmonyPatch(typeof(PlayerControllerB), "ConnectClientToPlayerObject")] [HarmonyPostfix] private static void OnLocalPlayerCreated(PlayerControllerB __instance) { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Expected O, but got Unknown if ((Object)(object)__instance != (Object)(object)StartOfRound.Instance.localPlayerController) { return; } if (!commandRegistered) { try { new SortCommand(); new SortBindCommand(); new SortSetCommand(); new SortResetCommand(); new SortPositionsCommand(); new SortBindingsListCommand(); new SortSkipCommand(); new PileCommand(); commandRegistered = true; Log.Info("Sort command registered"); } catch (Exception ex) { Log.Error("Failed to register sort command: " + ex.Message); } } if ((Object)(object)Plugin.sorterObject != (Object)null) { Object.Destroy((Object)(object)Plugin.sorterObject); } Plugin.sorterObject = new GameObject("PastaSorter"); Plugin.sorterObject.AddComponent<Sorter>(); Object.DontDestroyOnLoad((Object)(object)Plugin.sorterObject); } } public static class Ship { public static Action OnShipOrbit; public static Action OnShipTouchdown; public static Action OnShipAscent; public static Action OnShipDescent; private static readonly Dictionary<string, FieldInfo?> _startOfRoundBoolFields = new Dictionary<string, FieldInfo>(); public static bool Stationary { get { if (GetStartOfRoundBool("shipHasLanded").GetValueOrDefault()) { return true; } if (GetStartOfRoundBool("inShipPhase").GetValueOrDefault() && !GetStartOfRoundBool("shipIsLeaving").GetValueOrDefault()) { return true; } return false; } } public static bool InOrbit => GetStartOfRoundBool("inShipPhase").GetValueOrDefault() && !GetStartOfRoundBool("shipHasLanded").GetValueOrDefault(); private static bool? GetStartOfRoundBool(string fieldName) { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return null; } if (!_startOfRoundBoolFields.TryGetValue(fieldName, out FieldInfo value)) { value = AccessTools.Field(((object)instance).GetType(), fieldName); _startOfRoundBoolFields[fieldName] = value; } if (value == null) { return null; } try { object value2 = value.GetValue(instance); if (value2 is bool) { bool value3 = (bool)value2; if (true) { return value3; } } } catch { } return null; } [HarmonyPatch(typeof(StartOfRound), "ShipLeave")] [HarmonyPostfix] [HarmonyWrapSafe] public static void OnShipLeave() { OnShipAscent?.Invoke(); OnShipDescent?.Invoke(); } [HarmonyPatch(typeof(StartOfRound), "OnShipLandedMiscEvents")] [HarmonyPostfix] [HarmonyWrapSafe] public static void OnShipLanded() { OnShipTouchdown?.Invoke(); OnShipOrbit?.Invoke(); } } public class Sorter : MonoBehaviour { private static readonly HashSet<string> LowerYOffsetTypes = new HashSet<string> { "toilet_paper", "chemical_jug", "cash_register", "fancy_lamp", "large_axle", "v_type_engine" }; public ConfigEntry<float> sortOriginX; public ConfigEntry<float> sortOriginY; public ConfigEntry<float> sortOriginZ; public ConfigEntry<float> itemSpacing; public ConfigEntry<float> rowSpacing; public ConfigEntry<int> itemsPerRow; public ConfigEntry<string> skippedItems; public ConfigEntry<float> sortAreaWidth; public ConfigEntry<float> sortAreaDepth; public ConfigEntry<float> wallPadding; public ConfigEntry<bool> stackSameTypeTogether; public ConfigEntry<float> sameTypeStackStepY; private List<GrabbableObject> scrap; public static bool inProgress; private Vector3 SortOrigin => new Vector3(sortOriginX.Value, sortOriginY.Value, sortOriginZ.Value); private bool CanSort => Ship.InOrbit || Ship.Stationary; private static bool ShouldLowerYOffset(GrabbableObject item) { if ((Object)(object)item == (Object)null) { return false; } string text = item.Name(); if (string.IsNullOrWhiteSpace(text)) { return false; } return LowerYOffsetTypes.Contains(text); } private static string ApplyDefaultInputAliases(string normalizedKey) { if (string.IsNullOrWhiteSpace(normalizedKey)) { return normalizedKey; } if (1 == 0) { } string result = ((normalizedKey == "double_barrel") ? "shotgun" : ((!(normalizedKey == "shotgun_shell")) ? normalizedKey : "ammo")); if (1 == 0) { } return result; } private void Awake() { sortOriginX = Plugin.config.Bind<float>("Sorter", "sortOriginX", -2.8f, "X coordinate of the origin position for sorting items (relative to ship)"); sortOriginY = Plugin.config.Bind<float>("Sorter", "sortOriginY", 0.1f, "Y coordinate of the origin position for sorting items (relative to ship)"); sortOriginZ = Plugin.config.Bind<float>("Sorter", "sortOriginZ", -4.8f, "Z coordinate of the origin position for sorting items (relative to ship)"); itemSpacing = Plugin.config.Bind<float>("Sorter", "itemSpacing", 0.8f, "Spacing between items horizontally"); rowSpacing = Plugin.config.Bind<float>("Sorter", "rowSpacing", 0.8f, "Spacing between rows vertically"); itemsPerRow = Plugin.config.Bind<int>("Sorter", "itemsPerRow", 9, "Number of items per row"); skippedItems = Plugin.config.Bind<string>("Sorter", "skippedItems", "body, clipboard, sticky_note, boombox, shovel, jetpack, flashlight, pro_flashlight, key, stun_grenade, lockpicker, mapper, extension_ladder, tzp_inhalant, walkie_talkie, zap_gun, kitchen_knife, weed_killer, radar_booster, spray_paint, belt_bag, shotgun, ammo", "SCRAP skip list (comma-separated, substring match). Only applies to scrap items."); skippedItems.Value = NormalizeSkipListConfig(skippedItems.Value); sortAreaWidth = Plugin.config.Bind<float>("Sorter", "sortAreaWidth", 9f, "Reserved: used only for future bounding. (Keeping for compatibility)"); sortAreaDepth = Plugin.config.Bind<float>("Sorter", "sortAreaDepth", 6f, "How far forward (Z) types are allowed to expand from sortOriginZ before starting a new type-layer above."); wallPadding = Plugin.config.Bind<float>("Sorter", "wallPadding", 0.25f, "Padding used for depth calculation (prevents placing type rows into doors/walls)."); stackSameTypeTogether = Plugin.config.Bind<bool>("Sorter", "stackSameTypeTogether", true, "If true, items of the same type will be stacked at the exact same X/Z position (only Y increases), instead of being spread in a small grid."); sameTypeStackStepY = Plugin.config.Bind<float>("Sorter", "sameTypeStackStepY", 0f, "Vertical spacing between items when stackSameTypeTogether is enabled. Set to 0 for exact overlap; increase (e.g. 0.1~0.2) if physics makes items push apart."); } private void Update() { if (inProgress && ((ButtonControl)Keyboard.current.escapeKey).wasPressedThisFrame) { inProgress = false; Log.Chat("Sorting cancelled", "FF0000"); } } private void SortCommandHandler(string[] args) { if (args.Length != 0 && args[0] == "help") { Log.Chat("Usage: /sort [help]", "FFFF00"); return; } if (!CanSort) { Log.NotifyPlayer("Sorter Error", "Must be in orbit or stationary at company", isWarning: true); return; } if (inProgress) { Log.NotifyPlayer("Sorter Error", "Operation in progress", isWarning: true); return; } CategorizeItems(); Log.ConfirmSound(); ((MonoBehaviour)this).StartCoroutine(SortItems(force: false)); } public IEnumerator SortItems(bool force, bool ignoreSkippedItems = false, bool includeSavedPositionTypesEvenIfSkipped = false) { inProgress = true; Log.Chat("Press [Escape] to cancel sorting", "FFFF00"); if ((Object)(object)Player.Local == (Object)null) { Log.NotifyPlayer("Sorter Error", "Local player not ready yet", isWarning: true); inProgress = false; yield break; } GameObject ship = GameObject.Find("Environment/HangarShip"); if ((Object)(object)ship == (Object)null) { Log.NotifyPlayer("Sorter Error", "Ship not found", isWarning: true); inProgress = false; yield break; } Vector3 originLocal = SortOrigin; HashSet<string> savedTypes = null; string posListError; List<(string itemKey, Vector3 shipLocalPos)> savedPositions = SortPositions.ListAll(out posListError); if (posListError != null) { Log.Warning(posListError); } else { savedTypes = new HashSet<string>(savedPositions.Select<(string, Vector3), string>(((string itemKey, Vector3 shipLocalPos) p) => p.itemKey)); } Dictionary<string, List<GrabbableObject>> groupedItems = new Dictionary<string, List<GrabbableObject>>(); foreach (GrabbableObject item in scrap) { string itemName = item.Name(); bool ignoreSkipTokensForThisItem = ignoreSkippedItems || (includeSavedPositionTypesEvenIfSkipped && savedTypes != null && savedTypes.Contains(itemName)); if (!ShouldSkipFullSort(item, ignoreSkipTokensForThisItem)) { if (!groupedItems.ContainsKey(itemName)) { groupedItems[itemName] = new List<GrabbableObject>(); } groupedItems[itemName].Add(item); } } List<KeyValuePair<string, List<GrabbableObject>>> orderedGroups = (from kvp in groupedItems orderby kvp.Value != null && kvp.Value.Any(IsTwoHandedItem) descending, kvp.Key select kvp).ToList(); List<string> orderedTypeNames = orderedGroups.Select((KeyValuePair<string, List<GrabbableObject>> kvp) => kvp.Key).ToList(); HashSet<string> reservedTypes = savedTypes; Dictionary<string, Vector3> layout = CreateLayout(orderedTypeNames, reservedTypes); RaycastHit hitCenter = default(RaycastHit); foreach (KeyValuePair<string, List<GrabbableObject>> group in orderedGroups) { string itemName2 = group.Key; List<GrabbableObject> items = group.Value; Vector3 typePos = (layout.ContainsKey(itemName2) ? layout[itemName2] : Vector3.zero); Vector3 customShipLocal; string posError; bool hasCustomPos = SortPositions.TryGet(itemName2, out customShipLocal, out posError); if (posError != null) { Log.Warning(posError); } bool ignoreSkipTokensForThisType = includeSavedPositionTypesEvenIfSkipped && hasCustomPos; Vector3 pileCenterLocal = (Vector3)(hasCustomPos ? new Vector3(customShipLocal.x, 0f, customShipLocal.z) : (originLocal + new Vector3(typePos.x, 0f, typePos.z))); float customYOffset = (hasCustomPos ? customShipLocal.y : 0f); float typeLayerYOffset = (hasCustomPos ? 0f : typePos.y); float originYOffset = (hasCustomPos ? 0f : originLocal.y); float groundYLocal = pileCenterLocal.y; Vector3 rayStartCenter = ship.transform.TransformPoint(pileCenterLocal + Vector3.up * 2f); if (Physics.Raycast(rayStartCenter, Vector3.down, ref hitCenter, 80f, 268437761, (QueryTriggerInteraction)1)) { groundYLocal = ship.transform.InverseTransformPoint(((RaycastHit)(ref hitCenter)).point).y; } hitCenter = default(RaycastHit); for (int stackIndex = 0; stackIndex < items.Count; stackIndex++) { GrabbableObject item2 = items[stackIndex]; if (ShouldBreak(item2)) { Log.NotifyPlayer("Sorter Stopping", "Operation cancelled or ship is in motion", isWarning: true); inProgress = false; yield break; } float pileX = 0f; float pileZ = 0f; float pileY; if (stackSameTypeTogether.Value) { pileY = (float)stackIndex * Mathf.Max(0f, sameTypeStackStepY.Value); } else { int cols = Mathf.Max(1, itemsPerRow.Value); int rows = cols; int perLayer = cols * rows; int layer = stackIndex / perLayer; int inLayer = stackIndex % perLayer; int r = inLayer / cols; int c = inLayer % cols; pileX = ((float)c - (float)(cols - 1) / 2f) * 0.1f; pileZ = ((float)r - (float)(rows - 1) / 2f) * 0.1f; pileY = (float)layer * 0.07f; } Vector3 targetLocal = new Vector3(pileCenterLocal.x + pileX, groundYLocal + originYOffset + (item2.itemProperties.verticalOffset - 0.1f) + (typeLayerYOffset + customYOffset) + pileY - (ShouldLowerYOffset(item2) ? 0.2f : 0f), pileCenterLocal.z + pileZ); Vector3 worldPos = ship.transform.TransformPoint(targetLocal); if (!force && Vector3.Distance(worldPos, ((Component)item2).transform.position) < 0.25f) { continue; } yield return GrabbableRetry(item2); if (!ShouldSkipFullSort(item2, ignoreSkippedItems || ignoreSkipTokensForThisType)) { item2.floorYRot = -1; if (!MoveUtils.MoveItemOnShipLocal(item2, targetLocal, item2.floorYRot)) { Log.Warning("Failed to move " + (item2.itemProperties?.itemName ?? ((Object)item2).name)); } int retry = 15; while (!Player.CanGrabObject(item2) && retry > 0) { yield return (object)new WaitForEndOfFrame(); retry--; } } } customShipLocal = default(Vector3); posError = null; } Log.Chat("Sorting complete!", "00FF00"); inProgress = false; } private static bool IsTwoHandedItem(GrabbableObject item) { try { if ((Object)(object)item == (Object)null) { return false; } Item itemProperties = item.itemProperties; if ((Object)(object)itemProperties == (Object)null) { return false; } Type type = ((object)itemProperties).GetType(); FieldInfo fieldInfo = type.GetField("twoHanded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("isTwoHanded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("twoHandedItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("<twoHanded>k__BackingField", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("<isTwoHanded>k__BackingField", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("<twoHandedItem>k__BackingField", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (fieldInfo != null && fieldInfo.FieldType == typeof(bool)) { return (bool)fieldInfo.GetValue(itemProperties); } PropertyInfo propertyInfo = type.GetProperty("twoHanded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetProperty("isTwoHanded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetProperty("twoHandedItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (propertyInfo != null && propertyInfo.PropertyType == typeof(bool) && propertyInfo.GetIndexParameters().Length == 0) { return (bool)propertyInfo.GetValue(itemProperties, null); } FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo2 in fields) { if (!(fieldInfo2.FieldType != typeof(bool)) && fieldInfo2.Name.IndexOf("twohand", StringComparison.OrdinalIgnoreCase) >= 0) { return (bool)fieldInfo2.GetValue(itemProperties); } } PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo2 in properties) { if (!(propertyInfo2.PropertyType != typeof(bool)) && propertyInfo2.GetIndexParameters().Length == 0 && propertyInfo2.Name.IndexOf("twohand", StringComparison.OrdinalIgnoreCase) >= 0) { return (bool)propertyInfo2.GetValue(itemProperties, null); } } return false; } catch { return false; } } public bool TryStartGatherByQuery(string query, bool force, out string? error) { //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) error = null; if (!CanSort) { error = "Must be in orbit or stationary at company"; return false; } if (inProgress) { error = "Operation in progress"; return false; } if (string.IsNullOrWhiteSpace(query)) { error = "Missing item name"; return false; } CategorizeItems(includeSkippedItems: true); if (scrap == null || scrap.Count == 0) { error = "No items found in ship"; return false; } Dictionary<string, List<GrabbableObject>> dictionary = new Dictionary<string, List<GrabbableObject>>(); foreach (GrabbableObject item in scrap) { if (!ShouldSkipExplicitQuery(item)) { string key = item.Name(); if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new List<GrabbableObject>()); } value.Add(item); } } if (!TryResolveItemKeyFromGrouped(dictionary, query, out string resolvedKey, out error)) { return false; } if (!TryGetPlayerShipLocalTarget(out var shipLocalTarget, out error)) { return false; } if (!TryGetGroundYLocalAt(shipLocalTarget, out float groundYLocal, out string error2)) { error = error2; return false; } Vector3 targetCenterShipLocal = default(Vector3); ((Vector3)(ref targetCenterShipLocal))..ctor(shipLocalTarget.x, shipLocalTarget.y - groundYLocal, shipLocalTarget.z); ((MonoBehaviour)this).StartCoroutine(MoveItemsOfTypeToPosition(resolvedKey, targetCenterShipLocal, force, announce: true, ignoreSkipLists: true, applyTwoHandedSortYOffset: true)); return true; } public bool TryStartPileByQueryOrHeld(string? queryOrNull, bool force, out string? error) { //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) error = null; if (!CanSort) { error = "Must be in orbit or stationary at company"; return false; } if (inProgress) { error = "Operation in progress"; return false; } GrabbableObject val = (((Object)(object)Player.Local != (Object)null) ? Player.Local.currentlyHeldObjectServer : null); string text = ((!string.IsNullOrWhiteSpace(queryOrNull)) ? queryOrNull : (((Object)(object)val != (Object)null) ? val.Name() : "")); if (string.IsNullOrWhiteSpace(text)) { error = "Missing item name (hold the item or provide a name)."; return false; } CategorizeItems(includeSkippedItems: true); Dictionary<string, List<GrabbableObject>> dictionary = new Dictionary<string, List<GrabbableObject>>(); if (scrap != null) { foreach (GrabbableObject item in scrap) { if (!ShouldSkipExplicitQuery(item)) { string key = item.Name(); if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new List<GrabbableObject>()); } value.Add(item); } } } if ((Object)(object)val != (Object)null && !ShouldSkipExplicitQuery(val)) { string key2 = val.Name(); if (!dictionary.TryGetValue(key2, out var value2)) { value2 = (dictionary[key2] = new List<GrabbableObject>()); } if (!value2.Contains(val)) { value2.Add(val); } } if (dictionary.Count == 0) { error = "No items found in ship"; return false; } if (!TryResolveItemKeyFromGrouped(dictionary, text, out string resolvedKey, out error)) { return false; } if (!TryGetPlayerShipLocalTarget(out var shipLocalTarget, out error)) { return false; } if (!TryGetGroundYLocalAt(shipLocalTarget, out float groundYLocal, out string error2)) { error = error2; return false; } Vector3 targetCenterShipLocal = default(Vector3); ((Vector3)(ref targetCenterShipLocal))..ctor(shipLocalTarget.x, shipLocalTarget.y - groundYLocal, shipLocalTarget.z); ((MonoBehaviour)this).StartCoroutine(MoveItemsOfTypeToPosition(resolvedKey, targetCenterShipLocal, force, announce: true, ignoreSkipLists: true)); return true; } public bool TrySetAndMoveTypeToPlayer(string? queryOrNull, bool force, out string resolvedItemKey, out string? error) { //IL_0259: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) resolvedItemKey = ""; error = null; if (!CanSort) { error = "Must be in orbit or stationary at company"; return false; } if (inProgress) { error = "Operation in progress"; return false; } GrabbableObject val = (((Object)(object)Player.Local != (Object)null) ? Player.Local.currentlyHeldObjectServer : null); if (!string.IsNullOrWhiteSpace(queryOrNull)) { CategorizeItems(includeSkippedItems: true); if ((scrap == null || scrap.Count == 0) && (Object)(object)val == (Object)null) { error = "No items found in ship to match that name (hold the item or omit the name)."; return false; } Dictionary<string, List<GrabbableObject>> dictionary = new Dictionary<string, List<GrabbableObject>>(); if (scrap != null) { foreach (GrabbableObject item in scrap) { if (!ShouldSkipExplicitQuery(item)) { string key = item.Name(); if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new List<GrabbableObject>()); } value.Add(item); } } } if ((Object)(object)val != (Object)null && !ShouldSkipExplicitQuery(val)) { string key2 = val.Name(); if (!dictionary.TryGetValue(key2, out var value2)) { value2 = (dictionary[key2] = new List<GrabbableObject>()); } if (!value2.Contains(val)) { value2.Add(val); } } if (dictionary.Count == 0) { error = "No valid items found to match that name."; return false; } if (!TryResolveItemKeyFromGrouped(dictionary, queryOrNull, out resolvedItemKey, out error)) { return false; } } else { if ((Object)(object)val == (Object)null) { error = "No item name provided and no held item."; return false; } resolvedItemKey = val.Name(); } if (string.IsNullOrWhiteSpace(resolvedItemKey)) { error = "Invalid item name"; return false; } if (!TryGetPlayerShipLocalTarget(out var shipLocalTarget, out error)) { return false; } if (!TryGetGroundYLocalAt(shipLocalTarget, out float groundYLocal, out string error2)) { error = error2; return false; } Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(shipLocalTarget.x, shipLocalTarget.y - groundYLocal, shipLocalTarget.z); if (!SortPositions.Set(resolvedItemKey, val2, out error)) { return false; } Log.ConfirmSound(); ((MonoBehaviour)this).StartCoroutine(MoveItemsOfTypeToPosition(resolvedItemKey, val2, force, announce: true, ignoreSkipLists: true)); return true; } private bool TryResolveItemKeyFromGrouped(Dictionary<string, List<GrabbableObject>> grouped, string query, out string resolvedKey, out string? error) { resolvedKey = ""; error = null; string q = ApplyDefaultInputAliases(Extensions.NormalizeName(query)); if (string.IsNullOrWhiteSpace(q)) { error = "Invalid item name"; return false; } if (grouped.ContainsKey(q)) { resolvedKey = q; return true; } List<string> source = grouped.Keys.ToList(); string qLoose = q.Replace("_", ""); List<string> list = source.Where(delegate(string k) { if (k.Contains(q) || q.Contains(k)) { return true; } string text2 = k.Replace("_", ""); return text2.Contains(qLoose) || qLoose.Contains(text2); }).Distinct().ToList(); if (list.Count == 1) { resolvedKey = list[0]; return true; } if (list.Count == 0) { error = "No item match for '" + query + "'."; return false; } string text = string.Join(", ", list.Take(8)); if (list.Count > 8) { text += ", ..."; } error = "Ambiguous item '" + query + "'. Matches: " + text; return false; } private bool TryGetPlayerShipLocalTarget(out Vector3 shipLocalTarget, out string? error) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) shipLocalTarget = default(Vector3); error = null; if ((Object)(object)Player.Local == (Object)null) { error = "Local player not ready yet"; return false; } GameObject val = GameObject.Find("Environment/HangarShip"); if ((Object)(object)val == (Object)null) { error = "Ship not found"; return false; } Vector3 val2 = ((Component)Player.Local).transform.position + ((Component)Player.Local).transform.forward * 0.75f; shipLocalTarget = val.transform.InverseTransformPoint(val2); return true; } private bool TryGetGroundYLocalAt(Vector3 shipLocalXZ, out float groundYLocal, out string? error) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0094: 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) groundYLocal = 0f; error = null; GameObject val = GameObject.Find("Environment/HangarShip"); if ((Object)(object)val == (Object)null) { error = "Ship not found"; return false; } Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(shipLocalXZ.x, 0f, shipLocalXZ.z); Vector3 val3 = val.transform.TransformPoint(val2 + Vector3.up * 2f); RaycastHit val4 = default(RaycastHit); if (Physics.Raycast(val3, Vector3.down, ref val4, 80f, 268437761, (QueryTriggerInteraction)1)) { groundYLocal = val.transform.InverseTransformPoint(((RaycastHit)(ref val4)).point).y; return true; } groundYLocal = 0f; return true; } private IEnumerator MoveItemsOfTypeToPosition(string itemKey, Vector3 targetCenterShipLocal, bool force, bool announce, bool ignoreSkipLists = false, bool applyTwoHandedSortYOffset = false) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) inProgress = true; if (announce) { Log.Chat("Moving '" + itemKey + "' to your position... Press [Escape] to cancel", "FFFF00"); } if ((Object)(object)Player.Local == (Object)null) { Log.NotifyPlayer("Sorter Error", "Local player not ready yet", isWarning: true); inProgress = false; yield break; } GameObject ship = GameObject.Find("Environment/HangarShip"); if ((Object)(object)ship == (Object)null) { Log.NotifyPlayer("Sorter Error", "Ship not found", isWarning: true); inProgress = false; yield break; } Dictionary<string, List<GrabbableObject>> groupedItems = new Dictionary<string, List<GrabbableObject>>(); foreach (GrabbableObject item in scrap) { if (!(ignoreSkipLists ? ShouldSkipExplicitQuery(item) : ShouldSkip(item))) { string name = item.Name(); if (!groupedItems.TryGetValue(name, out List<GrabbableObject> list)) { list = (groupedItems[name] = new List<GrabbableObject>()); } list.Add(item); list = null; } } if (!groupedItems.TryGetValue(itemKey, out List<GrabbableObject> items) || items.Count == 0) { items = new List<GrabbableObject>(); } Vector3 pileCenterLocal = new Vector3(targetCenterShipLocal.x, 0f, targetCenterShipLocal.z); float extraYOffset = targetCenterShipLocal.y; float groundYLocal = pileCenterLocal.y; Vector3 rayStartCenter = ship.transform.TransformPoint(pileCenterLocal + Vector3.up * 2f); RaycastHit hitCenter = default(RaycastHit); if (Physics.Raycast(rayStartCenter, Vector3.down, ref hitCenter, 80f, 268437761, (QueryTriggerInteraction)1)) { groundYLocal = ship.transform.InverseTransformPoint(((RaycastHit)(ref hitCenter)).point).y; } hitCenter = default(RaycastHit); GrabbableObject held = (((Object)(object)Player.Local != (Object)null) ? Player.Local.currentlyHeldObjectServer : null); List<GrabbableObject> itemsToMove = new List<GrabbableObject>(items); if ((Object)(object)held != (Object)null && held.Name() == itemKey && !itemsToMove.Contains(held)) { itemsToMove.Insert(0, held); } int moved = 0; for (int stackIndex = 0; stackIndex < itemsToMove.Count; stackIndex++) { GrabbableObject item2 = itemsToMove[stackIndex]; if (ShouldBreak(item2)) { Log.NotifyPlayer("Sorter Stopping", "Operation cancelled or ship is in motion", isWarning: true); inProgress = false; yield break; } float pileX = 0f; float pileZ = 0f; float pileY; if (stackSameTypeTogether.Value) { pileY = (float)stackIndex * Mathf.Max(0f, sameTypeStackStepY.Value); } else { int cols = Mathf.Max(1, itemsPerRow.Value); int rows = cols; int perLayer = cols * rows; int layer = stackIndex / perLayer; int inLayer = stackIndex % perLayer; int r = inLayer / cols; int c = inLayer % cols; pileX = ((float)c - (float)(cols - 1) / 2f) * 0.1f; pileZ = ((float)r - (float)(rows - 1) / 2f) * 0.1f; pileY = (float)layer * 0.07f; } Vector3 targetLocal = new Vector3(pileCenterLocal.x + pileX, groundYLocal + (item2.itemProperties.verticalOffset - 0.05f) + extraYOffset + pileY, pileCenterLocal.z + pileZ); Vector3 worldPos = ship.transform.TransformPoint(targetLocal); if (!force && Vector3.Distance(worldPos, ((Component)item2).transform.position) < 0.25f) { continue; } if ((Object)(object)held != (Object)null && (Object)(object)item2 == (Object)(object)held) { item2.floorYRot = -1; MoveUtils.MoveItemOnShipLocal(item2, targetLocal, item2.floorYRot); moved++; yield return null; continue; } yield return GrabbableRetry(item2); if (!(ignoreSkipLists ? ShouldSkipExplicitQuery(item2) : ShouldSkip(item2))) { item2.floorYRot = -1; if (!MoveUtils.MoveItemOnShipLocal(item2, targetLocal, item2.floorYRot)) { Log.Warning("Failed to move " + (item2.itemProperties?.itemName ?? ((Object)item2).name)); } int retry = 15; while (!Player.CanGrabObject(item2) && retry > 0) { yield return (object)new WaitForEndOfFrame(); retry--; } moved++; } } Log.Chat($"Moved '{itemKey}' ({moved} items)", "00FF00"); inProgress = false; } private Dictionary<string, Vector3> CreateLayout(List<string> itemNames, HashSet<string>? reservedTypes = null) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) Dictionary<string, Vector3> dictionary = new Dictionary<string, Vector3>(); int num = Mathf.Max(1, itemsPerRow.Value); int num2 = 0; for (int i = 0; i < itemNames.Count; i++) { string text = itemNames[i]; if (reservedTypes != null && reservedTypes.Contains(text)) { num2++; continue; } int num3 = i - num2; int num4 = num3 / num; int num5 = num3 % num; float num6 = (float)(num - 1) * 0.5f; float num7 = ((float)num5 - num6) * itemSpacing.Value; float num8 = 0f; float num9 = (float)num4 * rowSpacing.Value + 0.05f; dictionary[text] = new Vector3(num7, num9, num8); } return dictionary; } public void CategorizeItems(bool includeSkippedItems = false) { scrap = new List<GrabbableObject>(); GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>(); GrabbableObject[] array2 = array; foreach (GrabbableObject val in array2) { if (!(includeSkippedItems ? ShouldSkipExplicitQuery(val) : ShouldSkip(val)) && val.isInShipRoom) { scrap.Add(val); } } scrap = (from item in scrap orderby item.scrapValue, item.Name() select item).ToList(); } private IEnumerator GrabbableRetry(GrabbableObject item) { int retry = 15; while (!Player.CanGrabObject(item) && retry > 0) { yield return (object)new WaitForEndOfFrame(); retry--; } } private bool ShouldBreak(GrabbableObject item) { return !inProgress || !Ship.Stationary || ((Object)(object)Player.Local != (Object)null && (Player.Local.beamOutParticle.isPlaying || Player.Local.beamUpParticle.isPlaying)); } private bool ShouldSkipExplicitQuery(GrabbableObject item) { if ((Object)(object)item == (Object)null) { return true; } if (!item.grabbable || item.deactivated || item.isHeld || item.isPocketed) { return true; } if (item.Name() == "body") { return true; } if (!item.isInShipRoom) { return true; } return false; } private static string NormalizeSkipListConfig(string list) { if (string.IsNullOrWhiteSpace(list)) { return list ?? ""; } HashSet<string> hashSet = new HashSet<string>(); List<string> list2 = new List<string>(); string[] array = list.Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i]?.Trim() ?? ""; if (!string.IsNullOrWhiteSpace(text)) { text = NormalizeSkipToken(text); if (text == "rader_booster") { text = "radar_booster"; } if (!string.IsNullOrWhiteSpace(text) && hashSet.Add(text)) { list2.Add(text); } } } return string.Join(", ", list2); } private static List<string> ParseSkipListTokens(string list) { if (string.IsNullOrWhiteSpace(list)) { return new List<string>(); } return (from t in list.Split(',') select NormalizeSkipToken(t) into t where !string.IsNullOrWhiteSpace(t) select (t == "rader_booster") ? "radar_booster" : t).Distinct().ToList(); } private static string NormalizeSkipToken(string s) { if (string.IsNullOrWhiteSpace(s)) { return ""; } string text = Extensions.NormalizeName(s); text = text.Trim('_'); if (text == "rader_booster") { text = "radar_booster"; } return text; } private static void TrySaveConfig() { try { ConfigFile config = Plugin.config; if (config != null) { config.Save(); } } catch (Exception ex) { Log.Warning("Failed to save config: " + ex.Message); } } public bool TrySkipAdd(string rawItemName, out string? error, out string? message) { error = null; message = null; string text = NormalizeSkipToken(rawItemName); if (string.IsNullOrWhiteSpace(text)) { error = "Usage: /sort skip add <itemName>"; return false; } List<string> list = ParseSkipListTokens(skippedItems.Value); if (list.Contains(text)) { message = "Already in skippedItems: " + text; return true; } list.Add(text); skippedItems.Value = NormalizeSkipListConfig(string.Join(", ", list)); TrySaveConfig(); message = "Added to skippedItems: " + text; return true; } public bool TrySkipRemove(string rawItemName, out string? error, out string? message) { error = null; message = null; string token = NormalizeSkipToken(rawItemName); if (string.IsNullOrWhiteSpace(token)) { error = "Usage: /sort skip remove <itemName>"; return false; } List<string> list = ParseSkipListTokens(skippedItems.Value); int count = list.Count; list = list.Where((string t) => t != token).ToList(); if (list.Count == count) { message = "Not in skippedItems: " + token; return true; } skippedItems.Value = NormalizeSkipListConfig(string.Join(", ", list)); TrySaveConfig(); message = "Removed from skippedItems: " + token; return true; } public List<string> GetSkippedTokens() { return (from t in ParseSkipListTokens(skippedItems.Value) orderby t select t).ToList(); } private bool ShouldSkip(GrabbableObject item) { if ((Object)(object)item == (Object)null) { return true; } if (!item.grabbable || item.deactivated || item.isHeld || item.isPocketed) { return true; } if (item.Name() == "body") { return true; } if (!item.isInShipRoom) { return true; } string text = item.Name(); string text2 = (((Object)(object)item.itemProperties != (Object)null && item.itemProperties.isScrap) ? skippedItems.Value : ""); if (!string.IsNullOrWhiteSpace(text2)) { string[] array = text2.Split(','); string[] array2 = array; foreach (string s in array2) { string value = NormalizeSkipToken(s); if (!string.IsNullOrWhiteSpace(value) && text.Contains(value)) { return true; } } } return false; } private bool ShouldSkipFullSort(GrabbableObject item, bool ignoreSkipTokens = false) { if ((Object)(object)item == (Object)null) { return true; } if (!item.grabbable || item.deactivated || item.isHeld || item.isPocketed) { return true; } if (item.Name() == "body") { return true; } if (!item.isInShipRoom) { return true; } if (ignoreSkipTokens) { return false; } string text = item.Name(); string value = skippedItems.Value; if (!string.IsNullOrWhiteSpace(value)) { string[] array = value.Split(','); foreach (string s in array) { string value2 = NormalizeSkipToken(s); if (!string.IsNullOrWhiteSpace(value2) && text.Contains(value2)) { return true; } } } return false; } } public class SortCommand : Command { public override string Name => "sort"; public override string[] Commands => new string[2] { "sort", ((Command)this).Name }; public override string Description => "Sorts items on the ship.\nUsage:\n /sort -> sort everything\n /sort -a -> sort everything, IGNORE skippedItems\n /sort -b -> full sort, but DO NOT skip item types that have a saved /sort set position\n (Note: -a and -b cannot be combined)\n /sort skip list -> show skippedItems tokens\n /sort skip add <name> -> add token to skippedItems (name can include spaces)\n /sort skip remove <name> -> remove token from skippedItems\n /sort <itemName> -> pull that item type to YOUR position (e.g. /sort cash_register)\n /sort <number> -> shortcut from JSON (pull to you) (e.g. /sort 1)\n /sort bind <name|id> -> bind your HELD item to an alias name OR shortcut id (then /sort <name> or /sort <id> works)\n /sort set [itemName] -> set this type's future sort position to YOUR position (name optional if holding)\n /sort reset [itemName]-> delete saved sort position for this type (name optional if holding)\n /sort bindings -> list all binds (numbers + names)\n /sort positions -> list saved sort positions"; public SortCommand() { Log.Info("SortCommand constructor called"); } public override bool Invoke(string[] args, Dictionary<string, string> kwargs, out string? error) { //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Expected O, but got Unknown //IL_0a71: Unknown result type (might be due to invalid IL or missing references) //IL_0a80: Unknown result type (might be due to invalid IL or missing references) //IL_0a8f: Unknown result type (might be due to invalid IL or missing references) error = null; Log.Info("SortCommand.Invoke called with args: [" + string.Join(", ", args) + "]"); if (args.Length != 0 && args[0] == "help") { ChatCommandAPI.Print(((Command)this).Description); return true; } if (!Ship.Stationary) { error = "Must be in orbit or stationary at company"; Log.Warning("SortCommand failed: " + error); return false; } if (Sorter.inProgress) { error = "Operation in progress"; Log.Warning("SortCommand failed: " + error); return false; } if (args.Contains("-r") || args.Contains("-redo")) { error = "Flag '-r/-redo' was removed."; return false; } if (args.Contains("-ab") || args.Contains("-ba")) { error = "Flags '-a' and '-b' cannot be combined. Use only one."; return false; } bool flag = args.Contains("-a") || args.Contains("-all") || (kwargs != null && (kwargs.ContainsKey("a") || kwargs.ContainsKey("all"))); bool flag2 = args.Contains("-b") || args.Contains("-bound") || (kwargs != null && (kwargs.ContainsKey("b") || kwargs.ContainsKey("bound"))); if (flag && flag2) { error = "Flags '-a' and '-b' cannot be combined. Use only one."; return false; } string[] array = args.Where((string a) => a != "-a" && a != "-all" && a != "-b" && a != "-bound" && a != "-ab" && a != "-ba").ToArray(); Sorter sorter = null; if ((Object)(object)Plugin.sorterObject != (Object)null) { sorter = Plugin.sorterObject.GetComponent<Sorter>(); } if ((Object)(object)sorter == (Object)null) { try { if ((Object)(object)Plugin.sorterObject == (Object)null) { Plugin.sorterObject = new GameObject("PastaSorter"); Object.DontDestroyOnLoad((Object)(object)Plugin.sorterObject); Log.Info("Sorter lazily initialized from SortCommand"); } sorter = Plugin.sorterObject.GetComponent<Sorter>(); if ((Object)(object)sorter == (Object)null) { sorter = Plugin.sorterObject.AddComponent<Sorter>(); Log.Info("Sorter component added from SortCommand"); } } catch (Exception ex) { error = "Sorter not initialized yet"; Log.Error("SortCommand failed: " + error); Log.Error("Exception: " + ex.Message); return false; } if ((Object)(object)sorter == (Object)null) { error = "Sorter not initialized yet"; Log.Warning("SortCommand failed: " + error); return false; } } Log.Info("SortCommand executing..."); if (array.Length != 0) { if (array[0] == "skip") { if (array.Length < 2) { error = "Usage: /sort skip <list|add|remove> ..."; return false; } string text = array[1]; if (text == "list" || text == "ls") { List<string> skippedTokens = sorter.GetSkippedTokens(); if (skippedTokens.Count == 0) { ChatCommandAPI.Print("skippedItems is empty."); return true; } string text2 = string.Join(", ", skippedTokens.Take(20)); if (skippedTokens.Count > 20) { text2 += ", ..."; } ChatCommandAPI.Print($"skippedItems ({skippedTokens.Count}): {text2}"); return true; } switch (text) { case "add": { string text4 = string.Join(" ", array.Skip(2)).Trim(); if (string.IsNullOrWhiteSpace(text4)) { text4 = (Player.Local?.currentlyHeldObjectServer)?.Name() ?? ""; if (string.IsNullOrWhiteSpace(text4)) { error = "Usage: /sort skip add <itemName> (or hold an item)"; return false; } } text4 = ResolveSkipTarget(text4); if (!sorter.TrySkipAdd(text4, out error, out string message2)) { return false; } if (!string.IsNullOrWhiteSpace(message2)) { ChatCommandAPI.Print(message2); } return true; } default: if (!(text == "del")) { error = "Usage: /sort skip <list|add|remove> ..."; return false; } goto case "remove"; case "remove": case "rm": { string text3 = string.Join(" ", array.Skip(2)).Trim(); if (string.IsNullOrWhiteSpace(text3)) { text3 = (Player.Local?.currentlyHeldObjectServer)?.Name() ?? ""; if (string.IsNullOrWhiteSpace(text3)) { error = "Usage: /sort skip remove <itemName> (or hold an item)"; return false; } } text3 = ResolveSkipTarget(text3); if (!sorter.TrySkipRemove(text3, out error, out string message)) { return false; } if (!string.IsNullOrWhiteSpace(message)) { ChatCommandAPI.Print(message); } return true; } } } if (array[0] == "bind") { if (array.Length < 2) { error = "Usage: /sort bind <name> (hold the item you want to bind)"; return false; } if (array.Length >= 3 && (array[1] == "reset" || array[1] == "rm" || array[1] == "remove" || array[1] == "del")) { string text5 = string.Join(" ", array.Skip(2)).Trim(); if (string.IsNullOrWhiteSpace(text5)) { error = "Usage: /sort bind reset <name|id>"; return false; } if (int.TryParse(text5, out var result) && result > 0) { if (!SortShortcuts.RemoveShortcut(result, out bool removed, out string error2)) { error = error2 ?? "Failed to remove shortcut."; return false; } ChatCommandAPI.Print(removed ? $"Unbound {result}" : $"No binding for {result}"); return true; } if (!SortShortcuts.RemoveAlias(text5, out bool removed2, out string error3)) { error = error3 ?? "Failed to remove alias."; return false; } string text6 = Extensions.NormalizeName(text5); ChatCommandAPI.Print(removed2 ? ("Unbound " + text6) : ("No binding for " + text6)); return true; } GrabbableObject val = (((Object)(object)Player.Local != (Object)null) ? Player.Local.currentlyHeldObjectServer : null); if ((Object)(object)val == (Object)null) { error = "You must hold an item to bind."; return false; } string text7 = string.Join(" ", array.Skip(1)); string text8 = val.Name(); if (int.TryParse(text7.Trim(), out var result2) && result2 > 0) { if (!SortShortcuts.SetShortcut(result2, text8, out string error4)) { error = error4 ?? "Failed to bind shortcut."; return false; } ChatCommandAPI.Print($"Bound {result2} => {text8}"); return true; } if (!SortShortcuts.BindAlias(text7, text8, out string error5)) { error = error5 ?? "Failed to bind alias."; return false; } ChatCommandAPI.Print("Bound " + Extensions.NormalizeName(text7) + " => " + text8); return true; } if (array[0] == "reset") { string text9 = ((array.Length > 1) ? Extensions.NormalizeName(string.Join(" ", array.Skip(1))) : (Player.Local?.currentlyHeldObjectServer)?.Name()); if (string.IsNullOrWhiteSpace(text9)) { error = "Missing item name (hold the item or provide a name)."; return false; } if (!SortPositions.Remove(text9, out bool removed3, out string error6)) { error = error6 ?? "Failed to remove saved position."; return false; } if (error6 != null) { error = error6; return false; } if (removed3) { ChatCommandAPI.Print("Removed saved sort position for '" + text9 + "'."); } else { ChatCommandAPI.Print("No saved sort position for '" + text9 + "'."); } return true; } if (array[0] == "set") { string text10 = ((array.Length > 1) ? string.Join(" ", array.Skip(1)) : null); if (!sorter.TrySetAndMoveTypeToPlayer(text10, force: false, out string resolvedItemKey, out error)) { return false; } if (!string.IsNullOrWhiteSpace(text10)) { string text11 = Extensions.NormalizeName(text10); if (!string.IsNullOrWhiteSpace(text11) && text11 != resolvedItemKey) { ChatCommandAPI.Print("Resolved '" + text10 + "' => '" + resolvedItemKey + "'."); } } string text12 = (string.IsNullOrWhiteSpace(resolvedItemKey) ? "held_item" : resolvedItemKey); if (SortPositions.TryGet(resolvedItemKey, out Vector3 shipLocalPos, out string error7)) { ChatCommandAPI.Print($"Saved sort position for '{text12}' => (x={shipLocalPos.x:F2}, y={shipLocalPos.y:F2}, z={shipLocalPos.z:F2})."); } else { if (error7 != null) { Log.Warning(error7); } ChatCommandAPI.Print("Saved sort position for '" + text12 + "'."); } return true; } if (array[0] == "positions") { string error8; List<(string, Vector3)> list = SortPositions.ListAll(out error8); if (error8 != null) { error = error8; return false; } if (list.Count == 0) { ChatCommandAPI.Print("No saved sort positions."); return true; } string text13 = string.Join(", ", from p in list.Take(8) select $"{p.itemKey}=(x={p.shipLocalPos.x:F1},y={p.shipLocalPos.y:F1},z={p.shipLocalPos.z:F1})"); if (list.Count > 8) { text13 += ", ..."; } ChatCommandAPI.Print(text13); return true; } if (array[0] == "bindings" || array[0] == "binds" || array[0] == "shortcuts" || array[0] == "aliases") { string error9; List<(int, string)> list2 = SortShortcuts.ListShortcuts(out error9); if (error9 != null) { error = error9; return false; } string error10; List<(string, string)> list3 = SortShortcuts.ListAliases(out error10); if (error10 != null) { error = error10; return false; } if (list2.Count == 0 && list3.Count == 0) { ChatCommandAPI.Print("No bindings found."); return true; } if (list2.Count > 0) { string text14 = string.Join(", ", list2.Select<(int, string), string>(((int id, string itemKey) s) => $"{s.id}={s.itemKey}")); ChatCommandAPI.Print(text14); } if (list3.Count > 0) { string text15 = string.Join(", ", from a in list3.Take(12) select a.alias + "=" + a.itemKey); if (list3.Count > 12) { text15 += ", ..."; } ChatCommandAPI.Print(text15); } return true; } if (array.Length == 1 && int.TryParse(array[0], out var result3)) { if (!SortShortcuts.TryResolve(result3, out string itemKey, out string error11)) { error = error11 ?? "Unknown shortcut error"; return false; } Log.ConfirmSound(); if (!sorter.TryStartGatherByQuery(itemKey, force: false, out error)) { return false; } Log.Info("SortCommand executed successfully (shortcut move)"); return true; } if (array.Length == 1 && SortShortcuts.TryResolveAlias(array[0], out string itemKey2, out string _)) { Log.ConfirmSound(); if (!sorter.TryStartGatherByQuery(itemKey2, force: false, out error)) { return false; } Log.Info("SortCommand executed successfully (alias move)"); return true; } string query = string.Join(" ", array); Log.ConfirmSound(); if (!sorter.TryStartGatherByQuery(query, force: false, out error)) { return false; } Log.Info("SortCommand executed successfully (item move)"); return true; } sorter.CategorizeItems(flag || flag2); Log.ConfirmSound(); ((MonoBehaviour)sorter).StartCoroutine(sorter.SortItems(force: false, flag, flag2)); Log.Info("SortCommand executed successfully (full sort)"); return true; static string ResolveSkipTarget(string raw) { raw = (raw ?? "").Trim(); if (string.IsNullOrWhiteSpace(raw)) { return raw; } string error13; if (int.TryParse(raw, out var result4) && result4 > 0) { if (SortShortcuts.TryResolve(result4, out string itemKey3, out error13)) { return itemKey3; } return raw; } if (SortShortcuts.TryResolveAlias(raw, out string itemKey4, out error13)) { return itemKey4; } return raw; } } } public class SortBindCommand : Command { public override string Name => "sb"; public override string[] Commands => new string[2] { "sb", ((Command)this).Name }; public override string Description => "Shortcut for /sort bind\nUsage:\n /sb <name|id> -> bind your HELD item to an alias name OR shortcut id"; public override bool Invoke(string[] args, Dictionary<string, string> kwargs, out string? error) { string[] array = new string[1] { "bind" }.Concat(args ?? Array.Empty<string>()).ToArray(); return ((Command)new SortCommand()).Invoke(array, kwargs, ref error); } } public class SortSetCommand : Command { public override string Name => "ss"; public override string[] Commands => new string[2] { "ss", ((Command)this).Name }; public override string Description => "Shortcut for /sort set\nUsage:\n /ss [itemName] -> set saved sort position for this type (name optional if holding; partial match supported)"; public override bool Invoke(string[] args, Dictionary<string, string> kwargs, out string? error) { string[] array = new string[1] { "set" }.Concat(args ?? Array.Empty<string>()).ToArray(); return ((Command)new SortCommand()).Invoke(array, kwargs, ref error); } } public class SortResetCommand : Command { public override string Name => "sr"; public override string[] Commands => new string[2] { "sr", ((Command)this).Name }; public override string Description => "Shortcut for /sort reset\nUsage:\n /sr [itemName] -> delete saved sort position for this type (name optional if holding)"; public override bool Invoke(string[] args, Dictionary<string, string> kwargs, out string? error) { string[] array = new string[1] { "reset" }.Concat(args ?? Array.Empty<string>()).ToArray(); return ((Command)new SortCommand()).Invoke(array, kwargs, ref error); } } public class SortPositionsCommand : Command { public override string Name => "sp"; public override string[] Commands => new string[2] { "sp", ((Command)this).Name }; public override string Description => "Shortcut for /sort positions\nUsage:\n /sp -> list saved sort positions"; public override bool Invoke(string[] args, Dictionary<string, string> kwargs, out string? error) { string[] array = new string[1] { "positions" }; return ((Command)new SortCommand()).Invoke(array, kwargs, ref error); } } public class SortBindingsListCommand : Command { public override string Name => "sbl"; public override string[] Commands => new string[2] { "sbl", ((Command)this).Name }; public override string Description => "Shortcut for /sort bindings\nUsage:\n /sbl -> list bindings (shortcuts + aliases)"; public override bool Invoke(string[] args, Dictionary<string, string> kwargs, out string? error) { string[] array = new string[1] { "bindings" }; return ((Command)new SortCommand()).Invoke(array, kwargs, ref error); } } public class SortSkipCommand : Command { public override string Name => "sk"; public override string[] Commands => new string[2] { "sk", ((Command)this).Name }; public override string Description => "Shortcut for /sort skip\nUsage:\n /sk list -> show skippedItems tokens\n /sk add [itemName] -> add token (or use held item if omitted)\n /sk remove [itemName] -> remove token (or use held item if omitted)"; public override bool Invoke(string[] args, Dictionary<string, string> kwargs, out string? error) { string[] array = new string[1] { "skip" }.Concat(args ?? Array.Empty<string>()).ToArray(); return ((Command)new SortCommand()).Invoke(array, kwargs, ref error); } } public class PileCommand : Command { public override string Name => "pile"; public override string[] Commands => new string[2] { "pile", ((Command)this).Name }; public override string Description => "Piles a specific item type onto your position (like /sort <item>).\nUsage:\n /pile <itemName> -> pull that item type to YOUR position (partial match supported)\n /pile -> uses your HELD item type and also moves the held item"; public override bool Invoke(string[] args, Dictionary<string, string> kwargs, out string? error) { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown error = null; if (args.Length != 0 && args[0] == "help") { ChatCommandAPI.Print(((Command)this).Description); return true; } Sorter sorter = null; if ((Object)(object)Plugin.sorterObject != (Object)null) { sorter = Plugin.sorterObject.GetComponent<Sorter>(); } if ((Object)(object)sorter == (Object)null) { try { if ((Object)(object)Plugin.sorterObject == (Object)null) { Plugin.sorterObject = new GameObject("PastaSorter"); Object.DontDestroyOnLoad((Object)(object)Plugin.sorterObject); } sorter = Plugin.sorterObject.GetComponent<Sorter>(); if ((Object)(object)sorter == (Object)null) { sorter = Plugin.sorterObject.AddComponent<Sorter>(); } } catch (Exception ex) { error = "Sorter not initialized yet"; Log.Error("PileCommand failed: " + ex.Message); return false; } } string queryOrNull = ((args != null && args.Length != 0) ? string.Join(" ", args) : null); Log.ConfirmSound(); return sorter.TryStartPileByQueryOrHeld(queryOrNull, force: false, out error); } } internal static class SortPositions { [Serializable] private class PositionsFile { public List<PositionEntry> positions = new List<PositionEntry>(); } [Serializable] private class PositionEntry { public string item = ""; public float x; public float y; public float z; } public static string PositionsPath => Path.Combine(Paths.ConfigPath, "pasta.quicksort.sort.positions.json"); public static void EnsureFileExists() { try { string directoryName = Path.GetDirectoryName(PositionsPath); if (!string.IsNullOrWhiteSpace(directoryName)) { Directory.CreateDirectory(directoryName); } if (!File.Exists(PositionsPath)) { PositionsFile positionsFile = new PositionsFile { positions = new List<PositionEntry> { new PositionEntry { item = "soccer_ball", x = 9f, y = -1.2f, z = -8.4f }, new PositionEntry { item = "whoopie_cushion", x = 9.4f, y = 1.81f, z = -6.3f } } }; string content = JsonConvert.SerializeObject((object)positionsFile, (Formatting)1); if (!TryWriteAllTextAtomic(PositionsPath, content, out string error)) { throw new IOException(error ?? "Failed to write positions file (unknown error)."); } } } catch (Exception ex) { Log.Warning("Failed to create positions file: " + ex.GetType().Name + ": " + ex.Message); Log.Warning("Stack trace: " + ex.StackTrace); } } private static PositionsFile Load(out string? error) { error = null; EnsureFileExists(); try { string text = File.ReadAllText(PositionsPath, Encoding.UTF8); PositionsFile positionsFile = JsonConvert.DeserializeObject<PositionsFile>(text) ?? new PositionsFile(); PositionsFile positionsFile2 = positionsFile; if (positionsFile2.positions == null) { positionsFile2.positions = new List<PositionEntry>(); } return positionsFile; } catch (Exception ex) { error = "Failed to load positions JSON: " + ex.Message; return new PositionsFile(); } } private static bool Save(PositionsFile data, out string? error) { error = null; try { string directoryName = Path.GetDirectoryName(PositionsPath); if (!string.IsNullOrWhiteSpace(directoryName)) { Directory.CreateDirectory(directoryName); } string content = JsonConvert.SerializeObject((object)data, (Formatting)1); if (!TryWriteAllTextAtomic(PositionsPath, content, out error)) { return false; } if (!File.Exists(PositionsPath)) { error = "Positions JSON not found after write. Path='" + PositionsPath + "', CWD='" + Directory.GetCurrentDirectory() + "', ConfigPath='" + Paths.ConfigPath + "', BepInExRoot='" + Paths.BepInExRootPath + "'."; return false; } FileInfo fileInfo = new FileInfo(PositionsPath); if (fileInfo.Length <= 0) { error = $"Positions JSON is empty after write. Path='{PositionsPath}', LastWriteUtc='{fileInfo.LastWriteTimeUtc:o}'."; return false; } return true; } catch (Exception ex) { error = "Failed to save positions JSON: " + ex.Message; return false; } } private static bool TryWriteAllTextAtomic(string path, string content, out string? error) { error = null; try { string directoryName = Path.GetDirectoryName(path); if (!string.IsNullOrWhiteSpace(directoryName)) { Directory.CreateDirectory(directoryName); } string text = path + ".tmp"; File.WriteAllText(text, content, Encoding.UTF8); if (File.Exists(path)) { string destinationBackupFileName = path + ".bak"; try { File.Replace(text, path, destinationBackupFileName, ignoreMetadataErrors: true); } catch { File.Copy(text, path, overwrite: true); File.Delete(text); } } else { File.Move(text, path); } return true; } catch (Exception ex) { error = ex.GetType().Name + ": " + ex.Message + " (Path='" + path + "', CWD='" + Directory.GetCurrentDirectory() + "', ConfigPath='" + Paths.ConfigPath + "')"; return false; } } public static bool TryGet(string itemKey, out Vector3 shipLocalPos, out string? error) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) string itemKey2 = itemKey; shipLocalPos = default(Vector3); itemKey2 = Extensions.NormalizeName(itemKey2); PositionsFile positionsFile = Load(out error); if (error != null) { return false; } PositionEntry positionEntry = positionsFile.positions?.FirstOrDefault((PositionEntry p) => p != null && Extensions.NormalizeName(p.item) == itemKey2); if (positionEntry == null) { return false; } shipLocalPos = new Vector3(positionEntry.x, positionEntry.y, positionEntry.z); return true; } public static bool Set(string itemKey, Vector3 shipLocalPos, out string? error) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) string itemKey2 = itemKey; itemKey2 = Extensions.NormalizeName(itemKey2); PositionsFile positionsFile = Load(out error); if (error != null) { return false; } PositionsFile positionsFile2 = positionsFile; if (positionsFile2.positions == null) { positionsFile2.positions = new List<PositionEntry>(); } PositionEntry positionEntry = positionsFile.positions.FirstOrDefault((PositionEntry p) => p != null && Extensions.NormalizeName(p.item) == itemKey2); if (positionEntry == null) { positionEntry = new PositionEntry { item = itemKey2 }; positionsFile.positions.Add(positionEntry); } positionEntry.item = itemKey2; positionEntry.x = shipLocalPos.x; positionEntry.y = shipLocalPos.y; positionEntry.z = shipLocalPos.z; if (!Save(positionsFile, out error)) { if (error != null) { Log.Warning(error); } return false; } Log.Info($"Saved sort position '{itemKey2}' => (x={shipLocalPos.x:F3}, y={shipLocalPos.y:F3}, z={shipLocalPos.z:F3}) to {PositionsPath}"); return true; } public static bool Remove(string itemKey, out bool removed, out string? error) { string itemKey2 = itemKey; removed = false; itemKey2 = Extensions.NormalizeName(itemKey2); PositionsFile positionsFile = Load(out error); if (error != null) { return false; } if (positionsFile.positions == null || positionsFile.positions.Count == 0) { removed = false; return true; } int count = positionsFile.positions.Count; positionsFile.positions = positionsFile.positions.Where((PositionEntry p) => p != null && Extensions.NormalizeName(p.item) != itemKey2).ToList(); removed = positionsFile.positions.Count != count; if (!Save(positionsFile, out error)) { if (error != null) { Log.Warning(error); } return false; } if (removed) { Log.Info("Removed sort position '" + itemKey2 + "' from " + PositionsPath); } return true; } public static List<(string itemKey, Vector3 shipLocalPos)> ListAll(out string? error) { PositionsFile positionsFile = Load(out error); if (error != null) { return new List<(string, Vector3)>(); } if (positionsFile.positions == null) { return new List<(string, Vector3)>(); } return (from p in positionsFile.positions.Where((PositionEntry p) => p != null && !string.IsNullOrWhiteSpace(p.item)).Select((Func<PositionEntry, (string, Vector3)>)((PositionEntry p) => (Extensions.NormalizeName(p.item), new Vector3(p.x, p.y, p.z)))) orderby p.Item1 select p).ToList(); } } internal static class SortShortcuts { [Serializable] private class ShortcutFile { public List<Shortcut> shortcuts = new List<Shortcut>(); public Dictionary<string, string> aliases = new Dictionary<string, string>(); } [Serializable] private class Shortcut { public int id; public string item = ""; } private static string OldShortcutPath => Path.Combine(Paths.ConfigPath, "pasta.quicksort.sort.shortcuts.json"); public static string ShortcutPath => Path.Combine(Paths.ConfigPath, "pasta.quicksort.sort.bindings.json"); public static void EnsureFileExists() { try { string directoryName = Path.GetDirectoryName(ShortcutPath); if (!string.IsNullOrWhiteSpace(directoryName)) { Directory.CreateDirectory(directoryName); } if (!File.Exists(ShortcutPath) && File.Exists(OldShortcutPath)) { try { File.Copy(OldShortcutPath, ShortcutPath, overwrite: false); Log.Info("Migrated shortcuts file '" + OldShortcutPath + "' => '" + ShortcutPath + "'"); } catch (Exception ex) { Log.Warning("Failed to migrate shortcuts file: " + ex.GetType().Name + ": " + ex.Message); } } if (!File.Exists(ShortcutPath)) { ShortcutFile shortcutFile = new ShortcutFile { shortcuts = new List<Shortcut> { new Shortcut { id = 1, item = "weed_killer" }, new Shortcut { id = 2, item = "shovel" } } }; string content = JsonConvert.SerializeObject((object)shortcutFile, (Formatting)1); if (!TryWriteAllTextAtomic(ShortcutPath, content, out string error)) { throw new IOException(error ?? "Failed to write shortcuts file (unknown error)."); } } } catch (Exception ex2) { Log.Warning("Failed to create shortcuts file: " + ex2.GetType().Name + ": " + ex2.Message); Log.Warning("Stack trace: " + ex2.StackTrace); } } public static bool TryResolve(int id, out string itemKey, out string? error) { itemKey = ""; error = null; EnsureFileExists(); try { string text = File.ReadAllText(ShortcutPath, Encoding.UTF8); ShortcutFile shortcutFile = JsonConvert.DeserializeObject<ShortcutFile>(text); if (shortcutFile?.shortcuts == null) { error = "Shortcuts file is empty or invalid."; return false; } ShortcutFile shortcutFile2 = shortcutFile; if (shortcutFile2.aliases == null) { shortcutFile2.aliases = new Dictionary<string, string>(); } Shortcut shortcut = shortcutFile.shortcuts.FirstOrDefault((Shortcut s) => s != null && s.id == id); if (shortcut == null || string.IsNullOrWhiteSpace(shortcut.item)) { error = $"No shortcut found for {id}."; return false; } itemKey = Extensions.NormalizeName(shortcut.item); return true; } catch (Exception ex) { error = "Failed to load shortcuts JSON: " + ex.Message; return false; } } public static bool SetShortcut(int id, string itemKey, out string? error) { error = null; EnsureFileExists(); if (id <= 0) { error = "Shortcut id must be >= 1."; return false; } itemKey = Extensions.NormalizeName(itemKey); if (string.IsNullOrWhiteSpace(itemKey)) { error = "Invalid item key."; return false; } try { string text = File.ReadAllText(ShortcutPath, Encoding.UTF8); ShortcutFile shortcutFile = JsonConvert.DeserializeObject<ShortcutFile>(text) ?? new ShortcutFile(); ShortcutFile shortcutFile2 = shortcutFile; if (shortcutFile2.shortcuts == null) { shortcutFile2.shortcuts = new List<Shortcut>(); } shortcutFile2 = shortcutFile; if (shortcutFile2.aliases == null) { shortcutFile2.aliases = new Dictionary<string, string>(); } Shortcut shortcut = shortcutFile.shortcuts.FirstOrDefault((Shortcut s) => s != null && s.id == id); if (shortcut == null) { shortcut = new Shortcut { id = id, item = itemKey }; shortcutFile.shortcuts.Add(shortcut); } shortcut.id = id; shortcut.item = itemKey; string content = JsonConvert.SerializeObject((object)shortcutFile, (Formatting)1); if (!TryWriteAllTextAtomic(ShortcutPath, content, out error)) { return false; } return true; } catch (Exception ex) { error = "Failed to save shortcuts JSON: " + ex.Message; return false; } } public static bool TryResolveAlias(string alias, out string itemKey, out string? error) { itemKey = ""; error = null; EnsureFileExists(); alias = Extensions.NormalizeName(alias); if (string.IsNullOrWhiteSpace(alias)) { error = "Invalid alias."; return false; } try { string text = File.ReadAllText(ShortcutPath, Encoding.UTF8); ShortcutFile shortcutFile = JsonConvert.DeserializeObject<ShortcutFile>(text); if (shortcutFile == null) { error = "Shortcuts file is empty or invalid."; return false; } ShortcutFile shortcutFile2 = shortcutFile; if (shortcutFile2.aliases == null) { shortcutFile2.aliases = new Dictionary<string, string>(); } if (!shortcutFile.aliases.TryGetValue(alias, out string value) || string.IsNullOrWhiteSpace(value)) { error = "No alias found for '" + alias + "'."; return false; } itemKey = Extensions.NormalizeName(value); return true; } catch (Exception ex) { error = "Failed to load shortcuts JSON: " + ex.Message; return false; } } public static bool BindAlias(string alias, string itemKey, out string? error) { error = null; EnsureFileExists(); alias = Extensions.NormalizeName(alias); itemKey = Extensions.NormalizeName(itemKey); if (string.IsNullOrWhiteSpace(alias)) { error = "Invalid alias."; return false; } if (string.IsNullOrWhiteSpace(itemKey)) { error = "Invalid item key."; return false; } try { string text = File.ReadAllText(ShortcutPath, Encoding.UTF8); ShortcutFile shortcutFile = JsonConvert.DeserializeObject<ShortcutFile>(text) ?? new ShortcutFile(); ShortcutFile shortcutFile2 = shortcutFile; if (shortcutFile2.shortcuts == null) { shortcutFile2.shortcuts = new List<Shortcut>(); } shortcutFile2 = shortcutFile; if (shortcutFile2.aliases == null) { shortcutFile2.aliases = new Dictionary<string, string>(); } shortcutFile.aliases[alias] = itemKey; string content = JsonConvert.SerializeObject((object)shortcutFile, (Formatting)1); if (!TryWriteAllTextAtomic(ShortcutPath, content, out error)) { return false; } return true; } catch (Exception ex) { error = "Failed to save shortcuts JSON: " + ex.Message; return false; } } public static bool RemoveShortcut(int id, out bool removed, out string? error) { removed = false; error = null; EnsureFileExists(); if (id <= 0) { error = "Shortcut id must be >= 1."; return false; } try { string text = File.ReadAllText(ShortcutPath, Encoding.UTF8); ShortcutFile shortcutFile = JsonConvert.DeserializeObject<ShortcutFile>(text) ?? new ShortcutFile(); ShortcutFile shortcutFile2 = shortcutFile; if (shortcutFile2.shortcuts == null) { shortcutFile2.shortcuts = new List<Shortcut>(); } shortcutFile2 = shortcutFile; if (shortcutFile2.aliases == null) { shortcutFile2.aliases = new Dictionary<string, string>(); } int count = shortcutFile.shortcuts.Count; shortcutFile.shortcuts = shortcutFile.shortcuts.Where((Shortcut s) => s != null && s.id != id).ToList(); removed = shortcutFile.shortcuts.Count != count; string content = JsonConvert.SerializeObject((object)shortcutFile, (Formatting)1); if (!TryWriteAllTextAtomic(ShortcutPath, content, out error)) { return false; } return true; } catch (Exception ex) { error = "Failed to save shortcuts JSON: " + ex.Message; return false; } } public static bool RemoveAlias(string alias, out bool removed, out string? error) { removed = false; error = null; EnsureFileExists(); alias = Extensions.NormalizeName(alias); if (string.IsNullOrWhiteSpace(alias)) { error = "Invalid alias."; return false; } try { string text = File.ReadAllText(ShortcutPath, Encoding.UTF8); ShortcutFile shortcutFile = JsonConvert.DeserializeObject<ShortcutFile>(text) ?? new ShortcutFile(); ShortcutFile shortcutFile2 = shortcutFile; if (shortcutFile2.shortcuts == null) { shortcutFile2.shortcuts = new List<Shortcut>(); } shortcutFile2 = shortcutFile; if (shortcutFile2.aliases == null) { shortcutFile2.aliases = new Dictionary<string, string>(); } removed = shortcutFile.aliases.Remove(alias); string content = JsonConvert.SerializeObject((object)shortcutFile, (Formatting)1); if (!TryWriteAllTextAtomic(ShortcutPath, content, out error)) { return false; } return true; } catch (Exception ex) { error = "Failed to save shortcuts JSON: " + ex.Message; return false; } } public static List<(string alias, string itemKey)> ListAliases(out string? error) { error = null; EnsureFileExists(); try { string text = File.ReadAllText(ShortcutPath, Encoding.UTF8); ShortcutFile shortcutFile = JsonConvert.DeserializeObject<ShortcutFile>(text); if (shortcutFile?.aliases == null) { return new List<(string, string)>(); } return (from kv in shortcutFile.aliases where !string.IsNullOrWhiteSpace(kv.Key) && !string.IsNullOrWhiteSpace(kv.Value) select (Extensions.NormalizeName(kv.Key), Extensions.NormalizeName(kv.Value)) into x orderby x.Item1 select x).ToList(); } catch (Exception ex) { error = "Failed to load shortcuts JSON: " + ex.Message; return new List<(string, strin