From bcc61cf9984e394b21b56883b252415b1e15f9c4 Mon Sep 17 00:00:00 2001 From: GrooveypenguinX Date: Fri, 23 Aug 2024 17:27:48 -0400 Subject: [PATCH 1/5] custom extracts fully implemented :sunglasses: --- PTT-Extracts/Core/CustomExtractHandler.cs | 266 ++++++++++++++++++ PTT-Extracts/PTTExtracts.csproj | 47 +++- .../Patches/InitAllExfiltrationPointsPatch.cs | 111 ++++++++ .../Patches/ScavExfiltrationPointPatch.cs | 25 ++ PTT-Extracts/Plugin.cs | 14 +- PTT-Extracts/Utils/WebRequestUtils.cs | 64 +++++ configs/Default/Tooltips.json | 8 +- configs/Default/config.json | 18 +- .../Default/customExtracts/exfilConfig.json | 155 ++++++++++ configs/Default/player_spawnpoints.json | 21 ++ src/all-exfils.ts | 35 ++- src/custom-extracts.ts | 121 ++++++++ src/instance-manager.ts | 98 +++++++ src/mod.ts | 12 +- src/path-to-tarkov-controller.ts | 6 + src/router-service.ts | 47 ++++ 16 files changed, 1027 insertions(+), 21 deletions(-) create mode 100644 PTT-Extracts/Core/CustomExtractHandler.cs create mode 100644 PTT-Extracts/Patches/InitAllExfiltrationPointsPatch.cs create mode 100644 PTT-Extracts/Patches/ScavExfiltrationPointPatch.cs create mode 100644 PTT-Extracts/Utils/WebRequestUtils.cs create mode 100644 configs/Default/customExtracts/exfilConfig.json create mode 100644 src/custom-extracts.ts create mode 100644 src/instance-manager.ts create mode 100644 src/router-service.ts diff --git a/PTT-Extracts/Core/CustomExtractHandler.cs b/PTT-Extracts/Core/CustomExtractHandler.cs new file mode 100644 index 00000000..4f175492 --- /dev/null +++ b/PTT-Extracts/Core/CustomExtractHandler.cs @@ -0,0 +1,266 @@ +#if !UNITY_EDITOR +using SPT.Reflection.Patching; +using SPT.Reflection.Utils; +using EFT.Interactive; +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using System.IO; +using Newtonsoft.Json; +using EFT.InventoryLogic; +using Comfort.Common; +using EFT; +using EFT.UI; +using static EFT.SpeedTree.TreeWind; +using UnityEngine.SceneManagement; +using PTTExtracts.Utils; +using SPT.Common.Http; +using System.Xml; + +namespace PTTExtracts.Core +{ + public static class CustomExtractHandler + { + public static List customExtracts; + + public static void PatchExfiltrationPoints(List pmcExfilList) + { + // Ensure that custom extracts are loaded + if (customExtracts == null) + { + Console.WriteLine("Custom extracts are not loaded. Requesting data from the server now..."); + CustomExtractUtils.LoadCustomExtracts(); // Ensure that the static field is set + } + + // If customExtracts is still null after attempting to load, log an error and return + if (customExtracts == null) + { + Console.WriteLine("Custom extracts could not be loaded. Exiting PatchExfiltrationPoints."); + return; + } + + string currentLocationId = CustomExtractUtils.GetCurrentLocationId()?.ToLower(); + + // Proceed with processing if customExtracts is not null + + foreach (var exitGroup in customExtracts) + { + if (exitGroup.Id.ToLower() != currentLocationId) + { + continue; + } + + foreach (var exitData in exitGroup.exits) + { + Vector3 position = new Vector3(exitData.CubeData.Position.x, exitData.CubeData.Position.y, exitData.CubeData.Position.z); + Vector3 scale = new Vector3(exitData.CubeData.Scale.x, exitData.CubeData.Scale.y, exitData.CubeData.Scale.z); + Quaternion rotation = Quaternion.Euler(exitData.CubeData.Rotation.x, exitData.CubeData.Rotation.y, exitData.CubeData.Rotation.z); + string name = exitData.CubeData.Name; + + GameObject newExtractObject = CustomExtractUtils.CreateExtractionGameObject(position, scale, rotation, name); + + if (newExtractObject == null) + { + continue; + } + + ExfiltrationPoint newExtractData = CustomExtractUtils.InitializeExtractionData(newExtractObject, exitData); + pmcExfilList.Add(newExtractData); + } + } + } + + } + + public class CustomExtractUtils + { + public static void LoadCustomExtracts() + { + try + { + var response = WebRequestUtils.Get>>("/PathToTarkov/CustomExtracts"); + + if (response != null && response.Success) + { + CustomExtractHandler.customExtracts = response.Data; + Console.WriteLine("Custom extracts successfully retrieved from the server."); + // Log the entire list by serializing it to JSON + var jsonExtracts = JsonConvert.SerializeObject(CustomExtractHandler.customExtracts, Newtonsoft.Json.Formatting.Indented); + + } + else + { + Console.WriteLine("Error retrieving custom extracts: No data or response was unsuccessful."); + } + } + catch (Exception ex) + { + Console.WriteLine("Error loading custom extracts: " + ex.Message); + } + } + + + public static string GetCurrentLocationId() + { + TarkovApplication tarkovApplication = (TarkovApplication)Singleton>.Instance; + + RaidSettings currentRaidSettings = (RaidSettings)typeof(TarkovApplication) + .GetField("_raidSettings", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(tarkovApplication); + + return currentRaidSettings?.SelectedLocation?.Id; + } + + public static GameObject CreateExtractionGameObject(Vector3 position, Vector3 scale, Quaternion rotation, string name) + { + try + { + GameObject newCube = GameObject.CreatePrimitive(PrimitiveType.Cube); + newCube.name = name; + newCube.GetComponent().enabled = false; + BoxCollider boxCollider = newCube.AddComponent(); + newCube.transform.position = position; + newCube.transform.localScale = scale; + newCube.transform.rotation = rotation; + newCube.AddComponent().ExtendedCollider = boxCollider; + + return newCube; + } + catch (Exception ex) + { + Console.WriteLine("Error creating new extract game object: " + ex.Message); + return null; + } + } + + public static ExfiltrationPoint InitializeExtractionData(GameObject extractObject, ExitData exitData) + { + try + { + ExfiltrationPoint exfiltrationPointScript = extractObject.GetComponent(); + // Assuming EExfiltrationStatus is an enum + if (Enum.TryParse(exitData?.AdditionalSettings?.Status, out EExfiltrationStatus parsedStatus)) + { + exfiltrationPointScript.Status = parsedStatus; + } + else + { + // Handle the case where parsing fails + Console.WriteLine($"Error parsing exfiltration status: {exitData?.AdditionalSettings?.Status}"); + // Sets default status to "RegularMode" + exfiltrationPointScript.Status = EExfiltrationStatus.RegularMode; + Console.WriteLine("Set status to RegularMode"); + } + + exfiltrationPointScript.ExfiltrationStartTime = 0f; + exfiltrationPointScript.Settings.Name = exitData?.ExtractData?.Name; + exfiltrationPointScript.Settings.ExfiltrationType = exitData?.ExtractData?.ExfiltrationType ?? default(EExfiltrationType); + exfiltrationPointScript.Settings.ExfiltrationTime = exitData?.ExtractData?.ExfiltrationTime ?? 0f; + exfiltrationPointScript.Settings.PlayersCount = exitData?.ExtractData?.PlayersCount ?? 0; + exfiltrationPointScript.Settings.Chance = exitData?.ExtractData?.Chance ?? 0; + exfiltrationPointScript.Settings.MinTime = exitData?.ExtractData?.MinTime ?? 0f; + exfiltrationPointScript.Settings.MaxTime = exitData?.ExtractData?.MaxTime ?? 0f; + exfiltrationPointScript.Settings.StartTime = exitData?.AdditionalSettings?.StartTime ?? 0; + + ExfiltrationRequirement requirement = new ExfiltrationRequirement(); + if (Enum.TryParse(exitData?.ExtractData?.Requirement, out ERequirementState parsedRequirement)) + { + requirement.Requirement = parsedRequirement; +#if DEBUG + Console.WriteLine($"Extract Requirement: {requirement.Requirement}"); +#endif + } + else + { + // Handle the case where the string doesn't match any enum value + Console.WriteLine($"Error parsing requirement state: {exitData?.ExtractData?.Requirement}"); + // Sets default state to None + requirement.Requirement = ERequirementState.None; + Console.WriteLine("Set requirement state to None"); + } + requirement.Id = exitData?.ExtractData?.Id; + requirement.Count = exitData?.ExtractData?.Count ?? 69; + if (Enum.TryParse(exitData?.ExtractData?.RequiredSlot, out EquipmentSlot parsedSlot)) + { + requirement.RequiredSlot = parsedSlot; +#if DEBUG + Console.WriteLine($"Parsed Required Slot: {requirement.RequiredSlot}"); +#endif + } + else + { + Console.WriteLine($"Error parsing equipment slot: {exitData?.ExtractData?.RequiredSlot}"); + } + requirement.RequirementTip = exitData?.ExtractData?.RequirementTip; // Replace with the desired tip +#if DEBUG + Console.WriteLine($"Requirement Tip: {requirement.RequirementTip}"); +#endif + exfiltrationPointScript.Requirements = new ExfiltrationRequirement[] { requirement }; +#if DEBUG + Console.WriteLine("Requirements added successfully."); +#endif + return exfiltrationPointScript; + + } + catch (Exception ex) + { + Console.WriteLine("Error initializing ExfiltrationPoint data: " + ex.Message); + return null; + } + } + + } + + public class ExitData + { + public ExtractData ExtractData { get; set; } + public AdditionalSettings AdditionalSettings { get; set; } + public CubeData CubeData { get; set; } // Add this property + } + public class PositionData + { + public float x { get; set; } + public float y { get; set; } + public float z { get; set; } + } + public class ExtractData + { + public int Chance { get; set; } + public string EntryPoints { get; set; } + public float ExfiltrationTime { get; set; } + public string Id { get; set; } + public float MaxTime { get; set; } + public float MinTime { get; set; } + public string Name { get; set; } + public int PlayersCount { get; set; } + public string Requirement { get; set; } + public int Count { get; set; } + public string RequiredSlot { get; set; } + public string RequirementTip { get; set; } + public EExfiltrationType ExfiltrationType { get; set; } // Add this property + } + public class AdditionalSettings + { + public int StartTime { get; set; } + public string Status { get; set; } + } + public class ExitGroup + { + public string Id { get; set; } + public List exits { get; set; } + } + public class CubeData + { + public string Name { get; set; } + public PositionData Position { get; set; } + public Vector3 Scale { get; set; } + public Vector3 Rotation { get; set; } + } + +} +#endif \ No newline at end of file diff --git a/PTT-Extracts/PTTExtracts.csproj b/PTT-Extracts/PTTExtracts.csproj index 08b1918e..a1cf44b5 100644 --- a/PTT-Extracts/PTTExtracts.csproj +++ b/PTT-Extracts/PTTExtracts.csproj @@ -3,32 +3,53 @@ netstandard2.0 PTTExtracts - Unlock scavs exfiltrations for PMCs (Patches for Path to Tarkov) - 1.0.1 + Extract Patches for Path to Tarkov + 1.0.0 true latest - - - - - - - ..\..\..\..\BepInEx\plugins\spt\spt-reflection.dll + + False + ..\..\..\..\BepInEx\core\0Harmony.dll + + + False + ..\..\..\..\EscapeFromTarkov_Data\Managed\Comfort.dll ..\..\..\..\EscapeFromTarkov_Data\Managed\Assembly-CSharp.dll - + + ..\..\..\..\EscapeFromTarkov_Data\Managed\Newtonsoft.Json.dll + + + ..\..\..\..\BepInEx\plugins\spt\spt-common.dll + + + ..\..\..\..\BepInEx\plugins\spt\spt-reflection.dll + + + ..\..\..\..\EscapeFromTarkov_Data\Managed\uLipSync.Runtime.dll + + + False ..\..\..\..\EscapeFromTarkov_Data\Managed\UnityEngine.dll - + + False ..\..\..\..\EscapeFromTarkov_Data\Managed\UnityEngine.CoreModule.dll - - + + ..\..\..\..\BepInEx\core\BepInEx.dll + + + ..\..\..\..\EscapeFromTarkov_Data\Managed\UnityEngine.PhysicsModule.dll + + + + diff --git a/PTT-Extracts/Patches/InitAllExfiltrationPointsPatch.cs b/PTT-Extracts/Patches/InitAllExfiltrationPointsPatch.cs new file mode 100644 index 00000000..527fe2c2 --- /dev/null +++ b/PTT-Extracts/Patches/InitAllExfiltrationPointsPatch.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using SPT.Reflection.Patching; +using EFT.Interactive; +using HarmonyLib; +using PTTExtracts.Core; + +namespace PTTExtracts.Patches +{ + public class InitAllExfiltrationPointsPatch : ModulePatch + { + public static bool NameMatches(LocationExitClass x) + { + return exitName == x.Name; + } + + public static string exitName; + + public static bool RandomRange(ExfiltrationPoint trigger) + { + return UnityEngine.Random.Range(0f, 100f) <= trigger.Settings.Chance; + } + + public static bool IsNotScavExfil(ExfiltrationPoint x) + { + return x is not ScavExfiltrationPoint || x is SharedExfiltrationPoint; + } + + public static bool IsScavExfil(ExfiltrationPoint x) + { + return x is ScavExfiltrationPoint; + } + + protected override MethodBase GetTargetMethod() + { + return typeof(ExfiltrationControllerClass).GetMethod("InitAllExfiltrationPoints", BindingFlags.Public | BindingFlags.Instance); + } + + [PatchPrefix] + private static bool PatchPrefix(ref ExfiltrationControllerClass __instance, LocationExitClass[] settings, bool justLoadSettings = false, bool giveAuthority = true) + { + ExfiltrationPoint[] source = LocationScene.GetAllObjects(false).ToArray(); + ExfiltrationPoint[] scavExfilArr = source.Where(new Func(IsScavExfil)).ToArray(); + ExfiltrationPoint[] pmcExfilArr = source.Where(new Func(IsNotScavExfil)).ToArray(); + + List pmcExfilList = pmcExfilArr.ToList(); + + CustomExtractHandler.PatchExfiltrationPoints(pmcExfilList); + + foreach (ExfiltrationPoint scavExfil in scavExfilArr) + { + if (!pmcExfilList.Any(k => k.Settings.Name == scavExfil.Settings.Name)) + { + pmcExfilList.Add(scavExfil); + } + } + + AccessTools.Property(typeof(ExfiltrationControllerClass), "ExfiltrationPoints").SetValue(__instance, pmcExfilList.ToArray()); + + AccessTools.Property(typeof(ExfiltrationControllerClass), "ScavExfiltrationPoints").SetValue(__instance, source.Where(new Func(IsScavExfil)).Cast().ToArray()); + + AccessTools.Field(typeof(ExfiltrationControllerClass), "list_0").SetValue(__instance, new List(__instance.ScavExfiltrationPoints.Length)); + AccessTools.Field(typeof(ExfiltrationControllerClass), "list_1").SetValue(__instance, new List()); + + List list_0 = (List)AccessTools.Field(typeof(ExfiltrationControllerClass), "list_0").GetValue(__instance); + List list_1 = (List)AccessTools.Field(typeof(ExfiltrationControllerClass), "list_1").GetValue(__instance); + + foreach (ScavExfiltrationPoint scavExfiltrationPoint in __instance.ScavExfiltrationPoints) + { + Logger.LogWarning("Scav Exfil name = " + scavExfiltrationPoint.Settings.Name); + SharedExfiltrationPoint sharedExfiltrationPoint; + if ((sharedExfiltrationPoint = (scavExfiltrationPoint as SharedExfiltrationPoint)) != null && sharedExfiltrationPoint.IsMandatoryForScavs) + { + list_1.Add(scavExfiltrationPoint); + } + else + { + list_0.Add(scavExfiltrationPoint); + } + } + AccessTools.Field(typeof(ExfiltrationControllerClass), "list_0").SetValue(__instance, list_0); + AccessTools.Field(typeof(ExfiltrationControllerClass), "list_1").SetValue(__instance, list_1); + + UnityEngine.Random.InitState((int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()); + + foreach (ExfiltrationPoint exfiltrationPoint in __instance.ExfiltrationPoints) + { + Logger.LogWarning("PMC Exfil name = " + exfiltrationPoint.Settings.Name); + exitName = exfiltrationPoint.Settings.Name; + LocationExitClass gclass = settings.FirstOrDefault(new Func(NameMatches)); + if (gclass != null) + { + exfiltrationPoint.LoadSettings(gclass, giveAuthority); + if (!justLoadSettings && !RandomRange(exfiltrationPoint)) + { + exfiltrationPoint.SetStatusLogged(EExfiltrationStatus.NotPresent, "ExfiltrationController.InitAllExfiltrationPoints-2"); + } + } + else + { + exfiltrationPoint.SetStatusLogged(EExfiltrationStatus.NotPresent, "ExfiltrationController.InitAllExfiltrationPoints-3"); + } + } + + return false; + } + } + +} diff --git a/PTT-Extracts/Patches/ScavExfiltrationPointPatch.cs b/PTT-Extracts/Patches/ScavExfiltrationPointPatch.cs new file mode 100644 index 00000000..0d118b23 --- /dev/null +++ b/PTT-Extracts/Patches/ScavExfiltrationPointPatch.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using SPT.Reflection.Patching; +using EFT.Interactive; +using HarmonyLib; + +namespace PTTExtracts.Patches +{ + public class ScavExfiltrationPointPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return typeof(ScavExfiltrationPoint).GetMethod("InfiltrationMatch", BindingFlags.Public | BindingFlags.Instance); + } + + [PatchPrefix] + private static bool Prefix(ref bool __result) + { + __result = true; + return false; + } + } +} diff --git a/PTT-Extracts/Plugin.cs b/PTT-Extracts/Plugin.cs index ea887d0f..368eeef0 100644 --- a/PTT-Extracts/Plugin.cs +++ b/PTT-Extracts/Plugin.cs @@ -1,16 +1,26 @@ using BepInEx; +using EFT.Quests; +using SPT.Reflection.Patching; +using System.Reflection; +using System; +using PTTExtracts.Core; +using PTTExtracts.Patches; +using PTTExtracts.Utils; + namespace PTTExtracts { [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] public class Plugin : BaseUnityPlugin { - public void Awake() + private void Awake() { new InitAllExfiltrationPointsPatch().Enable(); new ScavExfiltrationPointPatch().Enable(); - Logger.LogInfo($"Plugin {PluginInfo.PLUGIN_GUID} is loaded!"); } + } + + } diff --git a/PTT-Extracts/Utils/WebRequestUtils.cs b/PTT-Extracts/Utils/WebRequestUtils.cs new file mode 100644 index 00000000..d434b7c0 --- /dev/null +++ b/PTT-Extracts/Utils/WebRequestUtils.cs @@ -0,0 +1,64 @@ +#if !UNITY_EDITOR +using SPT.Common.Http; +using Newtonsoft.Json; +using System.Collections.Generic; +using System; + +namespace PTTExtracts.Utils +{ + internal class WebRequestUtils + { + public static T Get(string url) + { + var req = RequestHandler.GetJson(url); + return JsonConvert.DeserializeObject(req); + } + public static T Post(string url, string data) + { + if (url == null) + { + Console.WriteLine("Error: url is null."); + return default(T); + } + + if (data == null) + { + Console.WriteLine("Error: data is null."); + return default(T); + } + + try + { + // Convert the string data to JSON format + string jsonData = JsonConvert.SerializeObject(new { Data = data }); +#if DEBUG + Console.WriteLine($"Sending JSON data to {url}: {jsonData}"); +#endif + var req = RequestHandler.PostJson(url, jsonData); +#if DEBUG + Console.WriteLine($"Received response: {req}"); +#endif + if (req == null) + { + Console.WriteLine("Error: Response is null."); + return default(T); + } + + return default(T); + } + catch (Exception ex) + { + Console.WriteLine($"Error occurred during request: {ex.Message}"); + return default(T); + } + } + + } + public class ResponseWrapper + { + public bool Success { get; set; } + public T Data { get; set; } + } +} + +#endif \ No newline at end of file diff --git a/configs/Default/Tooltips.json b/configs/Default/Tooltips.json index 78080e84..8b1aae73 100644 --- a/configs/Default/Tooltips.json +++ b/configs/Default/Tooltips.json @@ -91,6 +91,12 @@ "Scav_coop_exit", "Scav Checkpoint to Customs, Streets - Therapist", "Sandbox_VExit", - "Police Cordon V-Ex to Customs, Interchange, Streets, Labs - Prapor" + "Police Cordon V-Ex to Customs, Interchange, Streets, Labs - Prapor", + "to_kilmov_street", + "To Kilmov Street (Streets)", + "this_is_a_test", + "THIS IS A TEST", + "tree_fiddy", + "I'm gonna need about treee-fiddy" ] } diff --git a/configs/Default/config.json b/configs/Default/config.json index bc6c7b35..5a0a753a 100644 --- a/configs/Default/config.json +++ b/configs/Default/config.json @@ -311,11 +311,13 @@ "exfiltrations": { "factory4_day": { "Gate 3": "MechanicStash", - "Office Window": "JaegerStash" + "Office Window": "JaegerStash", + "this_is_a_test": "ThisIsATest" }, "factory4_night": { "Gate 3": "MechanicStash", - "Office Window": "JaegerStash" + "Office Window": "JaegerStash", + "this_is_a_test": "ThisIsATest" }, "bigmap": { "Railroad To Tarkov": "TherapistStash", @@ -362,7 +364,8 @@ }, "sandbox": { "Scav_coop_exit": "TherapistStash", - "Sandbox_VExit": "PraporStash" + "Sandbox_VExit": "PraporStash", + "to_kilmov_street": "ToKilmovStreet" } }, "infiltrations": { @@ -385,11 +388,20 @@ ], "interchange": ["Car at Power Station"] }, + "ToKilmovStreet": { + "sandbox": ["Entrance to Kilmov Street"], + "tarkovstreets": ["Klimov Street"] + }, "MechanicStash": { "factory4_day": ["Gate 3"], "factory4_night": ["Gate 3"], "bigmap": ["ZB-1011"] }, + "ThisIsATest": { + "factory4_day": ["This Is a Test"], + "factory4_night": ["This Is a Test"], + "sandbox": ["GZ Scav checkpoint"] + }, "RagmanStash": { "bigmap": ["Old Gas Scav"], "interchange": ["Railway"] diff --git a/configs/Default/customExtracts/exfilConfig.json b/configs/Default/customExtracts/exfilConfig.json new file mode 100644 index 00000000..05ac911a --- /dev/null +++ b/configs/Default/customExtracts/exfilConfig.json @@ -0,0 +1,155 @@ +[ + { + "Id": "sandbox", + "exits": [ + { + "CubeData": { + "Name": "newExtract_ToKilmovStreet", + "Scale": { + "x": 2, + "y": 2, + "z": 2 + }, + "Rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "Position": { + "x": 232.3745, + "y": 17.5048, + "z": 76.0496 + } + }, + "ExtractData": { + "Name": "to_kilmov_street", + "RequirementTip": "", + "EntryPoints": "west", + "ExfiltrationTime": 10, + "ExfiltrationType": "Individual", + "Chance": 100, + "MaxTime": 0, + "MinTime": 0, + "PassageRequirement": "None", + "Id": "", + "Count": 350, + "PlayersCount": 0, + "RequiredSlot": "", + "EventAvailable": true, + "ChancePVE": 100, + "CountPVE": 0, + "ExfiltrationTimePVE": 10, + "MinTimePVE": 0, + "MaxTimePVE": 0, + "PlayersCountPVE": 0 + }, + "AdditionalSettings": { + "StartTime": 0, + "Status": "" + } + } + ] + }, + { + "Id": "sandbox_high", + "exits": [ + { + "CubeData": { + "Name": "newExtract_ToKilmovStreet", + "Scale": { + "x": 2, + "y": 2, + "z": 2 + }, + "Rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "Position": { + "x": 232.3745, + "y": 17.5048, + "z": 76.0496 + } + }, + "ExtractData": { + "Name": "to_kilmov_street", + "RequirementTip": "", + "EntryPoints": "west", + "ExfiltrationTime": 10, + "ExfiltrationType": "Individual", + "Chance": 100, + "MaxTime": 0, + "MinTime": 0, + "PassageRequirement": "None", + "Id": "", + "Count": 350, + "PlayersCount": 0, + "RequiredSlot": "", + "EventAvailable": true, + "ChancePVE": 100, + "CountPVE": 0, + "ExfiltrationTimePVE": 10, + "MinTimePVE": 0, + "MaxTimePVE": 0, + "PlayersCountPVE": 0 + }, + "AdditionalSettings": { + "StartTime": 0, + "Status": "" + } + } + ] + }, + { + "Id": "factory4_day", + "exits": [ + { + "CubeData": { + "Name": "newExtract_THISISATEST", + "Scale": { + "x": 2.5, + "y": 2.5, + "z": 2.5 + }, + "Rotation": { + "x": 0, + "y": 0, + "z": 0 + }, + "Position": { + "x": 46, + "y": 0, + "z": -40 + } + }, + "ExtractData": { + "Chance": 100, + "Count": 350, + "EntryPoints": "Factory", + "ExfiltrationTime": 10, + "ExfiltrationType": "Individual", + "Id": "5449016a4bdc2d6f028b456f", + "MaxTime": 0, + "MinTime": 0, + "Name": "this_is_a_test", + "PassageRequirement": "TransferItem", + "PlayersCount": 0, + "RequiredSlot": "FirstPrimaryWeapon", + "RequirementTip": "tree_fiddy", + "EventAvailable": true, + "ChancePVE": 100, + "CountPVE": 0, + "ExfiltrationTimePVE": 10, + "MinTimePVE": 0, + "MaxTimePVE": 0, + "PlayersCountPVE": 0 + }, + "AdditionalSettings": { + "StartTime": 0, + "Status": "UncompleteRequirements" + } + } + ] + } +] \ No newline at end of file diff --git a/configs/Default/player_spawnpoints.json b/configs/Default/player_spawnpoints.json index cffa610a..387e1708 100644 --- a/configs/Default/player_spawnpoints.json +++ b/configs/Default/player_spawnpoints.json @@ -39,6 +39,14 @@ "z": 40.92688 }, "Rotation": 158.9888 + }, + "This Is a Test": { + "Position": { + "x": 46, + "y": 0, + "z": -40 + }, + "Rotation": 129.412323 } }, "factory4_night": { @@ -81,6 +89,14 @@ "z": 40.92688 }, "Rotation": 158.9888 + }, + "This Is a Test": { + "Position": { + "x": 46, + "y": 0, + "z": -40 + }, + "Rotation": 129.412323 } }, "bigmap": { @@ -369,6 +385,11 @@ "Police Car": { "Position": [-12.97, 22.6, 116.99], "Rotation": 90.0 + }, + "Entrance to Kilmov Street": + { + "Position": [46, 0, -40], + "Rotation": 10.636034 } } } diff --git a/src/all-exfils.ts b/src/all-exfils.ts index bf34b2b0..fedc39d7 100644 --- a/src/all-exfils.ts +++ b/src/all-exfils.ts @@ -137,7 +137,28 @@ const ALL_DUMPED_EXFILS_FROM_SCRIPT = { 'Nakatani_stairs_free_exit', ], }; - +const ALL_NEW_CUSTOM_EXFILS = { + customs: [ + ], + factory: ['this_is_a_test'], + interchange: [ + ], + woods: [ + ], + shoreline: [ + ], + reserve: [ + ], + lighthouse: [ + ], + streets: [ + ], + laboratory: [ + ], + groundzero: [ + 'to_kilmov_street', + ], +}; // APPLY ALIASES FOR MAP const ALL_EXFILS: Record = { ...ALL_DUMPED_EXFILS_FROM_SCRIPT, @@ -149,8 +170,20 @@ const ALL_EXFILS: Record = { sandbox: ALL_DUMPED_EXFILS_FROM_SCRIPT.groundzero, sandbox_high: ALL_DUMPED_EXFILS_FROM_SCRIPT.groundzero, }; +const NEW_CUSTOM_EXFILS: Record = { + ...ALL_NEW_CUSTOM_EXFILS, + bigmap: ALL_NEW_CUSTOM_EXFILS.customs, + rezervbase: ALL_NEW_CUSTOM_EXFILS.reserve, + factory4_day: ALL_NEW_CUSTOM_EXFILS.factory, + factory4_night: ALL_NEW_CUSTOM_EXFILS.factory, + tarkovstreets: ALL_NEW_CUSTOM_EXFILS.streets, + sandbox: ALL_NEW_CUSTOM_EXFILS.groundzero, + sandbox_high: ALL_NEW_CUSTOM_EXFILS.groundzero, +}; export const isValidExfilForMap = (mapName: string, exfilName: string): boolean => { const exfils = ALL_EXFILS[mapName] ?? []; + const customExfils = NEW_CUSTOM_EXFILS[mapName] ?? []; + exfils.push(...customExfils); return exfils.includes(exfilName); }; diff --git a/src/custom-extracts.ts b/src/custom-extracts.ts new file mode 100644 index 00000000..47408f94 --- /dev/null +++ b/src/custom-extracts.ts @@ -0,0 +1,121 @@ +import type { Exit, ILocationBase } from "@spt/models/eft/common/ILocationBase"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import { CONFIGS_DIR, getUserConfig } from './config'; +import type { UserConfig } from './config'; + +interface CustomExitConfig { + Id: string; + exits: { ExtractData: Exit }[]; +} + +const getCustomExtractsDir = ( + userConfig: UserConfig, +): string | undefined => { + try { + return path.join(CONFIGS_DIR, userConfig.selectedConfig, 'customExtracts'); + } catch (_err) { + return undefined; + } +}; + +export class CustomExtracts { + + public customExtractsList: { [locationId: string]: Exit[] } = {}; // Store custom extracts + public userConfig = getUserConfig(); + + public initCustomExtracts(locationBase: ILocationBase): Exit[] { + // Load custom extracts if not already loaded + if (!this.customExtractsList[locationBase.Id]) { + this.loadCustomExtractsForLocation(locationBase.Id); + } + const allCustomExtracts = this.customExtractsList[locationBase.Id] ?? []; + const newExits = this.mergeCustomExtracts(locationBase.exits, allCustomExtracts); + + return newExits; + } + + public loadAllCustomExtractConfigs(): CustomExitConfig[] { + const configDirectory = getCustomExtractsDir(this.userConfig); + + // Guard clause to check if configDirectory is defined + if (!configDirectory) { + console.error('Custom extracts directory not found.'); + return []; + } + + const configFiles = this.getJsonFiles(configDirectory); // Assuming getJsonFiles returns an array of filenames + const allConfigs = this.loadConfigs(configFiles, configDirectory); + + return allConfigs; + } + + public loadCustomExtractsForLocation(locationId: string): void { + const allConfigs = this.loadAllCustomExtractConfigs(); + + // Convert locationId to lowercase for comparison + const lowerCaseLocationId = locationId.toLowerCase(); + + // Find and load custom extracts for the specific location (compare in lowercase) + const locationConfig = allConfigs.find(config => config.Id.toLowerCase() === lowerCaseLocationId); + + if (!locationConfig) { + console.error(`No custom extracts found for location ${locationId}`); + return; + } + + if (!Array.isArray(locationConfig.exits)) { + console.error(`No exits array found for location ${locationId}`); + return; + } + + // Map the custom extracts to the location's customExtracts array, preserving the original case of locationId + this.customExtractsList[locationId] = locationConfig.exits.map(exit => exit.ExtractData); + } + + public mergeCustomExtracts(existingExits: Exit[], customExtracts: Exit[]): Exit[] { + const existingExitNames = new Set(existingExits.map(exit => exit.Name)); + + // Merge custom extracts + const updatedExits = [...existingExits]; + + for (const customExtract of customExtracts) { + if (!existingExitNames.has(customExtract.Name)) { + // Log the custom extract being added + console.log(`Adding custom extract: ${customExtract.Name}`); + updatedExits.push(customExtract); + } else { + // Log if the custom extract already exists and is being skipped + console.log(`Custom extract '${customExtract.Name}' already exists, skipping.`); + } + } + + return updatedExits; + } + + public getJsonFiles(directory: string): string[] { + try { + return fs.readdirSync(path.resolve(__dirname, directory)) + .filter(file => file.endsWith(".json")); + } catch (err) { + console.error(`Error reading directory ${directory}:`, err); + return []; + } + } + + public loadConfigs(files: string[], directory: string): CustomExitConfig[] { + const configs: CustomExitConfig[] = []; + + for (const file of files) { + try { + const filePath = path.resolve(__dirname, directory, file); + const rawData = fs.readFileSync(filePath, "utf8"); + configs.push(...JSON.parse(rawData)); + } catch (err) { + console.error(`Error reading file ${file}:`, err); + } + } + + return configs; + } +} diff --git a/src/instance-manager.ts b/src/instance-manager.ts new file mode 100644 index 00000000..d1d35761 --- /dev/null +++ b/src/instance-manager.ts @@ -0,0 +1,98 @@ +import * as path from "node:path"; + +import type { ILogger } from "@spt/models/spt/utils/ILogger"; +import type { ProfileController } from "@spt/controllers/ProfileController"; +import type { ProfileCallbacks } from "@spt/callbacks/ProfileCallbacks"; +import type { EventOutputHolder } from "@spt/routers/EventOutputHolder"; +import type { DatabaseServer } from "@spt/servers/DatabaseServer"; +import type { IDatabaseTables } from "@spt/models/spt/server/IDatabaseTables"; +import type { StaticRouterModService } from "@spt/services/mod/staticRouter/StaticRouterModService"; +import type { DynamicRouterModService } from "@spt/services/mod/dynamicRouter/DynamicRouterModService"; +import type { TraderAssortService } from "@spt/services/TraderAssortService"; +import type { DependencyContainer } from "tsyringe"; +import type { CustomItemService } from "@spt/services/mod/CustomItemService"; +import type { ImageRouter } from "@spt/routers/ImageRouter"; +import type { PreSptModLoader } from "@spt/loaders/PreSptModLoader"; +import type { ConfigServer } from "@spt/servers/ConfigServer"; +import type { JsonUtil } from "@spt/utils/JsonUtil"; +import type { ProfileHelper } from "@spt/helpers/ProfileHelper"; +import type { RagfairPriceService } from "@spt/services/RagfairPriceService"; +import type { ImporterUtil } from "@spt/utils/ImporterUtil"; +import type { SaveServer } from "@spt/servers/SaveServer"; +import type { ItemHelper } from "@spt/helpers/ItemHelper"; +import { RouterService } from "./router-service"; +import type { TraderAssortHelper } from "@spt/helpers/TraderAssortHelper"; +import type { RagfairOfferGenerator } from "@spt/generators/RagfairOfferGenerator"; + +export class InstanceManager +{ + //#region Accessible in or after preSptLoad + public modName: string; + public debug: boolean; + // Useful Paths + public modPath: string = path.join(process.cwd(), "\\user\\mods\\PathToTarkov\\"); + public profilePath: string = path.join(process.cwd(), "\\user\\profiles"); + + // Instances + public container: DependencyContainer; + public preSptModLoader: PreSptModLoader; + public configServer: ConfigServer; + public saveServer: SaveServer; + public itemHelper: ItemHelper; + public logger: ILogger; + public staticRouter: StaticRouterModService; + public dynamicRouter: DynamicRouterModService; + public profileController: ProfileController; + public profileCallbacks: ProfileCallbacks; + //#endregion + + //#region Acceessible in or after postDBLoad + public database: IDatabaseTables; + public customItem: CustomItemService; + public imageRouter: ImageRouter; + public jsonUtil: JsonUtil; + public profileHelper: ProfileHelper; + public eventOutputHolder: EventOutputHolder; + public ragfairPriceService: RagfairPriceService; + public importerUtil: ImporterUtil; + public traderAssortService: TraderAssortService; + public traderAssortHelper: TraderAssortHelper; + public ragfairOfferGenerator: RagfairOfferGenerator; + private routerService: RouterService = new RouterService(); + //#endregion + + // Call at the start of the mods postDBLoad method + public preSptLoad(container: DependencyContainer, mod: string): void + { + this.modName = mod; + + this.container = container; + this.preSptModLoader = container.resolve("PreSptModLoader"); + this.imageRouter = container.resolve("ImageRouter"); + this.configServer = container.resolve("ConfigServer"); + this.saveServer = container.resolve("SaveServer"); + this.itemHelper = container.resolve("ItemHelper"); + this.eventOutputHolder = container.resolve("EventOutputHolder"); + this.profileController = container.resolve("ProfileController"); + this.profileCallbacks = container.resolve("ProfileCallbacks"); + this.logger = container.resolve("WinstonLogger"); + this.staticRouter = container.resolve("StaticRouterModService"); + this.dynamicRouter = container.resolve("DynamicRouterModService"); + this.traderAssortService = container.resolve("TraderAssortService"); + this.traderAssortHelper = container.resolve("TraderAssortHelper"); + this.ragfairOfferGenerator = container.resolve("RagfairOfferGenerator"); + + this.routerService.preSptLoad(this); + } + + public postDBLoad(container: DependencyContainer): void + { + this.database = container.resolve("DatabaseServer").getTables(); + this.customItem = container.resolve("CustomItemService"); + this.jsonUtil = container.resolve("JsonUtil"); + this.profileHelper = container.resolve("ProfileHelper"); + this.ragfairPriceService = container.resolve("RagfairPriceService"); + this.importerUtil = container.resolve("ImporterUtil"); + + } +} \ No newline at end of file diff --git a/src/mod.ts b/src/mod.ts index 0ba4a22c..dbde4fe1 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -30,9 +30,10 @@ import { getModDisplayName, noop, readJsonFile } from './utils'; import { EndOfRaidController } from './end-of-raid-controller'; import { fixRepeatableQuests } from './fix-repeatable-quests'; import { pathToTarkovReloadedTooltipsConfigCompat } from './pttr-tooltips'; -import path from 'path'; +import path from 'node:path'; import { analyzeConfig } from './config-analysis'; import { TradersAvailabilityService } from './services/TradersAvailabilityService'; +import { InstanceManager } from './instance-manager'; const getTooltipsConfig = ( userConfig: UserConfig, @@ -54,9 +55,12 @@ class PathToTarkov implements IPreSptLoadMod, IPostSptLoadMod { public container: DependencyContainer; public executeOnStartAPICallbacks: (sessionId: string) => void = noop; public pathToTarkovController: PathToTarkovController; + private instanceManager: InstanceManager = new InstanceManager(); + public modName = 'PathToTarkov'; public preSptLoad(container: DependencyContainer): void { this.container = container; + this.instanceManager.preSptLoad(container, this.modName); this.packageJson = readJsonFile(PACKAGE_JSON_PATH); const userConfig = getUserConfig(); @@ -139,6 +143,12 @@ class PathToTarkov implements IPreSptLoadMod, IPostSptLoadMod { public postSptLoad(container: DependencyContainer): void { this.container = container; + this.instanceManager.postDBLoad(container); + + if (!this.config.enabled) { + return; + } + const db = container.resolve('DatabaseServer'); const saveServer = container.resolve('SaveServer'); const profiles = saveServer.getProfiles(); diff --git a/src/path-to-tarkov-controller.ts b/src/path-to-tarkov-controller.ts index 15ecd5e1..bcca0dce 100644 --- a/src/path-to-tarkov-controller.ts +++ b/src/path-to-tarkov-controller.ts @@ -17,6 +17,8 @@ import { PTT_INFILTRATION, } from './helpers'; +import { CustomExtracts } from "./custom-extracts"; + import { getTemplateIdFromStashId, StashController } from './stash-controller'; import { TradersController } from './traders-controller'; import type { DependencyContainer } from 'tsyringe'; @@ -60,6 +62,7 @@ const getIndexedLocations = (locations: ILocations): IndexedLocations => { export class PathToTarkovController { public stashController: StashController; public tradersController: TradersController; + public customExtracts: CustomExtracts; // configs are indexed by sessionId private configCache: Record = {}; @@ -100,6 +103,7 @@ export class PathToTarkovController { this.logger, ); this.overrideControllers(); + this.customExtracts = new CustomExtracts(); } setConfig(config: Config, sessionId: string): void { @@ -591,6 +595,8 @@ export class PathToTarkovController { // erase all exits and create custom exit points without requirements locationBase.exits = extractPoints.map(createExitPoint); } + // combine custom extracts with vanilla ones + locationBase.exits = this.customExtracts.initCustomExtracts(locationBase); return true; } diff --git a/src/router-service.ts b/src/router-service.ts new file mode 100644 index 00000000..286b8397 --- /dev/null +++ b/src/router-service.ts @@ -0,0 +1,47 @@ +import { LogTextColor } from "@spt/models/spt/logging/LogTextColor"; +// WTT imports +import type { InstanceManager } from "./instance-manager"; +import { CustomExtracts } from "./custom-extracts"; + +export class RouterService +{ + // eslint-disable-next-line @typescript-eslint/naming-convention + private instanceManager: InstanceManager; + private customExtracts: CustomExtracts = new CustomExtracts(); + + public preSptLoad(instance: InstanceManager): void + { + this.instanceManager = instance; + this.registerGetAllCustomExtractsRoute(); + this.instanceManager.logger.log(`[${this.instanceManager.modName}] WTTRouter: Initialized and registered routes.`, LogTextColor.GREEN); + } + + private registerGetAllCustomExtractsRoute(): void + { + this.instanceManager.staticRouter.registerStaticRouter( + "CustomExtracts", + [ + { + url: "/PathToTarkov/CustomExtracts", + action: async (url, info, sessionId) => + { + // Call the method to load all custom extracts + const allCustomExtracts = this.customExtracts.loadAllCustomExtractConfigs(); + + if (!allCustomExtracts || allCustomExtracts.length === 0) + { + this.instanceManager.logger.log(`[${this.instanceManager.modName}] No custom extracts found.`, LogTextColor.RED); + return JSON.stringify({ success: false, message: "No custom extracts found" }); + } + + // Send all custom extracts data to the client + this.instanceManager.logger.log(`[${this.instanceManager.modName}] All custom extracts sent.`, LogTextColor.GREEN); + return JSON.stringify({ success: true, data: allCustomExtracts }); + } + } + ], + "" + ); + } + +} From 2df6a10eb578a76942cc83cfed23251f6f0d0114 Mon Sep 17 00:00:00 2001 From: Trap Date: Sat, 24 Aug 2024 10:43:24 +0200 Subject: [PATCH 2/5] style: apply prettier --- .../Default/customExtracts/exfilConfig.json | 2 +- configs/Default/player_spawnpoints.json | 3 +- src/all-exfils.ts | 28 +-- src/custom-extracts.ts | 182 +++++++++--------- src/instance-manager.ts | 176 +++++++++-------- src/path-to-tarkov-controller.ts | 2 +- src/router-service.ts | 83 ++++---- 7 files changed, 232 insertions(+), 244 deletions(-) diff --git a/configs/Default/customExtracts/exfilConfig.json b/configs/Default/customExtracts/exfilConfig.json index 05ac911a..42fad9f3 100644 --- a/configs/Default/customExtracts/exfilConfig.json +++ b/configs/Default/customExtracts/exfilConfig.json @@ -152,4 +152,4 @@ } ] } -] \ No newline at end of file +] diff --git a/configs/Default/player_spawnpoints.json b/configs/Default/player_spawnpoints.json index 387e1708..abfd3a0d 100644 --- a/configs/Default/player_spawnpoints.json +++ b/configs/Default/player_spawnpoints.json @@ -386,8 +386,7 @@ "Position": [-12.97, 22.6, 116.99], "Rotation": 90.0 }, - "Entrance to Kilmov Street": - { + "Entrance to Kilmov Street": { "Position": [46, 0, -40], "Rotation": 10.636034 } diff --git a/src/all-exfils.ts b/src/all-exfils.ts index fedc39d7..fea6da57 100644 --- a/src/all-exfils.ts +++ b/src/all-exfils.ts @@ -138,26 +138,16 @@ const ALL_DUMPED_EXFILS_FROM_SCRIPT = { ], }; const ALL_NEW_CUSTOM_EXFILS = { - customs: [ - ], + customs: [], factory: ['this_is_a_test'], - interchange: [ - ], - woods: [ - ], - shoreline: [ - ], - reserve: [ - ], - lighthouse: [ - ], - streets: [ - ], - laboratory: [ - ], - groundzero: [ - 'to_kilmov_street', - ], + interchange: [], + woods: [], + shoreline: [], + reserve: [], + lighthouse: [], + streets: [], + laboratory: [], + groundzero: ['to_kilmov_street'], }; // APPLY ALIASES FOR MAP const ALL_EXFILS: Record = { diff --git a/src/custom-extracts.ts b/src/custom-extracts.ts index 47408f94..1e974c7e 100644 --- a/src/custom-extracts.ts +++ b/src/custom-extracts.ts @@ -1,121 +1,121 @@ -import type { Exit, ILocationBase } from "@spt/models/eft/common/ILocationBase"; -import * as fs from "node:fs"; -import * as path from "node:path"; +import type { Exit, ILocationBase } from '@spt/models/eft/common/ILocationBase'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; import { CONFIGS_DIR, getUserConfig } from './config'; import type { UserConfig } from './config'; interface CustomExitConfig { - Id: string; - exits: { ExtractData: Exit }[]; + Id: string; + exits: { ExtractData: Exit }[]; } -const getCustomExtractsDir = ( - userConfig: UserConfig, -): string | undefined => { - try { - return path.join(CONFIGS_DIR, userConfig.selectedConfig, 'customExtracts'); - } catch (_err) { - return undefined; - } +const getCustomExtractsDir = (userConfig: UserConfig): string | undefined => { + try { + return path.join(CONFIGS_DIR, userConfig.selectedConfig, 'customExtracts'); + } catch (_err) { + return undefined; + } }; export class CustomExtracts { + public customExtractsList: { [locationId: string]: Exit[] } = {}; // Store custom extracts + public userConfig = getUserConfig(); - public customExtractsList: { [locationId: string]: Exit[] } = {}; // Store custom extracts - public userConfig = getUserConfig(); - - public initCustomExtracts(locationBase: ILocationBase): Exit[] { - // Load custom extracts if not already loaded - if (!this.customExtractsList[locationBase.Id]) { - this.loadCustomExtractsForLocation(locationBase.Id); - } - const allCustomExtracts = this.customExtractsList[locationBase.Id] ?? []; - const newExits = this.mergeCustomExtracts(locationBase.exits, allCustomExtracts); - - return newExits; + public initCustomExtracts(locationBase: ILocationBase): Exit[] { + // Load custom extracts if not already loaded + if (!this.customExtractsList[locationBase.Id]) { + this.loadCustomExtractsForLocation(locationBase.Id); } + const allCustomExtracts = this.customExtractsList[locationBase.Id] ?? []; + const newExits = this.mergeCustomExtracts(locationBase.exits, allCustomExtracts); - public loadAllCustomExtractConfigs(): CustomExitConfig[] { - const configDirectory = getCustomExtractsDir(this.userConfig); - - // Guard clause to check if configDirectory is defined - if (!configDirectory) { - console.error('Custom extracts directory not found.'); - return []; - } + return newExits; + } - const configFiles = this.getJsonFiles(configDirectory); // Assuming getJsonFiles returns an array of filenames - const allConfigs = this.loadConfigs(configFiles, configDirectory); + public loadAllCustomExtractConfigs(): CustomExitConfig[] { + const configDirectory = getCustomExtractsDir(this.userConfig); - return allConfigs; + // Guard clause to check if configDirectory is defined + if (!configDirectory) { + console.error('Custom extracts directory not found.'); + return []; } - public loadCustomExtractsForLocation(locationId: string): void { - const allConfigs = this.loadAllCustomExtractConfigs(); + const configFiles = this.getJsonFiles(configDirectory); // Assuming getJsonFiles returns an array of filenames + const allConfigs = this.loadConfigs(configFiles, configDirectory); - // Convert locationId to lowercase for comparison - const lowerCaseLocationId = locationId.toLowerCase(); + return allConfigs; + } - // Find and load custom extracts for the specific location (compare in lowercase) - const locationConfig = allConfigs.find(config => config.Id.toLowerCase() === lowerCaseLocationId); + public loadCustomExtractsForLocation(locationId: string): void { + const allConfigs = this.loadAllCustomExtractConfigs(); - if (!locationConfig) { - console.error(`No custom extracts found for location ${locationId}`); - return; - } + // Convert locationId to lowercase for comparison + const lowerCaseLocationId = locationId.toLowerCase(); - if (!Array.isArray(locationConfig.exits)) { - console.error(`No exits array found for location ${locationId}`); - return; - } + // Find and load custom extracts for the specific location (compare in lowercase) + const locationConfig = allConfigs.find( + config => config.Id.toLowerCase() === lowerCaseLocationId, + ); - // Map the custom extracts to the location's customExtracts array, preserving the original case of locationId - this.customExtractsList[locationId] = locationConfig.exits.map(exit => exit.ExtractData); + if (!locationConfig) { + console.error(`No custom extracts found for location ${locationId}`); + return; } - public mergeCustomExtracts(existingExits: Exit[], customExtracts: Exit[]): Exit[] { - const existingExitNames = new Set(existingExits.map(exit => exit.Name)); - - // Merge custom extracts - const updatedExits = [...existingExits]; - - for (const customExtract of customExtracts) { - if (!existingExitNames.has(customExtract.Name)) { - // Log the custom extract being added - console.log(`Adding custom extract: ${customExtract.Name}`); - updatedExits.push(customExtract); - } else { - // Log if the custom extract already exists and is being skipped - console.log(`Custom extract '${customExtract.Name}' already exists, skipping.`); - } - } - - return updatedExits; + if (!Array.isArray(locationConfig.exits)) { + console.error(`No exits array found for location ${locationId}`); + return; } - public getJsonFiles(directory: string): string[] { - try { - return fs.readdirSync(path.resolve(__dirname, directory)) - .filter(file => file.endsWith(".json")); - } catch (err) { - console.error(`Error reading directory ${directory}:`, err); - return []; - } + // Map the custom extracts to the location's customExtracts array, preserving the original case of locationId + this.customExtractsList[locationId] = locationConfig.exits.map(exit => exit.ExtractData); + } + + public mergeCustomExtracts(existingExits: Exit[], customExtracts: Exit[]): Exit[] { + const existingExitNames = new Set(existingExits.map(exit => exit.Name)); + + // Merge custom extracts + const updatedExits = [...existingExits]; + + for (const customExtract of customExtracts) { + if (!existingExitNames.has(customExtract.Name)) { + // Log the custom extract being added + console.log(`Adding custom extract: ${customExtract.Name}`); + updatedExits.push(customExtract); + } else { + // Log if the custom extract already exists and is being skipped + console.log(`Custom extract '${customExtract.Name}' already exists, skipping.`); + } } - public loadConfigs(files: string[], directory: string): CustomExitConfig[] { - const configs: CustomExitConfig[] = []; - - for (const file of files) { - try { - const filePath = path.resolve(__dirname, directory, file); - const rawData = fs.readFileSync(filePath, "utf8"); - configs.push(...JSON.parse(rawData)); - } catch (err) { - console.error(`Error reading file ${file}:`, err); - } - } + return updatedExits; + } - return configs; + public getJsonFiles(directory: string): string[] { + try { + return fs + .readdirSync(path.resolve(__dirname, directory)) + .filter(file => file.endsWith('.json')); + } catch (err) { + console.error(`Error reading directory ${directory}:`, err); + return []; } + } + + public loadConfigs(files: string[], directory: string): CustomExitConfig[] { + const configs: CustomExitConfig[] = []; + + for (const file of files) { + try { + const filePath = path.resolve(__dirname, directory, file); + const rawData = fs.readFileSync(filePath, 'utf8'); + configs.push(...JSON.parse(rawData)); + } catch (err) { + console.error(`Error reading file ${file}:`, err); + } + } + + return configs; + } } diff --git a/src/instance-manager.ts b/src/instance-manager.ts index d1d35761..082a5fe9 100644 --- a/src/instance-manager.ts +++ b/src/instance-manager.ts @@ -1,98 +1,94 @@ -import * as path from "node:path"; +import * as path from 'node:path'; -import type { ILogger } from "@spt/models/spt/utils/ILogger"; -import type { ProfileController } from "@spt/controllers/ProfileController"; -import type { ProfileCallbacks } from "@spt/callbacks/ProfileCallbacks"; -import type { EventOutputHolder } from "@spt/routers/EventOutputHolder"; -import type { DatabaseServer } from "@spt/servers/DatabaseServer"; -import type { IDatabaseTables } from "@spt/models/spt/server/IDatabaseTables"; -import type { StaticRouterModService } from "@spt/services/mod/staticRouter/StaticRouterModService"; -import type { DynamicRouterModService } from "@spt/services/mod/dynamicRouter/DynamicRouterModService"; -import type { TraderAssortService } from "@spt/services/TraderAssortService"; -import type { DependencyContainer } from "tsyringe"; -import type { CustomItemService } from "@spt/services/mod/CustomItemService"; -import type { ImageRouter } from "@spt/routers/ImageRouter"; -import type { PreSptModLoader } from "@spt/loaders/PreSptModLoader"; -import type { ConfigServer } from "@spt/servers/ConfigServer"; -import type { JsonUtil } from "@spt/utils/JsonUtil"; -import type { ProfileHelper } from "@spt/helpers/ProfileHelper"; -import type { RagfairPriceService } from "@spt/services/RagfairPriceService"; -import type { ImporterUtil } from "@spt/utils/ImporterUtil"; -import type { SaveServer } from "@spt/servers/SaveServer"; -import type { ItemHelper } from "@spt/helpers/ItemHelper"; -import { RouterService } from "./router-service"; -import type { TraderAssortHelper } from "@spt/helpers/TraderAssortHelper"; -import type { RagfairOfferGenerator } from "@spt/generators/RagfairOfferGenerator"; +import type { ILogger } from '@spt/models/spt/utils/ILogger'; +import type { ProfileController } from '@spt/controllers/ProfileController'; +import type { ProfileCallbacks } from '@spt/callbacks/ProfileCallbacks'; +import type { EventOutputHolder } from '@spt/routers/EventOutputHolder'; +import type { DatabaseServer } from '@spt/servers/DatabaseServer'; +import type { IDatabaseTables } from '@spt/models/spt/server/IDatabaseTables'; +import type { StaticRouterModService } from '@spt/services/mod/staticRouter/StaticRouterModService'; +import type { DynamicRouterModService } from '@spt/services/mod/dynamicRouter/DynamicRouterModService'; +import type { TraderAssortService } from '@spt/services/TraderAssortService'; +import type { DependencyContainer } from 'tsyringe'; +import type { CustomItemService } from '@spt/services/mod/CustomItemService'; +import type { ImageRouter } from '@spt/routers/ImageRouter'; +import type { PreSptModLoader } from '@spt/loaders/PreSptModLoader'; +import type { ConfigServer } from '@spt/servers/ConfigServer'; +import type { JsonUtil } from '@spt/utils/JsonUtil'; +import type { ProfileHelper } from '@spt/helpers/ProfileHelper'; +import type { RagfairPriceService } from '@spt/services/RagfairPriceService'; +import type { ImporterUtil } from '@spt/utils/ImporterUtil'; +import type { SaveServer } from '@spt/servers/SaveServer'; +import type { ItemHelper } from '@spt/helpers/ItemHelper'; +import { RouterService } from './router-service'; +import type { TraderAssortHelper } from '@spt/helpers/TraderAssortHelper'; +import type { RagfairOfferGenerator } from '@spt/generators/RagfairOfferGenerator'; -export class InstanceManager -{ - //#region Accessible in or after preSptLoad - public modName: string; - public debug: boolean; - // Useful Paths - public modPath: string = path.join(process.cwd(), "\\user\\mods\\PathToTarkov\\"); - public profilePath: string = path.join(process.cwd(), "\\user\\profiles"); +export class InstanceManager { + //#region Accessible in or after preSptLoad + public modName: string; + public debug: boolean; + // Useful Paths + public modPath: string = path.join(process.cwd(), '\\user\\mods\\PathToTarkov\\'); + public profilePath: string = path.join(process.cwd(), '\\user\\profiles'); - // Instances - public container: DependencyContainer; - public preSptModLoader: PreSptModLoader; - public configServer: ConfigServer; - public saveServer: SaveServer; - public itemHelper: ItemHelper; - public logger: ILogger; - public staticRouter: StaticRouterModService; - public dynamicRouter: DynamicRouterModService; - public profileController: ProfileController; - public profileCallbacks: ProfileCallbacks; - //#endregion + // Instances + public container: DependencyContainer; + public preSptModLoader: PreSptModLoader; + public configServer: ConfigServer; + public saveServer: SaveServer; + public itemHelper: ItemHelper; + public logger: ILogger; + public staticRouter: StaticRouterModService; + public dynamicRouter: DynamicRouterModService; + public profileController: ProfileController; + public profileCallbacks: ProfileCallbacks; + //#endregion - //#region Acceessible in or after postDBLoad - public database: IDatabaseTables; - public customItem: CustomItemService; - public imageRouter: ImageRouter; - public jsonUtil: JsonUtil; - public profileHelper: ProfileHelper; - public eventOutputHolder: EventOutputHolder; - public ragfairPriceService: RagfairPriceService; - public importerUtil: ImporterUtil; - public traderAssortService: TraderAssortService; - public traderAssortHelper: TraderAssortHelper; - public ragfairOfferGenerator: RagfairOfferGenerator; - private routerService: RouterService = new RouterService(); - //#endregion + //#region Acceessible in or after postDBLoad + public database: IDatabaseTables; + public customItem: CustomItemService; + public imageRouter: ImageRouter; + public jsonUtil: JsonUtil; + public profileHelper: ProfileHelper; + public eventOutputHolder: EventOutputHolder; + public ragfairPriceService: RagfairPriceService; + public importerUtil: ImporterUtil; + public traderAssortService: TraderAssortService; + public traderAssortHelper: TraderAssortHelper; + public ragfairOfferGenerator: RagfairOfferGenerator; + private routerService: RouterService = new RouterService(); + //#endregion - // Call at the start of the mods postDBLoad method - public preSptLoad(container: DependencyContainer, mod: string): void - { - this.modName = mod; + // Call at the start of the mods postDBLoad method + public preSptLoad(container: DependencyContainer, mod: string): void { + this.modName = mod; - this.container = container; - this.preSptModLoader = container.resolve("PreSptModLoader"); - this.imageRouter = container.resolve("ImageRouter"); - this.configServer = container.resolve("ConfigServer"); - this.saveServer = container.resolve("SaveServer"); - this.itemHelper = container.resolve("ItemHelper"); - this.eventOutputHolder = container.resolve("EventOutputHolder"); - this.profileController = container.resolve("ProfileController"); - this.profileCallbacks = container.resolve("ProfileCallbacks"); - this.logger = container.resolve("WinstonLogger"); - this.staticRouter = container.resolve("StaticRouterModService"); - this.dynamicRouter = container.resolve("DynamicRouterModService"); - this.traderAssortService = container.resolve("TraderAssortService"); - this.traderAssortHelper = container.resolve("TraderAssortHelper"); - this.ragfairOfferGenerator = container.resolve("RagfairOfferGenerator"); + this.container = container; + this.preSptModLoader = container.resolve('PreSptModLoader'); + this.imageRouter = container.resolve('ImageRouter'); + this.configServer = container.resolve('ConfigServer'); + this.saveServer = container.resolve('SaveServer'); + this.itemHelper = container.resolve('ItemHelper'); + this.eventOutputHolder = container.resolve('EventOutputHolder'); + this.profileController = container.resolve('ProfileController'); + this.profileCallbacks = container.resolve('ProfileCallbacks'); + this.logger = container.resolve('WinstonLogger'); + this.staticRouter = container.resolve('StaticRouterModService'); + this.dynamicRouter = container.resolve('DynamicRouterModService'); + this.traderAssortService = container.resolve('TraderAssortService'); + this.traderAssortHelper = container.resolve('TraderAssortHelper'); + this.ragfairOfferGenerator = container.resolve('RagfairOfferGenerator'); - this.routerService.preSptLoad(this); - } + this.routerService.preSptLoad(this); + } - public postDBLoad(container: DependencyContainer): void - { - this.database = container.resolve("DatabaseServer").getTables(); - this.customItem = container.resolve("CustomItemService"); - this.jsonUtil = container.resolve("JsonUtil"); - this.profileHelper = container.resolve("ProfileHelper"); - this.ragfairPriceService = container.resolve("RagfairPriceService"); - this.importerUtil = container.resolve("ImporterUtil"); - - } -} \ No newline at end of file + public postDBLoad(container: DependencyContainer): void { + this.database = container.resolve('DatabaseServer').getTables(); + this.customItem = container.resolve('CustomItemService'); + this.jsonUtil = container.resolve('JsonUtil'); + this.profileHelper = container.resolve('ProfileHelper'); + this.ragfairPriceService = container.resolve('RagfairPriceService'); + this.importerUtil = container.resolve('ImporterUtil'); + } +} diff --git a/src/path-to-tarkov-controller.ts b/src/path-to-tarkov-controller.ts index bcca0dce..75b908d9 100644 --- a/src/path-to-tarkov-controller.ts +++ b/src/path-to-tarkov-controller.ts @@ -17,7 +17,7 @@ import { PTT_INFILTRATION, } from './helpers'; -import { CustomExtracts } from "./custom-extracts"; +import { CustomExtracts } from './custom-extracts'; import { getTemplateIdFromStashId, StashController } from './stash-controller'; import { TradersController } from './traders-controller'; diff --git a/src/router-service.ts b/src/router-service.ts index 286b8397..5ecd6d59 100644 --- a/src/router-service.ts +++ b/src/router-service.ts @@ -1,47 +1,50 @@ -import { LogTextColor } from "@spt/models/spt/logging/LogTextColor"; +import { LogTextColor } from '@spt/models/spt/logging/LogTextColor'; // WTT imports -import type { InstanceManager } from "./instance-manager"; -import { CustomExtracts } from "./custom-extracts"; +import type { InstanceManager } from './instance-manager'; +import { CustomExtracts } from './custom-extracts'; -export class RouterService -{ - // eslint-disable-next-line @typescript-eslint/naming-convention - private instanceManager: InstanceManager; - private customExtracts: CustomExtracts = new CustomExtracts(); +export class RouterService { + // eslint-disable-next-line @typescript-eslint/naming-convention + private instanceManager: InstanceManager; + private customExtracts: CustomExtracts = new CustomExtracts(); - public preSptLoad(instance: InstanceManager): void - { - this.instanceManager = instance; - this.registerGetAllCustomExtractsRoute(); - this.instanceManager.logger.log(`[${this.instanceManager.modName}] WTTRouter: Initialized and registered routes.`, LogTextColor.GREEN); - } + public preSptLoad(instance: InstanceManager): void { + this.instanceManager = instance; + this.registerGetAllCustomExtractsRoute(); + this.instanceManager.logger.log( + `[${this.instanceManager.modName}] WTTRouter: Initialized and registered routes.`, + LogTextColor.GREEN, + ); + } - private registerGetAllCustomExtractsRoute(): void - { - this.instanceManager.staticRouter.registerStaticRouter( - "CustomExtracts", - [ - { - url: "/PathToTarkov/CustomExtracts", - action: async (url, info, sessionId) => - { - // Call the method to load all custom extracts - const allCustomExtracts = this.customExtracts.loadAllCustomExtractConfigs(); + private registerGetAllCustomExtractsRoute(): void { + this.instanceManager.staticRouter.registerStaticRouter( + 'CustomExtracts', + [ + { + url: '/PathToTarkov/CustomExtracts', + action: async (url, info, sessionId) => { + // Call the method to load all custom extracts + const allCustomExtracts = this.customExtracts.loadAllCustomExtractConfigs(); - if (!allCustomExtracts || allCustomExtracts.length === 0) - { - this.instanceManager.logger.log(`[${this.instanceManager.modName}] No custom extracts found.`, LogTextColor.RED); - return JSON.stringify({ success: false, message: "No custom extracts found" }); - } - - // Send all custom extracts data to the client - this.instanceManager.logger.log(`[${this.instanceManager.modName}] All custom extracts sent.`, LogTextColor.GREEN); - return JSON.stringify({ success: true, data: allCustomExtracts }); - } - } - ], - "" - ); - } + if (!allCustomExtracts || allCustomExtracts.length === 0) { + this.instanceManager.logger.log( + `[${this.instanceManager.modName}] No custom extracts found.`, + LogTextColor.RED, + ); + return JSON.stringify({ success: false, message: 'No custom extracts found' }); + } + // Send all custom extracts data to the client + this.instanceManager.logger.log( + `[${this.instanceManager.modName}] All custom extracts sent.`, + LogTextColor.GREEN, + ); + return JSON.stringify({ success: true, data: allCustomExtracts }); + }, + }, + ], + '', + ); + } } From eff189fd5dccc8bdaae3eaeb3cac5a139fc53625 Mon Sep 17 00:00:00 2001 From: Trap Date: Sat, 24 Aug 2024 10:44:14 +0200 Subject: [PATCH 3/5] style: fix lint errors --- src/router-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/router-service.ts b/src/router-service.ts index 5ecd6d59..b70fcf75 100644 --- a/src/router-service.ts +++ b/src/router-service.ts @@ -23,7 +23,7 @@ export class RouterService { [ { url: '/PathToTarkov/CustomExtracts', - action: async (url, info, sessionId) => { + action: async (_url, _info, _sessionId) => { // Call the method to load all custom extracts const allCustomExtracts = this.customExtracts.loadAllCustomExtractConfigs(); From bbda45a01a820416e516e47c4aef790ab7c6ff5b Mon Sep 17 00:00:00 2001 From: Trap Date: Sat, 24 Aug 2024 10:46:32 +0200 Subject: [PATCH 4/5] fix: remove old ptt patch --- PTT-Extracts/Patch.cs | 122 ------------------------------------------ 1 file changed, 122 deletions(-) delete mode 100644 PTT-Extracts/Patch.cs diff --git a/PTT-Extracts/Patch.cs b/PTT-Extracts/Patch.cs deleted file mode 100644 index 6b1d5860..00000000 --- a/PTT-Extracts/Patch.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using SPT.Reflection.Patching; -using EFT.Interactive; -using HarmonyLib; - -namespace PTTExtracts -{ - public class ScavExfiltrationPointPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() - { - return typeof(ScavExfiltrationPoint).GetMethod("InfiltrationMatch", BindingFlags.Public | BindingFlags.Instance); - } - - [PatchPrefix] - private static bool Prefix(ref bool __result) - { - __result = true; - return false; - } - } - - public class InitAllExfiltrationPointsPatch : ModulePatch - { - public static bool NameMatches(LocationExitClass x) - { - return exitName == x.Name; - } - - public static string exitName; - - public static bool RandomRange(ExfiltrationPoint trigger) - { - return UnityEngine.Random.Range(0f, 100f) <= trigger.Settings.Chance; - } - - public static bool IsNotScavExfil(ExfiltrationPoint x) - { - return x is not ScavExfiltrationPoint || x is SharedExfiltrationPoint; - } - - public static bool IsScavExfil(ExfiltrationPoint x) - { - return x is ScavExfiltrationPoint; - } - - protected override MethodBase GetTargetMethod() - { - return typeof(ExfiltrationControllerClass).GetMethod("InitAllExfiltrationPoints", BindingFlags.Public | BindingFlags.Instance); - } - - [PatchPrefix] - private static bool PatchPrefix(ref ExfiltrationControllerClass __instance, LocationExitClass[] settings, bool justLoadSettings = false, bool giveAuthority = true) - { - ExfiltrationPoint[] source = LocationScene.GetAllObjects(false).ToArray(); - ExfiltrationPoint[] scavExfilArr = source.Where(new Func(IsScavExfil)).ToArray(); - ExfiltrationPoint[] pmcExfilArr = source.Where(new Func(IsNotScavExfil)).ToArray(); - - List pmcExfilList = pmcExfilArr.ToList(); - - foreach (ExfiltrationPoint scavExfil in scavExfilArr) - { - if (!pmcExfilList.Any(k => k.Settings.Name == scavExfil.Settings.Name)) - { - pmcExfilList.Add(scavExfil); - } - } - - AccessTools.Property(typeof(ExfiltrationControllerClass), "ExfiltrationPoints").SetValue(__instance, pmcExfilList.ToArray()); - - AccessTools.Property(typeof(ExfiltrationControllerClass), "ScavExfiltrationPoints").SetValue(__instance, source.Where(new Func(IsScavExfil)).Cast().ToArray()); - - AccessTools.Field(typeof(ExfiltrationControllerClass), "list_0").SetValue(__instance, new List(__instance.ScavExfiltrationPoints.Length)); - AccessTools.Field(typeof(ExfiltrationControllerClass), "list_1").SetValue(__instance, new List()); - - List list_0 = (List)AccessTools.Field(typeof(ExfiltrationControllerClass), "list_0").GetValue(__instance); - List list_1 = (List)AccessTools.Field(typeof(ExfiltrationControllerClass), "list_1").GetValue(__instance); - - foreach (ScavExfiltrationPoint scavExfiltrationPoint in __instance.ScavExfiltrationPoints) - { - Logger.LogWarning("Scav Exfil name = " + scavExfiltrationPoint.Settings.Name); - SharedExfiltrationPoint sharedExfiltrationPoint; - if ((sharedExfiltrationPoint = (scavExfiltrationPoint as SharedExfiltrationPoint)) != null && sharedExfiltrationPoint.IsMandatoryForScavs) - { - list_1.Add(scavExfiltrationPoint); - } - else - { - list_0.Add(scavExfiltrationPoint); - } - } - AccessTools.Field(typeof(ExfiltrationControllerClass), "list_0").SetValue(__instance, list_0); - AccessTools.Field(typeof(ExfiltrationControllerClass), "list_1").SetValue(__instance, list_1); - - UnityEngine.Random.InitState((int) DateTimeOffset.UtcNow.ToUnixTimeSeconds()); - - foreach (ExfiltrationPoint exfiltrationPoint in __instance.ExfiltrationPoints) - { - Logger.LogWarning("PMC Exfil name = " + exfiltrationPoint.Settings.Name); - exitName = exfiltrationPoint.Settings.Name; - LocationExitClass gclass = settings.FirstOrDefault(new Func(NameMatches)); - if (gclass != null) - { - exfiltrationPoint.LoadSettings(gclass, giveAuthority); - if (!justLoadSettings && !RandomRange(exfiltrationPoint)) - { - exfiltrationPoint.SetStatusLogged(EExfiltrationStatus.NotPresent, "ExfiltrationController.InitAllExfiltrationPoints-2"); - } - } - else - { - exfiltrationPoint.SetStatusLogged(EExfiltrationStatus.NotPresent, "ExfiltrationController.InitAllExfiltrationPoints-3"); - } - } - - return false; - } - } -} From 72f765deae8c2da50adb9808648a60d2710a466e Mon Sep 17 00:00:00 2001 From: Trap Date: Sat, 24 Aug 2024 10:47:01 +0200 Subject: [PATCH 5/5] fix: router-service was broken when built into js --- src/router-service.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/router-service.ts b/src/router-service.ts index b70fcf75..69e964b2 100644 --- a/src/router-service.ts +++ b/src/router-service.ts @@ -1,5 +1,3 @@ -import { LogTextColor } from '@spt/models/spt/logging/LogTextColor'; -// WTT imports import type { InstanceManager } from './instance-manager'; import { CustomExtracts } from './custom-extracts'; @@ -13,7 +11,7 @@ export class RouterService { this.registerGetAllCustomExtractsRoute(); this.instanceManager.logger.log( `[${this.instanceManager.modName}] WTTRouter: Initialized and registered routes.`, - LogTextColor.GREEN, + 'green', ); } @@ -30,7 +28,7 @@ export class RouterService { if (!allCustomExtracts || allCustomExtracts.length === 0) { this.instanceManager.logger.log( `[${this.instanceManager.modName}] No custom extracts found.`, - LogTextColor.RED, + 'red', ); return JSON.stringify({ success: false, message: 'No custom extracts found' }); } @@ -38,7 +36,7 @@ export class RouterService { // Send all custom extracts data to the client this.instanceManager.logger.log( `[${this.instanceManager.modName}] All custom extracts sent.`, - LogTextColor.GREEN, + 'green', ); return JSON.stringify({ success: true, data: allCustomExtracts }); },