mirror of
https://github.com/sp-tarkov/modules.git
synced 2025-02-13 09:50:43 -05:00
152 lines
7.0 KiB
C#
152 lines
7.0 KiB
C#
using Aki.Reflection.Patching;
|
|
using Aki.Reflection.Utils;
|
|
using EFT;
|
|
using EFT.Bots;
|
|
using EFT.UI.Matchmaker;
|
|
using EFT.UI.Screens;
|
|
using HarmonyLib;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using OfflineRaidAction = System.Action;
|
|
|
|
// DON'T FORGET TO UPDATE REFERENCES IN CONSTRUCTOR
|
|
// AND IN THE LoadOfflineRaidScreenForScavs METHOD AS WELL
|
|
|
|
namespace Aki.SinglePlayer.Patches.ScavMode
|
|
{
|
|
public class LoadOfflineRaidScreenPatch : ModulePatch
|
|
{
|
|
private static readonly MethodInfo _onReadyScreenMethod;
|
|
private static readonly FieldInfo _isLocalField;
|
|
private static readonly FieldInfo _menuControllerField;
|
|
|
|
static LoadOfflineRaidScreenPatch()
|
|
{
|
|
_ = nameof(MainMenuController.InventoryController);
|
|
_ = nameof(TimeAndWeatherSettings.IsRandomWeather);
|
|
_ = nameof(BotControllerSettings.IsScavWars);
|
|
_ = nameof(WavesSettings.IsBosses);
|
|
_ = GClass3172.MAX_SCAV_COUNT; // UPDATE REFS TO THIS CLASS BELOW !!!
|
|
|
|
// `MatchmakerInsuranceScreen` OnShowNextScreen
|
|
_onReadyScreenMethod = AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_43));
|
|
|
|
_isLocalField = AccessTools.Field(typeof(MainMenuController), "bool_0");
|
|
_menuControllerField = typeof(TarkovApplication).GetFields(PatchConstants.PrivateFlags).FirstOrDefault(x => x.FieldType == typeof(MainMenuController));
|
|
|
|
if (_menuControllerField == null)
|
|
{
|
|
Logger.LogError($"LoadOfflineRaidScreenPatch() menuControllerField is null and could not be found in {nameof(TarkovApplication)} class");
|
|
}
|
|
}
|
|
|
|
protected override MethodBase GetTargetMethod()
|
|
{
|
|
// `MatchMakerSelectionLocationScreen` OnShowNextScreen
|
|
return AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_69));
|
|
}
|
|
|
|
[PatchTranspiler]
|
|
private static IEnumerable<CodeInstruction> PatchTranspiler(ILGenerator generator, IEnumerable<CodeInstruction> instructions)
|
|
{
|
|
var codes = new List<CodeInstruction>(instructions);
|
|
|
|
// The original method call that we want to replace
|
|
var onReadyScreenMethodIndex = -1;
|
|
var onReadyScreenMethodCode = new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(MainMenuController), _onReadyScreenMethod.Name));
|
|
|
|
// We additionally need to replace an instruction that jumps to a label on certain conditions, since we change the jump target instruction
|
|
var jumpWhenFalse_Index = -1;
|
|
|
|
for (var i = 0; i < codes.Count; i++)
|
|
{
|
|
if (codes[i].opcode == onReadyScreenMethodCode.opcode && codes[i].operand == onReadyScreenMethodCode.operand)
|
|
{
|
|
onReadyScreenMethodIndex = i;
|
|
continue;
|
|
}
|
|
|
|
if (codes[i].opcode == OpCodes.Brfalse)
|
|
{
|
|
if (jumpWhenFalse_Index != -1)
|
|
{
|
|
// If this warning is ever logged, the condition for locating the exact brfalse instruction will have to be updated
|
|
Logger.LogWarning($"[{nameof(LoadOfflineRaidScreenPatch)}] Found extra instructions with the brfalse opcode! " +
|
|
"This breaks an old assumption that there is only one such instruction in the method body and is now very likely to cause bugs!");
|
|
}
|
|
jumpWhenFalse_Index = i;
|
|
}
|
|
}
|
|
|
|
if (onReadyScreenMethodIndex == -1)
|
|
{
|
|
throw new Exception($"{nameof(LoadOfflineRaidScreenPatch)} failed: Could not find {nameof(_onReadyScreenMethod)} reference code.");
|
|
}
|
|
|
|
if (jumpWhenFalse_Index == -1)
|
|
{
|
|
throw new Exception($"{nameof(LoadOfflineRaidScreenPatch)} failed: Could not find jump (brfalse) reference code.");
|
|
}
|
|
|
|
// Define the new jump label
|
|
var brFalseLabel = generator.DefineLabel();
|
|
|
|
// We build the method call for our substituted method and replace the initial method call with our own, also adding our new label
|
|
var callCode = new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(LoadOfflineRaidScreenPatch), nameof(LoadOfflineRaidScreenForScav))) { labels = { brFalseLabel } };
|
|
codes[onReadyScreenMethodIndex] = callCode;
|
|
|
|
// We build a new brfalse instruction and give it our new label, then replace the original brfalse instruction
|
|
var newBrFalseCode = new CodeInstruction(OpCodes.Brfalse, brFalseLabel);
|
|
codes[jumpWhenFalse_Index] = newBrFalseCode;
|
|
|
|
// This will remove a stray ldarg.0 instruction. It's only needed if we wanted to reference something from `this` in the method body.
|
|
// This is done last to ensure that previous instruction indexes don't shift around (probably why this used to just turn it into a Nop OpCode)
|
|
codes.RemoveAt(onReadyScreenMethodIndex - 1);
|
|
|
|
return codes.AsEnumerable();
|
|
}
|
|
|
|
private static void LoadOfflineRaidScreenForScav()
|
|
{
|
|
var profile = PatchConstants.BackEndSession.Profile;
|
|
var menuController = (object)GetMenuController();
|
|
|
|
// Get fields from MainMenuController.cs
|
|
var raidSettings = Traverse.Create(menuController).Field("raidSettings_0").GetValue<RaidSettings>();
|
|
var matchmakerPlayersController = Traverse.Create(menuController).Field($"{nameof(GClass3172).ToLowerInvariant()}_0").GetValue<GClass3172>();
|
|
|
|
var gclass = new MatchmakerOfflineRaidScreen.GClass3160(profile?.Info, ref raidSettings, matchmakerPlayersController);
|
|
|
|
gclass.OnShowNextScreen += LoadOfflineRaidNextScreen;
|
|
|
|
// `MatchmakerOfflineRaidScreen` OnShowReadyScreen
|
|
gclass.OnShowReadyScreen += (OfflineRaidAction)Delegate.CreateDelegate(typeof(OfflineRaidAction), menuController, nameof(MainMenuController.method_73));
|
|
gclass.ShowScreen(EScreenState.Queued);
|
|
}
|
|
|
|
private static void LoadOfflineRaidNextScreen()
|
|
{
|
|
var menuController = GetMenuController();
|
|
|
|
var raidSettings = Traverse.Create(menuController).Field("raidSettings_0").GetValue<RaidSettings>();
|
|
if (raidSettings.SelectedLocation.Id == "laboratory")
|
|
{
|
|
raidSettings.WavesSettings.IsBosses = true;
|
|
}
|
|
|
|
// Set offline raid values
|
|
_isLocalField.SetValue(menuController, raidSettings.Local);
|
|
|
|
// Load ready screen method
|
|
_onReadyScreenMethod.Invoke(menuController, null);
|
|
}
|
|
|
|
private static MainMenuController GetMenuController()
|
|
{
|
|
return _menuControllerField.GetValue(ClientAppUtils.GetMainApp()) as MainMenuController;
|
|
}
|
|
}
|
|
} |