93 lines
4.0 KiB
C#
93 lines
4.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using HarmonyLib;
|
|
using NitroxClient.Communication.Abstract;
|
|
using NitroxModel.DataStructures;
|
|
using NitroxModel.Helper;
|
|
using NitroxModel.Packets;
|
|
using NitroxModel_Subnautica.DataStructures;
|
|
using UnityEngine;
|
|
|
|
namespace NitroxPatcher.Patches.Dynamic;
|
|
|
|
/// <summary>
|
|
/// Broadcasts entry progress and entry unlockings when they happen.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This is the only method that needs to be tracked to sync and persist PDAScanner's data, because the other methods
|
|
/// are either unused (found out that they have no actual calls in SN's code, just as <see cref="PDAScanner.RemoveAllEntriesWhichUnlocks"/>)
|
|
/// or simply called automatically by other events (just as <see cref="PDAScanner.CompleteAllEntriesWhichUnlocks"/>).
|
|
/// </remarks>
|
|
public sealed partial class PDAScanner_Scan_Patch : NitroxPatch, IDynamicPatch
|
|
{
|
|
internal static readonly MethodInfo TARGET_METHOD = Reflect.Method(() => PDAScanner.Scan());
|
|
|
|
internal static readonly OpCode INJECTION_OPCODE = OpCodes.Callvirt;
|
|
internal static readonly object INJECTION_OPERAND = Reflect.Method((GameObject t) => t.SendMessage(default, default, default));
|
|
|
|
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
|
|
{
|
|
foreach (CodeInstruction instruction in instructions)
|
|
{
|
|
yield return instruction;
|
|
|
|
if (instruction.opcode.Equals(INJECTION_OPCODE) && instruction.operand.Equals(INJECTION_OPERAND))
|
|
{
|
|
yield return original.Ldloc<PDAScanner.Result>();
|
|
yield return new(OpCodes.Ldloc_S, original.GetLocalVariableIndex<bool>(4));
|
|
yield return new(OpCodes.Call, ((Action<PDAScanner.Result, bool>)Callback).Method);
|
|
}
|
|
}
|
|
}
|
|
|
|
// To determine which exact path the code took, we just need those two parameters
|
|
public static void Callback(PDAScanner.Result result, bool alreadyComplete)
|
|
{
|
|
PDAScanner.ScanTarget scanTarget = PDAScanner.scanTarget;
|
|
|
|
TechType techType = scanTarget.techType;
|
|
PDAScanner.EntryData entryData = PDAScanner.GetEntryData(techType);
|
|
if (entryData == null || (result != PDAScanner.Result.Done && result != PDAScanner.Result.Researched))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// In the case the player has already fully researched the target
|
|
if (alreadyComplete)
|
|
{
|
|
// In the case that the target is destroyable, the player is given 2 titanium and the object is destroyed
|
|
// We want to broadcast the destruction event before the object is destroyed and corresponding scan data is invalidated.
|
|
if (scanTarget.hasUID)
|
|
{
|
|
PDAScanFinished packet = new(new(scanTarget.uid), techType.ToDto(), entryData.totalFragments, true, true, true);
|
|
Resolve<IPacketSender>().Send(packet);
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
NitroxId targetId = scanTarget.hasUID ? new(scanTarget.uid) : null;
|
|
|
|
// Case in which the unlocked progress is incremented
|
|
if (result == PDAScanner.Result.Done)
|
|
{
|
|
if (!PDAScanner.GetPartialEntryByKey(techType, out PDAScanner.Entry entry))
|
|
{
|
|
// Something wrong happened
|
|
return;
|
|
}
|
|
PDAScanFinished packet = new(targetId, techType.ToDto(), entry.unlocked, false, entryData.destroyAfterScan);
|
|
Resolve<IPacketSender>().Send(packet);
|
|
}
|
|
// Case in which the scanned entry is fully unlocked
|
|
else if (result == PDAScanner.Result.Researched)
|
|
{
|
|
PDAScanFinished packet = new(targetId, techType.ToDto(), entryData.totalFragments, true, entryData.destroyAfterScan);
|
|
Resolve<IPacketSender>().Send(packet);
|
|
}
|
|
}
|
|
}
|
|
}
|