diff --git a/mods/Weapon Customizer/BepInEx/plugins/Tyfon.WeaponCustomizer.dll b/mods/Weapon Customizer/BepInEx/plugins/Tyfon.WeaponCustomizer.dll index 53b0bb2..a68b30f 100644 Binary files a/mods/Weapon Customizer/BepInEx/plugins/Tyfon.WeaponCustomizer.dll and b/mods/Weapon Customizer/BepInEx/plugins/Tyfon.WeaponCustomizer.dll differ diff --git a/mods/Weapon Customizer/meta.ini b/mods/Weapon Customizer/meta.ini index 0ca81db..cb8a874 100644 --- a/mods/Weapon Customizer/meta.ini +++ b/mods/Weapon Customizer/meta.ini @@ -1,11 +1,11 @@ [General] gameName=spt modid=0 -version=d2025.1.6.0 +version=d2025.1.7.0 newestVersion= category="2," nexusFileStatus=1 -installationFile=Tyfon-WeaponCustomizer-1.0.0.zip +installationFile=Tyfon-WeaponCustomizer-1.0.1.zip repository=Nexus ignoredVersion= comments= diff --git a/mods/Weapon Customizer/user/mods/tyfon-weaponcustomizer/package.json b/mods/Weapon Customizer/user/mods/tyfon-weaponcustomizer/package.json index ea0ce22..29ddf6d 100644 --- a/mods/Weapon Customizer/user/mods/tyfon-weaponcustomizer/package.json +++ b/mods/Weapon Customizer/user/mods/tyfon-weaponcustomizer/package.json @@ -1,6 +1,6 @@ { "name": "weapon-customizer", - "version": "1.0.0", + "version": "1.0.1", "main": "src/mod.js", "license": "MIT", "author": "Tyfon", diff --git a/mods/Weapon Customizer/user/mods/tyfon-weaponcustomizer/src/mod.ts b/mods/Weapon Customizer/user/mods/tyfon-weaponcustomizer/src/mod.ts index 4c996e0..f6269cf 100644 --- a/mods/Weapon Customizer/user/mods/tyfon-weaponcustomizer/src/mod.ts +++ b/mods/Weapon Customizer/user/mods/tyfon-weaponcustomizer/src/mod.ts @@ -1,5 +1,7 @@ import type { DependencyContainer } from "tsyringe"; +import type { ProfileHelper } from "@spt/helpers/ProfileHelper"; +import type { IPostSptLoadMod } from "@spt/models/external/IPostSptLoadMod"; import type { IPreSptLoadMod } from "@spt/models/external/IPreSptLoadMod"; import { LogTextColor } from "@spt/models/spt/logging/LogTextColor"; import type { ILogger } from "@spt/models/spt/utils/ILogger"; @@ -26,15 +28,17 @@ type CustomizePayload = { type Customizations = Record>; -class WeaponCustomizer implements IPreSptLoadMod { +class WeaponCustomizer implements IPreSptLoadMod, IPostSptLoadMod { private logger: ILogger; private vfs: VFS; + private profileHelper: ProfileHelper; private customizations: Customizations = null; private filepath: string; public preSptLoad(container: DependencyContainer): void { this.logger = container.resolve("PrimaryLogger"); this.vfs = container.resolve("VFS"); + const staticRouterModService = container.resolve("StaticRouterModService"); this.filepath = path.resolve(__dirname, "../customizations.json"); @@ -56,6 +60,11 @@ class WeaponCustomizer implements IPreSptLoadMod { ); } + public postSptLoad(container: DependencyContainer): void { + this.profileHelper = container.resolve("ProfileHelper"); + this.clean(); + } + private async saveCustomization(payload: CustomizePayload): Promise { //this.logger.info(`WeaponCustomizer: Saving customization for weapon ${payload.weaponId}`); if (Object.keys(payload.slots).length === 0) { @@ -64,6 +73,7 @@ class WeaponCustomizer implements IPreSptLoadMod { this.customizations[payload.weaponId] = payload.slots; } await this.save(); + return JSON.stringify({ success: true }); } @@ -88,6 +98,39 @@ class WeaponCustomizer implements IPreSptLoadMod { } } + // Remove any customizations for items that no longer exist + private async clean() { + const map = new Map(); + for (const weaponId of Object.keys(this.customizations)) { + map.set(weaponId, false); + } + + for (const profile of Object.values(this.profileHelper.getProfiles())) { + for (const item of profile.characters.pmc.Inventory.items) { + if (map.has(item._id)) { + map.set(item._id, true); + } + } + } + + let dirtyCount = 0; + for (const [weaponId, found] of Object.entries(map)) { + if (!found) { + delete this.customizations[weaponId]; + dirtyCount++; + } + } + + if (dirtyCount > 0) { + this.logger.logWithColor( + `WeaponCustomizer: Cleaned up ${dirtyCount} customizations for weapons that no longer exist`, + LogTextColor.CYAN + ); + + await this.save(); + } + } + private async save() { try { await this.vfs.writeFileAsync(this.filepath, JSON.stringify(this.customizations, null, 2)); diff --git a/mods/Weapon Customizer_backup/BepInEx/plugins/Tyfon.WeaponCustomizer.dll b/mods/Weapon Customizer_backup/BepInEx/plugins/Tyfon.WeaponCustomizer.dll new file mode 100644 index 0000000..53b0bb2 Binary files /dev/null and b/mods/Weapon Customizer_backup/BepInEx/plugins/Tyfon.WeaponCustomizer.dll differ diff --git a/mods/Weapon Customizer_backup/meta.ini b/mods/Weapon Customizer_backup/meta.ini new file mode 100644 index 0000000..0ca81db --- /dev/null +++ b/mods/Weapon Customizer_backup/meta.ini @@ -0,0 +1,28 @@ +[General] +gameName=spt +modid=0 +version=d2025.1.6.0 +newestVersion= +category="2," +nexusFileStatus=1 +installationFile=Tyfon-WeaponCustomizer-1.0.0.zip +repository=Nexus +ignoredVersion= +comments= +notes= +nexusDescription= +url= +hasCustomURL=false +lastNexusQuery= +lastNexusUpdate= +nexusLastModified=2025-01-07T00:12:11Z +nexusCategory=0 +converted=false +validated=false +color=@Variant(\0\0\0\x43\0\xff\xff\0\0\0\0\0\0\0\0) +tracked=0 + +[installedFiles] +1\modid=0 +1\fileid=0 +size=1 diff --git a/mods/Weapon Customizer_backup/user/mods/tyfon-weaponcustomizer/package.json b/mods/Weapon Customizer_backup/user/mods/tyfon-weaponcustomizer/package.json new file mode 100644 index 0000000..ea0ce22 --- /dev/null +++ b/mods/Weapon Customizer_backup/user/mods/tyfon-weaponcustomizer/package.json @@ -0,0 +1,33 @@ +{ + "name": "weapon-customizer", + "version": "1.0.0", + "main": "src/mod.js", + "license": "MIT", + "author": "Tyfon", + "sptVersion": "~3.10", + "loadBefore": [], + "loadAfter": [], + "incompatibilities": [], + "contributors": [], + "isBundleMod": false, + "scripts": { + "setup": "npm i", + "build": "node ./build.mjs", + "buildinfo": "node ./build.mjs --verbose" + }, + "devDependencies": { + "@types/node": "20.11", + "@typescript-eslint/eslint-plugin": "7.2", + "@typescript-eslint/parser": "7.2", + "archiver": "^6.0", + "eslint": "8.57", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", + "fs-extra": "11.2", + "ignore": "^5.2", + "os": "^0.1", + "tsyringe": "4.8.0", + "typescript": "5.4", + "winston": "3.12" + } +} diff --git a/mods/Weapon Customizer_backup/user/mods/tyfon-weaponcustomizer/src/mod.ts b/mods/Weapon Customizer_backup/user/mods/tyfon-weaponcustomizer/src/mod.ts new file mode 100644 index 0000000..4c996e0 --- /dev/null +++ b/mods/Weapon Customizer_backup/user/mods/tyfon-weaponcustomizer/src/mod.ts @@ -0,0 +1,100 @@ +import type { DependencyContainer } from "tsyringe"; + +import type { IPreSptLoadMod } from "@spt/models/external/IPreSptLoadMod"; +import { LogTextColor } from "@spt/models/spt/logging/LogTextColor"; +import type { ILogger } from "@spt/models/spt/utils/ILogger"; +import type { StaticRouterModService } from "@spt/services/mod/staticRouter/StaticRouterModService"; +import { VFS } from "@spt/utils/VFS"; +import fs from "node:fs"; +import path from "node:path"; + +type Vector3 = { + x: number; + y: number; + z: number; +}; + +type CustomPosition = { + original: Vector3; + modified: Vector3; +}; + +type CustomizePayload = { + weaponId: string; + slots: Record; +}; + +type Customizations = Record>; + +class WeaponCustomizer implements IPreSptLoadMod { + private logger: ILogger; + private vfs: VFS; + private customizations: Customizations = null; + private filepath: string; + + public preSptLoad(container: DependencyContainer): void { + this.logger = container.resolve("PrimaryLogger"); + this.vfs = container.resolve("VFS"); + const staticRouterModService = container.resolve("StaticRouterModService"); + + this.filepath = path.resolve(__dirname, "../customizations.json"); + this.load(); + + staticRouterModService.registerStaticRouter( + "WeaponCustomizerRoutes", + [ + { + url: "/weaponcustomizer/save", + action: async (url, info: CustomizePayload, sessionId, output) => this.saveCustomization(info) + }, + { + url: "/weaponcustomizer/load", + action: async (url, info, sessionId, output) => JSON.stringify(this.customizations) + } + ], + "custom-static-weapon-customizer" + ); + } + + private async saveCustomization(payload: CustomizePayload): Promise { + //this.logger.info(`WeaponCustomizer: Saving customization for weapon ${payload.weaponId}`); + if (Object.keys(payload.slots).length === 0) { + delete this.customizations[payload.weaponId]; + } else { + this.customizations[payload.weaponId] = payload.slots; + } + await this.save(); + return JSON.stringify({ success: true }); + } + + private load() { + try { + if (this.vfs.exists(this.filepath)) { + this.customizations = JSON.parse(this.vfs.readFile(this.filepath)); + } else { + this.customizations = {}; + + // Create the file with fs - vfs.writeFile pukes on windows paths if it needs to create the file + fs.writeFileSync(this.filepath, JSON.stringify(this.customizations)); + } + + const count = Object.keys(this.customizations).length; + if (count > 0) { + this.logger.logWithColor(`WeaponCustomizer: ${count} weapon customizations loaded.`, LogTextColor.CYAN); + } + } catch (error) { + this.logger.error("WeaponCustomizer: Failed to load weapon customization! " + error); + this.customizations = {}; + } + } + + private async save() { + try { + await this.vfs.writeFileAsync(this.filepath, JSON.stringify(this.customizations, null, 2)); + } catch (error) { + this.logger.error("WeaponCustomizer: Failed to save weapon customization! " + error); + } + } +} + +export const mod = new WeaponCustomizer(); diff --git a/profiles/Multiplayer/modlist.txt b/profiles/Multiplayer/modlist.txt index 70744e5..6163cc2 100644 --- a/profiles/Multiplayer/modlist.txt +++ b/profiles/Multiplayer/modlist.txt @@ -1,4 +1,3 @@ -# This file was automatically generated by Mod Organizer. -Unsorted_separator -Visceral Combat -SWAG + DONUTS