Updated example 5/13/14/18 to use DatabaseService

This commit is contained in:
Dev 2024-05-29 10:40:24 +01:00
parent 6ea6803456
commit 9bbc7d7a25
5 changed files with 119 additions and 100 deletions

View File

@ -5,7 +5,6 @@ import { IPreSptLoadMod } from "@spt/models/external/IPreSptLoadMod";
import { IPostDBLoadMod } from "@spt/models/external/IPostDBLoadMod";
import { ILogger } from "@spt/models/spt/utils/ILogger";
import { PreSptModLoader } from "@spt/loaders/PreSptModLoader";
import { DatabaseServer } from "@spt/servers/DatabaseServer";
import { ImageRouter } from "@spt/routers/ImageRouter";
import { ConfigServer } from "@spt/servers/ConfigServer";
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
@ -21,6 +20,7 @@ import * as baseJson from "../db/base.json";
import { TraderHelper } from "./traderHelpers";
import { FluentAssortConstructor as FluentAssortCreator } from "./fluentTraderAssortCreator";
import { DatabaseService } from "@spt/services/DatabaseService";
class SampleTrader implements IPreSptLoadMod, IPostDBLoadMod
{
@ -44,7 +44,7 @@ class SampleTrader implements IPreSptLoadMod, IPostDBLoadMod
this.logger.debug(`[${this.mod}] preSpt Loading... `);
// Get SPT code/data we need later
const PreSptModLoader: PreSptModLoader = container.resolve<PreSptModLoader>("PreSptModLoader");
const preSptModLoader: PreSptModLoader = container.resolve<PreSptModLoader>("PreSptModLoader");
const imageRouter: ImageRouter = container.resolve<ImageRouter>("ImageRouter");
const hashUtil: HashUtil = container.resolve<HashUtil>("HashUtil");
const configServer = container.resolve<ConfigServer>("ConfigServer");
@ -54,7 +54,7 @@ class SampleTrader implements IPreSptLoadMod, IPostDBLoadMod
// Create helper class and use it to register our traders image/icon + set its stock refresh time
this.traderHelper = new TraderHelper();
this.fluentAssortCreator = new FluentAssortCreator(hashUtil, this.logger);
this.traderHelper.registerProfileImage(baseJson, this.mod, PreSptModLoader, imageRouter, "cat.jpg");
this.traderHelper.registerProfileImage(baseJson, this.mod, preSptModLoader, imageRouter, "cat.jpg");
this.traderHelper.setTraderUpdateTime(traderConfig, baseJson, 3600, 4000);
// Add trader to trader enum
@ -74,37 +74,38 @@ class SampleTrader implements IPreSptLoadMod, IPostDBLoadMod
{
this.logger.debug(`[${this.mod}] postDb Loading... `);
// Resolve SPT classes we'll use
const databaseServer: DatabaseServer = container.resolve<DatabaseServer>("DatabaseServer");
const configServer: ConfigServer = container.resolve<ConfigServer>("ConfigServer");
// Resolve SPT classes we'll need
const databaseService = container.resolve<DatabaseService>("DatabaseService");
const jsonUtil: JsonUtil = container.resolve<JsonUtil>("JsonUtil");
// Get a reference to the database tables
const tables = databaseServer.getTables();
// Get references to database data we need
const traders = databaseService.getTraders();
const globals = databaseService.getGlobals();
const locales = databaseService.getLocales();
// Add new trader to the trader dictionary in DatabaseServer - has no assorts (items) yet
this.traderHelper.addTraderToDb(baseJson, tables, jsonUtil);
this.traderHelper.addTraderToDb(baseJson, traders, jsonUtil);
// Add milk
const MILK_ID = "575146b724597720a27126d5"; // Can find item ids in `database\templates\items.json` or with https://db.sp-tarkov.com/search
const MILK_ID = "575146b724597720a27126d5"; // Can find item ids in: `database\templates\items.json` or with: https://db.sp-tarkov.com/search
this.fluentAssortCreator
.createSingleAssortItem(MILK_ID)
.addStackCount(200)
.addBuyRestriction(10)
.addMoneyCost(Money.ROUBLES, 2000)
.addLoyaltyLevel(1)
.export(tables.traders[baseJson._id]);
.export(traders[baseJson._id]);
// Add 3x bitcoin + salewa for milk barter
const BITCOIN_ID = "59faff1d86f7746c51718c9c";
const SALEWA_ID = "544fb45d4bdc2dee738b4568";
const BITCOIN_ID = "59faff1d86f7746c51718c9c"; // Can find item ids in: `database\templates\items.json` or with: https://db.sp-tarkov.com/search
const SALEWA_ID = "544fb45d4bdc2dee738b4568"; // Can find item ids in: `database\templates\items.json` or with: https://db.sp-tarkov.com/search
this.fluentAssortCreator
.createSingleAssortItem(MILK_ID)
.addStackCount(100)
.addBarterCost(BITCOIN_ID, 3)
.addBarterCost(SALEWA_ID, 1)
.addLoyaltyLevel(1)
.export(tables.traders[baseJson._id]);
.export(traders[baseJson._id]);
// Add glock as money purchase
@ -114,20 +115,20 @@ class SampleTrader implements IPreSptLoadMod, IPostDBLoadMod
.addMoneyCost(Money.ROUBLES, 20000)
.addBuyRestriction(3)
.addLoyaltyLevel(1)
.export(tables.traders[baseJson._id]);
.export(traders[baseJson._id]);
// Add mp133 preset as mayo barter
this.fluentAssortCreator
.createComplexAssortItem(tables.globals.ItemPresets["584148f2245977598f1ad387"]._items)
.createComplexAssortItem(globals.ItemPresets["584148f2245977598f1ad387"]._items)
.addStackCount(200)
.addBarterCost("5bc9b156d4351e00367fbce9", 1)
.addBuyRestriction(3)
.addLoyaltyLevel(1)
.export(tables.traders[baseJson._id]);
.export(traders[baseJson._id]);
// Add trader to locale file, ensures trader text shows properly on screen
// WARNING: adds the same text to ALL locales (e.g. chinese/french/english)
this.traderHelper.addTraderToLocales(baseJson, tables, baseJson.name, "Cat", baseJson.nickname, baseJson.location, "This is the cat shop");
this.traderHelper.addTraderToLocales(baseJson, locales, baseJson.name, "Cat", baseJson.nickname, baseJson.location, "This is the cat shop");
this.logger.debug(`[${this.mod}] postDb Loaded`);
}

View File

@ -1,29 +1,30 @@
import { PreSptModLoader } from "@spt/loaders/PreSptModLoader";
import { Item } from "@spt/models/eft/common/tables/IItem";
import { ITraderBase, ITraderAssort } from "@spt/models/eft/common/tables/ITrader";
import { ITraderBase, ITraderAssort, ITrader } from "@spt/models/eft/common/tables/ITrader";
import { ITraderConfig, UpdateTime } from "@spt/models/spt/config/ITraderConfig";
import { IDatabaseTables } from "@spt/models/spt/server/IDatabaseTables";
import { ILocaleBase } from "@spt/models/spt/server/ILocaleBase";
import { ImageRouter } from "@spt/routers/ImageRouter";
import { JsonUtil } from "@spt/utils/JsonUtil";
export class TraderHelper
{
/**
/**
* Add profile picture to our trader
* @param baseJson json file for trader (db/base.json)
* @param modName mod folder name
* @param PreSptModLoader mod loader class - used to get the mods file path
* @param preSptModLoader 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
*/
public registerProfileImage(baseJson: any, modName: string, PreSptModLoader: PreSptModLoader, imageRouter: ImageRouter, traderImageName: string): void
{
// Reference the mod "res" folder
const imageFilepath = `./${PreSptModLoader.getModPath(modName)}res`;
public registerProfileImage(baseJson: any, modName: string, preSptModLoader: PreSptModLoader, imageRouter: ImageRouter, traderImageName: string): void
{
// Reference the mod "res" folder
const imageFilepath = `./${preSptModLoader.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}`);
}
// 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)
@ -39,8 +40,8 @@ export class TraderHelper
traderId: baseJson._id,
seconds: {
min: refreshTimeSecondsMin,
max: refreshTimeSecondsMax,
},
max: refreshTimeSecondsMax
}
};
traderConfig.updateTime.push(traderRefreshRecord);
@ -49,21 +50,21 @@ export class TraderHelper
/**
* Add our new trader to the database
* @param traderDetailsToAdd trader details
* @param tables database
* @param traders Traders dictionary (key is traderid, value is trader data)
* @param jsonUtil json utility class
*/
// rome-ignore lint/suspicious/noExplicitAny: traderDetailsToAdd comes from base.json, so no type
public addTraderToDb(traderDetailsToAdd: any, tables: IDatabaseTables, jsonUtil: JsonUtil): void
public addTraderToDb(traderDetailsToAdd: any, traders: Record<string, ITrader>, jsonUtil: JsonUtil): void
{
// Add trader to trader table, key is the traders id
tables.traders[traderDetailsToAdd._id] = {
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
fail: {}
} // questassort is empty as trader has no assorts unlocked by quests
};
}
@ -78,7 +79,7 @@ export class TraderHelper
nextResupply: 0,
items: [],
barter_scheme: {},
loyal_level_items: {},
loyal_level_items: {}
};
return assortTable;
@ -96,7 +97,7 @@ export class TraderHelper
// 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
_tpl: "5a7ae0c351dfba0017554310" // This is the weapons tpl, found on: https://db.sp-tarkov.com/search
});
// Add barrel
@ -104,7 +105,7 @@ export class TraderHelper
_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
slotId: "mod_barrel" // Required for mods, you need to define what 'role' they have
});
// Add reciever
@ -112,15 +113,15 @@ export class TraderHelper
_id: "glockReciever",
_tpl:"5a9685b1a2750c0032157104",
parentId: "glockBase",
slotId: "mod_reciever",
slotId: "mod_reciever"
});
// Add compensator
glock.push({
// 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",
slotId: "mod_muzzle"
});
// Add Pistol grip
@ -128,7 +129,7 @@ export class TraderHelper
_id: "glockPistolGrip",
_tpl:"5a7b4960e899ef197b331a2d",
parentId: "glockBase",
slotId: "mod_pistol_grip",
slotId: "mod_pistol_grip"
});
// Add front sight
@ -136,7 +137,7 @@ export class TraderHelper
_id: "glockRearSight",
_tpl: "5a6f5d528dc32e00094b97d9",
parentId: "glockReciever",
slotId: "mod_sight_rear",
slotId: "mod_sight_rear"
});
// Add rear sight
@ -144,7 +145,7 @@ export class TraderHelper
_id: "glockFrontSight",
_tpl: "5a6f58f68dc32e000a311390",
parentId: "glockReciever",
slotId: "mod_sight_front",
slotId: "mod_sight_front"
});
// Add magazine
@ -152,28 +153,29 @@ export class TraderHelper
_id: "glockMagazine",
_tpl: "630769c4962d0247b029dc60",
parentId: "glockBase",
slotId: "mod_magazine",
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 baseJson json file for trader (mod/db/base.json)
* @param localesDb Locale data from database
* @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
*/
public addTraderToLocales(baseJson: any, tables: IDatabaseTables, fullName: string, firstName: string, nickName: string, location: string, description: string)
public addTraderToLocales(baseJson: any, localesDb: ILocaleBase, fullName: string, firstName: string, nickName: string, location: string, description: string): void
{
// For each language, add locale for the new trader
const locales = Object.values(tables.locales.global);
const localeValues = Object.values(localesDb.global);
for (const locale of locales) {
for (const locale of localeValues)
{
locale[`${baseJson._id} FullName`] = fullName;
locale[`${baseJson._id} FirstName`] = firstName;
locale[`${baseJson._id} Nickname`] = nickName;

View File

@ -4,46 +4,59 @@ import { IPreSptLoadMod } from "@spt/models/external/IPreSptLoadMod";
import { IPostSptLoadMod } from "@spt/models/external/IPostSptLoadMod";
import { IPostDBLoadMod } from "@spt/models/external/IPostDBLoadMod";
import { ILogger } from "@spt/models/spt/utils/ILogger";
import { DatabaseServer } from "@spt/servers/DatabaseServer";
import { LogTextColor } from "@spt/models/spt/logging/LogTextColor";
import { LogBackgroundColor } from "@spt/models/spt/logging/LogBackgroundColor";
import { DatabaseService } from "@spt/services/DatabaseService";
import { ItemHelper } from "@spt/helpers/ItemHelper";
class Mod implements IPreSptLoadMod, IPostSptLoadMod, IPostDBLoadMod
{
public preSptLoad(container: DependencyContainer): void {
public preSptLoad(container: DependencyContainer): void
{
// Database will be empty in here
const databaseServer = container.resolve<DatabaseServer>("DatabaseServer");
const databaseService = container.resolve<DatabaseService>("DatabaseService");
const logger = container.resolve<ILogger>("PrimaryLogger");
logger.logWithColor(`Database item table state: ${databaseServer.getTables().templates} (<<< should be undefined)`, LogTextColor.RED, LogBackgroundColor.YELLOW);
logger.logWithColor(`Database item table state: ${databaseService.getTemplates()} (<<< should be undefined)`, LogTextColor.RED, LogBackgroundColor.YELLOW);
}
public postDBLoad(container: DependencyContainer): void {
public postDBLoad(container: DependencyContainer): void
{
// Database will be loaded, this is the fresh state of the DB so NOTHING from the SPT
// logic has modified anything yet. This is the DB loaded straight from the JSON files
const databaseServer = container.resolve<DatabaseServer>("DatabaseServer");
const databaseService = container.resolve<DatabaseService>("DatabaseService");
const logger = container.resolve<ILogger>("PrimaryLogger");
logger.logWithColor(`Database item size: ${Object.entries(databaseServer.getTables().templates.items).length}`, LogTextColor.RED, LogBackgroundColor.YELLOW);
logger.logWithColor(`Database item size: ${Object.entries(databaseService.getItems()).length}`, LogTextColor.RED, LogBackgroundColor.YELLOW);
// lets do a quick modification and see how this reflect later on, on the postSptLoad()
// find the nvgs item by its Id
const nvgs = databaseServer.getTables().templates.items["5c0558060db834001b735271"];
const nvgs = databaseService.getItems()["5c0558060db834001b735271"];
// Lets log the state before the modification:
logger.logWithColor(`NVGs default CanSellOnRagfair: ${nvgs._props.CanSellOnRagfair}`, LogTextColor.RED, LogBackgroundColor.YELLOW);
// update one of its properties to be true
nvgs._props.CanSellOnRagfair = true;
}
public postSptLoad(container: DependencyContainer): void {
public postSptLoad(container: DependencyContainer): void
{
// The modification we made above would have been processed by now by SPT, so any values we changed had
// already been passed through the initial lifecycles (OnLoad) of SPT.
const databaseServer = container.resolve<DatabaseServer>("DatabaseServer");
const itemHelper = container.resolve<ItemHelper>("ItemHelper");
const logger = container.resolve<ILogger>("PrimaryLogger");
// find the nvgs item again by its Id
const nvgs = databaseServer.getTables().templates.items["5c0558060db834001b735271"];
// Lets log the state, this value should be true:
logger.logWithColor(`NVGs modified CanSellOnRagfair: ${nvgs._props.CanSellOnRagfair}`, LogTextColor.RED, LogBackgroundColor.YELLOW);
// Find the nvgs item again by its Id using ItemHelper class (alternate way of getting items that has more saftey checks)
const nvgs = itemHelper.getItem("5c0558060db834001b735271"); // Returns an array of 2 values, 1st is a bool, true if item is valid, 2nd is the item data
if (nvgs[0])
{
// item was found in database, hooray
// Assign a new variable so we can cleanly reference its _props data below
const nvgsData = nvgs[1];
// Lets log the state, this value should be true:
logger.logWithColor(`NVGs modified CanSellOnRagfair: ${nvgsData._props.CanSellOnRagfair}`, LogTextColor.RED, LogBackgroundColor.YELLOW);
}
}
}

View File

@ -4,18 +4,18 @@ import { IPostDBLoadMod } from "@spt/models/external/IPostDBLoadMod";
import { CustomItemService } from "@spt/services/mod/CustomItemService";
import { NewItemFromCloneDetails } from "@spt/models/spt/mod/NewItemDetails";
import { IPostSptLoadMod } from "@spt/models/external/IPostSptLoadMod";
import { DatabaseServer } from "@spt/servers/DatabaseServer";
import { DatabaseService } from "@spt/services/DatabaseService";
class Mod implements IPostDBLoadMod, IPostSptLoadMod
{
public postDBLoad(container: DependencyContainer): void
{
// Resolve the CustomItemService container
const CustomItem = container.resolve<CustomItemService>("CustomItemService");
// Resolve the CustomItemService
const customItemService = container.resolve<CustomItemService>("CustomItemService");
//Example of adding new item by cloning existing item using createclonedetails
const ExampleCloneItem: NewItemFromCloneDetails = {
itemTplToClone: "61f7c9e189e6fb1a5e3ea78d", //the item we want to clone, in this example i will cloning the MP-18
// Clone the mp-18 and adjust its properties slightly
const exampleCloneItem: NewItemFromCloneDetails = {
itemTplToClone: "61f7c9e189e6fb1a5e3ea78d", // MP-18 id
overrideProperties: {
Chambers: [
{
@ -41,40 +41,42 @@ class Mod implements IPostDBLoadMod, IPostSptLoadMod
"5d6e68b3a4b9361bca7e50b5",
"5d6e6891a4b9361bd473feea",
"5d6e689ca4b9361bc8618956",
"5d6e68d1a4b93622fe60e845",
],
},
],
"5d6e68d1a4b93622fe60e845"
]
}
]
},
_required: false,
_mergeSlotWithChildren: false,
_proto: "55d4af244bdc2d962f8b4571",
},
],
}, //Overried properties basically tell the server on what data inside _props to be modified from the cloned item, in this example i am modifying the ammo used to be 12G
parentId: "5447b6094bdc2dc3278b4567", //ParentId refers to the Node item the gun will be under, you can check it in https://db.sp-tarkov.com/search
newId: "CustomMP18", //The new id of our cloned item
fleaPriceRoubles: 50000, //Self explanatary
_proto: "55d4af244bdc2d962f8b4571"
}
]
}, // Overried properties basically tell the server on what data inside _props to be modified from the cloned item, in this example i am modifying the ammo used to be 12G
parentId: "5447b6094bdc2dc3278b4567", // ParentId refers to the Node item the gun will be under. For this example we use `Shotgun`, you can check it in https://db.sp-tarkov.com/search
newId: "CustomMP18", // The new id of our cloned item
fleaPriceRoubles: 50000, // Average price of item on flea
handbookPriceRoubles: 42500,
handbookParentId: "5b5f78e986f77447ed5636b1", //Handbook Parent Id refers to the category the gun will be under
//you see those side box tab thing that only select gun under specific icon? Handbook parent can be found in SPT_Data\Server\database\templates.
handbookParentId: "5b5f78e986f77447ed5636b1", // Handbook Parent Id refers to the category the gun will be under
// You see those side box tab thing that only select gun under specific icon? Handbook parent can be found in SPT_Data\Server\database\templates.
locales: {
en: {
name: "MP-18 12g",
shortName: "Custom MP18",
description: "A custom MP18 chambered in 12G",
},
},
description: "A custom MP18 chambered in 12G"
}
}
};
CustomItem.createItemFromClone(ExampleCloneItem); //Basically calls the function and tell the server to add our Cloned new item into the server
customItemService.createItemFromClone(exampleCloneItem); // Tell the server to add our Cloned item into the server using custom item service
}
//Check if our item is in the server or not
public postSptLoad(container: DependencyContainer): void {
const db = container.resolve<DatabaseServer>("DatabaseServer");
const item = db.getTables().templates.items;
public postSptLoad(container: DependencyContainer): void
{
const databaseService = container.resolve<DatabaseService>("DatabaseService");
const item = databaseService.getItems();
// Log our new guns properties to console
console.log(item["CustomMP18"]._props);
}
}

