From 6a490d0528e2669341dfd337f77d04a741edc132 Mon Sep 17 00:00:00 2001 From: Kaeno Date: Fri, 5 Jul 2024 07:42:11 +0000 Subject: [PATCH 01/27] Renamed Variables to hopefully improve reading. Check role instead of side to tell if they are PMCs. (!145) Renamed a few things to make it easier to read. Use role instead of side to know if they are pmcs. Cant test since one of patches fail due to remapping should be ok Reviewed-on: https://dev.sp-tarkov.com/SPT/Modules/pulls/145 Co-authored-by: Kaeno Co-committed-by: Kaeno --- .../Patches/ScavMode/ScavRepAdjustmentPatch.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/project/SPT.SinglePlayer/Patches/ScavMode/ScavRepAdjustmentPatch.cs b/project/SPT.SinglePlayer/Patches/ScavMode/ScavRepAdjustmentPatch.cs index 6a743e8..4078cf7 100644 --- a/project/SPT.SinglePlayer/Patches/ScavMode/ScavRepAdjustmentPatch.cs +++ b/project/SPT.SinglePlayer/Patches/ScavMode/ScavRepAdjustmentPatch.cs @@ -33,26 +33,27 @@ namespace SPT.SinglePlayer.Patches.ScavMode return; } - if (Singleton.Instance.GetEverExistedPlayerByID(playerProfileId) is Player killedPlayer) + if (Singleton.Instance.GetEverExistedPlayerByID(playerProfileId) is Player killedBot) { - __state = new Tuple(killedPlayer, killedPlayer.AIData.IsAI); + __state = new Tuple(killedBot, killedBot.AIData.IsAI); + var killedPlayerSettings = killedBot.Profile.Info.Settings; // Extra check to ensure we only set playerscavs to IsAI = false - if (killedPlayer.Profile.Info.Settings.Role == WildSpawnType.assault && killedPlayer.Profile.Nickname.Contains("(")) + if (killedPlayerSettings.Role == WildSpawnType.assault && killedBot.Profile.Nickname.Contains("(")) { - killedPlayer.AIData.IsAI = false; + killedBot.AIData.IsAI = false; } // If Victim is a PMC and has killed a Scav or Marksman. - if (killedPlayer.Side == EPlayerSide.Bear || killedPlayer.Side == EPlayerSide.Usec) + if (killedPlayerSettings.Role == WildSpawnType.pmcBEAR || killedPlayerSettings.Role == WildSpawnType.pmcUSEC) { - if(HasBotKilledScav(killedPlayer)) + if (HasBotKilledScav(killedBot)) { - player.Profile.FenceInfo.AddStanding(killedPlayer.Profile.Info.Settings.StandingForKill, EFT.Counters.EFenceStandingSource.ScavHelp); + player.Profile.FenceInfo.AddStanding(killedPlayerSettings.StandingForKill, EFT.Counters.EFenceStandingSource.ScavHelp); } } else { - player.Loyalty.method_1(killedPlayer); + player.Loyalty.method_1(killedBot); } } } From 5eaadd13917e375ffa37e2d2c192f82b38e486cb Mon Sep 17 00:00:00 2001 From: Kaeno Date: Fri, 5 Jul 2024 16:46:11 +0000 Subject: [PATCH 02/27] Changed logic in how we check if they are a boss and if they are of a allowed wildtype (!146) Reviewed-on: https://dev.sp-tarkov.com/SPT/Modules/pulls/146 --- project/SPT.Custom/Patches/CustomAiPatch.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/project/SPT.Custom/Patches/CustomAiPatch.cs b/project/SPT.Custom/Patches/CustomAiPatch.cs index b8f2b10..5d3e84a 100644 --- a/project/SPT.Custom/Patches/CustomAiPatch.cs +++ b/project/SPT.Custom/Patches/CustomAiPatch.cs @@ -5,6 +5,9 @@ using Comfort.Common; using System.Reflection; using SPT.Custom.CustomAI; using HarmonyLib; +using System.Collections.Generic; +using Newtonsoft.Json; +using SPT.Common.Http; namespace SPT.Custom.Patches { @@ -12,6 +15,7 @@ namespace SPT.Custom.Patches { private static readonly PmcFoundInRaidEquipment pmcFoundInRaidEquipment = new PmcFoundInRaidEquipment(Logger); private static readonly AIBrainSpawnWeightAdjustment aIBrainSpawnWeightAdjustment = new AIBrainSpawnWeightAdjustment(Logger); + private static List BossConvertAllowedTypes = GetBossConvertFromServer(); protected override MethodBase GetTargetMethod() { @@ -62,11 +66,7 @@ namespace SPT.Custom.Patches } // Is a boss bot and not already handled above - if (___botOwner_0.Profile.Info.Settings.IsBoss() - && !BotHasAssaultGroupRole(___botOwner_0) - && !isPlayerScav - && !isNormalAssaultScav - && !isSptPmc) + if (BossConvertAllowedTypes.Contains(nameof(__state))) { if (___botOwner_0.Boss.BossLogic == null) { @@ -106,6 +106,12 @@ namespace SPT.Custom.Patches return botOwner.Profile.Info.Settings.Role; } + private static List GetBossConvertFromServer() + { + string json = RequestHandler.GetJson("/singleplayer/BossConvert"); + return JsonConvert.DeserializeObject>(json); + } + private static bool BotHasAssaultGroupRole(BotOwner botOwner) { return botOwner.Profile.Info.Settings.Role == WildSpawnType.assaultGroup; From a3404853ff7f96dfc0987624614b1818fbf98d09 Mon Sep 17 00:00:00 2001 From: Kaeno Date: Sun, 7 Jul 2024 09:24:33 +0000 Subject: [PATCH 03/27] Fix Botgroups softlocking player from extracting from raid. (!147) Reviewed-on: https://dev.sp-tarkov.com/SPT/Modules/pulls/147 --- .../Patches/FixBotgroupMarkofTheUnknown.cs | 31 +++++++++++++++++++ project/SPT.Custom/SPTCustomPlugin.cs | 1 + 2 files changed, 32 insertions(+) create mode 100644 project/SPT.Custom/Patches/FixBotgroupMarkofTheUnknown.cs diff --git a/project/SPT.Custom/Patches/FixBotgroupMarkofTheUnknown.cs b/project/SPT.Custom/Patches/FixBotgroupMarkofTheUnknown.cs new file mode 100644 index 0000000..501f656 --- /dev/null +++ b/project/SPT.Custom/Patches/FixBotgroupMarkofTheUnknown.cs @@ -0,0 +1,31 @@ +using HarmonyLib; +using SPT.Reflection.Patching; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace SPT.Custom.Patches +{ + /// + /// If Scav war is turned on Botsgroup can be null for some reason if null return early to not softlock player. + /// + public class FixBotgroupMarkofTheUnknown : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(BotsGroupMarkOfUnknown), nameof(BotsGroupMarkOfUnknown.Dispose)); + } + [PatchPrefix] + private static bool PatchPrefix(BotsGroup ____groups) + { + if (____groups == null) + { + return false; + } + return true; + } + } +} diff --git a/project/SPT.Custom/SPTCustomPlugin.cs b/project/SPT.Custom/SPTCustomPlugin.cs index ce307c7..10173ae 100644 --- a/project/SPT.Custom/SPTCustomPlugin.cs +++ b/project/SPT.Custom/SPTCustomPlugin.cs @@ -81,6 +81,7 @@ namespace SPT.Custom new InsurancePlaceItem().Enable(); new FileCachePatch().Enable(); new PMCSpawnParamPatch().Enable(); + new FixBotgroupMarkofTheUnknown().Enable(); HookObject.AddOrGetComponent(); } From 34e84ff737df51f8a9d91c25fd3d5da54114e0de Mon Sep 17 00:00:00 2001 From: Lacyway Date: Mon, 8 Jul 2024 11:35:20 +0000 Subject: [PATCH 04/27] Update BundleUtils (!148) - Fixes problems with `GUI.skin` - More efficient code No longer breaks other IMGUI elements from my testing as the `GUIStyle` is isolated. Please test with SAIN and other possible mods. Co-authored-by: Lacyway <20912169+Lacyway@users.noreply.github.com> Reviewed-on: https://dev.sp-tarkov.com/SPT/Modules/pulls/148 Co-authored-by: Lacyway Co-committed-by: Lacyway --- project/SPT.Custom/Utils/BundleUtils.cs | 36 ++++++++++++++++++++----- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/project/SPT.Custom/Utils/BundleUtils.cs b/project/SPT.Custom/Utils/BundleUtils.cs index 25043f7..4bac10d 100644 --- a/project/SPT.Custom/Utils/BundleUtils.cs +++ b/project/SPT.Custom/Utils/BundleUtils.cs @@ -9,6 +9,10 @@ namespace SPT.Custom.Utils private int maximum; private string bundleName; private Texture2D bgTexture; + private bool started; + private GUIStyle labelStyle; + private GUIStyle windowStyle; + private Rect windowRect; public static BundleUtils Create() { @@ -19,6 +23,7 @@ namespace SPT.Custom.Utils bundleUtils.maximum = 0; bundleUtils.enabled = true; bundleUtils.bgTexture = new Texture2D(2, 2); + bundleUtils.windowRect = bundleUtils.CreateRectangle(500, 80); return bundleUtils; } @@ -41,12 +46,29 @@ namespace SPT.Custom.Utils public void OnGUI() { - GUI.skin.label.alignment = TextAnchor.MiddleCenter; - GUI.skin.window.alignment = TextAnchor.MiddleCenter; - GUI.skin.window.normal.background = bgTexture; - GUI.backgroundColor = Color.black; + if (!started) + { + CreateStyles(); + } - GUI.Window(0, CreateRectangle(500, 80), DrawWindow, "Bundle Loading"); + GUI.backgroundColor = Color.black; + GUI.Window(0, windowRect, DrawWindow, "Bundle Loading", windowStyle); + } + + private void CreateStyles() + { + labelStyle = new GUIStyle(GUI.skin.label) + { + alignment = TextAnchor.MiddleCenter + }; + + windowStyle = new GUIStyle(GUI.skin.window) + { + alignment = TextAnchor.UpperCenter + }; + windowStyle.normal.background = bgTexture; + + started = true; } private Rect CreateRectangle(int width, int height) @@ -59,8 +81,8 @@ namespace SPT.Custom.Utils private void DrawWindow(int windowId) { - GUI.Label(new Rect(0, 35, 500, 20), $"Loading bundle: {current} / {maximum}"); - GUI.Label(new Rect(0, 50, 500, 20), bundleName); + GUI.Label(new Rect(0, 35, 500, 20), $"Loading bundle: {current} / {maximum}", labelStyle); + GUI.Label(new Rect(0, 50, 500, 20), bundleName, labelStyle); } } } From 5bad769a440c5b23022bd4199992d3990c9e22c6 Mon Sep 17 00:00:00 2001 From: CWX Date: Thu, 11 Jul 2024 21:09:38 +0100 Subject: [PATCH 05/27] mid raid achievement patch no longer needed as we assign our own, added patch to fix achievements --- .../MidRaidAchievementChangePatch.cs | 25 ------------------ .../Patches/RaidFix/FixAchievementsPatch.cs | 26 +++++++++++++++++++ .../SPT.SinglePlayer/SPTSingleplayerPlugin.cs | 2 +- 3 files changed, 27 insertions(+), 26 deletions(-) delete mode 100644 project/SPT.SinglePlayer/Patches/Progression/MidRaidAchievementChangePatch.cs create mode 100644 project/SPT.SinglePlayer/Patches/RaidFix/FixAchievementsPatch.cs diff --git a/project/SPT.SinglePlayer/Patches/Progression/MidRaidAchievementChangePatch.cs b/project/SPT.SinglePlayer/Patches/Progression/MidRaidAchievementChangePatch.cs deleted file mode 100644 index 2cf0170..0000000 --- a/project/SPT.SinglePlayer/Patches/Progression/MidRaidAchievementChangePatch.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Reflection; -using SPT.Reflection.Patching; - -namespace SPT.SinglePlayer.Patches.Progression -{ - /// - /// BSG have disabled notifications for local raids, set updateAchievements in the achievement controller to always be true - /// This enables the achievement notifications and the client to save completed achievement data into profile.Achievements - /// - public class MidRaidAchievementChangePatch : ModulePatch - { - protected override MethodBase GetTargetMethod() - { - return typeof(AchievementControllerClass).GetConstructors()[0]; - } - - [PatchPrefix] - private static bool PatchPrefix(ref bool updateAchievements) - { - updateAchievements = true; - - return true; // Do original method - } - } -} \ No newline at end of file diff --git a/project/SPT.SinglePlayer/Patches/RaidFix/FixAchievementsPatch.cs b/project/SPT.SinglePlayer/Patches/RaidFix/FixAchievementsPatch.cs new file mode 100644 index 0000000..cc96a9c --- /dev/null +++ b/project/SPT.SinglePlayer/Patches/RaidFix/FixAchievementsPatch.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using EFT; +using HarmonyLib; +using SPT.Reflection.Patching; +using SPT.Reflection.Utils; + +namespace SPT.SinglePlayer.Patches.RaidFix +{ + public class FixAchievementsPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(Player), nameof(Player.Init)); + } + + [PatchPostfix] + public static void PatchPostfix(Profile profile, InventoryControllerClass inventoryController, ref AbstractAchievementControllerClass ____achievementsController, ref AbstractQuestControllerClass ____questController) + { + var achievementController = new GClass3233(profile, inventoryController, PatchConstants.BackEndSession, true); + achievementController.Init(); + achievementController.Run(); + + ____achievementsController = achievementController; + } + } +} \ No newline at end of file diff --git a/project/SPT.SinglePlayer/SPTSingleplayerPlugin.cs b/project/SPT.SinglePlayer/SPTSingleplayerPlugin.cs index 468dbd9..7002839 100644 --- a/project/SPT.SinglePlayer/SPTSingleplayerPlugin.cs +++ b/project/SPT.SinglePlayer/SPTSingleplayerPlugin.cs @@ -58,7 +58,6 @@ namespace SPT.SinglePlayer new MapReadyButtonPatch().Enable(); new LabsKeycardRemovalPatch().Enable(); new ScavLateStartPatch().Enable(); - new MidRaidAchievementChangePatch().Enable(); new GetTraderServicesPatch().Enable(); new PurchaseTraderServicePatch().Enable(); new ScavSellAllPriceStorePatch().Enable(); @@ -69,6 +68,7 @@ namespace SPT.SinglePlayer new ScavRepAdjustmentPatch().Enable(); new AmmoUsedCounterPatch().Enable(); new ArmorDamageCounterPatch().Enable(); + new FixAchievementsPatch().Enable(); } catch (Exception ex) { From 9b1aafeeb7b8365f9ce911dda1b4b8231c65a5d0 Mon Sep 17 00:00:00 2001 From: CWX Date: Thu, 11 Jul 2024 21:11:14 +0100 Subject: [PATCH 06/27] remove unused param --- .../SPT.SinglePlayer/Patches/RaidFix/FixAchievementsPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/SPT.SinglePlayer/Patches/RaidFix/FixAchievementsPatch.cs b/project/SPT.SinglePlayer/Patches/RaidFix/FixAchievementsPatch.cs index cc96a9c..65c410a 100644 --- a/project/SPT.SinglePlayer/Patches/RaidFix/FixAchievementsPatch.cs +++ b/project/SPT.SinglePlayer/Patches/RaidFix/FixAchievementsPatch.cs @@ -14,7 +14,7 @@ namespace SPT.SinglePlayer.Patches.RaidFix } [PatchPostfix] - public static void PatchPostfix(Profile profile, InventoryControllerClass inventoryController, ref AbstractAchievementControllerClass ____achievementsController, ref AbstractQuestControllerClass ____questController) + public static void PatchPostfix(Profile profile, InventoryControllerClass inventoryController, ref AbstractAchievementControllerClass ____achievementsController) { var achievementController = new GClass3233(profile, inventoryController, PatchConstants.BackEndSession, true); achievementController.Init(); From 775bad05abcc856e450460d9cfbfe1cd741249ca Mon Sep 17 00:00:00 2001 From: CWX Date: Thu, 11 Jul 2024 21:18:01 +0100 Subject: [PATCH 07/27] added comment on patch --- .../SPT.SinglePlayer/Patches/RaidFix/FixAchievementsPatch.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/project/SPT.SinglePlayer/Patches/RaidFix/FixAchievementsPatch.cs b/project/SPT.SinglePlayer/Patches/RaidFix/FixAchievementsPatch.cs index 65c410a..ef59e4c 100644 --- a/project/SPT.SinglePlayer/Patches/RaidFix/FixAchievementsPatch.cs +++ b/project/SPT.SinglePlayer/Patches/RaidFix/FixAchievementsPatch.cs @@ -6,6 +6,10 @@ using SPT.Reflection.Utils; namespace SPT.SinglePlayer.Patches.RaidFix { + /// + /// Aim of this patch is just to apply the backend session to the controller and supply true to updating achievements, + /// the mappings for these also need fixing. 3.8.3 remaps a different class compared to 3.9.0 and 3.10.0. + /// public class FixAchievementsPatch : ModulePatch { protected override MethodBase GetTargetMethod() From 2a2b81bc4a7304d4fd0ffe71701df229be9b534b Mon Sep 17 00:00:00 2001 From: Kaeno Date: Fri, 12 Jul 2024 16:21:03 +0000 Subject: [PATCH 08/27] change route to lowercase (!150) Reviewed-on: https://dev.sp-tarkov.com/SPT/Modules/pulls/150 --- project/SPT.Custom/Patches/CustomAiPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/SPT.Custom/Patches/CustomAiPatch.cs b/project/SPT.Custom/Patches/CustomAiPatch.cs index 5d3e84a..96ca886 100644 --- a/project/SPT.Custom/Patches/CustomAiPatch.cs +++ b/project/SPT.Custom/Patches/CustomAiPatch.cs @@ -108,7 +108,7 @@ namespace SPT.Custom.Patches private static List GetBossConvertFromServer() { - string json = RequestHandler.GetJson("/singleplayer/BossConvert"); + string json = RequestHandler.GetJson("/singleplayer/bossconvert"); return JsonConvert.DeserializeObject>(json); } From b4e9242999f81c0cbe9944270d614f7fcd9677ad Mon Sep 17 00:00:00 2001 From: Dev Date: Sat, 13 Jul 2024 18:36:58 +0100 Subject: [PATCH 09/27] Update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ccf1042..10be4b4 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ SPT.Debugging | Debug utilities (disabled in release builds) SPT.Reflection | Reflection utilities used across the project SPT.SinglePlayer | Simulating online game while offline + ## Privacy SPT is an open source project. Your commit credentials as author of a commit will be visible by anyone. Please make sure you understand this before submitting a PR. Feel free to use a "fake" username and email on your commits by using the following commands: From fbe5af61f37d79c10daecb50e5db2e567d55ab76 Mon Sep 17 00:00:00 2001 From: Dev Date: Mon, 15 Jul 2024 19:27:54 +0100 Subject: [PATCH 10/27] Cache bust --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 10be4b4..01f3052 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ git config --local user.email "USERNAME@SOMETHING.com" - [PowerShell v7](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows) - Can also be installed via: `dotnet tool update --global PowerShell` + ## Project Setup Copy-paste Live EFT's `EscapeFromTarkov_Data/Managed/` folder to into this project's `Project/Shared/Managed/` folder From e9f89801f215d7d8c11e58a5ccc7363b5a46551f Mon Sep 17 00:00:00 2001 From: Dev Date: Fri, 19 Jul 2024 13:51:00 +0100 Subject: [PATCH 11/27] Cache bust --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 01f3052..10be4b4 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ git config --local user.email "USERNAME@SOMETHING.com" - [PowerShell v7](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows) - Can also be installed via: `dotnet tool update --global PowerShell` - ## Project Setup Copy-paste Live EFT's `EscapeFromTarkov_Data/Managed/` folder to into this project's `Project/Shared/Managed/` folder From c0721317e49541ebdb55346c6cb491cc34601680 Mon Sep 17 00:00:00 2001 From: Cj Date: Thu, 25 Jul 2024 21:58:35 +0000 Subject: [PATCH 12/27] Fix duplicate logging (!151) Fixes a duplicate set of logging requests sent to the server for no reason. Co-authored-by: Cj <161484149+CJ-SPT@users.noreply.github.com> Reviewed-on: https://dev.sp-tarkov.com/SPT/Modules/pulls/151 Co-authored-by: Cj Co-committed-by: Cj --- project/SPT.Core/Utils/ValidationUtil.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/project/SPT.Core/Utils/ValidationUtil.cs b/project/SPT.Core/Utils/ValidationUtil.cs index c5e0144..c94de1b 100644 --- a/project/SPT.Core/Utils/ValidationUtil.cs +++ b/project/SPT.Core/Utils/ValidationUtil.cs @@ -6,6 +6,8 @@ namespace SPT.Core.Utils { public static class ValidationUtil { + private static bool _hasRun = false; + public static bool Validate() { var c0 = @"Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\EscapeFromTarkov"; @@ -27,10 +29,14 @@ namespace SPT.Core.Utils new FileInfo(Path.Combine(v2, "UnityCrashHandler64.exe")) }; - ServerLog.Debug("SPT.Core", Gfs(v2, "UnityCrashHandler64.exe")?.Length.ToString() ?? "0"); - ServerLog.Debug("SPT.Core", Gfs(v2, "Uninstall.exe")?.Length.ToString() ?? "0"); - ServerLog.Debug("SPT.Core", Gfs(v2, "Register.bat")?.Length.ToString() ?? "0"); - + if (!_hasRun) + { + ServerLog.Debug("SPT.Core", Gfs(v2, "UnityCrashHandler64.exe")?.Length.ToString() ?? "0"); + ServerLog.Debug("SPT.Core", Gfs(v2, "Uninstall.exe")?.Length.ToString() ?? "0"); + ServerLog.Debug("SPT.Core", Gfs(v2, "Register.bat")?.Length.ToString() ?? "0"); + _hasRun = true; + } + v0 = v4.Length - 1; foreach (var value in v4) From c93887a7b295e960f4088f2b6351106ea08dee83 Mon Sep 17 00:00:00 2001 From: Dev Date: Tue, 30 Jul 2024 10:26:40 +0100 Subject: [PATCH 13/27] Improved method comment (cherry picked from commit f1b142ad4937374d24af92a635913c831c37fed6) --- project/SPT.Custom/Patches/RagfairFeePatch.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/project/SPT.Custom/Patches/RagfairFeePatch.cs b/project/SPT.Custom/Patches/RagfairFeePatch.cs index e06f680..1804f22 100644 --- a/project/SPT.Custom/Patches/RagfairFeePatch.cs +++ b/project/SPT.Custom/Patches/RagfairFeePatch.cs @@ -26,14 +26,14 @@ namespace SPT.Custom.Patches return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.method_1)); } - /// - /// Calculate tax to charge player and send to server before the offer is sent - /// - /// Item sold - /// OfferItemCount - /// RequirementsPrice - /// SellInOnePiece - [PatchPrefix] + /// + /// Calculate tax to charge player and send to server before the offer is sent + /// + /// Item sold + /// OfferItemCount + /// RequirementsPrice + /// SellInOnePiece + [PatchPrefix] private static void PatchPrefix(ref Item ___item_0, ref RagfairOfferSellHelperClass ___ragfairOfferSellHelperClass, ref double ___double_0, ref bool ___bool_0) { RequestHandler.PutJson("/client/ragfair/offerfees", new From f08011788c2b7452abb500adccefa71ca78c4ed8 Mon Sep 17 00:00:00 2001 From: Lacyway Date: Thu, 1 Aug 2024 19:50:59 +0000 Subject: [PATCH 14/27] Make patches public (!152) Some patches are `internal` that should be `public` so that mods can disable them when altering AI. Co-authored-by: Lacyway <20912169+Lacyway@users.noreply.github.com> Reviewed-on: https://dev.sp-tarkov.com/SPT/Modules/pulls/152 Co-authored-by: Lacyway Co-committed-by: Lacyway --- project/SPT.Custom/Patches/BotCallForHelpCallBotPatch.cs | 2 +- project/SPT.Custom/Patches/BotCalledDataTryCallPatch.cs | 2 +- project/SPT.Custom/Patches/BotOwnerDisposePatch.cs | 2 +- project/SPT.Custom/Patches/BotSelfEnemyPatch.cs | 2 +- .../SPT.SinglePlayer/Patches/ScavMode/ScavEncyclopediaPatch.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/project/SPT.Custom/Patches/BotCallForHelpCallBotPatch.cs b/project/SPT.Custom/Patches/BotCallForHelpCallBotPatch.cs index e0a2cae..64c2cf4 100644 --- a/project/SPT.Custom/Patches/BotCallForHelpCallBotPatch.cs +++ b/project/SPT.Custom/Patches/BotCallForHelpCallBotPatch.cs @@ -12,7 +12,7 @@ namespace SPT.Custom.Patches * * This results in both an NRE, and the called bots target location being wrong */ - internal class BotCallForHelpCallBotPatch : ModulePatch + public class BotCallForHelpCallBotPatch : ModulePatch { private static FieldInfo _originalPanicTypeField; diff --git a/project/SPT.Custom/Patches/BotCalledDataTryCallPatch.cs b/project/SPT.Custom/Patches/BotCalledDataTryCallPatch.cs index 5a8ba51..663a56f 100644 --- a/project/SPT.Custom/Patches/BotCalledDataTryCallPatch.cs +++ b/project/SPT.Custom/Patches/BotCalledDataTryCallPatch.cs @@ -9,7 +9,7 @@ namespace SPT.Custom.Patches * It's possible for `AddEnemy` to return false, in that case, further code in TryCall will fail, * so we do the first bit of `TryCall` ourselves, and skip the original function if AddEnemy fails */ - internal class BotCalledDataTryCallPatch : ModulePatch + public class BotCalledDataTryCallPatch : ModulePatch { protected override MethodBase GetTargetMethod() { diff --git a/project/SPT.Custom/Patches/BotOwnerDisposePatch.cs b/project/SPT.Custom/Patches/BotOwnerDisposePatch.cs index 365e5f5..9242e71 100644 --- a/project/SPT.Custom/Patches/BotOwnerDisposePatch.cs +++ b/project/SPT.Custom/Patches/BotOwnerDisposePatch.cs @@ -9,7 +9,7 @@ namespace SPT.Custom.Patches * BotOwner doesn't call SetOff on the CalledData object when a bot is disposed, this can result * in bots that are no longer alive having their `OnEnemyAdd` method called */ - internal class BotOwnerDisposePatch : ModulePatch + public class BotOwnerDisposePatch : ModulePatch { protected override MethodBase GetTargetMethod() { diff --git a/project/SPT.Custom/Patches/BotSelfEnemyPatch.cs b/project/SPT.Custom/Patches/BotSelfEnemyPatch.cs index 1f1b4aa..97a0482 100644 --- a/project/SPT.Custom/Patches/BotSelfEnemyPatch.cs +++ b/project/SPT.Custom/Patches/BotSelfEnemyPatch.cs @@ -8,7 +8,7 @@ namespace SPT.Custom.Patches /// /// Goal: patch removes the current bot from its own enemy list - occurs when adding bots type to its enemy array in difficulty settings /// - internal class BotSelfEnemyPatch : ModulePatch + public class BotSelfEnemyPatch : ModulePatch { protected override MethodBase GetTargetMethod() { diff --git a/project/SPT.SinglePlayer/Patches/ScavMode/ScavEncyclopediaPatch.cs b/project/SPT.SinglePlayer/Patches/ScavMode/ScavEncyclopediaPatch.cs index 6db0b85..a4467b6 100644 --- a/project/SPT.SinglePlayer/Patches/ScavMode/ScavEncyclopediaPatch.cs +++ b/project/SPT.SinglePlayer/Patches/ScavMode/ScavEncyclopediaPatch.cs @@ -12,7 +12,7 @@ namespace SPT.SinglePlayer.Patches.ScavMode * At the start of a scav raid, copy the PMC encyclopedia to the scav profile, and * make sure the scav knows all of the items it has in its inventory */ - internal class ScavEncyclopediaPatch : ModulePatch + public class ScavEncyclopediaPatch : ModulePatch { protected override MethodBase GetTargetMethod() { From de7e762211aa6a9fe26fc80eb449f85db6464117 Mon Sep 17 00:00:00 2001 From: Lacyway Date: Thu, 1 Aug 2024 20:15:50 +0000 Subject: [PATCH 15/27] Update AddEnemyToAllGroupsInBotZonePatch to latest code (!153) Was missing checks that now exist in the client, works as intended now from my testing. Please test and confirm as well. **Note**: I strongly believe that this patch is now redundant and can be removed, I am still testing to see if this is the case. Co-authored-by: Lacyway <20912169+Lacyway@users.noreply.github.com> Reviewed-on: https://dev.sp-tarkov.com/SPT/Modules/pulls/153 Co-authored-by: Lacyway Co-committed-by: Lacyway --- .../Patches/AddEnemyToAllGroupsInBotZonePatch.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/project/SPT.Custom/Patches/AddEnemyToAllGroupsInBotZonePatch.cs b/project/SPT.Custom/Patches/AddEnemyToAllGroupsInBotZonePatch.cs index f59fa52..52baccd 100644 --- a/project/SPT.Custom/Patches/AddEnemyToAllGroupsInBotZonePatch.cs +++ b/project/SPT.Custom/Patches/AddEnemyToAllGroupsInBotZonePatch.cs @@ -45,11 +45,14 @@ namespace SPT.Custom.Patches bool differentSide = aggressor.Side != group.Side; bool sameSide = aggressor.Side == target.Side; - if (!group.Enemies.ContainsKey(aggressor) + if (!group.HaveFollowTarget(aggressor) + && !group.Enemies.ContainsKey(aggressor) && (differentSide || !sameSide) && !group.HaveMemberWithRole(WildSpawnType.gifter) - && group.ShallRevengeFor(target) - ) + && !group.HaveMemberWithRole(WildSpawnType.sectantWarrior) + && !group.HaveMemberWithRole(WildSpawnType.sectantPriest) + && !group.InitialFileSettings.Boss.NOT_ADD_TO_ENEMY_ON_KILLS + && group.ShallRevengeFor(target)) { group.AddEnemy(aggressor, EBotEnemyCause.AddEnemyToAllGroupsInBotZone); } From 42060f027d78f773d6d24e615d8e12f2cbcd10fb Mon Sep 17 00:00:00 2001 From: Dev Date: Fri, 2 Aug 2024 14:59:14 +0100 Subject: [PATCH 16/27] Removed unnecessary patch `BotCallForHelpCallBotPatch` Removed unnecessary null check --- .../Patches/BotCallForHelpCallBotPatch.cs | 54 ------------------- project/SPT.Custom/Patches/IsEnemyPatch.cs | 6 --- project/SPT.Custom/SPTCustomPlugin.cs | 1 - 3 files changed, 61 deletions(-) delete mode 100644 project/SPT.Custom/Patches/BotCallForHelpCallBotPatch.cs diff --git a/project/SPT.Custom/Patches/BotCallForHelpCallBotPatch.cs b/project/SPT.Custom/Patches/BotCallForHelpCallBotPatch.cs deleted file mode 100644 index 64c2cf4..0000000 --- a/project/SPT.Custom/Patches/BotCallForHelpCallBotPatch.cs +++ /dev/null @@ -1,54 +0,0 @@ -using SPT.Reflection.Patching; -using EFT; -using HarmonyLib; -using System.Reflection; -using UnityEngine; - -namespace SPT.Custom.Patches -{ - /** - * BSG passes the wrong target location into the TryCall method for BotCallForHelp, it's passing in - * the bots current target instead of the target of the bot calling for help - * - * This results in both an NRE, and the called bots target location being wrong - */ - public class BotCallForHelpCallBotPatch : ModulePatch - { - private static FieldInfo _originalPanicTypeField; - - protected override MethodBase GetTargetMethod() - { - _originalPanicTypeField = AccessTools.Field(typeof(BotCallForHelp), "_originalPanicType"); - - return AccessTools.FirstMethod(typeof(BotCallForHelp), IsTargetMethod); - } - - protected bool IsTargetMethod(MethodBase method) - { - var parameters = method.GetParameters(); - return (parameters.Length == 1 - && parameters[0].Name == "calledBot"); - } - - [PatchPrefix] - private static bool PatchPrefix(ref bool __result, BotCallForHelp __instance, BotOwner calledBot, BotOwner ___botOwner_0) - { - if (__instance.method_2(calledBot) && ___botOwner_0.Memory.GoalEnemy != null) - { - _originalPanicTypeField.SetValue(calledBot.CallForHelp, calledBot.DangerPointsData.PanicType); - calledBot.DangerPointsData.PanicType = PanicType.none; - calledBot.Brain.BaseBrain.CalcActionNextFrame(); - // Note: This differs from BSG's implementation in that we pass in botOwner_0's enemy pos instead of calledBot's enemy pos - calledBot.CalledData.TryCall(new Vector3?(___botOwner_0.Memory.GoalEnemy.Person.Position), ___botOwner_0, true); - __result = true; - } - else - { - __result = false; - } - - // Skip original - return false; - } - } -} diff --git a/project/SPT.Custom/Patches/IsEnemyPatch.cs b/project/SPT.Custom/Patches/IsEnemyPatch.cs index 1813f39..30375fe 100644 --- a/project/SPT.Custom/Patches/IsEnemyPatch.cs +++ b/project/SPT.Custom/Patches/IsEnemyPatch.cs @@ -41,12 +41,6 @@ namespace SPT.Custom.Patches } var isEnemy = false; // default not an enemy - if (requester == null) - { - __result = isEnemy; - - return false; // Skip original - } // Check existing enemies list // Could also check x.Value.Player?.Id - BSG do it this way diff --git a/project/SPT.Custom/SPTCustomPlugin.cs b/project/SPT.Custom/SPTCustomPlugin.cs index 10173ae..77e2b56 100644 --- a/project/SPT.Custom/SPTCustomPlugin.cs +++ b/project/SPT.Custom/SPTCustomPlugin.cs @@ -35,7 +35,6 @@ namespace SPT.Custom new VersionLabelPatch().Enable(); new IsEnemyPatch().Enable(); new BotCalledDataTryCallPatch().Enable(); - new BotCallForHelpCallBotPatch().Enable(); new BotOwnerDisposePatch().Enable(); new LocationLootCacheBustingPatch().Enable(); //new AddSelfAsEnemyPatch().Enable(); From cc674f5d4816e98d4b84d52f364cb867bfa30def Mon Sep 17 00:00:00 2001 From: Dev Date: Fri, 2 Aug 2024 16:03:17 +0100 Subject: [PATCH 17/27] Removed unused patches: BotOwnerDisposePatch BotEnemyTargetPatch BotCalledDataTryCallPatch BotSelfEnemyPatch AddEnemyToAllGroupsInBotZonePatch --- .../Patches/BotCalledDataTryCallPatch.cs | 49 ----------------- .../SPT.Custom/Patches/BotEnemyTargetPatch.cs | 52 ------------------- .../Patches/BotOwnerDisposePatch.cs | 28 ---------- .../SPT.Custom/Patches/BotSelfEnemyPatch.cs | 40 -------------- project/SPT.Custom/SPTCustomPlugin.cs | 5 +- 5 files changed, 1 insertion(+), 173 deletions(-) delete mode 100644 project/SPT.Custom/Patches/BotCalledDataTryCallPatch.cs delete mode 100644 project/SPT.Custom/Patches/BotEnemyTargetPatch.cs delete mode 100644 project/SPT.Custom/Patches/BotOwnerDisposePatch.cs delete mode 100644 project/SPT.Custom/Patches/BotSelfEnemyPatch.cs diff --git a/project/SPT.Custom/Patches/BotCalledDataTryCallPatch.cs b/project/SPT.Custom/Patches/BotCalledDataTryCallPatch.cs deleted file mode 100644 index 663a56f..0000000 --- a/project/SPT.Custom/Patches/BotCalledDataTryCallPatch.cs +++ /dev/null @@ -1,49 +0,0 @@ -using SPT.Reflection.Patching; -using EFT; -using HarmonyLib; -using System.Reflection; - -namespace SPT.Custom.Patches -{ - /** - * It's possible for `AddEnemy` to return false, in that case, further code in TryCall will fail, - * so we do the first bit of `TryCall` ourselves, and skip the original function if AddEnemy fails - */ - public class BotCalledDataTryCallPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() - { - return AccessTools.Method(typeof(BotCalledData), nameof(BotCalledData.TryCall)); - } - - [PatchPrefix] - private static bool PatchPrefix(ref bool __result, BotOwner caller, BotOwner ___botOwner_0, BotOwner ____caller) - { - if (___botOwner_0.EnemiesController.IsEnemy(caller.AIData.Player) || ____caller != null) - { - __result = false; - - // Skip original - return false; - } - - if (caller.Memory.GoalEnemy != null) - { - IPlayer person = caller.Memory.GoalEnemy.Person; - if (!___botOwner_0.BotsGroup.Enemies.ContainsKey(person)) - { - if (!___botOwner_0.BotsGroup.AddEnemy(person, EBotEnemyCause.callBot)) - { - __result = false; - - // Skip original - return false; - } - } - } - - // Allow original - return true; - } - } -} diff --git a/project/SPT.Custom/Patches/BotEnemyTargetPatch.cs b/project/SPT.Custom/Patches/BotEnemyTargetPatch.cs deleted file mode 100644 index 9810079..0000000 --- a/project/SPT.Custom/Patches/BotEnemyTargetPatch.cs +++ /dev/null @@ -1,52 +0,0 @@ -using SPT.Reflection.Patching; -using EFT; -using System.Reflection; -using HarmonyLib; - -namespace SPT.Custom.Patches -{ - public class BotEnemyTargetPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() - { - return AccessTools.Method(typeof(BotsController), nameof(BotsController.AddEnemyToAllGroupsInBotZone)); - } - - /// - /// AddEnemyToAllGroupsInBotZone() - /// Goal: by default, AddEnemyToAllGroupsInBotZone doesn't check if the bot group is on the same side as the player. - /// The effect of this is that when you are a Scav and kill a Usec, every bot group in the zone will aggro you including other Scavs. - /// This should fix that. - /// - [PatchPrefix] - private static bool PatchPrefix(BotsController __instance, IPlayer aggressor, IPlayer groupOwner, IPlayer target) - { - BotZone botZone = groupOwner.AIData.BotOwner.BotsGroup.BotZone; - foreach (var item in __instance.Groups()) - { - if (item.Key != botZone) - { - continue; - } - - foreach (var group in item.Value.GetGroups(notNull: true)) - { - if (!group.Enemies.ContainsKey(aggressor) && ShouldAttack(aggressor, target, group)) - { - group.AddEnemy(aggressor, EBotEnemyCause.AddEnemyToAllGroupsInBotZone); - } - } - } - - return false; - } - private static bool ShouldAttack(IPlayer attacker, IPlayer victim, BotsGroup groupToCheck) - { - // Group should target if player attack a victim on the same side or if the group is not on the same side as the player. - bool shouldAttack = attacker.Side != groupToCheck.Side - || attacker.Side == victim.Side; - - return !groupToCheck.HaveMemberWithRole(WildSpawnType.gifter) && groupToCheck.ShallRevengeFor(victim) && shouldAttack; - } - } -} diff --git a/project/SPT.Custom/Patches/BotOwnerDisposePatch.cs b/project/SPT.Custom/Patches/BotOwnerDisposePatch.cs deleted file mode 100644 index 9242e71..0000000 --- a/project/SPT.Custom/Patches/BotOwnerDisposePatch.cs +++ /dev/null @@ -1,28 +0,0 @@ -using SPT.Reflection.Patching; -using EFT; -using HarmonyLib; -using System.Reflection; - -namespace SPT.Custom.Patches -{ - /** - * BotOwner doesn't call SetOff on the CalledData object when a bot is disposed, this can result - * in bots that are no longer alive having their `OnEnemyAdd` method called - */ - public class BotOwnerDisposePatch : ModulePatch - { - protected override MethodBase GetTargetMethod() - { - return AccessTools.Method(typeof(BotOwner), nameof(BotOwner.Dispose)); - } - - [PatchPrefix] - private static void PatchPrefix(BotOwner __instance) - { - if (__instance.CalledData != null) - { - __instance.CalledData.SetOff(); - } - } - } -} diff --git a/project/SPT.Custom/Patches/BotSelfEnemyPatch.cs b/project/SPT.Custom/Patches/BotSelfEnemyPatch.cs deleted file mode 100644 index 97a0482..0000000 --- a/project/SPT.Custom/Patches/BotSelfEnemyPatch.cs +++ /dev/null @@ -1,40 +0,0 @@ -using SPT.Reflection.Patching; -using EFT; -using System.Reflection; -using HarmonyLib; - -namespace SPT.Custom.Patches -{ - /// - /// Goal: patch removes the current bot from its own enemy list - occurs when adding bots type to its enemy array in difficulty settings - /// - public class BotSelfEnemyPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() - { - return AccessTools.Method(typeof(BotOwner), nameof(BotOwner.PreActivate)); - } - - [PatchPrefix] - private static bool PatchPrefix(BotOwner __instance, BotsGroup group) - { - IPlayer selfToRemove = null; - - foreach (var enemy in group.Enemies) - { - if (enemy.Key.Id == __instance.Id) - { - selfToRemove = enemy.Key; - break; - } - } - - if (selfToRemove != null) - { - group.Enemies.Remove(selfToRemove); - } - - return true; - } - } -} diff --git a/project/SPT.Custom/SPTCustomPlugin.cs b/project/SPT.Custom/SPTCustomPlugin.cs index 77e2b56..5d942b9 100644 --- a/project/SPT.Custom/SPTCustomPlugin.cs +++ b/project/SPT.Custom/SPTCustomPlugin.cs @@ -34,13 +34,10 @@ namespace SPT.Custom // new SessionIdPatch().Enable(); new VersionLabelPatch().Enable(); new IsEnemyPatch().Enable(); - new BotCalledDataTryCallPatch().Enable(); - new BotOwnerDisposePatch().Enable(); new LocationLootCacheBustingPatch().Enable(); //new AddSelfAsEnemyPatch().Enable(); new CheckAndAddEnemyPatch().Enable(); - new BotSelfEnemyPatch().Enable(); // needed - new AddEnemyToAllGroupsInBotZonePatch().Enable(); + //new AddEnemyToAllGroupsInBotZonePatch().Enable(); new AirdropPatch().Enable(); new AirdropFlarePatch().Enable(); //new AddSptBotSettingsPatch().Enable(); From 2faf204d77be09db5116c50d8eff17d710d076ce Mon Sep 17 00:00:00 2001 From: Dev Date: Fri, 2 Aug 2024 17:25:50 +0100 Subject: [PATCH 18/27] Revert "Removed unused patches:" This reverts commit cc674f5d4816e98d4b84d52f364cb867bfa30def. --- .../Patches/BotCalledDataTryCallPatch.cs | 49 +++++++++++++++++ .../SPT.Custom/Patches/BotEnemyTargetPatch.cs | 52 +++++++++++++++++++ .../Patches/BotOwnerDisposePatch.cs | 28 ++++++++++ .../SPT.Custom/Patches/BotSelfEnemyPatch.cs | 40 ++++++++++++++ project/SPT.Custom/SPTCustomPlugin.cs | 5 +- 5 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 project/SPT.Custom/Patches/BotCalledDataTryCallPatch.cs create mode 100644 project/SPT.Custom/Patches/BotEnemyTargetPatch.cs create mode 100644 project/SPT.Custom/Patches/BotOwnerDisposePatch.cs create mode 100644 project/SPT.Custom/Patches/BotSelfEnemyPatch.cs diff --git a/project/SPT.Custom/Patches/BotCalledDataTryCallPatch.cs b/project/SPT.Custom/Patches/BotCalledDataTryCallPatch.cs new file mode 100644 index 0000000..663a56f --- /dev/null +++ b/project/SPT.Custom/Patches/BotCalledDataTryCallPatch.cs @@ -0,0 +1,49 @@ +using SPT.Reflection.Patching; +using EFT; +using HarmonyLib; +using System.Reflection; + +namespace SPT.Custom.Patches +{ + /** + * It's possible for `AddEnemy` to return false, in that case, further code in TryCall will fail, + * so we do the first bit of `TryCall` ourselves, and skip the original function if AddEnemy fails + */ + public class BotCalledDataTryCallPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(BotCalledData), nameof(BotCalledData.TryCall)); + } + + [PatchPrefix] + private static bool PatchPrefix(ref bool __result, BotOwner caller, BotOwner ___botOwner_0, BotOwner ____caller) + { + if (___botOwner_0.EnemiesController.IsEnemy(caller.AIData.Player) || ____caller != null) + { + __result = false; + + // Skip original + return false; + } + + if (caller.Memory.GoalEnemy != null) + { + IPlayer person = caller.Memory.GoalEnemy.Person; + if (!___botOwner_0.BotsGroup.Enemies.ContainsKey(person)) + { + if (!___botOwner_0.BotsGroup.AddEnemy(person, EBotEnemyCause.callBot)) + { + __result = false; + + // Skip original + return false; + } + } + } + + // Allow original + return true; + } + } +} diff --git a/project/SPT.Custom/Patches/BotEnemyTargetPatch.cs b/project/SPT.Custom/Patches/BotEnemyTargetPatch.cs new file mode 100644 index 0000000..9810079 --- /dev/null +++ b/project/SPT.Custom/Patches/BotEnemyTargetPatch.cs @@ -0,0 +1,52 @@ +using SPT.Reflection.Patching; +using EFT; +using System.Reflection; +using HarmonyLib; + +namespace SPT.Custom.Patches +{ + public class BotEnemyTargetPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(BotsController), nameof(BotsController.AddEnemyToAllGroupsInBotZone)); + } + + /// + /// AddEnemyToAllGroupsInBotZone() + /// Goal: by default, AddEnemyToAllGroupsInBotZone doesn't check if the bot group is on the same side as the player. + /// The effect of this is that when you are a Scav and kill a Usec, every bot group in the zone will aggro you including other Scavs. + /// This should fix that. + /// + [PatchPrefix] + private static bool PatchPrefix(BotsController __instance, IPlayer aggressor, IPlayer groupOwner, IPlayer target) + { + BotZone botZone = groupOwner.AIData.BotOwner.BotsGroup.BotZone; + foreach (var item in __instance.Groups()) + { + if (item.Key != botZone) + { + continue; + } + + foreach (var group in item.Value.GetGroups(notNull: true)) + { + if (!group.Enemies.ContainsKey(aggressor) && ShouldAttack(aggressor, target, group)) + { + group.AddEnemy(aggressor, EBotEnemyCause.AddEnemyToAllGroupsInBotZone); + } + } + } + + return false; + } + private static bool ShouldAttack(IPlayer attacker, IPlayer victim, BotsGroup groupToCheck) + { + // Group should target if player attack a victim on the same side or if the group is not on the same side as the player. + bool shouldAttack = attacker.Side != groupToCheck.Side + || attacker.Side == victim.Side; + + return !groupToCheck.HaveMemberWithRole(WildSpawnType.gifter) && groupToCheck.ShallRevengeFor(victim) && shouldAttack; + } + } +} diff --git a/project/SPT.Custom/Patches/BotOwnerDisposePatch.cs b/project/SPT.Custom/Patches/BotOwnerDisposePatch.cs new file mode 100644 index 0000000..9242e71 --- /dev/null +++ b/project/SPT.Custom/Patches/BotOwnerDisposePatch.cs @@ -0,0 +1,28 @@ +using SPT.Reflection.Patching; +using EFT; +using HarmonyLib; +using System.Reflection; + +namespace SPT.Custom.Patches +{ + /** + * BotOwner doesn't call SetOff on the CalledData object when a bot is disposed, this can result + * in bots that are no longer alive having their `OnEnemyAdd` method called + */ + public class BotOwnerDisposePatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(BotOwner), nameof(BotOwner.Dispose)); + } + + [PatchPrefix] + private static void PatchPrefix(BotOwner __instance) + { + if (__instance.CalledData != null) + { + __instance.CalledData.SetOff(); + } + } + } +} diff --git a/project/SPT.Custom/Patches/BotSelfEnemyPatch.cs b/project/SPT.Custom/Patches/BotSelfEnemyPatch.cs new file mode 100644 index 0000000..97a0482 --- /dev/null +++ b/project/SPT.Custom/Patches/BotSelfEnemyPatch.cs @@ -0,0 +1,40 @@ +using SPT.Reflection.Patching; +using EFT; +using System.Reflection; +using HarmonyLib; + +namespace SPT.Custom.Patches +{ + /// + /// Goal: patch removes the current bot from its own enemy list - occurs when adding bots type to its enemy array in difficulty settings + /// + public class BotSelfEnemyPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(BotOwner), nameof(BotOwner.PreActivate)); + } + + [PatchPrefix] + private static bool PatchPrefix(BotOwner __instance, BotsGroup group) + { + IPlayer selfToRemove = null; + + foreach (var enemy in group.Enemies) + { + if (enemy.Key.Id == __instance.Id) + { + selfToRemove = enemy.Key; + break; + } + } + + if (selfToRemove != null) + { + group.Enemies.Remove(selfToRemove); + } + + return true; + } + } +} diff --git a/project/SPT.Custom/SPTCustomPlugin.cs b/project/SPT.Custom/SPTCustomPlugin.cs index 5d942b9..77e2b56 100644 --- a/project/SPT.Custom/SPTCustomPlugin.cs +++ b/project/SPT.Custom/SPTCustomPlugin.cs @@ -34,10 +34,13 @@ namespace SPT.Custom // new SessionIdPatch().Enable(); new VersionLabelPatch().Enable(); new IsEnemyPatch().Enable(); + new BotCalledDataTryCallPatch().Enable(); + new BotOwnerDisposePatch().Enable(); new LocationLootCacheBustingPatch().Enable(); //new AddSelfAsEnemyPatch().Enable(); new CheckAndAddEnemyPatch().Enable(); - //new AddEnemyToAllGroupsInBotZonePatch().Enable(); + new BotSelfEnemyPatch().Enable(); // needed + new AddEnemyToAllGroupsInBotZonePatch().Enable(); new AirdropPatch().Enable(); new AirdropFlarePatch().Enable(); //new AddSptBotSettingsPatch().Enable(); From 9357555280d8d342994e6e0b93a0413649a88a9c Mon Sep 17 00:00:00 2001 From: dwesterwick Date: Sat, 10 Aug 2024 08:09:26 +0000 Subject: [PATCH 19/27] Fix for DogtagPatch NRE when Weapon Name is Null (!154) Added null check for `damageInfo.Weapon.Name` in `DogtagPatch::UpdateDogtagItemWithDeathDetails` because it can be null in rare cases. I see this happen when Zryachiy or his followers kill a bot, and I'm not sure why. This will prevent an exception from being thrown and the bot becoming "broken". Reviewed-on: https://dev.sp-tarkov.com/SPT/Modules/pulls/154 Co-authored-by: dwesterwick Co-committed-by: dwesterwick --- project/SPT.SinglePlayer/Patches/Quests/DogtagPatch.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/project/SPT.SinglePlayer/Patches/Quests/DogtagPatch.cs b/project/SPT.SinglePlayer/Patches/Quests/DogtagPatch.cs index fb5a315..39b3959 100644 --- a/project/SPT.SinglePlayer/Patches/Quests/DogtagPatch.cs +++ b/project/SPT.SinglePlayer/Patches/Quests/DogtagPatch.cs @@ -94,7 +94,15 @@ namespace SPT.SinglePlayer.Patches.Quests itemComponent.Status = "Killed by "; itemComponent.KillerAccountId = aggressor.Profile.AccountId; itemComponent.KillerProfileId = aggressor.Profile.Id; - itemComponent.WeaponName = damageInfo.Weapon.Name; + + string weaponName = damageInfo.Weapon?.Name; + if (weaponName == null) + { + Logger.LogWarning($"DogtagPatch error > The weapon used by {itemComponent.KillerName} to kill {itemComponent.Nickname} is null"); + weaponName = "???"; + } + + itemComponent.WeaponName = weaponName; if (__instance.Profile.Info.Experience > 0) { From 471c191bbb4daea7d8b56956eae67a29861781b4 Mon Sep 17 00:00:00 2001 From: Archangel Date: Wed, 14 Aug 2024 08:25:59 +0000 Subject: [PATCH 20/27] Remove bundle before re-acquiring (!155) Currently, the system knows which bundles are incorrect and attempts to re-download the correct ones.. However, upon next re-launch those bundles will again be invalid probably due to it not actually writing to them forcing the system to keep re-downloading. This fixes it by removing the invalid bundle, then re-downloading the proper one. Reviewed-on: https://dev.sp-tarkov.com/SPT/Modules/pulls/155 Co-authored-by: Archangel Co-committed-by: Archangel --- project/SPT.Custom/Patches/EasyAssetsPatch.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/project/SPT.Custom/Patches/EasyAssetsPatch.cs b/project/SPT.Custom/Patches/EasyAssetsPatch.cs index 090f383..06590ab 100644 --- a/project/SPT.Custom/Patches/EasyAssetsPatch.cs +++ b/project/SPT.Custom/Patches/EasyAssetsPatch.cs @@ -97,6 +97,8 @@ namespace SPT.Custom.Patches // only download when connected externally if (await BundleManager.ShouldReaquire(bundleInfo)) { + VFS.DeleteFile(BundleManager.GetBundleFilePath(bundleInfo)); + await BundleManager.DownloadBundle(bundleInfo); } } From af96c37815cf6c49879c71950287d991363c0436 Mon Sep 17 00:00:00 2001 From: DanW Date: Thu, 15 Aug 2024 08:45:47 +0000 Subject: [PATCH 21/27] IsEnemyPatch Improvements (!156) Revised `IsEnemyPatch` with the following changes: * Added a check to see if the player is in the group's `Allies` collection (needed for mod support) * Added a check to see if the player is in the group (possibly needed for future mod support) * Fixed Zryachiy check (and added his followers) by allowing the EFT method to run for them * Refactoring for clarity and to reorder the checks based on priority: 1) Null check(s) 2) Check if the player is in the group or in its `Allies` or `Enemies` collections 3) Allow the EFT method to run for certain bot roles 4) Finally, add the player to the group's `Enemies` collection if needed **This is a big change, so please double-check my work!** I tested this in multiple raids, in several maps, with and without SAIN, and I didn't find any issues. Reviewed-on: https://dev.sp-tarkov.com/SPT/Modules/pulls/156 Co-authored-by: DanW Co-committed-by: DanW --- project/SPT.Custom/CustomAI/AiHelpers.cs | 16 +++ project/SPT.Custom/Patches/IsEnemyPatch.cs | 119 +++++++++++---------- 2 files changed, 80 insertions(+), 55 deletions(-) diff --git a/project/SPT.Custom/CustomAI/AiHelpers.cs b/project/SPT.Custom/CustomAI/AiHelpers.cs index bbc1979..60153c8 100644 --- a/project/SPT.Custom/CustomAI/AiHelpers.cs +++ b/project/SPT.Custom/CustomAI/AiHelpers.cs @@ -1,4 +1,5 @@ using EFT; +using System.Collections.Generic; namespace SPT.Custom.CustomAI { @@ -42,5 +43,20 @@ namespace SPT.Custom.CustomAI return false; } + + public static List GetAllMembers(this BotsGroup group) + { + List members = new List(); + + if (group != null) + { + for (int m = 0; m < group.MembersCount; m++) + { + members.Add(group.Member(m)); + } + } + + return members; + } } } diff --git a/project/SPT.Custom/Patches/IsEnemyPatch.cs b/project/SPT.Custom/Patches/IsEnemyPatch.cs index 30375fe..c0dae1e 100644 --- a/project/SPT.Custom/Patches/IsEnemyPatch.cs +++ b/project/SPT.Custom/Patches/IsEnemyPatch.cs @@ -1,8 +1,10 @@ -using SPT.Reflection.Patching; -using EFT; +using EFT; +using HarmonyLib; +using SPT.Custom.CustomAI; +using SPT.Reflection.Patching; +using System.Collections.Generic; using System.Linq; using System.Reflection; -using HarmonyLib; namespace SPT.Custom.Patches { @@ -25,7 +27,28 @@ namespace SPT.Custom.Patches if (requester == null) { __result = false; + return false; // Skip original + } + // Check existing enemies list + // Could also check x.Value.Player?.Id - BSG do it this way + if (!__instance.Enemies.IsNullOrEmpty() && __instance.Enemies.Any(x => x.Key?.Id == requester.Id)) + { + __result = true; + return false; // Skip original + } + + // Do not force bots to be enemies if they are allies + if (!__instance.Allies.IsNullOrEmpty() && __instance.Allies.Any(x => x?.Id == requester.Id)) + { + __result = false; + return false; // Skip original + } + + // Bots should not become hostile with their group members here. This is needed in case mods add mixed groups (i.e. BEAR's and USEC's). + if (__instance.GetAllMembers().Any(i => i?.Id == requester.Id)) + { + __result = false; return false; // Skip original } @@ -35,73 +58,59 @@ namespace SPT.Custom.Patches || __instance.InitialBotType == WildSpawnType.sectantWarrior || __instance.InitialBotType == WildSpawnType.sectantPriest || __instance.InitialBotType == WildSpawnType.sectactPriestEvent - || __instance.InitialBotType == WildSpawnType.ravangeZryachiyEvent) + || __instance.InitialBotType == WildSpawnType.ravangeZryachiyEvent + || __instance.InitialBotType == WildSpawnType.bossZryachiy + || __instance.InitialBotType == WildSpawnType.followerZryachiy) { return true; // Do original code } - var isEnemy = false; // default not an enemy - - // Check existing enemies list - // Could also check x.Value.Player?.Id - BSG do it this way - if (!__instance.Enemies.IsNullOrEmpty() && __instance.Enemies.Any(x => x.Key.Id == requester.Id)) + // Let EFT manage Rogue behavior toward PMC's + if (__instance.InitialBotType == WildSpawnType.exUsec + && __instance.Side == EPlayerSide.Savage + && requester.Side != EPlayerSide.Savage) { - __result = true; - return false; // Skip original - } - else - { - // Weird edge case - without this you get spammed with key already in enemy list error when you move around on lighthouse - // Make zryachiy use existing isEnemy() code - if (__instance.InitialBotType == WildSpawnType.bossZryachiy) - { - return false; // Skip original - } - - if (__instance.Side == EPlayerSide.Usec) - { - if (requester.Side == EPlayerSide.Bear || requester.Side == EPlayerSide.Savage || - ShouldAttackUsec(requester)) - { - isEnemy = true; - __instance.AddEnemy(requester, EBotEnemyCause.checkAddTODO); - } - } - else if (__instance.Side == EPlayerSide.Bear) - { - if (requester.Side == EPlayerSide.Usec || requester.Side == EPlayerSide.Savage || - ShouldAttackBear(requester)) - { - isEnemy = true; - __instance.AddEnemy(requester, EBotEnemyCause.checkAddTODO); - } - } - else if (__instance.Side == EPlayerSide.Savage) - { - if (requester.Side != EPlayerSide.Savage) - { - //Lets exUsec warn Usecs and fire at will at Bears - if (__instance.InitialBotType == WildSpawnType.exUsec) - { - return true; // Let BSG handle things - } - // everyone else is an enemy to savage (scavs) - isEnemy = true; - __instance.AddEnemy(requester, EBotEnemyCause.checkAddTODO); - } - } + return true; // Do original code } - __result = isEnemy; + // In all other cases, requester needs to be added to the enemies collection of the bot group if it should be treated as hostile + // NOTE: Manually adding enemies is needed as a result of EFT's implementation of PMC's because they are not hostile toward + // Scavs (any probably other bot types too) + __result = CheckIfPlayerShouldBeEnemy(__instance, requester); + if (__result) + { + __instance.AddEnemy(requester, EBotEnemyCause.checkAddTODO); + } return false; // Skip original } + /// + /// Returns true if requester should be an enemy of the bot group + /// + /// + /// + /// + private static bool CheckIfPlayerShouldBeEnemy(BotsGroup __instance, IPlayer requester) + { + switch (__instance.Side) + { + case EPlayerSide.Usec: + return requester.Side != EPlayerSide.Usec || ShouldAttackUsec(requester); + case EPlayerSide.Bear: + return requester.Side != EPlayerSide.Bear || ShouldAttackBear(requester); + case EPlayerSide.Savage: + return requester.Side != EPlayerSide.Savage; + } + + return false; + } + /// /// Return True when usec default behavior is attack + bot is usec /// /// - /// bool + /// private static bool ShouldAttackUsec(IPlayer requester) { var requesterMind = requester?.AIData?.BotOwner?.Settings?.FileSettings?.Mind; From ebac61e0a472d019745a72b9a81eb20c9e507410 Mon Sep 17 00:00:00 2001 From: dwesterwick Date: Fri, 16 Aug 2024 17:35:35 +0000 Subject: [PATCH 22/27] IsEnemyPatch Improvements (Part 2) (!157) Made the following changes to !156: * Replaced `IsNullOrEmpty()` EFT checks with basic null checks * Replaced `Any()` with new helper method to check if bots exist in collections Reviewed-on: https://dev.sp-tarkov.com/SPT/Modules/pulls/157 Co-authored-by: dwesterwick Co-committed-by: dwesterwick --- project/SPT.Custom/CustomAI/AiHelpers.cs | 26 +++++++++++++++++++++- project/SPT.Custom/Patches/IsEnemyPatch.cs | 18 +++++++-------- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/project/SPT.Custom/CustomAI/AiHelpers.cs b/project/SPT.Custom/CustomAI/AiHelpers.cs index 60153c8..fc99a2b 100644 --- a/project/SPT.Custom/CustomAI/AiHelpers.cs +++ b/project/SPT.Custom/CustomAI/AiHelpers.cs @@ -47,7 +47,7 @@ namespace SPT.Custom.CustomAI public static List GetAllMembers(this BotsGroup group) { List members = new List(); - + if (group != null) { for (int m = 0; m < group.MembersCount; m++) @@ -58,5 +58,29 @@ namespace SPT.Custom.CustomAI return members; } + + /// + /// Returns true if the player is found in the collection by searching for matching player Id's + /// + /// + /// + /// + public static bool ContainsPlayer(this IEnumerable players, IPlayer playerToCheck) + { + if (playerToCheck == null) + { + return false; + } + + foreach (IPlayer player in players) + { + if (player.Id == playerToCheck.Id) + { + return true; + } + } + + return false; + } } } diff --git a/project/SPT.Custom/Patches/IsEnemyPatch.cs b/project/SPT.Custom/Patches/IsEnemyPatch.cs index c0dae1e..50197cb 100644 --- a/project/SPT.Custom/Patches/IsEnemyPatch.cs +++ b/project/SPT.Custom/Patches/IsEnemyPatch.cs @@ -32,33 +32,33 @@ namespace SPT.Custom.Patches // Check existing enemies list // Could also check x.Value.Player?.Id - BSG do it this way - if (!__instance.Enemies.IsNullOrEmpty() && __instance.Enemies.Any(x => x.Key?.Id == requester.Id)) + if (__instance.Enemies != null && __instance.Enemies.Keys.ContainsPlayer(requester)) { __result = true; return false; // Skip original } // Do not force bots to be enemies if they are allies - if (!__instance.Allies.IsNullOrEmpty() && __instance.Allies.Any(x => x?.Id == requester.Id)) + if (__instance.Allies != null && __instance.Allies.ContainsPlayer(requester)) { __result = false; return false; // Skip original } // Bots should not become hostile with their group members here. This is needed in case mods add mixed groups (i.e. BEAR's and USEC's). - if (__instance.GetAllMembers().Any(i => i?.Id == requester.Id)) + if (__instance.GetAllMembers().ContainsPlayer(requester)) { __result = false; return false; // Skip original } if (__instance.InitialBotType == WildSpawnType.peacefullZryachiyEvent - || __instance.InitialBotType == WildSpawnType.shooterBTR - || __instance.InitialBotType == WildSpawnType.gifter - || __instance.InitialBotType == WildSpawnType.sectantWarrior - || __instance.InitialBotType == WildSpawnType.sectantPriest - || __instance.InitialBotType == WildSpawnType.sectactPriestEvent - || __instance.InitialBotType == WildSpawnType.ravangeZryachiyEvent + || __instance.InitialBotType == WildSpawnType.shooterBTR + || __instance.InitialBotType == WildSpawnType.gifter + || __instance.InitialBotType == WildSpawnType.sectantWarrior + || __instance.InitialBotType == WildSpawnType.sectantPriest + || __instance.InitialBotType == WildSpawnType.sectactPriestEvent + || __instance.InitialBotType == WildSpawnType.ravangeZryachiyEvent || __instance.InitialBotType == WildSpawnType.bossZryachiy || __instance.InitialBotType == WildSpawnType.followerZryachiy) { From f8eb2374cbd7bbeb7199354a383cf50726d7c218 Mon Sep 17 00:00:00 2001 From: dwesterwick Date: Sat, 17 Aug 2024 07:05:43 +0000 Subject: [PATCH 23/27] IsEnemyPatch Improvements (Part 3) (!158) Added null check to new `ContainsPlayer` helper method from !157 Reviewed-on: https://dev.sp-tarkov.com/SPT/Modules/pulls/158 Co-authored-by: dwesterwick Co-committed-by: dwesterwick --- project/SPT.Custom/CustomAI/AiHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/SPT.Custom/CustomAI/AiHelpers.cs b/project/SPT.Custom/CustomAI/AiHelpers.cs index fc99a2b..b824488 100644 --- a/project/SPT.Custom/CustomAI/AiHelpers.cs +++ b/project/SPT.Custom/CustomAI/AiHelpers.cs @@ -74,7 +74,7 @@ namespace SPT.Custom.CustomAI foreach (IPlayer player in players) { - if (player.Id == playerToCheck.Id) + if (player != null && player.Id == playerToCheck.Id) { return true; } From f235ee21b70da764f255540f201ab72f0b548758 Mon Sep 17 00:00:00 2001 From: dwesterwick Date: Sat, 17 Aug 2024 07:43:42 +0000 Subject: [PATCH 24/27] LighthouseProgressionClass Improvements (!159) * Bug fix for getting negative Lightkeeper rep and disabling your transmitter only when killing Zryachiy, not his followers * Revised the class to support multiple players having access to the island, not only the main player * Added support for having multiple Lightkeeper-friendly PMC's on the island (currently requires mod support). Killing one of them while on the island will also result in getting reduced Lightkeeper rep and disabling your transmitter. * Separated location changes into discrete methods and made them public so mods can call them individually * Added static property that determines if the main player's transmitter status should open the island for everyone on the map. Also added property indicating if the island has been opened for everyone. This allows mods to override SPT's behavior and not have the island opened to everyone if the main player has access. * Rewrote the class so that the cyclic `Update()` method is no longer needed Co-authored-by: chomp Reviewed-on: https://dev.sp-tarkov.com/SPT/Modules/pulls/159 Co-authored-by: dwesterwick Co-committed-by: dwesterwick --- .../Progression/LighthouseProgressionClass.cs | 363 +++++++++++++----- .../Progression/LighthouseBridgePatch.cs | 2 +- 2 files changed, 259 insertions(+), 106 deletions(-) diff --git a/project/SPT.SinglePlayer/Models/Progression/LighthouseProgressionClass.cs b/project/SPT.SinglePlayer/Models/Progression/LighthouseProgressionClass.cs index 6021931..d0a84b8 100644 --- a/project/SPT.SinglePlayer/Models/Progression/LighthouseProgressionClass.cs +++ b/project/SPT.SinglePlayer/Models/Progression/LighthouseProgressionClass.cs @@ -1,5 +1,8 @@ -using Comfort.Common; +using BepInEx.Logging; +using Comfort.Common; using EFT; +using SPT.Reflection.Patching; +using System; using System.Collections.Generic; using System.Linq; using UnityEngine; @@ -8,123 +11,236 @@ namespace SPT.SinglePlayer.Models.Progression { public class LighthouseProgressionClass : MonoBehaviour { + /// + /// Flag to disable mines and AI data that instructs Zryachiy to attack you if the main player is authorized to enter Lightkeeper Island + /// + public static bool MainPlayerControlsIslandAccessForEveryone { get; set; } = true; + + /// + /// Flag indicating if the Lightkeeper-Island bridge mines and AI data that instructs Zryachiy to attack you have been disabled for everyone + /// + public bool IsIslandOpenForEveryone { get; private set; } = false; + + private static readonly string _transmitterId = "62e910aaf957f2915e0a5e36"; + private static readonly string _lightKeeperTid = "638f541a29ffd1183d187f57"; + private GameWorld _gameWorld; - private Player _player; - private float _timer; - private List _bridgeMines; - private RecodableItemClass _transmitter; - private readonly List _zryachiyAndFollowers = new List(); - private bool _aggressor; - private bool _isDoorDisabled; - private readonly string _transmitterId = "62e910aaf957f2915e0a5e36"; - private readonly string _lightKeeperTid = "638f541a29ffd1183d187f57"; + private ManualLogSource _logger; + private List lightkeeperFriendlyPlayers = new List(); + private List playersOnIsland = new List(); + + /// + /// PMC's that have been reported to be Lightkeeper-friendly + /// + public IReadOnlyList LightkeeperFriendlyPlayers => lightkeeperFriendlyPlayers.AsReadOnly(); + + /// + /// PMC's that have been reported to be on Lightkeeper Island + /// + public IReadOnlyList LightkeeperFriendlyPlayersOnIsland => playersOnIsland.AsReadOnly(); public void Start() { _gameWorld = Singleton.Instance; - _player = _gameWorld?.MainPlayer; - if (_gameWorld == null || _player == null) + if (_gameWorld == null || _gameWorld.MainPlayer == null) { - Destroy(this); - return; } + _logger = BepInEx.Logging.Logger.CreateLogSource(nameof(ModulePatch)); - // Get transmitter from players inventory - _transmitter = GetTransmitterFromInventory(); + // Watch for Zryachiy and his followers to spawn + Singleton.Instance.BotsController.BotSpawner.OnBotCreated += botCreated; // Exit if transmitter does not exist and isnt green - if (!PlayerHasActiveTransmitterInInventory()) + if (CheckAndAddLightkeeperFriendlyPlayer(_gameWorld.MainPlayer) && MainPlayerControlsIslandAccessForEveryone) { - Destroy(this); - - return; + AllowEveryoneAccessToLightkeeperIsland(); } - - var places = Singleton.Instance.BotsController.CoversData.AIPlaceInfoHolder.Places; - - places.First(x => x.name == "Attack").gameObject.SetActive(false); - - // Zone was added in a newer version and the gameObject actually has a \ - places.First(y => y.name == "CloseZone\\").gameObject.SetActive(false); - - // Give access to Lightkeepers door - _gameWorld.BufferZoneController.SetPlayerAccessStatus(_player.ProfileId, true); - - _bridgeMines = _gameWorld.MineManager.Mines; - - // Set mines to be non-active - SetBridgeMinesStatus(false); } - public void Update() + /// + /// Check if the player has been added to the Lightkeeper-friendly PMC list + /// + public bool IsALightkeeperFriendlyPlayer(IPlayer player) { - IncrementLastUpdateTimer(); + return player != null && lightkeeperFriendlyPlayers.Contains(player); + } - // Exit early if last update() run time was < 10 secs ago - if (_timer < 10f) + /// + /// Check if the player has been added to the list of Lightkeeper-friendly PMC's on Lightkeeper Island + /// + public bool IsLightkeeperFriendlyPlayerOnIsland(IPlayer player) + { + return player != null && playersOnIsland.Contains(player); + } + + /// + /// Checks if the player has an active transmitter in its inventory, and if so add it to the Lightkeeper-friendly PMC list + /// + /// True if the player was added to the Lightkeeper-friendly PMC list + public bool CheckAndAddLightkeeperFriendlyPlayer(IPlayer player) + { + if (PlayerHasActiveTransmitterInInventory(player)) + { + return AddLightkeeperFriendlyPlayer(player); + } + + return false; + } + + /// + /// Add the player to the Lightkeeper-friendly PMC list + /// + /// True if the player was added to the Lightkeeper-friendly PMC list + public bool AddLightkeeperFriendlyPlayer(IPlayer player) + { + if (player == null) + { + return false; + } + + if (lightkeeperFriendlyPlayers.Contains(player)) + { + _logger.LogWarning($"{player.Profile.Nickname} is already a registered Lightkeeper-friendly player"); + return false; + } + + lightkeeperFriendlyPlayers.Add(player); + + // Give access to Lightkeepers door + _gameWorld.BufferZoneController.SetPlayerAccessStatus(player.ProfileId, true); + + return true; + } + + /// + /// Remove the player from the Lightkeeper-friendly PMC list + /// + /// True if the player was removed from the Lightkeeper-friendly PMC list + public bool RemoveLightkeeperFriendlyPlayer(IPlayer player) + { + if (player == null) + { + return false; + } + + if (!lightkeeperFriendlyPlayers.Contains(player)) + { + _logger.LogWarning($"{player.Profile.Nickname} is not a registered Lightkeeper-friendly player"); + return false; + } + + lightkeeperFriendlyPlayers.Remove(player); + + // Revoke access to Lightkeepers door + _gameWorld.BufferZoneController.SetPlayerAccessStatus(player.ProfileId, false); + + return true; + } + + /// + /// Add the player to the list of PMC's that are on Lightkeeper Island + /// + public void LightkeeperFriendlyPlayerEnteredIsland(Player player) + { + if (playersOnIsland.Contains(player)) + { + _logger.LogWarning($"{player.name} is already a registered player on Lightkeeper Island"); + return; + } + + playersOnIsland.Add(player); + player.OnPlayerDead += OnLightkeeperFriendlyPlayerDead; + } + + /// + /// Remove the player from the list of PMC's that are on Lightkeeper Island + /// + public void LightkeeperFriendlyPlayerLeftIsland(Player player) + { + if (!playersOnIsland.Contains(player)) + { + _logger.LogWarning($"{player.name} is not a registered player on Lightkeeper Island"); + return; + } + + playersOnIsland.Remove(player); + player.OnPlayerDead -= OnLightkeeperFriendlyPlayerDead; + } + + /// + /// Disables brige mines, disables AI data to instruct Zryachiy to attack you, and watch for Zryachiy and his followers to spawn + /// + public void AllowEveryoneAccessToLightkeeperIsland() + { + if (IsIslandOpenForEveryone) { return; } - // Skip if: - // GameWorld missing - // Player not an enemy to Zryachiy - // Lk door not accessible - // Player has no transmitter on thier person - if (_gameWorld == null || _isDoorDisabled || _transmitter == null) - { - return; - } + DisableAIPlaceInfoForZryachiy(); - // Find Zryachiy and prep him - if (_zryachiyAndFollowers.Count == 0) - { - SetupZryachiyAndFollowerHostility(); - } + // Set mines to be non-active + SetBridgeMinesStatus(false); - // If player becomes aggressor, block access to LK - if (_aggressor) - { - DisableAccessToLightKeeper(); - } + IsIslandOpenForEveryone = true; + } + + /// + /// Disable the "Attack" and "CloseZone" AIPlaceInfo objects that instruct Zryachiy and his followers to attack you + /// + public void DisableAIPlaceInfoForZryachiy() + { + var places = Singleton.Instance.BotsController.CoversData.AIPlaceInfoHolder.Places; + + places.First(x => x.name == "Attack").gameObject.SetActive(false); + + // Zone was added in a newer version and the gameObject actually has a \ + places.First(y => y.name == "CloseZone\\").gameObject.SetActive(false); } /// /// Gets transmitter from players inventory /// - private RecodableItemClass GetTransmitterFromInventory() + public RecodableItemClass GetTransmitterFromInventory(IPlayer player) { - return (RecodableItemClass) _player.Profile.Inventory.AllRealPlayerItems.FirstOrDefault(x => x.TemplateId == _transmitterId); + if (player == null) + { + return null; + } + + return (RecodableItemClass)player.Profile.Inventory.AllRealPlayerItems.FirstOrDefault(x => x.TemplateId == _transmitterId); } /// /// Checks for transmitter status and exists in players inventory /// - private bool PlayerHasActiveTransmitterInInventory() + public bool PlayerHasActiveTransmitterInInventory(IPlayer player) { - return _transmitter != null && - _transmitter?.RecodableComponent?.Status == RadioTransmitterStatus.Green; + RecodableItemClass transmitter = GetTransmitterFromInventory(player); + return IsTransmitterActive(transmitter); } /// - /// Update _time to diff from last run of update() + /// Check if the transmitter allows access to the island /// - private void IncrementLastUpdateTimer() + public bool IsTransmitterActive(RecodableItemClass transmitter) { - _timer += Time.deltaTime; + return transmitter != null && transmitter?.RecodableComponent?.Status == RadioTransmitterStatus.Green; } /// /// Set all brdige mines to desire state /// /// What state should bridge mines be set to - private void SetBridgeMinesStatus(bool desiredMineState) + public void SetBridgeMinesStatus(bool desiredMineState) { - // Find mines with opposite state of what we want - var mines = _bridgeMines.Where(mine => mine.gameObject.activeSelf == !desiredMineState && mine.transform.parent.gameObject.name == "Directional_mines_LHZONE"); + // Find mines with opposite state of what we want + var mines = _gameWorld.MineManager.Mines + .Where(mine => IsLighthouseBridgeMine(mine) && mine.gameObject.activeSelf == !desiredMineState); + foreach (var mine in mines) { mine.gameObject.SetActive(desiredMineState); @@ -132,61 +248,98 @@ namespace SPT.SinglePlayer.Models.Progression } /// - /// Put Zryachiy and followers into a list and sub to their death event - /// Make player agressor if player kills them. + /// Check if the mine is on the Lightkeeper Island bridge /// - private void SetupZryachiyAndFollowerHostility() + /// True if the mine is on the Lightkeeper Island bridge + public static bool IsLighthouseBridgeMine(MineDirectional mine) { - // Only process non-players (ai) - foreach (var aiBot in _gameWorld.AllAlivePlayersList.Where(x => !x.IsYourPlayer)) + if (mine == null) { - // Bots that die on mounted guns get stuck in AllAlivePlayersList, need to check health - if (!aiBot.HealthController.IsAlive) + return false; + } + + return mine.transform.parent.gameObject.name == "Directional_mines_LHZONE"; + } + + /// + /// Set aggression + standing loss when Zryachiy/follower or a Lightkeeper-friendly PMC is killed by the main player + /// + /// The player that was killed + public void OnLightkeeperFriendlyPlayerDead(Player player, IPlayer lastAggressor, DamageInfo damageInfo, EBodyPart part) + { + foreach (Player lightkeeperFriendlyPlayer in lightkeeperFriendlyPlayers) + { + // Check if a Lightkeeper-friendly player was the killer + if ((lightkeeperFriendlyPlayer == null) || (lightkeeperFriendlyPlayer.ProfileId != player?.KillerId)) { continue; } - // Edge case of bossZryachiy not being hostile to player - if (aiBot.AIData.BotOwner.IsRole(WildSpawnType.bossZryachiy) || aiBot.AIData.BotOwner.IsRole(WildSpawnType.followerZryachiy)) + // A Lightkeeper-friendly player killed Zryachiy or one of his followers + if (isZryachiyOrFollower(player)) { - // Subscribe to bots OnDeath event - aiBot.OnPlayerDeadOrUnspawn += OnZryachiyOrFollowerDeath; + playerKilledLightkeeperFriendlyPlayer(lastAggressor); + break; + } - // Save bot to list for later access - if (!_zryachiyAndFollowers.Contains(aiBot)) - { - _zryachiyAndFollowers.Add(aiBot); - } + // A Lightkeeper-friendly player killed another Lightkeeper-friendly player when they were both on the island + if (playersOnIsland.Any(x => x?.Id == player?.Id) && playersOnIsland.Any(x => x?.Id == lastAggressor?.Id)) + { + playerKilledLightkeeperFriendlyPlayer(lastAggressor); + break; } } } - /// - /// Set aggression + standing loss when Zryachiy/follower is killed by player - /// - /// The player who killed Zryachiy/follower. - private void OnZryachiyOrFollowerDeath(Player player) + private void playerKilledLightkeeperFriendlyPlayer(IPlayer player) { - // Check if zryachiy/follower was killed by player - if (player?.KillerId == _player?.ProfileId) + if (player == null) { - // If player kills zryachiy or follower, force aggressor state - // Also set players Lk standing to negative (allows access to quest chain (Making Amends)) - _aggressor = true; - _player?.Profile.TradersInfo[_lightKeeperTid].SetStanding(-0.01); + return; + } + + // Set players Lk standing to negative (allows access to quest chain (Making Amends)) + player.Profile.TradersInfo[_lightKeeperTid].SetStanding(-0.01); + + // Disable access to Lightkeepers door for the player + _gameWorld.BufferZoneController.SetPlayerAccessStatus(player.ProfileId, false); + + RecodableItemClass transmitter = GetTransmitterFromInventory(player); + if ((transmitter != null) && IsTransmitterActive(transmitter)) + { + transmitter.RecodableComponent.SetStatus(RadioTransmitterStatus.Yellow); + transmitter.RecodableComponent.SetEncoded(false); + } + + RemoveLightkeeperFriendlyPlayer(player); + + _logger.LogInfo($"Removed Lightkeeper access for {player.Profile.Nickname}"); + } + + private void botCreated(BotOwner bot) + { + // Make sure the bot is Zryachiy or one of his followers + if (bot.Side != EPlayerSide.Savage) + { + return; + } + + // Check if the bot is Zryachiy or one of his followers + if (isZryachiyOrFollower(bot)) + { + // Subscribe to bots OnDeath event + bot.GetPlayer.OnPlayerDead += OnLightkeeperFriendlyPlayerDead; } } - /// - /// Disable door + set transmitter to 'red' - /// - private void DisableAccessToLightKeeper() + private static bool isZryachiyOrFollower(IPlayer player) { - // Disable access to Lightkeepers door for the player - _gameWorld.BufferZoneController.SetPlayerAccessStatus(_gameWorld.MainPlayer.ProfileId, false); - _transmitter?.RecodableComponent?.SetStatus(RadioTransmitterStatus.Yellow); - _transmitter?.RecodableComponent?.SetEncoded(false); - _isDoorDisabled = true; + if (player == null || !player.IsAI) + { + return false; + } + + return player.AIData.BotOwner.IsRole(WildSpawnType.bossZryachiy) || player.AIData.BotOwner.IsRole(WildSpawnType.followerZryachiy); } } } diff --git a/project/SPT.SinglePlayer/Patches/Progression/LighthouseBridgePatch.cs b/project/SPT.SinglePlayer/Patches/Progression/LighthouseBridgePatch.cs index 4db3ebc..85b6b82 100644 --- a/project/SPT.SinglePlayer/Patches/Progression/LighthouseBridgePatch.cs +++ b/project/SPT.SinglePlayer/Patches/Progression/LighthouseBridgePatch.cs @@ -24,7 +24,7 @@ namespace SPT.SinglePlayer.Patches.Progression return; } - if (gameWorld.MainPlayer.Location.ToLower() != "lighthouse" || gameWorld.MainPlayer.Side == EPlayerSide.Savage) + if (gameWorld.MainPlayer.Location.ToLower() != "lighthouse") { return; } From 939c85bc7cae493173b2a108e93bc6d6c2e95696 Mon Sep 17 00:00:00 2001 From: Archangel Date: Sun, 18 Aug 2024 15:49:22 +0000 Subject: [PATCH 25/27] Rename ShouldReaquire & check if bundle exists (!160) I didn't keep in mind that 'ShouldReaquire' handles both the initial check for acquiring of a file and also the re-acquiring of it, I've renamed the method here to be more accurate in this behavior so that hopefully it prevents this from happening again in the future. Also fixes the issue of an exception being thrown when checking if the bundle exists or not. Thanks to MadByte for finding this issue Reviewed-on: https://dev.sp-tarkov.com/SPT/Modules/pulls/160 Co-authored-by: Archangel Co-committed-by: Archangel --- project/SPT.Custom/Patches/EasyAssetsPatch.cs | 7 +++++-- project/SPT.Custom/Utils/BundleManager.cs | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/project/SPT.Custom/Patches/EasyAssetsPatch.cs b/project/SPT.Custom/Patches/EasyAssetsPatch.cs index 06590ab..280d27a 100644 --- a/project/SPT.Custom/Patches/EasyAssetsPatch.cs +++ b/project/SPT.Custom/Patches/EasyAssetsPatch.cs @@ -95,9 +95,12 @@ namespace SPT.Custom.Patches path = BundleManager.GetBundlePath(bundleInfo); // only download when connected externally - if (await BundleManager.ShouldReaquire(bundleInfo)) + if (await BundleManager.ShouldAcquire(bundleInfo)) { - VFS.DeleteFile(BundleManager.GetBundleFilePath(bundleInfo)); + if (VFS.Exists(BundleManager.GetBundleFilePath(bundleInfo))) + { + VFS.DeleteFile(BundleManager.GetBundleFilePath(bundleInfo)); + } await BundleManager.DownloadBundle(bundleInfo); } diff --git a/project/SPT.Custom/Utils/BundleManager.cs b/project/SPT.Custom/Utils/BundleManager.cs index 93f443b..ba0f749 100644 --- a/project/SPT.Custom/Utils/BundleManager.cs +++ b/project/SPT.Custom/Utils/BundleManager.cs @@ -51,7 +51,8 @@ namespace SPT.Custom.Utils await VFS.WriteFileAsync(filepath, data); } - public static async Task ShouldReaquire(BundleItem bundle) + // Handles both the check for initially acquiring and also re-acquiring a file. + public static async Task ShouldAcquire(BundleItem bundle) { // read cache var filepath = GetBundleFilePath(bundle); From af1a83fe718df499804a017b05f1fa6b6a88d20a Mon Sep 17 00:00:00 2001 From: Dev Date: Mon, 19 Aug 2024 09:06:32 +0100 Subject: [PATCH 26/27] Busted build cache --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 10be4b4..00de09d 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ git config --local user.name "USERNAME" git config --local user.email "USERNAME@SOMETHING.com" ``` + ## Requirements - Escape From Tarkov 30626 - Visual Studio Code -OR- Visual Studio 2022 From ab9d9c0e1386e3bf624afe926131c53f67e386aa Mon Sep 17 00:00:00 2001 From: Kaiden Date: Thu, 22 Aug 2024 08:11:58 +0000 Subject: [PATCH 27/27] Fixed recursion when using GetData syncronously (!161) Running [SPT.Common.Http.RequestHandler.GetData()](https://dev.sp-tarkov.com/SPT/Modules/src/commit/af1a83fe718df499804a017b05f1fa6b6a88d20a/project/SPT.Common/Http/RequestHandler.cs#L78) would run indefinitely because of an accidental recursion. Reviewed-on: https://dev.sp-tarkov.com/SPT/Modules/pulls/161 Co-authored-by: Kaiden Co-committed-by: Kaiden --- project/SPT.Common/Http/RequestHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/SPT.Common/Http/RequestHandler.cs b/project/SPT.Common/Http/RequestHandler.cs index 655a0f6..a12ee0a 100644 --- a/project/SPT.Common/Http/RequestHandler.cs +++ b/project/SPT.Common/Http/RequestHandler.cs @@ -75,7 +75,7 @@ namespace SPT.Common.Http public static byte[] GetData(string path) { - return Task.Run(() => GetData(path)).Result; + return Task.Run(() => GetDataAsync(path)).Result; } public static async Task GetJsonAsync(string path)