diff --git a/ASS.csproj b/ASS.csproj index 5787288..19e1493 100644 --- a/ASS.csproj +++ b/ASS.csproj @@ -7,11 +7,12 @@ {967E5737-8917-4C2B-A0A4-B2B553498462} Library Properties - armorMod.ASS - armorMod.ASS - v4.7.2 + dvize.ASS + dvize.ASS + v4.8 512 true + true @@ -86,13 +87,40 @@ False F:\SPT-AKI\EscapeFromTarkov_Data\Managed\UnityEngine.CoreModule.dll + + False + F:\SPT-AKI\EscapeFromTarkov_Data\Managed\UnityEngine.IMGUIModule.dll + + + False + F:\SPT-AKI\EscapeFromTarkov_Data\Managed\UnityEngine.TextRenderingModule.dll + + + + True + True + Settings.settings + + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + copy "$(TargetPath)" "F:\SPT-AKI\BepInEx\plugins\dvize.ASS.dll" + + + + \ No newline at end of file diff --git a/ArmorRegenComponent.cs b/ArmorRegenComponent.cs new file mode 100644 index 0000000..d684a11 --- /dev/null +++ b/ArmorRegenComponent.cs @@ -0,0 +1,186 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BepInEx.Logging; +using Comfort.Common; +using EFT; +using EFT.InventoryLogic; +using HarmonyLib; +using UnityEngine; + +namespace ASS +{ + internal class ArmorRegenComponent : MonoBehaviour + { + private static float newRepairRate; + private static ArmorComponent armor; + private static float timeSinceLastHit = 0f; + private static Player player; + private static Slot slotContents; + private static bool isRegenerating = false; + private static InventoryControllerClass inventoryController; + + private readonly Dictionary> equipmentSlotDictionary = + new Dictionary> + { + { EquipmentSlot.ArmorVest, new List() }, + { EquipmentSlot.TacticalVest, new List() }, + { EquipmentSlot.Eyewear, new List() }, + { EquipmentSlot.FaceCover, new List() }, + { EquipmentSlot.Headwear, new List() }, + }; + + protected static ManualLogSource Logger + { + get; private set; + } + + private ArmorRegenComponent() + { + if (Logger == null) + { + Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(ArmorRegenComponent)); + } + } + + internal static void Enable() + { + if (Singleton.Instantiated) + { + var gameWorld = Singleton.Instance; + gameWorld.GetOrAddComponent(); + + Logger.LogDebug("ASS: Attaching events"); + } + } + + private void Start() + { + player = Singleton.Instance.MainPlayer; + + player.OnPlayerDeadOrUnspawn += Player_OnPlayerDeadOrUnspawn; + player.BeingHitAction += Player_BeingHitAction; + } + + + private void Update() + { + if (ASS.Plugin.ArmorServiceMode.Value) + { + timeSinceLastHit += Time.unscaledDeltaTime; + if (timeSinceLastHit >= Plugin.TimeDelayRepairInSec.Value) + { + if (!isRegenerating) + { + isRegenerating = true; + StartCoroutine(RepairArmor()); + } + } + } + } + + + private IEnumerator RepairArmor() + { + //if the time since we were last hit exceeds TimeDelayRepairInSec.Value then repair all armor + while (isRegenerating && Plugin.ArmorServiceMode.Value) + { + //Logger.LogInfo($"Repairing Armor Block Reached Because TimeSinceLastHitReached: " + timeSinceLastHit); + //repair the armor divided by the time.unfixed rate + newRepairRate = Plugin.ArmorRepairRateOverTime.Value * Time.deltaTime; + + foreach (EquipmentSlot slot in equipmentSlotDictionary.Keys.ToArray()) + { + //Logger.LogInfo("ASS: Checking EquipmentSlot: " + slot); + Slot tempSlot = getEquipSlot(slot); + + if (tempSlot == null || tempSlot.ContainedItem == null) + { + continue; + } + + foreach (var item in tempSlot.ContainedItem.GetAllItems()) + { + //get the armorcomponent of each item in items and check to see if all item componenets (even helmet side ears) are max durability + armor = item.GetItemComponent(); + + //check if it needs repair for the current item in loop of all items for the slot + if ( + armor != null + && (armor.Repairable.Durability < armor.Repairable.MaxDurability) + ) + { + //increase armor durability by newRepairRate until maximum then set as maximum durability + if ( + armor.Repairable.Durability + newRepairRate + >= armor.Repairable.MaxDurability + ) + { + armor.Repairable.Durability = armor.Repairable.MaxDurability; + //Logger.LogInfo("ASS: Setting MaxDurability for " + item.LocalizedName()); + } + else + { + armor.Repairable.Durability += newRepairRate; + Logger.LogInfo("ASS: Repairing " + item.LocalizedName() + " : " + armor.Repairable.Durability + "/" + armor.Repairable.MaxDurability); + } + } + } + } + + // Wait for the next frame before continuing + yield return null; + } + + } + + private void Player_BeingHitAction(DamageInfo arg1, EBodyPart arg2, float arg3) + { + timeSinceLastHit = 0f; + isRegenerating = false; + StopCoroutine(RepairArmor()); + } + + private void Player_OnPlayerDeadOrUnspawn(Player player) + { + Disable(); + } + + private void Disable() + { + if (player != null) + { + player.OnPlayerDeadOrUnspawn -= Player_OnPlayerDeadOrUnspawn; + player.BeingHitAction -= Player_BeingHitAction; + } + } + + private Slot getEquipSlot(EquipmentSlot slot) + { + var player = Singleton.Instance.MainPlayer; + + // Use AccessTools to get the protected field _inventoryController + inventoryController = (InventoryControllerClass) + AccessTools.Field(typeof(Player), "_inventoryController").GetValue(player); + + if (inventoryController != null) + { + slotContents = inventoryController.Inventory.Equipment.GetSlot(slot); + + if (slotContents.ContainedItem == null) + { + return null; + } + + return slotContents; + } + + return null; + } + } +} + + + + diff --git a/Plugin.cs b/Plugin.cs index 16b6460..6b4f892 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -1,172 +1,75 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Remoting.Messaging; -using System.Threading.Tasks; +using System.Diagnostics; +using System.Reflection; +using Aki.Reflection.Patching; using BepInEx; using BepInEx.Configuration; -using Comfort.Common; using EFT; -using EFT.InventoryLogic; -using HarmonyLib; -using UnityEngine; +using VersionChecker; -namespace armorMod +namespace ASS { - [BepInPlugin("com.armorMod.ASS", "armorMod.ASS", "1.0.0")] - - public class ASS : BaseUnityPlugin + [BepInPlugin("com.dvize.ASS", "dvize.ASS", "1.1.0")] + public class Plugin : BaseUnityPlugin { - private ConfigEntry ArmorServiceMode - { - get; set; - } - private static ConfigEntry TimeDelayRepairInSec - { - get; set; - } - private static ConfigEntry ArmorRepairRateOverTime - { - get; set; - } - - private AbstractGame game; - private bool runOnceAlready = false; - private bool newGame = true; - private float newRepairRate; - private ArmorComponent armor; - private static float timeSinceLastHit = 0f; - - private readonly Dictionary> equipmentSlotDictionary = new Dictionary> - { - { EquipmentSlot.ArmorVest, new List() }, - { EquipmentSlot.TacticalVest, new List() }, - { EquipmentSlot.Eyewear, new List() }, - { EquipmentSlot.FaceCover, new List() }, - { EquipmentSlot.Headwear, new List() } - }; + internal static ConfigEntry ArmorServiceMode { get; set; } + internal static ConfigEntry TimeDelayRepairInSec { get; set; } + internal static ConfigEntry ArmorRepairRateOverTime { get; set; } internal void Awake() { - ArmorServiceMode = Config.Bind("Armor Repair Settings", "Enable/Disable Mod", true, "Enables the Armor Repairing Options Below"); - TimeDelayRepairInSec = Config.Bind("Armor Repair Settings", "Time Delay Repair in Sec", 60f, "How Long Before you were last hit that it repairs armor"); - ArmorRepairRateOverTime = Config.Bind("Armor Repair Settings", "Armor Repair Rate", 0.5f, "How much durability per second is repaired"); - } - private void Update() - { - try - { - game = Singleton.Instance; + CheckEftVersion(); - if (game.InRaid && Camera.main.transform.position != null && newGame && ArmorServiceMode.Value) - { - var player = Singleton.Instance.MainPlayer; - timeSinceLastHit += Time.deltaTime; - - if (!runOnceAlready && game.Status == GameStatus.Started) - { - Logger.LogDebug("ASS: Attaching events"); - player.BeingHitAction += Player_BeingHitAction; - player.OnPlayerDeadOrUnspawn += Player_OnPlayerDeadOrUnspawn; - runOnceAlready = true; - } - - RepairArmor(); - } - } - catch { } + ArmorServiceMode = Config.Bind( + "Armor Repair Settings", + "Enable/Disable Mod", + true, + "Enables the Armor Repairing Options Below" + ); + TimeDelayRepairInSec = Config.Bind( + "Armor Repair Settings", + "Time Delay Repair in Sec", + 60f, + "How Long Before you were last hit that it repairs armor" + ); + ArmorRepairRateOverTime = Config.Bind( + "Armor Repair Settings", + "Armor Repair Rate", + 0.5f, + "How much durability per second is repaired" + ); } - private void RepairArmor() + private void CheckEftVersion() { - //if the time since we were last hit exceeds TimeDelayRepairInSec.Value then repair all armor - if (timeSinceLastHit >= TimeDelayRepairInSec.Value) + // Make sure the version of EFT being run is the correct version + int currentVersion = FileVersionInfo + .GetVersionInfo(BepInEx.Paths.ExecutablePath) + .FilePrivatePart; + int buildVersion = TarkovVersion.BuildVersion; + if (currentVersion != buildVersion) { - //Logger.LogInfo($"Repairing Armor Block Reached Because TimeSinceLastHitReached: " + timeSinceLastHit); - //repair the armor divided by the time.unfixed rate - newRepairRate = ArmorRepairRateOverTime.Value * Time.deltaTime; - - foreach (EquipmentSlot slot in equipmentSlotDictionary.Keys.ToArray()) - { - Logger.LogInfo("ASS: Checking EquipmentSlot: " + slot); - Slot tempSlot = getEquipSlot(slot); - - if (tempSlot == null || tempSlot.ContainedItem == null) - { - continue; - } - - foreach (var item in tempSlot.ContainedItem.GetAllItems()) - { - //get the armorcomponent of each item in items and check to see if all item componenets (even helmet side ears) are max durability - armor = item.GetItemComponent(); - - //check if it needs repair for the current item in loop of all items for the slot - if (armor != null && (armor.Repairable.Durability < armor.Repairable.MaxDurability)) - { - //increase armor durability by newRepairRate until maximum then set as maximum durability - if (armor.Repairable.Durability + newRepairRate >= armor.Repairable.MaxDurability) - { - armor.Repairable.Durability = armor.Repairable.MaxDurability; - //Logger.LogInfo("ASS: Setting MaxDurability for " + item.LocalizedName()); - } - else - { - armor.Repairable.Durability += newRepairRate; - //Logger.LogInfo("ASS: Repairing " + item.LocalizedName() + " : " + armor.Repairable.Durability + "/" + armor.Repairable.MaxDurability); - } - } - - } - - } + Logger.LogError( + $"ERROR: This version of {Info.Metadata.Name} v{Info.Metadata.Version} was built for Tarkov {buildVersion}, but you are running {currentVersion}. Please download the correct plugin version." + ); + EFT.UI.ConsoleScreen.LogError( + $"ERROR: This version of {Info.Metadata.Name} v{Info.Metadata.Version} was built for Tarkov {buildVersion}, but you are running {currentVersion}. Please download the correct plugin version." + ); + throw new Exception($"Invalid EFT Version ({currentVersion} != {buildVersion})"); } } - - private void Player_BeingHitAction(DamageInfo dmgInfo, EBodyPart bodyPart, float hitEffectId) => timeSinceLastHit = 0f; - - private void Player_OnPlayerDeadOrUnspawn(Player player) - { - Logger.LogDebug("ASS: Undo all events"); - player.BeingHitAction -= Player_BeingHitAction; - player.OnPlayerDeadOrUnspawn -= Player_OnPlayerDeadOrUnspawn; - runOnceAlready = false; - newGame = false; - - Task.Delay(TimeSpan.FromSeconds(15)).ContinueWith(_ => - { - // Set newGame = true after the timer is finished so it doesn't execute the events right away - newGame = true; - }); - } - - private Slot slotContents; - private InventoryControllerClass inventoryController; - private Slot getEquipSlot(EquipmentSlot slot) - { - var player = Singleton.Instance.MainPlayer; - - // Use AccessTools to get the protected field _inventoryController - inventoryController = (InventoryControllerClass)AccessTools.Field(typeof(Player), "_inventoryController").GetValue(player); - - - if (inventoryController != null) - { - slotContents = inventoryController.Inventory.Equipment.GetSlot(slot); - - if (slotContents.ContainedItem == null) - { - return null; - } - - return slotContents; - } - - return null; - } - - } -} + //re-initializes each new game + internal class NewGamePatch : ModulePatch + { + protected override MethodBase GetTargetMethod() => + typeof(GameWorld).GetMethod(nameof(GameWorld.OnGameStarted)); + [PatchPrefix] + public static void PatchPrefix() + { + ASS.ArmorRegenComponent.Enable(); + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 92e355a..2bd2859 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -1,15 +1,16 @@ -using System.Reflection; +using System.Reflection; using System.Runtime.InteropServices; +using VersionChecker; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("armorMod.ASS")] +[assembly: AssemblyTitle("dvize.ASS")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("armorMod.ASS")] -[assembly: AssemblyCopyright("Copyright © 2023")] +[assembly: AssemblyProduct("dvize.ASS")] +[assembly: AssemblyCopyright("Copyright © 2023")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -31,5 +32,6 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("1.1.0.0")] +[assembly: AssemblyFileVersion("1.1.0.0")] +[assembly: TarkovVersion(23043)] diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs new file mode 100644 index 0000000..c2c769d --- /dev/null +++ b/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace dvize.ASS.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Properties/Settings.settings b/Properties/Settings.settings new file mode 100644 index 0000000..049245f --- /dev/null +++ b/Properties/Settings.settings @@ -0,0 +1,6 @@ + + + + + + diff --git a/VersionChecker/TarkovVersion.cs b/VersionChecker/TarkovVersion.cs new file mode 100644 index 0000000..0249f88 --- /dev/null +++ b/VersionChecker/TarkovVersion.cs @@ -0,0 +1,146 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using BepInEx.Configuration; +using BepInEx.Logging; +using BepInEx; +using static EFT.ScenesPreset; +using UnityEngine; + +namespace VersionChecker +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] + public class TarkovVersion : Attribute + { + private int version; + + public TarkovVersion() + : this(0) { } + + public TarkovVersion(int version) + { + this.version = version; + } + + public static int BuildVersion + { + get + { + return Assembly + .GetExecutingAssembly() + .GetCustomAttributes(typeof(TarkovVersion), false) + ?.Cast() + ?.FirstOrDefault() + ?.version ?? 0; + } + } + + // Make sure the version of EFT being run is the correct version, throw an exception and output log message if it isn't + /// + /// Check the currently running program's version against the plugin assembly TarkovVersion attribute, and + /// return false if they do not match. + /// Optionally add a fake setting to the F12 menu if Config is passed in + /// + /// The ManualLogSource to output an error to + /// The PluginInfo object for the plugin, used to get the plugin name and version + /// A BepinEx ConfigFile object, if provided, a custom message will be added to the F12 menu + /// + public static bool CheckEftVersion( + ManualLogSource Logger, + PluginInfo Info, + ConfigFile Config = null + ) + { + int currentVersion = FileVersionInfo + .GetVersionInfo(BepInEx.Paths.ExecutablePath) + .FilePrivatePart; + int buildVersion = BuildVersion; + if (currentVersion != buildVersion) + { + string errorMessage = + $"ERROR: This version of {Info.Metadata.Name} v{Info.Metadata.Version} was built for Tarkov {buildVersion}, but you are running {currentVersion}. Please download the correct plugin version."; + Logger.LogError(errorMessage); + + if (Config != null) + { + // This results in a bogus config entry in the BepInEx config file for the plugin, but it shouldn't hurt anything + // We leave the "section" parameter empty so there's no section header drawn + Config.Bind( + "", + "TarkovVersion", + "", + new ConfigDescription( + errorMessage, + null, + new ConfigurationManagerAttributes + { + CustomDrawer = ErrorLabelDrawer, + ReadOnly = true, + HideDefaultButton = true, + HideSettingName = true, + Category = null + } + ) + ); + } + return false; + } + + return true; + } + + static void ErrorLabelDrawer(ConfigEntryBase entry) + { + GUIStyle styleNormal = new GUIStyle(GUI.skin.label); + styleNormal.wordWrap = true; + styleNormal.stretchWidth = true; + + GUIStyle styleError = new GUIStyle(GUI.skin.label); + styleError.stretchWidth = true; + styleError.alignment = TextAnchor.MiddleCenter; + styleError.normal.textColor = Color.red; + styleError.fontStyle = FontStyle.Bold; + + // General notice that we're the wrong version + GUILayout.BeginVertical(); + GUILayout.Label( + entry.Description.Description, + styleNormal, + new GUILayoutOption[] { GUILayout.ExpandWidth(true) } + ); + + // Centered red disabled text + GUILayout.Label( + "Plugin has been disabled!", + styleError, + new GUILayoutOption[] { GUILayout.ExpandWidth(true) } + ); + GUILayout.EndVertical(); + } + +#pragma warning disable 0169, 0414, 0649 + internal sealed class ConfigurationManagerAttributes + { + public bool? ShowRangeAsPercent; + public System.Action CustomDrawer; + public CustomHotkeyDrawerFunc CustomHotkeyDrawer; + public delegate void CustomHotkeyDrawerFunc( + BepInEx.Configuration.ConfigEntryBase setting, + ref bool isCurrentlyAcceptingInput + ); + public bool? Browsable; + public string Category; + public object DefaultValue; + public bool? HideDefaultButton; + public bool? HideSettingName; + public string Description; + public string DispName; + public int? Order; + public bool? ReadOnly; + public bool? IsAdvanced; + public System.Func ObjToStr; + public System.Func StrToObj; + } + } +} diff --git a/VersionChecker/setbuild.ps1 b/VersionChecker/setbuild.ps1 new file mode 100644 index 0000000..48b9d83 --- /dev/null +++ b/VersionChecker/setbuild.ps1 @@ -0,0 +1,20 @@ +# Fetch the version from EscapeFromTarkov.exe +$tarkovPath = 'F:\SPT-AKI\EscapeFromTarkov.exe' +$tarkovVersion = (Get-Item -Path $tarkovPath).VersionInfo.FileVersionRaw.Revision +Write-Host "Current version of EscapeFromTarkov.exe is: $tarkovVersion" + +# Update AssemblyVersion +$assemblyPath = '{0}\..\Properties\AssemblyInfo.cs' -f $PSScriptRoot +$versionPattern = '^\[assembly: TarkovVersion\(.*\)\]' +(Get-Content $assemblyPath) | ForEach-Object { + if ($_ -match $versionPattern){ + $versionType = $matches[1] + $newLine = '[assembly: TarkovVersion({0})]' -f $tarkovVersion + Write-Host "Changed the line from '$_' to '$newLine'" + $newLine + } else { + $_ + } +} | Set-Content $assemblyPath + +Write-Host "AssemblyInfo.cs updated successfully!" \ No newline at end of file diff --git a/app.config b/app.config new file mode 100644 index 0000000..3e0e37c --- /dev/null +++ b/app.config @@ -0,0 +1,3 @@ + + +