Updated Weapon Customizer
This commit is contained in:
parent
02aac2b4da
commit
d3e8098b26
Binary file not shown.
|
@ -1,11 +1,11 @@
|
||||||
[General]
|
[General]
|
||||||
gameName=spt
|
gameName=spt
|
||||||
modid=0
|
modid=0
|
||||||
version=d2025.1.6.0
|
version=d2025.1.7.0
|
||||||
newestVersion=
|
newestVersion=
|
||||||
category="2,"
|
category="2,"
|
||||||
nexusFileStatus=1
|
nexusFileStatus=1
|
||||||
installationFile=Tyfon-WeaponCustomizer-1.0.0.zip
|
installationFile=Tyfon-WeaponCustomizer-1.0.1.zip
|
||||||
repository=Nexus
|
repository=Nexus
|
||||||
ignoredVersion=
|
ignoredVersion=
|
||||||
comments=
|
comments=
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "weapon-customizer",
|
"name": "weapon-customizer",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"main": "src/mod.js",
|
"main": "src/mod.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Tyfon",
|
"author": "Tyfon",
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import type { DependencyContainer } from "tsyringe";
|
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 type { IPreSptLoadMod } from "@spt/models/external/IPreSptLoadMod";
|
||||||
import { LogTextColor } from "@spt/models/spt/logging/LogTextColor";
|
import { LogTextColor } from "@spt/models/spt/logging/LogTextColor";
|
||||||
import type { ILogger } from "@spt/models/spt/utils/ILogger";
|
import type { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
|
@ -26,15 +28,17 @@ type CustomizePayload = {
|
||||||
|
|
||||||
type Customizations = Record<string, Record<string, CustomPosition>>;
|
type Customizations = Record<string, Record<string, CustomPosition>>;
|
||||||
|
|
||||||
class WeaponCustomizer implements IPreSptLoadMod {
|
class WeaponCustomizer implements IPreSptLoadMod, IPostSptLoadMod {
|
||||||
private logger: ILogger;
|
private logger: ILogger;
|
||||||
private vfs: VFS;
|
private vfs: VFS;
|
||||||
|
private profileHelper: ProfileHelper;
|
||||||
private customizations: Customizations = null;
|
private customizations: Customizations = null;
|
||||||
private filepath: string;
|
private filepath: string;
|
||||||
|
|
||||||
public preSptLoad(container: DependencyContainer): void {
|
public preSptLoad(container: DependencyContainer): void {
|
||||||
this.logger = container.resolve<ILogger>("PrimaryLogger");
|
this.logger = container.resolve<ILogger>("PrimaryLogger");
|
||||||
this.vfs = container.resolve<VFS>("VFS");
|
this.vfs = container.resolve<VFS>("VFS");
|
||||||
|
|
||||||
const staticRouterModService = container.resolve<StaticRouterModService>("StaticRouterModService");
|
const staticRouterModService = container.resolve<StaticRouterModService>("StaticRouterModService");
|
||||||
|
|
||||||
this.filepath = path.resolve(__dirname, "../customizations.json");
|
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>("ProfileHelper");
|
||||||
|
this.clean();
|
||||||
|
}
|
||||||
|
|
||||||
private async saveCustomization(payload: CustomizePayload): Promise<string> {
|
private async saveCustomization(payload: CustomizePayload): Promise<string> {
|
||||||
//this.logger.info(`WeaponCustomizer: Saving customization for weapon ${payload.weaponId}`);
|
//this.logger.info(`WeaponCustomizer: Saving customization for weapon ${payload.weaponId}`);
|
||||||
if (Object.keys(payload.slots).length === 0) {
|
if (Object.keys(payload.slots).length === 0) {
|
||||||
|
@ -64,6 +73,7 @@ class WeaponCustomizer implements IPreSptLoadMod {
|
||||||
this.customizations[payload.weaponId] = payload.slots;
|
this.customizations[payload.weaponId] = payload.slots;
|
||||||
}
|
}
|
||||||
await this.save();
|
await this.save();
|
||||||
|
|
||||||
return JSON.stringify({ success: true });
|
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<string, boolean>();
|
||||||
|
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() {
|
private async save() {
|
||||||
try {
|
try {
|
||||||
await this.vfs.writeFileAsync(this.filepath, JSON.stringify(this.customizations, null, 2));
|
await this.vfs.writeFileAsync(this.filepath, JSON.stringify(this.customizations, null, 2));
|
||||||
|
|
Binary file not shown.
|
@ -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
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<string, CustomPosition>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Customizations = Record<string, Record<string, CustomPosition>>;
|
||||||
|
|
||||||
|
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<ILogger>("PrimaryLogger");
|
||||||
|
this.vfs = container.resolve<VFS>("VFS");
|
||||||
|
const staticRouterModService = container.resolve<StaticRouterModService>("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<string> {
|
||||||
|
//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();
|
|
@ -1,4 +1,3 @@
|
||||||
# This file was automatically generated by Mod Organizer.
|
|
||||||
-Unsorted_separator
|
-Unsorted_separator
|
||||||
-Visceral Combat
|
-Visceral Combat
|
||||||
-SWAG + DONUTS
|
-SWAG + DONUTS
|
||||||
|
|
Loading…
Reference in New Issue