diff --git a/mods/Weapon Customizer/BepInEx/plugins/Tyfon.WeaponCustomizer.dll b/mods/Weapon Customizer/BepInEx/plugins/Tyfon.WeaponCustomizer.dll new file mode 100644 index 0000000..53b0bb2 Binary files /dev/null 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 new file mode 100644 index 0000000..0ca81db --- /dev/null +++ b/mods/Weapon Customizer/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/user/mods/tyfon-weaponcustomizer/package.json b/mods/Weapon Customizer/user/mods/tyfon-weaponcustomizer/package.json new file mode 100644 index 0000000..ea0ce22 --- /dev/null +++ b/mods/Weapon Customizer/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/user/mods/tyfon-weaponcustomizer/src/mod.ts b/mods/Weapon Customizer/user/mods/tyfon-weaponcustomizer/src/mod.ts new file mode 100644 index 0000000..4c996e0 --- /dev/null +++ b/mods/Weapon Customizer/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 8f7d1cd..70744e5 100644 --- a/profiles/Multiplayer/modlist.txt +++ b/profiles/Multiplayer/modlist.txt @@ -22,6 +22,7 @@ +SAIN - EpicRangeTimes Preset +SAIN -AI & Combat Tweaks_separator ++Weapon Customizer +Little Drummer Boy +SVD +M249 diff --git a/profiles/Server/modlist.txt b/profiles/Server/modlist.txt index 4b3528f..d4ca4dc 100644 --- a/profiles/Server/modlist.txt +++ b/profiles/Server/modlist.txt @@ -1,4 +1,3 @@ -# This file was automatically generated by Mod Organizer. +Unsorted_separator -Visceral Combat -SWAG + DONUTS @@ -22,6 +21,7 @@ +SAIN - EpicRangeTimes Preset +SAIN +AI & Combat Tweaks_separator +-Weapon Customizer +Little Drummer Boy +SVD +M249