diff --git a/.gitignore b/.gitignore index 9491a2f..f112bcf 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,9 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd + +/BackdoorBandit.csproj +dist/ +build.ps1 +**.7z diff --git a/BackdoorBandit.csproj b/BackdoorBandit.csproj deleted file mode 100644 index 200c94e..0000000 --- a/BackdoorBandit.csproj +++ /dev/null @@ -1,153 +0,0 @@ - - - - - Debug - AnyCPU - {30D38A81-B688-474D-A40A-47BFC63BFE69} - Library - Properties - BackdoorBandit - dvize.BackdoorBandit - v4.7.1 - 512 - true - - - - true - portable - true - bin\Debug\ - TRACE;DEBUG - prompt - 4 - - - none - true - bin\Release\ - - - prompt - 4 - false - - - x64 - bin\x64\Debug\ - - - x64 - bin\x64\Release\ - - - - F:\SPT-AKI-DEV\BepInEx\core\0Harmony.dll - - - False - F:\SPT-AKI-DEV\EscapeFromTarkov_Data\Managed\Assembly-CSharp.dll - - - F:\SPT-AKI-DEV\BepInEx\core\BepInEx.dll - - - - F:\SPT-AKI-DEV\EscapeFromTarkov_Data\Managed\Comfort.dll - - - False - F:\SPT-AKI-DEV\EscapeFromTarkov_Data\Managed\Comfort.Unity.dll - - - False - F:\SPT-AKI-DEV\EscapeFromTarkov_Data\Managed\DissonanceVoip.dll - - - False - F:\SPT-AKI-DEV\EscapeFromTarkov_Data\Managed\Newtonsoft.Json.dll - - - F:\SPT-AKI-DEV\BepInEx\plugins\spt\spt-common.dll - - - F:\SPT-AKI-DEV\BepInEx\patchers\spt-prepatch.dll - - - F:\SPT-AKI-DEV\BepInEx\plugins\spt\spt-reflection.dll - - - - F:\SPT-AKI-DEV\EscapeFromTarkov_Data\Managed\UnityEngine.dll - - - F:\SPT-AKI-DEV\EscapeFromTarkov_Data\Managed\UnityEngine.AudioModule.dll - - - F:\SPT-AKI-DEV\EscapeFromTarkov_Data\Managed\UnityEngine.CoreModule.dll - - - False - F:\SPT-AKI-DEV\EscapeFromTarkov_Data\Managed\UnityEngine.IMGUIModule.dll - - - False - F:\SPT-AKI-DEV\EscapeFromTarkov_Data\Managed\UnityEngine.InputLegacyModule.dll - - - False - F:\SPT-AKI-DEV\EscapeFromTarkov_Data\Managed\UnityEngine.Physics2DModule.dll - - - False - F:\SPT-AKI-DEV\EscapeFromTarkov_Data\Managed\UnityEngine.PhysicsModule.dll - - - False - F:\SPT-AKI-DEV\EscapeFromTarkov_Data\Managed\UnityEngine.TextRenderingModule.dll - - - F:\SPT-AKI-DEV\EscapeFromTarkov_Data\Managed\UnityEngine.UnityWebRequestAudioModule.dll - - - F:\SPT-AKI-DEV\EscapeFromTarkov_Data\Managed\UnityEngine.UnityWebRequestModule.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - copy "$(TargetPath)" "F:\SPT-AKI-DEV\BepInEx\plugins\dvize.BackdoorBandit\$(TargetName).dll" - -if "$(ConfigurationName)" == "Debug" ( - copy "$(TargetDir)$(TargetName).pdb" "F:\SPT-AKI-DEV\BepInEx\plugins\dvize.BackdoorBandit\$(TargetName).pdb" -) else ( - del "F:\SPT-AKI-DEV\BepInEx\plugins\dvize.BackdoorBandit\$(TargetName).pdb" -) - -if "$(ConfigurationName)" == "Release" ( - powershell -ExecutionPolicy Bypass -NoProfile -NonInteractive -File "$(ProjectDir)PackageMods.ps1" -ConfigurationName "$(ConfigurationName)" -TargetPath "F:\SPT-AKI-DEV\BepInEx\plugins\dvize.BackdoorBandit\$(TargetName).dll" -TargetName "$(TargetName)" -TargetDir "F:\SPT-AKI-DEV\BepInEx\plugins\dvize.BackdoorBandit" -) - - \ No newline at end of file diff --git a/DamageUtility.cs b/DamageUtility.cs index a40bf83..8173113 100644 --- a/DamageUtility.cs +++ b/DamageUtility.cs @@ -12,11 +12,10 @@ namespace BackdoorBandit { internal static class DamageUtility { - internal static void CheckWeaponAndAmmo(DamageInfo damageInfo, ref bool validDamage, ref HashSet validWeapons, Func isRoundValid, Func isValidLockHit) + internal static void CheckWeaponAndAmmo(DamageInfoStruct damageInfo, ref bool validDamage, ref HashSet validWeapons, Func isRoundValid, Func isValidLockHit) { - var material = damageInfo.HittedBallisticCollider.TypeOfMaterial; - var weapon = damageInfo.Weapon.TemplateId; - + MaterialType material = damageInfo.HittedBallisticCollider.TypeOfMaterial; + MongoID weaponID = damageInfo.Weapon.TemplateId; //semi-pleb mode. All regular doors are shootable any weapon except for reinforced doors if (DoorBreachPlugin.SemiPlebMode.Value && material != MaterialType.MetalThin && material != MaterialType.MetalThick) @@ -28,7 +27,7 @@ internal static void CheckWeaponAndAmmo(DamageInfo damageInfo, ref bool validDam //regular valid melee weapon check if (damageInfo.DamageType != EDamageType.Bullet && damageInfo.DamageType != EDamageType.GrenadeFragment) { - if (damageInfo.DamageType == EDamageType.Melee && DoorBreachComponent.MeleeWeapons.Contains(weapon) && material != MaterialType.MetalThin && material != MaterialType.MetalThick) + if (damageInfo.DamageType == EDamageType.Melee && DoorBreachComponent.MeleeWeapons.Contains(weaponID) && material != MaterialType.MetalThin && material != MaterialType.MetalThick) { validDamage = true; } @@ -36,16 +35,16 @@ internal static void CheckWeaponAndAmmo(DamageInfo damageInfo, ref bool validDam return; } - var bulletTemplate = Singleton.Instance.ItemTemplates[damageInfo.SourceId] as AmmoTemplate; + AmmoTemplate bulletTemplate = Singleton.Instance.ItemTemplates[damageInfo.SourceId] as AmmoTemplate; #if DEBUG - DoorBreachComponent.Logger.LogInfo($"ammoTemplate: {bulletTemplate.Name}"); - DoorBreachComponent.Logger.LogInfo($"BB: Actual DamageType is : {damageInfo.DamageType}"); - DoorBreachComponent.Logger.LogInfo($"isValidLockHit: {isValidLockHit(damageInfo)}"); - DoorBreachComponent.Logger.LogInfo($"isRoundValid: {isRoundValid(bulletTemplate)}"); - DoorBreachComponent.Logger.LogInfo($"weapon used: {damageInfo.Weapon.LocalizedName()}, id: {damageInfo.Weapon.TemplateId}"); - DoorBreachComponent.Logger.LogInfo($"validWeapons Contains weapon tpl id: {validWeapons.Contains(weapon).ToString()}"); + DoorBreachComponent.Logger.LogDebug($"ammoTemplate: {bulletTemplate.Name}"); + DoorBreachComponent.Logger.LogDebug($"BB: Actual DamageType is : {damageInfo.DamageType}"); + DoorBreachComponent.Logger.LogDebug($"isValidLockHit: {isValidLockHit(damageInfo)}"); + DoorBreachComponent.Logger.LogDebug($"isRoundValid: {isRoundValid(bulletTemplate)}"); + DoorBreachComponent.Logger.LogDebug($"weapon used: {damageInfo.Weapon.LocalizedName()}, id: {damageInfo.Weapon.TemplateId}"); #endif + //check if weapon is a shotgun and material type is metal if (!DoorBreachPlugin.BreachingRoundsOpenMetalDoors.Value) { @@ -57,16 +56,16 @@ internal static void CheckWeaponAndAmmo(DamageInfo damageInfo, ref bool validDam } //check if its on the validWeapons hashset and its not a shotgun.. something user added then we need to skip the isRoundValidCheck - if (validWeapons.Contains(weapon) && !isShotgun(damageInfo) && isValidLockHit(damageInfo)) + if (validWeapons.Contains(weaponID) && !isShotgun(damageInfo) && isValidLockHit(damageInfo)) { validDamage = true; return; } //regular valid weapon and round check - else if (validWeapons.Contains(weapon) && isRoundValid(bulletTemplate) && isValidLockHit(damageInfo)) + else if (validWeapons.Contains(weaponID) && isRoundValid(bulletTemplate) && isValidLockHit(damageInfo)) { #if DEBUG - DoorBreachComponent.Logger.LogInfo($"BB: Valid round detected."); + DoorBreachComponent.Logger.LogDebug($"BB: Valid round detected."); #endif validDamage = true; @@ -80,19 +79,19 @@ internal static void CheckWeaponAndAmmo(DamageInfo damageInfo, ref bool validDam } } - internal static void CheckDoorWeaponAndAmmo(DamageInfo damageInfo, ref bool validDamage) + internal static void CheckDoorWeaponAndAmmo(DamageInfoStruct damageInfo, ref bool validDamage) { CheckWeaponAndAmmo(damageInfo, ref validDamage, ref DoorBreachComponent.ApplicableWeapons, ammo => isHEGrenade(ammo) || isShrapnel(ammo) || isBreachingSlug(ammo), isValidDoorLockHit); } - internal static void CheckCarWeaponAndAmmo(DamageInfo damageInfo, ref bool validDamage) + internal static void CheckCarWeaponAndAmmo(DamageInfoStruct damageInfo, ref bool validDamage) { CheckWeaponAndAmmo(damageInfo, ref validDamage, ref DoorBreachComponent.ApplicableWeapons, ammo => isHEGrenade(ammo) || isShrapnel(ammo) || isBreachingSlug(ammo), isValidCarTrunkLockHit); } - internal static void CheckLootableContainerWeaponAndAmmo(DamageInfo damageInfo, ref bool validDamage) + internal static void CheckLootableContainerWeaponAndAmmo(DamageInfoStruct damageInfo, ref bool validDamage) { CheckWeaponAndAmmo(damageInfo, ref validDamage, ref DoorBreachComponent.ApplicableWeapons, ammo => isHEGrenade(ammo) || isShrapnel(ammo) || isBreachingSlug(ammo), isValidContainerLockHit); @@ -118,13 +117,13 @@ internal static bool isBreachingSlug(AmmoTemplate bulletTemplate) return (bulletTemplate._id == "660249a0712c1005a4a3ab41"); } - internal static bool isShotgun(DamageInfo damageInfo) + internal static bool isShotgun(DamageInfoStruct damageInfo) { //check if weapon is a shotgun return ((damageInfo.Weapon as Weapon)?.WeapClass == "shotgun"); } - internal static bool isValidDoorLockHit(DamageInfo damageInfo) + internal static bool isValidDoorLockHit(DamageInfoStruct damageInfo) { //check if door handle area was hit Collider col = damageInfo.HitCollider; @@ -146,7 +145,7 @@ internal static bool isValidDoorLockHit(DamageInfo damageInfo) } - internal static bool isValidCarTrunkLockHit(DamageInfo damageInfo) + internal static bool isValidCarTrunkLockHit(DamageInfoStruct damageInfo) { //check if door handle area was hit Collider col = damageInfo.HitCollider; @@ -154,11 +153,11 @@ internal static bool isValidCarTrunkLockHit(DamageInfo damageInfo) //if doorhandle exists and is hit if (col.GetComponentInParent().GetComponentInChildren() != null) { - var gameobj = col.GetComponentInParent().gameObject; + GameObject gameobj = col.GetComponentInParent().gameObject; //find child game object Lock from gameobj - var carLockObj = gameobj.transform.Find("CarLock_Hand").gameObject; - var lockObj = carLockObj.transform.Find("Lock").gameObject; + GameObject carLockObj = gameobj.transform.Find("CarLock_Hand").gameObject; + GameObject lockObj = carLockObj.transform.Find("Lock").gameObject; float distanceToLock = Vector3.Distance(damageInfo.HitPoint, lockObj.transform.position); @@ -172,7 +171,7 @@ internal static bool isValidCarTrunkLockHit(DamageInfo damageInfo) } - internal static bool isValidContainerLockHit(DamageInfo damageInfo) + internal static bool isValidContainerLockHit(DamageInfoStruct damageInfo) { //check if door handle area was hit Collider col = damageInfo.HitCollider; @@ -180,10 +179,10 @@ internal static bool isValidContainerLockHit(DamageInfo damageInfo) //if doorhandle exists and is hit if (col.GetComponentInParent().GetComponentInChildren() != null) { - var gameobj = col.GetComponentInParent().gameObject; + GameObject gameobj = col.GetComponentInParent().gameObject; //find child game object Lock from gameobj - var lockObj = gameobj.transform.Find("Lock").gameObject; + GameObject lockObj = gameobj.transform.Find("Lock").gameObject; float distanceToLock = Vector3.Distance(damageInfo.HitPoint, lockObj.transform.position); return distanceToLock < 0.25f; diff --git a/DoorBreachComponent.cs b/DoorBreachComponent.cs index 46526f3..ebc7d54 100644 --- a/DoorBreachComponent.cs +++ b/DoorBreachComponent.cs @@ -98,8 +98,8 @@ private void ProcessObjectsOfType(string objectType, int interactiveLayer) wh if (!IsValidObject(obj, ref invalidCount, ref inoperableCount, ref invalidLayerCount, interactiveLayer)) return; - var randHitPoints = UnityEngine.Random.Range(DoorBreachPlugin.MinHitPoints.Value, DoorBreachPlugin.MaxHitPoints.Value); - var hitpoints = obj.gameObject.GetOrAddComponent(); + int randHitPoints = UnityEngine.Random.Range(DoorBreachPlugin.MinHitPoints.Value, DoorBreachPlugin.MaxHitPoints.Value); + Hitpoints hitpoints = obj.gameObject.GetOrAddComponent(); hitpoints.hitpoints = randHitPoints; if (obj is Door door) @@ -173,8 +173,13 @@ private bool IsValidObject(T obj, ref int invalidCount, ref int inoperableCou return true; } - private bool IsValidDoorState(Door door) => - door.DoorState == EDoorState.Shut || door.DoorState == EDoorState.Locked || door.DoorState == EDoorState.Breaching || door.DoorState == EDoorState.Open; + private bool IsValidDoorState(Door door) + { + if(door.DoorState == EDoorState.Shut || door.DoorState == EDoorState.Locked || door.DoorState == EDoorState.Breaching || door.DoorState == EDoorState.Open) + return true; + + return false; + } private bool IsValidContainerState(LootableContainer container) => container.DoorState == EDoorState.Shut || container.DoorState == EDoorState.Locked || container.DoorState == EDoorState.Breaching; @@ -198,7 +203,7 @@ public static void Enable() { if (Singleton.Instantiated) { - var gameWorld = Singleton.Instance; + GameWorld gameWorld = Singleton.Instance; gameWorld.GetOrAddComponent(); } } @@ -226,10 +231,10 @@ internal static void SetupApplicableWeapons() ApplicableWeapons.UnionWith(DoorBreachComponent.OtherWeapons); #if DEBUG //print out applicable weapons hashes to console - Logger.LogInfo("Applicable Weapons:"); - foreach (var weapon in ApplicableWeapons) + Logger.LogDebug("Applicable Weapons:"); + foreach (string weapon in ApplicableWeapons) { - Logger.LogInfo(weapon); + Logger.LogDebug(weapon); } #endif } diff --git a/ExplosiveBreachComponent.cs b/ExplosiveBreachComponent.cs index 1b519ef..225797f 100644 --- a/ExplosiveBreachComponent.cs +++ b/ExplosiveBreachComponent.cs @@ -80,7 +80,7 @@ private IEnumerator LoadAudioClip(string filePath, bool isBeepClip) internal static bool hasC4Explosives(Player player) { // Search playerItems for first c4 explosive - var foundItem = player.Inventory.GetPlayerItems(EPlayerItems.Equipment).FirstOrDefault(x => x.TemplateId == C4ExplosiveId); + Item foundItem = player.Inventory.GetPlayerItems(EPlayerItems.Equipment).FirstOrDefault(x => x.TemplateId == C4ExplosiveId); if (foundItem != null) { @@ -118,14 +118,14 @@ internal static void StartExplosiveBreach(Door door, Player player) // Start a coroutine for the most recently placed TNT. if (c4Instances.Any()) { - var latestC4Instance = c4Instances.Last(); + C4Instance latestC4Instance = c4Instances.Last(); StartDelayedExplosionCoroutine(door, player, componentInstance, latestC4Instance); } } private static void TryPlaceC4OnDoor(Door door, Player player) { - var itemFactory = Singleton.Instance; - var c4Item = itemFactory.CreateItem(MongoID.Generate(), C4ExplosiveId, null); + ItemFactoryClass itemFactory = Singleton.Instance; + Item c4Item = itemFactory.CreateItem(MongoID.Generate(), C4ExplosiveId, null); Transform lockTransform = door.transform.Find("Lock"); Transform doorHandleTransform = null; @@ -193,11 +193,11 @@ private static void TryPlaceC4OnDoor(Door door, Player player) private static void RemoveItemFromPlayerInventory(Player player) { - var foundItem = player.Inventory.GetPlayerItems(EPlayerItems.Equipment).FirstOrDefault(x => x.TemplateId == C4ExplosiveId); + Item foundItem = player.Inventory.GetPlayerItems(EPlayerItems.Equipment).FirstOrDefault(x => x.TemplateId == C4ExplosiveId); if (foundItem == null) return; - var traderController = (TraderControllerClass)foundItem.Parent.GetOwner(); - var discardResult = InteractionsHandlerClass.Discard(foundItem, traderController, false, false); + TraderControllerClass traderController = (TraderControllerClass)foundItem.Parent.GetOwner(); + GStruct455 discardResult = InteractionsHandlerClass.Discard(foundItem, traderController, false); if (discardResult.Error != null) { @@ -320,7 +320,7 @@ private static void TriggerExplosion(Door door, Player player, C4Instance c4Inst float damageMultiplier = Mathf.Clamp01(1 - distance / explosionRadius); float damageAmount = baseDamage * damageMultiplier; - DamageInfo damageInfo = new DamageInfo + DamageInfoStruct damageInfo = new DamageInfoStruct { DamageType = EDamageType.Explosion, Damage = damageAmount, @@ -382,7 +382,7 @@ public static void Enable() { if (Singleton.Instantiated) { - var gameWorld = Singleton.Instance; + GameWorld gameWorld = Singleton.Instance; gameWorld.GetOrAddComponent(); } diff --git a/PackageMods.ps1 b/PackageMods.ps1 deleted file mode 100644 index 0203c1d..0000000 --- a/PackageMods.ps1 +++ /dev/null @@ -1,117 +0,0 @@ -param ( - [string]$ConfigurationName, - [string]$TargetPath, - [string]$TargetName, - [string]$TargetDir -) - -# Define the base directory -$baseDir = "F:\SPT-AKI-DEV\BepInEx\plugins" - -# Function to log messages to the console -function Log { - param ( - [string]$message - ) - $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" - Write-Host "$timestamp - $message" -} - -Log "Script started" -Log "ConfigurationName: $ConfigurationName" -Log "TargetPath: $TargetPath" -Log "TargetName: $TargetName" -Log "TargetDir: $TargetDir" - -# Get the assembly version -$assembly = [System.Reflection.Assembly]::LoadFile($TargetPath) -$version = $assembly.GetName().Version.ToString() -Log "Assembly version: $version" - -# Determine the directory of the deployed DLL -$deployDir = Split-Path -Parent $TargetPath -Log "DeployDir: $deployDir" - -# Check if the deploy directory is the base directory or one level further -if ($deployDir -ne $baseDir) { - $relativePath = $deployDir.Substring($baseDir.Length + 1) # Get the relative path beyond the base directory - Log "RelativePath: $relativePath" - if (($relativePath -split '\\').Count -eq 1) { # Check if it's exactly one directory level further - $directoryName = (Get-Item $deployDir).Name - $zipPath = "F:\SPT-AKI-DEV\BepInEx\plugins\$directoryName-v$version.zip" - Log "DirectoryName: $directoryName" - Log "ZipPath: $zipPath" - - # Remove existing zip file if it exists - if (Test-Path $zipPath) { - Log "ZipPath exists, removing" - Remove-Item $zipPath -Force - } - - # Create the temp directory structure - $tempZipDir = "F:\SPT-AKI-DEV\tempZip" - if (Test-Path $tempZipDir) { - Log "TempZipDir exists, removing" - Remove-Item $tempZipDir -Recurse -Force - } - New-Item -ItemType Directory -Path $tempZipDir - Log "TempZipDir created: $tempZipDir" - - $newZipStructure = Join-Path $tempZipDir "Bepinex\plugins\$directoryName" - New-Item -ItemType Directory -Path $newZipStructure -Force - Log "New zip structure directory created: $newZipStructure" - - # Copy files to the new zip structure - Copy-Item -Path "$TargetDir\*" -Destination $newZipStructure -Recurse -Force - Log "Files copied to new zip structure" - - # Create the final zip file - Add-Type -AssemblyName System.IO.Compression.FileSystem - [System.IO.Compression.ZipFile]::CreateFromDirectory($tempZipDir, $zipPath) - Log "Final zip file created: $zipPath" - - # Clean up temp directory - Remove-Item $tempZipDir -Recurse -Force - Log "TempZipDir removed" - } else { - Log "RelativePath is not one directory level further" - } -} else { - $zipPath = "F:\SPT-AKI-DEV\BepInEx\plugins\$TargetName-v$version.zip" - Log "ZipPath: $zipPath" - - # Remove existing zip file if it exists - if (Test-Path $zipPath) { - Log "ZipPath exists, removing" - Remove-Item $zipPath -Force - } - - # Create the temp directory structure - $tempZipDir = "F:\SPT-AKI-DEV\tempZip" - if (Test-Path $tempZipDir) { - Log "TempZipDir exists, removing" - Remove-Item $tempZipDir -Recurse -Force - } - New-Item -ItemType Directory -Path $tempZipDir - Log "TempZipDir created: $tempZipDir" - - # Create the required folder structure within the temp directory - $bepinexPluginsDir = Join-Path $tempZipDir "Bepinex\plugins" - New-Item -ItemType Directory -Path $bepinexPluginsDir -Force - Log "Bepinex\plugins directory created: $bepinexPluginsDir" - - # Copy the single DLL to the new structure - Copy-Item -Path $TargetPath -Destination $bepinexPluginsDir -Force - Log "DLL copied to Bepinex\plugins directory" - - # Create the final zip file - Add-Type -AssemblyName System.IO.Compression.FileSystem - [System.IO.Compression.ZipFile]::CreateFromDirectory($tempZipDir, $zipPath) - Log "Final zip file created: $zipPath" - - # Clean up temp directory - Remove-Item $tempZipDir -Recurse -Force - Log "TempZipDir removed" -} - -Log "Script finished" diff --git a/Patches/ActionMenuDoorPatch.cs b/Patches/ActionMenuDoorPatch.cs index a88f2ff..b5012a0 100644 --- a/Patches/ActionMenuDoorPatch.cs +++ b/Patches/ActionMenuDoorPatch.cs @@ -3,33 +3,34 @@ using SPT.Reflection.Patching; using EFT; using EFT.Interactive; +using DoorBreach; namespace BackdoorBandit.Patches { internal class ActionMenuDoorPatch : ModulePatch { - protected override MethodBase GetTargetMethod() => typeof(GetActionsClass).GetMethod(nameof(GetActionsClass.smethod_10)); + protected override MethodBase GetTargetMethod() => typeof(GetActionsClass).GetMethod(nameof(GetActionsClass.smethod_14)); [PatchPostfix] public static void Postfix(ref ActionsReturnClass __result, GamePlayerOwner owner, Door door) { - // Add an additional action after the original method executes - if (__result != null && __result.Actions != null) + if (__result == null || __result.Actions == null) return; + + // Add new action to exisitng actions + ActionsTypesClass breachC4 = new ActionsTypesClass { - __result.Actions.Add(new ActionsTypesClass + Name = "Plant Explosive", + Action = new Action(() => { - Name = "Plant Explosive", - Action = new Action(() => - { - BackdoorBandit.ExplosiveBreachComponent.StartExplosiveBreach(door, owner.Player); + BackdoorBandit.ExplosiveBreachComponent.StartExplosiveBreach(door, owner.Player); + }), + Disabled = (!door.IsBreachAngle(owner.Player.Position) || !BackdoorBandit.ExplosiveBreachComponent.IsValidDoorState(door) || + !BackdoorBandit.ExplosiveBreachComponent.hasC4Explosives(owner.Player)) + }; - }), - Disabled = (!door.IsBreachAngle(owner.Player.Position) || !BackdoorBandit.ExplosiveBreachComponent.IsValidDoorState(door) || - !BackdoorBandit.ExplosiveBreachComponent.hasC4Explosives(owner.Player)) - }); - } + __result.Actions.Add(breachC4); } } } \ No newline at end of file diff --git a/Patches/ActionMenuKeyCardPatch.cs b/Patches/ActionMenuKeyCardPatch.cs index 065d16b..77e1859 100644 --- a/Patches/ActionMenuKeyCardPatch.cs +++ b/Patches/ActionMenuKeyCardPatch.cs @@ -10,7 +10,7 @@ namespace BackdoorBandit.Patches { internal class ActionMenuKeyCardPatch : ModulePatch { - protected override MethodBase GetTargetMethod() => typeof(GetActionsClass).GetMethod(nameof(GetActionsClass.smethod_9)); + protected override MethodBase GetTargetMethod() => typeof(GetActionsClass).GetMethod(nameof(GetActionsClass.smethod_13)); // Check if an action is already added. Hopefully door's action takes precedence @@ -20,21 +20,24 @@ public static bool IsActionAdded(List actions, string actionN } [PatchPostfix] - public static void Postfix(ref ActionsReturnClass __result, GamePlayerOwner owner, Door door) + public static void Postfix(ref ActionsReturnClass __result, GamePlayerOwner owner, Door door, bool isProxy) { - if (__result != null && __result.Actions != null && !IsActionAdded(__result.Actions, "Plant Explosive")) + if (__result == null || __result.Actions == null || IsActionAdded(__result.Actions, "Plant Explosive")) { - __result.Actions.Add(new ActionsTypesClass - { - Name = "Plant Explosive", - Action = new Action(() => - { - BackdoorBandit.ExplosiveBreachComponent.StartExplosiveBreach(door, owner.Player); - }), - Disabled = (!door.IsBreachAngle(owner.Player.Position) || !BackdoorBandit.ExplosiveBreachComponent.IsValidDoorState(door) || - !BackdoorBandit.ExplosiveBreachComponent.hasC4Explosives(owner.Player)) - }); + return; } + + // Add new action after existing actions + __result.Actions.Add(new ActionsTypesClass + { + Name = "Plant Explosive", + Action = new Action(() => + { + BackdoorBandit.ExplosiveBreachComponent.StartExplosiveBreach(door, owner.Player); + }), + Disabled = (!door.IsBreachAngle(owner.Player.Position) || !BackdoorBandit.ExplosiveBreachComponent.IsValidDoorState(door) || + !BackdoorBandit.ExplosiveBreachComponent.hasC4Explosives(owner.Player)) + }); } } } diff --git a/Patches/ApplyHitPatch.cs b/Patches/ApplyHitPatch.cs index 08ea209..d04f219 100644 --- a/Patches/ApplyHitPatch.cs +++ b/Patches/ApplyHitPatch.cs @@ -30,7 +30,7 @@ internal class ApplyHit : ModulePatch [PatchPostfix] - public static void PatchPostFix(DamageInfo damageInfo, GStruct389 shotID) + public static void PatchPostFix(DamageInfoStruct damageInfo, ShotIdStruct shotID) { //try catch for random things applying damage that we don't want try @@ -43,7 +43,7 @@ public static void PatchPostFix(DamageInfo damageInfo, GStruct389 shotID) catch { } } - private static bool ShouldApplyDamage(DamageInfo damageInfo) + private static bool ShouldApplyDamage(DamageInfoStruct damageInfo) { return damageInfo.Player != null && damageInfo.Player.iPlayer.IsYourPlayer @@ -52,7 +52,7 @@ private static bool ShouldApplyDamage(DamageInfo damageInfo) && damageInfo.DamageType != EDamageType.Explosion; } - private static void HandleDamageForEntity(DamageInfo damageInfo, BallisticCollider collider) + private static void HandleDamageForEntity(DamageInfoStruct damageInfo, BallisticCollider collider) { bool isCarTrunk = false; bool isLootableContainer = false; @@ -85,7 +85,7 @@ private static void HandleDamageForEntity(DamageInfo damageInfo, BallisticCollid } #region DamageApplication - private static void HandleCarTrunkDamage(DamageInfo damageInfo, BallisticCollider collider, ref bool validDamage) + private static void HandleCarTrunkDamage(DamageInfoStruct damageInfo, BallisticCollider collider, ref bool validDamage) { if (!DoorBreachPlugin.PlebMode.Value && DoorBreachPlugin.OpenCarDoors.Value) { @@ -96,13 +96,13 @@ private static void HandleCarTrunkDamage(DamageInfo damageInfo, BallisticCollide { if (hitpoints.hitpoints <= 0) { - var carTrunk = entity.GetComponentInParent(); + Trunk carTrunk = entity.GetComponentInParent(); OpenDoorIfNotAlreadyOpen(carTrunk, damageInfo.Player.AIData.Player, EInteractionType.Open); } }); } - private static void HandleLootableContainerDamage(DamageInfo damageInfo, BallisticCollider collider, ref bool validDamage) + private static void HandleLootableContainerDamage(DamageInfoStruct damageInfo, BallisticCollider collider, ref bool validDamage) { if (!DoorBreachPlugin.PlebMode.Value && DoorBreachPlugin.OpenLootableContainers.Value) { @@ -113,13 +113,13 @@ private static void HandleLootableContainerDamage(DamageInfo damageInfo, Ballist { if (hitpoints.hitpoints <= 0) { - var lootContainer = entity.GetComponentInParent(); + LootableContainer lootContainer = entity.GetComponentInParent(); OpenDoorIfNotAlreadyOpen(lootContainer, damageInfo.Player.AIData.Player, EInteractionType.Open); } }); } - internal static void HandleDoorDamage(DamageInfo damageInfo, BallisticCollider collider, ref bool validDamage) + internal static void HandleDoorDamage(DamageInfoStruct damageInfo, BallisticCollider collider, ref bool validDamage) { if (!DoorBreachPlugin.PlebMode.Value) { @@ -128,17 +128,25 @@ internal static void HandleDoorDamage(DamageInfo damageInfo, BallisticCollider c HandleDamage(damageInfo, collider, ref validDamage, "Door", (hitpoints, entity) => { + WorldInteractiveObject door = entity.GetComponentInParent(); + +#if DEBUG + DoorBreachComponent.Logger.LogDebug("[Door info]"); + DoorBreachComponent.Logger.LogDebug($"KeyId: {door.KeyId}"); + DoorBreachComponent.Logger.LogDebug($"DoorState: {door.DoorState}"); + DoorBreachComponent.Logger.LogDebug($"InitialDoorState: {door.InitialDoorState}"); +#endif + if (hitpoints.hitpoints <= 0) { - var door = entity.GetComponentInParent(); OpenDoorIfNotAlreadyOpen(door, damageInfo.Player.AIData.Player, EInteractionType.Breach); } }); } - internal static void HandleDamage(DamageInfo damageInfo, BallisticCollider collider, ref bool validDamage, string entityName, Action onHitpointsZero) + internal static void HandleDamage(DamageInfoStruct damageInfo, BallisticCollider collider, ref bool validDamage, string entityName, Action onHitpointsZero) { - var hitpoints = collider.GetComponentInParent() as Hitpoints; + Hitpoints hitpoints = collider.GetComponentInParent() as Hitpoints; if (validDamage) { @@ -187,7 +195,7 @@ internal static void OpenDoorIfNotAlreadyOpen(T entity, Player player, EInter } - #endregion +#endregion diff --git a/Patches/PerfectCullingNullRefPatch.cs b/Patches/PerfectCullingNullRefPatch.cs index 62f0e20..d815e93 100644 --- a/Patches/PerfectCullingNullRefPatch.cs +++ b/Patches/PerfectCullingNullRefPatch.cs @@ -18,7 +18,7 @@ static bool Prefix(PerfectCullingBakeGroup __instance, bool rendererEnabled, int // Safely handle Renderer[] array if (__instance.runtimeProxies != null) { - foreach (var renderer in __instance.runtimeProxies) + foreach (Renderer renderer in __instance.runtimeProxies) { if (renderer != null) { @@ -30,7 +30,7 @@ static bool Prefix(PerfectCullingBakeGroup __instance, bool rendererEnabled, int // Safely handle CullingObject array if (__instance.cullingLightObjects != null) { - foreach (var cullingObject in __instance.cullingLightObjects) + foreach (CullingObject cullingObject in __instance.cullingLightObjects) { if (cullingObject != null) { @@ -42,7 +42,7 @@ static bool Prefix(PerfectCullingBakeGroup __instance, bool rendererEnabled, int // Safely handle AnalyticSource array if (__instance.analyticSources != null) { - foreach (var analyticSource in __instance.analyticSources) + foreach (AnalyticSource analyticSource in __instance.analyticSources) { if (analyticSource != null) { diff --git a/Plugin.cs b/Plugin.cs index d7a2e93..d82748e 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -11,8 +11,7 @@ namespace DoorBreach { - [BepInPlugin("com.dvize.BackdoorBandit", "dvize.BackdoorBandit", "1.9.0")] - //[BepInDependency("com.spt-aki.core", "3.7.6")] + [BepInPlugin("com.dvize.BackdoorBandit", "dvize.BackdoorBandit", "1.11.1")] public class DoorBreachPlugin : BaseUnityPlugin { public static ConfigEntry PlebMode; diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 3f62ddd..5c2088f 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -7,7 +7,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("dvize.BackdoorBandit")] -[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -18,6 +18,6 @@ [assembly: Guid("8fdfc616-1704-4056-a87e-2783692cc153")] -[assembly: AssemblyVersion("1.9.0.0")] -[assembly: AssemblyFileVersion("1.9.0.0")] -[assembly: TarkovVersion(29197)] +[assembly: AssemblyVersion("1.11.2")] +[assembly: AssemblyFileVersion("1.11.2")] +[assembly: TarkovVersion(35392)] diff --git a/server files/BepInEx/plugins/dvize.BackdoorBandit/Beep.mp3 b/server files/BepInEx/plugins/dvize.BackdoorBandit/Beep.mp3 new file mode 100644 index 0000000..07b424d Binary files /dev/null and b/server files/BepInEx/plugins/dvize.BackdoorBandit/Beep.mp3 differ diff --git a/server files/BepInEx/plugins/dvize.BackdoorBandit/FinalBeepTone.mp3 b/server files/BepInEx/plugins/dvize.BackdoorBandit/FinalBeepTone.mp3 new file mode 100644 index 0000000..d439391 Binary files /dev/null and b/server files/BepInEx/plugins/dvize.BackdoorBandit/FinalBeepTone.mp3 differ diff --git a/server files/BepInEx/plugins/dvize.BackdoorBandit/GrenadeLaunchers.json b/server files/BepInEx/plugins/dvize.BackdoorBandit/GrenadeLaunchers.json new file mode 100644 index 0000000..0ba0dd4 --- /dev/null +++ b/server files/BepInEx/plugins/dvize.BackdoorBandit/GrenadeLaunchers.json @@ -0,0 +1,7 @@ +[ + "62e7e7bbe6da9612f743f1e0", + "6357c98711fb55120211f7e1", + "5e81ebcd8e146c7080625e15", + "5d52cc5ba4b9367408500062", + "6275303a9f372d6ea97f9ec7" +] diff --git a/server files/BepInEx/plugins/dvize.BackdoorBandit/MeleeWeapons.json b/server files/BepInEx/plugins/dvize.BackdoorBandit/MeleeWeapons.json new file mode 100644 index 0000000..7fa2958 --- /dev/null +++ b/server files/BepInEx/plugins/dvize.BackdoorBandit/MeleeWeapons.json @@ -0,0 +1,7 @@ +[ + "63a0b208f444d32d6f03ea1e", + "6087e570b998180e9f76dc24", + "5c07df7f0db834001b73588a", + "5bffe7930db834001b734a39", + "57cd379a24597778e7682ecf" +] diff --git a/server files/BepInEx/plugins/dvize.BackdoorBandit/OtherWeapons.json b/server files/BepInEx/plugins/dvize.BackdoorBandit/OtherWeapons.json new file mode 100644 index 0000000..e062f20 --- /dev/null +++ b/server files/BepInEx/plugins/dvize.BackdoorBandit/OtherWeapons.json @@ -0,0 +1,3 @@ +[ + "" +] diff --git a/server files/BepInEx/plugins/dvize.BackdoorBandit/ShotgunWeapons.json b/server files/BepInEx/plugins/dvize.BackdoorBandit/ShotgunWeapons.json new file mode 100644 index 0000000..fcce5fb --- /dev/null +++ b/server files/BepInEx/plugins/dvize.BackdoorBandit/ShotgunWeapons.json @@ -0,0 +1,14 @@ +[ + "5580223e4bdc2d1c128b457f", + "54491c4f4bdc2db1078b4568", + "6259b864ebedf17603599e88", + "5e870397991fd70db46995c8", + "576165642459773c7a400233", + "5447b6094bdc2dc3278b4567", + "60db29ce99594040e04c4a27", + "5580223e4bdc2d1c128b457f", + "56dee2bdd2720bc8328b4567", + "606dae0ab0e443224b421bb7", + "64748cb8de82c85eaf0a273a", + "5a7828548dc32e5a9c28b516" +] diff --git a/server files/user/mods/DoorBreacher/bundles.json b/server files/user/mods/DoorBreacher/bundles.json new file mode 100644 index 0000000..e7276a2 --- /dev/null +++ b/server files/user/mods/DoorBreacher/bundles.json @@ -0,0 +1,28 @@ +{ + "manifest": [ + { + "key": "DoorBreacher.bundle", + "dependencyKeys": [ + "cubemaps", + "shaders", + "assets/commonassets/physics/physicsmaterials.bundle" + ] + }, + { + "key": "DoorBreacherBox.bundle", + "dependencyKeys": [ + "cubemaps", + "shaders", + "assets/commonassets/physics/physicsmaterials.bundle" + ] + }, + { + "key": "C4Explosive.bundle", + "dependencyKeys": [ + "cubemaps", + "shaders", + "assets/commonassets/physics/physicsmaterials.bundle" + ] + } + ] +} \ No newline at end of file diff --git a/server files/user/mods/DoorBreacher/bundles/C4Explosive.bundle b/server files/user/mods/DoorBreacher/bundles/C4Explosive.bundle new file mode 100644 index 0000000..576bd69 Binary files /dev/null and b/server files/user/mods/DoorBreacher/bundles/C4Explosive.bundle differ diff --git a/server files/user/mods/DoorBreacher/bundles/DoorBreacher.bundle b/server files/user/mods/DoorBreacher/bundles/DoorBreacher.bundle new file mode 100644 index 0000000..5dd5dce Binary files /dev/null and b/server files/user/mods/DoorBreacher/bundles/DoorBreacher.bundle differ diff --git a/server files/user/mods/DoorBreacher/bundles/DoorBreacherBox.bundle b/server files/user/mods/DoorBreacher/bundles/DoorBreacherBox.bundle new file mode 100644 index 0000000..fde5c4c Binary files /dev/null and b/server files/user/mods/DoorBreacher/bundles/DoorBreacherBox.bundle differ diff --git a/server files/user/mods/DoorBreacher/database/templates/craftingItem.jsonc b/server files/user/mods/DoorBreacher/database/templates/craftingItem.jsonc new file mode 100644 index 0000000..693e86c --- /dev/null +++ b/server files/user/mods/DoorBreacher/database/templates/craftingItem.jsonc @@ -0,0 +1,54 @@ +[ + { + "_id": "665d4ce7e381d16c8676292b", // Unique MongoID for this recipe + "areaType": 10, + "requirements": [ + { + "areaType": 10, //Workbench + "requiredLevel": 2, + "type": "Area" + }, + { + "templateId": "60391b0fb847c71012789415", // TNT + "count": 2, + "isFunctional": false, + "isEncoded": false, + "type": "Item" + }, + { + "templateId": "5c06779c86f77426e00dd782", // Bundle of wires + "count": 1, + "isFunctional": false, + "isEncoded": false, + "type": "Item" + }, + { + "templateId": "5e2af51086f7746d3f3c3402", // Greade fuze + "count": 1, + "isFunctional": false, + "isEncoded": false, + "type": "Item" + }, + { + "templateId": "5d0376a486f7747d8050965c", // Military circuit board + "count": 1, + "isFunctional": false, + "isEncoded": false, + "type": "Item" + }, + { + "templateId": "5af04b6486f774195a3ebb49", // Pliers elite tool + "type": "Tool" + } + ], + "productionTime": 3600, // Time to craft in seconds + "needFuelForAllProductionTime": false, + "locked": false, + "endProduct": "6636606320e842b50084e51a", // C4 template_id, don't change + "continuous": false, + "count": 1, // How many can be produced at once + "productionLimitCount": 0, + "isEncoded": false, + "isCodeProduction": false + } +] \ No newline at end of file diff --git a/server files/user/mods/DoorBreacher/database/templates/items.jsonc b/server files/user/mods/DoorBreacher/database/templates/items.jsonc new file mode 100644 index 0000000..40c88f2 --- /dev/null +++ b/server files/user/mods/DoorBreacher/database/templates/items.jsonc @@ -0,0 +1,290 @@ +{ + "doorbreacher": { + "_id": "660249a0712c1005a4a3ab41", //unique mongo id for doorbreacher shell + "_name": "doorbreacher", + "_parent": "5485a8684bdc2da71d8b4567", //ammo + "_type": "Item", + "_props": { + "Name": "12x70 Breaching Round", + "ShortName": "12x70", + "Description": "12x70 Breacher", + "Weight": 0.048, + "BackgroundColor": "yellow", + "Width": 1, + "Height": 1, + "StackMaxSize": 20, + "ItemSound": "ammo_shotgun", + "Prefab": { + "path": "DoorBreacher.bundle", + "rcid": "" + }, + "UsePrefab": { + "path": "", + "rcid": "" + }, + "StackObjectsCount": 1, + "NotShownInSlot": false, + "ExaminedByDefault": false, + "ExamineTime": 1, + "IsUndiscardable": false, + "IsUnsaleable": false, + "IsUnbuyable": false, + "IsUngivable": false, + "IsLockedafterEquip": false, + "QuestItem": false, + "LootExperience": 0, + "ExamineExperience": 100, + "HideEntrails": false, + "RepairCost": 0, + "RepairSpeed": 0, + "ExtraSizeLeft": 0, + "ExtraSizeRight": 0, + "ExtraSizeUp": 0, + "ExtraSizeDown": 0, + "ExtraSizeForceAdd": false, + "MergesWithChildren": false, + "CanSellOnRagfair": true, + "CanRequireOnRagfair": true, + "ConflictingItems": [], + "Unlootable": false, + "UnlootableFromSlot": "FirstPrimaryWeapon", + "UnlootableFromSide": [], + "AnimationVariantsNumber": 0, + "DiscardingBlock": false, + "RagFairCommissionModifier": 1, + "IsAlwaysAvailableForInsurance": false, + "DiscardLimit": -1, + "DropSoundType": "None", + "InsuranceDisabled": false, + "QuestStashMaxCount": 0, + "IsSpecialSlotOnly": false, + "IsUnremovable": false, + "StackMinRandom": 1, + "StackMaxRandom": 10, + "ammoType": "bullet", + "InitialSpeed": 370, + "BallisticCoeficient": 0.121, + "BulletMassGram": 45, + "BulletDiameterMilimeters": 13, + "Damage": 197, + "ammoAccr": 115, + "ammoRec": 0, + "ammoDist": 0, + "buckshotBullets": 8, + "PenetrationPower": 26, + "PenetrationPowerDiviation": 1, + "ammoHear": 0, + "ammoSfx": "standart", + "MisfireChance": 0.00, + "MinFragmentsCount": 1, + "MaxFragmentsCount": 1, + "ammoShiftChance": 0, + "casingName": "12x70 Breacher", + "casingEjectPower": 0, + "casingMass": 0, + "casingSounds": "shotgun_big", + "ProjectileCount": 1, + "PenetrationChance": 0.28, + "RicochetChance": 0.1, + "FragmentationChance": 0.05, + "Deterioration": 1, + "SpeedRetardation": 0.00013, + "Tracer": false, + "TracerColor": "red", + "TracerDistance": 0.5, + "ArmorDamage": 57, + "Caliber": "Caliber12g", + "StaminaBurnPerDamage": 0.21024, + "HeavyBleedingDelta": 0.15, + "LightBleedingDelta": 0, + "ShowBullet": false, + "HasGrenaderComponent": false, + "FuzeArmTimeSec": 0, + "ExplosionStrength": 0, + "MinExplosionDistance": 0, + "MaxExplosionDistance": 0, + "FragmentsCount": 0, + "FragmentType": "5996f6d686f77467977ba6cc", + "ShowHitEffectOnExplode": false, + "ExplosionType": "", + "AmmoLifeTimeSec": 5, + "Contusion": { + "x": 0, + "y": 0, + "z": 0 + }, + "ArmorDistanceDistanceDamage": { + "x": 0, + "y": 0, + "z": 0 + }, + "Blindness": { + "x": 0, + "y": 0, + "z": 0 + }, + "IsLightAndSoundShot": false, + "LightAndSoundShotAngle": 0, + "LightAndSoundShotSelfContusionTime": 0, + "LightAndSoundShotSelfContusionStrength": 0, + "MalfMisfireChance": 0.0, + "DurabilityBurnModificator": 1.5, + "HeatFactor": 2.51, + "MalfFeedChance": 0, + "RemoveShellAfterFire": false + }, + "_proto": "560d5e524bdc2d25448b4571" //12/70 buckshot + }, + + "doorbreacherbox": { + "_id": "660ed60d5effc350b86bb289", //unique mongo id for doorbreacher box + "_name": "doorbreacherbox", + "_parent": "543be5cb4bdc2deb348b4568", // Ammo Box + "_type": "Item", + "_props": { + "Name": "ammo_box_12x70_default", + "ShortName": "ammo_box_12x70_default", + "Description": "ammo_box_12x70_default", + "Weight": 0.35, + "BackgroundColor": "yellow", + "Width": 1, + "Height": 1, + "StackMaxSize": 1, + "ItemSound": "ammo_pack_generic", + "Prefab": { + "path": "DoorBreacherBox.bundle", + "rcid": "" + }, + "UsePrefab": { + "path": "", + "rcid": "" + }, + "StackObjectsCount": 1, + "NotShownInSlot": false, + "ExaminedByDefault": true, + "ExamineTime": 1, + "IsUndiscardable": false, + "IsUnsaleable": false, + "IsUnbuyable": false, + "IsUngivable": false, + "IsLockedafterEquip": false, + "QuestItem": false, + "LootExperience": 0, + "ExamineExperience": 10, + "HideEntrails": true, + "RepairCost": 0, + "RepairSpeed": 0, + "ExtraSizeLeft": 0, + "ExtraSizeRight": 0, + "ExtraSizeUp": 0, + "ExtraSizeDown": 0, + "ExtraSizeForceAdd": false, + "MergesWithChildren": false, + "CanSellOnRagfair": true, + "CanRequireOnRagfair": true, + "ConflictingItems": [], + "Unlootable": false, + "UnlootableFromSlot": "FirstPrimaryWeapon", + "UnlootableFromSide": [], + "AnimationVariantsNumber": 0, + "DiscardingBlock": false, + "RagFairCommissionModifier": 1, + "IsAlwaysAvailableForInsurance": false, + "DiscardLimit": -1, + "DropSoundType": "None", + "InsuranceDisabled": false, + "QuestStashMaxCount": 0, + "IsSpecialSlotOnly": false, + "IsUnremovable": false, + "StackMinRandom": 5, + "StackMaxRandom": 5, + "ammoCaliber": "Caliber12g", + "StackSlots": [ + { + "_name": "cartridges", + "_id": "6627cd20915ff02066761f3f", //? unique id for stack slot + "_parent": "660ed60d5effc350b86bb289", //doorbreacher box + "_max_count": 5, + "_props": { + "filters": [ + { + "Filter": [ + "660249a0712c1005a4a3ab41" //doorbreacher shell + ] + } + ] + }, + "_proto": "5748538b2459770af276a261" //?? + } + ] + } + }, + + "C4Explosive":{ + "_id": "6636606320e842b50084e51a", //unique mongo id for c4explosive + "_name": "C4Explosive", + "_parent": "57864e4c24597754843f8723", //lubricant + "_type": "Item", + "_props": { + "Name": "C4 Explosive", + "ShortName": "C4 Explosive", + "Description": "C4 Explosive", + "Weight": 0.6, + "BackgroundColor": "blue", + "Width": 2, + "Height": 1, + "StackMaxSize": 1, + "ItemSound": "generic", + "Prefab": { + "path": "C4Explosive.bundle", + "rcid": "" + }, + "UsePrefab": { + "path": "", + "rcid": "" + }, + "StackObjectsCount": 1, + "NotShownInSlot": false, + "ExaminedByDefault": true, + "ExamineTime": 1, + "IsUndiscardable": false, + "IsUnsaleable": false, + "IsUnbuyable": false, + "IsUngivable": false, + "IsLockedafterEquip": false, + "QuestItem": false, + "LootExperience": 20, + "ExamineExperience": 10, + "HideEntrails": false, + "RepairCost": 0, + "RepairSpeed": 0, + "ExtraSizeLeft": 0, + "ExtraSizeRight": 0, + "ExtraSizeUp": 0, + "ExtraSizeDown": 0, + "ExtraSizeForceAdd": false, + "MergesWithChildren": false, + "CanSellOnRagfair": true, + "CanRequireOnRagfair": false, + "ConflictingItems": [], + "Unlootable": false, + "UnlootableFromSlot": "FirstPrimaryWeapon", + "UnlootableFromSide": [], + "AnimationVariantsNumber": 0, + "DiscardingBlock": false, + "RagFairCommissionModifier": 1, + "IsAlwaysAvailableForInsurance": false, + "DiscardLimit": 0, + "DropSoundType": "None", + "InsuranceDisabled": false, + "QuestStashMaxCount": 0, + "IsSpecialSlotOnly": false, + "IsUnremovable": false, + "MaxResource": 0, + "Resource": 0 + }, + "_proto": "590a373286f774287540368b" + } + +} + diff --git a/server files/user/mods/DoorBreacher/package.json b/server files/user/mods/DoorBreacher/package.json new file mode 100644 index 0000000..5365d20 --- /dev/null +++ b/server files/user/mods/DoorBreacher/package.json @@ -0,0 +1,31 @@ +{ + "name": "props-doorbreacher", + "version": "1.11.0", + "main": "src/mod.js", + "isBundleMod": true, + "license": "MIT", + "author": "Props", + "sptVersion": "~3.11", + "loadBefore": [], + "loadAfter": [], + "incompatibilities": [], + "contributors": ["Tron", "MakerMacher"], + "scripts": { + "setup": "npm i", + "build": "node ./build.mjs", + "buildinfo": "node ./build.mjs --verbose" + }, + "devDependencies": { + "@types/node": "^22.12.0", + "@typescript-eslint/eslint-plugin": "6.7.5", + "@typescript-eslint/parser": "6.7.5", + "archiver": "^6.0", + "eslint": "8.51.0", + "fs-extra": "^11.1", + "ignore": "^5.2", + "os": "^0.1", + "tsyringe": "4.8.0", + "typescript": "5.8.2", + "winston": "3.11.0" + } +} diff --git a/server files/user/mods/DoorBreacher/src/fluentTraderAssortCreator.js b/server files/user/mods/DoorBreacher/src/fluentTraderAssortCreator.js new file mode 100644 index 0000000..cad5657 --- /dev/null +++ b/server files/user/mods/DoorBreacher/src/fluentTraderAssortCreator.js @@ -0,0 +1,102 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.FluentAssortConstructor = void 0; +class FluentAssortConstructor { + itemsToSell = []; + barterScheme = {}; + loyaltyLevels = {}; + hashUtil; + logger; + constructor(hashUtil, logger) { + this.hashUtil = hashUtil; + this.logger = logger; + } + createSingleAssortItem(itemTpl, itemId = undefined) { + const newItem = { + _id: itemId || this.hashUtil.generate(), + _tpl: itemTpl, + parentId: "hideout", + slotId: "hideout", + upd: { UnlimitedCount: false, StackObjectsCount: 100 } + }; + this.itemsToSell.push(newItem); + return this; + } + addStackCount(stackCount, itemId) { + const item = itemId ? this.itemsToSell.find(i => i._id === itemId) : this.itemsToSell[0]; + if (item) { + item.upd.StackObjectsCount = stackCount; + } + return this; + } + addUnlimitedStackCount(itemId) { + const item = itemId ? this.itemsToSell.find(i => i._id === itemId) : this.itemsToSell[0]; + if (item) { + item.upd.StackObjectsCount = 999999; + item.upd.UnlimitedCount = true; + } + return this; + } + addBuyRestriction(maxBuyLimit, itemId) { + const item = itemId ? this.itemsToSell.find(i => i._id === itemId) : this.itemsToSell[0]; + if (item) { + item.upd.BuyRestrictionMax = maxBuyLimit; + item.upd.BuyRestrictionCurrent = 0; + } + return this; + } + addLoyaltyLevel(level, itemId) { + const item = itemId ? this.itemsToSell.find(i => i._id === itemId) : this.itemsToSell[0]; + if (item) { + this.loyaltyLevels[item._id] = level; + } + return this; + } + addMoneyCost(currencyType, amount, itemId) { + const item = itemId ? this.itemsToSell.find(i => i._id === itemId) : this.itemsToSell[0]; + if (item) { + this.barterScheme[item._id] = [[{ count: amount, _tpl: currencyType }]]; + } + return this; + } + addBarterCost(itemTpl, count, itemId) { + const item = itemId ? this.itemsToSell.find(i => i._id === itemId) : this.itemsToSell[0]; + if (!this.barterScheme[item._id]) { + this.barterScheme[item._id] = [[{ count, _tpl: itemTpl }]]; + } + else { + const scheme = this.barterScheme[item._id][0].find(s => s._tpl === itemTpl); + if (scheme) { + scheme.count += count; + } + else { + this.barterScheme[item._id][0].push({ count, _tpl: itemTpl }); + } + } + return this; + } + export(data) { + for (const item of this.itemsToSell) { + if (data.assort.items.some(i => i._id === item._id)) { + this.logger.error(`Item with ID ${item._id} already exists in the assortment.`); + return; + } + //this.logger.info(`Adding item with ID ${item._id} to the assortment.`) + //this.logger.info(item); + data.assort.items.push(item); + if (this.barterScheme[item._id]) { + data.assort.barter_scheme[item._id] = this.barterScheme[item._id]; + } + if (this.loyaltyLevels[item._id]) { + data.assort.loyal_level_items[item._id] = this.loyaltyLevels[item._id]; + } + } + // Reset internal state + this.itemsToSell = []; + this.barterScheme = {}; + this.loyaltyLevels = {}; + return this; + } +} +exports.FluentAssortConstructor = FluentAssortConstructor; +//# sourceMappingURL=fluentTraderAssortCreator.js.map \ No newline at end of file diff --git a/server files/user/mods/DoorBreacher/src/fluentTraderAssortCreator.js.map b/server files/user/mods/DoorBreacher/src/fluentTraderAssortCreator.js.map new file mode 100644 index 0000000..349e6e8 --- /dev/null +++ b/server files/user/mods/DoorBreacher/src/fluentTraderAssortCreator.js.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "file": "fluentTraderAssortCreator.js", + "sourceRoot": "", + "sources": [ + "fluentTraderAssortCreator.ts" + ], + "names": [], + "mappings": "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" +} \ No newline at end of file diff --git a/server files/user/mods/DoorBreacher/src/fluentTraderAssortCreator.ts b/server files/user/mods/DoorBreacher/src/fluentTraderAssortCreator.ts new file mode 100644 index 0000000..2455461 --- /dev/null +++ b/server files/user/mods/DoorBreacher/src/fluentTraderAssortCreator.ts @@ -0,0 +1,113 @@ +import { IItem } from "@spt/models/eft/common/tables/IItem"; +import { IBarterScheme, ITrader } from "@spt/models/eft/common/tables/ITrader"; +import { Money } from "@spt/models/enums/Money"; +import { ILogger } from "@spt/models/spt/utils/ILogger"; +import { HashUtil } from "@spt/utils/HashUtil"; + +export class FluentAssortConstructor { + protected itemsToSell: IItem[] = []; + protected barterScheme: Record = {}; + protected loyaltyLevels: Record = {}; + protected hashUtil: HashUtil; + protected logger: ILogger; + + constructor(hashUtil: HashUtil, logger: ILogger) { + this.hashUtil = hashUtil; + this.logger = logger; + } + + public createSingleAssortItem(itemTpl: string, itemId = undefined): FluentAssortConstructor { + const newItem: IItem = { + _id: itemId || this.hashUtil.generate(), + _tpl: itemTpl, + parentId: "hideout", + slotId: "hideout", + upd: { UnlimitedCount: false, StackObjectsCount: 100 } + }; + this.itemsToSell.push(newItem); + return this; + } + + public addStackCount(stackCount: number, itemId?: string): FluentAssortConstructor { + const item = itemId ? this.itemsToSell.find(i => i._id === itemId) : this.itemsToSell[0]; + if (item) { + item.upd.StackObjectsCount = stackCount; + } + return this; + } + + public addUnlimitedStackCount(itemId?: string): FluentAssortConstructor { + const item = itemId ? this.itemsToSell.find(i => i._id === itemId) : this.itemsToSell[0]; + if (item) { + item.upd.StackObjectsCount = 999999; + item.upd.UnlimitedCount = true; + } + return this; + } + + public addBuyRestriction(maxBuyLimit: number, itemId?: string): FluentAssortConstructor { + const item = itemId ? this.itemsToSell.find(i => i._id === itemId) : this.itemsToSell[0]; + if (item) { + item.upd.BuyRestrictionMax = maxBuyLimit; + item.upd.BuyRestrictionCurrent = 0; + } + return this; + } + + public addLoyaltyLevel(level: number, itemId?: string): FluentAssortConstructor { + const item = itemId ? this.itemsToSell.find(i => i._id === itemId) : this.itemsToSell[0]; + if (item) { + this.loyaltyLevels[item._id] = level; + } + return this; + } + + public addMoneyCost(currencyType: Money, amount: number, itemId?: string): FluentAssortConstructor { + const item = itemId ? this.itemsToSell.find(i => i._id === itemId) : this.itemsToSell[0]; + if (item) { + this.barterScheme[item._id] = [[{ count: amount, _tpl: currencyType }]]; + } + return this; + } + + public addBarterCost(itemTpl: string, count: number, itemId?: string): FluentAssortConstructor { + const item = itemId ? this.itemsToSell.find(i => i._id === itemId) : this.itemsToSell[0]; + if (!this.barterScheme[item._id]) { + this.barterScheme[item._id] = [[{ count, _tpl: itemTpl }]]; + } else { + const scheme = this.barterScheme[item._id][0].find(s => s._tpl === itemTpl); + if (scheme) { + scheme.count += count; + } else { + this.barterScheme[item._id][0].push({ count, _tpl: itemTpl }); + } + } + return this; + } + + public export(data: ITrader): FluentAssortConstructor { + for (const item of this.itemsToSell) { + if (data.assort.items.some(i => i._id === item._id)) { + this.logger.error(`Item with ID ${item._id} already exists in the assortment.`); + return; + } + //this.logger.info(`Adding item with ID ${item._id} to the assortment.`) + //this.logger.info(item); + + data.assort.items.push(item); + if (this.barterScheme[item._id]) { + data.assort.barter_scheme[item._id] = this.barterScheme[item._id]; + } + if (this.loyaltyLevels[item._id]) { + data.assort.loyal_level_items[item._id] = this.loyaltyLevels[item._id]; + } + } + + // Reset internal state + this.itemsToSell = []; + this.barterScheme = {}; + this.loyaltyLevels = {}; + + return this; + } +} diff --git a/server files/user/mods/DoorBreacher/src/items.type.js b/server files/user/mods/DoorBreacher/src/items.type.js new file mode 100644 index 0000000..900e554 --- /dev/null +++ b/server files/user/mods/DoorBreacher/src/items.type.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=items.type.js.map \ No newline at end of file diff --git a/server files/user/mods/DoorBreacher/src/items.type.js.map b/server files/user/mods/DoorBreacher/src/items.type.js.map new file mode 100644 index 0000000..517158d --- /dev/null +++ b/server files/user/mods/DoorBreacher/src/items.type.js.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "file": "items.type.js", + "sourceRoot": "", + "sources": [ + "items.type.ts" + ], + "names": [], + "mappings": "" +} \ No newline at end of file diff --git a/server files/user/mods/DoorBreacher/src/items.type.ts b/server files/user/mods/DoorBreacher/src/items.type.ts new file mode 100644 index 0000000..83588a8 --- /dev/null +++ b/server files/user/mods/DoorBreacher/src/items.type.ts @@ -0,0 +1,8 @@ +import type { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem"; + + +export interface ItemsJson { + doorbreacher: ITemplateItem; + doorbreacherbox: ITemplateItem; + C4Explosive: ITemplateItem; +} \ No newline at end of file diff --git a/server files/user/mods/DoorBreacher/src/mod.js b/server files/user/mods/DoorBreacher/src/mod.js new file mode 100644 index 0000000..ebaa8db --- /dev/null +++ b/server files/user/mods/DoorBreacher/src/mod.js @@ -0,0 +1,194 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const Money_1 = require("C:/snapshot/project/obj/models/enums/Money"); +const fluentTraderAssortCreator_1 = require("./fluentTraderAssortCreator"); +const node_path_1 = __importDefault(require("node:path")); +class Mod { + // Declare private variable db of DatabaseServer type + dbServer; + db; + fluentTraderAssortHelper; + traderID; + itemsJson; + logger; + jsonUtil; + postDBLoad(container) { + // Resolve containers + this.logger = container.resolve("WinstonLogger"); + const customItem = container.resolve("CustomItemService"); + const hashUtil = container.resolve("HashUtil"); + this.dbServer = container.resolve("DatabaseServer"); + this.db = this.dbServer.getTables(); + this.fluentTraderAssortHelper = new fluentTraderAssortCreator_1.FluentAssortConstructor(hashUtil, this.logger); + this.jsonUtil = container.resolve("JsonUtil"); + // Get fileSystem to read in configs + const fileSystem = container.resolve("FileSystemSync"); + const itemsJsonPath = node_path_1.default.resolve(__dirname, "../database/templates/items.jsonc"); + // Read the items.json file with type ItemsJson + this.itemsJson = this.jsonUtil.deserializeJsonC(fileSystem.read(itemsJsonPath)); + // Set trader id we want to add assort items to + this.traderID = "5a7c2eca46aef81a7ca2145d"; + // Load hideoutrecipes and our custom recipes + const hideoutRecipes = this.db.hideout.production.recipes; + const customRecipes = this.jsonUtil.deserializeJsonC(fileSystem.read(node_path_1.default.resolve(__dirname, "../database/templates/craftingItem.jsonc"))); + setupItems(this.itemsJson, customItem); + handleAssorts(this.db, this.fluentTraderAssortHelper, this.traderID, this.itemsJson); + modifyAmmoPropForWeapons(this.db, this.itemsJson); + this.logger.info("DoorBreacher: Finished Modifying Ammo Properties for Weapons"); + this.logger.info(`Before ${hideoutRecipes.length}`); + // Adds custom recipe(s) to the workbench. Currently only the C4 + customRecipes.forEach(customRecipe => { + hideoutRecipes.push(customRecipe); + }); + this.logger.info(`After ${hideoutRecipes.length}`); + this.logger.info(`Is recipe loaded? ${hideoutRecipes.filter(r => r._id === "665d4ce7e381d16c8676292b").length >= 1}`); + this.logger.info("Added custom recipes to hideout recipes"); + } +} +function setupItems(itemsjson, customItem) { + //Make locale for DoorBreacher + const doorBreacherLocale = { + en: { + name: "12/70 Door-Breaching Round", + shortName: "Breach", + description: "The door-breaching round is designed to destroy deadbolts, locks, and hinges without risking lives by ricocheting or penetrating through doors. These frangible rounds are made of a dense sintered material which can destroy a lock or hinge and then immediately disperse." + } + }; + // Add new custom item + const doorBreacher = { + newItem: itemsjson.doorbreacher, + fleaPriceRoubles: 8000, + handbookPriceRoubles: 10000, + handbookParentId: "5b47574386f77428ca22b33b", + locales: doorBreacherLocale + }; + // Make locale for DoorBreacherBox + const doorBreacherBoxLocale = { + en: { + name: "12/70 Door-Breaching 5-Round Box", + shortName: "Breach", + description: "A 5-round box of 12ga door breaching shells. The door-breaching round is designed to destroy deadbolts, locks, and hinges without risking lives by ricocheting or penetrating through doors. These frangible rounds are made of a dense sintered material which can destroy a lock or hinge and then immediately disperse." + } + }; + // Add new custom item + const doorBreacherBox = { + newItem: itemsjson.doorbreacherbox, + fleaPriceRoubles: 40000, + handbookPriceRoubles: 50000, + handbookParentId: "5b47574386f77428ca22b33c", + locales: doorBreacherBoxLocale + }; + // Make locale for DoorBreacher + const c4ExplosiveLocale = { + en: { + name: "C4 Explosive", + shortName: "C4", + description: "This C4 Explosive is used for breaching reinforced doors. It is a powerful explosive that is used in the military and law enforcement. It is a plastic explosive that is stable and safe to handle and triggered after a set timer." + } + }; + // Add new custom item + const c4Explosive = { + newItem: itemsjson.C4Explosive, + fleaPriceRoubles: 45000, + handbookPriceRoubles: 40000, + handbookParentId: "5b47574386f77428ca22b2f2", + locales: c4ExplosiveLocale + }; + // Create the items + customItem.createItem(doorBreacher); + customItem.createItem(doorBreacherBox); + customItem.createItem(c4Explosive); +} +function modifyAmmoPropForWeapons(db, itemsJson) { + const weaponProperties = [ + { name: "Chambers", index: 0 }, + { name: "Cartridges", index: 1 }, + { name: "camora_000", index: 2 }, + { name: "camora_001", index: 3 }, + { name: "camora_002", index: 4 }, + { name: "camora_003", index: 5 }, + { name: "camora_004", index: 6 } + ]; + const is12GaugeAmmo = (filters) => { + return filters ? filters.some(filter => filter.Filter?.includes("560d5e524bdc2d25448b4571")) : false; + }; + const addDoorBreacher = (item, filters, weaponPropName) => { + console.info(`DoorBreacher added to: ${item._name} in weaponPropName: ${weaponPropName}`); + filters[0].Filter.push(itemsJson.doorbreacher._id.toString()); + }; + const processWeaponProperty = (item, weaponPropName) => { + const property = item._props[weaponPropName]; + if (!property) { + return; + } + if (Array.isArray(property)) { + // For properties like "Chambers" + for (const subProperty of property) { + if (subProperty._props.filters && is12GaugeAmmo(subProperty._props.filters)) { + addDoorBreacher(item, subProperty._props.filters, weaponPropName); + } + } + } + else { + // For properties directly under _props like "Cartridges" + if (property.filters && is12GaugeAmmo(property.filters)) { + addDoorBreacher(item, property.filters, weaponPropName); + } + } + }; + const processSlots = (slots) => { + if (!slots || slots.length === 0) { + return; + } + for (const slot of slots) { + if (slot._props.filters && is12GaugeAmmo(slot._props.filters)) { + addDoorBreacher(slot, slot._props.filters, slot._name); + } + } + }; + // Iterate over all items + for (const item of Object.values(db.templates.items)) { + for (const prop of weaponProperties) { + if (item._props[prop.name]) { + processWeaponProperty(item, prop.name); + } + } + // Process slots for "camora" + if (item._props.Slots) { + processSlots(item._props.Slots); + } + } +} +function handleAssorts(db, assortHelper, traderID, itemsjson) { + const targetTrader = db.traders[traderID]; + // Create assort for doorbreacher. No money, add barter only later + assortHelper + .createSingleAssortItem(itemsjson.doorbreacher._id) + .addStackCount(100) + .addUnlimitedStackCount() + .addLoyaltyLevel(1) + .addMoneyCost(Money_1.Money.ROUBLES, 10000) + .export(targetTrader); + // Create assort for doorbreacherbox - no assort since no other trader sells a packl + // assortHelper + // .createSingleAssortItem(itemsjson.doorbreacherbox._id) + // .addStackCount(100) + // .addUnlimitedStackCount() + // .addLoyaltyLevel(1) + // .addMoneyCost(Money.ROUBLES, 50000) + // .export(targetTrader); + // Create barter item for doorbreacher + const electricWire = "5c06779c86f77426e00dd782"; + assortHelper + .createSingleAssortItem(itemsjson.doorbreacher._id) + .addStackCount(100) + .addUnlimitedStackCount() + .addBarterCost(electricWire, 1) + .addLoyaltyLevel(1) + .export(targetTrader); +} +module.exports = { mod: new Mod() }; +//# sourceMappingURL=mod.js.map \ No newline at end of file diff --git a/server files/user/mods/DoorBreacher/src/mod.js.map b/server files/user/mods/DoorBreacher/src/mod.js.map new file mode 100644 index 0000000..dba7202 --- /dev/null +++ b/server files/user/mods/DoorBreacher/src/mod.js.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "file": "mod.js", + "sourceRoot": "", + "sources": [ + "mod.ts" + ], + "names": [], + "mappings": "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" +} \ No newline at end of file diff --git a/server files/user/mods/DoorBreacher/src/mod.ts b/server files/user/mods/DoorBreacher/src/mod.ts new file mode 100644 index 0000000..dded29c --- /dev/null +++ b/server files/user/mods/DoorBreacher/src/mod.ts @@ -0,0 +1,252 @@ +import { DependencyContainer } from "tsyringe"; +import { IPostDBLoadMod } from "@spt/models/external/IPostDBLoadMod"; +import { CustomItemService } from "@spt/services/mod/CustomItemService"; +import { LocaleDetails, NewItemDetails } from "@spt/models/spt/mod/NewItemDetails"; +import { ILogger } from "@spt/models/spt/utils/ILogger"; +import { DatabaseServer } from "@spt/servers/DatabaseServer"; +import { HashUtil } from "@spt/utils/HashUtil"; +import { Money } from "@spt/models/enums/Money"; +import { FluentAssortConstructor } from "./fluentTraderAssortCreator"; +import { ItemsJson } from "./items.type"; +import { FileSystemSync } from "@spt/utils/FileSystemSync"; +import path from "node:path"; +import { JsonUtil } from "@spt/utils/JsonUtil"; +import { IHideoutProduction } from "@spt/models/eft/hideout/IHideoutProduction"; +import { IDatabaseTables } from "@spt/models/spt/server/IDatabaseTables"; + + +class Mod implements IPostDBLoadMod { + // Declare private variable db of DatabaseServer type + private dbServer: DatabaseServer; + private db: IDatabaseTables; + private fluentTraderAssortHelper: FluentAssortConstructor; + private traderID: string; + private itemsJson: ItemsJson; + private logger: ILogger; + private jsonUtil: JsonUtil; + + + public postDBLoad(container: DependencyContainer): void { + // Resolve containers + this.logger = container.resolve("WinstonLogger"); + const customItem = container.resolve("CustomItemService"); + const hashUtil: HashUtil = container.resolve("HashUtil"); + + this.dbServer = container.resolve("DatabaseServer"); + this.db = this.dbServer.getTables(); + this.fluentTraderAssortHelper = new FluentAssortConstructor( + hashUtil, + this.logger + ); + this.jsonUtil = container.resolve("JsonUtil"); + + // Get fileSystem to read in configs + const fileSystem = container.resolve("FileSystemSync"); + const itemsJsonPath = path.resolve(__dirname, "../database/templates/items.jsonc"); + + // Read the items.json file with type ItemsJson + this.itemsJson = this.jsonUtil.deserializeJsonC(fileSystem.read(itemsJsonPath)); + + // Set trader id we want to add assort items to + this.traderID = "5a7c2eca46aef81a7ca2145d"; + + // Load hideoutrecipes and our custom recipes + const hideoutRecipes: IHideoutProduction[] = this.db.hideout.production.recipes; + const customRecipes = this.jsonUtil.deserializeJsonC(fileSystem.read(path.resolve(__dirname, "../database/templates/craftingItem.jsonc"))); + + + setupItems(this.itemsJson, customItem); + handleAssorts( + this.db, + this.fluentTraderAssortHelper, + this.traderID, + this.itemsJson + ); + + modifyAmmoPropForWeapons(this.db, this.itemsJson); + this.logger.info("DoorBreacher: Finished Modifying Ammo Properties for Weapons"); + + this.logger.info(`Before ${hideoutRecipes.length}`); + // Adds custom recipe(s) to the workbench. Currently only the C4 + customRecipes.forEach(customRecipe => { + hideoutRecipes.push(customRecipe); + }); + this.logger.info(`After ${hideoutRecipes.length}`); + this.logger.info(`Is recipe loaded? ${hideoutRecipes.filter(r => r._id === "665d4ce7e381d16c8676292b").length >= 1}`); + this.logger.info("Added custom recipes to hideout recipes"); + } +} + +function setupItems(itemsjson: ItemsJson, customItem: CustomItemService) { + //Make locale for DoorBreacher + const doorBreacherLocale: Record = { + en: { + name: "12/70 Door-Breaching Round", + shortName: "Breach", + description: + "The door-breaching round is designed to destroy deadbolts, locks, and hinges without risking lives by ricocheting or penetrating through doors. These frangible rounds are made of a dense sintered material which can destroy a lock or hinge and then immediately disperse." + } + }; + + // Add new custom item + const doorBreacher: NewItemDetails = { + newItem: itemsjson.doorbreacher, + fleaPriceRoubles: 8000, + handbookPriceRoubles: 10000, + handbookParentId: "5b47574386f77428ca22b33b", + locales: doorBreacherLocale + }; + + // Make locale for DoorBreacherBox + const doorBreacherBoxLocale: Record = { + en: { + name: "12/70 Door-Breaching 5-Round Box", + shortName: "Breach", + description: + "A 5-round box of 12ga door breaching shells. The door-breaching round is designed to destroy deadbolts, locks, and hinges without risking lives by ricocheting or penetrating through doors. These frangible rounds are made of a dense sintered material which can destroy a lock or hinge and then immediately disperse." + } + }; + + // Add new custom item + const doorBreacherBox: NewItemDetails = { + newItem: itemsjson.doorbreacherbox, + fleaPriceRoubles: 40000, + handbookPriceRoubles: 50000, + handbookParentId: "5b47574386f77428ca22b33c", + locales: doorBreacherBoxLocale + }; + + // Make locale for DoorBreacher + const c4ExplosiveLocale: Record = { + en: { + name: "C4 Explosive", + shortName: "C4", + description: + "This C4 Explosive is used for breaching reinforced doors. It is a powerful explosive that is used in the military and law enforcement. It is a plastic explosive that is stable and safe to handle and triggered after a set timer." + } + }; + + // Add new custom item + const c4Explosive: NewItemDetails = { + newItem: itemsjson.C4Explosive, + fleaPriceRoubles: 45000, + handbookPriceRoubles: 40000, + handbookParentId: "5b47574386f77428ca22b2f2", + locales: c4ExplosiveLocale + }; + + + // Create the items + customItem.createItem(doorBreacher); + customItem.createItem(doorBreacherBox); + customItem.createItem(c4Explosive); +} + +function modifyAmmoPropForWeapons(db: IDatabaseTables, itemsJson: ItemsJson) { + const weaponProperties = [ + { name: "Chambers", index: 0 }, + { name: "Cartridges", index: 1 }, + { name: "camora_000", index: 2 }, + { name: "camora_001", index: 3 }, + { name: "camora_002", index: 4 }, + { name: "camora_003", index: 5 }, + { name: "camora_004", index: 6 } + ]; + + const is12GaugeAmmo = (filters) => { + return filters ? filters.some(filter => filter.Filter?.includes("560d5e524bdc2d25448b4571")) : false; + }; + + const addDoorBreacher = (item, filters, weaponPropName) => { + console.info(`DoorBreacher added to: ${item._name} in weaponPropName: ${weaponPropName}`); + filters[0].Filter.push(itemsJson.doorbreacher._id.toString()); + }; + + const processWeaponProperty = (item, weaponPropName) => { + const property = item._props[weaponPropName]; + if (!property) { + return; + } + + if (Array.isArray(property)) { + // For properties like "Chambers" + for (const subProperty of property) { + if (subProperty._props.filters && is12GaugeAmmo(subProperty._props.filters)) { + addDoorBreacher(item, subProperty._props.filters, weaponPropName); + } + } + } else { + // For properties directly under _props like "Cartridges" + if (property.filters && is12GaugeAmmo(property.filters)) { + addDoorBreacher(item, property.filters, weaponPropName); + } + } + }; + + const processSlots = (slots) => { + if (!slots || slots.length === 0) { + return; + } + + for (const slot of slots) { + if (slot._props.filters && is12GaugeAmmo(slot._props.filters)) { + addDoorBreacher(slot, slot._props.filters, slot._name); + } + } + }; + + // Iterate over all items + for (const item of Object.values(db.templates.items)) { + for (const prop of weaponProperties) { + if (item._props[prop.name]) { + processWeaponProperty(item, prop.name); + } + } + + // Process slots for "camora" + if (item._props.Slots) { + processSlots(item._props.Slots); + } + } +} + +function handleAssorts( + db: IDatabaseTables, + assortHelper: FluentAssortConstructor, + traderID: string, + itemsjson: ItemsJson +) { + const targetTrader = db.traders[traderID]; + + // Create assort for doorbreacher. No money, add barter only later + assortHelper + .createSingleAssortItem(itemsjson.doorbreacher._id) + .addStackCount(100) + .addUnlimitedStackCount() + .addLoyaltyLevel(1) + .addMoneyCost(Money.ROUBLES, 10000) + .export(targetTrader); + + // Create assort for doorbreacherbox - no assort since no other trader sells a packl + // assortHelper + // .createSingleAssortItem(itemsjson.doorbreacherbox._id) + // .addStackCount(100) + // .addUnlimitedStackCount() + // .addLoyaltyLevel(1) + // .addMoneyCost(Money.ROUBLES, 50000) + // .export(targetTrader); + + // Create barter item for doorbreacher + const electricWire = "5c06779c86f77426e00dd782"; + assortHelper + .createSingleAssortItem(itemsjson.doorbreacher._id) + .addStackCount(100) + .addUnlimitedStackCount() + .addBarterCost(electricWire, 1) + .addLoyaltyLevel(1) + .export(targetTrader); + +} + + +module.exports = { mod: new Mod() }; diff --git a/server files/user/mods/DoorBreacher/src/traderHelpers.js b/server files/user/mods/DoorBreacher/src/traderHelpers.js new file mode 100644 index 0000000..8e4f9ac --- /dev/null +++ b/server files/user/mods/DoorBreacher/src/traderHelpers.js @@ -0,0 +1,160 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TraderHelper = void 0; +class TraderHelper { + /** + * Add profile picture to our trader + * @param baseJson json file for trader (db/base.json) + * @param preAkiModLoader mod loader class - used to get the mods file path + * @param imageRouter image router class - used to register the trader image path so we see their image on trader page + * @param traderImageName Filename of the trader icon to use + */ + //biome-ignore lint/suspicious/noExplicitAny: baseJson comes from base.json, so no type + registerProfileImage(baseJson, modName, preAkiModLoader, imageRouter, traderImageName) { + // Reference the mod "res" folder + const imageFilepath = `./${preAkiModLoader.getModPath(modName)}res`; + // Register a route to point to the profile picture - remember to remove the .jpg from it + imageRouter.addRoute(baseJson.avatar.replace(".jpg", ""), `${imageFilepath}/${traderImageName}`); + } + /** + * Add record to trader config to set the refresh time of trader in seconds (default is 60 minutes) + * @param traderConfig trader config to add our trader to + * @param baseJson json file for trader (db/base.json) + * @param refreshTimeSecondsMin How many seconds between trader stock refresh min time + * @param refreshTimeSecondsMax How many seconds between trader stock refresh max time + */ + //biome-ignore lint/suspicious/noExplicitAny: baseJson comes from base.json, so no type + setTraderUpdateTime(traderConfig, baseJson, refreshTimeSecondsMin, refreshTimeSecondsMax) { + // Add refresh time in seconds to config + const traderRefreshRecord = { + traderId: baseJson._id, + seconds: { + min: refreshTimeSecondsMin, + max: refreshTimeSecondsMax + } + }; + traderConfig.updateTime.push(traderRefreshRecord); + } + /** + * Add our new trader to the database + * @param traderDetailsToAdd trader details + * @param tables database + * @param jsonUtil json utility class + */ + //biome-ignore lint/suspicious/noExplicitAny: traderDetailsToAdd comes from base.json, so no type + addTraderToDb(traderDetailsToAdd, tables, jsonUtil) { + // Add trader to trader table, key is the traders id + tables.traders[traderDetailsToAdd._id] = { + assort: this.createAssortTable(), // assorts are the 'offers' trader sells, can be a single item (e.g. carton of milk) or multiple items as a collection (e.g. a gun) + base: jsonUtil.deserialize(jsonUtil.serialize(traderDetailsToAdd)), // Deserialise/serialise creates a copy of the json and allows us to cast it as an ITraderBase + questassort: { + started: {}, + success: {}, + fail: {} + } // questassort is empty as trader has no assorts unlocked by quests + }; + } + /** + * Create basic data for trader + add empty assorts table for trader + * @param tables SPT db + * @param jsonUtil SPT JSON utility class + * @returns ITraderAssort + */ + createAssortTable() { + // Create a blank assort object, ready to have items added + const assortTable = { + nextResupply: 0, + items: [], + barter_scheme: {}, + loyal_level_items: {} + }; + return assortTable; + } + /** + * Create a weapon from scratch, ready to be added to trader + * @returns Item[] + */ + createGlock() { + // Create an array ready to hold weapon + all mods + const glock = []; + // Add the base first + glock.push({ + _id: "glockBase", // Ids dont matter, as long as they are unique (can use hashUtil.generate() if you dont want to type every id by hand) + _tpl: "5a7ae0c351dfba0017554310" // This is the weapons tpl, found on: https://db.sp-tarkov.com/search + }); + // Add barrel + glock.push({ + _id: "glockbarrel", + _tpl: "5a6b60158dc32e000a31138b", + parentId: "glockBase", // This is a sub item, you need to define its parent its attached to / inserted into + slotId: "mod_barrel" // Required for mods, you need to define what 'role' they have + }); + // Add reciever + glock.push({ + _id: "glockReciever", + _tpl: "5a9685b1a2750c0032157104", + parentId: "glockBase", + slotId: "mod_reciever" + }); + // Add compensator + glock.push({ + _id: "glockCompensator", + _tpl: "5a7b32a2e899ef00135e345a", + parentId: "glockReciever", // The parent of this mod is the reciever NOT weapon, be careful to get the correct parent + slotId: "mod_muzzle" + }); + // Add Pistol grip + glock.push({ + _id: "glockPistolGrip", + _tpl: "5a7b4960e899ef197b331a2d", + parentId: "glockBase", + slotId: "mod_pistol_grip" + }); + // Add front sight + glock.push({ + _id: "glockRearSight", + _tpl: "5a6f5d528dc32e00094b97d9", + parentId: "glockReciever", + slotId: "mod_sight_rear" + }); + // Add rear sight + glock.push({ + _id: "glockFrontSight", + _tpl: "5a6f58f68dc32e000a311390", + parentId: "glockReciever", + slotId: "mod_sight_front" + }); + // Add magazine + glock.push({ + _id: "glockMagazine", + _tpl: "630769c4962d0247b029dc60", + parentId: "glockBase", + slotId: "mod_magazine" + }); + return glock; + } + /** + * Add traders name/location/description to the locale table + * @param baseJson json file for trader (db/base.json) + * @param tables database tables + * @param fullName Complete name of trader + * @param firstName First name of trader + * @param nickName Nickname of trader + * @param location Location of trader (e.g. "Here in the cat shop") + * @param description Description of trader + */ + //biome-ignore lint/suspicious/noExplicitAny: baseJson comes from base.json, so no type + addTraderToLocales(baseJson, tables, fullName, firstName, nickName, location, description) { + // For each language, add locale for the new trader + const locales = Object.values(tables.locales.global); + for (const locale of locales) { + locale[`${baseJson._id} FullName`] = fullName; + locale[`${baseJson._id} FirstName`] = firstName; + locale[`${baseJson._id} Nickname`] = nickName; + locale[`${baseJson._id} Location`] = location; + locale[`${baseJson._id} Description`] = description; + } + } +} +exports.TraderHelper = TraderHelper; +//# sourceMappingURL=traderHelpers.js.map \ No newline at end of file diff --git a/server files/user/mods/DoorBreacher/src/traderHelpers.js.map b/server files/user/mods/DoorBreacher/src/traderHelpers.js.map new file mode 100644 index 0000000..0ee089c --- /dev/null +++ b/server files/user/mods/DoorBreacher/src/traderHelpers.js.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "file": "traderHelpers.js", + "sourceRoot": "", + "sources": [ + "traderHelpers.ts" + ], + "names": [], + "mappings": "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" +} \ No newline at end of file diff --git a/server files/user/mods/DoorBreacher/src/traderHelpers.ts b/server files/user/mods/DoorBreacher/src/traderHelpers.ts new file mode 100644 index 0000000..e8d0602 --- /dev/null +++ b/server files/user/mods/DoorBreacher/src/traderHelpers.ts @@ -0,0 +1,190 @@ +import type { PreAkiModLoader } from "@spt/loaders/PreAkiModLoader"; +import type { Item } from "@spt/models/eft/common/tables/IItem"; +import type { ITraderBase, ITraderAssort } from "@spt/models/eft/common/tables/ITrader"; +import type { ITraderConfig, UpdateTime } from "@spt/models/spt/config/ITraderConfig"; +import type { IDatabaseTables } from "@spt/models/spt/server/IDatabaseTables"; +import type { ImageRouter } from "@spt/routers/ImageRouter"; +import type { JsonUtil } from "@spt/utils/JsonUtil"; + +export class TraderHelper +{ + /** + * Add profile picture to our trader + * @param baseJson json file for trader (db/base.json) + * @param preAkiModLoader mod loader class - used to get the mods file path + * @param imageRouter image router class - used to register the trader image path so we see their image on trader page + * @param traderImageName Filename of the trader icon to use + */ + + //biome-ignore lint/suspicious/noExplicitAny: baseJson comes from base.json, so no type + public registerProfileImage(baseJson: any, modName: string, preAkiModLoader: PreAkiModLoader, imageRouter: ImageRouter, traderImageName: string): void + { + // Reference the mod "res" folder + const imageFilepath = `./${preAkiModLoader.getModPath(modName)}res`; + + // Register a route to point to the profile picture - remember to remove the .jpg from it + imageRouter.addRoute(baseJson.avatar.replace(".jpg", ""), `${imageFilepath}/${traderImageName}`); + } + + /** + * Add record to trader config to set the refresh time of trader in seconds (default is 60 minutes) + * @param traderConfig trader config to add our trader to + * @param baseJson json file for trader (db/base.json) + * @param refreshTimeSecondsMin How many seconds between trader stock refresh min time + * @param refreshTimeSecondsMax How many seconds between trader stock refresh max time + */ + + //biome-ignore lint/suspicious/noExplicitAny: baseJson comes from base.json, so no type + public setTraderUpdateTime(traderConfig: ITraderConfig, baseJson: any, refreshTimeSecondsMin: number, refreshTimeSecondsMax: number): void + { + // Add refresh time in seconds to config + const traderRefreshRecord: UpdateTime = { + traderId: baseJson._id, + seconds: { + min: refreshTimeSecondsMin, + max: refreshTimeSecondsMax + } }; + + traderConfig.updateTime.push(traderRefreshRecord); + } + + /** + * Add our new trader to the database + * @param traderDetailsToAdd trader details + * @param tables database + * @param jsonUtil json utility class + */ + + //biome-ignore lint/suspicious/noExplicitAny: traderDetailsToAdd comes from base.json, so no type + public addTraderToDb(traderDetailsToAdd: any, tables: IDatabaseTables, jsonUtil: JsonUtil): void + { + // Add trader to trader table, key is the traders id + tables.traders[traderDetailsToAdd._id] = { + assort: this.createAssortTable(), // assorts are the 'offers' trader sells, can be a single item (e.g. carton of milk) or multiple items as a collection (e.g. a gun) + base: jsonUtil.deserialize(jsonUtil.serialize(traderDetailsToAdd)) as ITraderBase, // Deserialise/serialise creates a copy of the json and allows us to cast it as an ITraderBase + questassort: { + started: {}, + success: {}, + fail: {} + } // questassort is empty as trader has no assorts unlocked by quests + }; + } + + /** + * Create basic data for trader + add empty assorts table for trader + * @param tables SPT db + * @param jsonUtil SPT JSON utility class + * @returns ITraderAssort + */ + private createAssortTable(): ITraderAssort + { + // Create a blank assort object, ready to have items added + const assortTable: ITraderAssort = { + nextResupply: 0, + items: [], + barter_scheme: {}, + loyal_level_items: {} + } + + return assortTable; + } + + /** + * Create a weapon from scratch, ready to be added to trader + * @returns Item[] + */ + public createGlock(): Item[] + { + // Create an array ready to hold weapon + all mods + const glock: Item[] = []; + + // Add the base first + glock.push({ // Add the base weapon first + _id: "glockBase", // Ids dont matter, as long as they are unique (can use hashUtil.generate() if you dont want to type every id by hand) + _tpl: "5a7ae0c351dfba0017554310" // This is the weapons tpl, found on: https://db.sp-tarkov.com/search + }); + + // Add barrel + glock.push({ + _id: "glockbarrel", + _tpl: "5a6b60158dc32e000a31138b", + parentId: "glockBase", // This is a sub item, you need to define its parent its attached to / inserted into + slotId: "mod_barrel" // Required for mods, you need to define what 'role' they have + }); + + // Add reciever + glock.push({ + _id: "glockReciever", + _tpl:"5a9685b1a2750c0032157104", + parentId: "glockBase", + slotId: "mod_reciever" + }); + + // Add compensator + glock.push({ + _id: "glockCompensator", + _tpl:"5a7b32a2e899ef00135e345a", + parentId: "glockReciever", // The parent of this mod is the reciever NOT weapon, be careful to get the correct parent + slotId: "mod_muzzle" + }); + + // Add Pistol grip + glock.push({ + _id: "glockPistolGrip", + _tpl:"5a7b4960e899ef197b331a2d", + parentId: "glockBase", + slotId: "mod_pistol_grip" + }); + + // Add front sight + glock.push({ + _id: "glockRearSight", + _tpl: "5a6f5d528dc32e00094b97d9", + parentId: "glockReciever", + slotId: "mod_sight_rear" + }); + + // Add rear sight + glock.push({ + _id: "glockFrontSight", + _tpl: "5a6f58f68dc32e000a311390", + parentId: "glockReciever", + slotId: "mod_sight_front" + }); + + // Add magazine + glock.push({ + _id: "glockMagazine", + _tpl: "630769c4962d0247b029dc60", + parentId: "glockBase", + slotId: "mod_magazine" + }); + + return glock; + } + + /** + * Add traders name/location/description to the locale table + * @param baseJson json file for trader (db/base.json) + * @param tables database tables + * @param fullName Complete name of trader + * @param firstName First name of trader + * @param nickName Nickname of trader + * @param location Location of trader (e.g. "Here in the cat shop") + * @param description Description of trader + */ + + //biome-ignore lint/suspicious/noExplicitAny: baseJson comes from base.json, so no type + public addTraderToLocales(baseJson: any, tables: IDatabaseTables, fullName: string, firstName: string, nickName: string, location: string, description: string) + { + // For each language, add locale for the new trader + const locales = Object.values(tables.locales.global) as Record[]; + for (const locale of locales) { + locale[`${baseJson._id} FullName`] = fullName; + locale[`${baseJson._id} FirstName`] = firstName; + locale[`${baseJson._id} Nickname`] = nickName; + locale[`${baseJson._id} Location`] = location; + locale[`${baseJson._id} Description`] = description; + } + } +} \ No newline at end of file