using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BetterBuddy; using HarmonyLib; using Il2Cpp; using Il2CppAI; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppKeepsake; using Il2CppKeepsake.CriticalComponentInteraction; using Il2CppKeepsake.DefenceComponent; using Il2CppKeepsake.Framework.Networking.Data; using Il2CppKeepsake.Framework.Networking.Logic; using Il2CppKeepsake.GameplayFeatures.Assembler; using Il2CppKeepsake.GeneratedItems; using Il2CppKeepsake.HyperSpace.GameplayFeatures.AI.BuddyBot; using Il2CppKeepsake.HyperSpace.GameplayFeatures.AI.BuddyBot.BuddyBotManifestations.HullRepair; using Il2CppKeepsake.HyperSpace.GameplayFeatures.AI.BuddyBot.BuddyBotManifestations.ScannerStation; using Il2CppKeepsake.HyperSpace.GameplayFeatures.AI.BuddyBot.BuddyBotManifestations.Shared; using Il2CppKeepsake.HyperSpace.GameplayFeatures.GUI.Vassist.VAssistRework; using Il2CppKeepsake.HyperSpace.GameplayFeatures.Triggers; using Il2CppLegacyNetwork; using Il2CppPathFinding; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppSystem.Collections.ObjectModel; using Il2CppSystem.Reflection; using Il2CppUniRx; using Il2CppUtilities.Enums; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using Unity.Netcode; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Main), "Better Buddy", "1.1.0", "Nemo", null)] [assembly: MelonGame("Keepsake Games", "Jump Space")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("BetterBuddy")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("BetterBuddy")] [assembly: AssemblyTitle("BetterBuddy")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace BetterBuddy { internal static class Buddy { private sealed class Job { internal string Name; internal bool Idle; } private sealed class Labels { internal string Name; internal string Missing; internal string Read; internal string Write; internal string Reread; internal string Held; internal string Stomp; } private static AI_BehaviourSelector_Base s_Selector; private static IntPtr s_SelectorFor; private static BuddyBot_OnFootController s_OnFoot; private static BuddyBot_HullRepairController s_HullBody; private static GameObject s_Target; private static GameObject s_Body; private static int s_BodyFrame = -1; private static readonly Dictionary s_Jobs = new Dictionary(); internal const float BlockingRank = 10f; private static readonly Labels OnFootLabels = new Labels { Name = "m_OnFootTarget", Missing = "show:missing:onfoot", Read = "show:read:onfoot", Write = "show:write:onfoot", Reread = "show:reread:onfoot", Held = "show:onfoot", Stomp = "show:stomp:onfoot" }; private static readonly Labels HullLabels = new Labels { Name = "m_HullRepairTargetPoint", Missing = "show:missing:hull", Read = "show:read:hull", Write = "show:write:hull", Reread = "show:reread:hull", Held = "show:hull", Stomp = "show:stomp:hull" }; internal static AI_Controller_Buddbybot Controller { get { try { AI_Controller_Buddbybot lastController = Repair.LastController; return ((Object)(object)lastController == (Object)null) ? null : lastController; } catch { return null; } } } internal static BuddybotBlackboardData Board => Trace.Read("buddy.board", delegate { AI_Controller_Buddbybot controller = Controller; return (controller == null) ? null : controller.m_BuddybotBlackboardData; }, null); internal static bool CanCraft { get { BuddybotBlackboardData board = Board; NetworkReactiveProperty flag = ((board != null) ? board.m_CanCraft : null); if (flag == null) { Trace.Every("buddy:nocancraft", 30f, "buddy: m_CanCraft is not on the blackboard, so the assembler is taken as open to him"); return true; } bool flag2 = Trace.Read("buddy:cancraft", () => flag.Value, fallback: true); Trace.Change("buddy:cancraft", "the assembler is " + (flag2 ? "his to use" : "off limits")); return flag2; } } internal static GameObject Body { get { int frameCount = Time.frameCount; if (frameCount == s_BodyFrame) { return s_Body; } s_BodyFrame = frameCount; FindBodies(); s_Body = Trace.Read("buddy.art", delegate { BuddyBot_OnFootController obj = s_OnFoot; return (obj == null) ? null : obj.m_ArtObject; }, null) ?? Trace.Read("buddy.onfootgo", delegate { BuddyBot_OnFootController obj = s_OnFoot; return (obj == null) ? null : ((Component)obj).gameObject; }, null) ?? Trace.Read("buddy.brain", delegate { AI_Controller_Buddbybot controller = Controller; return (controller == null) ? null : ((Component)controller).gameObject; }, null); return s_Body; } } internal static Vector3 Position { get { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) GameObject body = Body; if ((Object)(object)body == (Object)null) { return Vector3.zero; } return Trace.Read("buddy.pos", () => body.transform.position, Vector3.zero); } } internal static GameObject Actor => Trace.Read("buddy.actor", delegate { AI_Controller_Buddbybot controller = Controller; return (controller == null) ? null : ((Component)controller).gameObject; }, null); internal static Vector3 ShownPosition { get { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) FindBodies(); if ((Object)(object)s_HullBody == (Object)null) { return Position; } return Trace.Read("buddy.shownpos", () => ((Component)s_HullBody).transform.position, Vector3.zero); } } internal static float Blocker { get; private set; } private static void FindBodies() { } internal static void TakeOnFootBody(BuddyBot_OnFootController body) { s_OnFoot = body; s_Body = null; s_BodyFrame = -1; Trace.Say("buddy: the on-foot body is " + Trace.Name((Component)(object)body)); } internal static void TakeHullBody(BuddyBot_HullRepairController body) { s_HullBody = body; Trace.Say("buddy: the hull repair body is " + Trace.Name((Component)(object)body)); } internal static void Forget() { s_Selector = null; s_SelectorFor = IntPtr.Zero; s_OnFoot = null; s_HullBody = null; s_Target = null; s_Body = null; s_BodyFrame = -1; s_Jobs.Clear(); } internal static bool Free(out string doing) { doing = "nothing"; AI_Controller_Buddbybot controller = Controller; if ((Object)(object)controller == (Object)null) { doing = "no Buddy aboard"; return false; } if (s_SelectorFor != ((Il2CppObjectBase)controller).Pointer) { try { s_Selector = ((Component)controller).GetComponent(); } catch { s_Selector = null; } s_SelectorFor = ((Il2CppObjectBase)controller).Pointer; } AI_BehaviourSelector_Base val = s_Selector; if ((Object)(object)val == (Object)null) { return true; } List internal_CurrentBehaviours; try { internal_CurrentBehaviours = val.m_Internal_CurrentBehaviours; } catch { return true; } if (internal_CurrentBehaviours == null || internal_CurrentBehaviours.Count == 0) { return true; } for (int i = 0; i < internal_CurrentBehaviours.Count; i++) { AI_Behaviour_Base val2 = internal_CurrentBehaviours[i]; if ((Object)(object)val2 == (Object)null) { continue; } Job job = Describe(val2); if (job != null) { float num = Rank(val2); if (!(num < 10f) && (num != float.MaxValue || !job.Idle)) { doing = job.Name; Blocker = num; return false; } } } return true; } private static Job Describe(AI_Behaviour_Base behaviour) { IntPtr pointer; try { pointer = ((Il2CppObjectBase)behaviour).Pointer; } catch { return null; } if (s_Jobs.TryGetValue(pointer, out var value)) { return value; } string name; try { name = ((MemberInfo)((Object)behaviour).GetIl2CppType()).Name; } catch { return null; } Job job = new Job { Name = name, Idle = Idle(name) }; s_Jobs[pointer] = job; return job; } private static float Rank(AI_Behaviour_Base behaviour) { try { return behaviour.Incentive; } catch { return float.MaxValue; } } private static bool Idle(string name) { if (!name.Contains("Idle") && !name.Contains("OnFoot") && !name.Contains("Follow")) { return name == "AI_Behaviour_BuddyBot_Base"; } return true; } internal static float DistanceTo(GameObject target) { if ((Object)(object)target == (Object)null) { return float.MaxValue; } GameObject body = Body; if ((Object)(object)body == (Object)null) { return float.MaxValue; } return Trace.Read("buddy.distance", () => Vector3.Distance(body.transform.position, target.transform.position), float.MaxValue); } internal static void Show(GameObject target) { if ((Object)(object)target == (Object)null) { return; } if (s_Target != target) { s_Target = target; Trace.Say($"show: putting Buddy at {Trace.Name((Object)(object)target)}, {DistanceTo(target):0.0} m from his on-foot body now"); } BuddybotBlackboardData board = Board; if ((Object)(object)board == (Object)null) { return; } WriteTarget(board.m_OnFootTarget, target, OnFootLabels); Afoot(afoot: true); WriteTarget(board.m_HullRepairTargetPoint, target, HullLabels); FindBodies(); if ((Object)(object)s_HullBody != (Object)null) { if (Trace.On) { Trace.Once("show:hullbody", "show: hull repair body is " + Trace.Name((Component)(object)s_HullBody)); } Trace.Try("show:snap", delegate { s_HullBody.SnapToPoint(); }); } else { Trace.Once("show:nohullbody", "show: no hull repair body exists. that body is the outside-the-hull drone, so an interior job has to ride on the on-foot body instead"); } if (Trace.On) { float value = Trace.Read("show:shownto", () => Vector3.Distance(ShownPosition, target.transform.position), -1f); Trace.Every("show:distances", 2f, $"show: {Trace.Name((Object)(object)target)} is {value:0.0} m from the hull body and {DistanceTo(target):0.0} m from the on-foot body"); } } private static void Afoot(bool afoot) { BuddybotBlackboardData board = Board; NetworkReactiveProperty flag = ((board != null) ? board.m_BuddyIsOnFoot : null); if (flag == null) { Trace.Once("show:noafoot", "show: m_BuddyIsOnFoot is null on the blackboard"); } else if (Trace.Read("show:afootread", () => flag.Value, fallback: false) != afoot) { Trace.Try("show:afootwrite", delegate { flag.Value = afoot; }); bool flag2 = Trace.Read("show:afootreread", () => flag.Value, fallback: false); Trace.Change("show:afoot", $"m_BuddyIsOnFoot={flag2}"); if (flag2 != afoot) { Trace.Every("show:afootstomp", 5f, $"show: m_BuddyIsOnFoot did not hold. wrote {afoot}, read {flag2}. " + "the game owns this flag"); } } } private static void WriteTarget(NetworkReactiveProperty property, GameObject target, Labels label) { if (property == null) { Trace.Once(label.Missing, "show: " + label.Name + " is null on the blackboard"); return; } if (Trace.Read(label.Read, () => property.Value, null) != target) { Trace.Try(label.Write, delegate { property.Value = target; }); } if (Trace.On) { GameObject val = Trace.Read(label.Reread, () => property.Value, null); Trace.Change(label.Held, Trace.Name((Object)(object)val)); if (val != target) { Trace.Every(label.Stomp, 5f, $"show: {label.Name} did not hold our value. wrote {Trace.Name((Object)(object)target)}, read back {Trace.Name((Object)(object)val)}. " + "the game owns this property"); } } } internal static void Release() { if ((Object)(object)s_Target == (Object)null) { return; } s_Target = null; BuddybotBlackboardData board = Board; if (!((Object)(object)board == (Object)null)) { Trace.Try("show:clear:onfoot", delegate { board.m_OnFootTarget.Value = null; }); Trace.Try("show:clear:hull", delegate { board.m_HullRepairTargetPoint.Value = null; }); Afoot(afoot: false); } } internal static void Busy(bool repairing) { BuddybotBlackboardData board = Board; NetworkReactiveProperty flag = ((board != null) ? board.m_BuddyIsRepairing : null); if (flag == null) { Trace.Once("flag:missing", "flags: m_BuddyIsRepairing is null on the blackboard"); } else if (Trace.Read("flag:read", () => flag.Value, fallback: false) != repairing) { Trace.Try("flag:write", delegate { flag.Value = repairing; }); bool flag2 = Trace.Read("flag:reread", () => flag.Value, fallback: false); Trace.Change("flag:isrepairing", $"{flag2}"); if (flag2 != repairing) { Trace.Once("flag:stomp", $"flags: m_BuddyIsRepairing did not hold. wrote {repairing}, read {flag2}"); } } } } internal static class Cfg { internal const int CrewLimit = 99; internal const float WorkUntil = 1.1f; internal const float ThinkFast = 1f; internal const float ClaimTimeout = 30f; internal const float StockEnterHealth = 0.5f; internal const float StockThink = 5f; internal static MelonPreferences_Entry BetterRepairs; internal static MelonPreferences_Entry MaxCrew; internal static MelonPreferences_Entry BetterAim; internal static MelonPreferences_Entry JobPriorities; internal static MelonPreferences_Entry EnhancementsWheel; internal static MelonPreferences_Entry FixHazards; internal static MelonPreferences_Entry ShowAtWork; internal static MelonPreferences_Entry CraftParts; internal static MelonPreferences_Entry ChargeForRepairs; internal static MelonPreferences_Entry Announce; private static bool s_IsHost = true; private static float s_HostCheckedAt = -99f; internal static bool IsHost { get { float unscaledTime = Time.unscaledTime; if (unscaledTime - s_HostCheckedAt < 1f) { return s_IsHost; } s_HostCheckedAt = unscaledTime; s_IsHost = ReadHost(); return s_IsHost; } } private static bool ReadHost() { NetworkManager singleton = NetworkManager.Singleton; bool flag = (Object)(object)singleton == (Object)null || singleton.IsServer; try { bool flag2 = NetworkManager.IsServer(); bool flag3 = NetworkManager.IsClient(); Trace.Change("host", $"legacy says server={flag2} client={flag3}, unity says {(((Object)(object)singleton == (Object)null) ? "no singleton" : $"IsServer={singleton.IsServer}")}, using {flag}"); if ((flag2 || flag3) && flag2 != flag) { Trace.Once("host:disagree", $"host: the two network layers disagree. legacy server={flag2}, this mod is acting as host={flag}. if a crew member sees the " + "Buddy Enhancements prompt and cannot use it, this is why"); } } catch (Exception ex) { Trace.Once("host:legacyfailed", "host: the legacy network layer would not answer (" + ex.GetType().Name + ")"); } return flag; } internal static void Init() { MelonPreferences_Category obj = MelonPreferences.CreateCategory("BetterBuddy", "Better Buddy"); BetterRepairs = obj.CreateEntry("BetterRepairs", true, (string)null, "Buddy repairs until the hull is whole, and re-decides every second.", false, false, (ValueValidator)null, (string)null); MaxCrew = obj.CreateEntry("MaxCrew", true, (string)null, "Buddy keeps doing every job at any crew size.", false, false, (ValueValidator)null, (string)null); BetterAim = obj.CreateEntry("BetterAim", true, (string)null, "Buddy stops missing on purpose at the combat station.", false, false, (ValueValidator)null, (string)null); JobPriorities = obj.CreateEntry("JobPriorities", true, (string)null, "Sensible job order: core repairs and ammo outrank idling.", false, false, (ValueValidator)null, (string)null); EnhancementsWheel = obj.CreateEntry("EnhancementsWheel", true, (string)null, "Buddy carries a third interaction that opens the switches. Turn this off if his other prompts do not show.", false, false, (ValueValidator)null, (string)null); FixHazards = obj.CreateEntry("FixHazards", true, (string)null, "Buddy clears leaks, radiator and part failures, fires, and refits blown shield plates, while he has no other job.", false, false, (ValueValidator)null, (string)null); ShowAtWork = obj.CreateEntry("ShowAtWork", true, (string)null, "Buddy appears at a hazard and stands there while he clears it. Turn this off to let him work unseen.", false, false, (ValueValidator)null, (string)null); CraftParts = obj.CreateEntry("CraftParts", true, (string)null, "Buddy builds a shield plate at the assembler when the ship has none, the way he already builds a hull repair kit. The crew pays the normal materia price.", false, false, (ValueValidator)null, (string)null); ChargeForRepairs = obj.CreateEntry("ChargeForRepairs", true, (string)null, "Every fix Buddy makes costs the crew materia, at the assembler price of the extinguisher or repair tool a crew member would have carried. Nothing is built. Turn this off to let him repair for free.", false, false, (ValueValidator)null, (string)null); Announce = obj.CreateEntry("Announce", true, (string)null, "Buddy says what he is doing and what it cost, on the same label the game uses for his own lines. Host screen only.", false, false, (ValueValidator)null, (string)null); } internal static void Save() { MelonPreferences.Save(); } } internal static class Craft { private const float Interval = 15f; private static float s_TriedAt = -99f; internal static bool PlateFor(ShieldUnitInteraction slot) { if (!Cfg.CraftParts.Value || (Object)(object)slot == (Object)null) { return false; } if (!Buddy.CanCraft) { Trace.Change("craft:blocked", "craft: the crew have taken the assembler off him, so he fits only what is already aboard"); return false; } float unscaledTime = Time.unscaledTime; if (unscaledTime - s_TriedAt < 15f) { return false; } Assembler assembler = Ship.Assembler(); if ((Object)(object)assembler == (Object)null) { Trace.Once("craft:noassembler", "craft: no assembler aboard, so nothing can be built"); return false; } AssemblerBlackboardData board = Trace.Read("craft:board", () => assembler.m_BlackboardData, null); ReactiveCollection items = Trace.Read("craft:items", delegate { AssemblerBlackboardData obj2 = board; return (obj2 == null) ? null : obj2.m_AvailableItems; }, null); if (items == null) { return false; } int count; try { count = ((Collection)(object)items).Count; } catch { return false; } for (int i = 0; i < count; i++) { Craftable blueprint = Trace.Read("craft:entry", () => ((Collection)(object)items)[i], null); if (!((Object)(object)blueprint == (Object)null)) { ScriptableObject val = Trace.Read("craft:object", () => blueprint.m_CraftableObject, null); PickupableItem_Data data = ((val != null) ? ((Il2CppObjectBase)val).TryCast() : null); if (!((Object)(object)data == (Object)null) && Trace.Read("craft:allowed", () => ((PickupableInteract)slot).IsItemAllowed(data), fallback: false)) { s_TriedAt = unscaledTime; return Build(assembler, blueprint, data); } } } Trace.Once("craft:nofit:" + Trace.Name((Component)(object)slot), $"craft: the assembler offers {count} blueprints and {Trace.Name((Component)(object)slot)} " + "accepts none of them. this slot cannot be filled by building one"); s_TriedAt = unscaledTime; return false; } private static bool Build(Assembler assembler, Craftable blueprint, PickupableItem_Data data) { //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Expected O, but got Unknown //IL_025c: Unknown result type (might be due to invalid IL or missing references) string text = Trace.Read("craft:name", () => blueprint.DisplayName, "?"); int num = Materia(); GameObject crafter = Buddy.Actor ?? Trace.Read("craft:crafter", delegate { PlayerController obj = Ship.HostPlayer(); return (obj == null) ? null : ((Component)obj).gameObject; }, null); int cost = Trace.Read("craft:price", () => blueprint.GetMateriaCraftCost(crafter), -1); if (cost < 0) { int num2 = Trace.Read("craft:baseprice", () => blueprint.m_MateriaCraftCost, -1); Trace.Once("craft:noprice:" + text, $"craft: the assembler would not price {text}, falling back to its base cost of {num2}"); cost = num2; } if (cost < 0) { Trace.Say("craft: " + text + " reports no price, so it stays unbuilt"); return false; } if (num < cost) { Trace.Change("craft:broke", $"craft: {text} costs {cost} materia and the ship holds {num}. waiting"); return false; } Trace.Say($"craft: building {text} for {cost} materia, ship holds {num}"); string guid = Trace.Read("craft:assetguid", () => blueprint.AssetGUID, null); if (string.IsNullOrEmpty(guid)) { Trace.Say("craft: " + text + " has no asset guid, so the assembler cannot be asked"); return false; } GeneratedItem generated = new GeneratedItem(); Trace.Try("craft:guid", delegate { generated.m_BlueprintGuid = guid; }); ServerRpcParams rpc = default(ServerRpcParams); if (!Trace.Try("craft:assembler", delegate { //IL_0019: Unknown result type (might be due to invalid IL or missing references) assembler.CraftObjectServerRpc(guid, cost, generated, rpc); })) { return false; } Trace.Say("craft: the assembler is building " + text + ". the shield job takes it from there"); return true; } private static int Materia() { return Trace.Read("craft:materia", () => ShipMateriaController.GetShipMateriaAmount, -1); } } internal static class Interop { private static class Cached where T : Il2CppObjectBase { internal static IntPtr Class; } private static IntPtr s_BuddyOrderClass; private static HashSet s_BuddyBehaviourClasses; internal static IntPtr ClassOf() where T : Il2CppObjectBase { RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); return Il2CppClassPointerStore.NativeClassPtr; } internal static bool IsBuddyOrderInteraction(Il2CppObjectBase o) { if (o == null) { return false; } if (s_BuddyOrderClass == IntPtr.Zero) { s_BuddyOrderClass = Interop.ClassOf(); if (s_BuddyOrderClass == IntPtr.Zero) { return false; } } IntPtr pointer = o.Pointer; if (pointer != IntPtr.Zero) { return IL2CPP.il2cpp_object_get_class(pointer) == s_BuddyOrderClass; } return false; } internal static bool IsBuddyBehaviour(Il2CppObjectBase o) { if (o == null) { return false; } HashSet hashSet = s_BuddyBehaviourClasses ?? (s_BuddyBehaviourClasses = BuildBehaviourClasses()); if (hashSet == null) { return false; } IntPtr pointer = o.Pointer; if (pointer != IntPtr.Zero) { return hashSet.Contains(IL2CPP.il2cpp_object_get_class(pointer)); } return false; } private static HashSet BuildBehaviourClasses() { HashSet hashSet = new HashSet { Interop.ClassOf(), Interop.ClassOf(), Interop.ClassOf(), Interop.ClassOf(), Interop.ClassOf(), Interop.ClassOf(), Interop.ClassOf(), Interop.ClassOf(), Interop.ClassOf(), Interop.ClassOf() }; if (!hashSet.Contains(IntPtr.Zero)) { return hashSet; } return null; } internal static bool IsBehaviour(Il2CppObjectBase o) where T : Il2CppObjectBase { if (o == null) { return false; } IntPtr intPtr = Cached.Class; if (intPtr == IntPtr.Zero) { intPtr = (Cached.Class = ClassOf()); if (intPtr == IntPtr.Zero) { return false; } } IntPtr pointer = o.Pointer; if (pointer != IntPtr.Zero) { return IL2CPP.il2cpp_object_get_class(pointer) == intPtr; } return false; } internal static void Report() { IntPtr intPtr = Interop.ClassOf(); HashSet hashSet = BuildBehaviourClasses(); Instance log = Main.Log; if (log != null) { log.Msg($"classes: order interaction=0x{intPtr.ToInt64():X}, behaviours={((hashSet == null) ? "incomplete" : (hashSet.Count + " of 10"))}"); } } } public class Main : MelonMod { internal static Instance Log; private bool _reported; public override void OnInitializeMelon() { Log = ((MelonBase)this).LoggerInstance; Cfg.Init(); Log.Msg($"repairs={Cfg.BetterRepairs.Value} crew={Cfg.MaxCrew.Value} aim={Cfg.BetterAim.Value} jobs={Cfg.JobPriorities.Value} hazards={Cfg.FixHazards.Value} show={Cfg.ShowAtWork.Value} craft={Cfg.CraftParts.Value} charge={Cfg.ChargeForRepairs.Value} announce={Cfg.Announce.Value}"); } private void ReportPatches() { try { int num = 0; foreach (MethodBase allPatchedMethod in Harmony.GetAllPatchedMethods()) { Patches patchInfo = Harmony.GetPatchInfo(allPatchedMethod); if (patchInfo != null && (Ours(patchInfo.Postfixes) || Ours(patchInfo.Prefixes))) { num++; } } Log.Msg($"patches: {num} methods detoured"); } catch (Exception ex) { Log.Warning("patches: could not read the patch list: " + ex.Message); } } private static bool Ours(IList patches) { if (patches == null) { return false; } for (int i = 0; i < patches.Count; i++) { if (patches[i].owner != null && patches[i].owner.Contains("BetterBuddy")) { return true; } } return false; } public override void OnUpdate() { if (!_reported) { _reported = true; Interop.Report(); ReportPatches(); } Interaction.Tick(); Work.Tick(); Say.Tick(); } } [HarmonyPatch(typeof(AI_Behaviour_Buddybot_CombatStation), "ToggleMissing")] internal static class Patch_NeverMiss { private static bool Prefix(AI_Behaviour_Buddybot_CombatStation __instance) { if (!Cfg.BetterAim.Value || !Cfg.IsHost) { return true; } BuddybotBlackboardData buddybotBlackboardData = ((AI_Behaviour_BuddyBot_Base)__instance).BuddybotBlackboardData; NetworkReactiveProperty val = ((buddybotBlackboardData != null) ? buddybotBlackboardData.m_ShouldMissWithCombatStation : null); if (val == null) { return true; } val.Value = false; return false; } } internal static class Gate { internal static void Apply(Il2CppObjectBase instance, ref int result) { if (Cfg.MaxCrew.Value && Interop.IsBuddyBehaviour(instance)) { result = 99; } } } [HarmonyPatch(typeof(AI_Behaviour_BuddyBot_RepairHull), "get_MaxPlayerRequrement")] internal static class Patch_GateReturnsTwo { private static void Postfix(Il2CppObjectBase __instance, ref int __result) { Gate.Apply(__instance, ref __result); } } [HarmonyPatch(typeof(AI_Behaviour_BuddyBot_ScanObjects), "get_MaxPlayerRequrement")] internal static class Patch_GateReturnsOne { private static void Postfix(Il2CppObjectBase __instance, ref int __result) { Gate.Apply(__instance, ref __result); } } internal static class Interaction { internal static BuddybotOrderController Controller; private static bool s_WarnedOnce; internal static bool InQuery; private static bool s_NotedClaim; private static bool s_NotedRow; private static float s_RowAt; private static bool s_NotedStuck; private static bool s_NotedMiss; internal static bool WheelActive { get { if (Cfg.EnhancementsWheel.Value) { return Cfg.IsHost; } return false; } } internal static void WarnOnce(Exception e) { if (!s_WarnedOnce) { s_WarnedOnce = true; Instance log = Main.Log; if (log != null) { log.Warning($"a Buddy prompt patch failed and was ignored: {e}"); } } } internal static void NoteClaim() { if (!s_NotedClaim) { s_NotedClaim = true; Instance log = Main.Log; if (log != null) { log.Msg("claim: told the game Buddy carries a third interaction"); } } } internal static void NoteRow() { if (!s_NotedRow) { s_NotedRow = true; s_RowAt = Time.unscaledTime; Instance log = Main.Log; if (log != null) { log.Msg("row: drew the third prompt row on Buddy"); } } } internal static Interactable_Base Read(Func read) { try { return read(); } catch { return null; } } internal static void NoteMiss(Interactable_Base approved, Interactable_Base predicted, Interactable_Base eligible) { if (!s_NotedMiss) { s_NotedMiss = true; Instance log = Main.Log; if (log != null) { log.Warning("activation: the third interaction fired but none of the three interaction " + $"slots held Buddy. approved={Name(approved)} predicted={Name(predicted)} " + "eligible=" + Name(eligible) + ". the wheel stays shut when this happens."); } } static string Name(Interactable_Base i) { try { return ((Object)(object)i == (Object)null) ? "none" : ((Object)((Component)i).gameObject).name; } catch { return ""; } } } internal static void CheckStuck() { if (!s_NotedStuck && s_NotedRow && !s_NotedClaim && !(Time.unscaledTime - s_RowAt < 30f)) { s_NotedStuck = true; Instance log = Main.Log; if (log != null) { log.Warning("the Buddy Enhancements prompt has been on screen for 30 seconds and the game has never asked whether Buddy carries a third interaction. drawing the prompt does not touch the input, so this points at the TertiaryInteract action having no key bound. check that binding in the game's own controls, or set EnhancementsWheel=false in MelonPreferences.cfg to remove the prompt and keep the four job changes."); } } } internal static void Release(TertiaryInteractAbility ability, bool applySelection) { Patch_TertiaryOpen.ClaimedByUs = false; Patch_TertiaryOpen.Ability = null; try { try { if (applySelection) { Wheel.CloseAndApply(); } else { Wheel.Cancel(); } } finally { if (ability != null) { InteractionController interactionController = ability.m_InteractionController; if (interactionController != null) { interactionController.ForceStopInteract(); } } } } catch (Exception ex) { Instance log = Main.Log; if (log != null) { log.Warning("tertiary close failed: " + ex.Message); } } } internal static void Tick() { CheckStuck(); if (Patch_TertiaryOpen.ClaimedByUs && !(Time.unscaledTime - Patch_TertiaryOpen.ClaimedAt < 30f)) { Instance log = Main.Log; if (log != null) { log.Warning($"the third interaction stood for {30f:0} seconds, dropping it"); } Release(Patch_TertiaryOpen.Ability, applySelection: false); } } } [HarmonyPatch(typeof(Interactable_Base), "get_IsTertiaryInteraction")] internal static class Patch_ClaimTertiary { private static void Postfix(Il2CppObjectBase __instance, ref bool __result) { if (!(!Interaction.InQuery | __result) && Interop.IsBuddyOrderInteraction(__instance) && Interaction.WheelActive) { __result = true; Interaction.NoteClaim(); } } } [HarmonyPatch(typeof(TertiaryInteractAbility), "ShouldActivate")] internal static class Patch_TertiaryQuery { private static void Prefix() { Interaction.InQuery = true; } private static void Postfix() { Interaction.InQuery = false; } } [HarmonyPatch(typeof(TertiaryInteractAbility), "OnActivate")] internal static class Patch_TertiaryOpen { internal static bool ClaimedByUs; internal static TertiaryInteractAbility Ability; internal static float ClaimedAt; private static void Postfix(TertiaryInteractAbility __instance) { if (!Interaction.WheelActive) { return; } InteractionController ic = __instance.m_InteractionController; if ((Object)(object)ic == (Object)null) { return; } Interactable_Base val = Interaction.Read(() => ic.ApprovedInteractable); Interactable_Base val2 = Interaction.Read(() => ic.PredictedInteractable); Interactable_Base val3 = Interaction.Read(() => ic.BestEligibleInteraction); if (!Interop.IsBuddyOrderInteraction((Il2CppObjectBase)(object)val) && !Interop.IsBuddyOrderInteraction((Il2CppObjectBase)(object)val2) && !Interop.IsBuddyOrderInteraction((Il2CppObjectBase)(object)val3)) { Interaction.NoteMiss(val, val2, val3); return; } ClaimedByUs = true; Ability = __instance; ClaimedAt = Time.unscaledTime; Instance log = Main.Log; if (log != null) { log.Msg("activation: opening the enhancements wheel"); } try { Wheel.Open(); } catch (Exception ex) { Instance log2 = Main.Log; if (log2 != null) { log2.Warning("could not open the enhancements wheel: " + ex.Message); } } } } [HarmonyPatch(typeof(TertiaryInteractAbility), "OnDeactivate")] internal static class Patch_TertiaryClose { private static void Prefix(TertiaryInteractAbility __instance) { if (Patch_TertiaryOpen.ClaimedByUs) { Interaction.Release(__instance, applySelection: true); } } } [HarmonyPatch(typeof(RadialMenu), "Show")] internal static class Patch_RadialShow { private static void Prefix() { if (!Wheel.Opening) { Wheel.Disown(); } } } [HarmonyPatch(typeof(VAssist_Interactable_Base), "get_TertiaryInteractIsButtonHold")] internal static class Patch_TertiaryIsHold { private static void Postfix(VAssist_Interactable_Base __instance, ref bool __result) { if (__result) { return; } try { if (Interop.IsBuddyOrderInteraction((Il2CppObjectBase)(object)__instance.Interactable) && Interaction.WheelActive) { __result = true; } } catch (Exception e) { Interaction.WarnOnce(e); } } } [HarmonyPatch(typeof(VAssist_Interactable_Base), "get_ShouldShowTertiaryInteract")] internal static class Patch_ShowTertiaryRow { private static void Postfix(VAssist_Interactable_Base __instance, ref bool __result) { if (__result) { return; } try { if (Interop.IsBuddyOrderInteraction((Il2CppObjectBase)(object)__instance.Interactable) && Interaction.WheelActive) { __result = true; Interaction.NoteRow(); } } catch (Exception e) { Interaction.WarnOnce(e); } } } [HarmonyPatch(typeof(VAssist_Interactable_Base), "get_TertiaryInteractText")] internal static class Patch_TertiaryLabel { private static void Postfix(VAssist_Interactable_Base __instance, ref string __result) { try { if (Interop.IsBuddyOrderInteraction((Il2CppObjectBase)(object)__instance.Interactable) && Interaction.WheelActive) { __result = "Buddy Enhancements"; } } catch (Exception e) { Interaction.WarnOnce(e); } } } [HarmonyPatch(typeof(BuddybotOrderController), "SafeStart")] internal static class Patch_CaptureOrderController { private static void Postfix(BuddybotOrderController __instance) { Interaction.Controller = __instance; } } internal static class Priorities { internal const float CombatStation = 20f; internal const float CombatStationWhileBroken = 8f; internal const float CoreRepair = 15f; internal const float LoadAmmo = 12f; internal const float MateriaFilter = 3f; } [HarmonyPatch(typeof(AI_Behaviour_BuddyBot_InsertRepairKit), "get_Incentive")] internal static class Patch_InheritedIncentives { private static void Postfix(Il2CppObjectBase __instance, ref float __result) { if (Cfg.JobPriorities.Value) { if (Interop.IsBehaviour(__instance)) { __result = 15f; } else if (Interop.IsBehaviour(__instance)) { __result = 12f; } } } } [HarmonyPatch(typeof(AI_Behaviour_Buddybot_CombatStation), "get_Incentive")] internal static class Patch_CombatStationIncentive { private static void Postfix(Il2CppObjectBase __instance, ref float __result) { if (Cfg.JobPriorities.Value && Interop.IsBehaviour(__instance)) { __result = ((Cfg.FixHazards.Value && Work.Waiting) ? 8f : 20f); } } } [HarmonyPatch(typeof(AI_Behaviour_Buddybot_EmptyMateriaFilter), "get_Incentive")] internal static class Patch_MateriaFilterIncentive { private static void Postfix(Il2CppObjectBase __instance, ref float __result) { if (Cfg.JobPriorities.Value && Interop.IsBehaviour(__instance)) { __result = 3f; } } } internal static class Repair { internal static AI_Behaviour_BuddyBot_RepairHull LastBehaviour; internal static AI_Controller_Buddbybot LastController; internal static void ReapplyNow() { try { AI_Controller_Buddbybot lastController = LastController; if ((Object)(object)lastController != (Object)null) { ((AI_Controller_Base)lastController).m_ForceThinkTimeInterval = (Cfg.BetterRepairs.Value ? 1f : 5f); } } catch { LastController = null; } try { AI_Behaviour_BuddyBot_RepairHull lastBehaviour = LastBehaviour; if ((Object)(object)lastBehaviour != (Object)null && !Cfg.BetterRepairs.Value) { lastBehaviour.m_EnterHealthFraction = 0.5f; } } catch { LastBehaviour = null; } } } [HarmonyPatch(typeof(AI_Behaviour_BuddyBot_RepairHull), "Setup")] internal static class Patch_RepairSetup { private static void Postfix(AI_Behaviour_BuddyBot_RepairHull __instance) { Repair.LastBehaviour = __instance; __instance.m_EnterHealthFraction = 0.5f; } } [HarmonyPatch(typeof(AI_Behaviour_BuddyBot_RepairHull), "StartBehaviour")] internal static class Patch_RepairStart { private static void Postfix(AI_Behaviour_BuddyBot_RepairHull __instance) { if (Cfg.BetterRepairs.Value) { __instance.m_EnterHealthFraction = 1.1f; } } } [HarmonyPatch(typeof(AI_Behaviour_BuddyBot_RepairHull), "OnStoppedBehaviour")] internal static class Patch_RepairStopped { private static void Postfix(AI_Behaviour_BuddyBot_RepairHull __instance) { __instance.m_EnterHealthFraction = 0.5f; } } [HarmonyPatch(typeof(AI_Controller_Buddbybot), "SafeStart")] internal static class Patch_ControllerThink { private static void Postfix(AI_Controller_Buddbybot __instance) { Repair.LastController = __instance; ((AI_Controller_Base)__instance).m_ForceThinkTimeInterval = (Cfg.BetterRepairs.Value ? 1f : 5f); } } internal static class Price { internal enum Item { Extinguisher, ShipRepair } private const int ShipRepairMultiple = 3; private const float RetryInterval = 20f; private static readonly Dictionary s_Blueprint = new Dictionary(); private static readonly Dictionary s_LookedAt = new Dictionary(); private static readonly Dictionary s_Refused = new Dictionary(); private static ShipMateriaController s_Wallet; private static int s_Owed; private static int s_PoolAfterBill = -1; private static readonly HashSet s_Warned = new HashSet(); internal static bool Affordable(Item item) { if (!Cfg.ChargeForRepairs.Value) { return true; } int num = Cost(item); if (num <= 0) { return true; } int num2 = Pool(); if (num2 < 0) { return true; } bool flag = num2 >= num; bool value; bool flag2 = s_Refused.TryGetValue(item, out value) && value; if (!flag && !flag2) { Trace.Say($"price: the {Name(item)} costs {num} materia and the ship holds {num2}. that work waits for materia"); Say.It($"No {Name(item)} to spare. {num} materia needed, {num2} left.", 5f); } else if (flag && flag2) { Trace.Say($"price: the {Name(item)} is within reach again at {num} materia, ship holds {num2}"); } s_Refused[item] = !flag; return flag; } internal static bool Charge(Item item, string what, out int paid) { paid = 0; if (!Cfg.ChargeForRepairs.Value) { return true; } int num = Cost(item); if (num <= 0) { return true; } int num2 = Pool(); if (num2 < 0) { Trace.Once("price:nopool", "price: the ship pool cannot be read, so nothing is billed"); return true; } if (num2 < num) { Trace.Change($"price:broke:{item}", $"price: {what} needs {num} materia for one {item} and the ship holds {num2}. he leaves it"); return false; } if (!Spend(num)) { Trace.Once($"price:nospend:{item}", $"price: {num} materia for {what} could not be taken. the repair goes " + "ahead unpaid rather than never happening"); return true; } s_Owed = num; s_PoolAfterBill = Pool(); Instance log = Main.Log; if (log != null) { log.Msg($"price: {what} billed {num} materia for the {Name(item)}. ship pool {num2} to {s_PoolAfterBill}"); } paid = num; return true; } internal static void Settle(string what) { if (s_Owed <= 0) { return; } int num = s_Owed; int num2 = s_PoolAfterBill; s_Owed = 0; int num3 = Pool(); if (num3 < 0 || num2 < 0) { return; } int num4 = num2 - num3; if (num4 > 0) { Instance log = Main.Log; if (log != null) { log.Msg($"price: {what} took {num + num4} materia in total. {num} billed by this mod, {num4} more taken elsewhere"); } } } private static int Cost(Item item) { if (item == Item.ShipRepair) { int num = Cost(Item.Extinguisher); if (num > 0) { return num * 3; } return -1; } Craftable blueprint = Blueprint(item); if ((Object)(object)blueprint == (Object)null) { return -1; } GameObject crafter = Crafter(); int num2 = Trace.Read("price:cost", () => blueprint.GetMateriaCraftCost(crafter), -1); if (num2 < 0) { num2 = Trace.Read("price:base", () => blueprint.m_MateriaCraftCost, -1); Trace.Once($"price:noquote:{item}", $"price: the assembler would not quote {item} for this crafter, so the base price of {num2} is used and no modifier is applied"); } Trace.Change($"price:now:{item}", $"{num2} materia"); return num2; } internal static string Name(Item item) { return item switch { Item.Extinguisher => "extinguisher", Item.ShipRepair => "ship repair", _ => item.ToString(), }; } private static GameObject Crafter() { return Buddy.Actor ?? Trace.Read("price:crafter", delegate { PlayerController obj = Ship.HostPlayer(); return (obj == null) ? null : ((Component)obj).gameObject; }, null); } private static Craftable Blueprint(Item item) { if (s_Blueprint.TryGetValue(item, out var value) && (Object)(object)value != (Object)null) { return value; } float unscaledTime = Time.unscaledTime; if (s_LookedAt.TryGetValue(item, out var value2) && unscaledTime - value2 < 20f) { return null; } s_LookedAt[item] = unscaledTime; string tag = TagGuid(item); if (string.IsNullOrEmpty(tag)) { Trace.Every($"price:notag:{item}", 30f, $"price: the game has no tag for {item} yet, so nothing can be priced"); return null; } Assembler assembler = Ship.Assembler(); if ((Object)(object)assembler == (Object)null) { Trace.Every("price:noassembler", 30f, "price: no assembler aboard, so nothing can be priced"); return null; } AssemblerBlackboardData board = Trace.Read("price:board", () => assembler.m_BlackboardData, null); ReactiveCollection items = Trace.Read("price:items", delegate { AssemblerBlackboardData obj2 = board; return (obj2 == null) ? null : obj2.m_AvailableItems; }, null); if (items == null) { return null; } int count; try { count = ((Collection)(object)items).Count; } catch { return null; } GameObject crafter = Crafter(); Craftable val = null; int num = -1; string value3 = "?"; int num2 = 0; for (int i = 0; i < count; i++) { Craftable blueprint = Trace.Read("price:entry", () => ((Collection)(object)items)[i], null); if ((Object)(object)blueprint == (Object)null) { continue; } ScriptableObject val2 = Trace.Read("price:object", () => blueprint.m_CraftableObject, null); PickupableItem_Data data = ((val2 != null) ? ((Il2CppObjectBase)val2).TryCast() : null); if ((Object)(object)data == (Object)null || !Trace.Read("price:hastag", () => data.HasTag(tag), fallback: false)) { continue; } num2++; string text = Trace.Read("price:name", () => blueprint.DisplayName, "?"); int num3 = Trace.Read("price:seen", () => blueprint.GetMateriaCraftCost(crafter), -1); if (num3 < 0) { num3 = Trace.Read("price:seenbase", () => blueprint.m_MateriaCraftCost, -1); } Trace.Say($"price: {item} candidate {text} at {num3} materia"); if (num3 >= 0 && (!((Object)(object)val != (Object)null) || num3 < num)) { val = blueprint; num = num3; value3 = text; } } if ((Object)(object)val == (Object)null) { if (s_Warned.Add(item)) { Instance log = Main.Log; if (log != null) { log.Warning($"price: nothing in the assembler is tagged {Name(item)}, so that repair costs the crew nothing. the assembler holds {count} " + "blueprints and none of them matched. set ChargeForRepairs=false if you would rather repairs were free on purpose"); } } return null; } s_Blueprint[item] = val; Instance log2 = Main.Log; if (log2 != null) { log2.Msg($"price: the {Name(item)} is {value3} at {num} materia, chosen from {num2} tagged blueprint(s)"); } return val; } private static string TagGuid(Item item) { return Trace.Read($"price:tag:{item}", delegate { if (item == Item.Extinguisher) { AssetReferenceT fireExtinguisherTag = ItemStatics.FireExtinguisherTag; return (fireExtinguisherTag != null) ? ((AssetReference)fireExtinguisherTag).AssetGUID : null; } return (string)null; }, null); } private static int Pool() { return Trace.Read("price:pool", () => ShipMateriaController.GetShipMateriaAmount, -1); } private static bool Spend(int amount) { if (amount <= 0) { return true; } ShipMateriaController wallet = Wallet(); if ((Object)(object)wallet == (Object)null) { return false; } if (Trace.Try("price:modify", delegate { wallet.ModifyMateria(-amount); })) { return true; } return Trace.Try("price:rpc", delegate { wallet.RequestModifyMateriaServerRpc(-amount); }); } private static ShipMateriaController Wallet() { if ((Object)(object)s_Wallet != (Object)null) { return s_Wallet; } foreach (DamagePoint_Base point in Registry.Points) { if ((Object)(object)point == (Object)null) { continue; } shipref owner = Trace.Read("price:owner", () => point.m_Owner, null); if (!((Object)(object)owner == (Object)null)) { s_Wallet = Trace.Read("price:wallet", () => owner.MateriaController, null); if (!((Object)(object)s_Wallet == (Object)null)) { Trace.Once("price:walletfound", "price: the materia controller is " + Trace.Name((Component)(object)s_Wallet)); return s_Wallet; } } } Trace.Every("price:nowallet", 30f, "price: no materia controller found yet"); return null; } internal static void Reset() { s_Blueprint.Clear(); s_LookedAt.Clear(); s_Refused.Clear(); s_Warned.Clear(); s_Owed = 0; s_PoolAfterBill = -1; s_Wallet = null; } } internal static class Registry { internal static readonly List Points = new List(); internal static readonly List Parts = new List(); internal static readonly List Shields = new List(); internal static readonly List Pods = new List(); internal static void Add(List list, T item) where T : Object { if ((Object)(object)item == (Object)null) { return; } try { IntPtr pointer = ((Il2CppObjectBase)(object)item).Pointer; for (int i = 0; i < list.Count; i++) { if ((Object)(object)list[i] != (Object)null && ((Il2CppObjectBase)(object)list[i]).Pointer == pointer) { return; } } list.Add(item); Trace.Change("reg:" + typeof(T).Name, $"{list.Count}"); } catch { } } internal static void Drop(List list, T item) where T : Object { if ((Object)(object)item == (Object)null) { return; } try { IntPtr pointer = ((Il2CppObjectBase)(object)item).Pointer; for (int num = list.Count - 1; num >= 0; num--) { if ((Object)(object)list[num] == (Object)null || ((Il2CppObjectBase)(object)list[num]).Pointer == pointer) { list.RemoveAt(num); } } } catch { } } internal static void Prune() { Sweep(Points); Sweep(Parts); Sweep(Shields); Sweep(Pods); } private static void Sweep(List list) where T : Object { for (int num = list.Count - 1; num >= 0; num--) { bool flag; try { flag = (Object)(object)list[num] == (Object)null; } catch { flag = true; } if (flag) { list.RemoveAt(num); } } } internal static void Clear() { Points.Clear(); Parts.Clear(); Shields.Clear(); Pods.Clear(); } internal static void Report() { Trace.Say($"registry: {Points.Count} damage points, {Parts.Count} parts, {Shields.Count} shield units, {Pods.Count} critical-part pods"); } } [HarmonyPatch(typeof(DamagePoint_Base), "SafeStart")] internal static class Patch_RegisterPoint { private static void Postfix(DamagePoint_Base __instance) { Registry.Add(Registry.Points, __instance); } } [HarmonyPatch(typeof(DamagePoint_Base), "WillDestroy")] internal static class Patch_ForgetPoint { private static void Postfix(DamagePoint_Base __instance) { Registry.Drop(Registry.Points, __instance); } } [HarmonyPatch(typeof(SpaceShip_BaseComponent), "SafeStart")] internal static class Patch_RegisterPart { private static void Postfix(SpaceShip_BaseComponent __instance) { Registry.Add(Registry.Parts, __instance); } } [HarmonyPatch(typeof(SpaceShip_BaseComponent), "WillDestroy")] internal static class Patch_ForgetPart { private static void Postfix(SpaceShip_BaseComponent __instance) { Registry.Drop(Registry.Parts, __instance); } } [HarmonyPatch(typeof(ShieldUnitController), "SafeStart")] internal static class Patch_RegisterShield { private static void Postfix(ShieldUnitController __instance) { Registry.Add(Registry.Shields, __instance); } } [HarmonyPatch(typeof(ShieldUnitController), "OnDestroy")] internal static class Patch_ForgetShield { private static void Postfix(ShieldUnitController __instance) { Registry.Drop(Registry.Shields, __instance); } } [HarmonyPatch(typeof(CriticalComponentInteraction), "SafeStart")] internal static class Patch_RegisterPod { private static void Postfix(CriticalComponentInteraction __instance) { Registry.Add(Registry.Pods, __instance); } } [HarmonyPatch(typeof(CriticalComponentInteraction), "WillDestroy")] internal static class Patch_ForgetPod { private static void Postfix(CriticalComponentInteraction __instance) { Registry.Drop(Registry.Pods, __instance); } } [HarmonyPatch(typeof(BuddyBot_OnFootController), "SafeStart")] internal static class Patch_RegisterOnFootBody { private static void Postfix(BuddyBot_OnFootController __instance) { Buddy.TakeOnFootBody(__instance); } } [HarmonyPatch(typeof(BuddyBot_HullRepairController), "SafeStart")] internal static class Patch_RegisterHullBody { private static void Postfix(BuddyBot_HullRepairController __instance) { Buddy.TakeHullBody(__instance); } } internal static class Say { private const float DefaultSeconds = 4f; private const float Gap = 0.75f; private static GUI_HintDisplayManager s_Manager; private static AssetReference s_Label; private static Hint_LabelData s_Data; private static float s_SpokeAt = -99f; private static string s_Last; private static HintLabelBlackboardData s_Shown; private static float s_HideAt; internal static bool Ready { get { if ((Object)(object)s_Manager != (Object)null) { return s_Label != null; } return false; } } internal static void Learn(GUI_HintDisplayManager manager, HintLabelBlackboardData data) { if ((Object)(object)manager != (Object)null) { s_Manager = manager; } if (data != null) { AssetReference val = Trace.Read("say:label", () => ((ReactiveProperty)(object)data.m_HintDisplayLabel)?.Value, null); Hint_LabelData val2 = Trace.Read("say:asset", () => data.m_HintDataAsset?.Value, null); if ((Object)(object)val2 != (Object)null && (Object)(object)((Il2CppObjectBase)val2).TryCast() != (Object)null && val != null && s_Label == null) { s_Label = val; s_Data = val2; Trace.Say("say: borrowed the dialogue label from " + Trace.Name((Object)(object)val2) + ". Buddy can speak from here on"); } } } internal static void It(string text, float seconds = 4f) { if (!Cfg.Announce.Value || string.IsNullOrEmpty(text)) { return; } if (!Ready) { Trace.Every("say:notready", 30f, "say: no dialogue label borrowed yet, so \"" + text + "\" stays in the log"); return; } float unscaledTime = Time.unscaledTime; if ((text == s_Last && unscaledTime - s_SpokeAt < 10f) || unscaledTime - s_SpokeAt < 0.75f) { return; } HintBlackboardData board = Trace.Read("say:board", () => s_Manager.m_HintBlackBoardData, null); HintBlackboardDataReactiveProperty active = Trace.Read("say:active", delegate { HintBlackboardData obj = board; return (obj == null) ? null : obj.m_ActiveHint; }, null); if (active == null) { Trace.Once("say:noboard", "say: the hint blackboard has no active slot"); return; } HintLabelBlackboardData val = Trace.Read("say:standing", () => ((ReactiveProperty)(object)active).Value, null); if (val != null && val != s_Shown) { Trace.Every("say:busy", 20f, "say: the game has its own hint up, so \"" + text + "\" is not shown"); return; } HintLabelBlackboardData data = Build(text, seconds); if (data != null && Trace.Try("say:push", delegate { ((ReactiveProperty)(object)active).Value = data; })) { s_SpokeAt = unscaledTime; s_Last = text; s_Shown = data; s_HideAt = unscaledTime + seconds; Trace.Say("say: \"" + text + "\""); } } internal static void Tick() { if (s_Shown == null || Time.unscaledTime < s_HideAt) { return; } HintLabelBlackboardData going = s_Shown; s_Shown = null; Trace.Try("say:hide", delegate { ((ReactiveProperty)(object)going.m_ShouldDraw).Value = false; }); HintBlackboardDataReactiveProperty active = Trace.Read("say:slot", (Func)delegate { GUI_HintDisplayManager obj = s_Manager; if (obj == null) { return (HintBlackboardDataReactiveProperty)null; } HintBlackboardData hintBlackBoardData = obj.m_HintBlackBoardData; return (hintBlackBoardData == null) ? null : hintBlackBoardData.m_ActiveHint; }, (HintBlackboardDataReactiveProperty)null); if (active != null && Trace.Read("say:held", () => ((ReactiveProperty)(object)active).Value, null) == going) { Trace.Try("say:release", delegate { ((ReactiveProperty)(object)active).Value = null; }); } } private static HintLabelBlackboardData Build(string text, float seconds) { HintLabelBlackboardData data = null; if (!Trace.Try("say:new", delegate { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown data = new HintLabelBlackboardData(); })) { return null; } if (data == null) { return null; } Trace.Try("say:msg", delegate { ((ReactiveProperty)(object)data.m_Message).Value = text; }); Trace.Try("say:id", delegate { ((ReactiveProperty)(object)data.m_ID).Value = "betterbuddy"; }); Trace.Try("say:time", delegate { ((ReactiveProperty)(object)data.m_TimeToShow).Value = seconds; }); Trace.Try("say:draw", delegate { ((ReactiveProperty)(object)data.m_ShouldDraw).Value = true; }); Trace.Try("say:instahide", delegate { ((ReactiveProperty)(object)data.m_Instahide).Value = false; }); Trace.Try("say:art", delegate { ((ReactiveProperty)(object)data.m_HintDisplayLabel).Value = s_Label; }); Trace.Try("say:data", delegate { data.m_HintDataAsset.Value = s_Data; }); return data; } internal static string Upper(string text) { if (!string.IsNullOrEmpty(text)) { return char.ToUpperInvariant(text[0]) + text.Substring(1); } return text; } internal static void Reset() { s_SpokeAt = -99f; s_Last = null; s_Shown = null; } } [HarmonyPatch(typeof(GUI_HintDisplayManager), "OnHintSet")] internal static class Patch_LearnHint { private static void Postfix(GUI_HintDisplayManager __instance, HintLabelBlackboardData data) { Say.Learn(__instance, data); } } internal static class Ship { private const float ThinkInterval = 1f; private const float PruneInterval = 10f; private static readonly List s_ShieldSlots = new List(); private static float s_SlotsFoundAt = -99f; private static readonly Dictionary s_SlotOf = new Dictionary(); private static readonly List s_Units = new List(); private static float s_ThoughtAt = -99f; private static float s_PrunedAt = -99f; private static Transform s_Root; private static Assembler s_Assembler; private static float s_AssemblerAt = -99f; private static PlayerController s_Host; private static float s_HostFoundAt = -99f; private static Spaceship_FireController s_FireControl; private static readonly List s_Fires = new List(); private static readonly List s_Loose = new List(); private static readonly List s_Carried = new List(); private static readonly List s_All = new List(); private static ShieldUnitsManager s_ShieldManager; private static float s_ShieldManagerAt = -99f; internal static IReadOnlyList Points => Registry.Points; internal static IReadOnlyList Parts => Registry.Parts; internal static IReadOnlyList Fires { get { s_Fires.Clear(); EmergencyBlackboardData emergency = Emergency; if ((Object)(object)emergency == (Object)null) { Trace.Every("ship:noemergency", 20f, "ship: no emergency blackboard yet, so nothing can see a fire"); return s_Fires; } List val = Trace.Read("ship:firelist", () => emergency.m_FireNodeOnFire?.m_InnerList, null); if (val == null) { Trace.Every("ship:nofirelist", 20f, "ship: the emergency blackboard holds no burning list"); return s_Fires; } try { for (int num = 0; num < val.Count; num++) { FireNodePlayership val2 = val[num]; if ((Object)(object)val2 != (Object)null) { s_Fires.Add(val2); } } } catch (Exception ex) { Trace.Warn("ship: reading the burning list failed: " + ex.Message); } Trace.Change("ship:fires", $"{s_Fires.Count} nodes alight"); return s_Fires; } } internal static EmergencyBlackboardData Emergency { get { foreach (DamagePoint_Base point in Registry.Points) { if (!((Object)(object)point == (Object)null)) { EmergencyBlackboardData val = Trace.Read("ship:emergency", () => point.m_EmergencyBlackboardData, null); if ((Object)(object)val != (Object)null) { return val; } } } return null; } } internal static Spaceship_FireController FireControl { get { if ((Object)(object)s_FireControl != (Object)null) { return s_FireControl; } foreach (DamagePoint_Base point in Registry.Points) { if ((Object)(object)point == (Object)null) { continue; } shipref owner = Trace.Read("ship:fireowner", () => point.m_Owner, null); if (!((Object)(object)owner == (Object)null)) { s_FireControl = Trace.Read("ship:firectl", () => owner.FireController, null); if (!((Object)(object)s_FireControl == (Object)null)) { Trace.Once("ship:firectlfound", "ship: the fire controller is " + Trace.Name((Component)(object)s_FireControl)); return s_FireControl; } } } Trace.Every("ship:nofirectl", 20f, "ship: no fire controller found yet"); return null; } } internal static IReadOnlyList Shields => Registry.Shields; internal static IReadOnlyList Pods => Registry.Pods; internal static IReadOnlyList ShieldSlots { get { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) float unscaledTime = Time.unscaledTime; if (s_ShieldSlots.Count > 0 && unscaledTime - s_SlotsFoundAt < 30f) { return s_ShieldSlots; } s_SlotsFoundAt = unscaledTime; s_ShieldSlots.Clear(); int num = 0; StringBuilder stringBuilder = new StringBuilder(); try { Il2CppReferenceArray val = Resources.FindObjectsOfTypeAll(Il2CppType.Of()); if (val == null) { return s_ShieldSlots; } for (int i = 0; i < ((Il2CppArrayBase)(object)val).Length; i++) { ShieldUnitInteraction slot = ((Il2CppObjectBase)((Il2CppArrayBase)(object)val)[i]).TryCast(); if ((Object)(object)slot == (Object)null) { continue; } bool flag = false; bool value = false; try { Scene scene = ((Component)slot).gameObject.scene; flag = ((Scene)(ref scene)).IsValid(); value = ((Component)slot).gameObject.activeInHierarchy; } catch { } if (Trace.On) { StringBuilder stringBuilder2 = stringBuilder; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(28, 4, stringBuilder2); handler.AppendLiteral("\n "); handler.AppendFormatted(Trace.Name((Component)(object)slot)); handler.AppendLiteral(" inScene="); handler.AppendFormatted(flag); handler.AppendLiteral(" active="); handler.AppendFormatted(value); handler.AppendLiteral(" "); handler.AppendLiteral("unit="); handler.AppendFormatted(Trace.Name((Component)(object)Trace.Read("ship:censusunit", () => slot.ShieldUnitController, null))); stringBuilder2.Append(ref handler); } if (!flag) { num++; } else { s_ShieldSlots.Add(slot); } } } catch (Exception ex) { Trace.Warn("ship: the shield slot search failed: " + ex.Message); } Trace.Change("ship:slots", $"{s_ShieldSlots.Count} shield slots kept, {num} dropped as out of scene, {Registry.Shields.Count} plates registered"); if (stringBuilder.Length > 0) { Trace.Once($"ship:census:{s_ShieldSlots.Count}:{num}", $"ship: every shield slot object the engine holds:{stringBuilder}"); } return s_ShieldSlots; } } internal static Transform Root { get { if ((Object)(object)s_Root != (Object)null) { return s_Root; } foreach (DamagePoint_Base point in Registry.Points) { if ((Object)(object)point == (Object)null) { continue; } shipref owner = Trace.Read("ship:owner", () => point.m_Owner, null); if (!((Object)(object)owner == (Object)null)) { s_Root = Trace.Read("ship:roottf", () => ((Component)owner).transform, null); if (!((Object)(object)s_Root == (Object)null)) { Trace.Once("ship:root", "ship: the ship root is " + Trace.Name((Component)(object)s_Root)); return s_Root; } } } Trace.Once("ship:noroot", "ship: no ship root found yet"); return null; } } internal static SpaceShip_StateBlackboardData State => Trace.Read("ship:state", delegate { AI_Behaviour_BuddyBot_RepairHull lastBehaviour = Repair.LastBehaviour; return (lastBehaviour == null) ? null : lastBehaviour.m_ShipState; }, null); internal static PickupableItemGlobalBlackboardData Items { get { PickupableItemGlobalBlackboardData val = Trace.Read("ship:itemboard", delegate { AI_Behaviour_BuddyBot_RepairHull lastBehaviour = Repair.LastBehaviour; return (lastBehaviour == null) ? null : lastBehaviour.m_PickupableItemGlobalBlackboardData; }, null); Trace.Once("ship:itemboard:first", ((Object)(object)val == (Object)null) ? "ship: no item blackboard yet. Buddy's hull job has not run Setup" : "ship: item blackboard found on Buddy's hull job"); return val; } } internal static bool Think() { float unscaledTime = Time.unscaledTime; if (unscaledTime - s_PrunedAt > 10f) { s_PrunedAt = unscaledTime; Registry.Prune(); } if (unscaledTime - s_ThoughtAt < 1f) { return false; } s_ThoughtAt = unscaledTime; return true; } internal static ShieldUnitInteraction SlotFor(ShieldUnitController unit) { IntPtr pointer; try { pointer = ((Il2CppObjectBase)unit).Pointer; } catch { return null; } if (s_SlotOf.TryGetValue(pointer, out var value) && (Object)(object)value != (Object)null) { return value; } foreach (ShieldUnitInteraction slot in ShieldSlots) { if ((Object)(object)slot == (Object)null) { continue; } ShieldUnitController val = Trace.Read("ship:slotowner", () => slot.ShieldUnitController, null); if ((Object)(object)val == (Object)null) { continue; } try { if (((Il2CppObjectBase)val).Pointer != pointer) { continue; } } catch { continue; } s_SlotOf[pointer] = slot; return slot; } return null; } internal static List ShieldUnits() { ReactiveCollection val = Trace.Read("ship:units", delegate { SpaceShip_StateBlackboardData state = State; return (state == null) ? null : state.m_TotalShieldUnitControllers; }, null); if (val == null) { Trace.Once("ship:nounits", "ship: the ship state has no shield unit list yet"); return null; } s_Units.Clear(); try { for (int num = 0; num < ((Collection)(object)val).Count; num++) { ShieldUnitController val2 = ((Collection)(object)val)[num]; if ((Object)(object)val2 != (Object)null) { s_Units.Add(val2); } } } catch (Exception ex) { Trace.Warn("ship: reading the shield unit list failed: " + ex.Message); return null; } if (Trace.On) { ReportShield(s_Units.Count); } return s_Units; } private static void ReportShield(int total) { SpaceShip_StateBlackboardData state = State; if ((Object)(object)state == (Object)null) { return; } int value = Trace.Read("ship:active", () => ((Collection)(object)state.m_ActiveShieldUnitControllers)?.Count ?? (-1), -1); float value2 = Trace.Read("ship:maxnow", () => state.m_CurrentShipMaxShield.Value, -1f); float value3 = Trace.Read("ship:maxpot", () => state.m_PotentialShipMaxShield.Value, -1f); Trace.Change("ship:shield", $"{value} of {total} plates fitted, pool {value2:0} of a possible {value3:0}"); if (!Trace.On) { return; } StringBuilder stringBuilder = new StringBuilder($"shield now: {value} fitted of {total} listed, pool {value2:0} of a possible {value3:0}"); float num = 0f; StringBuilder stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler; for (int num2 = 0; num2 < s_Units.Count; num2++) { ShieldUnitController unit = s_Units[num2]; if (!((Object)(object)unit == (Object)null)) { float value4 = Trace.Read("ship:hbhp", () => unit.CurrentHealth, -1f); float num3 = Trace.Read("ship:hbmax", () => unit.MaxHealth, -1f); num += ((num3 > 0f) ? num3 : 0f); stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(10, 4, stringBuilder2); handler.AppendLiteral("\n "); handler.AppendFormatted(Trace.Name((Component)(object)unit)); handler.AppendLiteral(" "); handler.AppendFormatted(value4, "0"); handler.AppendLiteral(" of "); handler.AppendFormatted(num3, "0"); handler.AppendFormatted(IsFitted(unit) ? "" : " NEEDS A PLATE"); stringBuilder3.Append(ref handler); } } float value5 = Trace.Read("ship:genmax", delegate { ShieldUnitsManager obj = ShieldManager(); float? obj2; if (obj == null) { obj2 = null; } else { Spaceship_CompData_ShieldGenerator shieldGeneratorData = obj.m_ShieldGeneratorData; obj2 = ((shieldGeneratorData != null) ? new float?(shieldGeneratorData.MaxShields) : ((float?)null)); } return obj2 ?? (-1f); }, -1f); stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(39, 1, stringBuilder2); handler.AppendLiteral("\n listed reactive armour adds up to "); handler.AppendFormatted(num, "0"); stringBuilder4.Append(ref handler); stringBuilder2 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(33, 1, stringBuilder2); handler.AppendLiteral("\n the ship reports a possible "); handler.AppendFormatted(value3, "0"); stringBuilder5.Append(ref handler); stringBuilder2 = stringBuilder; StringBuilder stringBuilder6 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(35, 1, stringBuilder2); handler.AppendLiteral("\n the shield generator is rated "); handler.AppendFormatted(value5, "0"); stringBuilder6.Append(ref handler); Trace.Every("ship:heartbeat", 30f, stringBuilder.ToString()); } internal static bool NeedsPlate(ShieldUnitController unit) { return Trace.Read("ship:platehp", () => unit.CurrentHealth, 1f) <= 0f; } internal static bool IsFitted(ShieldUnitController unit) { ReactiveCollection val = Trace.Read("ship:activelist", delegate { SpaceShip_StateBlackboardData state = State; return (state == null) ? null : state.m_ActiveShieldUnitControllers; }, null); if (val == null) { return true; } IntPtr pointer; try { pointer = ((Il2CppObjectBase)unit).Pointer; } catch { return true; } try { for (int num = 0; num < ((Collection)(object)val).Count; num++) { ShieldUnitController val2 = ((Collection)(object)val)[num]; if ((Object)(object)val2 != (Object)null && ((Il2CppObjectBase)val2).Pointer == pointer) { return true; } } } catch { return true; } return false; } internal static List LooseItems() { PickupableItemGlobalBlackboardData items = Items; return Read((items != null) ? items.m_PickupableItemsOnShip : null, "on the ship", s_Loose); } internal static List CarriedItems() { PickupableItemGlobalBlackboardData items = Items; return Read((items != null) ? items.m_PickupableItemsCarriedByPlayers : null, "carried", s_Carried); } internal static List AllItems() { PickupableItemGlobalBlackboardData items = Items; return Read((items != null) ? items.m_AllPersistentPickupables : null, "known", s_All); } private static List Read(NetworkReactiveCollection collection, string what, List into) { into.Clear(); if (collection == null) { return into; } List val = Trace.Read("ship:list:" + what, () => collection.m_InnerList, null); if (val == null) { Trace.Once("ship:null:" + what, "ship: the " + what + " item list is null"); return into; } try { for (int num = 0; num < val.Count; num++) { PersistentPickupable val2 = val[num]; if ((Object)(object)val2 != (Object)null) { into.Add(val2); } } } catch (Exception ex) { Trace.Warn("ship: reading the " + what + " item list failed: " + ex.Message); } Trace.Change("ship:items:" + what, $"{into.Count} items {what}"); return into; } internal static void ReportItems() { if (!Trace.On) { return; } List list = LooseItems(); if (list.Count == 0) { return; } string text = $"ship: {list.Count} loose items:"; foreach (PersistentPickupable item in list) { PickupableItem_Data data = Trace.Read("ship:itemdata", () => item.ItemData, null); string text2 = (((Object)(object)data == (Object)null) ? "" : Trace.Read("ship:itemname", () => data.ItemTypeNameText, "")); text = text + "\n " + Trace.Name((Component)(object)item) + " = " + text2; } Trace.Once($"ship:items:{list.Count}", text); } internal static Assembler Assembler() { //IL_0067: 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) if ((Object)(object)s_Assembler != (Object)null) { return s_Assembler; } float unscaledTime = Time.unscaledTime; if (unscaledTime - s_AssemblerAt < 10f) { return null; } s_AssemblerAt = unscaledTime; try { Il2CppReferenceArray val = Resources.FindObjectsOfTypeAll(Il2CppType.Of()); if (val == null) { return null; } for (int i = 0; i < ((Il2CppArrayBase)(object)val).Length; i++) { Assembler val2 = ((Il2CppObjectBase)((Il2CppArrayBase)(object)val)[i]).TryCast(); if (!((Object)(object)val2 == (Object)null)) { Scene scene = ((Component)val2).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { s_Assembler = val2; Trace.Say("ship: the assembler is " + Trace.Name((Component)(object)val2)); return s_Assembler; } } } } catch (Exception ex) { Trace.Warn("ship: the assembler search failed: " + ex.Message); } return null; } internal static ShieldUnitsManager ShieldManager() { //IL_0067: 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) if ((Object)(object)s_ShieldManager != (Object)null) { return s_ShieldManager; } float unscaledTime = Time.unscaledTime; if (unscaledTime - s_ShieldManagerAt < 10f) { return null; } s_ShieldManagerAt = unscaledTime; try { Il2CppReferenceArray val = Resources.FindObjectsOfTypeAll(Il2CppType.Of()); if (val == null) { return null; } for (int i = 0; i < ((Il2CppArrayBase)(object)val).Length; i++) { ShieldUnitsManager val2 = ((Il2CppObjectBase)((Il2CppArrayBase)(object)val)[i]).TryCast(); if (!((Object)(object)val2 == (Object)null)) { Scene scene = ((Component)val2).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { s_ShieldManager = val2; Trace.Say("ship: the shield unit manager is " + Trace.Name((Component)(object)val2)); return s_ShieldManager; } } } } catch (Exception ex) { Trace.Warn("ship: the shield manager search failed: " + ex.Message); } return null; } internal static PlayerController HostPlayer() { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) float unscaledTime = Time.unscaledTime; if ((Object)(object)s_Host != (Object)null && unscaledTime - s_HostFoundAt < 10f) { return s_Host; } s_HostFoundAt = unscaledTime; PlayerController val = null; try { Il2CppReferenceArray val2 = Resources.FindObjectsOfTypeAll(Il2CppType.Of()); if (val2 == null) { return null; } for (int i = 0; i < ((Il2CppArrayBase)(object)val2).Length; i++) { PlayerController player = ((Il2CppObjectBase)((Il2CppArrayBase)(object)val2)[i]).TryCast(); if ((Object)(object)player == (Object)null) { continue; } Scene scene = ((Component)player).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { if (Trace.Read("ship:isserver", () => player.IsServerPlayer, fallback: false)) { val = player; break; } if ((Object)(object)val == (Object)null && Trace.Read("ship:islocal", () => player.IsLocalPlayer, fallback: false)) { val = player; } } } } catch (Exception ex) { Trace.Warn("ship: the player search failed: " + ex.Message); } Trace.Once("ship:hostplayer", ((Object)(object)val == (Object)null) ? "ship: no host player found" : ("ship: host player is " + Trace.Name((Component)(object)val))); s_Host = val; return val; } internal static float Health01(DamagePoint_Base point) { return Trace.Read("ship:health01", () => point.Health01, -1f); } internal static bool Critical(DamagePoint_Base point) { return Trace.Read("ship:critical", () => point.IsCriticallyDamaged, fallback: false); } internal static InteriorDamageType Kind(DamagePoint_Base point) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) return Trace.Read("ship:kind", () => point.DamageType, (InteriorDamageType)11); } internal static ComponentHealthStatus Status(SpaceShip_BaseComponent part) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) return Trace.Read("ship:partstatus", delegate { //IL_001c: Unknown result type (might be due to invalid IL or missing references) BaseComponent_Blackboarddata blackboardData = part.GetBlackboardData(); return (ComponentHealthStatus)(((??)((blackboardData != null) ? blackboardData.m_ComponentHealthStatus : null)?.Value) ?? 0); }, (ComponentHealthStatus)0); } internal static bool Burning(FireNodePlayership fire) { return Trace.Read("ship:burning", () => fire.IsOnFire, fallback: false); } internal static GameObject Where(Component c) { return Trace.Read("ship:where", () => c.gameObject, null); } } internal static class Trace { private const bool Verbose = false; private static readonly HashSet Said = new HashSet(); private static readonly Dictionary Last = new Dictionary(); private static readonly Dictionary Beat = new Dictionary(); private static readonly Dictionary Failures = new Dictionary(); internal static int Era { get; private set; } internal static bool On => false; internal static void Reset(string why) { Era++; Said.Clear(); Last.Clear(); Beat.Clear(); Failures.Clear(); if (On) { Instance log = Main.Log; if (log != null) { log.Msg($"[t{Era}] ---- trace reset: {why} ----"); } } } internal static void Say(string text) { if (On) { Instance log = Main.Log; if (log != null) { log.Msg($"[t{Era}] {text}"); } } } internal static void Warn(string text) { if (On) { Instance log = Main.Log; if (log != null) { log.Warning($"[t{Era}] {text}"); } } } internal static void Once(string key, string text) { if (On && Said.Add(key)) { Instance log = Main.Log; if (log != null) { log.Msg($"[t{Era}] {text}"); } } } internal static void Change(string key, string text) { if (!On) { return; } if (Last.TryGetValue(key, out var value)) { if (!(value == text)) { Last[key] = text; Instance log = Main.Log; if (log != null) { log.Msg($"[t{Era}] {key}: {value} -> {text}"); } } } else { Last[key] = text; Instance log2 = Main.Log; if (log2 != null) { log2.Msg($"[t{Era}] {key}: {text}"); } } } internal static void Every(string key, float seconds, string text) { if (!On) { return; } float unscaledTime = Time.unscaledTime; if (!Beat.TryGetValue(key, out var value) || !(unscaledTime - value < seconds)) { Beat[key] = unscaledTime; Instance log = Main.Log; if (log != null) { log.Msg($"[t{Era}] {text}"); } } } internal static bool Try(string label, Action call) { try { call(); Once("ok:" + label, "call ok: " + label); return true; } catch (Exception ex) { Failures.TryGetValue(label, out var value); Failures[label] = value + 1; if (value == 0) { Instance log = Main.Log; if (log != null) { log.Warning($"[t{Era}] call failed: {label} -> {ex.GetType().Name}: {ex.Message}"); } } return false; } } internal static T Read(string label, Func read, T fallback) { try { return read(); } catch (Exception ex) { Failures.TryGetValue(label, out var value); Failures[label] = value + 1; if (value == 0) { Instance log = Main.Log; if (log != null) { log.Warning($"[t{Era}] read failed: {label} -> {ex.GetType().Name}: {ex.Message}"); } } return fallback; } } internal static void Summary() { if (!On || Failures.Count == 0) { return; } float unscaledTime = Time.unscaledTime; if (Beat.TryGetValue("summary", out var value) && unscaledTime - value < 60f) { return; } Beat["summary"] = unscaledTime; StringBuilder stringBuilder = new StringBuilder("failure counts:"); foreach (KeyValuePair failure in Failures) { StringBuilder stringBuilder2 = stringBuilder; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(8, 2, stringBuilder2); handler.AppendLiteral("\n "); handler.AppendFormatted(failure.Value, 6); handler.AppendLiteral(" x "); handler.AppendFormatted(failure.Key); stringBuilder2.Append(ref handler); } Instance log = Main.Log; if (log != null) { log.Msg($"[t{Era}] {stringBuilder}"); } } internal static void Banner(string text) { if (On) { Instance log = Main.Log; if (log != null) { log.Msg($"[t{Era}] ===== {text} ====="); } } } internal static string Name(Object o) { try { return (o == (Object)null) ? "null" : o.name; } catch { return ""; } } internal static string Name(Component c) { try { return ((Object)(object)c == (Object)null) ? "null" : ((Object)c.gameObject).name; } catch { return ""; } } } internal static class Wheel { private const int IdxBetterRepairs = 0; private const int IdxMaxCrew = 1; private const int IdxBetterAim = 2; private const int IdxJobPriorities = 3; private const int IdxFixHazards = 4; internal static bool IsOurs { get; private set; } internal static bool Opening { get; private set; } internal static void Open() { IsOurs = true; Opening = true; try { RadialMenu.Show(BuildSwitches(), false); } finally { Opening = false; } } internal static void Disown() { if (IsOurs) { IsOurs = false; Instance log = Main.Log; if (log != null) { log.Msg("another wheel took the menu, dropping our selection"); } } } private static Il2CppReferenceArray BuildSwitches() { return new Il2CppReferenceArray(5L) { [0] = Option("Repair Until Hull Full", Cfg.BetterRepairs.Value), [1] = Option("Help At Any Crew Size", Cfg.MaxCrew.Value), [2] = Option("Space Combat Aim(Ro)bot", Cfg.BetterAim.Value), [3] = Option("Smarter Job Order", Cfg.JobPriorities.Value), [4] = Option("Fix Ship Hazards", Cfg.FixHazards.Value) }; } internal static void Cancel() { if (IsOurs) { IsOurs = false; if (RadialMenu.IsOpen) { RadialMenu.GetResultIndex(); } } } internal static void CloseAndApply() { if (!IsOurs) { return; } IsOurs = false; int num = (RadialMenu.IsOpen ? RadialMenu.GetResultIndex() : (-1)); if (num < 0) { return; } bool flag = Cfg.BetterRepairs.Value; bool flag2 = Cfg.MaxCrew.Value; bool flag3 = Cfg.BetterAim.Value; bool flag4 = Cfg.JobPriorities.Value; bool flag5 = Cfg.FixHazards.Value; switch (num) { default: return; case 0: flag = !flag; break; case 1: flag2 = !flag2; break; case 2: flag3 = !flag3; break; case 3: flag4 = !flag4; break; case 4: flag5 = !flag5; break; } if (!Cfg.IsHost) { Instance log = Main.Log; if (log != null) { log.Msg("only the host's switches change Buddy, ignoring"); } return; } Cfg.BetterRepairs.Value = flag; Cfg.MaxCrew.Value = flag2; Cfg.BetterAim.Value = flag3; Cfg.JobPriorities.Value = flag4; Cfg.FixHazards.Value = flag5; Cfg.Save(); Repair.ReapplyNow(); Instance log2 = Main.Log; if (log2 != null) { log2.Msg($"repairs={flag} crew={flag2} aim={flag3} jobs={flag4} hazards={flag5}"); } } private static RadialMenuOption Option(string text, bool on) { //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_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown return new RadialMenuOption { m_Text = text, m_IconMaterial = Icon(on) }; } private static Material Icon(bool on) { try { BuddybotOrderController controller = Interaction.Controller; if ((Object)(object)controller == (Object)null) { return null; } BuddybotOrderWheelOrderData val = controller.m_InstaRepair ?? controller.m_UseAmmo ?? controller.m_Crafting; if ((Object)(object)val == (Object)null) { return null; } return on ? val.m_IconMaterial : val.m_IconMaterial_BlockedOrder; } catch { return null; } } } internal static class Work { private enum Stage { Idle, Present, Act, Settle } private const float SettleSeconds = 1.5f; private const float JobLimit = 40f; private const float DwellFallback = 4f; private static float s_Dwell; private const float SkipSeconds = 90f; private static Stage s_Stage = Stage.Idle; private static Job s_Job; private static float s_StageAt; private static float s_JobAt; private static IntPtr s_LastBuddy; private static readonly Dictionary s_Skip = new Dictionary(); private static bool s_Supplies = true; private const float BilledSeconds = 60f; private static readonly Dictionary s_Billed = new Dictionary(); private static float PresentSeconds { get { if (s_Dwell > 0f) { return s_Dwell; } float num = Trace.Read("work:dwell", delegate { AI_Behaviour_BuddyBot_RepairHull lastBehaviour = Repair.LastBehaviour; return (lastBehaviour == null) ? 0f : lastBehaviour.m_MoveToPointSeconds; }, 0f); if (num <= 0f) { return 4f; } s_Dwell = num; Trace.Say($"work: standing at a job for {s_Dwell:0.0} s, " + "the same pause his hull job takes at a breach"); return s_Dwell; } } internal static bool Waiting { get; private set; } internal static void Tick() { if (!Cfg.FixHazards.Value || !Cfg.IsHost) { if (s_Job != null) { Abandon("the switch is off"); } return; } try { WatchForNewBuddy(); Run(); } catch (Exception ex) { Trace.Warn("work: the tick threw " + ex.GetType().Name + ": " + ex.Message); Abandon("the tick threw"); } Trace.Summary(); } private static void WatchForNewBuddy() { AI_Controller_Buddbybot controller = Buddy.Controller; IntPtr intPtr = (((Object)(object)controller == (Object)null) ? IntPtr.Zero : ((Il2CppObjectBase)controller).Pointer); if (!(intPtr == s_LastBuddy)) { s_LastBuddy = intPtr; Buddy.Forget(); Price.Reset(); Say.Reset(); s_Supplies = true; s_Skip.Clear(); s_Billed.Clear(); s_Job = null; s_Stage = Stage.Idle; if (!(intPtr == IntPtr.Zero)) { Trace.Reset("a new Buddy"); Trace.Banner("Better Buddy 1.1 hazard work"); Trace.Say("work: showing him at the job is " + (Cfg.ShowAtWork.Value ? "on" : "off")); Registry.Report(); Ship.ReportItems(); } } } private static void Run() { float unscaledTime = Time.unscaledTime; bool flag = Ship.Think(); if (!Buddy.Free(out var doing)) { if (Trace.On) { Trace.Change("work:buddy", $"{doing} at {Buddy.Blocker:0}"); } if (s_Job != null) { Abandon("Buddy is busy with " + doing); } if (flag) { Look(); } return; } Trace.Change("work:buddy", "free"); switch (s_Stage) { case Stage.Idle: Idle(unscaledTime, flag); break; case Stage.Present: Present(unscaledTime); break; case Stage.Act: Act(unscaledTime); break; case Stage.Settle: Settle(unscaledTime); break; } } private static void Look() { Waiting = Choose() != null; } private static void Idle(float now, bool scanned) { if (!scanned) { return; } Job job = Choose(); Waiting = job != null; if (job == null) { Trace.Change("work:target", "nothing needs doing"); return; } Price.Item? bill = job.Bill; int paid = 0; if (bill.HasValue && !PaidFor(job.Key, now)) { if (!Price.Charge(bill.Value, job.Name, out paid)) { return; } if (job.Key != IntPtr.Zero) { s_Billed[job.Key] = now; } } s_Job = job; s_JobAt = now; Trace.Say("work: taking on " + job.Name); Say.It((paid > 0 && bill.HasValue) ? $"On the {job.Told}. {Say.Upper(Price.Name(bill.Value))}, {paid} materia." : ("On the " + job.Told + ".")); if (!Cfg.ShowAtWork.Value) { Trace.Once("work:noshow", "work: showing him at the job is off. he works unseen"); Enter(Stage.Act, now); } else { Enter(Stage.Present, now); } } private static void Present(float now) { if (!Alive(now)) { return; } GameObject val = s_Job.Where; if ((Object)(object)val == (Object)null) { Trace.Say("work: " + s_Job.Name + " has no place to stand. working unseen"); Enter(Stage.Act, now); return; } Buddy.Busy(repairing: true); Buddy.Show(val); float presentSeconds = PresentSeconds; if (!(now - s_StageAt < presentSeconds)) { Trace.Say($"work: Buddy has stood at {s_Job.Name} for {presentSeconds:0.0} s, {Buddy.DistanceTo(val):0.0} m from it. starting"); Enter(Stage.Act, now); } } private static void Act(float now) { if (!Alive(now)) { return; } Buddy.Busy(repairing: true); s_Job.Hold(); float num = s_Job.Measure(); if (Trace.On) { Trace.Every("work:act:" + s_Job.Name, 2f, $"work: {s_Job.Name} on route {s_Job.RouteName}, measure {num:0.000}, {now - s_StageAt:0.0} s in"); } if (!s_Job.StillNeeded) { Trace.Say($"work: DONE. {s_Job.Name} cleared by route {s_Job.RouteName} in {now - s_StageAt:0.0} s"); Say.It(Say.Upper(s_Job.Told) + " sorted."); Price.Settle(s_Job.Name); Enter(Stage.Settle, now); } else if (s_Job.Improving(num)) { Trace.Once("work:moving:" + s_Job.Name + ":" + s_Job.RouteName, "work: ROUTE PROVEN. " + s_Job.RouteName + " is moving " + s_Job.Name); s_StageAt = now; } else if (!(now - s_StageAt < s_Job.Patience)) { if (s_Job.Escalate()) { Trace.Say($"work: {s_Job.Name} did not move in {s_Job.Patience:0} s. stepping to route {s_Job.RouteName}"); s_StageAt = now; } else { Trace.Say("work: NO ROUTE CLEARS " + s_Job.Name + ". every route was tried. this line is the answer the session was for"); Skip(s_Job); Enter(Stage.Settle, now); } } } private static void Settle(float now) { if (s_Job != null) { s_Job.Release(); s_Job = null; Buddy.Busy(repairing: false); Buddy.Release(); } if (!(now - s_StageAt < 1.5f)) { Enter(Stage.Idle, now); } } private static void Enter(Stage stage, float now) { s_Stage = stage; s_StageAt = now; Trace.Change("work:stage", stage.ToString()); } private static bool Alive(float now) { if (s_Job == null) { Enter(Stage.Idle, now); return false; } if (!s_Job.Exists) { Trace.Say("work: " + s_Job.Name + " is gone"); Enter(Stage.Settle, now); return false; } if (!s_Job.StillNeeded && s_Stage == Stage.Present) { Trace.Say("work: " + s_Job.Name + " was fixed by someone else on the way"); Enter(Stage.Settle, now); return false; } if (now - s_JobAt > 80f) { Trace.Say("work: " + s_Job.Name + " held Buddy too long. dropping it"); Skip(s_Job); Enter(Stage.Settle, now); return false; } return true; } private static void Abandon(string why) { if (s_Job != null) { Trace.Say("work: dropped " + s_Job.Name + " because " + why); s_Job.Release(); s_Job = null; } Buddy.Busy(repairing: false); Buddy.Release(); s_Stage = Stage.Idle; } private static void Skip(Job job) { IntPtr key = job.Key; if (!(key == IntPtr.Zero)) { s_Skip[key] = Time.unscaledTime; Trace.Say($"work: leaving {job.Name} alone for {90f:0} s"); } } private static bool PaidFor(IntPtr key, float now) { if (key == IntPtr.Zero) { return false; } if (!s_Billed.TryGetValue(key, out var value)) { return false; } if (now - value < 60f) { return true; } s_Billed.Remove(key); return false; } private static bool Skipped(IntPtr key) { if (key == IntPtr.Zero) { return false; } if (!s_Skip.TryGetValue(key, out var value)) { return false; } if (Time.unscaledTime - value < 90f) { return true; } s_Skip.Remove(key); return false; } private static Job Choose() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Invalid comparison between Unknown and I4 DamagePoint_Base val = null; float num = float.MaxValue; bool flag = false; foreach (DamagePoint_Base point in Ship.Points) { if ((Object)(object)point == (Object)null || Skipped(((Il2CppObjectBase)point).Pointer)) { continue; } float num2 = Ship.Health01(point); if (num2 < 0f) { continue; } bool flag2 = Ship.Critical(point); if (((int)Ship.Kind(point) != 11 || flag2) && (!(num2 >= 1f) || flag2)) { float num3 = (flag2 ? (num2 - 1f) : num2); if (!(num3 >= num)) { val = point; num = num3; flag = flag2; } } } bool canCraft = Buddy.CanCraft; if (canCraft != s_Supplies) { s_Supplies = canCraft; Say.It(canCraft ? "Assembler is mine again. Back on repairs." : "Assembler is off limits. Shield plates only.", 5f); } bool flag3 = canCraft && Price.Affordable(Price.Item.ShipRepair); bool flag4 = canCraft && Price.Affordable(Price.Item.Extinguisher); if (flag3 && (Object)(object)val != (Object)null && flag) { return new PointJob(val); } if (flag3) { Job job = ChoosePart(); if (job != null) { return job; } } if (flag4) { Job job2 = ChooseFire(); if (job2 != null) { return job2; } } Job job3 = ChooseShield(); if (job3 != null) { return job3; } if (flag3 && (Object)(object)val != (Object)null) { return new PointJob(val); } ReportUnreachableParts(); return null; } private static Job ChooseFire() { foreach (FireNodePlayership fire in Ship.Fires) { if (!((Object)(object)fire == (Object)null) && !Skipped(((Il2CppObjectBase)fire).Pointer) && Ship.Burning(fire)) { return new FireJob(fire); } } return null; } private static Job ChooseShield() { List list = Ship.ShieldUnits(); if (list == null) { return null; } for (int i = 0; i < list.Count; i++) { ShieldUnitController controller = list[i]; if ((Object)(object)controller == (Object)null || Skipped(((Il2CppObjectBase)controller).Pointer)) { continue; } bool flag = Ship.NeedsPlate(controller); if (Trace.On) { Trace.Change("work:plate:" + Trace.Name((Component)(object)controller), $"{(flag ? "BLOWN" : "good")}, {Trace.Read("work:shieldhp", () => controller.CurrentHealth, -1f):0} of {Trace.Read("work:shieldmax", () => controller.MaxHealth, -1f):0}, shipCountsItFitted={Ship.IsFitted(controller)}"); } if (!flag) { continue; } ShieldUnitInteraction val = Ship.SlotFor(controller); if ((Object)(object)val == (Object)null) { Trace.Once("work:noslot:" + Trace.Name((Component)(object)controller), "work: " + Trace.Name((Component)(object)controller) + " needs a plate but no slot interaction was found for it, so nothing can be fitted"); continue; } PersistentPickupable val2 = ShieldJob.FindPlate(val); if ((Object)(object)val2 == (Object)null) { Trace.Every("work:noplate:" + Trace.Name((Component)(object)val), 30f, "work: " + Trace.Name((Component)(object)val) + " needs a plate and none is aboard"); if (!ShieldJob.PlateExists(val)) { Craft.PlateFor(val); } continue; } return new ShieldJob(val, controller, val2); } return null; } private static Job ChoosePart() { //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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) foreach (CriticalComponentInteraction pod in Ship.Pods) { if ((Object)(object)pod == (Object)null || Skipped(((Il2CppObjectBase)pod).Pointer)) { continue; } SpaceShip_BaseComponent val = Trace.Read("work:podpart", () => pod.LinkedComponent, null); if (!((Object)(object)val == (Object)null)) { ComponentHealthStatus val2 = Ship.Status(val); if ((int)val2 != 0) { Trace.Say($"work: {Trace.Name((Component)(object)val)} is {val2}, and pod {Trace.Name((Component)(object)pod)} is the way in"); return new PartJob(pod); } } } return null; } private static void ReportUnreachableParts() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) foreach (SpaceShip_BaseComponent part in Ship.Parts) { if (!((Object)(object)part == (Object)null)) { ComponentHealthStatus val = Ship.Status(part); if ((int)val != 0) { Trace.Change("work:part:" + Trace.Name((Component)(object)part), $"broken part {Trace.Name((Component)(object)part)} is {val}, and neither a damage point " + "nor a critical-component pod was found for it"); } } } } } internal abstract class Job { private float _mark = float.NaN; protected virtual float Epsilon => 0.005f; protected abstract bool Rising { get; } internal abstract string Name { get; } internal abstract string RouteName { get; } internal abstract IntPtr Key { get; } internal abstract bool Exists { get; } internal abstract GameObject Where { get; } internal abstract bool StillNeeded { get; } internal virtual Price.Item? Bill => null; internal virtual string Told => Name; internal virtual float Patience => 8f; internal abstract float Measure(); internal abstract void Hold(); internal abstract void Release(); internal bool Escalate() { Release(); _mark = float.NaN; return Step(); } protected abstract bool Step(); internal bool Improving(float measure) { if (float.IsNaN(measure) || measure < 0f) { return false; } if (float.IsNaN(_mark)) { _mark = measure; return false; } if ((Rising ? (measure - _mark) : (_mark - measure)) <= Epsilon) { return false; } _mark = measure; return true; } } internal sealed class PointJob : Job { private enum R { HoldAndTool, ToolOnly, HoldOnly, Spent } private const float RegripInterval = 1f; private readonly DamagePoint_Base _point; private readonly DamagePoint_Repair _repair; private R _route; private GameObject _holder; private float _grippedAt; protected override bool Rising => true; internal override string Name => $"{Ship.Kind(_point)} at {Trace.Name((Component)(object)_point)}"; internal override string Told { get { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_0020: Unknown result type (might be due to invalid IL or missing references) if ((int)Ship.Kind(_point) != 11) { return $"{Ship.Kind(_point)}"; } return "damage"; } } internal override string RouteName => _route.ToString(); internal override IntPtr Key { get { if (!((Object)(object)_point == (Object)null)) { return ((Il2CppObjectBase)_point).Pointer; } return IntPtr.Zero; } } internal override bool Exists => (Object)(object)_point != (Object)null; internal override GameObject Where => Ship.Where((Component)(object)_point); internal override bool StillNeeded { get { if (!(Ship.Health01(_point) < 0.999f)) { return Ship.Critical(_point); } return true; } } internal override Price.Item? Bill => Price.Item.ShipRepair; internal PointJob(DamagePoint_Base point) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) _point = point; _repair = Trace.Read("point:cast", () => ((Il2CppObjectBase)point).TryCast(), null); Trace.Once("point:kind:" + Trace.Name((Component)(object)point), $"point: {Trace.Name((Component)(object)point)} is {Ship.Kind(point)}, repairable={(Object)(object)_repair != (Object)null}"); } internal override float Measure() { return Ship.Health01(_point); } internal override void Hold() { switch (_route) { case R.HoldAndTool: Grip(Buddy.Actor); Tool(); break; case R.ToolOnly: Tool(); break; case R.HoldOnly: Grip(Buddy.Actor); break; } } private void Grip(GameObject holder) { if ((Object)(object)_repair == (Object)null) { Trace.Once("point:norepair:" + Trace.Name((Component)(object)_point), "point: " + Trace.Name((Component)(object)_point) + " is not a DamagePoint_Repair, so the held route cannot apply"); return; } if ((Object)(object)holder == (Object)null) { Trace.Once($"point:noholder:{_route}", $"point: route {_route} has nobody to hold it"); return; } bool flag = Trace.Read("point:isrepairing", () => _repair.IsBeingRepaired, fallback: false); if (Trace.On) { Trace.Change("point:held:" + Trace.Name((Component)(object)_point), $"IsBeingRepaired={flag}"); } if (flag && _holder == holder) { return; } float unscaledTime = Time.unscaledTime; bool flag2 = (Object)(object)_holder == (Object)null; if (!flag2 && unscaledTime - _grippedAt < 1f) { return; } if (!flag2) { Trace.Every("point:regrip:" + Trace.Name((Component)(object)_point), 5f, "point: " + Trace.Name((Component)(object)_point) + " did not keep the repair state. the game cleared it, taking it again"); } _holder = holder; _grippedAt = unscaledTime; Trace.Try($"point:grip:{_route}", delegate { _repair.SetIsBeingRepairedByPlayer(true, holder); }); if (flag2) { bool value = Trace.Read("point:isrepairing", () => _repair.IsBeingRepaired, fallback: false); Trace.Say($"point: held {Trace.Name((Component)(object)_point)} with {Trace.Name((Object)(object)holder)}. IsBeingRepaired={value}"); } } private void Tool() { RepairTool_Collider_Base collider = Trace.Read("point:collider", delegate { DamagePoint_Repair repair = _repair; return (repair == null) ? null : repair.RepairTool_Collider_Component; }, null); if ((Object)(object)collider == (Object)null) { Trace.Once("point:notool:" + Trace.Name((Component)(object)_point), "point: " + Trace.Name((Component)(object)_point) + " carries no repair tool collider"); return; } Trace.Once("point:tool:" + Trace.Name((Component)(object)_point), $"point: tool collider on {Trace.Name((Component)(object)_point)}, valid={Trace.Read("point:toolvalid", () => collider.IsValidTarget, fallback: false)}, cost={Trace.Read("point:toolcost", () => collider.CostToRepair, -1f):0.0}"); Trace.Try("point:toolrepair", delegate { collider.Repair(); }); } internal override void Release() { if ((Object)(object)_repair != (Object)null && (Object)(object)_holder != (Object)null) { GameObject holder = _holder; Trace.Try("point:letgo", delegate { _repair.SetIsBeingRepairedByPlayer(false, holder); }); } _holder = null; } protected override bool Step() { _route = _route switch { R.HoldAndTool => R.ToolOnly, R.ToolOnly => R.HoldOnly, R.HoldOnly => R.Spent, _ => R.Spent, }; return _route != R.Spent; } } internal sealed class FireJob : Job { private enum R { Rpc, Server, Node, FlagAndTick, TurnOff, Spent } private const float CallInterval = 1f; private readonly FireNodePlayership _fire; private readonly Spaceship_FireController _control; private readonly int _id; private R _route; private float _calledAt = -99f; protected override float Epsilon => 0.01f; protected override bool Rising => false; internal override string Name => "fire at " + Trace.Name((Component)(object)_fire); internal override string Told => "fire"; internal override string RouteName => _route.ToString(); internal override IntPtr Key { get { if (!((Object)(object)_fire == (Object)null)) { return ((Il2CppObjectBase)_fire).Pointer; } return IntPtr.Zero; } } internal override bool Exists => (Object)(object)_fire != (Object)null; internal override GameObject Where => Ship.Where((Component)(object)_fire); internal override bool StillNeeded => Ship.Burning(_fire); internal override Price.Item? Bill => Price.Item.Extinguisher; internal FireJob(FireNodePlayership fire) { _fire = fire; _control = Trace.Read("fire:control", () => fire.m_fireController, null) ?? Ship.FireControl; _id = Trace.Read("fire:id", () => ((Pathnode_Base)fire).ID, -1); _route = ((!((Object)(object)_control != (Object)null) || _id < 0) ? R.Node : R.Rpc); Trace.Once("fire:found:" + Trace.Name((Component)(object)fire), $"fire: {Trace.Name((Component)(object)fire)} id {_id} health {Trace.Read("fire:health", () => fire.Health, -1f):0.00} of {Trace.Read("fire:full", () => fire.FullHealth, -1f):0.00}, minimum {Trace.Read("fire:min", () => FireNodePlayership.MinimumFireHealth, -1f):0.00}, controller={(((Object)(object)_control != (Object)null) ? Trace.Name((Component)(object)_control) : "none")}, starting route {_route}"); } internal override float Measure() { return Trace.Read("fire:measure", () => _fire.Health, -1f); } internal override void Hold() { switch (_route) { case R.Rpc: if (Due()) { Report(); Trace.Try("fire:rpc", delegate { _control.SetBeingExtinguishedServerRpc(_id, true); }); } break; case R.Server: if (Due()) { Report(); Trace.Try("fire:server", delegate { _control.SetBeingExtinguished(_id, true); }); } break; case R.Node: if (Due()) { Report(); Trace.Try("fire:node", delegate { _fire.SetIsExtinguishing(true); }); } break; case R.FlagAndTick: Flag(); Trace.Try("fire:tick", delegate { _fire.ExtinguishUpdate(); }); break; case R.TurnOff: if (Due()) { Trace.Once("fire:turnoff:" + Trace.Name((Component)(object)_fire), "fire: falling back to TurnOffFire on " + Trace.Name((Component)(object)_fire) + ". no state route put it out"); Trace.Try("fire:turnoff", delegate { _fire.TurnOffFire(); }); } break; } } private void Report() { if (Trace.On) { Trace.Change("fire:state:" + Trace.Name((Component)(object)_fire), $"onfire={Trace.Read("fire:onfire", () => _fire.IsOnFire, fallback: false)}, extinguishing={Trace.Read("fire:isbeing", () => _fire.IsBeingExtinguished, fallback: false)}, health {Trace.Read("fire:h", () => _fire.Health, -1f):0.00}"); } } private void Flag() { bool flag = Trace.Read("fire:isbeing", () => _fire.IsBeingExtinguished, fallback: false); Trace.Change("fire:flagstate:" + Trace.Name((Component)(object)_fire), $"IsBeingExtinguished={flag}"); if (!flag) { Trace.Try("fire:flag", delegate { _fire.IsBeingExtinguished = true; }); } } private bool Due() { float unscaledTime = Time.unscaledTime; if (unscaledTime - _calledAt < 1f) { return false; } _calledAt = unscaledTime; return true; } internal override void Release() { _calledAt = -99f; if ((Object)(object)_fire == (Object)null) { return; } if ((Object)(object)_control != (Object)null && _id >= 0) { Trace.Try("fire:letgo:server", delegate { _control.SetBeingExtinguished(_id, false); }); } Trace.Try("fire:letgo:node", delegate { _fire.SetIsExtinguishing(false); }); } protected override bool Step() { _route = _route switch { R.Rpc => R.Server, R.Server => R.Node, R.Node => R.FlagAndTick, R.FlagAndTick => R.TurnOff, _ => R.Spent, }; return _route != R.Spent; } } internal sealed class ShieldJob : Job { private enum R { BoardWrite, PlayerPath, Spent } private readonly ShieldUnitInteraction _slot; private readonly ShieldUnitController _unit; private readonly PersistentPickupable _plate; private R _route; private bool _fired; protected override float Epsilon => 0.5f; protected override bool Rising => true; internal override float Patience => 4f; internal override string Name => "shield unit " + Trace.Name((Component)(object)_slot); internal override string Told => "shield plate"; internal override string RouteName => _route.ToString(); internal override IntPtr Key { get { if (!((Object)(object)_slot == (Object)null)) { return ((Il2CppObjectBase)_slot).Pointer; } return IntPtr.Zero; } } internal override bool Exists { get { if ((Object)(object)_slot != (Object)null && (Object)(object)_unit != (Object)null) { return (Object)(object)_plate != (Object)null; } return false; } } internal override GameObject Where => Ship.Where((Component)(object)_slot); internal override bool StillNeeded => Ship.NeedsPlate(_unit); internal ShieldJob(ShieldUnitInteraction slot, ShieldUnitController unit, PersistentPickupable plate) { _slot = slot; _unit = unit; _plate = plate; Trace.Say($"shield: {Trace.Name((Component)(object)slot)} at {Trace.Read("shield:hp", () => unit.CurrentHealth, -1f):0} of {Trace.Read("shield:max", () => unit.MaxHealth, -1f):0}, plate {Trace.Name((Component)(object)plate)} is aboard"); } internal override float Measure() { return Trace.Read("shield:measure", () => _unit.CurrentHealth, -1f); } internal override void Hold() { if (!_fired) { _fired = true; switch (_route) { case R.PlayerPath: PlayerPath(); break; case R.BoardWrite: BoardWrite(); break; } } } private void PlayerPath() { PlayerController player = Ship.HostPlayer(); if ((Object)(object)player == (Object)null) { Trace.Say("shield: no host player, so the game's own insert cannot run"); return; } PickupableItem_Data data = Trace.Read("shield:platedata", () => _plate.ItemData, null); if ((Object)(object)data == (Object)null) { Trace.Say("shield: plate " + Trace.Name((Component)(object)_plate) + " carries no item data"); return; } bool value = Trace.Read("shield:allowed", () => ((Interactable_Base)_slot).ServerAllowInteraction(player), fallback: false); Trace.Say($"shield: ServerAllowInteraction says {value}"); Trace.Try("shield:playerpath", delegate { ((PickupableInteract)_slot).OnNetworkPlayerInteractedWithItem(player, _plate, data); }); } private void BoardWrite() { ShieldUnitBlackboardData board = Trace.Read("shield:board", () => _unit.m_ShieldUnitBlackboardData, null); if ((Object)(object)board == (Object)null) { Trace.Say("shield: " + Trace.Name((Component)(object)_slot) + " carries no blackboard"); return; } float max = Trace.Read("shield:max", () => _unit.MaxHealth, 0f); if (max <= 0f) { Trace.Say("shield: " + Trace.Name((Component)(object)_slot) + " reports no maximum health"); return; } if (!StillAboard()) { Trace.Say("shield: plate " + Trace.Name((Component)(object)_plate) + " is no longer on the ship. the first route spent it, so nothing is written"); return; } if (!Consume()) { Trace.Say("shield: the plate could not be consumed, so nothing was written"); return; } Trace.Try("shield:health", delegate { board.m_ShieldUnitHealth.Value = (int)max; }); Trace.Try("shield:active", delegate { board.m_IsShieldUnitActivated.Value = true; }); Recount(); Trace.Say($"shield: wrote {Trace.Name((Component)(object)_slot)} to {max:0} health and active. read back {Trace.Read("shield:hp", () => _unit.CurrentHealth, -1f):0}"); } private static void Recount() { ShieldUnitsManager manager = Ship.ShieldManager(); if ((Object)(object)manager == (Object)null) { Trace.Once("shield:nomanager", "shield: no shield unit manager found, so the pool keeps its old size"); return; } Trace.Try("shield:recount", delegate { manager.CalculateShieldUnits(); }); Trace.Say("shield: asked the ship to add its shield units up again"); } private bool StillAboard() { foreach (PersistentPickupable item in Ship.LooseItems()) { if (item == _plate || ((Il2CppObjectBase)item).Pointer == ((Il2CppObjectBase)_plate).Pointer) { return true; } } return false; } private bool Consume() { PickupableItemBlackboardData board = Trace.Read("shield:platebb", () => _plate.m_PickupableItemBlackboardData, null); float num = Trace.Read("shield:charges", () => (!((Object)(object)board == (Object)null)) ? board.m_ResourceAmount.Value : (-1f), -1f); Trace.Say($"shield: plate {Trace.Name((Component)(object)_plate)} carries {num:0.##} charges"); if (num > 1f) { float left = num - 1f; if (Trace.Try("shield:spendcharge", delegate { _plate.Temp_Server_SetResourceAmount(left); })) { Trace.Say($"shield: spent one charge, {left:0.##} left on the pack"); return true; } Trace.Say("shield: could not spend a single charge, falling back to the whole item"); } PlayerController player = Ship.HostPlayer(); PlayerPickupableItemHandler handler = Trace.Read("shield:handler", delegate { PlayerController obj = player; return (obj == null) ? null : ((Component)obj).GetComponentInChildren(true); }, null); if ((Object)(object)handler == (Object)null) { Trace.Once("shield:nohandler", "shield: no PlayerPickupableItemHandler found, so no plate can be spent"); return false; } PersistentPickupable plate = _plate; bool flag = Trace.Try("shield:consume", delegate { handler.DestroyItemInternal(plate); }); Trace.Say("shield: used up plate " + Trace.Name((Component)(object)plate) + " " + (flag ? "cleanly" : "and it failed")); return flag; } internal override void Release() { _fired = false; } protected override bool Step() { _fired = false; R route = ((_route == R.BoardWrite) ? R.PlayerPath : R.Spent); _route = route; return _route != R.Spent; } internal static PersistentPickupable FindPlate(ShieldUnitInteraction slot) { return Match(slot, Ship.LooseItems()); } internal static bool PlateExists(ShieldUnitInteraction slot) { if ((Object)(object)Match(slot, Ship.LooseItems()) != (Object)null) { return true; } if ((Object)(object)Match(slot, Ship.CarriedItems()) != (Object)null) { Trace.Every("shield:carried:" + Trace.Name((Component)(object)slot), 20f, "shield: a crew member is carrying a plate for " + Trace.Name((Component)(object)slot) + ", so nothing is built"); return true; } if ((Object)(object)Match(slot, Ship.AllItems()) == (Object)null) { return false; } Trace.Every("shield:stowed:" + Trace.Name((Component)(object)slot), 20f, "shield: a plate for " + Trace.Name((Component)(object)slot) + " exists aboard but is not loose, so nothing is built"); return true; } private static PersistentPickupable Match(ShieldUnitInteraction slot, List items) { if (items == null) { return null; } for (int i = 0; i < items.Count; i++) { PersistentPickupable item = items[i]; if (!((Object)(object)item == (Object)null)) { PickupableItem_Data data = Trace.Read("shield:finddata", () => item.ItemData, null); if (!((Object)(object)data == (Object)null) && Trace.Read("shield:isallowed", () => ((PickupableInteract)slot).IsItemAllowed(data), fallback: false)) { return item; } } } return null; } } internal sealed class PartJob : Job { private enum R { ToolTick, EndGame, Spent } private readonly CriticalComponentInteraction _pod; private R _route; protected override bool Rising => true; protected override float Epsilon => 0.1f; internal override string Name => "critical part at " + Trace.Name((Component)(object)_pod); internal override string Told => "failing part"; internal override string RouteName => _route.ToString(); internal override IntPtr Key { get { if (!((Object)(object)_pod == (Object)null)) { return ((Il2CppObjectBase)_pod).Pointer; } return IntPtr.Zero; } } internal override bool Exists { get { if ((Object)(object)_pod != (Object)null) { return (Object)(object)Linked() != (Object)null; } return false; } } internal override GameObject Where => Ship.Where((Component)(object)_pod); internal override bool StillNeeded { get { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 SpaceShip_BaseComponent val = Linked(); if ((Object)(object)val == (Object)null) { return false; } return (int)Ship.Status(val) > 0; } } internal override Price.Item? Bill => Price.Item.ShipRepair; internal PartJob(CriticalComponentInteraction pod) { _pod = pod; Trace.Once("part:found:" + Trace.Name((Component)(object)pod), $"part: {Trace.Name((Component)(object)pod)} repairs {Trace.Name((Component)(object)Linked())}, tool={(((Object)(object)Tool() == (Object)null) ? "none" : "present")}"); } internal override float Measure() { if (!StillNeeded) { return 1f; } return 0f; } internal override void Hold() { switch (_route) { case R.ToolTick: Repair(); break; case R.EndGame: Trace.Try("part:endgame", delegate { _pod.OnGameEnd((ResultType)0); }); break; } } private void Repair() { RepairTool_Collider_Component tool = Tool(); if ((Object)(object)tool == (Object)null) { Trace.Once("part:notool:" + Trace.Name((Component)(object)_pod), "part: " + Trace.Name((Component)(object)_pod) + " carries no repair tool collider"); return; } Trace.Once("part:tool:" + Trace.Name((Component)(object)_pod), $"part: tool on {Trace.Name((Component)(object)_pod)}, valid={Trace.Read("part:valid", () => ((RepairTool_Collider_Base)tool).IsValidTarget, fallback: false)}, cost={Trace.Read("part:cost", () => ((RepairTool_Collider_Base)tool).CostToRepair, -1f):0.0}"); Trace.Try("part:toolrepair", delegate { ((RepairTool_Collider_Base)tool).Repair(); }); } internal override void Release() { } protected override bool Step() { _route = _route switch { R.ToolTick => R.EndGame, R.EndGame => R.Spent, _ => R.Spent, }; return _route != R.Spent; } private SpaceShip_BaseComponent Linked() { return Trace.Read("part:linked", () => _pod.LinkedComponent, null); } private RepairTool_Collider_Component Tool() { return Trace.Read("part:tool", () => _pod.m_RepairTool_Collider_Component, null); } } }