View File

@ -2,10 +2,10 @@ import { DependencyContainer } from "tsyringe";
import { IPreSptLoadMod } from "@spt/models/external/IPreSptLoadMod";
import { LauncherController } from "@spt/controllers/LauncherController";
import { DatabaseServer } from "@spt/servers/DatabaseServer";
import { ILoginRequestData } from "@spt/models/eft/launcher/ILoginRequestData";
import { ILogger } from "@spt/models/spt/utils/ILogger";
import { SaveServer } from "@spt/servers/SaveServer";
import { DatabaseService } from "@spt/services/DatabaseService";
class Mod implements IPreSptLoadMod
{
@ -31,7 +31,7 @@ class Mod implements IPreSptLoadMod
return this.replacementFunction(info);
}
// The modifier Always makes sure this replacement method is ALWAYS replaced
}, {frequency: "Always"});
}, { frequency: "Always" });
}
// our new replacement function, ready to be used
@ -55,15 +55,16 @@ class Mod implements IPreSptLoadMod
// This is now extra stuff we want to add
// We resolve 2 more dependencies: The logger and the DatabaseServer
const logger = Mod.container.resolve<ILogger>("PrimaryLogger");
const dbServer = Mod.container.resolve<DatabaseServer>("DatabaseServer");
const databaseService = Mod.container.resolve<DatabaseService>("DatabaseService");
// As an example lets count the number of items in the database
const loadedItems = Object.entries(databaseService.getItems()).length;
// As an example Im counting the amount of loaded items on the DB
const loadedItems = Object.entries(dbServer.getTables().templates.items).length;
// Lets do a few informational messages
logger.success(`User ${info.username} logged in to SPT, there are ${loadedItems} items loaded into the database`);
logger.success(originalReturn.length > 0 ? `User session ID: ${originalReturn}` : "User not found");
// And finally return whatever we were supposed to return through this function
// And finally return whatever we were supposed to return originally through this function
return originalReturn;
}
}