rename file

This commit is contained in:
2024-06-09 14:50:21 +08:00
parent 138a16c289
commit 0e22d8dbce
376 changed files with 3 additions and 3 deletions
@@ -0,0 +1,126 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FluentAssortConstructor = void 0;
class FluentAssortConstructor {
itemsToSell = [];
barterScheme = {};
loyaltyLevel = {};
hashUtil;
logger;
constructor(hashutil, logger) {
this.hashUtil = hashutil;
this.logger = logger;
}
/**
* Start selling item with tpl
* @param itemTpl Tpl id of the item you want trader to sell
* @param itemId Optional - set your own Id, otherwise unique id will be generated
*/
createSingleAssortItem(itemTpl, itemId = undefined) {
// Create item ready for insertion into assort table
const newItemToAdd = {
_id: !itemId ? this.hashUtil.generate() : itemId,
_tpl: itemTpl,
parentId: "hideout", // Should always be "hideout"
slotId: "hideout", // Should always be "hideout"
upd: {
UnlimitedCount: false,
StackObjectsCount: 100
}
};
this.itemsToSell.push(newItemToAdd);
return this;
}
createComplexAssortItem(items) {
items[0].parentId = "hideout";
items[0].slotId = "hideout";
if (!items[0].upd) {
items[0].upd = {};
}
items[0].upd.UnlimitedCount = false;
items[0].upd.StackObjectsCount = 100;
this.itemsToSell.push(...items);
return this;
}
addStackCount(stackCount) {
this.itemsToSell[0].upd.StackObjectsCount = stackCount;
return this;
}
addUnlimitedStackCount() {
this.itemsToSell[0].upd.StackObjectsCount = 999999;
this.itemsToSell[0].upd.UnlimitedCount = true;
return this;
}
makeStackCountUnlimited() {
this.itemsToSell[0].upd.StackObjectsCount = 999999;
return this;
}
addBuyRestriction(maxBuyLimit) {
this.itemsToSell[0].upd.BuyRestrictionMax = maxBuyLimit;
this.itemsToSell[0].upd.BuyRestrictionCurrent = 0;
return this;
}
addLoyaltyLevel(level) {
this.loyaltyLevel[this.itemsToSell[0]._id] = level;
return this;
}
addMoneyCost(currencyType, amount) {
this.barterScheme[this.itemsToSell[0]._id] = [
[
{
count: amount,
_tpl: currencyType
}
]
];
return this;
}
addBarterCost(itemTpl, count) {
const sellableItemId = this.itemsToSell[0]._id;
// No data at all, create
if (Object.keys(this.barterScheme).length === 0) {
this.barterScheme[sellableItemId] = [[
{
count: count,
_tpl: itemTpl
}
]];
}
else {
// Item already exists, add to
const existingData = this.barterScheme[sellableItemId][0].find(x => x._tpl === itemTpl);
if (existingData) {
// itemtpl already a barter for item, add to count
existingData.count += count;
}
else {
// No barter for item, add it fresh
this.barterScheme[sellableItemId][0].push({
count: count,
_tpl: itemTpl
});
}
}
return this;
}
/**
* Reset objet ready for reuse
* @returns
*/
export(data) {
const itemBeingSoldId = this.itemsToSell[0]._id;
if (data.assort.items.find(x => x._id === itemBeingSoldId)) {
this.logger.error(`Unable to add complex item with item key ${this.itemsToSell[0]._id}, key already used`);
return;
}
data.assort.items.push(...this.itemsToSell);
data.assort.barter_scheme[itemBeingSoldId] = this.barterScheme[itemBeingSoldId];
data.assort.loyal_level_items[itemBeingSoldId] = this.loyaltyLevel[itemBeingSoldId];
this.itemsToSell = [];
this.barterScheme = {};
this.loyaltyLevel = {};
return this;
}
}
exports.FluentAssortConstructor = FluentAssortConstructor;
//# sourceMappingURL=fluentTraderAssortCreator.js.map
@@ -0,0 +1 @@
{"version":3,"file":"fluentTraderAssortCreator.js","sourceRoot":"","sources":["fluentTraderAssortCreator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,175 @@
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
import { IBarterScheme, ITrader } from "@spt-aki/models/eft/common/tables/ITrader";
import { Money } from "@spt-aki/models/enums/Money";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { HashUtil } from "@spt-aki/utils/HashUtil";
export class FluentAssortConstructor
{
protected itemsToSell: Item[] = [];
protected barterScheme: Record<string, IBarterScheme[][]> = {};
protected loyaltyLevel: Record<string, number> = {};
protected hashUtil: HashUtil;
protected logger: ILogger;
constructor(hashutil: HashUtil, logger: ILogger)
{
this.hashUtil = hashutil
this.logger = logger;
}
/**
* Start selling item with tpl
* @param itemTpl Tpl id of the item you want trader to sell
* @param itemId Optional - set your own Id, otherwise unique id will be generated
*/
public createSingleAssortItem(itemTpl: string, itemId = undefined): FluentAssortConstructor
{
// Create item ready for insertion into assort table
const newItemToAdd: Item = {
_id: !itemId ? this.hashUtil.generate(): itemId,
_tpl: itemTpl,
parentId: "hideout", // Should always be "hideout"
slotId: "hideout", // Should always be "hideout"
upd: {
UnlimitedCount: false,
StackObjectsCount: 100
}
};
this.itemsToSell.push(newItemToAdd);
return this;
}
public createComplexAssortItem(items: Item[]): FluentAssortConstructor
{
items[0].parentId = "hideout";
items[0].slotId = "hideout";
if (!items[0].upd)
{
items[0].upd = {}
}
items[0].upd.UnlimitedCount = false;
items[0].upd.StackObjectsCount = 100;
this.itemsToSell.push(...items);
return this;
}
public addStackCount(stackCount: number): FluentAssortConstructor
{
this.itemsToSell[0].upd.StackObjectsCount = stackCount;
return this;
}
public addUnlimitedStackCount(): FluentAssortConstructor
{
this.itemsToSell[0].upd.StackObjectsCount = 999999;
this.itemsToSell[0].upd.UnlimitedCount = true;
return this;
}
public makeStackCountUnlimited(): FluentAssortConstructor
{
this.itemsToSell[0].upd.StackObjectsCount = 999999;
return this;
}
public addBuyRestriction(maxBuyLimit: number): FluentAssortConstructor
{
this.itemsToSell[0].upd.BuyRestrictionMax = maxBuyLimit;
this.itemsToSell[0].upd.BuyRestrictionCurrent = 0;
return this;
}
public addLoyaltyLevel(level: number)
{
this.loyaltyLevel[this.itemsToSell[0]._id] = level;
return this;
}
public addMoneyCost(currencyType: Money, amount: number): FluentAssortConstructor
{
this.barterScheme[this.itemsToSell[0]._id] = [
[
{
count: amount,
_tpl: currencyType
}
]
];
return this;
}
public addBarterCost(itemTpl: string, count: number): FluentAssortConstructor
{
const sellableItemId = this.itemsToSell[0]._id;
// No data at all, create
if (Object.keys(this.barterScheme).length === 0)
{
this.barterScheme[sellableItemId] = [[
{
count: count,
_tpl: itemTpl
}
]];
}
else
{
// Item already exists, add to
const existingData = this.barterScheme[sellableItemId][0].find(x => x._tpl === itemTpl);
if (existingData)
{
// itemtpl already a barter for item, add to count
existingData.count+= count;
}
else
{
// No barter for item, add it fresh
this.barterScheme[sellableItemId][0].push({
count: count,
_tpl: itemTpl
})
}
}
return this;
}
/**
* Reset objet ready for reuse
* @returns
*/
public export(data: ITrader): FluentAssortConstructor
{
const itemBeingSoldId = this.itemsToSell[0]._id;
if (data.assort.items.find(x => x._id === itemBeingSoldId))
{
this.logger.error(`Unable to add complex item with item key ${this.itemsToSell[0]._id}, key already used`);
return;
}
data.assort.items.push(...this.itemsToSell);
data.assort.barter_scheme[itemBeingSoldId] = this.barterScheme[itemBeingSoldId];
data.assort.loyal_level_items[itemBeingSoldId] = this.loyaltyLevel[itemBeingSoldId];
this.itemsToSell = [];
this.barterScheme = {};
this.loyaltyLevel = {};
return this;
}
}
@@ -0,0 +1,161 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const ConfigTypes_1 = require("/snapshot/project/obj/models/enums/ConfigTypes");
const fs = __importStar(require("node:fs"));
const path = __importStar(require("node:path"));
// New trader settings
const baseJson = __importStar(require("../db/base.json"));
const traderHelpers_1 = require("./traderHelpers");
const fluentTraderAssortCreator_1 = require("./fluentTraderAssortCreator");
const Money_1 = require("/snapshot/project/obj/models/enums/Money");
const Traders_1 = require("/snapshot/project/obj/models/enums/Traders");
class HideoutHarry {
mod;
logger;
traderHelper;
fluentAssortCreator;
static config;
static itemsPath = path.resolve(__dirname, "../config/items.json");
static configPath = path.resolve(__dirname, "../config/config.json");
constructor() {
this.mod = "acidphantasm-harryhideout"; // Set name of mod so we can log it to console later
}
/**
* Some work needs to be done prior to SPT code being loaded, registering the profile image + setting trader update time inside the trader config json
* @param container Dependency container
*/
preAkiLoad(container) {
// Get a logger
this.logger = container.resolve("WinstonLogger");
// Get SPT code/data we need later
const preAkiModLoader = container.resolve("PreAkiModLoader");
const imageRouter = container.resolve("ImageRouter");
const hashUtil = container.resolve("HashUtil");
const configServer = container.resolve("ConfigServer");
const traderConfig = configServer.getConfig(ConfigTypes_1.ConfigTypes.TRADER);
const ragfairConfig = configServer.getConfig(ConfigTypes_1.ConfigTypes.RAGFAIR);
// Create helper class and use it to register our traders image/icon + set its stock refresh time
this.traderHelper = new traderHelpers_1.TraderHelper();
this.fluentAssortCreator = new fluentTraderAssortCreator_1.FluentAssortConstructor(hashUtil, this.logger);
this.traderHelper.registerProfileImage(baseJson, this.mod, preAkiModLoader, imageRouter, "harry.jpg");
this.traderHelper.setTraderUpdateTime(traderConfig, baseJson, 3600, 4000);
// Add trader to trader enum
Traders_1.Traders[baseJson._id] = baseJson._id;
// Add trader to flea market
ragfairConfig.traders[baseJson._id] = true;
}
/**
* Majority of trader-related work occurs after the aki database has been loaded but prior to SPT code being run
* @param container Dependency container
*/
postDBLoad(container) {
HideoutHarry.config = JSON.parse(fs.readFileSync(HideoutHarry.configPath, "utf-8"));
// Resolve SPT classes we'll use
const logger = container.resolve("WinstonLogger");
const databaseServer = container.resolve("DatabaseServer");
const configServer = container.resolve("ConfigServer");
const jsonUtil = container.resolve("JsonUtil");
const priceTable = databaseServer.getTables().templates.prices;
const handbookTable = databaseServer.getTables().templates.handbook;
// Get a reference to the database tables
const tables = databaseServer.getTables();
// Add new trader to the trader dictionary in DatabaseServer - has no assorts (items) yet
this.traderHelper.addTraderToDb(baseJson, tables, jsonUtil);
const start = performance.now();
const itemList = JSON.parse(fs.readFileSync(HideoutHarry.itemsPath, "utf-8"));
const nonBarterItems = itemList.nonBarterItems;
const barterItems = itemList.barterItems;
const lowFleaRange = 0.85;
// Non-Barter Items Iteration
for (const item in nonBarterItems) {
{
const itemID = nonBarterItems[item].itemID;
if (HideoutHarry.config.useFleaPrices) {
let price = (priceTable[itemID] * HideoutHarry.config.itemPriceMultiplier);
if (!price) {
price = (handbookTable.Items.find(x => x.Id === itemID)?.Price ?? 1) * HideoutHarry.config.itemPriceMultiplier;
}
this.fluentAssortCreator.createSingleAssortItem(itemID)
.addUnlimitedStackCount()
.addMoneyCost(Money_1.Money.ROUBLES, Math.round(price * lowFleaRange))
.addLoyaltyLevel(1)
.export(tables.traders[baseJson._id]);
if (HideoutHarry.config.enableConsoleDebug) {
logger.log("ItemID: " + itemID + " for price: " + Math.round(price), "cyan");
}
}
else {
const price = nonBarterItems[item].price;
this.fluentAssortCreator.createSingleAssortItem(itemID)
.addUnlimitedStackCount()
.addMoneyCost(Money_1.Money.ROUBLES, Math.round(price * lowFleaRange))
.addLoyaltyLevel(1)
.export(tables.traders[baseJson._id]);
if (HideoutHarry.config.enableConsoleDebug) {
logger.log("ItemID: " + itemID + " for price: " + Math.round(price), "cyan");
}
}
}
}
// Barter Items Iteration
for (const item in barterItems) {
{
const itemID = barterItems[item].itemID;
const barterItem = barterItems[item].barterItemID;
const barterAmount = barterItems[item].barterAmount;
if (HideoutHarry.config.useBarters) {
this.fluentAssortCreator.createSingleAssortItem(itemID)
.addUnlimitedStackCount()
.addBarterCost(barterItem, barterAmount)
.addLoyaltyLevel(1)
.export(tables.traders[baseJson._id]);
if (HideoutHarry.config.enableConsoleDebug) {
logger.log("ItemID: " + itemID + " for barter: " + barterAmount + " " + barterItem, "cyan");
}
}
else {
const price = barterItems[item].price;
this.fluentAssortCreator.createSingleAssortItem(itemID)
.addUnlimitedStackCount()
.addMoneyCost(Money_1.Money.ROUBLES, Math.round(price))
.addLoyaltyLevel(1)
.export(tables.traders[baseJson._id]);
if (HideoutHarry.config.enableConsoleDebug) {
logger.log("ItemID: " + itemID + " for price: " + Math.round(price), "cyan");
}
}
}
}
// 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, "Hideout Harry", baseJson.nickname, baseJson.location, "I'm sellin', what are you buyin'?");
this.logger.debug(`[${this.mod}] loaded... `);
const timeTaken = performance.now() - start;
logger.log(`[${this.mod}] Assort generation took ${timeTaken.toFixed(3)}ms.`, "green");
}
}
module.exports = { mod: new HideoutHarry() };
//# sourceMappingURL=mod.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,184 @@
import { DependencyContainer } from "tsyringe";
// SPT types
import { IPreAkiLoadMod } from "@spt-aki/models/external/IPreAkiLoadMod";
import { IPostDBLoadMod } from "@spt-aki/models/external/IPostDBLoadMod";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { PreAkiModLoader } from "@spt-aki/loaders/PreAkiModLoader";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { ImageRouter } from "@spt-aki/routers/ImageRouter";
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { ITraderConfig } from "@spt-aki/models/spt/config/ITraderConfig";
import { IRagfairConfig } from "@spt-aki/models/spt/config/IRagfairConfig";
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
import * as fs from "node:fs";
import * as path from "node:path";
// New trader settings
import * as baseJson from "../db/base.json";
import { TraderHelper } from "./traderHelpers";
import { FluentAssortConstructor as FluentAssortCreator } from "./fluentTraderAssortCreator";
import { Money } from "@spt-aki/models/enums/Money";
import { Traders } from "@spt-aki/models/enums/Traders";
import { HashUtil } from "@spt-aki/utils/HashUtil";
class HideoutHarry implements IPreAkiLoadMod, IPostDBLoadMod
{
private mod: string
private logger: ILogger
private traderHelper: TraderHelper
private fluentAssortCreator: FluentAssortCreator
private static config: Config;
private static itemsPath = path.resolve(__dirname, "../config/items.json");
private static configPath = path.resolve(__dirname, "../config/config.json");
constructor() {
this.mod = "acidphantasm-harryhideout"; // Set name of mod so we can log it to console later
}
/**
* Some work needs to be done prior to SPT code being loaded, registering the profile image + setting trader update time inside the trader config json
* @param container Dependency container
*/
public preAkiLoad(container: DependencyContainer): void
{
// Get a logger
this.logger = container.resolve<ILogger>("WinstonLogger");
// Get SPT code/data we need later
const preAkiModLoader: PreAkiModLoader = container.resolve<PreAkiModLoader>("PreAkiModLoader");
const imageRouter: ImageRouter = container.resolve<ImageRouter>("ImageRouter");
const hashUtil: HashUtil = container.resolve<HashUtil>("HashUtil");
const configServer = container.resolve<ConfigServer>("ConfigServer");
const traderConfig: ITraderConfig = configServer.getConfig<ITraderConfig>(ConfigTypes.TRADER);
const ragfairConfig = configServer.getConfig<IRagfairConfig>(ConfigTypes.RAGFAIR);
// 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, preAkiModLoader, imageRouter, "harry.jpg");
this.traderHelper.setTraderUpdateTime(traderConfig, baseJson, 3600, 4000);
// Add trader to trader enum
Traders[baseJson._id] = baseJson._id;
// Add trader to flea market
ragfairConfig.traders[baseJson._id] = true;
}
/**
* Majority of trader-related work occurs after the aki database has been loaded but prior to SPT code being run
* @param container Dependency container
*/
public postDBLoad(container: DependencyContainer): void
{
HideoutHarry.config = JSON.parse(fs.readFileSync(HideoutHarry.configPath, "utf-8"));
// Resolve SPT classes we'll use
const logger = container.resolve<ILogger>("WinstonLogger");
const databaseServer: DatabaseServer = container.resolve<DatabaseServer>("DatabaseServer");
const configServer: ConfigServer = container.resolve<ConfigServer>("ConfigServer");
const jsonUtil: JsonUtil = container.resolve<JsonUtil>("JsonUtil");
const priceTable = databaseServer.getTables().templates.prices;
const handbookTable = databaseServer.getTables().templates.handbook;
// Get a reference to the database tables
const tables = databaseServer.getTables();
// Add new trader to the trader dictionary in DatabaseServer - has no assorts (items) yet
this.traderHelper.addTraderToDb(baseJson, tables, jsonUtil);
const start = performance.now();
const itemList = JSON.parse(fs.readFileSync(HideoutHarry.itemsPath, "utf-8"));
const nonBarterItems = itemList.nonBarterItems;
const barterItems = itemList.barterItems;
const lowFleaRange = 0.85;
// Non-Barter Items Iteration
for (const item in nonBarterItems){
{
const itemID = nonBarterItems[item].itemID;
if (HideoutHarry.config.useFleaPrices)
{
let price = (priceTable[itemID] * HideoutHarry.config.itemPriceMultiplier);
if (!price)
{
price = (handbookTable.Items.find(x => x.Id === itemID)?.Price ?? 1) * HideoutHarry.config.itemPriceMultiplier;
}
this.fluentAssortCreator.createSingleAssortItem(itemID)
.addUnlimitedStackCount()
.addMoneyCost(Money.ROUBLES, Math.round(price * lowFleaRange))
.addLoyaltyLevel(1)
.export(tables.traders[baseJson._id])
if (HideoutHarry.config.enableConsoleDebug){
logger.log("ItemID: " + itemID + " for price: " + Math.round(price), "cyan");
}
}
else
{
const price = nonBarterItems[item].price
this.fluentAssortCreator.createSingleAssortItem(itemID)
.addUnlimitedStackCount()
.addMoneyCost(Money.ROUBLES, Math.round(price * lowFleaRange))
.addLoyaltyLevel(1)
.export(tables.traders[baseJson._id]);
if (HideoutHarry.config.enableConsoleDebug){
logger.log("ItemID: " + itemID + " for price: " + Math.round(price), "cyan");
}
}
}
}
// Barter Items Iteration
for (const item in barterItems){
{
const itemID = barterItems[item].itemID;
const barterItem = barterItems[item].barterItemID;
const barterAmount = barterItems[item].barterAmount;
if (HideoutHarry.config.useBarters)
{
this.fluentAssortCreator.createSingleAssortItem(itemID)
.addUnlimitedStackCount()
.addBarterCost(barterItem, barterAmount)
.addLoyaltyLevel(1)
.export(tables.traders[baseJson._id])
if (HideoutHarry.config.enableConsoleDebug){
logger.log("ItemID: " + itemID + " for barter: " + barterAmount + " "+ barterItem, "cyan");
}
}
else
{
const price = barterItems[item].price
this.fluentAssortCreator.createSingleAssortItem(itemID)
.addUnlimitedStackCount()
.addMoneyCost(Money.ROUBLES, Math.round(price))
.addLoyaltyLevel(1)
.export(tables.traders[baseJson._id]);
if (HideoutHarry.config.enableConsoleDebug){
logger.log("ItemID: " + itemID + " for price: " + Math.round(price), "cyan");
}
}
}
}
// 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, "Hideout Harry", baseJson.nickname, baseJson.location, "I'm sellin', what are you buyin'?");
this.logger.debug(`[${this.mod}] loaded... `);
const timeTaken = performance.now() - start;
logger.log(`[${this.mod}] Assort generation took ${timeTaken.toFixed(3)}ms.`, "green");
}
}
interface Config
{
useBarters: boolean,
itemPriceMultiplier: number,
useFleaPrices: boolean,
enableConsoleDebug: boolean,
}
module.exports = { mod: new HideoutHarry() }
@@ -0,0 +1,94 @@
"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
*/
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
*/
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
*/
// rome-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;
}
/**
* 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
*/
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
@@ -0,0 +1 @@
{"version":3,"file":"traderHelpers.js","sourceRoot":"","sources":["traderHelpers.ts"],"names":[],"mappings":";;;AAOA,MAAa,YAAY;IAErB;;;;;;OAMG;IACI,oBAAoB,CAAC,QAAa,EAAE,OAAe,EAAE,eAAgC,EAAE,WAAwB,EAAE,eAAuB;QAE3I,iCAAiC;QACjC,MAAM,aAAa,GAAG,KAAK,eAAe,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC;QAEpE,yFAAyF;QACzF,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,GAAG,aAAa,IAAI,eAAe,EAAE,CAAC,CAAC;IACrG,CAAC;IAED;;;;;;OAMG;IACI,mBAAmB,CAAC,YAA2B,EAAE,QAAa,EAAE,qBAA6B,EAAE,qBAA6B;QAE/H,wCAAwC;QACxC,MAAM,mBAAmB,GAAe;YACpC,QAAQ,EAAE,QAAQ,CAAC,GAAG;YACtB,OAAO,EAAE;gBACL,GAAG,EAAE,qBAAqB;gBAC1B,GAAG,EAAE,qBAAqB;aAC7B;SAAE,CAAC;QAER,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACtD,CAAC;IAED;;;;;OAKG;IACH,iGAAiG;IAC1F,aAAa,CAAC,kBAAuB,EAAE,MAAuB,EAAE,QAAkB;QAErF,oDAAoD;QACpD,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,GAAG,CAAC,GAAG;YACrC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,EAAE,mIAAmI;YACrK,IAAI,EAAE,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAgB,EAAE,8FAA8F;YACjL,WAAW,EAAE;gBACT,OAAO,EAAE,EAAE;gBACX,OAAO,EAAE,EAAE;gBACX,IAAI,EAAE,EAAE;aACX,CAAC,mEAAmE;SACxE,CAAC;IACN,CAAC;IAED;;;;;OAKG;IACK,iBAAiB;QAErB,0DAA0D;QAC1D,MAAM,WAAW,GAAkB;YAC/B,YAAY,EAAE,CAAC;YACf,KAAK,EAAE,EAAE;YACT,aAAa,EAAE,EAAE;YACjB,iBAAiB,EAAE,EAAE;SACxB,CAAA;QAED,OAAO,WAAW,CAAC;IACvB,CAAC;IAED;;;;;;;;;OASG;IACI,kBAAkB,CAAC,QAAa,EAAE,MAAuB,EAAE,QAAgB,EAAE,SAAiB,EAAE,QAAgB,EAAE,QAAgB,EAAE,WAAmB;QAE1J,mDAAmD;QACnD,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAA6B,CAAC;QACjF,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,WAAW,CAAC,GAAG,QAAQ,CAAC;YAC9C,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,YAAY,CAAC,GAAG,SAAS,CAAC;YAChD,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,WAAW,CAAC,GAAG,QAAQ,CAAC;YAC9C,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,WAAW,CAAC,GAAG,QAAQ,CAAC;YAC9C,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,cAAc,CAAC,GAAG,WAAW,CAAC;QACxD,CAAC;IACL,CAAC;CACJ;AApGD,oCAoGC"}
@@ -0,0 +1,108 @@
import { PreAkiModLoader } from "@spt-aki/loaders/PreAkiModLoader";
import { ITraderBase, ITraderAssort } from "@spt-aki/models/eft/common/tables/ITrader";
import { ITraderConfig, UpdateTime } from "@spt-aki/models/spt/config/ITraderConfig";
import { IDatabaseTables } from "@spt-aki/models/spt/server/IDatabaseTables";
import { ImageRouter } from "@spt-aki/routers/ImageRouter";
import { JsonUtil } from "@spt-aki/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
*/
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
*/
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
*/
// rome-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;
}
/**
* 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
*/
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<string, string>[];
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;
}
}
}