Skip to content

Commit

Permalink
Better fix for issue #43, properly handle a case where EVA kerbals ar…
Browse files Browse the repository at this point in the history
…e attached to a vessel (external seat, Klaw...)
  • Loading branch information
gotmachine committed Jan 30, 2023
1 parent 0fe35a9 commit 9ec9b39
Showing 1 changed file with 58 additions and 61 deletions.
119 changes: 58 additions & 61 deletions KSPCommunityFixes/BugFixes/EVAKerbalRecovery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

namespace KSPCommunityFixes.BugFixes
{
Expand All @@ -15,85 +13,84 @@ public class EVAKerbalRecovery : BasePatch
protected override void ApplyPatches(ref List<PatchInfo> patches)
{
patches.Add(new PatchInfo(
PatchMethodType.Transpiler,
PatchMethodType.Prefix,
AccessTools.Method(typeof(ProtoVessel), nameof(ProtoVessel.GetAllProtoPartsIncludingCargo)),
this));
}

static IEnumerable<CodeInstruction> ProtoVessel_GetAllProtoPartsIncludingCargo_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator ilGen)
private static bool ProtoVessel_GetAllProtoPartsIncludingCargo_Prefix(ProtoVessel __instance, out List<ProtoPartSnapshot> __result)
{
MethodInfo mInfo_IsEVAKerbal = AccessTools.Method(typeof(EVAKerbalRecovery), nameof(EVAKerbalRecovery.IsEVAKerbal));
MethodInfo mInfo_GetAllProtoPartsFromCrew = AccessTools.Method(typeof(ProtoVessel), nameof(ProtoVessel.GetAllProtoPartsFromCrew));
int partCount = __instance.protoPartSnapshots.Count;
__result = new List<ProtoPartSnapshot>(partCount * 2);
for (int i = 0; i < partCount; i++)
{
ProtoPartSnapshot protoPartSnapshot = __instance.protoPartSnapshots[i];

List<CodeInstruction> code = new List<CodeInstruction>(instructions);
AvailablePart partInfoByName = PartLoader.getPartInfoByName(protoPartSnapshot.partInfo.name);
if (partInfoByName == null)
continue;

for (int i = 0; i < code.Count; i++)
{
// find "if (partInfoByName == null || string.Equals(partInfoByName.name, "kerbalEVA"))"
// and nop the whole "string.Equals(partInfoByName.name, "kerbalEVA")" condition
// Note that this condition was added in KSP 1.12.0 (probably to attempt to fix cargo parts being recovered twice)
// so it won't be found in KSP 1.11.x. Since this "fix" doesn't work and create even more problems, we remove it.
if (code[i].opcode == OpCodes.Ldstr && code[i].operand is string str && str == "kerbalEVA")
{
int j = i + 1;
// advance to the jump
while (code[j].opcode != OpCodes.Brtrue)
j++;
__result.Add(protoPartSnapshot);

// rewind and nop all instructions until the "partInfoByName == null" jump is reached
while (code[j].opcode != OpCodes.Brfalse)
// if the part has an inventory module, instantiate protoparts for the cargo parts
// this include EVA kerbal inventories
for (int j = 0; j < protoPartSnapshot.modules.Count; j++)
{
// note : this won't handle ModuleInventoryPart derivatives, but the same issue is present
// in many other places in the stock codebase, so we assume this is an unsupported scenario.
if (string.Equals(protoPartSnapshot.modules[j].moduleName, nameof(ModuleInventoryPart)))
{
code[j].opcode = OpCodes.Nop;
code[j].operand = null;
j--;
AddProtoPartsFromInventoryNode(__result, __instance, protoPartSnapshot.modules[j].moduleValues);
break;
}
}

// find the "list.AddRange(GetAllProtoPartsFromCrew());" line at the end of the method and don't execute it for EVA kerbals
// this is the proper fix to the EVA kerbal inventories being recovered twice.
if (code[i].opcode == OpCodes.Call && ReferenceEquals(code[i].operand, mInfo_GetAllProtoPartsFromCrew))
// if the part has some crew, add the cargo parts of their inventories,
// unless the part is an EVA kerbal (its inventory was already handled in the above code)
if (protoPartSnapshot.protoModuleCrew != null && protoPartSnapshot.protoModuleCrew.Count > 0 && !partInfoByName.name.StartsWith("kerbalEVA"))
{
// search the begining of line
int insertPos = i;
while (code[insertPos].opcode != OpCodes.Ldloc_0)
insertPos--;

// search the final "return list;" line
int jumpPos = i;
while (code[jumpPos].opcode != OpCodes.Ret)
jumpPos++;

while (code[jumpPos].opcode != OpCodes.Ldloc_0)
jumpPos--;

// and add a label so we can jump to it
Label jump = ilGen.DefineLabel();
code[jumpPos].labels.Add(jump);

// then add a jump to bypass the "list.AddRange(GetAllProtoPartsFromCrew());" line if our
// IsEVAKerbal() method returns true
CodeInstruction[] insert = new CodeInstruction[3];
insert[0] = new CodeInstruction(OpCodes.Ldarg_0);
insert[1] = new CodeInstruction(OpCodes.Call, mInfo_IsEVAKerbal);
insert[2] = new CodeInstruction(OpCodes.Brtrue, jump);

code.InsertRange(insertPos, insert);
break;
for (int j = 0; j < protoPartSnapshot.protoModuleCrew.Count; j++)
{
AddProtoPartsFromInventoryNode(__result, __instance, protoPartSnapshot.protoModuleCrew[j].InventoryNode);
}
}
}

return code;
return false;
}

private static bool IsEVAKerbal(ProtoVessel pv)
private static void AddProtoPartsFromInventoryNode(List<ProtoPartSnapshot> list, ProtoVessel pv, ConfigNode moduleInventoryNode)
{
// EVA kerbals are always 1 part vessels
if (pv.protoPartSnapshots.Count != 1)
return false;
if (moduleInventoryNode == null)
return;

return pv.protoPartSnapshots[0].partInfo.name.StartsWith("kerbalEVA");
}
}
ConfigNode storedPartsNode = null;
if (!moduleInventoryNode.TryGetNode("STOREDPARTS", ref storedPartsNode))
return;

for (int k = 0; k < storedPartsNode.nodes.Count; k++)
{
ConfigNode storedPartNode = storedPartsNode.nodes[k];

if (storedPartNode == null)
continue;

int quantity = 1;
storedPartNode.TryGetValue("quantity", ref quantity);
ConfigNode protoPartSnapshotNode = null;

if (!storedPartNode.TryGetNode("PART", ref protoPartSnapshotNode))
continue;

ProtoPartSnapshot protoPartSnapshot = new ProtoPartSnapshot(protoPartSnapshotNode, pv, null);
if (protoPartSnapshot != null)
{
for (int l = 0; l < quantity; l++)
{
list.Add(protoPartSnapshot);
}
}
}
}
}
}

0 comments on commit 9ec9b39

Please sign in to comment.