Formatting for helper classes.

This commit is contained in:
Refringe 2023-11-13 11:07:59 -05:00
parent d3e5418fc8
commit 8586447d21
No known key found for this signature in database
GPG Key ID: 64E03E5F892C6F9E
37 changed files with 1533 additions and 840 deletions

View File

@ -12,25 +12,30 @@ import { LocalisationService } from "@spt-aki/services/LocalisationService";
@injectable() @injectable()
export class AssortHelper export class AssortHelper
{ {
constructor( constructor(
@inject("WinstonLogger") protected logger: ILogger, @inject("WinstonLogger") protected logger: ILogger,
@inject("ItemHelper") protected itemHelper: ItemHelper, @inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("QuestHelper") protected questHelper: QuestHelper @inject("QuestHelper") protected questHelper: QuestHelper,
) )
{ } {}
/** /**
* Remove assorts from a trader that have not been unlocked yet (via player completing corrisponding quest) * Remove assorts from a trader that have not been unlocked yet (via player completing corresponding quest)
* @param pmcProfile Player profile * @param pmcProfile Player profile
* @param traderId Traders id the assort belongs to * @param traderId Traders id the assort belongs to
* @param traderAssorts All assort items from same trader * @param traderAssorts All assort items from same trader
* @param mergedQuestAssorts Dict of quest assort to quest id unlocks for all traders (key = started/failed/complete) * @param mergedQuestAssorts Dict of quest assort to quest id unlocks for all traders (key = started/failed/complete)
* @returns Assort items minus locked quest assorts * @returns Assort items minus locked quest assorts
*/ */
public stripLockedQuestAssort(pmcProfile: IPmcData, traderId: string, traderAssorts: ITraderAssort, mergedQuestAssorts: Record<string, Record<string, string>>, flea = false): ITraderAssort public stripLockedQuestAssort(
pmcProfile: IPmcData,
traderId: string,
traderAssorts: ITraderAssort,
mergedQuestAssorts: Record<string, Record<string, string>>,
flea = false,
): ITraderAssort
{ {
// Trader assort does not always contain loyal_level_items // Trader assort does not always contain loyal_level_items
if (!traderAssorts.loyal_level_items) if (!traderAssorts.loyal_level_items)
@ -67,20 +72,26 @@ export class AssortHelper
* @param assortId Assort to look for linked quest id * @param assortId Assort to look for linked quest id
* @returns quest id + array of quest status the assort should show for * @returns quest id + array of quest status the assort should show for
*/ */
protected getQuestIdAndStatusThatShowAssort(mergedQuestAssorts: Record<string, Record<string, string>>, assortId: string): { questId: string; status: QuestStatus[]; } protected getQuestIdAndStatusThatShowAssort(
mergedQuestAssorts: Record<string, Record<string, string>>,
assortId: string,
): {questId: string; status: QuestStatus[];}
{ {
if (assortId in mergedQuestAssorts.started) if (assortId in mergedQuestAssorts.started)
{ {
// Assort unlocked by starting quest, assort is visible to player when : started or ready to hand in + handed in // Assort unlocked by starting quest, assort is visible to player when : started or ready to hand in + handed in
return { questId: mergedQuestAssorts.started[assortId], status: [QuestStatus.Started, QuestStatus.AvailableForFinish, QuestStatus.Success]}; return {
questId: mergedQuestAssorts.started[assortId],
status: [QuestStatus.Started, QuestStatus.AvailableForFinish, QuestStatus.Success],
};
} }
else if (assortId in mergedQuestAssorts.success) else if (assortId in mergedQuestAssorts.success)
{ {
return { questId: mergedQuestAssorts.success[assortId], status: [QuestStatus.Success]}; return {questId: mergedQuestAssorts.success[assortId], status: [QuestStatus.Success]};
} }
else if (assortId in mergedQuestAssorts.fail) else if (assortId in mergedQuestAssorts.fail)
{ {
return { questId: mergedQuestAssorts.fail[assortId], status: [QuestStatus.Fail]}; return {questId: mergedQuestAssorts.fail[assortId], status: [QuestStatus.Fail]};
} }
return undefined; return undefined;
@ -152,4 +163,4 @@ export class AssortHelper
return assort; return assort;
} }
} }

View File

@ -23,22 +23,27 @@ export class BotDifficultyHelper
@inject("RandomUtil") protected randomUtil: RandomUtil, @inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("BotHelper") protected botHelper: BotHelper, @inject("BotHelper") protected botHelper: BotHelper,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC); this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
} }
public getPmcDifficultySettings(pmcType: "bear"|"usec", difficulty: string, usecType: string, bearType: string): Difficulty public getPmcDifficultySettings(
pmcType: "bear" | "usec",
difficulty: string,
usecType: string,
bearType: string,
): Difficulty
{ {
const difficultySettings = this.getDifficultySettings(pmcType, difficulty); const difficultySettings = this.getDifficultySettings(pmcType, difficulty);
const friendlyType = pmcType === "bear" const friendlyType = pmcType === "bear" ?
? bearType bearType :
: usecType; usecType;
const enemyType = pmcType === "bear" const enemyType = pmcType === "bear" ?
? usecType usecType :
: bearType; bearType;
this.botHelper.addBotToEnemyList(difficultySettings, this.pmcConfig.enemyTypes, friendlyType); // Add generic bot types to enemy list this.botHelper.addBotToEnemyList(difficultySettings, this.pmcConfig.enemyTypes, friendlyType); // Add generic bot types to enemy list
this.botHelper.addBotToEnemyList(difficultySettings, [enemyType, friendlyType], ""); // add same/opposite side to enemy list this.botHelper.addBotToEnemyList(difficultySettings, [enemyType, friendlyType], ""); // add same/opposite side to enemy list
@ -61,14 +66,23 @@ export class BotDifficultyHelper
{ {
// get fallback // get fallback
this.logger.warning(this.localisationService.getText("bot-unable_to_get_bot_fallback_to_assault", type)); this.logger.warning(this.localisationService.getText("bot-unable_to_get_bot_fallback_to_assault", type));
this.databaseServer.getTables().bots.types[type] = this.jsonUtil.clone(this.databaseServer.getTables().bots.types.assault); this.databaseServer.getTables().bots.types[type] = this.jsonUtil.clone(
this.databaseServer.getTables().bots.types.assault,
);
} }
const difficultySettings = this.botHelper.getBotTemplate(type).difficulty[difficulty]; const difficultySettings = this.botHelper.getBotTemplate(type).difficulty[difficulty];
if (!difficultySettings) if (!difficultySettings)
{ {
this.logger.warning(this.localisationService.getText("bot-unable_to_get_bot_difficulty_fallback_to_assault", {botType: type, difficulty: difficulty})); this.logger.warning(
this.databaseServer.getTables().bots.types[type].difficulty[difficulty] = this.jsonUtil.clone(this.databaseServer.getTables().bots.types.assault.difficulty[difficulty]); this.localisationService.getText("bot-unable_to_get_bot_difficulty_fallback_to_assault", {
botType: type,
difficulty: difficulty,
}),
);
this.databaseServer.getTables().bots.types[type].difficulty[difficulty] = this.jsonUtil.clone(
this.databaseServer.getTables().bots.types.assault.difficulty[difficulty],
);
} }
return this.jsonUtil.clone(difficultySettings); return this.jsonUtil.clone(difficultySettings);
@ -77,14 +91,14 @@ export class BotDifficultyHelper
/** /**
* Get difficulty settings for a PMC * Get difficulty settings for a PMC
* @param type "usec" / "bear" * @param type "usec" / "bear"
* @param difficulty what difficulty to retrieve * @param difficulty what difficulty to retrieve
* @returns Difficulty object * @returns Difficulty object
*/ */
protected getDifficultySettings(type: string, difficulty: string): Difficulty protected getDifficultySettings(type: string, difficulty: string): Difficulty
{ {
let difficultySetting = this.pmcConfig.difficulty.toLowerCase() === "asonline" let difficultySetting = this.pmcConfig.difficulty.toLowerCase() === "asonline" ?
? difficulty difficulty :
: this.pmcConfig.difficulty.toLowerCase(); this.pmcConfig.difficulty.toLowerCase();
difficultySetting = this.convertBotDifficultyDropdownToBotDifficulty(difficultySetting); difficultySetting = this.convertBotDifficultyDropdownToBotDifficulty(difficultySetting);
@ -117,4 +131,4 @@ export class BotDifficultyHelper
{ {
return this.randomUtil.getArrayValue(["easy", "normal", "hard", "impossible"]); return this.randomUtil.getArrayValue(["easy", "normal", "hard", "impossible"]);
} }
} }

View File

@ -19,7 +19,7 @@ import { JsonUtil } from "@spt-aki/utils/JsonUtil";
import { RandomUtil } from "@spt-aki/utils/RandomUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil";
@injectable() @injectable()
export class BotGeneratorHelper export class BotGeneratorHelper
{ {
protected botConfig: IBotConfig; protected botConfig: IBotConfig;
protected pmcConfig: IPmcConfig; protected pmcConfig: IPmcConfig;
@ -32,8 +32,8 @@ export class BotGeneratorHelper
@inject("ItemHelper") protected itemHelper: ItemHelper, @inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("ApplicationContext") protected applicationContext: ApplicationContext, @inject("ApplicationContext") protected applicationContext: ApplicationContext,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT); this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC); this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
@ -46,93 +46,113 @@ export class BotGeneratorHelper
* @param botRole Used by weapons to randomize the durability values. Null for non-equipped items * @param botRole Used by weapons to randomize the durability values. Null for non-equipped items
* @returns Item Upd object with extra properties * @returns Item Upd object with extra properties
*/ */
public generateExtraPropertiesForItem(itemTemplate: ITemplateItem, botRole?: string): { upd?: Upd } public generateExtraPropertiesForItem(itemTemplate: ITemplateItem, botRole?: string): {upd?: Upd;}
{ {
// Get raid settings, if no raid, default to day // Get raid settings, if no raid, default to day
const raidSettings = this.applicationContext.getLatestValue(ContextVariableType.RAID_CONFIGURATION)?.getValue<IGetRaidConfigurationRequestData>(); const raidSettings = this.applicationContext.getLatestValue(ContextVariableType.RAID_CONFIGURATION)?.getValue<
IGetRaidConfigurationRequestData
>();
const raidIsNight = raidSettings?.timeVariant === "PAST"; const raidIsNight = raidSettings?.timeVariant === "PAST";
const itemProperties: Upd = {}; const itemProperties: Upd = {};
if (itemTemplate._props.MaxDurability) if (itemTemplate._props.MaxDurability)
{ {
if (itemTemplate._props.weapClass) // Is weapon if (itemTemplate._props.weapClass)
{ { // Is weapon
itemProperties.Repairable = this.generateWeaponRepairableProperties(itemTemplate, botRole); itemProperties.Repairable = this.generateWeaponRepairableProperties(itemTemplate, botRole);
} }
else if (itemTemplate._props.armorClass) // Is armor else if (itemTemplate._props.armorClass)
{ { // Is armor
itemProperties.Repairable = this.generateArmorRepairableProperties(itemTemplate, botRole); itemProperties.Repairable = this.generateArmorRepairableProperties(itemTemplate, botRole);
} }
} }
if (itemTemplate._props.HasHinge) if (itemTemplate._props.HasHinge)
{ {
itemProperties.Togglable = { On: true }; itemProperties.Togglable = {On: true};
} }
if (itemTemplate._props.Foldable) if (itemTemplate._props.Foldable)
{ {
itemProperties.Foldable = { Folded: false }; itemProperties.Foldable = {Folded: false};
} }
if (itemTemplate._props.weapFireType?.length) if (itemTemplate._props.weapFireType?.length)
{ {
if (itemTemplate._props.weapFireType.includes("fullauto")) if (itemTemplate._props.weapFireType.includes("fullauto"))
{ {
itemProperties.FireMode = { FireMode: "fullauto" }; itemProperties.FireMode = {FireMode: "fullauto"};
} }
else else
{ {
itemProperties.FireMode = { FireMode: this.randomUtil.getArrayValue(itemTemplate._props.weapFireType) }; itemProperties.FireMode = {FireMode: this.randomUtil.getArrayValue(itemTemplate._props.weapFireType)};
} }
} }
if (itemTemplate._props.MaxHpResource) if (itemTemplate._props.MaxHpResource)
{ {
itemProperties.MedKit = { HpResource: this.getRandomizedResourceValue(itemTemplate._props.MaxHpResource, this.botConfig.lootItemResourceRandomization[botRole]?.meds) }; itemProperties.MedKit = {
HpResource: this.getRandomizedResourceValue(
itemTemplate._props.MaxHpResource,
this.botConfig.lootItemResourceRandomization[botRole]?.meds,
),
};
} }
if (itemTemplate._props.MaxResource && itemTemplate._props.foodUseTime) if (itemTemplate._props.MaxResource && itemTemplate._props.foodUseTime)
{ {
itemProperties.FoodDrink = { HpPercent: this.getRandomizedResourceValue(itemTemplate._props.MaxResource, this.botConfig.lootItemResourceRandomization[botRole]?.food) }; itemProperties.FoodDrink = {
HpPercent: this.getRandomizedResourceValue(
itemTemplate._props.MaxResource,
this.botConfig.lootItemResourceRandomization[botRole]?.food,
),
};
} }
if (itemTemplate._parent === BaseClasses.FLASHLIGHT) if (itemTemplate._parent === BaseClasses.FLASHLIGHT)
{ {
// Get chance from botconfig for bot type // Get chance from botconfig for bot type
const lightLaserActiveChance = raidIsNight const lightLaserActiveChance = raidIsNight ?
? this.getBotEquipmentSettingFromConfig(botRole, "lightIsActiveNightChancePercent", 50) this.getBotEquipmentSettingFromConfig(botRole, "lightIsActiveNightChancePercent", 50) :
: this.getBotEquipmentSettingFromConfig(botRole, "lightIsActiveDayChancePercent", 25); this.getBotEquipmentSettingFromConfig(botRole, "lightIsActiveDayChancePercent", 25);
itemProperties.Light = { IsActive: (this.randomUtil.getChance100(lightLaserActiveChance)), SelectedMode: 0 }; itemProperties.Light = {IsActive: (this.randomUtil.getChance100(lightLaserActiveChance)), SelectedMode: 0};
} }
else if (itemTemplate._parent === BaseClasses.TACTICAL_COMBO) else if (itemTemplate._parent === BaseClasses.TACTICAL_COMBO)
{ {
// Get chance from botconfig for bot type, use 50% if no value found // Get chance from botconfig for bot type, use 50% if no value found
const lightLaserActiveChance = this.getBotEquipmentSettingFromConfig(botRole, "laserIsActiveChancePercent", 50); const lightLaserActiveChance = this.getBotEquipmentSettingFromConfig(
itemProperties.Light = { IsActive: (this.randomUtil.getChance100(lightLaserActiveChance)), SelectedMode: 0 }; botRole,
"laserIsActiveChancePercent",
50,
);
itemProperties.Light = {IsActive: (this.randomUtil.getChance100(lightLaserActiveChance)), SelectedMode: 0};
} }
if (itemTemplate._parent === BaseClasses.NIGHTVISION) if (itemTemplate._parent === BaseClasses.NIGHTVISION)
{ {
// Get chance from botconfig for bot type // Get chance from botconfig for bot type
const nvgActiveChance = raidIsNight const nvgActiveChance = raidIsNight ?
? this.getBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChanceNightPercent", 90) this.getBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChanceNightPercent", 90) :
: this.getBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChanceDayPercent", 15); this.getBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChanceDayPercent", 15);
itemProperties.Togglable = { On: (this.randomUtil.getChance100(nvgActiveChance)) }; itemProperties.Togglable = {On: (this.randomUtil.getChance100(nvgActiveChance))};
} }
// Togglable face shield // Togglable face shield
if (itemTemplate._props.HasHinge && itemTemplate._props.FaceShieldComponent) if (itemTemplate._props.HasHinge && itemTemplate._props.FaceShieldComponent)
{ {
// Get chance from botconfig for bot type, use 75% if no value found // Get chance from botconfig for bot type, use 75% if no value found
const faceShieldActiveChance = this.getBotEquipmentSettingFromConfig(botRole, "faceShieldIsActiveChancePercent", 75); const faceShieldActiveChance = this.getBotEquipmentSettingFromConfig(
itemProperties.Togglable = { On: (this.randomUtil.getChance100(faceShieldActiveChance)) }; botRole,
"faceShieldIsActiveChancePercent",
75,
);
itemProperties.Togglable = {On: (this.randomUtil.getChance100(faceShieldActiveChance))};
} }
return Object.keys(itemProperties).length return Object.keys(itemProperties).length ?
? { upd: itemProperties } {upd: itemProperties} :
: {}; {};
} }
/** /**
@ -153,8 +173,10 @@ export class BotGeneratorHelper
return maxResource; return maxResource;
} }
return this.randomUtil.getInt(this.randomUtil.getPercentOfValue(randomizationValues.resourcePercent, maxResource, 0), maxResource); return this.randomUtil.getInt(
this.randomUtil.getPercentOfValue(randomizationValues.resourcePercent, maxResource, 0),
maxResource,
);
} }
/** /**
@ -164,22 +186,38 @@ export class BotGeneratorHelper
* @param defaultValue default value for the chance of activation if the botrole or bot equipment role is null * @param defaultValue default value for the chance of activation if the botrole or bot equipment role is null
* @returns Percent chance to be active * @returns Percent chance to be active
*/ */
protected getBotEquipmentSettingFromConfig(botRole: string, setting: keyof EquipmentFilters, defaultValue: number): number protected getBotEquipmentSettingFromConfig(
botRole: string,
setting: keyof EquipmentFilters,
defaultValue: number,
): number
{ {
if (!botRole) if (!botRole)
{ {
return defaultValue; return defaultValue;
} }
const botEquipmentSettings = this.botConfig.equipment[this.getBotEquipmentRole(botRole)]; const botEquipmentSettings = this.botConfig.equipment[this.getBotEquipmentRole(botRole)];
if (!botEquipmentSettings) if (!botEquipmentSettings)
{ {
this.logger.warning(this.localisationService.getText("bot-missing_equipment_settings", {botRole: botRole, setting: setting, defaultValue: defaultValue})); this.logger.warning(
this.localisationService.getText("bot-missing_equipment_settings", {
botRole: botRole,
setting: setting,
defaultValue: defaultValue,
}),
);
return defaultValue; return defaultValue;
} }
if (botEquipmentSettings[setting] === undefined || typeof botEquipmentSettings[setting] !== "number") if (botEquipmentSettings[setting] === undefined || typeof botEquipmentSettings[setting] !== "number")
{ {
this.logger.warning(this.localisationService.getText("bot-missing_equipment_settings_property", {botRole: botRole, setting: setting, defaultValue: defaultValue})); this.logger.warning(
this.localisationService.getText("bot-missing_equipment_settings_property", {
botRole: botRole,
setting: setting,
defaultValue: defaultValue,
}),
);
return defaultValue; return defaultValue;
} }
@ -193,14 +231,18 @@ export class BotGeneratorHelper
* @param botRole type of bot being generated for * @param botRole type of bot being generated for
* @returns Repairable object * @returns Repairable object
*/ */
protected generateWeaponRepairableProperties(itemTemplate: ITemplateItem, botRole: string): Repairable protected generateWeaponRepairableProperties(itemTemplate: ITemplateItem, botRole: string): Repairable
{ {
const maxDurability = this.durabilityLimitsHelper.getRandomizedMaxWeaponDurability(itemTemplate, botRole); const maxDurability = this.durabilityLimitsHelper.getRandomizedMaxWeaponDurability(itemTemplate, botRole);
const currentDurability = this.durabilityLimitsHelper.getRandomizedWeaponDurability(itemTemplate, botRole, maxDurability); const currentDurability = this.durabilityLimitsHelper.getRandomizedWeaponDurability(
itemTemplate,
botRole,
maxDurability,
);
return { return {
Durability: currentDurability, Durability: currentDurability,
MaxDurability: maxDurability MaxDurability: maxDurability,
}; };
} }
@ -210,7 +252,7 @@ export class BotGeneratorHelper
* @param botRole type of bot being generated for * @param botRole type of bot being generated for
* @returns Repairable object * @returns Repairable object
*/ */
protected generateArmorRepairableProperties(itemTemplate: ITemplateItem, botRole: string): Repairable protected generateArmorRepairableProperties(itemTemplate: ITemplateItem, botRole: string): Repairable
{ {
let maxDurability: number; let maxDurability: number;
let currentDurability: number; let currentDurability: number;
@ -219,15 +261,19 @@ export class BotGeneratorHelper
maxDurability = itemTemplate._props.MaxDurability; maxDurability = itemTemplate._props.MaxDurability;
currentDurability = itemTemplate._props.MaxDurability; currentDurability = itemTemplate._props.MaxDurability;
} }
else else
{ {
maxDurability = this.durabilityLimitsHelper.getRandomizedMaxArmorDurability(itemTemplate, botRole); maxDurability = this.durabilityLimitsHelper.getRandomizedMaxArmorDurability(itemTemplate, botRole);
currentDurability = this.durabilityLimitsHelper.getRandomizedArmorDurability(itemTemplate, botRole, maxDurability); currentDurability = this.durabilityLimitsHelper.getRandomizedArmorDurability(
itemTemplate,
botRole,
maxDurability,
);
} }
return { return {
Durability: currentDurability, Durability: currentDurability,
MaxDurability: maxDurability MaxDurability: maxDurability,
}; };
} }
@ -238,54 +284,81 @@ export class BotGeneratorHelper
* @param equipmentSlot Slot the item will be placed into * @param equipmentSlot Slot the item will be placed into
* @returns false if no incompatibilities, also has incompatibility reason * @returns false if no incompatibilities, also has incompatibility reason
*/ */
public isItemIncompatibleWithCurrentItems(items: Item[], tplToCheck: string, equipmentSlot: string): { incompatible: boolean, reason: string } public isItemIncompatibleWithCurrentItems(
items: Item[],
tplToCheck: string,
equipmentSlot: string,
): {incompatible: boolean; reason: string;}
{ {
// Skip slots that have no incompatibilities // Skip slots that have no incompatibilities
if (["Scabbard", "Backpack", "SecureContainer", "Holster", "ArmBand"].includes(equipmentSlot)) if (["Scabbard", "Backpack", "SecureContainer", "Holster", "ArmBand"].includes(equipmentSlot))
{ {
return { incompatible: false, reason: "" }; return {incompatible: false, reason: ""};
} }
// TODO: Can probably be optimized to cache itemTemplates as items are added to inventory // TODO: Can probably be optimized to cache itemTemplates as items are added to inventory
const equippedItems = items.map(i => this.databaseServer.getTables().templates.items[i._tpl]); const equippedItems = items.map((i) => this.databaseServer.getTables().templates.items[i._tpl]);
const item = this.itemHelper.getItem(tplToCheck); const item = this.itemHelper.getItem(tplToCheck);
const itemToCheck = item[1]; const itemToCheck = item[1];
if (!item[0]) if (!item[0])
{ {
this.logger.warning(this.localisationService.getText("bot-invalid_item_compatibility_check", {itemTpl: tplToCheck, slot: equipmentSlot})); this.logger.warning(
this.localisationService.getText("bot-invalid_item_compatibility_check", {
itemTpl: tplToCheck,
slot: equipmentSlot,
}),
);
} }
if (!itemToCheck._props) if (!itemToCheck._props)
{ {
this.logger.warning(this.localisationService.getText("bot-compatibility_check_missing_props", {id: itemToCheck._id, name: itemToCheck._name, slot: equipmentSlot})); this.logger.warning(
this.localisationService.getText("bot-compatibility_check_missing_props", {
id: itemToCheck._id,
name: itemToCheck._name,
slot: equipmentSlot,
}),
);
} }
// Does an equipped item have a property that blocks the desired item - check for prop "BlocksX" .e.g BlocksEarpiece / BlocksFaceCover // Does an equipped item have a property that blocks the desired item - check for prop "BlocksX" .e.g BlocksEarpiece / BlocksFaceCover
let blockingItem = equippedItems.find(x => x._props[`Blocks${equipmentSlot}`]); let blockingItem = equippedItems.find((x) => x._props[`Blocks${equipmentSlot}`]);
if (blockingItem) if (blockingItem)
{ {
//this.logger.warning(`1 incompatibility found between - ${itemToEquip[1]._name} and ${blockingItem._name} - ${equipmentSlot}`); // this.logger.warning(`1 incompatibility found between - ${itemToEquip[1]._name} and ${blockingItem._name} - ${equipmentSlot}`);
return { incompatible: true, reason: `${tplToCheck} ${itemToCheck._name} in slot: ${equipmentSlot} blocked by: ${blockingItem._id} ${blockingItem._name}` }; return {
incompatible: true,
reason:
`${tplToCheck} ${itemToCheck._name} in slot: ${equipmentSlot} blocked by: ${blockingItem._id} ${blockingItem._name}`,
};
} }
// Check if any of the current inventory templates have the incoming item defined as incompatible // Check if any of the current inventory templates have the incoming item defined as incompatible
blockingItem = equippedItems.find(x => x._props.ConflictingItems?.includes(tplToCheck)); blockingItem = equippedItems.find((x) => x._props.ConflictingItems?.includes(tplToCheck));
if (blockingItem) if (blockingItem)
{ {
//this.logger.warning(`2 incompatibility found between - ${itemToEquip[1]._name} and ${blockingItem._props.Name} - ${equipmentSlot}`); // this.logger.warning(`2 incompatibility found between - ${itemToEquip[1]._name} and ${blockingItem._props.Name} - ${equipmentSlot}`);
return { incompatible: true, reason: `${tplToCheck} ${itemToCheck._name} in slot: ${equipmentSlot} blocked by: ${blockingItem._id} ${blockingItem._name}` }; return {
incompatible: true,
reason:
`${tplToCheck} ${itemToCheck._name} in slot: ${equipmentSlot} blocked by: ${blockingItem._id} ${blockingItem._name}`,
};
} }
// Check if the incoming item has any inventory items defined as incompatible // Check if the incoming item has any inventory items defined as incompatible
const blockingInventoryItem = items.find(x => itemToCheck._props.ConflictingItems?.includes(x._tpl)); const blockingInventoryItem = items.find((x) => itemToCheck._props.ConflictingItems?.includes(x._tpl));
if (blockingInventoryItem) if (blockingInventoryItem)
{ {
//this.logger.warning(`3 incompatibility found between - ${itemToEquip[1]._name} and ${blockingInventoryItem._tpl} - ${equipmentSlot}`) // this.logger.warning(`3 incompatibility found between - ${itemToEquip[1]._name} and ${blockingInventoryItem._tpl} - ${equipmentSlot}`)
return { incompatible: true, reason: `${tplToCheck} blocks existing item ${blockingInventoryItem._tpl} in slot ${blockingInventoryItem.slotId}` }; return {
incompatible: true,
reason:
`${tplToCheck} blocks existing item ${blockingInventoryItem._tpl} in slot ${blockingInventoryItem.slotId}`,
};
} }
return { incompatible: false, reason: "" }; return {incompatible: false, reason: ""};
} }
/** /**
@ -293,11 +366,13 @@ export class BotGeneratorHelper
* @param botRole Role to convert * @param botRole Role to convert
* @returns Equipment role (e.g. pmc / assault / bossTagilla) * @returns Equipment role (e.g. pmc / assault / bossTagilla)
*/ */
public getBotEquipmentRole(botRole: string): string public getBotEquipmentRole(botRole: string): string
{ {
return ([this.pmcConfig.usecType.toLowerCase(), this.pmcConfig.bearType.toLowerCase()].includes(botRole.toLowerCase())) return ([this.pmcConfig.usecType.toLowerCase(), this.pmcConfig.bearType.toLowerCase()].includes(
? "pmc" botRole.toLowerCase(),
: botRole; )) ?
"pmc" :
botRole;
} }
} }
@ -309,15 +384,15 @@ export class ExhaustableArray<T>
constructor( constructor(
private itemPool: T[], private itemPool: T[],
private randomUtil: RandomUtil, private randomUtil: RandomUtil,
private jsonUtil: JsonUtil private jsonUtil: JsonUtil,
) )
{ {
this.pool = this.jsonUtil.clone(itemPool); this.pool = this.jsonUtil.clone(itemPool);
} }
public getRandomValue(): T public getRandomValue(): T
{ {
if (!this.pool?.length) if (!this.pool?.length)
{ {
return null; return null;
} }
@ -328,9 +403,9 @@ export class ExhaustableArray<T>
return toReturn; return toReturn;
} }
public getFirstValue(): T public getFirstValue(): T
{ {
if (!this.pool?.length) if (!this.pool?.length)
{ {
return null; return null;
} }
@ -340,13 +415,13 @@ export class ExhaustableArray<T>
return toReturn; return toReturn;
} }
public hasValues(): boolean public hasValues(): boolean
{ {
if (this.pool?.length) if (this.pool?.length)
{ {
return true; return true;
} }
return false; return false;
} }
} }

View File

@ -24,15 +24,13 @@ export class BotHelper
@inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("RandomUtil") protected randomUtil: RandomUtil, @inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT); this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC); this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
} }
/** /**
* Get a template object for the specified botRole from bots.types db * Get a template object for the specified botRole from bots.types db
* @param role botRole to get template for * @param role botRole to get template for
@ -71,7 +69,7 @@ export class BotHelper
public isBotBoss(botRole: string): boolean public isBotBoss(botRole: string): boolean
{ {
return this.botConfig.bosses.some(x => x.toLowerCase() === botRole?.toLowerCase()); return this.botConfig.bosses.some((x) => x.toLowerCase() === botRole?.toLowerCase());
} }
public isBotFollower(botRole: string): boolean public isBotFollower(botRole: string): boolean
@ -187,13 +185,15 @@ export class BotHelper
public rollChanceToBePmc(role: string, botConvertMinMax: MinMax): boolean public rollChanceToBePmc(role: string, botConvertMinMax: MinMax): boolean
{ {
return role.toLowerCase() in this.pmcConfig.convertIntoPmcChance return role.toLowerCase() in this.pmcConfig.convertIntoPmcChance &&
&& this.randomUtil.getChance100(this.randomUtil.getInt(botConvertMinMax.min, botConvertMinMax.max)); this.randomUtil.getChance100(this.randomUtil.getInt(botConvertMinMax.min, botConvertMinMax.max));
} }
public botRoleIsPmc(botRole: string): boolean public botRoleIsPmc(botRole: string): boolean
{ {
return [this.pmcConfig.usecType.toLowerCase(), this.pmcConfig.bearType.toLowerCase()].includes(botRole.toLowerCase()); return [this.pmcConfig.usecType.toLowerCase(), this.pmcConfig.bearType.toLowerCase()].includes(
botRole.toLowerCase(),
);
} }
/** /**
@ -209,8 +209,8 @@ export class BotHelper
{ {
return null; return null;
} }
return botEquipConfig.randomisation.find(x => botLevel >= x.levelRange.min && botLevel <= x.levelRange.max); return botEquipConfig.randomisation.find((x) => botLevel >= x.levelRange.min && botLevel <= x.levelRange.max);
} }
/** /**
@ -219,9 +219,9 @@ export class BotHelper
*/ */
public getRandomizedPmcRole(): string public getRandomizedPmcRole(): string
{ {
return (this.randomUtil.getChance100(this.pmcConfig.isUsec)) return (this.randomUtil.getChance100(this.pmcConfig.isUsec)) ?
? this.pmcConfig.usecType this.pmcConfig.usecType :
: this.pmcConfig.bearType; this.pmcConfig.bearType;
} }
/** /**
@ -248,8 +248,8 @@ export class BotHelper
*/ */
protected getRandomizedPmcSide(): string protected getRandomizedPmcSide(): string
{ {
return (this.randomUtil.getChance100(this.pmcConfig.isUsec)) return (this.randomUtil.getChance100(this.pmcConfig.isUsec)) ?
? "Usec" "Usec" :
: "Bear"; "Bear";
} }
} }

View File

@ -29,9 +29,9 @@ export class BotWeaponGeneratorHelper
@inject("InventoryHelper") protected inventoryHelper: InventoryHelper, @inject("InventoryHelper") protected inventoryHelper: InventoryHelper,
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper, @inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("ContainerHelper") protected containerHelper: ContainerHelper @inject("ContainerHelper") protected containerHelper: ContainerHelper,
) )
{ } {}
/** /**
* Get a randomized number of bullets for a specific magazine * Get a randomized number of bullets for a specific magazine
@ -61,7 +61,7 @@ export class BotWeaponGeneratorHelper
/* Get the amount of bullets that would fit in the internal magazine /* Get the amount of bullets that would fit in the internal magazine
* and multiply by how many magazines were supposed to be created */ * and multiply by how many magazines were supposed to be created */
return chamberBulletCount * randomizedMagazineCount; return chamberBulletCount * randomizedMagazineCount;
} }
/** /**
@ -71,8 +71,8 @@ export class BotWeaponGeneratorHelper
*/ */
public getRandomizedMagazineCount(magCounts: GenerationData): number public getRandomizedMagazineCount(magCounts: GenerationData): number
{ {
//const range = magCounts.max - magCounts.min; // const range = magCounts.max - magCounts.min;
//return this.randomUtil.getBiasedRandomNumber(magCounts.min, magCounts.max, Math.round(range * 0.75), 4); // return this.randomUtil.getBiasedRandomNumber(magCounts.min, magCounts.max, Math.round(range * 0.75), 4);
return this.weightedRandomHelper.getWeightedValue(magCounts.weights); return this.weightedRandomHelper.getWeightedValue(magCounts.weights);
} }
@ -98,7 +98,7 @@ export class BotWeaponGeneratorHelper
{ {
const magazine: Item[] = [{ const magazine: Item[] = [{
_id: this.hashUtil.generate(), _id: this.hashUtil.generate(),
_tpl: magazineTpl _tpl: magazineTpl,
}]; }];
this.itemHelper.fillMagazineWithCartridge(magazine, magTemplate, ammoTpl, 1); this.itemHelper.fillMagazineWithCartridge(magazine, magTemplate, ammoTpl, 1);
@ -113,12 +113,17 @@ export class BotWeaponGeneratorHelper
* @param inventory bot inventory to add cartridges to * @param inventory bot inventory to add cartridges to
* @param equipmentSlotsToAddTo what equipment slots should bullets be added into * @param equipmentSlotsToAddTo what equipment slots should bullets be added into
*/ */
public addAmmoIntoEquipmentSlots(ammoTpl: string, cartridgeCount: number, inventory: Inventory, equipmentSlotsToAddTo: EquipmentSlots[] = [EquipmentSlots.TACTICAL_VEST, EquipmentSlots.POCKETS] ): void public addAmmoIntoEquipmentSlots(
ammoTpl: string,
cartridgeCount: number,
inventory: Inventory,
equipmentSlotsToAddTo: EquipmentSlots[] = [EquipmentSlots.TACTICAL_VEST, EquipmentSlots.POCKETS],
): void
{ {
const ammoItems = this.itemHelper.splitStack({ const ammoItems = this.itemHelper.splitStack({
_id: this.hashUtil.generate(), _id: this.hashUtil.generate(),
_tpl: ammoTpl, _tpl: ammoTpl,
upd: { StackObjectsCount: cartridgeCount } upd: {StackObjectsCount: cartridgeCount},
}); });
for (const ammoItem of ammoItems) for (const ammoItem of ammoItems)
@ -128,7 +133,8 @@ export class BotWeaponGeneratorHelper
ammoItem._id, ammoItem._id,
ammoItem._tpl, ammoItem._tpl,
[ammoItem], [ammoItem],
inventory); inventory,
);
if (result === ItemAddedResult.NO_SPACE) if (result === ItemAddedResult.NO_SPACE)
{ {
@ -151,22 +157,32 @@ export class BotWeaponGeneratorHelper
* TODO - move into BotGeneratorHelper, this is not the class for it * TODO - move into BotGeneratorHelper, this is not the class for it
* Adds an item with all its children into specified equipmentSlots, wherever it fits. * Adds an item with all its children into specified equipmentSlots, wherever it fits.
* @param equipmentSlots Slot to add item+children into * @param equipmentSlots Slot to add item+children into
* @param parentId * @param parentId
* @param parentTpl * @param parentTpl
* @param itemWithChildren Item to add * @param itemWithChildren Item to add
* @param inventory Inventory to add item+children into * @param inventory Inventory to add item+children into
* @returns a `boolean` indicating item was added * @returns a `boolean` indicating item was added
*/ */
public addItemWithChildrenToEquipmentSlot(equipmentSlots: string[], parentId: string, parentTpl: string, itemWithChildren: Item[], inventory: Inventory): ItemAddedResult public addItemWithChildrenToEquipmentSlot(
equipmentSlots: string[],
parentId: string,
parentTpl: string,
itemWithChildren: Item[],
inventory: Inventory,
): ItemAddedResult
{ {
for (const slot of equipmentSlots) for (const slot of equipmentSlots)
{ {
// Get container to put item into // Get container to put item into
const container = inventory.items.find(i => i.slotId === slot); const container = inventory.items.find((i) => i.slotId === slot);
if (!container) if (!container)
{ {
// Desired equipment container (e.g. backpack) not found // Desired equipment container (e.g. backpack) not found
this.logger.debug(`Unable to add item: ${itemWithChildren[0]._tpl} to: ${slot}, slot missing/bot generated without equipment`); this.logger.debug(
`Unable to add item: ${
itemWithChildren[0]._tpl
} to: ${slot}, slot missing/bot generated without equipment`,
);
continue; continue;
} }
@ -202,10 +218,12 @@ export class BotWeaponGeneratorHelper
} }
// Get all base level items in backpack // Get all base level items in backpack
const containerItems = inventory.items.filter(i => i.parentId === container._id && i.slotId === slotGrid._name); const containerItems = inventory.items.filter((i) =>
i.parentId === container._id && i.slotId === slotGrid._name
);
// Get a copy of base level items we can iterate over // Get a copy of base level items we can iterate over
const containerItemsToCheck = containerItems.filter(x => x.slotId === slotGrid._name); const containerItemsToCheck = containerItems.filter((x) => x.slotId === slotGrid._name);
for (const item of containerItemsToCheck) for (const item of containerItemsToCheck)
{ {
// Look for children on items, insert into array if found // Look for children on items, insert into array if found
@ -218,14 +236,19 @@ export class BotWeaponGeneratorHelper
} }
// Get rid of items free/used spots in current grid // Get rid of items free/used spots in current grid
const slotGridMap = this.inventoryHelper.getContainerMap(slotGrid._props.cellsH, slotGrid._props.cellsV, containerItems, container._id); const slotGridMap = this.inventoryHelper.getContainerMap(
slotGrid._props.cellsH,
slotGrid._props.cellsV,
containerItems,
container._id,
);
// Try to fit item into grid // Try to fit item into grid
const findSlotResult = this.containerHelper.findSlotForItem(slotGridMap, itemSize[0], itemSize[1]); const findSlotResult = this.containerHelper.findSlotForItem(slotGridMap, itemSize[0], itemSize[1]);
// Open slot found, add item to inventory // Open slot found, add item to inventory
if (findSlotResult.success) if (findSlotResult.success)
{ {
const parentItem = itemWithChildren.find(i => i._id === parentId); const parentItem = itemWithChildren.find((i) => i._id === parentId);
// Set items parent to container id // Set items parent to container id
parentItem.parentId = container._id; parentItem.parentId = container._id;
@ -233,7 +256,7 @@ export class BotWeaponGeneratorHelper
parentItem.location = { parentItem.location = {
x: findSlotResult.x, x: findSlotResult.x,
y: findSlotResult.y, y: findSlotResult.y,
r: findSlotResult.rotation ? 1 : 0 r: findSlotResult.rotation ? 1 : 0,
}; };
inventory.items.push(...itemWithChildren); inventory.items.push(...itemWithChildren);
@ -284,4 +307,4 @@ export class BotWeaponGeneratorHelper
return true; return true;
} }
} }

View File

@ -43,7 +43,7 @@ export class ContainerHelper
/** /**
* Try to rotate if there is enough room for the item * Try to rotate if there is enough room for the item
* Only occupies one grid of items, no rotation required * Only occupies one grid of items, no rotation required
* */ */
if (!foundSlot && itemWidth * itemHeight > 1) if (!foundSlot && itemWidth * itemHeight > 1)
{ {
foundSlot = this.locateSlot(container2D, containerX, containerY, x, y, itemHeight, itemWidth); foundSlot = this.locateSlot(container2D, containerX, containerY, x, y, itemHeight, itemWidth);
@ -77,7 +77,15 @@ export class ContainerHelper
* @param itemH Items height * @param itemH Items height
* @returns True - slot found * @returns True - slot found
*/ */
protected locateSlot(container2D: number[][], containerX: number, containerY: number, x: number, y: number, itemW: number, itemH: number): boolean protected locateSlot(
container2D: number[][],
containerX: number,
containerY: number,
x: number,
y: number,
itemW: number,
itemH: number,
): boolean
{ {
let foundSlot = true; let foundSlot = true;
@ -124,7 +132,14 @@ export class ContainerHelper
* @param rotate is item rotated * @param rotate is item rotated
* @returns Location to place item * @returns Location to place item
*/ */
public fillContainerMapWithItem(container2D: number[][], x: number, y: number, itemW: number, itemH: number, rotate: boolean): number[][] public fillContainerMapWithItem(
container2D: number[][],
x: number,
y: number,
itemW: number,
itemH: number,
rotate: boolean,
): number[][]
{ {
const itemWidth = rotate ? itemH : itemW; const itemWidth = rotate ? itemH : itemW;
const itemHeight = rotate ? itemW : itemH; const itemHeight = rotate ? itemW : itemH;
@ -146,4 +161,4 @@ export class ContainerHelper
return container2D; return container2D;
} }
} }

View File

@ -4,7 +4,13 @@ import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
import { NotificationSendHelper } from "@spt-aki/helpers/NotificationSendHelper"; import { NotificationSendHelper } from "@spt-aki/helpers/NotificationSendHelper";
import { NotifierHelper } from "@spt-aki/helpers/NotifierHelper"; import { NotifierHelper } from "@spt-aki/helpers/NotifierHelper";
import { Item } from "@spt-aki/models/eft/common/tables/IItem"; import { Item } from "@spt-aki/models/eft/common/tables/IItem";
import { Dialogue, Message, MessageContent, MessageItems, MessagePreview } from "@spt-aki/models/eft/profile/IAkiProfile"; import {
Dialogue,
Message,
MessageContent,
MessageItems,
MessagePreview,
} from "@spt-aki/models/eft/profile/IAkiProfile";
import { MessageType } from "@spt-aki/models/enums/MessageType"; import { MessageType } from "@spt-aki/models/enums/MessageType";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
@ -24,9 +30,9 @@ export class DialogueHelper
@inject("NotifierHelper") protected notifierHelper: NotifierHelper, @inject("NotifierHelper") protected notifierHelper: NotifierHelper,
@inject("NotificationSendHelper") protected notificationSendHelper: NotificationSendHelper, @inject("NotificationSendHelper") protected notificationSendHelper: NotificationSendHelper,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("ItemHelper") protected itemHelper: ItemHelper @inject("ItemHelper") protected itemHelper: ItemHelper,
) )
{ } {}
/** /**
* @deprecated Use MailSendService.sendMessage() or helpers * @deprecated Use MailSendService.sendMessage() or helpers
@ -35,7 +41,7 @@ export class DialogueHelper
{ {
const result: MessageContent = { const result: MessageContent = {
templateId: templateId, templateId: templateId,
type: messageType type: messageType,
}; };
if (maxStoreTime) if (maxStoreTime)
@ -49,7 +55,13 @@ export class DialogueHelper
/** /**
* @deprecated Use MailSendService.sendMessage() or helpers * @deprecated Use MailSendService.sendMessage() or helpers
*/ */
public addDialogueMessage(dialogueID: string, messageContent: MessageContent, sessionID: string, rewards: Item[] = [], messageType = MessageType.NPC_TRADER): void public addDialogueMessage(
dialogueID: string,
messageContent: MessageContent,
sessionID: string,
rewards: Item[] = [],
messageType = MessageType.NPC_TRADER,
): void
{ {
const dialogueData = this.saveServer.getProfile(sessionID).dialogues; const dialogueData = this.saveServer.getProfile(sessionID).dialogues;
const isNewDialogue = !(dialogueID in dialogueData); const isNewDialogue = !(dialogueID in dialogueData);
@ -63,7 +75,7 @@ export class DialogueHelper
messages: [], messages: [],
pinned: false, pinned: false,
new: 0, new: 0,
attachmentsNew: 0 attachmentsNew: 0,
}; };
dialogueData[dialogueID] = dialogue; dialogueData[dialogueID] = dialogue;
@ -79,7 +91,7 @@ export class DialogueHelper
const stashId = this.hashUtil.generate(); const stashId = this.hashUtil.generate();
items = { items = {
stash: stashId, stash: stashId,
data: [] data: [],
}; };
rewards = this.itemHelper.replaceIDs(null, rewards); rewards = this.itemHelper.replaceIDs(null, rewards);
@ -95,7 +107,12 @@ export class DialogueHelper
if (!itemTemplate) if (!itemTemplate)
{ {
// Can happen when modded items are insured + mod is removed // Can happen when modded items are insured + mod is removed
this.logger.error(this.localisationService.getText("dialog-missing_item_template", {tpl: reward._tpl, type: MessageType[messageContent.type]})); this.logger.error(
this.localisationService.getText("dialog-missing_item_template", {
tpl: reward._tpl,
type: MessageType[messageContent.type],
}),
);
continue; continue;
} }
@ -132,7 +149,9 @@ export class DialogueHelper
items: items, items: items,
maxStorageTime: messageContent.maxStorageTime, maxStorageTime: messageContent.maxStorageTime,
systemData: messageContent.systemData ? messageContent.systemData : undefined, systemData: messageContent.systemData ? messageContent.systemData : undefined,
profileChangeEvents: (messageContent.profileChangeEvents?.length === 0) ? messageContent.profileChangeEvents : undefined profileChangeEvents: (messageContent.profileChangeEvents?.length === 0) ?
messageContent.profileChangeEvents :
undefined,
}; };
if (!message.templateId) if (!message.templateId)
@ -145,7 +164,10 @@ export class DialogueHelper
// Offer Sold notifications are now separate from the main notification // Offer Sold notifications are now separate from the main notification
if (messageContent.type === MessageType.FLEAMARKET_MESSAGE && messageContent.ragfair) if (messageContent.type === MessageType.FLEAMARKET_MESSAGE && messageContent.ragfair)
{ {
const offerSoldMessage = this.notifierHelper.createRagfairOfferSoldNotification(message, messageContent.ragfair); const offerSoldMessage = this.notifierHelper.createRagfairOfferSoldNotification(
message,
messageContent.ragfair,
);
this.notificationSendHelper.sendMessage(sessionID, offerSoldMessage); this.notificationSendHelper.sendMessage(sessionID, offerSoldMessage);
message.type = MessageType.MESSAGE_WITH_ITEMS; // Should prevent getting the same notification popup twice message.type = MessageType.MESSAGE_WITH_ITEMS; // Should prevent getting the same notification popup twice
} }
@ -156,7 +178,7 @@ export class DialogueHelper
/** /**
* Get the preview contents of the last message in a dialogue. * Get the preview contents of the last message in a dialogue.
* @param dialogue * @param dialogue
* @returns MessagePreview * @returns MessagePreview
*/ */
public getMessagePreview(dialogue: Dialogue): MessagePreview public getMessagePreview(dialogue: Dialogue): MessagePreview
@ -167,7 +189,7 @@ export class DialogueHelper
dt: message?.dt, dt: message?.dt,
type: message?.type, type: message?.type,
templateId: message?.templateId, templateId: message?.templateId,
uid: dialogue._id uid: dialogue._id,
}; };
if (message?.text) if (message?.text)
@ -185,17 +207,17 @@ export class DialogueHelper
/** /**
* Get the item contents for a particular message. * Get the item contents for a particular message.
* @param messageID * @param messageID
* @param sessionID * @param sessionID
* @param itemId Item being moved to inventory * @param itemId Item being moved to inventory
* @returns * @returns
*/ */
public getMessageItemContents(messageID: string, sessionID: string, itemId: string): Item[] public getMessageItemContents(messageID: string, sessionID: string, itemId: string): Item[]
{ {
const dialogueData = this.saveServer.getProfile(sessionID).dialogues; const dialogueData = this.saveServer.getProfile(sessionID).dialogues;
for (const dialogueId in dialogueData) for (const dialogueId in dialogueData)
{ {
const message = dialogueData[dialogueId].messages.find(x => x._id === messageID); const message = dialogueData[dialogueId].messages.find((x) => x._id === messageID);
if (!message) if (!message)
{ {
continue; continue;
@ -211,7 +233,7 @@ export class DialogueHelper
// Check reward count when item being moved isn't in reward list // Check reward count when item being moved isn't in reward list
// if count is 0, it means after this move the reward array will be empty and all rewards collected // if count is 0, it means after this move the reward array will be empty and all rewards collected
const rewardItemCount = message.items.data.filter(x => x._id !== itemId ); const rewardItemCount = message.items.data.filter((x) => x._id !== itemId);
if (rewardItemCount.length === 0) if (rewardItemCount.length === 0)
{ {
message.rewardCollected = true; message.rewardCollected = true;
@ -240,4 +262,4 @@ export class DialogueHelper
return profile.dialogues; return profile.dialogues;
} }
} }

View File

@ -15,7 +15,7 @@ export class DurabilityLimitsHelper
constructor( constructor(
@inject("RandomUtil") protected randomUtil: RandomUtil, @inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("BotHelper") protected botHelper: BotHelper, @inject("BotHelper") protected botHelper: BotHelper,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT); this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
@ -172,12 +172,14 @@ export class DurabilityLimitsHelper
const maxDelta = this.getMaxWeaponDeltaFromConfig(botRole); const maxDelta = this.getMaxWeaponDeltaFromConfig(botRole);
const delta = this.randomUtil.getInt(minDelta, maxDelta); const delta = this.randomUtil.getInt(minDelta, maxDelta);
const result = maxDurability - delta; const result = maxDurability - delta;
const durabilityValueMinLimit = Math.round((this.getMinWeaponLimitPercentFromConfig(botRole) / 100) * maxDurability); const durabilityValueMinLimit = Math.round(
(this.getMinWeaponLimitPercentFromConfig(botRole) / 100) * maxDurability,
);
// Dont let weapon dura go below the percent defined in config // Dont let weapon dura go below the percent defined in config
return (result >= durabilityValueMinLimit) return (result >= durabilityValueMinLimit) ?
? result result :
: durabilityValueMinLimit; durabilityValueMinLimit;
} }
protected generateArmorDurability(botRole: string, maxDurability: number): number protected generateArmorDurability(botRole: string, maxDurability: number): number
@ -186,12 +188,14 @@ export class DurabilityLimitsHelper
const maxDelta = this.getMaxArmorDeltaFromConfig(botRole); const maxDelta = this.getMaxArmorDeltaFromConfig(botRole);
const delta = this.randomUtil.getInt(minDelta, maxDelta); const delta = this.randomUtil.getInt(minDelta, maxDelta);
const result = maxDurability - delta; const result = maxDurability - delta;
const durabilityValueMinLimit = Math.round((this.getMinArmorLimitPercentFromConfig(botRole) / 100) * maxDurability); const durabilityValueMinLimit = Math.round(
(this.getMinArmorLimitPercentFromConfig(botRole) / 100) * maxDurability,
);
// Dont let armor dura go below the percent defined in config // Dont let armor dura go below the percent defined in config
return (result >= durabilityValueMinLimit) return (result >= durabilityValueMinLimit) ?
? result result :
: durabilityValueMinLimit; durabilityValueMinLimit;
} }
protected getMinWeaponDeltaFromConfig(botRole: string): number protected getMinWeaponDeltaFromConfig(botRole: string): number
@ -234,7 +238,7 @@ export class DurabilityLimitsHelper
return this.botConfig.durability.default.armor.maxDelta; return this.botConfig.durability.default.armor.maxDelta;
} }
protected getMinArmorLimitPercentFromConfig (botRole: string): number protected getMinArmorLimitPercentFromConfig(botRole: string): number
{ {
if (this.botConfig.durability[botRole]) if (this.botConfig.durability[botRole])
{ {
@ -244,7 +248,7 @@ export class DurabilityLimitsHelper
return this.botConfig.durability.default.armor.minLimitPercent; return this.botConfig.durability.default.armor.minLimitPercent;
} }
protected getMinWeaponLimitPercentFromConfig (botRole: string): number protected getMinWeaponLimitPercentFromConfig(botRole: string): number
{ {
if (this.botConfig.durability[botRole]) if (this.botConfig.durability[botRole])
{ {
@ -253,4 +257,4 @@ export class DurabilityLimitsHelper
return this.botConfig.durability.default.weapon.minLimitPercent; return this.botConfig.durability.default.weapon.minLimitPercent;
} }
} }

View File

@ -12,10 +12,9 @@ export class GameEventHelper
constructor( constructor(
@inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.seasonalEventConfig = this.configServer.getConfig(ConfigTypes.SEASONAL_EVENT); this.seasonalEventConfig = this.configServer.getConfig(ConfigTypes.SEASONAL_EVENT);
} }
}
}

View File

@ -29,7 +29,7 @@ export class LookupCollection
@injectable() @injectable()
export class HandbookHelper export class HandbookHelper
{ {
protected lookupCacheGenerated = false; protected lookupCacheGenerated = false;
protected handbookPriceCache = new LookupCollection(); protected handbookPriceCache = new LookupCollection();
@ -89,7 +89,7 @@ export class HandbookHelper
return this.handbookPriceCache.items.byId.get(tpl); return this.handbookPriceCache.items.byId.get(tpl);
} }
const handbookItem = this.databaseServer.getTables().templates.handbook.Items.find(x => x.Id === tpl); const handbookItem = this.databaseServer.getTables().templates.handbook.Items.find((x) => x.Id === tpl);
if (!handbookItem) if (!handbookItem)
{ {
const newValue = 0; const newValue = 0;
@ -103,7 +103,7 @@ export class HandbookHelper
/** /**
* Get all items in template with the given parent category * Get all items in template with the given parent category
* @param parentId * @param parentId
* @returns string array * @returns string array
*/ */
public templatesWithParent(parentId: string): string[] public templatesWithParent(parentId: string): string[]
@ -113,7 +113,7 @@ export class HandbookHelper
/** /**
* Does category exist in handbook cache * Does category exist in handbook cache
* @param category * @param category
* @returns true if exists in cache * @returns true if exists in cache
*/ */
public isCategory(category: string): boolean public isCategory(category: string): boolean
@ -123,7 +123,7 @@ export class HandbookHelper
/** /**
* Get all items associated with a categories parent * Get all items associated with a categories parent
* @param categoryParent * @param categoryParent
* @returns string array * @returns string array
*/ */
public childrenCategories(categoryParent: string): string[] public childrenCategories(categoryParent: string): string[]
@ -164,4 +164,4 @@ export class HandbookHelper
const price = this.getTemplatePrice(currencyTypeTo); const price = this.getTemplatePrice(currencyTypeTo);
return price ? Math.round(roubleCurrencyCount / price) : 0; return price ? Math.round(roubleCurrencyCount / price) : 0;
} }
} }

View File

@ -21,7 +21,7 @@ export class HealthHelper
@inject("WinstonLogger") protected logger: ILogger, @inject("WinstonLogger") protected logger: ILogger,
@inject("TimeUtil") protected timeUtil: TimeUtil, @inject("TimeUtil") protected timeUtil: TimeUtil,
@inject("SaveServer") protected saveServer: SaveServer, @inject("SaveServer") protected saveServer: SaveServer,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.healthConfig = this.configServer.getConfig(ConfigTypes.HEALTH); this.healthConfig = this.configServer.getConfig(ConfigTypes.HEALTH);
@ -36,11 +36,11 @@ export class HealthHelper
{ {
const profile = this.saveServer.getProfile(sessionID); const profile = this.saveServer.getProfile(sessionID);
if (!profile.vitality) // Occurs on newly created profiles if (!profile.vitality)
{ { // Occurs on newly created profiles
profile.vitality = { profile.vitality = {
health: null, health: null,
effects: null effects: null,
}; };
} }
profile.vitality.health = { profile.vitality.health = {
@ -53,7 +53,7 @@ export class HealthHelper
LeftArm: 0, LeftArm: 0,
RightArm: 0, RightArm: 0,
LeftLeg: 0, LeftLeg: 0,
RightLeg: 0 RightLeg: 0,
}; };
profile.vitality.effects = { profile.vitality.effects = {
@ -63,7 +63,7 @@ export class HealthHelper
LeftArm: {}, LeftArm: {},
RightArm: {}, RightArm: {},
LeftLeg: {}, LeftLeg: {},
RightLeg: {} RightLeg: {},
}; };
return profile; return profile;
@ -77,7 +77,13 @@ export class HealthHelper
* @param addEffects Should effects be added or removed (default - add) * @param addEffects Should effects be added or removed (default - add)
* @param deleteExistingEffects Should all prior effects be removed before apply new ones * @param deleteExistingEffects Should all prior effects be removed before apply new ones
*/ */
public saveVitality(pmcData: IPmcData, request: ISyncHealthRequestData, sessionID: string, addEffects = true, deleteExistingEffects = true): void public saveVitality(
pmcData: IPmcData,
request: ISyncHealthRequestData,
sessionID: string,
addEffects = true,
deleteExistingEffects = true,
): void
{ {
const postRaidBodyParts = request.Health; // post raid health settings const postRaidBodyParts = request.Health; // post raid health settings
const profile = this.saveServer.getProfile(sessionID); const profile = this.saveServer.getProfile(sessionID);
@ -96,20 +102,26 @@ export class HealthHelper
{ {
profileEffects[bodyPart] = postRaidBodyParts[bodyPart].Effects; profileEffects[bodyPart] = postRaidBodyParts[bodyPart].Effects;
} }
if (request.IsAlive === true) // is player alive, not is limb alive if (request.IsAlive === true)
{ { // is player alive, not is limb alive
profileHealth[bodyPart] = postRaidBodyParts[bodyPart].Current; profileHealth[bodyPart] = postRaidBodyParts[bodyPart].Current;
} }
else else
{ {
profileHealth[bodyPart] = pmcData.Health.BodyParts[bodyPart].Health.Maximum * this.healthConfig.healthMultipliers.death; profileHealth[bodyPart] = pmcData.Health.BodyParts[bodyPart].Health.Maximum *
this.healthConfig.healthMultipliers.death;
} }
} }
// Add effects to body parts // Add effects to body parts
if (addEffects) if (addEffects)
{ {
this.saveEffects(pmcData, sessionID, this.jsonUtil.clone(this.saveServer.getProfile(sessionID).vitality.effects), deleteExistingEffects); this.saveEffects(
pmcData,
sessionID,
this.jsonUtil.clone(this.saveServer.getProfile(sessionID).vitality.effects),
deleteExistingEffects,
);
} }
// Adjust hydration/energy/temp and limb hp // Adjust hydration/energy/temp and limb hp
@ -159,7 +171,10 @@ export class HealthHelper
if (target === 0) if (target === 0)
{ {
// Blacked body part // Blacked body part
target = Math.round(pmcData.Health.BodyParts[healthModifier].Health.Maximum * this.healthConfig.healthMultipliers.blacked); target = Math.round(
pmcData.Health.BodyParts[healthModifier].Health.Maximum *
this.healthConfig.healthMultipliers.blacked,
);
} }
pmcData.Health.BodyParts[healthModifier].Health.Current = Math.round(target); pmcData.Health.BodyParts[healthModifier].Health.Current = Math.round(target);
@ -176,7 +191,12 @@ export class HealthHelper
* @param bodyPartsWithEffects dict of body parts with effects that should be added to profile * @param bodyPartsWithEffects dict of body parts with effects that should be added to profile
* @param addEffects Should effects be added back to profile * @param addEffects Should effects be added back to profile
*/ */
protected saveEffects(pmcData: IPmcData, sessionId: string, bodyPartsWithEffects: Effects, deleteExistingEffects = true): void protected saveEffects(
pmcData: IPmcData,
sessionId: string,
bodyPartsWithEffects: Effects,
deleteExistingEffects = true,
): void
{ {
if (!this.healthConfig.save.effects) if (!this.healthConfig.save.effects)
{ {
@ -208,7 +228,9 @@ export class HealthHelper
// Sometimes the value can be Infinity instead of -1, blame HealthListener.cs in modules // Sometimes the value can be Infinity instead of -1, blame HealthListener.cs in modules
if (time === "Infinity") if (time === "Infinity")
{ {
this.logger.warning(`Effect ${effectType} found with value of Infinity, changed to -1, this is an issue with HealthListener.cs`); this.logger.warning(
`Effect ${effectType} found with value of Infinity, changed to -1, this is an issue with HealthListener.cs`,
);
time = -1; time = -1;
} }
this.addEffect(pmcData, bodyPart, effectType, time); this.addEffect(pmcData, bodyPart, effectType, time);
@ -236,7 +258,7 @@ export class HealthHelper
profileBodyPart.Effects = {}; profileBodyPart.Effects = {};
} }
profileBodyPart.Effects[effectType] = { Time: duration }; profileBodyPart.Effects[effectType] = {Time: duration};
// Delete empty property to prevent client bugs // Delete empty property to prevent client bugs
if (this.isEmpty(profileBodyPart.Effects)) if (this.isEmpty(profileBodyPart.Effects))
@ -245,7 +267,7 @@ export class HealthHelper
} }
} }
protected isEmpty(map: Record<string, { Time: number }>): boolean protected isEmpty(map: Record<string, {Time: number;}>): boolean
{ {
for (const key in map) for (const key in map)
{ {
@ -257,4 +279,4 @@ export class HealthHelper
return true; return true;
} }
} }

View File

@ -48,7 +48,7 @@ export class HideoutHelper
@inject("InventoryHelper") protected inventoryHelper: InventoryHelper, @inject("InventoryHelper") protected inventoryHelper: InventoryHelper,
@inject("PlayerService") protected playerService: PlayerService, @inject("PlayerService") protected playerService: PlayerService,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT); this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT);
@ -61,9 +61,13 @@ export class HideoutHelper
* @param sessionID Session id * @param sessionID Session id
* @returns client response * @returns client response
*/ */
public registerProduction(pmcData: IPmcData, body: IHideoutSingleProductionStartRequestData | IHideoutContinuousProductionStartRequestData, sessionID: string): IItemEventRouterResponse public registerProduction(
pmcData: IPmcData,
body: IHideoutSingleProductionStartRequestData | IHideoutContinuousProductionStartRequestData,
sessionID: string,
): IItemEventRouterResponse
{ {
const recipe = this.databaseServer.getTables().hideout.production.find(p => p._id === body.recipeId); const recipe = this.databaseServer.getTables().hideout.production.find((p) => p._id === body.recipeId);
if (!recipe) if (!recipe)
{ {
this.logger.error(this.localisationService.getText("hideout-missing_recipe_in_db", body.recipeId)); this.logger.error(this.localisationService.getText("hideout-missing_recipe_in_db", body.recipeId));
@ -71,7 +75,8 @@ export class HideoutHelper
return this.httpResponse.appendErrorToOutput(this.eventOutputHolder.getOutput(sessionID)); return this.httpResponse.appendErrorToOutput(this.eventOutputHolder.getOutput(sessionID));
} }
const modifiedProductionTime = recipe.productionTime - this.getCraftingSkillProductionTimeReduction(pmcData, recipe.productionTime); const modifiedProductionTime = recipe.productionTime -
this.getCraftingSkillProductionTimeReduction(pmcData, recipe.productionTime);
// @Important: Here we need to be very exact: // @Important: Here we need to be very exact:
// - normal recipe: Production time value is stored in attribute "productionType" with small "p" // - normal recipe: Production time value is stored in attribute "productionType" with small "p"
@ -80,7 +85,11 @@ export class HideoutHelper
{ {
pmcData.Hideout.Production = {}; pmcData.Hideout.Production = {};
} }
pmcData.Hideout.Production[body.recipeId] = this.initProduction(body.recipeId, modifiedProductionTime, recipe.needFuelForAllProductionTime); pmcData.Hideout.Production[body.recipeId] = this.initProduction(
body.recipeId,
modifiedProductionTime,
recipe.needFuelForAllProductionTime,
);
} }
/** /**
@ -100,14 +109,14 @@ export class HideoutHelper
Interrupted: false, Interrupted: false,
NeedFuelForAllProductionTime: needFuelForAllProductionTime, // Used when sending to client NeedFuelForAllProductionTime: needFuelForAllProductionTime, // Used when sending to client
needFuelForAllProductionTime: needFuelForAllProductionTime, // used when stored in production.json needFuelForAllProductionTime: needFuelForAllProductionTime, // used when stored in production.json
SkipTime: 0 SkipTime: 0,
}; };
} }
/** /**
* Is the provided object a Production type * Is the provided object a Production type
* @param productive * @param productive
* @returns * @returns
*/ */
public isProductionType(productive: Productive): productive is Production public isProductionType(productive: Productive): productive is Production
{ {
@ -124,12 +133,15 @@ export class HideoutHelper
// Handle additional changes some bonuses need before being added // Handle additional changes some bonuses need before being added
switch (bonus.type) switch (bonus.type)
{ {
case "StashSize": { case "StashSize":
{
// Find stash item and adjust tpl to new tpl from bonus // Find stash item and adjust tpl to new tpl from bonus
const stashItem = pmcData.Inventory.items.find(x => x._id === pmcData.Inventory.stash); const stashItem = pmcData.Inventory.items.find((x) => x._id === pmcData.Inventory.stash);
if (!stashItem) if (!stashItem)
{ {
this.logger.warning(`Unable to apply StashSize bonus, stash with id: ${pmcData.Inventory.stash} not found`); this.logger.warning(
`Unable to apply StashSize bonus, stash with id: ${pmcData.Inventory.stash} not found`,
);
} }
stashItem._tpl = bonus.templateId; stashItem._tpl = bonus.templateId;
@ -140,7 +152,7 @@ export class HideoutHelper
pmcData.Health.Energy.Maximum += bonus.value; pmcData.Health.Energy.Maximum += bonus.value;
break; break;
case "TextBonus": case "TextBonus":
//Delete values before they're added to profile // Delete values before they're added to profile
delete bonus.value; delete bonus.value;
delete bonus.passive; delete bonus.passive;
delete bonus.production; delete bonus.production;
@ -173,15 +185,19 @@ export class HideoutHelper
* @param pmcData Player profile * @param pmcData Player profile
* @returns Properties * @returns Properties
*/ */
protected getHideoutProperties(pmcData: IPmcData): { btcFarmCGs: number; isGeneratorOn: boolean; waterCollectorHasFilter: boolean; } protected getHideoutProperties(
pmcData: IPmcData,
): {btcFarmCGs: number; isGeneratorOn: boolean; waterCollectorHasFilter: boolean;}
{ {
const bitcoinFarm = pmcData.Hideout.Areas.find(x => x.type === HideoutAreas.BITCOIN_FARM); const bitcoinFarm = pmcData.Hideout.Areas.find((x) => x.type === HideoutAreas.BITCOIN_FARM);
const bitcoinCount = bitcoinFarm?.slots.filter(slot => slot.item).length ?? 0; // Get slots with an item property const bitcoinCount = bitcoinFarm?.slots.filter((slot) => slot.item).length ?? 0; // Get slots with an item property
const hideoutProperties = { const hideoutProperties = {
btcFarmCGs: bitcoinCount, btcFarmCGs: bitcoinCount,
isGeneratorOn: pmcData.Hideout.Areas.find(x => x.type === HideoutAreas.GENERATOR)?.active ?? false, isGeneratorOn: pmcData.Hideout.Areas.find((x) => x.type === HideoutAreas.GENERATOR)?.active ?? false,
waterCollectorHasFilter: this.doesWaterCollectorHaveFilter(pmcData.Hideout.Areas.find(x => x.type === HideoutAreas.WATER_COLLECTOR)) waterCollectorHasFilter: this.doesWaterCollectorHaveFilter(
pmcData.Hideout.Areas.find((x) => x.type === HideoutAreas.WATER_COLLECTOR),
),
}; };
return hideoutProperties; return hideoutProperties;
@ -189,23 +205,27 @@ export class HideoutHelper
protected doesWaterCollectorHaveFilter(waterCollector: HideoutArea): boolean protected doesWaterCollectorHaveFilter(waterCollector: HideoutArea): boolean
{ {
if (waterCollector.level === 3) // can put filters in from L3 if (waterCollector.level === 3)
{ { // can put filters in from L3
// Has filter in at least one slot // Has filter in at least one slot
return waterCollector.slots.some(x => x.item); return waterCollector.slots.some((x) => x.item);
} }
// No Filter // No Filter
return false; return false;
} }
/** /**
* Update progress timer for water collector * Update progress timer for water collector
* @param pmcData profile to update * @param pmcData profile to update
* @param productionId id of water collection production to update * @param productionId id of water collection production to update
* @param hideoutProperties Hideout properties * @param hideoutProperties Hideout properties
*/ */
protected updateWaterCollectorProductionTimer(pmcData: IPmcData, productionId: string, hideoutProperties: { btcFarmCGs?: number; isGeneratorOn: boolean; waterCollectorHasFilter: boolean; }): void protected updateWaterCollectorProductionTimer(
pmcData: IPmcData,
productionId: string,
hideoutProperties: {btcFarmCGs?: number; isGeneratorOn: boolean; waterCollectorHasFilter: boolean;},
): void
{ {
const timeElapsed = this.getTimeElapsedSinceLastServerTick(pmcData, hideoutProperties.isGeneratorOn); const timeElapsed = this.getTimeElapsedSinceLastServerTick(pmcData, hideoutProperties.isGeneratorOn);
if (hideoutProperties.waterCollectorHasFilter) if (hideoutProperties.waterCollectorHasFilter)
@ -219,7 +239,10 @@ export class HideoutHelper
* @param pmcData Profile to check for productions and update * @param pmcData Profile to check for productions and update
* @param hideoutProperties Hideout properties * @param hideoutProperties Hideout properties
*/ */
protected updateProductionTimers(pmcData: IPmcData, hideoutProperties: { btcFarmCGs: number; isGeneratorOn: boolean; waterCollectorHasFilter: boolean; }): void protected updateProductionTimers(
pmcData: IPmcData,
hideoutProperties: {btcFarmCGs: number; isGeneratorOn: boolean; waterCollectorHasFilter: boolean;},
): void
{ {
const recipes = this.databaseServer.getTables().hideout.production; const recipes = this.databaseServer.getTables().hideout.production;
@ -255,12 +278,16 @@ export class HideoutHelper
if (prodId === HideoutHelper.bitcoinFarm) if (prodId === HideoutHelper.bitcoinFarm)
{ {
pmcData.Hideout.Production[prodId] = this.updateBitcoinFarm(pmcData, hideoutProperties.btcFarmCGs, hideoutProperties.isGeneratorOn); pmcData.Hideout.Production[prodId] = this.updateBitcoinFarm(
pmcData,
hideoutProperties.btcFarmCGs,
hideoutProperties.isGeneratorOn,
);
continue; continue;
} }
// Other recipes not covered by above // Other recipes not covered by above
const recipe = recipes.find(r => r._id === prodId); const recipe = recipes.find((r) => r._id === prodId);
if (!recipe) if (!recipe)
{ {
this.logger.error(this.localisationService.getText("hideout-missing_recipe_for_area", prodId)); this.logger.error(this.localisationService.getText("hideout-missing_recipe_for_area", prodId));
@ -277,9 +304,14 @@ export class HideoutHelper
* @param pmcData Player profile * @param pmcData Player profile
* @param prodId Production id being crafted * @param prodId Production id being crafted
* @param recipe Recipe data being crafted * @param recipe Recipe data being crafted
* @param hideoutProperties * @param hideoutProperties
*/ */
protected updateProductionProgress(pmcData: IPmcData, prodId: string, recipe: IHideoutProduction, hideoutProperties: { btcFarmCGs?: number; isGeneratorOn: boolean; waterCollectorHasFilter?: boolean; }): void protected updateProductionProgress(
pmcData: IPmcData,
prodId: string,
recipe: IHideoutProduction,
hideoutProperties: {btcFarmCGs?: number; isGeneratorOn: boolean; waterCollectorHasFilter?: boolean;},
): void
{ {
// Production is complete, no need to do any calculations // Production is complete, no need to do any calculations
if (this.doesProgressMatchProductionTime(pmcData, prodId)) if (this.doesProgressMatchProductionTime(pmcData, prodId))
@ -321,7 +353,8 @@ export class HideoutHelper
*/ */
protected updateScavCaseProductionTimer(pmcData: IPmcData, productionId: string): void protected updateScavCaseProductionTimer(pmcData: IPmcData, productionId: string): void
{ {
const timeElapsed = (this.timeUtil.getTimestamp() - pmcData.Hideout.Production[productionId].StartTimestamp) - pmcData.Hideout.Production[productionId].Progress; const timeElapsed = (this.timeUtil.getTimestamp() - pmcData.Hideout.Production[productionId].StartTimestamp) -
pmcData.Hideout.Production[productionId].Progress;
pmcData.Hideout.Production[productionId].Progress += timeElapsed; pmcData.Hideout.Production[productionId].Progress += timeElapsed;
} }
@ -331,7 +364,11 @@ export class HideoutHelper
* @param pmcData Profile to update areas of * @param pmcData Profile to update areas of
* @param hideoutProperties hideout properties * @param hideoutProperties hideout properties
*/ */
protected updateAreasWithResources(sessionID: string, pmcData: IPmcData, hideoutProperties: { btcFarmCGs: number; isGeneratorOn: boolean; waterCollectorHasFilter: boolean; }): void protected updateAreasWithResources(
sessionID: string,
pmcData: IPmcData,
hideoutProperties: {btcFarmCGs: number; isGeneratorOn: boolean; waterCollectorHasFilter: boolean;},
): void
{ {
for (const area of pmcData.Hideout.Areas) for (const area of pmcData.Hideout.Areas)
{ {
@ -361,9 +398,10 @@ export class HideoutHelper
{ {
// 1 resource last 14 min 27 sec, 1/14.45/60 = 0.00115 // 1 resource last 14 min 27 sec, 1/14.45/60 = 0.00115
// 10-10-2021 From wiki, 1 resource last 12 minutes 38 seconds, 1/12.63333/60 = 0.00131 // 10-10-2021 From wiki, 1 resource last 12 minutes 38 seconds, 1/12.63333/60 = 0.00131
let fuelDrainRate = this.databaseServer.getTables().hideout.settings.generatorFuelFlowRate * this.hideoutConfig.runIntervalSeconds; let fuelDrainRate = this.databaseServer.getTables().hideout.settings.generatorFuelFlowRate *
this.hideoutConfig.runIntervalSeconds;
// implemented moddable bonus for fuel consumption bonus instead of using solar power variable as before // implemented moddable bonus for fuel consumption bonus instead of using solar power variable as before
const fuelBonus = pmcData.Bonuses.find(b => b.type === "FuelConsumption"); const fuelBonus = pmcData.Bonuses.find((b) => b.type === "FuelConsumption");
const fuelBonusPercent = 1.0 - (fuelBonus ? Math.abs(fuelBonus.value) : 0) / 100; const fuelBonusPercent = 1.0 - (fuelBonus ? Math.abs(fuelBonus.value) : 0) / 100;
fuelDrainRate *= fuelBonusPercent; fuelDrainRate *= fuelBonusPercent;
// Hideout management resource consumption bonus: // Hideout management resource consumption bonus:
@ -376,9 +414,9 @@ export class HideoutHelper
{ {
if (generatorArea.slots[i].item) if (generatorArea.slots[i].item)
{ {
let resourceValue = (generatorArea.slots[i].item[0].upd?.Resource) let resourceValue = (generatorArea.slots[i].item[0].upd?.Resource) ?
? generatorArea.slots[i].item[0].upd.Resource.Value generatorArea.slots[i].item[0].upd.Resource.Value :
: null; null;
if (resourceValue === 0) if (resourceValue === 0)
{ {
continue; continue;
@ -386,9 +424,9 @@ export class HideoutHelper
else if (!resourceValue) else if (!resourceValue)
{ {
const fuelItem = HideoutHelper.expeditionaryFuelTank; const fuelItem = HideoutHelper.expeditionaryFuelTank;
resourceValue = generatorArea.slots[i].item[0]._tpl === fuelItem resourceValue = generatorArea.slots[i].item[0]._tpl === fuelItem ?
? 60 - fuelDrainRate 60 - fuelDrainRate :
: 100 - fuelDrainRate; 100 - fuelDrainRate;
pointsConsumed = fuelDrainRate; pointsConsumed = fuelDrainRate;
} }
else else
@ -400,7 +438,7 @@ export class HideoutHelper
resourceValue = Math.round(resourceValue * 10000) / 10000; resourceValue = Math.round(resourceValue * 10000) / 10000;
pointsConsumed = Math.round(pointsConsumed * 10000) / 10000; pointsConsumed = Math.round(pointsConsumed * 10000) / 10000;
//check unit consumed for increment skill point // check unit consumed for increment skill point
if (pmcData && Math.floor(pointsConsumed / 10) >= 1) if (pmcData && Math.floor(pointsConsumed / 10) >= 1)
{ {
this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, 1); this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, 1);
@ -432,7 +470,12 @@ export class HideoutHelper
} }
} }
protected updateWaterCollector(sessionId: string, pmcData: IPmcData, area: HideoutArea, isGeneratorOn: boolean): void protected updateWaterCollector(
sessionId: string,
pmcData: IPmcData,
area: HideoutArea,
isGeneratorOn: boolean,
): void
{ {
// Skip water collector when not level 3 (cant collect until 3) // Skip water collector when not level 3 (cant collect until 3)
if (area.level !== 3) if (area.level !== 3)
@ -454,7 +497,7 @@ export class HideoutHelper
recipeId: HideoutHelper.waterCollector, recipeId: HideoutHelper.waterCollector,
Action: "HideoutSingleProductionStart", Action: "HideoutSingleProductionStart",
items: [], items: [],
timestamp: this.timeUtil.getTimestamp() timestamp: this.timeUtil.getTimestamp(),
}; };
this.registerProduction(pmcData, recipe, sessionId); this.registerProduction(pmcData, recipe, sessionId);
@ -469,14 +512,24 @@ export class HideoutHelper
* @param pmcData Player profile * @param pmcData Player profile
* @returns Updated HideoutArea object * @returns Updated HideoutArea object
*/ */
protected updateWaterFilters(waterFilterArea: HideoutArea, production: Production, isGeneratorOn: boolean, pmcData: IPmcData): HideoutArea protected updateWaterFilters(
waterFilterArea: HideoutArea,
production: Production,
isGeneratorOn: boolean,
pmcData: IPmcData,
): HideoutArea
{ {
let filterDrainRate = this.getWaterFilterDrainRate(pmcData); let filterDrainRate = this.getWaterFilterDrainRate(pmcData);
const productionTime = this.getTotalProductionTimeSeconds(HideoutHelper.waterCollector); const productionTime = this.getTotalProductionTimeSeconds(HideoutHelper.waterCollector);
const secondsSinceServerTick = this.getTimeElapsedSinceLastServerTick(pmcData, isGeneratorOn); const secondsSinceServerTick = this.getTimeElapsedSinceLastServerTick(pmcData, isGeneratorOn);
filterDrainRate = this.adjustWaterFilterDrainRate(secondsSinceServerTick, productionTime, production.Progress, filterDrainRate); filterDrainRate = this.adjustWaterFilterDrainRate(
secondsSinceServerTick,
productionTime,
production.Progress,
filterDrainRate,
);
// Production hasn't completed // Production hasn't completed
let pointsConsumed = 0; let pointsConsumed = 0;
if (production.Progress < productionTime) if (production.Progress < productionTime)
@ -489,9 +542,9 @@ export class HideoutHelper
if (waterFilterArea.slots[i].item) if (waterFilterArea.slots[i].item)
{ {
// How many units of filter are left // How many units of filter are left
let resourceValue = (waterFilterArea.slots[i].item[0].upd?.Resource) let resourceValue = (waterFilterArea.slots[i].item[0].upd?.Resource) ?
? waterFilterArea.slots[i].item[0].upd.Resource.Value waterFilterArea.slots[i].item[0].upd.Resource.Value :
: null; null;
if (!resourceValue) if (!resourceValue)
{ {
// None left // None left
@ -500,7 +553,8 @@ export class HideoutHelper
} }
else else
{ {
pointsConsumed = (waterFilterArea.slots[i].item[0].upd.Resource.UnitsConsumed || 0) + filterDrainRate; pointsConsumed = (waterFilterArea.slots[i].item[0].upd.Resource.UnitsConsumed || 0) +
filterDrainRate;
resourceValue -= filterDrainRate; resourceValue -= filterDrainRate;
} }
@ -537,19 +591,25 @@ export class HideoutHelper
} }
/** /**
* Get an adjusted water filter drain rate based on time elapsed since last run, * Get an adjusted water filter drain rate based on time elapsed since last run,
* handle edge case when craft time has gone on longer than total production time * handle edge case when craft time has gone on longer than total production time
* @param secondsSinceServerTick Time passed * @param secondsSinceServerTick Time passed
* @param totalProductionTime Total time collecting water * @param totalProductionTime Total time collecting water
* @param productionProgress how far water collector has progressed * @param productionProgress how far water collector has progressed
* @param baseFilterDrainRate Base drain rate * @param baseFilterDrainRate Base drain rate
* @returns * @returns
*/ */
protected adjustWaterFilterDrainRate(secondsSinceServerTick: number, totalProductionTime: number, productionProgress: number, baseFilterDrainRate: number): number protected adjustWaterFilterDrainRate(
secondsSinceServerTick: number,
totalProductionTime: number,
productionProgress: number,
baseFilterDrainRate: number,
): number
{ {
const drainRateMultiplier = secondsSinceServerTick > totalProductionTime const drainRateMultiplier = secondsSinceServerTick > totalProductionTime ?
? (totalProductionTime - productionProgress) // more time passed than prod time, get total minus the current progress (totalProductionTime - productionProgress) // more time passed than prod time, get total minus the current progress
: secondsSinceServerTick; :
secondsSinceServerTick;
// Multiply drain rate by calculated multiplier // Multiply drain rate by calculated multiplier
baseFilterDrainRate *= drainRateMultiplier; baseFilterDrainRate *= drainRateMultiplier;
@ -578,16 +638,16 @@ export class HideoutHelper
*/ */
protected getTotalProductionTimeSeconds(prodId: string): number protected getTotalProductionTimeSeconds(prodId: string): number
{ {
const recipe = this.databaseServer.getTables().hideout.production.find(prod => prod._id === prodId); const recipe = this.databaseServer.getTables().hideout.production.find((prod) => prod._id === prodId);
return (recipe.productionTime || 0); return (recipe.productionTime || 0);
} }
/** /**
* Create a upd object using passed in parameters * Create a upd object using passed in parameters
* @param stackCount * @param stackCount
* @param resourceValue * @param resourceValue
* @param resourceUnitsConsumed * @param resourceUnitsConsumed
* @returns Upd * @returns Upd
*/ */
protected getAreaUpdObject(stackCount: number, resourceValue: number, resourceUnitsConsumed: number): Upd protected getAreaUpdObject(stackCount: number, resourceValue: number, resourceUnitsConsumed: number): Upd
@ -596,8 +656,8 @@ export class HideoutHelper
StackObjectsCount: stackCount, StackObjectsCount: stackCount,
Resource: { Resource: {
Value: resourceValue, Value: resourceValue,
UnitsConsumed: resourceUnitsConsumed UnitsConsumed: resourceUnitsConsumed,
} },
}; };
} }
@ -608,7 +668,8 @@ export class HideoutHelper
Lasts for 17 hours 38 minutes and 49 seconds (23 hours 31 minutes and 45 seconds with elite hideout management skill), Lasts for 17 hours 38 minutes and 49 seconds (23 hours 31 minutes and 45 seconds with elite hideout management skill),
300/17.64694/60/60 = 0.004722 300/17.64694/60/60 = 0.004722
*/ */
let filterDrainRate = this.databaseServer.getTables().hideout.settings.airFilterUnitFlowRate * this.hideoutConfig.runIntervalSeconds; let filterDrainRate = this.databaseServer.getTables().hideout.settings.airFilterUnitFlowRate *
this.hideoutConfig.runIntervalSeconds;
// Hideout management resource consumption bonus: // Hideout management resource consumption bonus:
const hideoutManagementConsumptionBonus = 1.0 - this.getHideoutManagementConsumptionBonus(pmcData); const hideoutManagementConsumptionBonus = 1.0 - this.getHideoutManagementConsumptionBonus(pmcData);
filterDrainRate *= hideoutManagementConsumptionBonus; filterDrainRate *= hideoutManagementConsumptionBonus;
@ -618,9 +679,9 @@ export class HideoutHelper
{ {
if (airFilterArea.slots[i].item) if (airFilterArea.slots[i].item)
{ {
let resourceValue = (airFilterArea.slots[i].item[0].upd?.Resource) let resourceValue = (airFilterArea.slots[i].item[0].upd?.Resource) ?
? airFilterArea.slots[i].item[0].upd.Resource.Value airFilterArea.slots[i].item[0].upd.Resource.Value :
: null; null;
if (!resourceValue) if (!resourceValue)
{ {
resourceValue = 300 - filterDrainRate; resourceValue = 300 - filterDrainRate;
@ -634,7 +695,7 @@ export class HideoutHelper
resourceValue = Math.round(resourceValue * 10000) / 10000; resourceValue = Math.round(resourceValue * 10000) / 10000;
pointsConsumed = Math.round(pointsConsumed * 10000) / 10000; pointsConsumed = Math.round(pointsConsumed * 10000) / 10000;
//check unit consumed for increment skill point // check unit consumed for increment skill point
if (pmcData && Math.floor(pointsConsumed / 10) >= 1) if (pmcData && Math.floor(pointsConsumed / 10) >= 1)
{ {
this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, 1); this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, 1);
@ -647,8 +708,8 @@ export class HideoutHelper
StackObjectsCount: 1, StackObjectsCount: 1,
Resource: { Resource: {
Value: resourceValue, Value: resourceValue,
UnitsConsumed: pointsConsumed UnitsConsumed: pointsConsumed,
} },
}; };
this.logger.debug(`Air filter: ${resourceValue} filter left on slot ${i + 1}`); this.logger.debug(`Air filter: ${resourceValue} filter left on slot ${i + 1}`);
break; // Break here to avoid updating all filters break; // Break here to avoid updating all filters
@ -662,11 +723,13 @@ export class HideoutHelper
} }
} }
} }
protected updateBitcoinFarm(pmcData: IPmcData, btcFarmCGs: number, isGeneratorOn: boolean): Production protected updateBitcoinFarm(pmcData: IPmcData, btcFarmCGs: number, isGeneratorOn: boolean): Production
{ {
const btcProd = pmcData.Hideout.Production[HideoutHelper.bitcoinFarm]; const btcProd = pmcData.Hideout.Production[HideoutHelper.bitcoinFarm];
const bitcoinProdData = this.databaseServer.getTables().hideout.production.find(p => p._id === "5d5c205bd582a50d042a3c0e"); const bitcoinProdData = this.databaseServer.getTables().hideout.production.find((p) =>
p._id === "5d5c205bd582a50d042a3c0e"
);
const coinSlotCount = this.getBTCSlots(pmcData); const coinSlotCount = this.getBTCSlots(pmcData);
// Full on bitcoins, halt progress // Full on bitcoins, halt progress
@ -718,7 +781,8 @@ export class HideoutHelper
} }
*/ */
// BSG finally fixed their settings, they now get loaded from the settings and used in the client // BSG finally fixed their settings, they now get loaded from the settings and used in the client
const coinCraftTimeSeconds = bitcoinProdData.productionTime / (1 + (btcFarmCGs - 1) * this.databaseServer.getTables().hideout.settings.gpuBoostRate); const coinCraftTimeSeconds = bitcoinProdData.productionTime /
(1 + (btcFarmCGs - 1) * this.databaseServer.getTables().hideout.settings.gpuBoostRate);
while (btcProd.Progress > coinCraftTimeSeconds) while (btcProd.Progress > coinCraftTimeSeconds)
{ {
if (btcProd.Products.length < coinSlotCount) if (btcProd.Products.length < coinSlotCount)
@ -753,8 +817,8 @@ export class HideoutHelper
_id: this.hashUtil.generate(), _id: this.hashUtil.generate(),
_tpl: "59faff1d86f7746c51718c9c", _tpl: "59faff1d86f7746c51718c9c",
upd: { upd: {
StackObjectsCount: 1 StackObjectsCount: 1,
} },
}); });
btcProd.Progress -= coinCraftTimeSeconds; btcProd.Progress -= coinCraftTimeSeconds;
@ -767,13 +831,17 @@ export class HideoutHelper
* @param recipe Hideout production recipe being crafted we need the ticks for * @param recipe Hideout production recipe being crafted we need the ticks for
* @returns Amount of time elapsed in seconds * @returns Amount of time elapsed in seconds
*/ */
protected getTimeElapsedSinceLastServerTick(pmcData: IPmcData, isGeneratorOn: boolean, recipe: IHideoutProduction = null): number protected getTimeElapsedSinceLastServerTick(
pmcData: IPmcData,
isGeneratorOn: boolean,
recipe: IHideoutProduction = null,
): number
{ {
// Reduce time elapsed (and progress) when generator is off // Reduce time elapsed (and progress) when generator is off
let timeElapsed = this.timeUtil.getTimestamp() - pmcData.Hideout.sptUpdateLastRunTimestamp; let timeElapsed = this.timeUtil.getTimestamp() - pmcData.Hideout.sptUpdateLastRunTimestamp;
if (recipe?.areaType === HideoutAreas.LAVATORY) // Lavatory works at 100% when power is on / off if (recipe?.areaType === HideoutAreas.LAVATORY)
{ { // Lavatory works at 100% when power is on / off
return timeElapsed; return timeElapsed;
} }
@ -792,7 +860,9 @@ export class HideoutHelper
*/ */
protected getBTCSlots(pmcData: IPmcData): number protected getBTCSlots(pmcData: IPmcData): number
{ {
const bitcoinProduction = this.databaseServer.getTables().hideout.production.find(p => p._id === HideoutHelper.bitcoinFarm); const bitcoinProduction = this.databaseServer.getTables().hideout.production.find((p) =>
p._id === HideoutHelper.bitcoinFarm
);
const productionSlots = bitcoinProduction?.productionLimitCount || 3; const productionSlots = bitcoinProduction?.productionLimitCount || 3;
const hasManagementSkillSlots = this.profileHelper.hasEliteSkillLevel(SkillTypes.HIDEOUT_MANAGEMENT, pmcData); const hasManagementSkillSlots = this.profileHelper.hasEliteSkillLevel(SkillTypes.HIDEOUT_MANAGEMENT, pmcData);
const managementSlotsCount = this.getBitcoinMinerContainerSlotSize() || 2; const managementSlotsCount = this.getBitcoinMinerContainerSlotSize() || 2;
@ -805,7 +875,8 @@ export class HideoutHelper
*/ */
protected getBitcoinMinerContainerSlotSize(): number protected getBitcoinMinerContainerSlotSize(): number
{ {
return this.databaseServer.getTables().globals.config.SkillsSettings.HideoutManagement.EliteSlots.BitcoinFarm.Container; return this.databaseServer.getTables().globals.config.SkillsSettings.HideoutManagement.EliteSlots.BitcoinFarm
.Container;
} }
/** /**
@ -826,11 +897,13 @@ export class HideoutHelper
// at level 1 you already get 0.5%, so it goes up until level 50. For some reason the wiki // at level 1 you already get 0.5%, so it goes up until level 50. For some reason the wiki
// says that it caps at level 51 with 25% but as per dump data that is incorrect apparently // says that it caps at level 51 with 25% but as per dump data that is incorrect apparently
let roundedLevel = Math.floor(hideoutManagementSkill.Progress / 100); let roundedLevel = Math.floor(hideoutManagementSkill.Progress / 100);
roundedLevel = (roundedLevel === 51) roundedLevel = (roundedLevel === 51) ?
? roundedLevel - 1 roundedLevel - 1 :
: roundedLevel; roundedLevel;
return (roundedLevel * this.databaseServer.getTables().globals.config.SkillsSettings.HideoutManagement.ConsumptionReductionPerLevel) / 100; return (roundedLevel *
this.databaseServer.getTables().globals.config.SkillsSettings.HideoutManagement
.ConsumptionReductionPerLevel) / 100;
} }
/** /**
@ -841,7 +914,7 @@ export class HideoutHelper
*/ */
protected getCraftingSkillProductionTimeReduction(pmcData: IPmcData, productionTime: number): number protected getCraftingSkillProductionTimeReduction(pmcData: IPmcData, productionTime: number): number
{ {
const craftingSkill = pmcData.Skills.Common.find(x=> x.Id === SkillTypes.CRAFTING); const craftingSkill = pmcData.Skills.Common.find((x) => x.Id === SkillTypes.CRAFTING);
if (!craftingSkill) if (!craftingSkill)
{ {
return productionTime; return productionTime;
@ -865,7 +938,11 @@ export class HideoutHelper
* @param sessionId Session id * @param sessionId Session id
* @returns IItemEventRouterResponse * @returns IItemEventRouterResponse
*/ */
public getBTC(pmcData: IPmcData, request: IHideoutTakeProductionRequestData, sessionId: string): IItemEventRouterResponse public getBTC(
pmcData: IPmcData,
request: IHideoutTakeProductionRequestData,
sessionId: string,
): IItemEventRouterResponse
{ {
const output = this.eventOutputHolder.getOutput(sessionId); const output = this.eventOutputHolder.getOutput(sessionId);
@ -875,7 +952,7 @@ export class HideoutHelper
{ {
const errorMsg = this.localisationService.getText("hideout-no_bitcoins_to_collect"); const errorMsg = this.localisationService.getText("hideout-no_bitcoins_to_collect");
this.logger.error(errorMsg); this.logger.error(errorMsg);
return this.httpResponse.appendErrorToOutput(output, errorMsg); return this.httpResponse.appendErrorToOutput(output, errorMsg);
} }
@ -911,9 +988,9 @@ export class HideoutHelper
items: [{ items: [{
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
item_id: HideoutHelper.bitcoin, item_id: HideoutHelper.bitcoin,
count: pmcData.Hideout.Production[HideoutHelper.bitcoinFarm].Products.length count: pmcData.Hideout.Production[HideoutHelper.bitcoinFarm].Products.length,
}], }],
tid: "ragfair" tid: "ragfair",
}; };
} }
@ -923,9 +1000,9 @@ export class HideoutHelper
*/ */
public unlockHideoutWallInProfile(pmcProfile: IPmcData): void public unlockHideoutWallInProfile(pmcProfile: IPmcData): void
{ {
const waterCollector = pmcProfile.Hideout.Areas.find(x => x.type === HideoutAreas.WATER_COLLECTOR); const waterCollector = pmcProfile.Hideout.Areas.find((x) => x.type === HideoutAreas.WATER_COLLECTOR);
const medStation = pmcProfile.Hideout.Areas.find(x => x.type === HideoutAreas.MEDSTATION); const medStation = pmcProfile.Hideout.Areas.find((x) => x.type === HideoutAreas.MEDSTATION);
const wall = pmcProfile.Hideout.Areas.find(x => x.type === HideoutAreas.EMERGENCY_WALL); const wall = pmcProfile.Hideout.Areas.find((x) => x.type === HideoutAreas.EMERGENCY_WALL);
// No collector or med station, skip // No collector or med station, skip
if (!(waterCollector && medStation)) if (!(waterCollector && medStation))
@ -947,9 +1024,9 @@ export class HideoutHelper
*/ */
protected hideoutImprovementIsComplete(improvement: IHideoutImprovement): boolean protected hideoutImprovementIsComplete(improvement: IHideoutImprovement): boolean
{ {
return improvement?.completed return improvement?.completed ?
? true true :
: false; false;
} }
/** /**
@ -961,10 +1038,13 @@ export class HideoutHelper
for (const improvementId in pmcProfile.Hideout.Improvement) for (const improvementId in pmcProfile.Hideout.Improvement)
{ {
const improvementDetails = pmcProfile.Hideout.Improvement[improvementId]; const improvementDetails = pmcProfile.Hideout.Improvement[improvementId];
if (improvementDetails.completed === false && improvementDetails.improveCompleteTimestamp < this.timeUtil.getTimestamp()) if (
improvementDetails.completed === false &&
improvementDetails.improveCompleteTimestamp < this.timeUtil.getTimestamp()
)
{ {
improvementDetails.completed = true; improvementDetails.completed = true;
} }
} }
} }
} }

View File

@ -18,11 +18,11 @@ export class HttpServerHelper
json: "application/json", json: "application/json",
png: "image/png", png: "image/png",
svg: "image/svg+xml", svg: "image/svg+xml",
txt: "text/plain" txt: "text/plain",
}; };
constructor( constructor(
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP); this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP);
@ -60,7 +60,7 @@ export class HttpServerHelper
public sendTextJson(resp: any, output: any): void public sendTextJson(resp: any, output: any): void
{ {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
resp.writeHead(200, "OK", { "Content-Type": this.mime["json"] }); resp.writeHead(200, "OK", {"Content-Type": this.mime["json"]});
resp.end(output); resp.end(output);
} }
} }

View File

@ -39,7 +39,7 @@ export class InRaidHelper
@inject("PaymentHelper") protected paymentHelper: PaymentHelper, @inject("PaymentHelper") protected paymentHelper: PaymentHelper,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("ProfileFixerService") protected profileFixerService: ProfileFixerService, @inject("ProfileFixerService") protected profileFixerService: ProfileFixerService,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.lostOnDeathConfig = this.configServer.getConfig(ConfigTypes.LOST_ON_DEATH); this.lostOnDeathConfig = this.configServer.getConfig(ConfigTypes.LOST_ON_DEATH);
@ -95,8 +95,13 @@ export class InRaidHelper
{ {
return acc + standingForKill; return acc + standingForKill;
} }
this.logger.warning(this.localisationService.getText("inraid-missing_standing_for_kill", {victimSide: victim.Side, victimRole: victim.Role})); this.logger.warning(
this.localisationService.getText("inraid-missing_standing_for_kill", {
victimSide: victim.Side,
victimRole: victim.Role,
}),
);
return acc; return acc;
}, existingFenceStanding); }, existingFenceStanding);
@ -116,7 +121,7 @@ export class InRaidHelper
// Scavs and bosses // Scavs and bosses
return botTypes[victim.Role.toLowerCase()]?.experience?.standingForKill; return botTypes[victim.Role.toLowerCase()]?.experience?.standingForKill;
} }
// PMCs - get by bear/usec // PMCs - get by bear/usec
return botTypes[victim.Side.toLowerCase()]?.experience?.standingForKill; return botTypes[victim.Side.toLowerCase()]?.experience?.standingForKill;
} }
@ -131,7 +136,11 @@ export class InRaidHelper
* @param sessionID Session id * @param sessionID Session id
* @returns Reset profile object * @returns Reset profile object
*/ */
public updateProfileBaseStats(profileData: IPmcData, saveProgressRequest: ISaveProgressRequestData, sessionID: string): IPmcData public updateProfileBaseStats(
profileData: IPmcData,
saveProgressRequest: ISaveProgressRequestData,
sessionID: string,
): IPmcData
{ {
// remove old skill fatigue // remove old skill fatigue
this.resetSkillPointsEarnedDuringRaid(saveProgressRequest.profile); this.resetSkillPointsEarnedDuringRaid(saveProgressRequest.profile);
@ -168,7 +177,9 @@ export class InRaidHelper
if (matchingPreRaidCounter.value !== postRaidValue) if (matchingPreRaidCounter.value !== postRaidValue)
{ {
this.logger.error(`Backendcounter: ${backendCounterKey} value is different post raid, old: ${matchingPreRaidCounter.value} new: ${postRaidValue}`); this.logger.error(
`Backendcounter: ${backendCounterKey} value is different post raid, old: ${matchingPreRaidCounter.value} new: ${postRaidValue}`,
);
} }
} }
@ -199,7 +210,6 @@ export class InRaidHelper
return profileData; return profileData;
} }
/** /**
* Look for quests not are now status = fail that were not failed pre-raid and run the failQuest() function * Look for quests not are now status = fail that were not failed pre-raid and run the failQuest() function
* @param sessionId Player id * @param sessionId Player id
@ -207,7 +217,12 @@ export class InRaidHelper
* @param preRaidQuests Quests prior to starting raid * @param preRaidQuests Quests prior to starting raid
* @param postRaidQuests Quest after raid * @param postRaidQuests Quest after raid
*/ */
protected processFailedQuests(sessionId: string, pmcData: IPmcData, preRaidQuests: IQuestStatus[], postRaidQuests: IQuestStatus[]): void protected processFailedQuests(
sessionId: string,
pmcData: IPmcData,
preRaidQuests: IQuestStatus[],
postRaidQuests: IQuestStatus[],
): void
{ {
if (!preRaidQuests) if (!preRaidQuests)
{ {
@ -219,7 +234,7 @@ export class InRaidHelper
for (const postRaidQuest of postRaidQuests) for (const postRaidQuest of postRaidQuests)
{ {
// Find matching pre-raid quest // Find matching pre-raid quest
const preRaidQuest = preRaidQuests?.find(x => x.qid === postRaidQuest.qid); const preRaidQuest = preRaidQuests?.find((x) => x.qid === postRaidQuest.qid);
if (preRaidQuest) if (preRaidQuest)
{ {
// Post-raid quest is failed but wasn't pre-raid // Post-raid quest is failed but wasn't pre-raid
@ -230,12 +245,11 @@ export class InRaidHelper
const failBody: IFailQuestRequestData = { const failBody: IFailQuestRequestData = {
Action: "QuestComplete", Action: "QuestComplete",
qid: postRaidQuest.qid, qid: postRaidQuest.qid,
removeExcessItems: true removeExcessItems: true,
}; };
this.questHelper.failQuest(pmcData, failBody, sessionId); this.questHelper.failQuest(pmcData, failBody, sessionId);
} }
} }
} }
} }
@ -252,7 +266,10 @@ export class InRaidHelper
* @param saveProgressRequest post-raid request * @param saveProgressRequest post-raid request
* @param profileData player profile on server * @param profileData player profile on server
*/ */
protected transferPostRaidLimbEffectsToProfile(saveProgressRequest: ISaveProgressRequestData, profileData: IPmcData): void protected transferPostRaidLimbEffectsToProfile(
saveProgressRequest: ISaveProgressRequestData,
profileData: IPmcData,
): void
{ {
// Iterate over each body part // Iterate over each body part
for (const bodyPartId in saveProgressRequest.profile.Health.BodyParts) for (const bodyPartId in saveProgressRequest.profile.Health.BodyParts)
@ -287,7 +304,10 @@ export class InRaidHelper
* @param tradersServerProfile Server * @param tradersServerProfile Server
* @param tradersClientProfile Client * @param tradersClientProfile Client
*/ */
protected applyTraderStandingAdjustments(tradersServerProfile: Record<string, TraderInfo>, tradersClientProfile: Record<string, TraderInfo>): void protected applyTraderStandingAdjustments(
tradersServerProfile: Record<string, TraderInfo>,
tradersClientProfile: Record<string, TraderInfo>,
): void
{ {
for (const traderId in tradersClientProfile) for (const traderId in tradersClientProfile)
{ {
@ -356,12 +376,13 @@ export class InRaidHelper
public removeSpawnedInSessionPropertyFromItems(postRaidProfile: IPostRaidPmcData): IPostRaidPmcData public removeSpawnedInSessionPropertyFromItems(postRaidProfile: IPostRaidPmcData): IPostRaidPmcData
{ {
const dbItems = this.databaseServer.getTables().templates.items; const dbItems = this.databaseServer.getTables().templates.items;
const itemsToRemovePropertyFrom = postRaidProfile.Inventory.items.filter(x => const itemsToRemovePropertyFrom = postRaidProfile.Inventory.items.filter((x) =>
{ {
// Has upd object + upd.SpawnedInSession property + not a quest item // Has upd object + upd.SpawnedInSession property + not a quest item
return "upd" in x && "SpawnedInSession" in x.upd return "upd" in x && "SpawnedInSession" in x.upd &&
&& !dbItems[x._tpl]._props.QuestItem !dbItems[x._tpl]._props.QuestItem &&
&& !(this.inRaidConfig.keepFiRSecureContainerOnDeath && this.itemHelper.itemIsInsideContainer(x, "SecuredContainer", postRaidProfile.Inventory.items)); !(this.inRaidConfig.keepFiRSecureContainerOnDeath &&
this.itemHelper.itemIsInsideContainer(x, "SecuredContainer", postRaidProfile.Inventory.items));
}); });
for (const item of itemsToRemovePropertyFrom) for (const item of itemsToRemovePropertyFrom)
@ -409,7 +430,7 @@ export class InRaidHelper
public deleteInventory(pmcData: IPmcData, sessionID: string): void public deleteInventory(pmcData: IPmcData, sessionID: string): void
{ {
// Get inventory item ids to remove from players profile // Get inventory item ids to remove from players profile
const itemIdsToDeleteFromProfile = this.getInventoryItemsLostOnDeath(pmcData).map(x => x._id); const itemIdsToDeleteFromProfile = this.getInventoryItemsLostOnDeath(pmcData).map((x) => x._id);
for (const itemId of itemIdsToDeleteFromProfile) for (const itemId of itemIdsToDeleteFromProfile)
{ {
this.inventoryHelper.removeItem(pmcData, itemId, sessionID); this.inventoryHelper.removeItem(pmcData, itemId, sessionID);
@ -426,30 +447,30 @@ export class InRaidHelper
*/ */
protected getInventoryItemsLostOnDeath(pmcProfile: IPmcData): Item[] protected getInventoryItemsLostOnDeath(pmcProfile: IPmcData): Item[]
{ {
const inventoryItems = pmcProfile.Inventory.items ?? []; const inventoryItems = pmcProfile.Inventory.items ?? [];
const equipment = pmcProfile?.Inventory?.equipment; const equipment = pmcProfile?.Inventory?.equipment;
const questRaidItems = pmcProfile?.Inventory?.questRaidItems; const questRaidItems = pmcProfile?.Inventory?.questRaidItems;
return inventoryItems.filter(x => return inventoryItems.filter((x) =>
{ {
// Keep items flagged as kept after death // Keep items flagged as kept after death
if (this.isItemKeptAfterDeath(pmcProfile, x)) if (this.isItemKeptAfterDeath(pmcProfile, x))
{ {
return false; return false;
} }
// Remove normal items or quest raid items // Remove normal items or quest raid items
if (x.parentId === equipment || x.parentId === questRaidItems) if (x.parentId === equipment || x.parentId === questRaidItems)
{ {
return true; return true;
} }
// Pocket items are not lost on death // Pocket items are not lost on death
if (x.slotId.startsWith("pocket")) if (x.slotId.startsWith("pocket"))
{ {
return true; return true;
} }
return false; return false;
}); });
} }
@ -461,13 +482,13 @@ export class InRaidHelper
*/ */
protected getBaseItemsInRigPocketAndBackpack(pmcData: IPmcData): Item[] protected getBaseItemsInRigPocketAndBackpack(pmcData: IPmcData): Item[]
{ {
const rig = pmcData.Inventory.items.find(x => x.slotId === "TacticalVest"); const rig = pmcData.Inventory.items.find((x) => x.slotId === "TacticalVest");
const pockets = pmcData.Inventory.items.find(x => x.slotId === "Pockets"); const pockets = pmcData.Inventory.items.find((x) => x.slotId === "Pockets");
const backpack = pmcData.Inventory.items.find(x => x.slotId === "Backpack"); const backpack = pmcData.Inventory.items.find((x) => x.slotId === "Backpack");
const baseItemsInRig = pmcData.Inventory.items.filter(x => x.parentId === rig?._id); const baseItemsInRig = pmcData.Inventory.items.filter((x) => x.parentId === rig?._id);
const baseItemsInPockets = pmcData.Inventory.items.filter(x => x.parentId === pockets?._id); const baseItemsInPockets = pmcData.Inventory.items.filter((x) => x.parentId === pockets?._id);
const baseItemsInBackpack = pmcData.Inventory.items.filter(x => x.parentId === backpack?._id); const baseItemsInBackpack = pmcData.Inventory.items.filter((x) => x.parentId === backpack?._id);
return [...baseItemsInRig, ...baseItemsInPockets, ...baseItemsInBackpack]; return [...baseItemsInRig, ...baseItemsInPockets, ...baseItemsInBackpack];
} }
@ -539,7 +560,7 @@ export class InRaidHelper
"pocket1", "pocket1",
"pocket2", "pocket2",
"pocket3", "pocket3",
"pocket4" "pocket4",
]; ];
let inventoryItems: Item[] = []; let inventoryItems: Item[] = [];
@ -574,7 +595,7 @@ export class InRaidHelper
// Add these new found items to our list of inventory items // Add these new found items to our list of inventory items
inventoryItems = [ inventoryItems = [
...inventoryItems, ...inventoryItems,
...foundItems ...foundItems,
]; ];
// Now find the children of these items // Now find the children of these items
@ -583,4 +604,4 @@ export class InRaidHelper
return inventoryItems; return inventoryItems;
} }
} }

View File

@ -32,11 +32,11 @@ import { JsonUtil } from "@spt-aki/utils/JsonUtil";
export interface OwnerInventoryItems export interface OwnerInventoryItems
{ {
/** Inventory items from source */ /** Inventory items from source */
from: Item[] from: Item[];
/** Inventory items at destination */ /** Inventory items at destination */
to: Item[] to: Item[];
sameInventory: boolean, sameInventory: boolean;
isMail: boolean isMail: boolean;
} }
@injectable() @injectable()
@ -58,7 +58,7 @@ export class InventoryHelper
@inject("ContainerHelper") protected containerHelper: ContainerHelper, @inject("ContainerHelper") protected containerHelper: ContainerHelper,
@inject("ProfileHelper") protected profileHelper: ProfileHelper, @inject("ProfileHelper") protected profileHelper: ProfileHelper,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.inventoryConfig = this.configServer.getConfig(ConfigTypes.INVENTORY); this.inventoryConfig = this.configServer.getConfig(ConfigTypes.INVENTORY);
@ -76,7 +76,16 @@ export class InventoryHelper
* @param useSortingTable Allow items to go into sorting table when stash has no space * @param useSortingTable Allow items to go into sorting table when stash has no space
* @returns IItemEventRouterResponse * @returns IItemEventRouterResponse
*/ */
public addItem(pmcData: IPmcData, request: IAddItemRequestData, output: IItemEventRouterResponse, sessionID: string, callback: () => void, foundInRaid = false, addUpd = null, useSortingTable = false): IItemEventRouterResponse public addItem(
pmcData: IPmcData,
request: IAddItemRequestData,
output: IItemEventRouterResponse,
sessionID: string,
callback: () => void,
foundInRaid = false,
addUpd = null,
useSortingTable = false,
): IItemEventRouterResponse
{ {
const itemLib: Item[] = []; // TODO: what is the purpose of this property const itemLib: Item[] = []; // TODO: what is the purpose of this property
const itemsToAdd: IAddItemTempObject[] = []; const itemsToAdd: IAddItemTempObject[] = [];
@ -85,19 +94,21 @@ export class InventoryHelper
{ {
if (requestItem.item_id in this.databaseServer.getTables().globals.ItemPresets) if (requestItem.item_id in this.databaseServer.getTables().globals.ItemPresets)
{ {
const presetItems = this.jsonUtil.clone(this.databaseServer.getTables().globals.ItemPresets[requestItem.item_id]._items); const presetItems = this.jsonUtil.clone(
this.databaseServer.getTables().globals.ItemPresets[requestItem.item_id]._items,
);
itemLib.push(...presetItems); itemLib.push(...presetItems);
requestItem.isPreset = true; requestItem.isPreset = true;
requestItem.item_id = presetItems[0]._id; requestItem.item_id = presetItems[0]._id;
} }
else if (this.paymentHelper.isMoneyTpl(requestItem.item_id)) else if (this.paymentHelper.isMoneyTpl(requestItem.item_id))
{ {
itemLib.push({ _id: requestItem.item_id, _tpl: requestItem.item_id }); itemLib.push({_id: requestItem.item_id, _tpl: requestItem.item_id});
} }
else if (request.tid === Traders.FENCE) else if (request.tid === Traders.FENCE)
{ {
const fenceItems = this.fenceService.getRawFenceAssorts().items; const fenceItems = this.fenceService.getRawFenceAssorts().items;
const itemIndex = fenceItems.findIndex(i => i._id === requestItem.item_id); const itemIndex = fenceItems.findIndex((i) => i._id === requestItem.item_id);
if (itemIndex === -1) if (itemIndex === -1)
{ {
this.logger.debug(`Tried to buy item ${requestItem.item_id} from fence that no longer exists`); this.logger.debug(`Tried to buy item ${requestItem.item_id} from fence that no longer exists`);
@ -105,20 +116,25 @@ export class InventoryHelper
return this.httpResponse.appendErrorToOutput(output, message); return this.httpResponse.appendErrorToOutput(output, message);
} }
const purchasedItemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(fenceItems, requestItem.item_id); const purchasedItemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(
fenceItems,
requestItem.item_id,
);
addUpd = purchasedItemWithChildren[0].upd; // Must persist the fence upd properties (e.g. durability/currentHp) addUpd = purchasedItemWithChildren[0].upd; // Must persist the fence upd properties (e.g. durability/currentHp)
itemLib.push(...purchasedItemWithChildren); itemLib.push(...purchasedItemWithChildren);
} }
else if (request.tid === "RandomLootContainer") else if (request.tid === "RandomLootContainer")
{ {
itemLib.push({ _id: requestItem.item_id, _tpl: requestItem.item_id }); itemLib.push({_id: requestItem.item_id, _tpl: requestItem.item_id});
} }
else else
{ {
// Only grab the relevant trader items and add unique values // Only grab the relevant trader items and add unique values
const traderItems = this.traderAssortHelper.getAssort(sessionID, request.tid).items; const traderItems = this.traderAssortHelper.getAssort(sessionID, request.tid).items;
const relevantItems = this.itemHelper.findAndReturnChildrenAsItems(traderItems, requestItem.item_id); const relevantItems = this.itemHelper.findAndReturnChildrenAsItems(traderItems, requestItem.item_id);
const toAdd = relevantItems.filter(traderItem => !itemLib.some(item => traderItem._id === item._id)); // what's this const toAdd = relevantItems.filter((traderItem) =>
!itemLib.some((item) => traderItem._id === item._id)
); // what's this
itemLib.push(...toAdd); itemLib.push(...toAdd);
} }
@ -133,7 +149,15 @@ export class InventoryHelper
for (const itemToAdd of itemsToAdd) for (const itemToAdd of itemsToAdd)
{ {
const errorOutput = this.placeItemInInventory(itemToAdd, stashFS2D, sortingTableFS2D, itemLib, pmcData.Inventory, useSortingTable, output); const errorOutput = this.placeItemInInventory(
itemToAdd,
stashFS2D,
sortingTableFS2D,
itemLib,
pmcData.Inventory,
useSortingTable,
output,
);
if (errorOutput) if (errorOutput)
{ {
return errorOutput; return errorOutput;
@ -151,9 +175,9 @@ export class InventoryHelper
catch (err) catch (err)
{ {
// Callback failed // Callback failed
const message = typeof err === "string" const message = typeof err === "string" ?
? err err :
: this.localisationService.getText("http-unknown_error"); this.localisationService.getText("http-unknown_error");
return this.httpResponse.appendErrorToOutput(output, message); return this.httpResponse.appendErrorToOutput(output, message);
} }
@ -163,7 +187,7 @@ export class InventoryHelper
{ {
let idForItemToAdd = this.hashUtil.generate(); let idForItemToAdd = this.hashUtil.generate();
const toDo: string[][] = [[itemToAdd.itemRef._id, idForItemToAdd]]; // WHAT IS THIS?! const toDo: string[][] = [[itemToAdd.itemRef._id, idForItemToAdd]]; // WHAT IS THIS?!
let upd: Upd = { StackObjectsCount: itemToAdd.count }; let upd: Upd = {StackObjectsCount: itemToAdd.count};
// If item being added is preset, load preset's upd data too. // If item being added is preset, load preset's upd data too.
if (itemToAdd.isPreset) if (itemToAdd.isPreset)
@ -191,7 +215,7 @@ export class InventoryHelper
// add ragfair upd properties // add ragfair upd properties
if (addUpd) if (addUpd)
{ {
upd = { ...addUpd, ...upd }; upd = {...addUpd, ...upd};
} }
// Hideout items need to be marked as found in raid // Hideout items need to be marked as found in raid
@ -200,7 +224,7 @@ export class InventoryHelper
{ {
upd.SpawnedInSession = true; upd.SpawnedInSession = true;
} }
// Remove invalid properties prior to adding to inventory // Remove invalid properties prior to adding to inventory
if (upd.UnlimitedCount !== undefined) if (upd.UnlimitedCount !== undefined)
{ {
@ -222,8 +246,8 @@ export class InventoryHelper
_tpl: itemToAdd.itemRef._tpl, _tpl: itemToAdd.itemRef._tpl,
parentId: itemToAdd.containerId, parentId: itemToAdd.containerId,
slotId: "hideout", slotId: "hideout",
location: { x: itemToAdd.location.x, y: itemToAdd.location.y, r: itemToAdd.location.rotation ? 1 : 0 }, location: {x: itemToAdd.location.x, y: itemToAdd.location.y, r: itemToAdd.location.rotation ? 1 : 0},
upd: this.jsonUtil.clone(upd) upd: this.jsonUtil.clone(upd),
}); });
pmcData.Inventory.items.push({ pmcData.Inventory.items.push({
@ -231,8 +255,8 @@ export class InventoryHelper
_tpl: itemToAdd.itemRef._tpl, _tpl: itemToAdd.itemRef._tpl,
parentId: itemToAdd.containerId, parentId: itemToAdd.containerId,
slotId: "hideout", slotId: "hideout",
location: { x: itemToAdd.location.x, y: itemToAdd.location.y, r: itemToAdd.location.rotation ? 1 : 0 }, location: {x: itemToAdd.location.x, y: itemToAdd.location.y, r: itemToAdd.location.rotation ? 1 : 0},
upd: this.jsonUtil.clone(upd) // Clone upd to prevent multi-purchases of same item referencing same upd object in memory upd: this.jsonUtil.clone(upd), // Clone upd to prevent multi-purchases of same item referencing same upd object in memory
}); });
if (this.itemHelper.isOfBaseclass(itemToAdd.itemRef._tpl, BaseClasses.AMMO_BOX)) if (this.itemHelper.isOfBaseclass(itemToAdd.itemRef._tpl, BaseClasses.AMMO_BOX))
@ -255,7 +279,7 @@ export class InventoryHelper
// If its from ItemPreset, load preset's upd data too. // If its from ItemPreset, load preset's upd data too.
if (itemToAdd.isPreset) if (itemToAdd.isPreset)
{ {
upd = { StackObjectsCount: itemToAdd.count }; upd = {StackObjectsCount: itemToAdd.count};
for (const updID in itemLib[tmpKey].upd) for (const updID in itemLib[tmpKey].upd)
{ {
@ -278,8 +302,9 @@ export class InventoryHelper
location: { location: {
x: itemToAdd.location.x, x: itemToAdd.location.x,
y: itemToAdd.location.y, y: itemToAdd.location.y,
r: "Horizontal" }, r: "Horizontal",
upd: this.jsonUtil.clone(upd) },
upd: this.jsonUtil.clone(upd),
}); });
pmcData.Inventory.items.push({ pmcData.Inventory.items.push({
@ -290,8 +315,9 @@ export class InventoryHelper
location: { location: {
x: itemToAdd.location.x, x: itemToAdd.location.x,
y: itemToAdd.location.y, y: itemToAdd.location.y,
r: "Horizontal" }, r: "Horizontal",
upd: this.jsonUtil.clone(upd) },
upd: this.jsonUtil.clone(upd),
}); });
} }
else else
@ -310,7 +336,7 @@ export class InventoryHelper
parentId: toDo[0][1], parentId: toDo[0][1],
slotId: slotID, slotId: slotID,
...itemLocation, ...itemLocation,
upd: this.jsonUtil.clone(upd) upd: this.jsonUtil.clone(upd),
}); });
pmcData.Inventory.items.push({ pmcData.Inventory.items.push({
@ -319,7 +345,7 @@ export class InventoryHelper
parentId: toDo[0][1], parentId: toDo[0][1],
slotId: itemLib[tmpKey].slotId, slotId: itemLib[tmpKey].slotId,
...itemLocation, ...itemLocation,
upd: this.jsonUtil.clone(upd) upd: this.jsonUtil.clone(upd),
}); });
this.logger.debug(`Added ${itemLib[tmpKey]._tpl} with id: ${idForItemToAdd} to inventory`); this.logger.debug(`Added ${itemLib[tmpKey]._tpl} with id: ${idForItemToAdd} to inventory`);
} }
@ -340,13 +366,21 @@ export class InventoryHelper
* @param itemToAdd Item to add to inventory * @param itemToAdd Item to add to inventory
* @param stashFS2D Two dimentional stash map * @param stashFS2D Two dimentional stash map
* @param sortingTableFS2D Two dimentional sorting table stash map * @param sortingTableFS2D Two dimentional sorting table stash map
* @param itemLib * @param itemLib
* @param pmcData Player profile * @param pmcData Player profile
* @param useSortingTable Should sorting table be used for overflow items when no inventory space for item * @param useSortingTable Should sorting table be used for overflow items when no inventory space for item
* @param output Client output object * @param output Client output object
* @returns Client error output if placing item failed * @returns Client error output if placing item failed
*/ */
protected placeItemInInventory(itemToAdd: IAddItemTempObject, stashFS2D: number[][], sortingTableFS2D: number[][], itemLib: Item[], playerInventory: Inventory, useSortingTable: boolean, output: IItemEventRouterResponse): IItemEventRouterResponse protected placeItemInInventory(
itemToAdd: IAddItemTempObject,
stashFS2D: number[][],
sortingTableFS2D: number[][],
itemLib: Item[],
playerInventory: Inventory,
useSortingTable: boolean,
output: IItemEventRouterResponse,
): IItemEventRouterResponse
{ {
const itemSize = this.getItemSize(itemToAdd.itemRef._tpl, itemToAdd.itemRef._id, itemLib); const itemSize = this.getItemSize(itemToAdd.itemRef._tpl, itemToAdd.itemRef._id, itemLib);
@ -360,16 +394,26 @@ export class InventoryHelper
try try
{ {
stashFS2D = this.containerHelper.fillContainerMapWithItem(stashFS2D, findSlotResult.x, findSlotResult.y, itemSizeX, itemSizeY, false); // TODO: rotation not passed in, bad? stashFS2D = this.containerHelper.fillContainerMapWithItem(
stashFS2D,
findSlotResult.x,
findSlotResult.y,
itemSizeX,
itemSizeY,
false,
); // TODO: rotation not passed in, bad?
} }
catch (err) catch (err)
{ {
const errorText = typeof err === "string" const errorText = typeof err === "string" ?
? ` -> ${err}` ` -> ${err}` :
: ""; "";
this.logger.error(this.localisationService.getText("inventory-fill_container_failed", errorText)); this.logger.error(this.localisationService.getText("inventory-fill_container_failed", errorText));
return this.httpResponse.appendErrorToOutput(output, this.localisationService.getText("inventory-no_stash_space")); return this.httpResponse.appendErrorToOutput(
output,
this.localisationService.getText("inventory-no_stash_space"),
);
} }
// Store details for object, incuding container item will be placed in // Store details for object, incuding container item will be placed in
itemToAdd.containerId = playerInventory.stash; itemToAdd.containerId = playerInventory.stash;
@ -377,7 +421,7 @@ export class InventoryHelper
x: findSlotResult.x, x: findSlotResult.x,
y: findSlotResult.y, y: findSlotResult.y,
r: findSlotResult.rotation ? 1 : 0, r: findSlotResult.rotation ? 1 : 0,
rotation: findSlotResult.rotation rotation: findSlotResult.rotation,
}; };
// Success! exit // Success! exit
@ -387,19 +431,33 @@ export class InventoryHelper
// Space not found in main stash, use sorting table // Space not found in main stash, use sorting table
if (useSortingTable) if (useSortingTable)
{ {
const findSortingSlotResult = this.containerHelper.findSlotForItem(sortingTableFS2D, itemSize[0], itemSize[1]); const findSortingSlotResult = this.containerHelper.findSlotForItem(
sortingTableFS2D,
itemSize[0],
itemSize[1],
);
const itemSizeX = findSortingSlotResult.rotation ? itemSize[1] : itemSize[0]; const itemSizeX = findSortingSlotResult.rotation ? itemSize[1] : itemSize[0];
const itemSizeY = findSortingSlotResult.rotation ? itemSize[0] : itemSize[1]; const itemSizeY = findSortingSlotResult.rotation ? itemSize[0] : itemSize[1];
try try
{ {
sortingTableFS2D = this.containerHelper.fillContainerMapWithItem(sortingTableFS2D, findSortingSlotResult.x, findSortingSlotResult.y, itemSizeX, itemSizeY, false); // TODO: rotation not passed in, bad? sortingTableFS2D = this.containerHelper.fillContainerMapWithItem(
sortingTableFS2D,
findSortingSlotResult.x,
findSortingSlotResult.y,
itemSizeX,
itemSizeY,
false,
); // TODO: rotation not passed in, bad?
} }
catch (err) catch (err)
{ {
const errorText = typeof err === "string" ? ` -> ${err}` : ""; const errorText = typeof err === "string" ? ` -> ${err}` : "";
this.logger.error(this.localisationService.getText("inventory-fill_container_failed", errorText)); this.logger.error(this.localisationService.getText("inventory-fill_container_failed", errorText));
return this.httpResponse.appendErrorToOutput(output, this.localisationService.getText("inventory-no_stash_space")); return this.httpResponse.appendErrorToOutput(
output,
this.localisationService.getText("inventory-no_stash_space"),
);
} }
// Store details for object, incuding container item will be placed in // Store details for object, incuding container item will be placed in
@ -408,12 +466,15 @@ export class InventoryHelper
x: findSortingSlotResult.x, x: findSortingSlotResult.x,
y: findSortingSlotResult.y, y: findSortingSlotResult.y,
r: findSortingSlotResult.rotation ? 1 : 0, r: findSortingSlotResult.rotation ? 1 : 0,
rotation: findSortingSlotResult.rotation rotation: findSortingSlotResult.rotation,
}; };
} }
else else
{ {
return this.httpResponse.appendErrorToOutput(output, this.localisationService.getText("inventory-no_stash_space")); return this.httpResponse.appendErrorToOutput(
output,
this.localisationService.getText("inventory-no_stash_space"),
);
} }
} }
@ -427,7 +488,14 @@ export class InventoryHelper
* @param output object to send to client * @param output object to send to client
* @param foundInRaid should ammo be FiR * @param foundInRaid should ammo be FiR
*/ */
protected hydrateAmmoBoxWithAmmo(pmcData: IPmcData, itemToAdd: IAddItemTempObject, parentId: string, sessionID: string, output: IItemEventRouterResponse, foundInRaid: boolean): void protected hydrateAmmoBoxWithAmmo(
pmcData: IPmcData,
itemToAdd: IAddItemTempObject,
parentId: string,
sessionID: string,
output: IItemEventRouterResponse,
foundInRaid: boolean,
): void
{ {
const itemInfo = this.itemHelper.getItem(itemToAdd.itemRef._tpl)[1]; const itemInfo = this.itemHelper.getItem(itemToAdd.itemRef._tpl)[1];
const stackSlots = itemInfo._props.StackSlots; const stackSlots = itemInfo._props.StackSlots;
@ -450,7 +518,7 @@ export class InventoryHelper
parentId: parentId, parentId: parentId,
slotId: "cartridges", slotId: "cartridges",
location: location, location: location,
upd: { StackObjectsCount: ammoStackSize } upd: {StackObjectsCount: ammoStackSize},
}; };
if (foundInRaid) if (foundInRaid)
@ -472,7 +540,6 @@ export class InventoryHelper
} }
/** /**
*
* @param assortItems Items to add to inventory * @param assortItems Items to add to inventory
* @param requestItem Details of purchased item to add to inventory * @param requestItem Details of purchased item to add to inventory
* @param result Array split stacks are added to * @param result Array split stacks are added to
@ -488,18 +555,21 @@ export class InventoryHelper
const itemToAdd: IAddItemTempObject = { const itemToAdd: IAddItemTempObject = {
itemRef: item, itemRef: item,
count: requestItem.count, count: requestItem.count,
isPreset: requestItem.isPreset }; isPreset: requestItem.isPreset,
};
// Split stacks if the size is higher than allowed by items StackMaxSize property // Split stacks if the size is higher than allowed by items StackMaxSize property
let maxStackCount = 1; let maxStackCount = 1;
if (requestItem.count > itemDetails._props.StackMaxSize) if (requestItem.count > itemDetails._props.StackMaxSize)
{ {
let remainingCountOfItemToAdd = requestItem.count; let remainingCountOfItemToAdd = requestItem.count;
const calc = requestItem.count - (Math.floor(requestItem.count / itemDetails._props.StackMaxSize) * itemDetails._props.StackMaxSize); const calc = requestItem.count -
(Math.floor(requestItem.count / itemDetails._props.StackMaxSize) *
itemDetails._props.StackMaxSize);
maxStackCount = (calc > 0) maxStackCount = (calc > 0) ?
? maxStackCount + Math.floor(remainingCountOfItemToAdd / itemDetails._props.StackMaxSize) maxStackCount + Math.floor(remainingCountOfItemToAdd / itemDetails._props.StackMaxSize) :
: Math.floor(remainingCountOfItemToAdd / itemDetails._props.StackMaxSize); Math.floor(remainingCountOfItemToAdd / itemDetails._props.StackMaxSize);
// Iterate until totalCountOfPurchasedItem is 0 // Iterate until totalCountOfPurchasedItem is 0
for (let i = 0; i < maxStackCount; i++) for (let i = 0; i < maxStackCount; i++)
@ -542,7 +612,12 @@ export class InventoryHelper
* @param output Existing IItemEventRouterResponse object to append data to, creates new one by default if not supplied * @param output Existing IItemEventRouterResponse object to append data to, creates new one by default if not supplied
* @returns IItemEventRouterResponse * @returns IItemEventRouterResponse
*/ */
public removeItem(profile: IPmcData, itemId: string, sessionID: string, output: IItemEventRouterResponse = undefined): IItemEventRouterResponse public removeItem(
profile: IPmcData,
itemId: string,
sessionID: string,
output: IItemEventRouterResponse = undefined,
): IItemEventRouterResponse
{ {
if (!itemId) if (!itemId)
{ {
@ -559,14 +634,14 @@ export class InventoryHelper
// We have output object, inform client of item deletion // We have output object, inform client of item deletion
if (output) if (output)
{ {
output.profileChanges[sessionID].items.del.push({ _id: itemId }); output.profileChanges[sessionID].items.del.push({_id: itemId});
} }
for (const childId of itemToRemoveWithChildren) for (const childId of itemToRemoveWithChildren)
{ {
// We expect that each inventory item and each insured item has unique "_id", respective "itemId". // We expect that each inventory item and each insured item has unique "_id", respective "itemId".
// Therefore we want to use a NON-Greedy function and escape the iteration as soon as we find requested item. // Therefore we want to use a NON-Greedy function and escape the iteration as soon as we find requested item.
const inventoryIndex = inventoryItems.findIndex(item => item._id === childId); const inventoryIndex = inventoryItems.findIndex((item) => item._id === childId);
if (inventoryIndex > -1) if (inventoryIndex > -1)
{ {
inventoryItems.splice(inventoryIndex, 1); inventoryItems.splice(inventoryIndex, 1);
@ -574,10 +649,12 @@ export class InventoryHelper
if (inventoryIndex === -1) if (inventoryIndex === -1)
{ {
this.logger.warning(`Unable to remove item with Id: ${childId} as it was not found in inventory ${profile._id}`); this.logger.warning(
`Unable to remove item with Id: ${childId} as it was not found in inventory ${profile._id}`,
);
} }
const insuredIndex = insuredItems.findIndex(item => item.itemId === childId); const insuredIndex = insuredItems.findIndex((item) => item.itemId === childId);
if (insuredIndex > -1) if (insuredIndex > -1)
{ {
insuredItems.splice(insuredIndex, 1); insuredItems.splice(insuredIndex, 1);
@ -587,7 +664,11 @@ export class InventoryHelper
return output; return output;
} }
public removeItemAndChildrenFromMailRewards(sessionId: string, removeRequest: IInventoryRemoveRequestData, output: IItemEventRouterResponse): IItemEventRouterResponse public removeItemAndChildrenFromMailRewards(
sessionId: string,
removeRequest: IInventoryRemoveRequestData,
output: IItemEventRouterResponse,
): IItemEventRouterResponse
{ {
const fullProfile = this.profileHelper.getFullProfile(sessionId); const fullProfile = this.profileHelper.getFullProfile(sessionId);
@ -595,18 +676,23 @@ export class InventoryHelper
const dialogs = Object.values(fullProfile.dialogues); const dialogs = Object.values(fullProfile.dialogues);
for (const dialog of dialogs) for (const dialog of dialogs)
{ {
const messageWithReward = dialog.messages.find(x => x._id === removeRequest.fromOwner.id); const messageWithReward = dialog.messages.find((x) => x._id === removeRequest.fromOwner.id);
if (messageWithReward) if (messageWithReward)
{ {
// Find item + any possible children and remove them from mails items array // Find item + any possible children and remove them from mails items array
const itemWithChildern = this.itemHelper.findAndReturnChildrenAsItems(messageWithReward.items.data, removeRequest.item); const itemWithChildern = this.itemHelper.findAndReturnChildrenAsItems(
messageWithReward.items.data,
removeRequest.item,
);
for (const itemToDelete of itemWithChildern) for (const itemToDelete of itemWithChildern)
{ {
// Get index of item to remove from reward array + remove it // Get index of item to remove from reward array + remove it
const indexOfItemToRemove = messageWithReward.items.data.indexOf(itemToDelete); const indexOfItemToRemove = messageWithReward.items.data.indexOf(itemToDelete);
if (indexOfItemToRemove === -1) if (indexOfItemToRemove === -1)
{ {
this.logger.error(`Unable to remove item: ${removeRequest.item} from mail: ${removeRequest.fromOwner.id} as item could not be found, restart client immediately to prevent data corruption`); this.logger.error(
`Unable to remove item: ${removeRequest.item} from mail: ${removeRequest.fromOwner.id} as item could not be found, restart client immediately to prevent data corruption`,
);
continue; continue;
} }
messageWithReward.items.data.splice(indexOfItemToRemove, 1); messageWithReward.items.data.splice(indexOfItemToRemove, 1);
@ -622,10 +708,18 @@ export class InventoryHelper
return output; return output;
} }
public removeItemByCount(pmcData: IPmcData, itemId: string, count: number, sessionID: string, output: IItemEventRouterResponse = undefined): IItemEventRouterResponse public removeItemByCount(
pmcData: IPmcData,
itemId: string,
count: number,
sessionID: string,
output: IItemEventRouterResponse = undefined,
): IItemEventRouterResponse
{ {
if (!itemId) if (!itemId)
{
return output; return output;
}
const itemsToReduce = this.itemHelper.findAndReturnChildrenAsItems(pmcData.Inventory.items, itemId); const itemsToReduce = this.itemHelper.findAndReturnChildrenAsItems(pmcData.Inventory.items, itemId);
let remainingCount = count; let remainingCount = count;
@ -644,11 +738,15 @@ export class InventoryHelper
itemToReduce.upd.StackObjectsCount -= remainingCount; itemToReduce.upd.StackObjectsCount -= remainingCount;
remainingCount = 0; remainingCount = 0;
if (output) if (output)
{
output.profileChanges[sessionID].items.change.push(itemToReduce); output.profileChanges[sessionID].items.change.push(itemToReduce);
}
} }
if (remainingCount === 0) if (remainingCount === 0)
{
break; break;
}
} }
return output; return output;
@ -666,7 +764,11 @@ export class InventoryHelper
// note from 2027: there IS a thing i didn't explore and that is Merges With Children // note from 2027: there IS a thing i didn't explore and that is Merges With Children
// -> Prepares item Width and height returns [sizeX, sizeY] // -> Prepares item Width and height returns [sizeX, sizeY]
protected getSizeByInventoryItemHash(itemTpl: string, itemID: string, inventoryItemHash: InventoryHelper.InventoryItemHash): number[] protected getSizeByInventoryItemHash(
itemTpl: string,
itemID: string,
inventoryItemHash: InventoryHelper.InventoryItemHash,
): number[]
{ {
const toDo = [itemID]; const toDo = [itemID];
const result = this.itemHelper.getItem(itemTpl); const result = this.itemHelper.getItem(itemTpl);
@ -681,7 +783,10 @@ export class InventoryHelper
// Item found but no _props property // Item found but no _props property
if (tmpItem && !tmpItem._props) if (tmpItem && !tmpItem._props)
{ {
this.localisationService.getText("inventory-item_missing_props_property", {itemTpl: itemTpl, itemName: tmpItem?._name}); this.localisationService.getText("inventory-item_missing_props_property", {
itemTpl: itemTpl,
itemName: tmpItem?._name,
});
} }
// No item object or getItem() returned false // No item object or getItem() returned false
@ -711,11 +816,11 @@ export class InventoryHelper
const skipThisItems: string[] = [ const skipThisItems: string[] = [
BaseClasses.BACKPACK, BaseClasses.BACKPACK,
BaseClasses.SEARCHABLE_ITEM, BaseClasses.SEARCHABLE_ITEM,
BaseClasses.SIMPLE_CONTAINER BaseClasses.SIMPLE_CONTAINER,
]; ];
const rootFolded = rootItem.upd?.Foldable && rootItem.upd.Foldable.Folded === true; const rootFolded = rootItem.upd?.Foldable && rootItem.upd.Foldable.Folded === true;
//The item itself is collapsible // The item itself is collapsible
if (foldableWeapon && (foldedSlot === undefined || foldedSlot === "") && rootFolded) if (foldableWeapon && (foldedSlot === undefined || foldedSlot === "") && rootFolded)
{ {
outX -= tmpItem._props.SizeReduceRight; outX -= tmpItem._props.SizeReduceRight;
@ -729,7 +834,7 @@ export class InventoryHelper
{ {
for (const item of inventoryItemHash.byParentId[toDo[0]]) for (const item of inventoryItemHash.byParentId[toDo[0]])
{ {
//Filtering child items outside of mod slots, such as those inside containers, without counting their ExtraSize attribute // Filtering child items outside of mod slots, such as those inside containers, without counting their ExtraSize attribute
if (item.slotId.indexOf("mod_") < 0) if (item.slotId.indexOf("mod_") < 0)
{ {
continue; continue;
@ -741,7 +846,12 @@ export class InventoryHelper
const itemResult = this.itemHelper.getItem(item._tpl); const itemResult = this.itemHelper.getItem(item._tpl);
if (!itemResult[0]) if (!itemResult[0])
{ {
this.logger.error(this.localisationService.getText("inventory-get_item_size_item_not_found_by_tpl", item._tpl)); this.logger.error(
this.localisationService.getText(
"inventory-get_item_size_item_not_found_by_tpl",
item._tpl,
),
);
} }
const itm = itemResult[1]; const itm = itemResult[1];
@ -781,7 +891,7 @@ export class InventoryHelper
return [ return [
outX + sizeLeft + sizeRight + forcedLeft + forcedRight, outX + sizeLeft + sizeRight + forcedLeft + forcedRight,
outY + sizeUp + sizeDown + forcedUp + forcedDown outY + sizeUp + sizeDown + forcedUp + forcedDown,
]; ];
} }
@ -789,7 +899,7 @@ export class InventoryHelper
{ {
const inventoryItemHash: InventoryHelper.InventoryItemHash = { const inventoryItemHash: InventoryHelper.InventoryItemHash = {
byItemId: {}, byItemId: {},
byParentId: {} byParentId: {},
}; };
for (const item of inventoryItem) for (const item of inventoryItem)
@ -832,8 +942,14 @@ export class InventoryHelper
const tmpSize = this.getSizeByInventoryItemHash(item._tpl, item._id, inventoryItemHash); const tmpSize = this.getSizeByInventoryItemHash(item._tpl, item._id, inventoryItemHash);
const iW = tmpSize[0]; // x const iW = tmpSize[0]; // x
const iH = tmpSize[1]; // y const iH = tmpSize[1]; // y
const fH = (((item.location as Location).r === 1 || (item.location as Location).r === "Vertical" || (item.location as Location).rotation === "Vertical") ? iW : iH); const fH = ((item.location as Location).r === 1 || (item.location as Location).r === "Vertical" ||
const fW = (((item.location as Location).r === 1 || (item.location as Location).r === "Vertical" || (item.location as Location).rotation === "Vertical") ? iH : iW); (item.location as Location).rotation === "Vertical") ?
iW :
iH;
const fW = ((item.location as Location).r === 1 || (item.location as Location).r === "Vertical" ||
(item.location as Location).rotation === "Vertical") ?
iH :
iW;
const fillTo = (item.location as Location).x + fW; const fillTo = (item.location as Location).x + fW;
for (let y = 0; y < fH; y++) for (let y = 0; y < fH; y++)
@ -844,7 +960,12 @@ export class InventoryHelper
} }
catch (e) catch (e)
{ {
this.logger.error(this.localisationService.getText("inventory-unable_to_fill_container", {id: item._id, error: e})); this.logger.error(
this.localisationService.getText("inventory-unable_to_fill_container", {
id: item._id,
error: e,
}),
);
} }
} }
} }
@ -860,7 +981,10 @@ export class InventoryHelper
* @param sessionId Session id / playerid * @param sessionId Session id / playerid
* @returns OwnerInventoryItems with inventory of player/scav to adjust * @returns OwnerInventoryItems with inventory of player/scav to adjust
*/ */
public getOwnerInventoryItems(request: IInventoryMoveRequestData | IInventorySplitRequestData | IInventoryMergeRequestData, sessionId: string): OwnerInventoryItems public getOwnerInventoryItems(
request: IInventoryMoveRequestData | IInventorySplitRequestData | IInventoryMergeRequestData,
sessionId: string,
): OwnerInventoryItems
{ {
let isSameInventory = false; let isSameInventory = false;
const pmcItems = this.profileHelper.getPmcProfile(sessionId).Inventory.items; const pmcItems = this.profileHelper.getPmcProfile(sessionId).Inventory.items;
@ -878,9 +1002,9 @@ export class InventoryHelper
else if (request.fromOwner.type.toLocaleLowerCase() === "mail") else if (request.fromOwner.type.toLocaleLowerCase() === "mail")
{ {
// Split requests dont use 'use' but 'splitItem' property // Split requests dont use 'use' but 'splitItem' property
const item = "splitItem" in request const item = "splitItem" in request ?
? request.splitItem request.splitItem :
: request.item; request.item;
fromInventoryItems = this.dialogueHelper.getMessageItemContents(request.fromOwner.id, sessionId, item); fromInventoryItems = this.dialogueHelper.getMessageItemContents(request.fromOwner.id, sessionId, item);
fromType = "mail"; fromType = "mail";
} }
@ -908,7 +1032,7 @@ export class InventoryHelper
from: fromInventoryItems, from: fromInventoryItems,
to: toInventoryItems, to: toInventoryItems,
sameInventory: isSameInventory, sameInventory: isSameInventory,
isMail: fromType === "mail" isMail: fromType === "mail",
}; };
} }
@ -921,7 +1045,12 @@ export class InventoryHelper
protected getStashSlotMap(pmcData: IPmcData, sessionID: string): number[][] protected getStashSlotMap(pmcData: IPmcData, sessionID: string): number[][]
{ {
const playerStashSize = this.getPlayerStashSize(sessionID); const playerStashSize = this.getPlayerStashSize(sessionID);
return this.getContainerMap(playerStashSize[0], playerStashSize[1], pmcData.Inventory.items, pmcData.Inventory.stash); return this.getContainerMap(
playerStashSize[0],
playerStashSize[1],
pmcData.Inventory.items,
pmcData.Inventory.stash,
);
} }
protected getSortingTableSlotMap(pmcData: IPmcData): number[][] protected getSortingTableSlotMap(pmcData: IPmcData): number[][]
@ -936,7 +1065,7 @@ export class InventoryHelper
*/ */
protected getPlayerStashSize(sessionID: string): Record<number, number> protected getPlayerStashSize(sessionID: string): Record<number, number>
{ {
//this sets automatically a stash size from items.json (its not added anywhere yet cause we still use base stash) // this sets automatically a stash size from items.json (its not added anywhere yet cause we still use base stash)
const stashTPL = this.getStashType(sessionID); const stashTPL = this.getStashType(sessionID);
if (!stashTPL) if (!stashTPL)
{ {
@ -948,12 +1077,12 @@ export class InventoryHelper
this.logger.error(this.localisationService.getText("inventory-stash_not_found", stashTPL)); this.logger.error(this.localisationService.getText("inventory-stash_not_found", stashTPL));
} }
const stashX = stashItemDetails[1]._props.Grids[0]._props.cellsH !== 0 const stashX = stashItemDetails[1]._props.Grids[0]._props.cellsH !== 0 ?
? stashItemDetails[1]._props.Grids[0]._props.cellsH stashItemDetails[1]._props.Grids[0]._props.cellsH :
: 10; 10;
const stashY = stashItemDetails[1]._props.Grids[0]._props.cellsV !== 0 const stashY = stashItemDetails[1]._props.Grids[0]._props.cellsV !== 0 ?
? stashItemDetails[1]._props.Grids[0]._props.cellsV stashItemDetails[1]._props.Grids[0]._props.cellsV :
: 66; 66;
return [stashX, stashY]; return [stashX, stashY];
} }
@ -965,7 +1094,7 @@ export class InventoryHelper
protected getStashType(sessionID: string): string protected getStashType(sessionID: string): string
{ {
const pmcData = this.profileHelper.getPmcProfile(sessionID); const pmcData = this.profileHelper.getPmcProfile(sessionID);
const stashObj = pmcData.Inventory.items.find(item => item._id === pmcData.Inventory.stash); const stashObj = pmcData.Inventory.items.find((item) => item._id === pmcData.Inventory.stash);
if (!stashObj) if (!stashObj)
{ {
this.logger.error(this.localisationService.getText("inventory-unable_to_find_stash")); this.logger.error(this.localisationService.getText("inventory-unable_to_find_stash"));
@ -987,7 +1116,7 @@ export class InventoryHelper
const idsToMove = this.itemHelper.findAndReturnChildrenByItems(fromItems, body.item); const idsToMove = this.itemHelper.findAndReturnChildrenByItems(fromItems, body.item);
for (const itemId of idsToMove) for (const itemId of idsToMove)
{ {
const itemToMove = fromItems.find(x => x._id === itemId); const itemToMove = fromItems.find((x) => x._id === itemId);
if (!itemToMove) if (!itemToMove)
{ {
this.logger.error(`Unable to find item to move: ${itemId}`); this.logger.error(`Unable to find item to move: ${itemId}`);
@ -1022,16 +1151,20 @@ export class InventoryHelper
/** /**
* Internal helper function to move item within the same profile_f. * Internal helper function to move item within the same profile_f.
* @param pmcData profile to edit * @param pmcData profile to edit
* @param inventoryItems * @param inventoryItems
* @param moveRequest * @param moveRequest
* @returns True if move was successful * @returns True if move was successful
*/ */
public moveItemInternal(pmcData: IPmcData, inventoryItems: Item[], moveRequest: IInventoryMoveRequestData): {success: boolean, errorMessage?: string} public moveItemInternal(
pmcData: IPmcData,
inventoryItems: Item[],
moveRequest: IInventoryMoveRequestData,
): {success: boolean; errorMessage?: string;}
{ {
this.handleCartridges(inventoryItems, moveRequest); this.handleCartridges(inventoryItems, moveRequest);
// Find item we want to 'move' // Find item we want to 'move'
const matchingInventoryItem = inventoryItems.find(x => x._id === moveRequest.item); const matchingInventoryItem = inventoryItems.find((x) => x._id === moveRequest.item);
if (!matchingInventoryItem) if (!matchingInventoryItem)
{ {
const errorMesage = `Unable to move item: ${moveRequest.item}, cannot find in inventory`; const errorMesage = `Unable to move item: ${moveRequest.item}, cannot find in inventory`;
@ -1040,12 +1173,19 @@ export class InventoryHelper
return {success: false, errorMessage: errorMesage}; return {success: false, errorMessage: errorMesage};
} }
this.logger.debug(`${moveRequest.Action} item: ${moveRequest.item} from slotid: ${matchingInventoryItem.slotId} to container: ${moveRequest.to.container}`); this.logger.debug(
`${moveRequest.Action} item: ${moveRequest.item} from slotid: ${matchingInventoryItem.slotId} to container: ${moveRequest.to.container}`,
);
// don't move shells from camora to cartridges (happens when loading shells into mts-255 revolver shotgun) // don't move shells from camora to cartridges (happens when loading shells into mts-255 revolver shotgun)
if (matchingInventoryItem.slotId.includes("camora_") && moveRequest.to.container === "cartridges") if (matchingInventoryItem.slotId.includes("camora_") && moveRequest.to.container === "cartridges")
{ {
this.logger.warning(this.localisationService.getText("inventory-invalid_move_to_container", {slotId: matchingInventoryItem.slotId, container: moveRequest.to.container})); this.logger.warning(
this.localisationService.getText("inventory-invalid_move_to_container", {
slotId: matchingInventoryItem.slotId,
container: moveRequest.to.container,
}),
);
return {success: true}; return {success: true};
} }
@ -1059,7 +1199,6 @@ export class InventoryHelper
if ("location" in moveRequest.to) if ("location" in moveRequest.to)
{ {
matchingInventoryItem.location = moveRequest.to.location; matchingInventoryItem.location = moveRequest.to.location;
} }
else else
{ {
@ -1085,8 +1224,8 @@ export class InventoryHelper
if (pmcData.Inventory.fastPanel[itemKey] === itemBeingMoved._id) if (pmcData.Inventory.fastPanel[itemKey] === itemBeingMoved._id)
{ {
// Get moved items parent // Get moved items parent
const itemParent = pmcData.Inventory.items.find(x => x._id === itemBeingMoved.parentId); const itemParent = pmcData.Inventory.items.find((x) => x._id === itemBeingMoved.parentId);
// Empty out id if item is moved to a container other than pocket/rig // Empty out id if item is moved to a container other than pocket/rig
if (itemParent && !(itemParent.slotId?.startsWith("Pockets") || itemParent.slotId === "TacticalVest")) if (itemParent && !(itemParent.slotId?.startsWith("Pockets") || itemParent.slotId === "TacticalVest"))
{ {
@ -1099,8 +1238,8 @@ export class InventoryHelper
} }
/** /**
* Internal helper function to handle cartridges in inventory if any of them exist. * Internal helper function to handle cartridges in inventory if any of them exist.
*/ */
protected handleCartridges(items: Item[], body: IInventoryMoveRequestData): void protected handleCartridges(items: Item[], body: IInventoryMoveRequestData): void
{ {
// -> Move item to different place - counts with equipping filling magazine etc // -> Move item to different place - counts with equipping filling magazine etc
@ -1140,7 +1279,7 @@ namespace InventoryHelper
{ {
export interface InventoryItemHash export interface InventoryItemHash
{ {
byItemId: Record<string, Item> byItemId: Record<string, Item>;
byParentId: Record<string, Item[]> byParentId: Record<string, Item[]>;
} }
} }

View File

@ -29,7 +29,7 @@ class ItemHelper
BaseClasses.SORTING_TABLE, BaseClasses.SORTING_TABLE,
BaseClasses.INVENTORY, BaseClasses.INVENTORY,
BaseClasses.STATIONARY_CONTAINER, BaseClasses.STATIONARY_CONTAINER,
BaseClasses.POCKETS BaseClasses.POCKETS,
]; ];
constructor( constructor(
@ -44,7 +44,7 @@ class ItemHelper
@inject("ItemBaseClassService") protected itemBaseClassService: ItemBaseClassService, @inject("ItemBaseClassService") protected itemBaseClassService: ItemBaseClassService,
@inject("ItemFilterService") protected itemFilterService: ItemFilterService, @inject("ItemFilterService") protected itemFilterService: ItemFilterService,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("LocaleService") protected localeService: LocaleService @inject("LocaleService") protected localeService: LocaleService,
) )
{} {}
@ -68,11 +68,11 @@ class ItemHelper
} }
// Is item valid // Is item valid
return !itemDetails[1]._props.QuestItem return !itemDetails[1]._props.QuestItem &&
&& itemDetails[1]._type === "Item" itemDetails[1]._type === "Item" &&
&& invalidBaseTypes.every(x => !this.isOfBaseclass(tpl, x)) invalidBaseTypes.every((x) => !this.isOfBaseclass(tpl, x)) &&
&& this.getItemPrice(tpl) > 0 this.getItemPrice(tpl) > 0 &&
&& !this.itemFilterService.isItemBlacklisted(tpl); !this.itemFilterService.isItemBlacklisted(tpl);
} }
/** /**
@ -177,7 +177,7 @@ class ItemHelper
if (item.upd === undefined) if (item.upd === undefined)
{ {
item.upd = { item.upd = {
StackObjectsCount: 1 StackObjectsCount: 1,
}; };
} }
@ -244,8 +244,8 @@ class ItemHelper
slotId: slotId, slotId: slotId,
location: 0, location: 0,
upd: { upd: {
StackObjectsCount: count StackObjectsCount: count,
} },
}; };
stackSlotItems.push(stackSlotItem); stackSlotItems.push(stackSlotItem);
} }
@ -352,7 +352,6 @@ class ItemHelper
return result; return result;
} }
/** /**
* Get a quality value based on a repairable items (weapon/armor) current state between current and max durability * Get a quality value based on a repairable items (weapon/armor) current state between current and max durability
* @param itemDetails Db details for item we want quality value for * @param itemDetails Db details for item we want quality value for
@ -365,7 +364,9 @@ class ItemHelper
// Edge case, max durability is below durability // Edge case, max durability is below durability
if (repairable.Durability > repairable.MaxDurability) if (repairable.Durability > repairable.MaxDurability)
{ {
this.logger.warning(`Max durability: ${repairable.MaxDurability} for item id: ${item._id} was below Durability: ${repairable.Durability}, adjusting values to match`); this.logger.warning(
`Max durability: ${repairable.MaxDurability} for item id: ${item._id} was below Durability: ${repairable.Durability}, adjusting values to match`,
);
repairable.MaxDurability = repairable.Durability; repairable.MaxDurability = repairable.Durability;
} }
@ -377,9 +378,9 @@ class ItemHelper
// Weapon // Weapon
// Get max dura from props, if it isnt there use repairable max dura value // Get max dura from props, if it isnt there use repairable max dura value
const maxDurability = (itemDetails._props.MaxDurability) const maxDurability = (itemDetails._props.MaxDurability) ?
? itemDetails._props.MaxDurability itemDetails._props.MaxDurability :
: repairable.MaxDurability; repairable.MaxDurability;
const durability = repairable.Durability / maxDurability; const durability = repairable.Durability / maxDurability;
if (!durability) if (!durability)
@ -434,7 +435,7 @@ class ItemHelper
continue; continue;
} }
if (childItem.parentId === baseItemId && !list.find(item => childItem._id === item._id)) if (childItem.parentId === baseItemId && !list.find((item) => childItem._id === item._id))
{ {
list.push(...this.findAndReturnChildrenAsItems(items, childItem._id)); list.push(...this.findAndReturnChildrenAsItems(items, childItem._id));
} }
@ -455,7 +456,7 @@ class ItemHelper
for (const itemFromAssort of assort) for (const itemFromAssort of assort)
{ {
if (itemFromAssort.parentId === itemIdToFind && !list.find(item => itemFromAssort._id === item._id)) if (itemFromAssort.parentId === itemIdToFind && !list.find((item) => itemFromAssort._id === item._id))
{ {
list.push(itemFromAssort); list.push(itemFromAssort);
list = list.concat(this.findAndReturnChildrenByAssort(itemFromAssort._id, assort)); list = list.concat(this.findAndReturnChildrenByAssort(itemFromAssort._id, assort));
@ -472,8 +473,10 @@ class ItemHelper
*/ */
public hasBuyRestrictions(itemToCheck: Item): boolean public hasBuyRestrictions(itemToCheck: Item): boolean
{ {
if (itemToCheck.upd?.BuyRestrictionCurrent !== undefined if (
&& itemToCheck.upd?.BuyRestrictionMax !== undefined) itemToCheck.upd?.BuyRestrictionCurrent !== undefined &&
itemToCheck.upd?.BuyRestrictionMax !== undefined
)
{ {
return true; return true;
} }
@ -571,18 +574,18 @@ class ItemHelper
public findBarterItems(by: "tpl" | "id", items: Item[], barterItemId: string): Item[] public findBarterItems(by: "tpl" | "id", items: Item[], barterItemId: string): Item[]
{ {
// find required items to take after buying (handles multiple items) // find required items to take after buying (handles multiple items)
const barterIDs = typeof barterItemId === "string" const barterIDs = typeof barterItemId === "string" ?
? [barterItemId] [barterItemId] :
: barterItemId; barterItemId;
let barterItems: Item[] = []; let barterItems: Item[] = [];
for (const barterID of barterIDs) for (const barterID of barterIDs)
{ {
const filterResult = items.filter(item => const filterResult = items.filter((item) =>
{ {
return by === "tpl" return by === "tpl" ?
? (item._tpl === barterID) (item._tpl === barterID) :
: (item._id === barterID); (item._id === barterID);
}); });
barterItems = Object.assign(barterItems, filterResult); barterItems = Object.assign(barterItems, filterResult);
@ -615,17 +618,19 @@ class ItemHelper
{ {
// Insured items shouldn't be renamed // Insured items shouldn't be renamed
// only works for pmcs. // only works for pmcs.
if (insuredItems?.find(insuredItem => insuredItem.itemId === item._id)) if (insuredItems?.find((insuredItem) => insuredItem.itemId === item._id))
{ {
continue; continue;
} }
// Do not replace important ID's // Do not replace important ID's
if (item._id === pmcData.Inventory.equipment if (
|| item._id === pmcData.Inventory.questRaidItems item._id === pmcData.Inventory.equipment ||
|| item._id === pmcData.Inventory.questStashItems item._id === pmcData.Inventory.questRaidItems ||
|| item._id === pmcData.Inventory.sortingTable item._id === pmcData.Inventory.questStashItems ||
|| item._id === pmcData.Inventory.stash) item._id === pmcData.Inventory.sortingTable ||
item._id === pmcData.Inventory.stash
)
{ {
continue; continue;
} }
@ -804,7 +809,9 @@ class ItemHelper
let isRequiredSlot = false; let isRequiredSlot = false;
if (parentTemplate[0] && parentTemplate[1]?._props?.Slots) if (parentTemplate[0] && parentTemplate[1]?._props?.Slots)
{ {
isRequiredSlot = parentTemplate[1]._props.Slots.some(slot => slot._name === item.slotId && slot._required); isRequiredSlot = parentTemplate[1]._props.Slots.some((slot) =>
slot._name === item.slotId && slot._required
);
} }
return itemTemplate[0] && parentTemplate[0] && !(isNotRaidModdable || isRequiredSlot); return itemTemplate[0] && parentTemplate[0] && !(isNotRaidModdable || isRequiredSlot);
@ -865,7 +872,7 @@ class ItemHelper
*/ */
public getItemSize(items: Item[], rootItemId: string): ItemHelper.ItemSize public getItemSize(items: Item[], rootItemId: string): ItemHelper.ItemSize
{ {
const rootTemplate = this.getItem(items.filter(x => x._id === rootItemId)[0]._tpl)[1]; const rootTemplate = this.getItem(items.filter((x) => x._id === rootItemId)[0]._tpl)[1];
const width = rootTemplate._props.Width; const width = rootTemplate._props.Width;
const height = rootTemplate._props.Height; const height = rootTemplate._props.Height;
@ -897,13 +904,15 @@ class ItemHelper
sizeUp = sizeUp < itemTemplate._props.ExtraSizeUp ? itemTemplate._props.ExtraSizeUp : sizeUp; sizeUp = sizeUp < itemTemplate._props.ExtraSizeUp ? itemTemplate._props.ExtraSizeUp : sizeUp;
sizeDown = sizeDown < itemTemplate._props.ExtraSizeDown ? itemTemplate._props.ExtraSizeDown : sizeDown; sizeDown = sizeDown < itemTemplate._props.ExtraSizeDown ? itemTemplate._props.ExtraSizeDown : sizeDown;
sizeLeft = sizeLeft < itemTemplate._props.ExtraSizeLeft ? itemTemplate._props.ExtraSizeLeft : sizeLeft; sizeLeft = sizeLeft < itemTemplate._props.ExtraSizeLeft ? itemTemplate._props.ExtraSizeLeft : sizeLeft;
sizeRight = sizeRight < itemTemplate._props.ExtraSizeRight ? itemTemplate._props.ExtraSizeRight : sizeRight; sizeRight = sizeRight < itemTemplate._props.ExtraSizeRight ?
itemTemplate._props.ExtraSizeRight :
sizeRight;
} }
} }
return { return {
width: width + sizeLeft + sizeRight + forcedLeft + forcedRight, width: width + sizeLeft + sizeRight + forcedLeft + forcedRight,
height: height + sizeUp + sizeDown + forcedUp + forcedDown height: height + sizeUp + sizeDown + forcedUp + forcedDown,
}; };
} }
@ -945,15 +954,15 @@ class ItemHelper
while (currentStoredCartridgeCount < ammoBoxMaxCartridgeCount) while (currentStoredCartridgeCount < ammoBoxMaxCartridgeCount)
{ {
const remainingSpace = ammoBoxMaxCartridgeCount - currentStoredCartridgeCount; const remainingSpace = ammoBoxMaxCartridgeCount - currentStoredCartridgeCount;
const cartridgeCountToAdd = (remainingSpace < maxPerStack) const cartridgeCountToAdd = (remainingSpace < maxPerStack) ?
? remainingSpace remainingSpace :
: maxPerStack; maxPerStack;
// Add cartridge item into items array // Add cartridge item into items array
ammoBox.push(this.createCartridges(ammoBox[0]._id, cartridgeTpl, cartridgeCountToAdd, location)); ammoBox.push(this.createCartridges(ammoBox[0]._id, cartridgeTpl, cartridgeCountToAdd, location));
currentStoredCartridgeCount += cartridgeCountToAdd; currentStoredCartridgeCount += cartridgeCountToAdd;
location ++; location++;
} }
} }
@ -967,7 +976,7 @@ class ItemHelper
public itemIsInsideContainer(item: Item, desiredContainerSlotId: string, items: Item[]): boolean public itemIsInsideContainer(item: Item, desiredContainerSlotId: string, items: Item[]): boolean
{ {
// Get items parent // Get items parent
const parent = items.find(x => x._id === item.parentId); const parent = items.find((x) => x._id === item.parentId);
if (!parent) if (!parent)
{ {
// No parent, end of line, not inside container // No parent, end of line, not inside container
@ -997,7 +1006,7 @@ class ItemHelper
magTemplate: ITemplateItem, magTemplate: ITemplateItem,
staticAmmoDist: Record<string, IStaticAmmoDetails[]>, staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
caliber: string = undefined, caliber: string = undefined,
minSizePercent = 0.25 minSizePercent = 0.25,
): void ): void
{ {
// no caliber defined, choose one at random // no caliber defined, choose one at random
@ -1028,7 +1037,7 @@ class ItemHelper
magazine: Item[], magazine: Item[],
magTemplate: ITemplateItem, magTemplate: ITemplateItem,
cartridgeTpl: string, cartridgeTpl: string,
minSizePercent = 0.25 minSizePercent = 0.25,
): void ): void
{ {
// Get cartrdge properties and max allowed stack size // Get cartrdge properties and max allowed stack size
@ -1037,7 +1046,10 @@ class ItemHelper
// Get max number of cartridges in magazine, choose random value between min/max // Get max number of cartridges in magazine, choose random value between min/max
const magazineCartridgeMaxCount = magTemplate._props.Cartridges[0]._max_count; const magazineCartridgeMaxCount = magTemplate._props.Cartridges[0]._max_count;
const desiredStackCount = this.randomUtil.getInt(Math.round(minSizePercent * magazineCartridgeMaxCount), magazineCartridgeMaxCount); const desiredStackCount = this.randomUtil.getInt(
Math.round(minSizePercent * magazineCartridgeMaxCount),
magazineCartridgeMaxCount,
);
// Loop over cartridge count and add stacks to magazine // Loop over cartridge count and add stacks to magazine
let currentStoredCartridgeCount = 0; let currentStoredCartridgeCount = 0;
@ -1045,9 +1057,9 @@ class ItemHelper
while (currentStoredCartridgeCount < desiredStackCount) while (currentStoredCartridgeCount < desiredStackCount)
{ {
// Get stack size of cartridges // Get stack size of cartridges
let cartridgeCountToAdd = (desiredStackCount <= cartridgeMaxStackSize) let cartridgeCountToAdd = (desiredStackCount <= cartridgeMaxStackSize) ?
? desiredStackCount desiredStackCount :
: cartridgeMaxStackSize; cartridgeMaxStackSize;
// Ensure we don't go over the max stackcount size // Ensure we don't go over the max stackcount size
const remainingSpace = desiredStackCount - currentStoredCartridgeCount; const remainingSpace = desiredStackCount - currentStoredCartridgeCount;
@ -1060,7 +1072,7 @@ class ItemHelper
magazine.push(this.createCartridges(magazine[0]._id, cartridgeTpl, cartridgeCountToAdd, location)); magazine.push(this.createCartridges(magazine[0]._id, cartridgeTpl, cartridgeCountToAdd, location));
currentStoredCartridgeCount += cartridgeCountToAdd; currentStoredCartridgeCount += cartridgeCountToAdd;
location ++; location++;
} }
} }
@ -1075,11 +1087,11 @@ class ItemHelper
const calibers = [ const calibers = [
...new Set( ...new Set(
ammoTpls.filter( ammoTpls.filter(
(x: string) => this.getItem(x)[0] (x: string) => this.getItem(x)[0],
).map( ).map(
(x: string) => this.getItem(x)[1]._props.Caliber (x: string) => this.getItem(x)[1]._props.Caliber,
) ),
) ),
]; ];
return this.randomUtil.drawRandomFromList(calibers)[0]; return this.randomUtil.drawRandomFromList(calibers)[0];
} }
@ -1096,7 +1108,7 @@ class ItemHelper
for (const icd of staticAmmoDist[caliber]) for (const icd of staticAmmoDist[caliber])
{ {
ammoArray.push( ammoArray.push(
new ProbabilityObject(icd.tpl, icd.relativeProbability) new ProbabilityObject(icd.tpl, icd.relativeProbability),
); );
} }
return ammoArray.draw(1)[0]; return ammoArray.draw(1)[0];
@ -1118,7 +1130,7 @@ class ItemHelper
parentId: parentId, parentId: parentId,
slotId: "cartridges", slotId: "cartridges",
location: location, location: location,
upd: { StackObjectsCount: stackCount } upd: {StackObjectsCount: stackCount},
}; };
} }
@ -1149,7 +1161,9 @@ class ItemHelper
public getItemTplsOfBaseType(desiredBaseType: string): string[] public getItemTplsOfBaseType(desiredBaseType: string): string[]
{ {
return Object.values(this.databaseServer.getTables().templates.items).filter(x => x._parent === desiredBaseType).map(x =>x._id); return Object.values(this.databaseServer.getTables().templates.items).filter((x) =>
x._parent === desiredBaseType
).map((x) => x._id);
} }
} }
@ -1157,10 +1171,9 @@ namespace ItemHelper
{ {
export interface ItemSize export interface ItemSize
{ {
width: number width: number;
height: number height: number;
} }
} }
export { ItemHelper }; export {ItemHelper};

View File

@ -16,14 +16,14 @@ export class NotificationSendHelper
@inject("WebSocketServer") protected webSocketServer: WebSocketServer, @inject("WebSocketServer") protected webSocketServer: WebSocketServer,
@inject("HashUtil") protected hashUtil: HashUtil, @inject("HashUtil") protected hashUtil: HashUtil,
@inject("SaveServer") protected saveServer: SaveServer, @inject("SaveServer") protected saveServer: SaveServer,
@inject("NotificationService") protected notificationService: NotificationService @inject("NotificationService") protected notificationService: NotificationService,
) )
{} {}
/** /**
* Send notification message to the appropriate channel * Send notification message to the appropriate channel
* @param sessionID * @param sessionID
* @param notificationMessage * @param notificationMessage
*/ */
public sendMessage(sessionID: string, notificationMessage: INotification): void public sendMessage(sessionID: string, notificationMessage: INotification): void
{ {
@ -44,7 +44,12 @@ export class NotificationSendHelper
* @param messageText Text to send player * @param messageText Text to send player
* @param messageType Underlying type of message being sent * @param messageType Underlying type of message being sent
*/ */
public sendMessageToPlayer(sessionId: string, senderDetails: IUserDialogInfo, messageText: string, messageType: MessageType): void public sendMessageToPlayer(
sessionId: string,
senderDetails: IUserDialogInfo,
messageText: string,
messageType: MessageType,
): void
{ {
const dialog = this.getDialog(sessionId, messageType, senderDetails); const dialog = this.getDialog(sessionId, messageType, senderDetails);
@ -57,7 +62,7 @@ export class NotificationSendHelper
text: messageText, text: messageText,
hasRewards: undefined, hasRewards: undefined,
rewardCollected: undefined, rewardCollected: undefined,
items: undefined items: undefined,
}; };
dialog.messages.push(message); dialog.messages.push(message);
@ -65,7 +70,7 @@ export class NotificationSendHelper
type: NotificationType.NEW_MESSAGE, type: NotificationType.NEW_MESSAGE,
eventId: message._id, eventId: message._id,
dialogId: message.uid, dialogId: message.uid,
message: message message: message,
}; };
this.sendMessage(sessionId, notification); this.sendMessage(sessionId, notification);
} }
@ -80,7 +85,9 @@ export class NotificationSendHelper
protected getDialog(sessionId: string, messageType: MessageType, senderDetails: IUserDialogInfo): Dialogue protected getDialog(sessionId: string, messageType: MessageType, senderDetails: IUserDialogInfo): Dialogue
{ {
// Use trader id if sender is trader, otherwise use nickname // Use trader id if sender is trader, otherwise use nickname
const key = (senderDetails.info.MemberCategory === MemberCategory.TRADER) ? senderDetails._id : senderDetails.info.Nickname; const key = (senderDetails.info.MemberCategory === MemberCategory.TRADER) ?
senderDetails._id :
senderDetails.info.Nickname;
const dialogueData = this.saveServer.getProfile(sessionId).dialogues; const dialogueData = this.saveServer.getProfile(sessionId).dialogues;
const isNewDialogue = !(key in dialogueData); const isNewDialogue = !(key in dialogueData);
let dialogue: Dialogue = dialogueData[key]; let dialogue: Dialogue = dialogueData[key];
@ -95,11 +102,11 @@ export class NotificationSendHelper
pinned: false, pinned: false,
new: 0, new: 0,
attachmentsNew: 0, attachmentsNew: 0,
Users: (senderDetails.info.MemberCategory === MemberCategory.TRADER) ? undefined : [senderDetails] Users: (senderDetails.info.MemberCategory === MemberCategory.TRADER) ? undefined : [senderDetails],
}; };
dialogueData[key] = dialogue; dialogueData[key] = dialogue;
} }
return dialogue; return dialogue;
} }
} }

View File

@ -12,11 +12,11 @@ export class NotifierHelper
*/ */
protected defaultNotification: INotification = { protected defaultNotification: INotification = {
type: NotificationType.PING, type: NotificationType.PING,
eventId: "ping" eventId: "ping",
}; };
constructor( constructor(
@inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper @inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper,
) )
{} {}
@ -29,22 +29,25 @@ export class NotifierHelper
* Create a new notification that displays the "Your offer was sold!" prompt and removes sold offer from "My Offers" on clientside * Create a new notification that displays the "Your offer was sold!" prompt and removes sold offer from "My Offers" on clientside
* @param dialogueMessage Message from dialog that was sent * @param dialogueMessage Message from dialog that was sent
* @param ragfairData Ragfair data to attach to notification * @param ragfairData Ragfair data to attach to notification
* @returns * @returns
*/ */
public createRagfairOfferSoldNotification(dialogueMessage: Message, ragfairData: MessageContentRagfair): INotification public createRagfairOfferSoldNotification(
dialogueMessage: Message,
ragfairData: MessageContentRagfair,
): INotification
{ {
return { return {
type: NotificationType.RAGFAIR_OFFER_SOLD, type: NotificationType.RAGFAIR_OFFER_SOLD,
eventId: dialogueMessage._id, eventId: dialogueMessage._id,
dialogId: dialogueMessage.uid, dialogId: dialogueMessage.uid,
...ragfairData ...ragfairData,
}; };
} }
/** /**
* Create a new notification with the specified dialogueMessage object * Create a new notification with the specified dialogueMessage object
* @param dialogueMessage * @param dialogueMessage
* @returns * @returns
*/ */
public createNewMessageNotification(dialogueMessage: Message): INotification public createNewMessageNotification(dialogueMessage: Message): INotification
{ {
@ -52,7 +55,7 @@ export class NotifierHelper
type: NotificationType.NEW_MESSAGE, type: NotificationType.NEW_MESSAGE,
eventId: dialogueMessage._id, eventId: dialogueMessage._id,
dialogId: dialogueMessage.uid, dialogId: dialogueMessage.uid,
message: dialogueMessage message: dialogueMessage,
}; };
} }
@ -60,4 +63,4 @@ export class NotifierHelper
{ {
return `${this.httpServerHelper.getWebsocketUrl()}/notifierServer/getwebsocket/${sessionID}`; return `${this.httpServerHelper.getWebsocketUrl()}/notifierServer/getwebsocket/${sessionID}`;
} }
} }

View File

@ -11,7 +11,7 @@ export class PaymentHelper
protected inventoryConfig: IInventoryConfig; protected inventoryConfig: IInventoryConfig;
constructor( constructor(
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.inventoryConfig = this.configServer.getConfig(ConfigTypes.INVENTORY); this.inventoryConfig = this.configServer.getConfig(ConfigTypes.INVENTORY);
@ -24,14 +24,16 @@ export class PaymentHelper
*/ */
public isMoneyTpl(tpl: string): boolean public isMoneyTpl(tpl: string): boolean
{ {
return [Money.DOLLARS, Money.EUROS, Money.ROUBLES, ...this.inventoryConfig.customMoneyTpls].some(element => element === tpl); return [Money.DOLLARS, Money.EUROS, Money.ROUBLES, ...this.inventoryConfig.customMoneyTpls].some((element) =>
element === tpl
);
} }
/** /**
* Gets currency TPL from TAG * Gets currency TPL from TAG
* @param {string} currency * @param {string} currency
* @returns string * @returns string
*/ */
public getCurrency(currency: string): string public getCurrency(currency: string): string
{ {
switch (currency) switch (currency)
@ -46,4 +48,4 @@ export class PaymentHelper
return ""; return "";
} }
} }
} }

View File

@ -12,9 +12,9 @@ export class PresetHelper
constructor( constructor(
@inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("JsonUtil") protected jsonUtil: JsonUtil,
@inject("DatabaseServer") protected databaseServer: DatabaseServer @inject("DatabaseServer") protected databaseServer: DatabaseServer,
) )
{ } {}
public hydratePresetStore(input: Record<string, string[]>): void public hydratePresetStore(input: Record<string, string[]>): void
{ {
@ -26,7 +26,7 @@ export class PresetHelper
if (!this.defaultPresets) if (!this.defaultPresets)
{ {
this.defaultPresets = Object.values(this.databaseServer.getTables().globals.ItemPresets) this.defaultPresets = Object.values(this.databaseServer.getTables().globals.ItemPresets)
.filter(x => x._encyclopedia !== undefined) .filter((x) => x._encyclopedia !== undefined)
.reduce((acc, cur) => .reduce((acc, cur) =>
{ {
acc[cur._id] = cur; acc[cur._id] = cur;
@ -112,4 +112,4 @@ export class PresetHelper
return ""; return "";
} }
} }

View File

@ -8,9 +8,9 @@ export class ProbabilityHelper
{ {
constructor( constructor(
@inject("WinstonLogger") protected logger: ILogger, @inject("WinstonLogger") protected logger: ILogger,
@inject("RandomUtil") protected randomUtil: RandomUtil @inject("RandomUtil") protected randomUtil: RandomUtil,
) )
{ } {}
/** /**
* Chance to roll a number out of 100 * Chance to roll a number out of 100
@ -20,6 +20,6 @@ export class ProbabilityHelper
*/ */
public rollChance(chance: number, scale = 1): boolean public rollChance(chance: number, scale = 1): boolean
{ {
return (this.randomUtil.getInt(1, 100 * scale)/ (1 * scale)) <= chance; return (this.randomUtil.getInt(1, 100 * scale) / (1 * scale)) <= chance;
} }
} }

View File

@ -27,9 +27,9 @@ export class ProfileHelper
@inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("ItemHelper") protected itemHelper: ItemHelper, @inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("ProfileSnapshotService") protected profileSnapshotService: ProfileSnapshotService, @inject("ProfileSnapshotService") protected profileSnapshotService: ProfileSnapshotService,
@inject("LocalisationService") protected localisationService: LocalisationService @inject("LocalisationService") protected localisationService: LocalisationService,
) )
{ } {}
/** /**
* Remove/reset a completed quest condtion from players profile quest data * Remove/reset a completed quest condtion from players profile quest data
@ -41,7 +41,7 @@ export class ProfileHelper
for (const questId in questConditionId) for (const questId in questConditionId)
{ {
const conditionId = questConditionId[questId]; const conditionId = questConditionId[questId];
const profileQuest = pmcData.Quests.find(x => x.qid === questId); const profileQuest = pmcData.Quests.find((x) => x.qid === questId);
// Find index of condition in array // Find index of condition in array
const index = profileQuest.completedConditions.indexOf(conditionId); const index = profileQuest.completedConditions.indexOf(conditionId);
@ -51,7 +51,7 @@ export class ProfileHelper
profileQuest.completedConditions.splice(index, 1); profileQuest.completedConditions.splice(index, 1);
} }
} }
} }
/** /**
* Get all profiles from server * Get all profiles from server
@ -97,7 +97,12 @@ export class ProfileHelper
* @param scavProfile post-raid scav profile * @param scavProfile post-raid scav profile
* @returns updated profile array * @returns updated profile array
*/ */
protected postRaidXpWorkaroundFix(sessionId: string, output: IPmcData[], pmcProfile: IPmcData, scavProfile: IPmcData): IPmcData[] protected postRaidXpWorkaroundFix(
sessionId: string,
output: IPmcData[],
pmcProfile: IPmcData,
scavProfile: IPmcData,
): IPmcData[]
{ {
const clonedPmc = this.jsonUtil.clone(pmcProfile); const clonedPmc = this.jsonUtil.clone(pmcProfile);
const clonedScav = this.jsonUtil.clone(scavProfile); const clonedScav = this.jsonUtil.clone(scavProfile);
@ -119,7 +124,7 @@ export class ProfileHelper
/** /**
* Check if a nickname is used by another profile loaded by the server * Check if a nickname is used by another profile loaded by the server
* @param nicknameRequest * @param nicknameRequest
* @param sessionID Session id * @param sessionID Session id
* @returns True if already used * @returns True if already used
*/ */
@ -133,8 +138,10 @@ export class ProfileHelper
continue; continue;
} }
if (!this.sessionIdMatchesProfileId(profile.info.id, sessionID) if (
&& this.nicknameMatches(profile.characters.pmc.Info.LowerNickname, nicknameRequest.nickname)) !this.sessionIdMatchesProfileId(profile.info.id, sessionID) &&
this.nicknameMatches(profile.characters.pmc.Info.LowerNickname, nicknameRequest.nickname)
)
{ {
return true; return true;
} }
@ -211,7 +218,7 @@ export class ProfileHelper
public getDefaultAkiDataObject(): any public getDefaultAkiDataObject(): any
{ {
return { return {
version: this.getServerVersion() version: this.getServerVersion(),
}; };
} }
@ -221,10 +228,10 @@ export class ProfileHelper
{ {
return undefined; return undefined;
} }
return this.saveServer.getProfile(sessionID); return this.saveServer.getProfile(sessionID);
} }
public getPmcProfile(sessionID: string): IPmcData public getPmcProfile(sessionID: string): IPmcData
{ {
const fullProfile = this.getFullProfile(sessionID); const fullProfile = this.getFullProfile(sessionID);
@ -232,10 +239,10 @@ export class ProfileHelper
{ {
return undefined; return undefined;
} }
return this.saveServer.getProfile(sessionID).characters.pmc; return this.saveServer.getProfile(sessionID).characters.pmc;
} }
public getScavProfile(sessionID: string): IPmcData public getScavProfile(sessionID: string): IPmcData
{ {
return this.saveServer.getProfile(sessionID).characters.scav; return this.saveServer.getProfile(sessionID).characters.scav;
@ -253,24 +260,24 @@ export class ProfileHelper
DamageHistory: { DamageHistory: {
LethalDamagePart: "Head", LethalDamagePart: "Head",
LethalDamage: undefined, LethalDamage: undefined,
BodyParts: <any>[] BodyParts: <any>[],
}, },
DroppedItems: [], DroppedItems: [],
ExperienceBonusMult: 0, ExperienceBonusMult: 0,
FoundInRaidItems: [], FoundInRaidItems: [],
LastPlayerState: undefined, LastPlayerState: undefined,
LastSessionDate: 0, LastSessionDate: 0,
OverallCounters: { Items: [] }, OverallCounters: {Items: []},
SessionCounters: { Items: [] }, SessionCounters: {Items: []},
SessionExperienceMult: 0, SessionExperienceMult: 0,
SurvivorClass: "Unknown", SurvivorClass: "Unknown",
TotalInGameTime: 0, TotalInGameTime: 0,
TotalSessionExperience: 0, TotalSessionExperience: 0,
Victims: [] Victims: [],
} },
}; };
} }
protected isWiped(sessionID: string): boolean protected isWiped(sessionID: string): boolean
{ {
return this.saveServer.getProfile(sessionID).info.wipe; return this.saveServer.getProfile(sessionID).info.wipe;
@ -289,14 +296,17 @@ export class ProfileHelper
public removeSecureContainer(profile: IPmcData): IPmcData public removeSecureContainer(profile: IPmcData): IPmcData
{ {
const items = profile.Inventory.items; const items = profile.Inventory.items;
const secureContainer = items.find(x => x.slotId === "SecuredContainer"); const secureContainer = items.find((x) => x.slotId === "SecuredContainer");
if (secureContainer) if (secureContainer)
{ {
// Find and remove container + children // Find and remove container + children
const childItemsInSecureContainer = this.itemHelper.findAndReturnChildrenByItems(items, secureContainer._id); const childItemsInSecureContainer = this.itemHelper.findAndReturnChildrenByItems(
items,
secureContainer._id,
);
// Remove child items + secure container // Remove child items + secure container
profile.Inventory.items = items.filter(x => !childItemsInSecureContainer.includes(x._id)); profile.Inventory.items = items.filter((x) => !childItemsInSecureContainer.includes(x._id));
} }
return profile; return profile;
@ -340,7 +350,7 @@ export class ProfileHelper
return false; return false;
} }
return !!profile.aki.receivedGifts.find(x => x.giftId === giftId); return !!profile.aki.receivedGifts.find((x) => x.giftId === giftId);
} }
/** /**
@ -350,7 +360,7 @@ export class ProfileHelper
*/ */
public incrementStatCounter(counters: CounterKeyValue[], keyToIncrement: string): void public incrementStatCounter(counters: CounterKeyValue[], keyToIncrement: string): void
{ {
const stat = counters.find(x => x.Key.includes(keyToIncrement)); const stat = counters.find((x) => x.Key.includes(keyToIncrement));
if (stat) if (stat)
{ {
stat.Value++; stat.Value++;
@ -371,7 +381,7 @@ export class ProfileHelper
return false; return false;
} }
const profileSkill = profileSkills.find(x => x.Id === skillType); const profileSkill = profileSkills.find((x) => x.Id === skillType);
if (!profileSkill) if (!profileSkill)
{ {
this.logger.warning(`Unable to check for elite skill ${skillType}, not found in profile`); this.logger.warning(`Unable to check for elite skill ${skillType}, not found in profile`);
@ -387,13 +397,20 @@ export class ProfileHelper
* @param pointsToAdd Points to add * @param pointsToAdd Points to add
* @param pmcProfile Player profile with skill * @param pmcProfile Player profile with skill
* @param useSkillProgressRateMultipler Skills are multiplied by a value in globals, default is off to maintain compatibility with legacy code * @param useSkillProgressRateMultipler Skills are multiplied by a value in globals, default is off to maintain compatibility with legacy code
* @returns * @returns
*/ */
public addSkillPointsToPlayer(pmcProfile: IPmcData, skill: SkillTypes, pointsToAdd: number, useSkillProgressRateMultipler = false): void public addSkillPointsToPlayer(
pmcProfile: IPmcData,
skill: SkillTypes,
pointsToAdd: number,
useSkillProgressRateMultipler = false,
): void
{ {
if (!pointsToAdd || pointsToAdd < 0) if (!pointsToAdd || pointsToAdd < 0)
{ {
this.logger.error(this.localisationService.getText("player-attempt_to_increment_skill_with_negative_value", skill)); this.logger.error(
this.localisationService.getText("player-attempt_to_increment_skill_with_negative_value", skill),
);
return; return;
} }
@ -405,7 +422,7 @@ export class ProfileHelper
return; return;
} }
const profileSkill = profileSkills.find(x => x.Id === skill); const profileSkill = profileSkills.find((x) => x.Id === skill);
if (!profileSkill) if (!profileSkill)
{ {
this.logger.error(this.localisationService.getText("quest-no_skill_found", skill)); this.logger.error(this.localisationService.getText("quest-no_skill_found", skill));
@ -426,7 +443,7 @@ export class ProfileHelper
public getSkillFromProfile(pmcData: IPmcData, skill: SkillTypes): Common public getSkillFromProfile(pmcData: IPmcData, skill: SkillTypes): Common
{ {
const skillToReturn = pmcData.Skills.Common.find(x => x.Id === skill); const skillToReturn = pmcData.Skills.Common.find((x) => x.Id === skill);
if (!skillToReturn) if (!skillToReturn)
{ {
this.logger.warning(`Profile ${pmcData.sessionId} does not have a skill named: ${skill}`); this.logger.warning(`Profile ${pmcData.sessionId} does not have a skill named: ${skill}`);
@ -435,4 +452,4 @@ export class ProfileHelper
return skillToReturn; return skillToReturn;
} }
} }

View File

@ -1,4 +1,3 @@
import { injectable } from "tsyringe"; import { injectable } from "tsyringe";
import { AvailableForConditions } from "@spt-aki/models/eft/common/tables/IQuest"; import { AvailableForConditions } from "@spt-aki/models/eft/common/tables/IQuest";
@ -6,29 +5,45 @@ import { AvailableForConditions } from "@spt-aki/models/eft/common/tables/IQuest
@injectable() @injectable()
export class QuestConditionHelper export class QuestConditionHelper
{ {
public getQuestConditions(q: AvailableForConditions[], furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null): AvailableForConditions[] public getQuestConditions(
q: AvailableForConditions[],
furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null,
): AvailableForConditions[]
{ {
return this.filterConditions(q, "Quest", furtherFilter); return this.filterConditions(q, "Quest", furtherFilter);
} }
public getLevelConditions(q: AvailableForConditions[], furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null): AvailableForConditions[] public getLevelConditions(
q: AvailableForConditions[],
furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null,
): AvailableForConditions[]
{ {
return this.filterConditions(q, "Level", furtherFilter); return this.filterConditions(q, "Level", furtherFilter);
} }
public getLoyaltyConditions(q: AvailableForConditions[], furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null): AvailableForConditions[] public getLoyaltyConditions(
q: AvailableForConditions[],
furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null,
): AvailableForConditions[]
{ {
return this.filterConditions(q, "TraderLoyalty", furtherFilter); return this.filterConditions(q, "TraderLoyalty", furtherFilter);
} }
public getStandingConditions(q: AvailableForConditions[], furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null): AvailableForConditions[] public getStandingConditions(
q: AvailableForConditions[],
furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null,
): AvailableForConditions[]
{ {
return this.filterConditions(q, "TraderStanding", furtherFilter); return this.filterConditions(q, "TraderStanding", furtherFilter);
} }
protected filterConditions(q: AvailableForConditions[], questType: string, furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null): AvailableForConditions[] protected filterConditions(
q: AvailableForConditions[],
questType: string,
furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null,
): AvailableForConditions[]
{ {
const filteredQuests = q.filter(c => const filteredQuests = q.filter((c) =>
{ {
if (c._parent === questType) if (c._parent === questType)
{ {
@ -43,4 +58,4 @@ export class QuestConditionHelper
return filteredQuests; return filteredQuests;
} }
} }

View File

@ -53,25 +53,25 @@ export class QuestHelper
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("TraderHelper") protected traderHelper: TraderHelper, @inject("TraderHelper") protected traderHelper: TraderHelper,
@inject("MailSendService") protected mailSendService: MailSendService, @inject("MailSendService") protected mailSendService: MailSendService,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST); this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST);
} }
/** /**
* Get status of a quest in player profile by its id * Get status of a quest in player profile by its id
* @param pmcData Profile to search * @param pmcData Profile to search
* @param questId Quest id to look up * @param questId Quest id to look up
* @returns QuestStatus enum * @returns QuestStatus enum
*/ */
public getQuestStatus(pmcData: IPmcData, questId: string): QuestStatus public getQuestStatus(pmcData: IPmcData, questId: string): QuestStatus
{ {
const quest = pmcData.Quests?.find(q => q.qid === questId); const quest = pmcData.Quests?.find((q) => q.qid === questId);
return quest return quest ?
? quest.status quest.status :
: QuestStatus.Locked; QuestStatus.Locked;
} }
/** /**
@ -97,7 +97,12 @@ export class QuestHelper
case "=": case "=":
return playerLevel === <number>condition._props.value; return playerLevel === <number>condition._props.value;
default: default:
this.logger.error(this.localisationService.getText("quest-unable_to_find_compare_condition", condition._props.compareMethod)); this.logger.error(
this.localisationService.getText(
"quest-unable_to_find_compare_condition",
condition._props.compareMethod,
),
);
return false; return false;
} }
} }
@ -181,7 +186,7 @@ export class QuestHelper
/** /**
* Get quest name by quest id * Get quest name by quest id
* @param questId id to get * @param questId id to get
* @returns * @returns
*/ */
public getQuestNameFromLocale(questId: string): string public getQuestNameFromLocale(questId: string): string
{ {
@ -189,7 +194,6 @@ export class QuestHelper
return this.localeService.getLocaleDb()[questNameKey]; return this.localeService.getLocaleDb()[questNameKey];
} }
/** /**
* Check if trader has sufficient loyalty to fulfill quest requirement * Check if trader has sufficient loyalty to fulfill quest requirement
* @param questProperties Quest props * @param questProperties Quest props
@ -242,7 +246,7 @@ export class QuestHelper
return current !== required; return current !== required;
case "==": case "==":
return current === required; return current === required;
default: default:
this.logger.error(this.localisationService.getText("quest-compare_operator_unhandled", compareMethod)); this.logger.error(this.localisationService.getText("quest-compare_operator_unhandled", compareMethod));
@ -274,9 +278,11 @@ export class QuestHelper
// separate base item and mods, fix stacks // separate base item and mods, fix stacks
if (item._id === reward.target) if (item._id === reward.target)
{ {
if ((item.parentId !== undefined) && (item.parentId === "hideout") if (
&& (item.upd !== undefined) && (item.upd.StackObjectsCount !== undefined) (item.parentId !== undefined) && (item.parentId === "hideout") &&
&& (item.upd.StackObjectsCount > 1)) (item.upd !== undefined) && (item.upd.StackObjectsCount !== undefined) &&
(item.upd.StackObjectsCount > 1)
)
{ {
item.upd.StackObjectsCount = 1; item.upd.StackObjectsCount = 1;
} }
@ -307,7 +313,7 @@ export class QuestHelper
items.push(this.jsonUtil.clone(mod)); items.push(this.jsonUtil.clone(mod));
} }
rewardItems = rewardItems.concat(<Reward[]> this.ragfairServerHelper.reparentPresets(target, items)); rewardItems = rewardItems.concat(<Reward[]>this.ragfairServerHelper.reparentPresets(target, items));
} }
return rewardItems; return rewardItems;
@ -323,9 +329,11 @@ export class QuestHelper
{ {
// Iterate over all rewards with the desired status, flatten out items that have a type of Item // Iterate over all rewards with the desired status, flatten out items that have a type of Item
const questRewards = quest.rewards[QuestStatus[status]] const questRewards = quest.rewards[QuestStatus[status]]
.flatMap((reward: Reward) => reward.type === "Item" .flatMap((reward: Reward) =>
? this.processReward(reward) reward.type === "Item" ?
: []); this.processReward(reward) :
[]
);
return questRewards; return questRewards;
} }
@ -336,9 +344,13 @@ export class QuestHelper
* @param newState State the new quest should be in when returned * @param newState State the new quest should be in when returned
* @param acceptedQuest Details of accepted quest from client * @param acceptedQuest Details of accepted quest from client
*/ */
public getQuestReadyForProfile(pmcData: IPmcData, newState: QuestStatus, acceptedQuest: IAcceptQuestRequestData): IQuestStatus public getQuestReadyForProfile(
pmcData: IPmcData,
newState: QuestStatus,
acceptedQuest: IAcceptQuestRequestData,
): IQuestStatus
{ {
const existingQuest = pmcData.Quests.find(q => q.qid === acceptedQuest.qid); const existingQuest = pmcData.Quests.find((q) => q.qid === acceptedQuest.qid);
if (existingQuest) if (existingQuest)
{ {
// Quest exists, update its status // Quest exists, update its status
@ -360,12 +372,12 @@ export class QuestHelper
qid: acceptedQuest.qid, qid: acceptedQuest.qid,
startTime: this.timeUtil.getTimestamp(), startTime: this.timeUtil.getTimestamp(),
status: newState, status: newState,
statusTimers: {} statusTimers: {},
}; };
// Check if quest has a prereq to be placed in a 'pending' state // Check if quest has a prereq to be placed in a 'pending' state
const questDbData = this.getQuestFromDb(acceptedQuest.qid, pmcData); const questDbData = this.getQuestFromDb(acceptedQuest.qid, pmcData);
const waitTime = questDbData.conditions.AvailableForStart.find(x => x._props.availableAfter > 0); const waitTime = questDbData.conditions.AvailableForStart.find((x) => x._props.availableAfter > 0);
if (waitTime && acceptedQuest.type !== "repeatable") if (waitTime && acceptedQuest.type !== "repeatable")
{ {
// Quest should be put into 'pending' state // Quest should be put into 'pending' state
@ -392,18 +404,18 @@ export class QuestHelper
{ {
// Get quest acceptance data from profile // Get quest acceptance data from profile
const profile: IPmcData = this.profileHelper.getPmcProfile(sessionID); const profile: IPmcData = this.profileHelper.getPmcProfile(sessionID);
const startedQuestInProfile = profile.Quests.find(x => x.qid === startedQuestId); const startedQuestInProfile = profile.Quests.find((x) => x.qid === startedQuestId);
// Get quests that // Get quests that
const eligibleQuests = this.getQuestsFromDb().filter((quest) => const eligibleQuests = this.getQuestsFromDb().filter((quest) =>
{ {
// Quest is accessible to player when the accepted quest passed into param is started // Quest is accessible to player when the accepted quest passed into param is started
// e.g. Quest A passed in, quest B is looped over and has requirement of A to be started, include it // e.g. Quest A passed in, quest B is looped over and has requirement of A to be started, include it
const acceptedQuestCondition = quest.conditions.AvailableForStart.find(x => const acceptedQuestCondition = quest.conditions.AvailableForStart.find((x) =>
{ {
return x._parent === "Quest" return x._parent === "Quest" &&
&& x._props.target === startedQuestId x._props.target === startedQuestId &&
&& x._props.status[0] === QuestStatus.Started; x._props.status[0] === QuestStatus.Started;
}); });
// Not found, skip quest // Not found, skip quest
@ -412,7 +424,9 @@ export class QuestHelper
return false; return false;
} }
const standingRequirements = this.questConditionHelper.getStandingConditions(quest.conditions.AvailableForStart); const standingRequirements = this.questConditionHelper.getStandingConditions(
quest.conditions.AvailableForStart,
);
for (const condition of standingRequirements) for (const condition of standingRequirements)
{ {
if (!this.traderStandingRequirementCheck(condition._props, profile)) if (!this.traderStandingRequirementCheck(condition._props, profile))
@ -421,7 +435,9 @@ export class QuestHelper
} }
} }
const loyaltyRequirements = this.questConditionHelper.getLoyaltyConditions(quest.conditions.AvailableForStart); const loyaltyRequirements = this.questConditionHelper.getLoyaltyConditions(
quest.conditions.AvailableForStart,
);
for (const condition of loyaltyRequirements) for (const condition of loyaltyRequirements)
{ {
if (!this.traderLoyaltyLevelRequirementCheck(condition._props, profile)) if (!this.traderLoyaltyLevelRequirementCheck(condition._props, profile))
@ -431,7 +447,8 @@ export class QuestHelper
} }
// Include if quest found in profile and is started or ready to hand in // Include if quest found in profile and is started or ready to hand in
return startedQuestInProfile && ([QuestStatus.Started, QuestStatus.AvailableForFinish].includes(startedQuestInProfile.status)); return startedQuestInProfile &&
([QuestStatus.Started, QuestStatus.AvailableForFinish].includes(startedQuestInProfile.status));
}); });
return this.getQuestsWithOnlyLevelRequirementStartCondition(eligibleQuests); return this.getQuestsWithOnlyLevelRequirementStartCondition(eligibleQuests);
@ -446,17 +463,18 @@ export class QuestHelper
public failedUnlocked(failedQuestId: string, sessionId: string): IQuest[] public failedUnlocked(failedQuestId: string, sessionId: string): IQuest[]
{ {
const profile = this.profileHelper.getPmcProfile(sessionId); const profile = this.profileHelper.getPmcProfile(sessionId);
const profileQuest = profile.Quests.find(x => x.qid === failedQuestId); const profileQuest = profile.Quests.find((x) => x.qid === failedQuestId);
const quests = this.getQuestsFromDb().filter((q) => const quests = this.getQuestsFromDb().filter((q) =>
{ {
const acceptedQuestCondition = q.conditions.AvailableForStart.find( const acceptedQuestCondition = q.conditions.AvailableForStart.find(
c => (c) =>
{ {
return c._parent === "Quest" return c._parent === "Quest" &&
&& c._props.target === failedQuestId c._props.target === failedQuestId &&
&& c._props.status[0] === QuestStatus.Fail; c._props.status[0] === QuestStatus.Fail;
}); },
);
if (!acceptedQuestCondition) if (!acceptedQuestCondition)
{ {
@ -490,7 +508,9 @@ export class QuestHelper
{ {
if (this.paymentHelper.isMoneyTpl(reward.items[0]._tpl)) if (this.paymentHelper.isMoneyTpl(reward.items[0]._tpl))
{ {
reward.items[0].upd.StackObjectsCount += Math.round(reward.items[0].upd.StackObjectsCount * multiplier / 100); reward.items[0].upd.StackObjectsCount += Math.round(
reward.items[0].upd.StackObjectsCount * multiplier / 100,
);
} }
} }
} }
@ -507,9 +527,15 @@ export class QuestHelper
* @param sessionID Session id * @param sessionID Session id
* @param output ItemEvent router response * @param output ItemEvent router response
*/ */
public changeItemStack(pmcData: IPmcData, itemId: string, newStackSize: number, sessionID: string, output: IItemEventRouterResponse): void public changeItemStack(
pmcData: IPmcData,
itemId: string,
newStackSize: number,
sessionID: string,
output: IItemEventRouterResponse,
): void
{ {
const inventoryItemIndex = pmcData.Inventory.items.findIndex(item => item._id === itemId); const inventoryItemIndex = pmcData.Inventory.items.findIndex((item) => item._id === itemId);
if (inventoryItemIndex < 0) if (inventoryItemIndex < 0)
{ {
this.logger.error(this.localisationService.getText("quest-item_not_found_in_inventory", itemId)); this.logger.error(this.localisationService.getText("quest-item_not_found_in_inventory", itemId));
@ -532,7 +558,7 @@ export class QuestHelper
{ {
// this case is probably dead Code right now, since the only calling function // this case is probably dead Code right now, since the only calling function
// checks explicitly for Value > 0. // checks explicitly for Value > 0.
output.profileChanges[sessionID].items.del.push({ _id: itemId }); output.profileChanges[sessionID].items.del.push({_id: itemId});
pmcData.Inventory.items.splice(inventoryItemIndex, 1); pmcData.Inventory.items.splice(inventoryItemIndex, 1);
} }
} }
@ -543,7 +569,11 @@ export class QuestHelper
* @param sessionId Session id * @param sessionId Session id
* @param item Item that was adjusted * @param item Item that was adjusted
*/ */
protected addItemStackSizeChangeIntoEventResponse(output: IItemEventRouterResponse, sessionId: string, item: Item): void protected addItemStackSizeChangeIntoEventResponse(
output: IItemEventRouterResponse,
sessionId: string,
item: Item,
): void
{ {
output.profileChanges[sessionId].items.change.push({ output.profileChanges[sessionId].items.change.push({
_id: item._id, _id: item._id,
@ -552,8 +582,8 @@ export class QuestHelper
slotId: item.slotId, slotId: item.slotId,
location: item.location, location: item.location,
upd: { upd: {
StackObjectsCount: item.upd.StackObjectsCount StackObjectsCount: item.upd.StackObjectsCount,
} },
}); });
} }
@ -580,7 +610,7 @@ export class QuestHelper
public getQuestWithOnlyLevelRequirementStartCondition(quest: IQuest): IQuest public getQuestWithOnlyLevelRequirementStartCondition(quest: IQuest): IQuest
{ {
quest = this.jsonUtil.clone(quest); quest = this.jsonUtil.clone(quest);
quest.conditions.AvailableForStart = quest.conditions.AvailableForStart.filter(q => q._parent === "Level"); quest.conditions.AvailableForStart = quest.conditions.AvailableForStart.filter((q) => q._parent === "Level");
return quest; return quest;
} }
@ -593,7 +623,12 @@ export class QuestHelper
* @param output Client output * @param output Client output
* @returns Item event router response * @returns Item event router response
*/ */
public failQuest(pmcData: IPmcData, failRequest: IFailQuestRequestData, sessionID: string, output: IItemEventRouterResponse = null): IItemEventRouterResponse public failQuest(
pmcData: IPmcData,
failRequest: IFailQuestRequestData,
sessionID: string,
output: IItemEventRouterResponse = null,
): IItemEventRouterResponse
{ {
// Prepare response to send back client // Prepare response to send back client
if (!output) if (!output)
@ -613,7 +648,7 @@ export class QuestHelper
MessageType.QUEST_FAIL, MessageType.QUEST_FAIL,
quest.failMessageText, quest.failMessageText,
questRewards, questRewards,
this.timeUtil.getHoursAsSeconds(this.questConfig.redeemTime) this.timeUtil.getHoursAsSeconds(this.questConfig.redeemTime),
); );
output.profileChanges[sessionID].quests.push(this.failedUnlocked(failRequest.qid, sessionID)); output.profileChanges[sessionID].quests.push(this.failedUnlocked(failRequest.qid, sessionID));
@ -647,7 +682,7 @@ export class QuestHelper
// Check daily/weekly objects // Check daily/weekly objects
for (const repeatableType of pmcData.RepeatableQuests) for (const repeatableType of pmcData.RepeatableQuests)
{ {
quest = <IQuest><unknown>repeatableType.activeQuests.find(x => x._id === questId); quest = <IQuest><unknown>repeatableType.activeQuests.find((x) => x._id === questId);
if (quest) if (quest)
{ {
break; break;
@ -668,7 +703,10 @@ export class QuestHelper
{ {
// blank or is a guid, use description instead // blank or is a guid, use description instead
const startedMessageText = this.getQuestLocaleIdFromDb(startedMessageTextId); const startedMessageText = this.getQuestLocaleIdFromDb(startedMessageTextId);
if (!startedMessageText || startedMessageText.trim() === "" || startedMessageText.toLowerCase() === "test" || startedMessageText.length === 24) if (
!startedMessageText || startedMessageText.trim() === "" || startedMessageText.toLowerCase() === "test" ||
startedMessageText.length === 24
)
{ {
return questDescriptionId; return questDescriptionId;
} }
@ -696,7 +734,7 @@ export class QuestHelper
public updateQuestState(pmcData: IPmcData, newQuestState: QuestStatus, questId: string): void public updateQuestState(pmcData: IPmcData, newQuestState: QuestStatus, questId: string): void
{ {
// Find quest in profile, update status to desired status // Find quest in profile, update status to desired status
const questToUpdate = pmcData.Quests.find(quest => quest.qid === questId); const questToUpdate = pmcData.Quests.find((quest) => quest.qid === questId);
if (questToUpdate) if (questToUpdate)
{ {
questToUpdate.status = newQuestState; questToUpdate.status = newQuestState;
@ -713,8 +751,14 @@ export class QuestHelper
* @param questResponse Response to send back to client * @param questResponse Response to send back to client
* @returns Array of reward objects * @returns Array of reward objects
*/ */
public applyQuestReward(pmcData: IPmcData, questId: string, state: QuestStatus, sessionId: string, questResponse: IItemEventRouterResponse): Reward[] public applyQuestReward(
{ pmcData: IPmcData,
questId: string,
state: QuestStatus,
sessionId: string,
questResponse: IItemEventRouterResponse,
): Reward[]
{
let questDetails = this.getQuestFromDb(questId, pmcData); let questDetails = this.getQuestFromDb(questId, pmcData);
if (!questDetails) if (!questDetails)
{ {
@ -722,7 +766,7 @@ export class QuestHelper
return []; return [];
} }
// Check for and apply intel center money bonus if it exists // Check for and apply intel center money bonus if it exists
const questMoneyRewardBonus = this.getQuestMoneyRewardBonus(pmcData); const questMoneyRewardBonus = this.getQuestMoneyRewardBonus(pmcData);
if (questMoneyRewardBonus > 0) if (questMoneyRewardBonus > 0)
@ -738,7 +782,11 @@ export class QuestHelper
switch (reward.type) switch (reward.type)
{ {
case QuestRewardType.SKILL: case QuestRewardType.SKILL:
this.profileHelper.addSkillPointsToPlayer(pmcData, reward.target as SkillTypes, Number(reward.value)); this.profileHelper.addSkillPointsToPlayer(
pmcData,
reward.target as SkillTypes,
Number(reward.value),
);
break; break;
case QuestRewardType.EXPERIENCE: case QuestRewardType.EXPERIENCE:
this.profileHelper.addExperienceToPmc(sessionId, parseInt(<string>reward.value)); // this must occur first as the output object needs to take the modified profile exp value this.profileHelper.addExperienceToPmc(sessionId, parseInt(<string>reward.value)); // this must occur first as the output object needs to take the modified profile exp value
@ -759,10 +807,22 @@ export class QuestHelper
this.logger.debug("Not implemented stash rows reward yet"); this.logger.debug("Not implemented stash rows reward yet");
break; break;
case QuestRewardType.PRODUCTIONS_SCHEME: case QuestRewardType.PRODUCTIONS_SCHEME:
this.findAndAddHideoutProductionIdToProfile(pmcData, reward, questDetails, sessionId, questResponse); this.findAndAddHideoutProductionIdToProfile(
pmcData,
reward,
questDetails,
sessionId,
questResponse,
);
break; break;
default: default:
this.logger.error(this.localisationService.getText("quest-reward_type_not_handled", {rewardType: reward.type, questId: questId, questName: questDetails.QuestName})); this.logger.error(
this.localisationService.getText("quest-reward_type_not_handled", {
rewardType: reward.type,
questId: questId,
questName: questDetails.QuestName,
}),
);
break; break;
} }
} }
@ -779,19 +839,31 @@ export class QuestHelper
* @param sessionID Session id * @param sessionID Session id
* @param response Response to send back to client * @param response Response to send back to client
*/ */
protected findAndAddHideoutProductionIdToProfile(pmcData: IPmcData, craftUnlockReward: Reward, questDetails: IQuest, sessionID: string, response: IItemEventRouterResponse): void protected findAndAddHideoutProductionIdToProfile(
pmcData: IPmcData,
craftUnlockReward: Reward,
questDetails: IQuest,
sessionID: string,
response: IItemEventRouterResponse,
): void
{ {
// Get hideout crafts and find those that match by areatype/required level/end product tpl - hope for just one match // Get hideout crafts and find those that match by areatype/required level/end product tpl - hope for just one match
const hideoutProductions = this.databaseServer.getTables().hideout.production; const hideoutProductions = this.databaseServer.getTables().hideout.production;
const matchingProductions = hideoutProductions.filter(x => const matchingProductions = hideoutProductions.filter((x) =>
x.areaType === Number.parseInt(craftUnlockReward.traderId) x.areaType === Number.parseInt(craftUnlockReward.traderId) &&
&& x.requirements.some(x => x.requiredLevel === craftUnlockReward.loyaltyLevel) x.requirements.some((x) => x.requiredLevel === craftUnlockReward.loyaltyLevel) &&
&& x.endProduct === craftUnlockReward.items[0]._tpl); x.endProduct === craftUnlockReward.items[0]._tpl
);
// More/less than 1 match, above filtering wasn't strict enough // More/less than 1 match, above filtering wasn't strict enough
if (matchingProductions.length !== 1) if (matchingProductions.length !== 1)
{ {
this.logger.error(this.localisationService.getText("quest-unable_to_find_matching_hideout_production", {questName: questDetails.QuestName, matchCount: matchingProductions.length})); this.logger.error(
this.localisationService.getText("quest-unable_to_find_matching_hideout_production", {
questName: questDetails.QuestName,
matchCount: matchingProductions.length,
}),
);
return; return;
} }
@ -810,7 +882,7 @@ export class QuestHelper
protected getQuestMoneyRewardBonus(pmcData: IPmcData): number protected getQuestMoneyRewardBonus(pmcData: IPmcData): number
{ {
// Check player has intel center // Check player has intel center
const moneyRewardBonuses = pmcData.Bonuses.filter(x => x.type === "QuestMoneyReward"); const moneyRewardBonuses = pmcData.Bonuses.filter((x) => x.type === "QuestMoneyReward");
if (!moneyRewardBonuses) if (!moneyRewardBonuses)
{ {
return 0; return 0;
@ -823,7 +895,7 @@ export class QuestHelper
const hideoutManagementSkill = this.profileHelper.getSkillFromProfile(pmcData, SkillTypes.HIDEOUT_MANAGEMENT); const hideoutManagementSkill = this.profileHelper.getSkillFromProfile(pmcData, SkillTypes.HIDEOUT_MANAGEMENT);
if (hideoutManagementSkill) if (hideoutManagementSkill)
{ {
moneyRewardBonus *= (1 + (hideoutManagementSkill.Progress / 10000)); // 5100 becomes 0.51, add 1 to it, 1.51, multiply the moneyreward bonus by it (e.g. 15 x 51) moneyRewardBonus *= 1 + (hideoutManagementSkill.Progress / 10000); // 5100 becomes 0.51, add 1 to it, 1.51, multiply the moneyreward bonus by it (e.g. 15 x 51)
} }
return moneyRewardBonus; return moneyRewardBonus;
@ -835,19 +907,27 @@ export class QuestHelper
* @param questIds Quests to search through for the findItem condition * @param questIds Quests to search through for the findItem condition
* @returns quest id with 'FindItem' condition id * @returns quest id with 'FindItem' condition id
*/ */
public getFindItemConditionByQuestItem(itemTpl: string, questIds: string[], allQuests: IQuest[]): Record<string, string> public getFindItemConditionByQuestItem(
itemTpl: string,
questIds: string[],
allQuests: IQuest[],
): Record<string, string>
{ {
const result: Record<string, string> = {}; const result: Record<string, string> = {};
for (const questId of questIds) for (const questId of questIds)
{ {
const questInDb = allQuests.find(x => x._id === questId); const questInDb = allQuests.find((x) => x._id === questId);
if (!questInDb) if (!questInDb)
{ {
this.logger.warning(`Unable to find quest: ${questId} in db, cannot get 'FindItem' condition, skipping`); this.logger.warning(
`Unable to find quest: ${questId} in db, cannot get 'FindItem' condition, skipping`,
);
continue; continue;
} }
const condition = questInDb.conditions.AvailableForFinish.find(c => c._parent === "FindItem" && c._props?.target?.includes(itemTpl)); const condition = questInDb.conditions.AvailableForFinish.find((c) =>
c._parent === "FindItem" && c._props?.target?.includes(itemTpl)
);
if (condition) if (condition)
{ {
result[questId] = condition._props.id; result[questId] = condition._props.id;
@ -872,7 +952,7 @@ export class QuestHelper
{ {
// Quest from db matches quests in profile, skip // Quest from db matches quests in profile, skip
const questData = quests[questKey]; const questData = quests[questKey];
if (pmcProfile.Quests.find(x => x.qid === questData._id)) if (pmcProfile.Quests.find((x) => x.qid === questData._id))
{ {
continue; continue;
} }
@ -889,13 +969,13 @@ export class QuestHelper
status: statuses[statuses.length - 1], status: statuses[statuses.length - 1],
statusTimers: statusesDict, statusTimers: statusesDict,
completedConditions: [], completedConditions: [],
availableAfter: 0 availableAfter: 0,
}; };
if (pmcProfile.Quests.some(x => x.qid === questKey)) if (pmcProfile.Quests.some((x) => x.qid === questKey))
{ {
// Update existing // Update existing
const existingQuest = pmcProfile.Quests.find(x => x.qid === questKey); const existingQuest = pmcProfile.Quests.find((x) => x.qid === questKey);
existingQuest.status = questRecordToAdd.status; existingQuest.status = questRecordToAdd.status;
existingQuest.statusTimers = questRecordToAdd.statusTimers; existingQuest.statusTimers = questRecordToAdd.statusTimers;
} }
@ -909,10 +989,10 @@ export class QuestHelper
public findAndRemoveQuestFromArrayIfExists(questId: string, quests: IQuestStatus[]): void public findAndRemoveQuestFromArrayIfExists(questId: string, quests: IQuestStatus[]): void
{ {
const pmcQuestToReplaceStatus = quests.find(x => x.qid === questId); const pmcQuestToReplaceStatus = quests.find((x) => x.qid === questId);
if (pmcQuestToReplaceStatus) if (pmcQuestToReplaceStatus)
{ {
quests.splice(quests.indexOf(pmcQuestToReplaceStatus, 1)); quests.splice(quests.indexOf(pmcQuestToReplaceStatus, 1));
} }
} }
} }

View File

@ -31,17 +31,17 @@ export class RagfairHelper
@inject("ItemHelper") protected itemHelper: ItemHelper, @inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("RagfairLinkedItemService") protected ragfairLinkedItemService: RagfairLinkedItemService, @inject("RagfairLinkedItemService") protected ragfairLinkedItemService: RagfairLinkedItemService,
@inject("UtilityHelper") protected utilityHelper: UtilityHelper, @inject("UtilityHelper") protected utilityHelper: UtilityHelper,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR); this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
} }
/** /**
* Gets currency TAG from TPL * Gets currency TAG from TPL
* @param {string} currency * @param {string} currency
* @returns string * @returns string
*/ */
public getCurrencyTag(currency: string): string public getCurrencyTag(currency: string): string
{ {
switch (currency) switch (currency)
@ -73,9 +73,9 @@ export class RagfairHelper
if (info.linkedSearchId) if (info.linkedSearchId)
{ {
const data = this.ragfairLinkedItemService.getLinkedItems(info.linkedSearchId); const data = this.ragfairLinkedItemService.getLinkedItems(info.linkedSearchId);
result = !data result = !data ?
? [] [] :
: [...data]; [...data];
} }
// Case: category // Case: category
@ -185,7 +185,7 @@ export class RagfairHelper
for (let item of items) for (let item of items)
{ {
item = this.itemHelper.fixItemStackCount(item); item = this.itemHelper.fixItemStackCount(item);
const isChild = items.find(it => it._id === item.parentId); const isChild = items.find((it) => it._id === item.parentId);
if (!isChild) if (!isChild)
{ {

View File

@ -60,7 +60,7 @@ export class RagfairOfferHelper
@inject("LocaleService") protected localeService: LocaleService, @inject("LocaleService") protected localeService: LocaleService,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("MailSendService") protected mailSendService: MailSendService, @inject("MailSendService") protected mailSendService: MailSendService,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR); this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
@ -75,9 +75,16 @@ export class RagfairOfferHelper
* @param pmcProfile Player profile * @param pmcProfile Player profile
* @returns Offers the player should see * @returns Offers the player should see
*/ */
public getValidOffers(searchRequest: ISearchRequestData, itemsToAdd: string[], traderAssorts: Record<string, ITraderAssort>, pmcProfile: IPmcData): IRagfairOffer[] public getValidOffers(
searchRequest: ISearchRequestData,
itemsToAdd: string[],
traderAssorts: Record<string, ITraderAssort>,
pmcProfile: IPmcData,
): IRagfairOffer[]
{ {
return this.ragfairOfferService.getOffers().filter(x => this.isDisplayableOffer(searchRequest, itemsToAdd, traderAssorts, x, pmcProfile)); return this.ragfairOfferService.getOffers().filter((x) =>
this.isDisplayableOffer(searchRequest, itemsToAdd, traderAssorts, x, pmcProfile)
);
} }
/** /**
@ -88,7 +95,12 @@ export class RagfairOfferHelper
* @param pmcProfile Player profile * @param pmcProfile Player profile
* @returns IRagfairOffer array * @returns IRagfairOffer array
*/ */
public getOffersForBuild(searchRequest: ISearchRequestData, itemsToAdd: string[], traderAssorts: Record<string, ITraderAssort>, pmcProfile: IPmcData): IRagfairOffer[] public getOffersForBuild(
searchRequest: ISearchRequestData,
itemsToAdd: string[],
traderAssorts: Record<string, ITraderAssort>,
pmcProfile: IPmcData,
): IRagfairOffer[]
{ {
const offersMap = new Map<string, IRagfairOffer[]>(); const offersMap = new Map<string, IRagfairOffer[]>();
const offers: IRagfairOffer[] = []; const offers: IRagfairOffer[] = [];
@ -137,13 +149,13 @@ export class RagfairOfferHelper
if (possibleOffers.length > 1) if (possibleOffers.length > 1)
{ {
const lockedOffers = this.getLoyaltyLockedOffers(possibleOffers, pmcProfile); const lockedOffers = this.getLoyaltyLockedOffers(possibleOffers, pmcProfile);
// Exclude locked offers + above loyalty locked offers if at least 1 was found // Exclude locked offers + above loyalty locked offers if at least 1 was found
const availableOffers = possibleOffers.filter(x => !(x.locked || lockedOffers.includes(x._id))); const availableOffers = possibleOffers.filter((x) => !(x.locked || lockedOffers.includes(x._id)));
if (availableOffers.length > 0) if (availableOffers.length > 0)
{ {
possibleOffers = availableOffers; possibleOffers = availableOffers;
} }
} }
const offer = this.ragfairSortHelper.sortOffers(possibleOffers, RagfairSort.PRICE, 0)[0]; const offer = this.ragfairSortHelper.sortOffers(possibleOffers, RagfairSort.PRICE, 0)[0];
@ -174,7 +186,9 @@ export class RagfairOfferHelper
*/ */
public traderOfferItemQuestLocked(offer: IRagfairOffer, traderAssorts: Record<string, ITraderAssort>): boolean public traderOfferItemQuestLocked(offer: IRagfairOffer, traderAssorts: Record<string, ITraderAssort>): boolean
{ {
return offer.items?.some(i => traderAssorts[offer.user.id].barter_scheme[i._id]?.some(bs1 => bs1?.some(bs2 => bs2.sptQuestLocked))); return offer.items?.some((i) =>
traderAssorts[offer.user.id].barter_scheme[i._id]?.some((bs1) => bs1?.some((bs2) => bs2.sptQuestLocked))
);
} }
/** /**
@ -182,7 +196,7 @@ export class RagfairOfferHelper
* @param offer Offer to check stock of * @param offer Offer to check stock of
* @returns true if out of stock * @returns true if out of stock
*/ */
protected traderOutOfStock(offer: IRagfairOffer): boolean protected traderOutOfStock(offer: IRagfairOffer): boolean
{ {
if (offer?.items?.length === 0) if (offer?.items?.length === 0)
{ {
@ -200,17 +214,21 @@ export class RagfairOfferHelper
protected traderBuyRestrictionReached(offer: IRagfairOffer): boolean protected traderBuyRestrictionReached(offer: IRagfairOffer): boolean
{ {
const traderAssorts = this.traderHelper.getTraderAssortsByTraderId(offer.user.id).items; const traderAssorts = this.traderHelper.getTraderAssortsByTraderId(offer.user.id).items;
const assortData = traderAssorts.find(x => x._id === offer.items[0]._id); const assortData = traderAssorts.find((x) => x._id === offer.items[0]._id);
// No trader assort data // No trader assort data
if (!assortData) if (!assortData)
{ {
this.logger.warning(`Unable to find trader: ${offer.user.nickname} assort for item: ${this.itemHelper.getItemName(offer.items[0]._tpl)} ${offer.items[0]._tpl}, cannot check if buy restriction reached`); this.logger.warning(
`Unable to find trader: ${offer.user.nickname} assort for item: ${
this.itemHelper.getItemName(offer.items[0]._tpl)
} ${offer.items[0]._tpl}, cannot check if buy restriction reached`,
);
return false; return false;
} }
// No restriction values // No restriction values
// Can't use !assortData.upd.BuyRestrictionX as value could be 0 // Can't use !assortData.upd.BuyRestrictionX as value could be 0
if (assortData.upd.BuyRestrictionMax === undefined || assortData.upd.BuyRestrictionCurrent === undefined) if (assortData.upd.BuyRestrictionMax === undefined || assortData.upd.BuyRestrictionCurrent === undefined)
{ {
return false; return false;
@ -279,7 +297,10 @@ export class RagfairOfferHelper
boughtAmount = offer.sellResult[0].amount; boughtAmount = offer.sellResult[0].amount;
} }
this.increaseProfileRagfairRating(this.saveServer.getProfile(sessionID), offer.summaryCost / totalItemsCount * boughtAmount); this.increaseProfileRagfairRating(
this.saveServer.getProfile(sessionID),
offer.summaryCost / totalItemsCount * boughtAmount,
);
this.completeOffer(sessionID, offer, boughtAmount); this.completeOffer(sessionID, offer, boughtAmount);
offer.sellResult.splice(0, 1); offer.sellResult.splice(0, 1);
@ -331,7 +352,7 @@ export class RagfairOfferHelper
protected deleteOfferById(sessionID: string, offerId: string): void protected deleteOfferById(sessionID: string, offerId: string): void
{ {
const profileRagfairInfo = this.saveServer.getProfile(sessionID).characters.pmc.RagfairInfo; const profileRagfairInfo = this.saveServer.getProfile(sessionID).characters.pmc.RagfairInfo;
const index = profileRagfairInfo.offers.findIndex(o => o._id === offerId); const index = profileRagfairInfo.offers.findIndex((o) => o._id === offerId);
profileRagfairInfo.offers.splice(index, 1); profileRagfairInfo.offers.splice(index, 1);
// Also delete from ragfair // Also delete from ragfair
this.ragfairOfferService.removeOfferById(offerId); this.ragfairOfferService.removeOfferById(offerId);
@ -357,7 +378,7 @@ export class RagfairOfferHelper
else else
{ {
offer.items[0].upd.StackObjectsCount -= boughtAmount; offer.items[0].upd.StackObjectsCount -= boughtAmount;
const rootItems = offer.items.filter(i => i.parentId === "hideout"); const rootItems = offer.items.filter((i) => i.parentId === "hideout");
rootItems.splice(0, 1); rootItems.splice(0, 1);
let removeCount = boughtAmount; let removeCount = boughtAmount;
@ -384,11 +405,12 @@ export class RagfairOfferHelper
while (foundNewItems) while (foundNewItems)
{ {
foundNewItems = false; foundNewItems = false;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const id of idsToRemove) for (const id of idsToRemove)
{ {
const newIds = offer.items.filter(i => !idsToRemove.includes(i._id) && idsToRemove.includes(i.parentId)).map(i => i._id); const newIds = offer.items.filter((i) =>
!idsToRemove.includes(i._id) && idsToRemove.includes(i.parentId)
).map((i) => i._id);
if (newIds.length > 0) if (newIds.length > 0)
{ {
foundNewItems = true; foundNewItems = true;
@ -399,7 +421,7 @@ export class RagfairOfferHelper
if (idsToRemove.length > 0) if (idsToRemove.length > 0)
{ {
offer.items = offer.items.filter(i => !idsToRemove.includes(i._id)); offer.items = offer.items.filter((i) => !idsToRemove.includes(i._id));
} }
} }
@ -410,7 +432,7 @@ export class RagfairOfferHelper
const requestedItem: Item = { const requestedItem: Item = {
_id: this.hashUtil.generate(), _id: this.hashUtil.generate(),
_tpl: requirement._tpl, _tpl: requirement._tpl,
upd: { StackObjectsCount: requirement.count * boughtAmount } upd: {StackObjectsCount: requirement.count * boughtAmount},
}; };
const stacks = this.itemHelper.splitStack(requestedItem); const stacks = this.itemHelper.splitStack(requestedItem);
@ -436,7 +458,7 @@ export class RagfairOfferHelper
const ragfairDetails = { const ragfairDetails = {
offerId: offer._id, offerId: offer._id,
count: offer.sellInOnePiece ? offerStackCount : boughtAmount, // pack-offers NEED to the full item count otherwise it only removes 1 from the pack, leaving phantom offer on client ui count: offer.sellInOnePiece ? offerStackCount : boughtAmount, // pack-offers NEED to the full item count otherwise it only removes 1 from the pack, leaving phantom offer on client ui
handbookId: itemTpl handbookId: itemTpl,
}; };
this.mailSendService.sendDirectNpcMessageToPlayer( this.mailSendService.sendDirectNpcMessageToPlayer(
@ -447,7 +469,8 @@ export class RagfairOfferHelper
itemsToSend, itemsToSend,
this.timeUtil.getHoursAsSeconds(this.questConfig.redeemTime), this.timeUtil.getHoursAsSeconds(this.questConfig.redeemTime),
null, null,
ragfairDetails); ragfairDetails,
);
return this.eventOutputHolder.getOutput(sessionID); return this.eventOutputHolder.getOutput(sessionID);
} }
@ -465,14 +488,19 @@ export class RagfairOfferHelper
const soldMessageLocaleGuid = globalLocales[RagfairOfferHelper.goodSoldTemplate]; const soldMessageLocaleGuid = globalLocales[RagfairOfferHelper.goodSoldTemplate];
if (!soldMessageLocaleGuid) if (!soldMessageLocaleGuid)
{ {
this.logger.error(this.localisationService.getText("ragfair-unable_to_find_locale_by_key", RagfairOfferHelper.goodSoldTemplate)); this.logger.error(
this.localisationService.getText(
"ragfair-unable_to_find_locale_by_key",
RagfairOfferHelper.goodSoldTemplate,
),
);
} }
// Used to replace tokens in sold message sent to player // Used to replace tokens in sold message sent to player
const tplVars: ISystemData = { const tplVars: ISystemData = {
soldItem: globalLocales[`${itemTpl} Name`] || itemTpl, soldItem: globalLocales[`${itemTpl} Name`] || itemTpl,
buyerNickname: this.ragfairServerHelper.getNickname(this.hashUtil.generate()), buyerNickname: this.ragfairServerHelper.getNickname(this.hashUtil.generate()),
itemCount: boughtAmount itemCount: boughtAmount,
}; };
const offerSoldMessageText = soldMessageLocaleGuid.replace(/{\w+}/g, (matched) => const offerSoldMessageText = soldMessageLocaleGuid.replace(/{\w+}/g, (matched) =>
@ -492,14 +520,23 @@ export class RagfairOfferHelper
* @param pmcProfile Player profile * @param pmcProfile Player profile
* @returns True = should be shown to player * @returns True = should be shown to player
*/ */
public isDisplayableOffer(searchRequest: ISearchRequestData, itemsToAdd: string[], traderAssorts: Record<string, ITraderAssort>, offer: IRagfairOffer, pmcProfile: IPmcData): boolean public isDisplayableOffer(
searchRequest: ISearchRequestData,
itemsToAdd: string[],
traderAssorts: Record<string, ITraderAssort>,
offer: IRagfairOffer,
pmcProfile: IPmcData,
): boolean
{ {
const item = offer.items[0]; const item = offer.items[0];
const money = offer.requirements[0]._tpl; const money = offer.requirements[0]._tpl;
const isTraderOffer = offer.user.memberType === MemberCategory.TRADER; const isTraderOffer = offer.user.memberType === MemberCategory.TRADER;
const isDefaultUserOffer = offer.user.memberType === MemberCategory.DEFAULT; const isDefaultUserOffer = offer.user.memberType === MemberCategory.DEFAULT;
if (pmcProfile.Info.Level < this.databaseServer.getTables().globals.config.RagFair.minUserLevel && isDefaultUserOffer) if (
pmcProfile.Info.Level < this.databaseServer.getTables().globals.config.RagFair.minUserLevel &&
isDefaultUserOffer
)
{ {
// Skip item if player is < global unlock level (default is 15) and item is from a dynamically generated source // Skip item if player is < global unlock level (default is 15) and item is from a dynamically generated source
return false; return false;
@ -512,7 +549,7 @@ export class RagfairOfferHelper
} }
// Performing a required search and offer doesn't have requirement for item // Performing a required search and offer doesn't have requirement for item
if (searchRequest.neededSearchId && !offer.requirements.some(x => x._tpl === searchRequest.neededSearchId)) if (searchRequest.neededSearchId && !offer.requirements.some((x) => x._tpl === searchRequest.neededSearchId))
{ {
return false; return false;
} }
@ -559,7 +596,10 @@ export class RagfairOfferHelper
return false; return false;
} }
if (( item.upd.MedKit || item.upd.Repairable ) && !this.itemQualityInRange(item, searchRequest.conditionFrom, searchRequest.conditionTo)) if (
(item.upd.MedKit || item.upd.Repairable) &&
!this.itemQualityInRange(item, searchRequest.conditionFrom, searchRequest.conditionTo)
)
{ {
return false; return false;
} }
@ -612,10 +652,12 @@ export class RagfairOfferHelper
return false; return false;
} }
if (!traderAssorts[offer.user.id].items.find((item) => if (
{ !traderAssorts[offer.user.id].items.find((item) =>
return item._id === offer.root; {
})) return item._id === offer.root;
})
)
{ {
// skip (quest) locked items // skip (quest) locked items
return false; return false;
@ -649,4 +691,4 @@ export class RagfairOfferHelper
return true; return true;
} }
} }

View File

@ -17,7 +17,7 @@ export class RagfairSellHelper
@inject("WinstonLogger") protected logger: ILogger, @inject("WinstonLogger") protected logger: ILogger,
@inject("RandomUtil") protected randomUtil: RandomUtil, @inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("TimeUtil") protected timeUtil: TimeUtil, @inject("TimeUtil") protected timeUtil: TimeUtil,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR); this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
@ -30,15 +30,20 @@ export class RagfairSellHelper
* @param qualityMultiplier Quality multipler of item being sold * @param qualityMultiplier Quality multipler of item being sold
* @returns percent value * @returns percent value
*/ */
public calculateSellChance(averageOfferPriceRub: number, playerListedPriceRub: number, qualityMultiplier: number): number public calculateSellChance(
averageOfferPriceRub: number,
playerListedPriceRub: number,
qualityMultiplier: number,
): number
{ {
const baseSellChancePercent = this.ragfairConfig.sell.chance.base * qualityMultiplier; const baseSellChancePercent = this.ragfairConfig.sell.chance.base * qualityMultiplier;
const listedPriceAboveAverage = playerListedPriceRub > averageOfferPriceRub; const listedPriceAboveAverage = playerListedPriceRub > averageOfferPriceRub;
// Get sell chance multiplier // Get sell chance multiplier
const multiplier = (listedPriceAboveAverage) const multiplier = listedPriceAboveAverage ?
? this.ragfairConfig.sell.chance.overpriced // Player price is over average listing price this.ragfairConfig.sell.chance.overpriced // Player price is over average listing price
: this.getSellMultiplierWhenPlayerPriceIsBelowAverageListingPrice(averageOfferPriceRub, playerListedPriceRub); :
this.getSellMultiplierWhenPlayerPriceIsBelowAverageListingPrice(averageOfferPriceRub, playerListedPriceRub);
return Math.round(baseSellChancePercent * (averageOfferPriceRub / playerListedPriceRub * multiplier)); return Math.round(baseSellChancePercent * (averageOfferPriceRub / playerListedPriceRub * multiplier));
} }
@ -49,11 +54,14 @@ export class RagfairSellHelper
* @param averageOfferPriceRub Price of average offer in roubles * @param averageOfferPriceRub Price of average offer in roubles
* @returns percent value * @returns percent value
*/ */
protected getSellMultiplierWhenPlayerPriceIsBelowAverageListingPrice(averageOfferPriceRub: number, playerListedPriceRub: number): number protected getSellMultiplierWhenPlayerPriceIsBelowAverageListingPrice(
averageOfferPriceRub: number,
playerListedPriceRub: number,
): number
{ {
return (playerListedPriceRub < averageOfferPriceRub) return (playerListedPriceRub < averageOfferPriceRub) ?
? this.ragfairConfig.sell.chance.underpriced this.ragfairConfig.sell.chance.underpriced :
: 1; 1;
} }
/** /**
@ -75,14 +83,16 @@ export class RagfairSellHelper
let sellTime = startTime; let sellTime = startTime;
let remainingCount = itemSellCount; let remainingCount = itemSellCount;
const result: SellResult[] = []; const result: SellResult[] = [];
// Value can sometimes be NaN for whatever reason, default to base chance if that happens // Value can sometimes be NaN for whatever reason, default to base chance if that happens
if (Number.isNaN(sellChancePercent)) if (Number.isNaN(sellChancePercent))
{ {
this.logger.warning(`Sell chance was not a number: ${sellChancePercent}, defaulting to ${this.ragfairConfig.sell.chance.base} %`); this.logger.warning(
`Sell chance was not a number: ${sellChancePercent}, defaulting to ${this.ragfairConfig.sell.chance.base} %`,
);
sellChancePercent = this.ragfairConfig.sell.chance.base; sellChancePercent = this.ragfairConfig.sell.chance.base;
} }
this.logger.debug(`Rolling to sell: ${itemSellCount} items (chance: ${sellChancePercent}%)`); this.logger.debug(`Rolling to sell: ${itemSellCount} items (chance: ${sellChancePercent}%)`);
// No point rolling for a sale on a 0% chance item, exit early // No point rolling for a sale on a 0% chance item, exit early
@ -90,18 +100,21 @@ export class RagfairSellHelper
{ {
return result; return result;
} }
while (remainingCount > 0 && sellTime < endTime) while (remainingCount > 0 && sellTime < endTime)
{ {
const boughtAmount = this.randomUtil.getInt(1, remainingCount); const boughtAmount = this.randomUtil.getInt(1, remainingCount);
if (this.randomUtil.getChance100(sellChancePercent)) if (this.randomUtil.getChance100(sellChancePercent))
{ {
// Passed roll check, item will be sold // Passed roll check, item will be sold
sellTime += Math.max(Math.round(chance / 100 * this.ragfairConfig.sell.time.max * 60), this.ragfairConfig.sell.time.min * 60); sellTime += Math.max(
Math.round(chance / 100 * this.ragfairConfig.sell.time.max * 60),
this.ragfairConfig.sell.time.min * 60,
);
result.push({ result.push({
sellTime: sellTime, sellTime: sellTime,
amount: boughtAmount amount: boughtAmount,
}); });
this.logger.debug(`Offer will sell at: ${new Date(sellTime * 1000).toLocaleTimeString("en-US")}`); this.logger.debug(`Offer will sell at: ${new Date(sellTime * 1000).toLocaleTimeString("en-US")}`);
@ -116,4 +129,4 @@ export class RagfairSellHelper
return result; return result;
} }
} }

View File

@ -48,7 +48,7 @@ export class RagfairServerHelper
@inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("JsonUtil") protected jsonUtil: JsonUtil,
@inject("MailSendService") protected mailSendService: MailSendService, @inject("MailSendService") protected mailSendService: MailSendService,
@inject("ItemFilterService") protected itemFilterService: ItemFilterService, @inject("ItemFilterService") protected itemFilterService: ItemFilterService,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR); this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
@ -57,7 +57,7 @@ export class RagfairServerHelper
/** /**
* Is item valid / on blacklist / quest item * Is item valid / on blacklist / quest item
* @param itemDetails * @param itemDetails
* @returns boolean * @returns boolean
*/ */
public isItemValidRagfairItem(itemDetails: [boolean, ITemplateItem]): boolean public isItemValidRagfairItem(itemDetails: [boolean, ITemplateItem]): boolean
@ -95,7 +95,10 @@ export class RagfairServerHelper
} }
// Don't include damaged ammo packs // Don't include damaged ammo packs
if (this.ragfairConfig.dynamic.blacklist.damagedAmmoPacks && itemDetails[1]._parent === BaseClasses.AMMO_BOX && itemDetails[1]._name.includes("_damaged")) if (
this.ragfairConfig.dynamic.blacklist.damagedAmmoPacks && itemDetails[1]._parent === BaseClasses.AMMO_BOX &&
itemDetails[1]._name.includes("_damaged")
)
{ {
return false; return false;
} }
@ -120,7 +123,7 @@ export class RagfairServerHelper
/** /**
* is supplied id a trader * is supplied id a trader
* @param traderId * @param traderId
* @returns True if id was a trader * @returns True if id was a trader
*/ */
public isTrader(traderId: string): boolean public isTrader(traderId: string): boolean
@ -155,7 +158,7 @@ export class RagfairServerHelper
MessageType.MESSAGE_WITH_ITEMS, MessageType.MESSAGE_WITH_ITEMS,
RagfairServerHelper.goodsReturnedTemplate, RagfairServerHelper.goodsReturnedTemplate,
returnedItems, returnedItems,
this.timeUtil.getHoursAsSeconds(this.questConfig.redeemTime) this.timeUtil.getHoursAsSeconds(this.questConfig.redeemTime),
); );
} }
@ -171,7 +174,10 @@ export class RagfairServerHelper
} }
// Item Types to return one of // Item Types to return one of
if (isWeaponPreset || this.itemHelper.isOfBaseclasses(itemDetails[1]._id, this.ragfairConfig.dynamic.showAsSingleStack)) if (
isWeaponPreset ||
this.itemHelper.isOfBaseclasses(itemDetails[1]._id, this.ragfairConfig.dynamic.showAsSingleStack)
)
{ {
return 1; return 1;
} }
@ -185,7 +191,9 @@ export class RagfairServerHelper
return Math.round(this.randomUtil.getInt(config.nonStackableCount.min, config.nonStackableCount.max)); return Math.round(this.randomUtil.getInt(config.nonStackableCount.min, config.nonStackableCount.max));
} }
const stackPercent = Math.round(this.randomUtil.getInt(config.stackablePercent.min, config.stackablePercent.max)); const stackPercent = Math.round(
this.randomUtil.getInt(config.stackablePercent.min, config.stackablePercent.max),
);
return Math.round((maxStackCount / 100) * stackPercent); return Math.round((maxStackCount / 100) * stackPercent);
} }
@ -263,7 +271,9 @@ export class RagfairServerHelper
{ {
if (this.databaseServer.getTables().globals.ItemPresets[itemId]._items[0]._tpl === item._tpl) if (this.databaseServer.getTables().globals.ItemPresets[itemId]._items[0]._tpl === item._tpl)
{ {
const presetItems = this.jsonUtil.clone(this.databaseServer.getTables().globals.ItemPresets[itemId]._items); const presetItems = this.jsonUtil.clone(
this.databaseServer.getTables().globals.ItemPresets[itemId]._items,
);
presets.push(this.reparentPresets(item, presetItems)); presets.push(this.reparentPresets(item, presetItems));
} }
} }
@ -274,7 +284,7 @@ export class RagfairServerHelper
/** /**
* Generate new unique ids for the children while preserving hierarchy * Generate new unique ids for the children while preserving hierarchy
* @param item base item * @param item base item
* @param preset * @param preset
* @returns Item array with new IDs * @returns Item array with new IDs
*/ */
public reparentPresets(item: Item, preset: Item[]): Item[] public reparentPresets(item: Item, preset: Item[]): Item[]
@ -296,11 +306,11 @@ export class RagfairServerHelper
idMappings[mod.parentId] = this.hashUtil.generate(); idMappings[mod.parentId] = this.hashUtil.generate();
} }
mod._id = idMappings[mod._id]; mod._id = idMappings[mod._id];
if (mod.parentId !== undefined) if (mod.parentId !== undefined)
{ {
mod.parentId = idMappings[mod.parentId]; mod.parentId = idMappings[mod.parentId];
} }
} }
@ -309,4 +319,4 @@ export class RagfairServerHelper
return preset; return preset;
} }
} }

View File

@ -10,15 +10,15 @@ export class RagfairSortHelper
{ {
constructor( constructor(
@inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("LocaleService") protected localeService: LocaleService @inject("LocaleService") protected localeService: LocaleService,
) )
{ } {}
/** /**
* Sort a list of ragfair offers by something (id/rating/offer name/price/expiry time) * Sort a list of ragfair offers by something (id/rating/offer name/price/expiry time)
* @param offers Offers to sort * @param offers Offers to sort
* @param type How to sort it * @param type How to sort it
* @param direction Ascending/descending * @param direction Ascending/descending
* @returns Sorted offers * @returns Sorted offers
*/ */
public sortOffers(offers: IRagfairOffer[], type: RagfairSort, direction = 0): IRagfairOffer[] public sortOffers(offers: IRagfairOffer[], type: RagfairSort, direction = 0): IRagfairOffer[]
@ -75,16 +75,18 @@ export class RagfairSortHelper
const nameA = locale[`${tplA} Name`] || tplA; const nameA = locale[`${tplA} Name`] || tplA;
const nameB = locale[`${tplB} Name`] || tplB; const nameB = locale[`${tplB} Name`] || tplB;
return (nameA < nameB) return (nameA < nameB) ?
? -1 -1 :
: (nameA > nameB) ? 1 : 0; (nameA > nameB) ?
1 :
0;
} }
/** /**
* Order two offers by rouble price value * Order two offers by rouble price value
* @param a Offer a * @param a Offer a
* @param b Offer b * @param b Offer b
* @returns * @returns
*/ */
protected sortOffersByPrice(a: IRagfairOffer, b: IRagfairOffer): number protected sortOffersByPrice(a: IRagfairOffer, b: IRagfairOffer): number
{ {
@ -95,4 +97,4 @@ export class RagfairSortHelper
{ {
return a.endTime - b.endTime; return a.endTime - b.endTime;
} }
} }

View File

@ -21,7 +21,7 @@ export class RepairHelper
@inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("JsonUtil") protected jsonUtil: JsonUtil,
@inject("RandomUtil") protected randomUtil: RandomUtil, @inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.repairConfig = this.configServer.getConfig(ConfigTypes.REPAIR); this.repairConfig = this.configServer.getConfig(ConfigTypes.REPAIR);
@ -44,7 +44,7 @@ export class RepairHelper
amountToRepair: number, amountToRepair: number,
useRepairKit: boolean, useRepairKit: boolean,
traderQualityMultipler: number, traderQualityMultipler: number,
applyMaxDurabilityDegradation = true applyMaxDurabilityDegradation = true,
): void ): void
{ {
this.logger.debug(`Adding ${amountToRepair} to ${itemToRepairDetails._name} using kit: ${useRepairKit}`); this.logger.debug(`Adding ${amountToRepair} to ${itemToRepairDetails._name} using kit: ${useRepairKit}`);
@ -70,20 +70,30 @@ export class RepairHelper
// Construct object to return // Construct object to return
itemToRepair.upd.Repairable = { itemToRepair.upd.Repairable = {
Durability: newCurrentDurability, Durability: newCurrentDurability,
MaxDurability: newCurrentMaxDurability MaxDurability: newCurrentMaxDurability,
}; };
// when modders set the repair coefficient to 0 it means that they dont want to lose durability on items // when modders set the repair coefficient to 0 it means that they dont want to lose durability on items
// the code below generates a random degradation on the weapon durability // the code below generates a random degradation on the weapon durability
if (applyMaxDurabilityDegradation) if (applyMaxDurabilityDegradation)
{ {
const randomisedWearAmount = (isArmor) const randomisedWearAmount = isArmor ?
? this.getRandomisedArmorRepairDegradationValue(itemToRepairDetails._props.ArmorMaterial, useRepairKit, itemCurrentMaxDurability, traderQualityMultipler) this.getRandomisedArmorRepairDegradationValue(
: this.getRandomisedWeaponRepairDegradationValue(itemToRepairDetails._props, useRepairKit, itemCurrentMaxDurability, traderQualityMultipler); itemToRepairDetails._props.ArmorMaterial,
useRepairKit,
itemCurrentMaxDurability,
traderQualityMultipler,
) :
this.getRandomisedWeaponRepairDegradationValue(
itemToRepairDetails._props,
useRepairKit,
itemCurrentMaxDurability,
traderQualityMultipler,
);
// Apply wear to durability // Apply wear to durability
itemToRepair.upd.Repairable.MaxDurability -= randomisedWearAmount; itemToRepair.upd.Repairable.MaxDurability -= randomisedWearAmount;
// After adjusting max durability with degradation, ensure current dura isnt above max // After adjusting max durability with degradation, ensure current dura isnt above max
if (itemToRepair.upd.Repairable.Durability > itemToRepair.upd.Repairable.MaxDurability) if (itemToRepair.upd.Repairable.Durability > itemToRepair.upd.Repairable.MaxDurability)
{ {
@ -98,32 +108,42 @@ export class RepairHelper
} }
} }
protected getRandomisedArmorRepairDegradationValue(armorMaterial: string, isRepairKit: boolean, armorMax: number, traderQualityMultipler: number): number protected getRandomisedArmorRepairDegradationValue(
armorMaterial: string,
isRepairKit: boolean,
armorMax: number,
traderQualityMultipler: number,
): number
{ {
const armorMaterialSettings = this.databaseServer.getTables().globals.config.ArmorMaterials[armorMaterial]; const armorMaterialSettings = this.databaseServer.getTables().globals.config.ArmorMaterials[armorMaterial];
const minMultiplier = isRepairKit const minMultiplier = isRepairKit ?
? armorMaterialSettings.MinRepairKitDegradation armorMaterialSettings.MinRepairKitDegradation :
: armorMaterialSettings.MinRepairDegradation; armorMaterialSettings.MinRepairDegradation;
const maxMultiplier = isRepairKit const maxMultiplier = isRepairKit ?
? armorMaterialSettings.MaxRepairKitDegradation armorMaterialSettings.MaxRepairKitDegradation :
: armorMaterialSettings.MaxRepairDegradation; armorMaterialSettings.MaxRepairDegradation;
const duraLossPercent = this.randomUtil.getFloat(minMultiplier, maxMultiplier); const duraLossPercent = this.randomUtil.getFloat(minMultiplier, maxMultiplier);
const duraLossMultipliedByTraderMultiplier = (duraLossPercent * armorMax) * traderQualityMultipler; const duraLossMultipliedByTraderMultiplier = (duraLossPercent * armorMax) * traderQualityMultipler;
return Number(duraLossMultipliedByTraderMultiplier.toFixed(2)); return Number(duraLossMultipliedByTraderMultiplier.toFixed(2));
} }
protected getRandomisedWeaponRepairDegradationValue(itemProps: Props, isRepairKit: boolean, weaponMax: number, traderQualityMultipler: number): number protected getRandomisedWeaponRepairDegradationValue(
itemProps: Props,
isRepairKit: boolean,
weaponMax: number,
traderQualityMultipler: number,
): number
{ {
const minRepairDeg = (isRepairKit) const minRepairDeg = isRepairKit ?
? itemProps.MinRepairKitDegradation itemProps.MinRepairKitDegradation :
: itemProps.MinRepairDegradation; itemProps.MinRepairDegradation;
let maxRepairDeg = (isRepairKit) let maxRepairDeg = isRepairKit ?
? itemProps.MaxRepairKitDegradation itemProps.MaxRepairKitDegradation :
: itemProps.MaxRepairDegradation; itemProps.MaxRepairDegradation;
// WORKAROUND: Some items are always 0 when repairkit is true // WORKAROUND: Some items are always 0 when repairkit is true
if (maxRepairDeg === 0) if (maxRepairDeg === 0)
@ -132,7 +152,7 @@ export class RepairHelper
} }
const duraLossPercent = this.randomUtil.getFloat(minRepairDeg, maxRepairDeg); const duraLossPercent = this.randomUtil.getFloat(minRepairDeg, maxRepairDeg);
const duraLossMultipliedByTraderMultiplier = (duraLossPercent * weaponMax) * traderQualityMultipler; const duraLossMultipliedByTraderMultiplier = (duraLossPercent * weaponMax) * traderQualityMultipler;
return Number(duraLossMultipliedByTraderMultiplier.toFixed(2)); return Number(duraLossMultipliedByTraderMultiplier.toFixed(2));
} }
@ -151,5 +171,4 @@ export class RepairHelper
return parentNode._id === BaseClasses.WEAPON; return parentNode._id === BaseClasses.WEAPON;
} }
}
}

View File

@ -15,7 +15,7 @@ export class RepeatableQuestHelper
constructor( constructor(
@inject("MathUtil") protected mathUtil: MathUtil, @inject("MathUtil") protected mathUtil: MathUtil,
@inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("JsonUtil") protected jsonUtil: JsonUtil,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST); this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST);
@ -27,9 +27,14 @@ export class RepeatableQuestHelper
* @param repeatableConfig Main repeatable config * @param repeatableConfig Main repeatable config
* @returns IEliminationConfig * @returns IEliminationConfig
*/ */
public getEliminationConfigByPmcLevel(pmcLevel: number, repeatableConfig: IRepeatableQuestConfig): IEliminationConfig public getEliminationConfigByPmcLevel(
pmcLevel: number,
repeatableConfig: IRepeatableQuestConfig,
): IEliminationConfig
{ {
return repeatableConfig.questConfig.Elimination.find(x => pmcLevel >= x.levelRange.min && pmcLevel <= x.levelRange.max); return repeatableConfig.questConfig.Elimination.find((x) =>
pmcLevel >= x.levelRange.min && pmcLevel <= x.levelRange.max
);
} }
public probabilityObjectArray<K, V>(configArrayInput: ProbabilityObject<K, V>[]): ProbabilityObjectArray<K, V> public probabilityObjectArray<K, V>(configArrayInput: ProbabilityObject<K, V>[]): ProbabilityObjectArray<K, V>
@ -38,8 +43,10 @@ export class RepeatableQuestHelper
const probabilityArray = new ProbabilityObjectArray<K, V>(this.mathUtil, this.jsonUtil); const probabilityArray = new ProbabilityObjectArray<K, V>(this.mathUtil, this.jsonUtil);
for (const configObject of configArray) for (const configObject of configArray)
{ {
probabilityArray.push(new ProbabilityObject(configObject.key, configObject.relativeProbability, configObject.data)); probabilityArray.push(
new ProbabilityObject(configObject.key, configObject.relativeProbability, configObject.data),
);
} }
return probabilityArray; return probabilityArray;
} }
} }

View File

@ -5,24 +5,23 @@ import { Item } from "@spt-aki/models/eft/common/tables/IItem";
export interface OwnerInventoryItems export interface OwnerInventoryItems
{ {
from: Item[] from: Item[];
to: Item[] to: Item[];
sameInventory: boolean, sameInventory: boolean;
isMail: boolean isMail: boolean;
} }
@injectable() @injectable()
export class SecureContainerHelper export class SecureContainerHelper
{ {
constructor( constructor(
@inject("ItemHelper") protected itemHelper: ItemHelper @inject("ItemHelper") protected itemHelper: ItemHelper,
) )
{ } {}
public getSecureContainerItems(items: Item[]): string[] public getSecureContainerItems(items: Item[]): string[]
{ {
const secureContainer = items.find(x => x.slotId === "SecuredContainer"); const secureContainer = items.find((x) => x.slotId === "SecuredContainer");
// No container found, drop out // No container found, drop out
if (!secureContainer) if (!secureContainer)
@ -33,6 +32,6 @@ export class SecureContainerHelper
const itemsInSecureContainer = this.itemHelper.findAndReturnChildrenByItems(items, secureContainer._id); const itemsInSecureContainer = this.itemHelper.findAndReturnChildrenByItems(items, secureContainer._id);
// Return all items returned and exclude the secure container item itself // Return all items returned and exclude the secure container item itself
return itemsInSecureContainer.filter(x => x !== secureContainer._id); return itemsInSecureContainer.filter((x) => x !== secureContainer._id);
} }
} }

View File

@ -34,7 +34,7 @@ export class TradeHelper
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil, @inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
@inject("InventoryHelper") protected inventoryHelper: InventoryHelper, @inject("InventoryHelper") protected inventoryHelper: InventoryHelper,
@inject("RagfairServer") protected ragfairServer: RagfairServer, @inject("RagfairServer") protected ragfairServer: RagfairServer,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER); this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
@ -47,9 +47,15 @@ export class TradeHelper
* @param sessionID Session id * @param sessionID Session id
* @param foundInRaid Should item be found in raid * @param foundInRaid Should item be found in raid
* @param upd optional item details used when buying from flea * @param upd optional item details used when buying from flea
* @returns * @returns
*/ */
public buyItem(pmcData: IPmcData, buyRequestData: IProcessBuyTradeRequestData, sessionID: string, foundInRaid: boolean, upd: Upd): IItemEventRouterResponse public buyItem(
pmcData: IPmcData,
buyRequestData: IProcessBuyTradeRequestData,
sessionID: string,
foundInRaid: boolean,
upd: Upd,
): IItemEventRouterResponse
{ {
let output = this.eventOutputHolder.getOutput(sessionID); let output = this.eventOutputHolder.getOutput(sessionID);
@ -58,10 +64,10 @@ export class TradeHelper
{ {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
item_id: buyRequestData.item_id, item_id: buyRequestData.item_id,
count: buyRequestData.count count: buyRequestData.count,
} },
], ],
tid: buyRequestData.tid tid: buyRequestData.tid,
}; };
const callback = () => const callback = () =>
@ -71,13 +77,13 @@ export class TradeHelper
if (isRagfair) if (isRagfair)
{ {
const allOffers = this.ragfairServer.getOffers(); const allOffers = this.ragfairServer.getOffers();
const offersWithItem = allOffers.find(x => x.items[0]._id === buyRequestData.item_id); const offersWithItem = allOffers.find((x) => x.items[0]._id === buyRequestData.item_id);
itemPurchased = offersWithItem.items[0]; itemPurchased = offersWithItem.items[0];
} }
else else
{ {
const traderAssorts = this.traderHelper.getTraderAssortsByTraderId(buyRequestData.tid).items; const traderAssorts = this.traderHelper.getTraderAssortsByTraderId(buyRequestData.tid).items;
itemPurchased = traderAssorts.find(x => x._id === buyRequestData.item_id); itemPurchased = traderAssorts.find((x) => x._id === buyRequestData.item_id);
} }
// Ensure purchase does not exceed trader item limit // Ensure purchase does not exceed trader item limit
@ -130,7 +136,12 @@ export class TradeHelper
* @param sessionID Session id * @param sessionID Session id
* @returns IItemEventRouterResponse * @returns IItemEventRouterResponse
*/ */
public sellItem(profileWithItemsToSell: IPmcData, profileToReceiveMoney: IPmcData, sellRequest: IProcessSellTradeRequestData, sessionID: string): IItemEventRouterResponse public sellItem(
profileWithItemsToSell: IPmcData,
profileToReceiveMoney: IPmcData,
sellRequest: IProcessSellTradeRequestData,
sessionID: string,
): IItemEventRouterResponse
{ {
let output = this.eventOutputHolder.getOutput(sessionID); let output = this.eventOutputHolder.getOutput(sessionID);
@ -140,7 +151,7 @@ export class TradeHelper
const itemIdToFind = itemToBeRemoved.id.replace(/\s+/g, ""); // Strip out whitespace const itemIdToFind = itemToBeRemoved.id.replace(/\s+/g, ""); // Strip out whitespace
// Find item in player inventory, or show error to player if not found // Find item in player inventory, or show error to player if not found
const matchingItemInInventory = profileWithItemsToSell.Inventory.items.find(x => x._id === itemIdToFind); const matchingItemInInventory = profileWithItemsToSell.Inventory.items.find((x) => x._id === itemIdToFind);
if (!matchingItemInInventory) if (!matchingItemInInventory)
{ {
const errorMessage = `Unable to sell item ${itemToBeRemoved.id}, cannot be found in player inventory`; const errorMessage = `Unable to sell item ${itemToBeRemoved.id}, cannot be found in player inventory`;
@ -179,7 +190,9 @@ export class TradeHelper
{ {
if ((assortBeingPurchased.upd.BuyRestrictionCurrent + count) > assortBeingPurchased.upd?.BuyRestrictionMax) if ((assortBeingPurchased.upd.BuyRestrictionCurrent + count) > assortBeingPurchased.upd?.BuyRestrictionMax)
{ {
throw new Error(`Unable to purchase ${count} items, this would exceed your purchase limit of ${assortBeingPurchased.upd.BuyRestrictionMax} from the trader this refresh`); throw new Error(
`Unable to purchase ${count} items, this would exceed your purchase limit of ${assortBeingPurchased.upd.BuyRestrictionMax} from the trader this refresh`,
);
} }
} }
} }

View File

@ -29,7 +29,7 @@ export class TraderAssortHelper
protected mergedQuestAssorts: Record<string, Record<string, string>> = { protected mergedQuestAssorts: Record<string, Record<string, string>> = {
started: {}, started: {},
success: {}, success: {},
fail: {} fail: {},
}; };
protected createdMergedQuestAssorts = false; protected createdMergedQuestAssorts = false;
@ -46,10 +46,11 @@ export class TraderAssortHelper
@inject("RagfairOfferGenerator") protected ragfairOfferGenerator: RagfairOfferGenerator, @inject("RagfairOfferGenerator") protected ragfairOfferGenerator: RagfairOfferGenerator,
@inject("TraderAssortService") protected traderAssortService: TraderAssortService, @inject("TraderAssortService") protected traderAssortService: TraderAssortService,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("TraderPurchasePersisterService") protected traderPurchasePersisterService: TraderPurchasePersisterService, @inject("TraderPurchasePersisterService") protected traderPurchasePersisterService:
TraderPurchasePersisterService,
@inject("TraderHelper") protected traderHelper: TraderHelper, @inject("TraderHelper") protected traderHelper: TraderHelper,
@inject("FenceService") protected fenceService: FenceService, @inject("FenceService") protected fenceService: FenceService,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER); this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
@ -81,7 +82,7 @@ export class TraderAssortHelper
// Strip assorts player should not see yet // Strip assorts player should not see yet
if (!flea) if (!flea)
{ {
trader.assort = this.assortHelper.stripLockedLoyaltyAssort(pmcProfile, traderId, trader.assort); trader.assort = this.assortHelper.stripLockedLoyaltyAssort(pmcProfile, traderId, trader.assort);
} }
@ -89,21 +90,28 @@ export class TraderAssortHelper
trader.assort.nextResupply = trader.base.nextResupply; trader.assort.nextResupply = trader.base.nextResupply;
// Adjust displayed assort counts based on values stored in profile // Adjust displayed assort counts based on values stored in profile
const assortPurchasesfromTrader = this.traderPurchasePersisterService.getProfileTraderPurchases(sessionId, traderId); const assortPurchasesfromTrader = this.traderPurchasePersisterService.getProfileTraderPurchases(
sessionId,
traderId,
);
for (const assortId in assortPurchasesfromTrader) for (const assortId in assortPurchasesfromTrader)
{ {
// Find assort we want to update current buy count of // Find assort we want to update current buy count of
const assortToAdjust = trader.assort.items.find(x => x._id === assortId); const assortToAdjust = trader.assort.items.find((x) => x._id === assortId);
if (!assortToAdjust) if (!assortToAdjust)
{ {
this.logger.debug(`Cannot find trader: ${trader.base.nickname} assort: ${assortId} to adjust BuyRestrictionCurrent value, skipping`); this.logger.debug(
`Cannot find trader: ${trader.base.nickname} assort: ${assortId} to adjust BuyRestrictionCurrent value, skipping`,
);
continue; continue;
} }
if (!assortToAdjust.upd) if (!assortToAdjust.upd)
{ {
this.logger.debug(`Unable to adjust assort ${assortToAdjust._id} item: ${assortToAdjust._tpl} BuyRestrictionCurrent value, assort has an undefined upd object`); this.logger.debug(
`Unable to adjust assort ${assortToAdjust._id} item: ${assortToAdjust._tpl} BuyRestrictionCurrent value, assort has an undefined upd object`,
);
continue; continue;
} }
@ -117,7 +125,13 @@ export class TraderAssortHelper
this.hydrateMergedQuestAssorts(); this.hydrateMergedQuestAssorts();
this.createdMergedQuestAssorts = true; this.createdMergedQuestAssorts = true;
} }
trader.assort = this.assortHelper.stripLockedQuestAssort(pmcProfile, traderId, trader.assort, this.mergedQuestAssorts, flea); trader.assort = this.assortHelper.stripLockedQuestAssort(
pmcProfile,
traderId,
trader.assort,
this.mergedQuestAssorts,
flea,
);
// Multiply price if multiplier is other than 1 // Multiply price if multiplier is other than 1
if (this.traderConfig.traderPriceMultipler !== 1) if (this.traderConfig.traderPriceMultipler !== 1)
@ -234,7 +248,7 @@ export class TraderAssortHelper
barter_scheme: {}, barter_scheme: {},
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
loyal_level_items: {}, loyal_level_items: {},
nextResupply: null nextResupply: null,
}; };
} }
} }

View File

@ -42,14 +42,14 @@ export class TraderHelper
@inject("FenceService") protected fenceService: FenceService, @inject("FenceService") protected fenceService: FenceService,
@inject("TimeUtil") protected timeUtil: TimeUtil, @inject("TimeUtil") protected timeUtil: TimeUtil,
@inject("RandomUtil") protected randomUtil: RandomUtil, @inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer,
) )
{ {
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER); this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
} }
/** /**
* Get a trader base object, update profile to reflect players current standing in profile * Get a trader base object, update profile to reflect players current standing in profile
* when trader not found in profile * when trader not found in profile
* @param traderID Traders Id to get * @param traderID Traders Id to get
* @param sessionID Players id * @param sessionID Players id
@ -62,7 +62,7 @@ export class TraderHelper
{ {
this.logger.error(`No profile with sessionId: ${sessionID}`); this.logger.error(`No profile with sessionId: ${sessionID}`);
} }
// Profile has traderInfo dict (profile beyond creation stage) but no requested trader in profile // Profile has traderInfo dict (profile beyond creation stage) but no requested trader in profile
if (pmcData.TradersInfo && !(traderID in pmcData.TradersInfo)) if (pmcData.TradersInfo && !(traderID in pmcData.TradersInfo))
{ {
@ -70,7 +70,7 @@ export class TraderHelper
this.resetTrader(sessionID, traderID); this.resetTrader(sessionID, traderID);
this.lvlUp(traderID, pmcData); this.lvlUp(traderID, pmcData);
} }
const trader = this.databaseServer.getTables().traders?.[traderID]?.base; const trader = this.databaseServer.getTables().traders?.[traderID]?.base;
if (!trader) if (!trader)
{ {
@ -87,9 +87,9 @@ export class TraderHelper
*/ */
public getTraderAssortsByTraderId(traderId: string): ITraderAssort public getTraderAssortsByTraderId(traderId: string): ITraderAssort
{ {
return traderId === Traders.FENCE return traderId === Traders.FENCE ?
? this.fenceService.getRawFenceAssorts() this.fenceService.getRawFenceAssorts() :
: this.databaseServer.getTables().traders[traderId].assort; this.databaseServer.getTables().traders[traderId].assort;
} }
/** /**
@ -109,7 +109,7 @@ export class TraderHelper
} }
// Find specific assort in traders data // Find specific assort in traders data
const purchasedAssort = traderAssorts.items.find(x => x._id === assortId); const purchasedAssort = traderAssorts.items.find((x) => x._id === assortId);
if (!purchasedAssort) if (!purchasedAssort)
{ {
this.logger.debug(`No assort ${assortId} on trader: ${traderId} found`); this.logger.debug(`No assort ${assortId} on trader: ${traderId} found`);
@ -130,15 +130,17 @@ export class TraderHelper
{ {
const account = this.saveServer.getProfile(sessionID); const account = this.saveServer.getProfile(sessionID);
const pmcData = this.profileHelper.getPmcProfile(sessionID); const pmcData = this.profileHelper.getPmcProfile(sessionID);
const rawProfileTemplate: ProfileTraderTemplate = this.databaseServer.getTables().templates.profiles[account.info.edition][pmcData.Info.Side.toLowerCase()].trader; const rawProfileTemplate: ProfileTraderTemplate =
this.databaseServer.getTables().templates.profiles[account.info.edition][pmcData.Info.Side.toLowerCase()]
.trader;
pmcData.TradersInfo[traderID] = { pmcData.TradersInfo[traderID] = {
disabled: false, disabled: false,
loyaltyLevel: rawProfileTemplate.initialLoyaltyLevel, loyaltyLevel: rawProfileTemplate.initialLoyaltyLevel,
salesSum: rawProfileTemplate.initialSalesSum, salesSum: rawProfileTemplate.initialSalesSum,
standing: this.getStartingStanding(traderID, rawProfileTemplate), standing: this.getStartingStanding(traderID, rawProfileTemplate),
nextResupply: this.databaseServer.getTables().traders[traderID].base.nextResupply, nextResupply: this.databaseServer.getTables().traders[traderID].base.nextResupply,
unlocked: this.databaseServer.getTables().traders[traderID].base.unlockedByDefault unlocked: this.databaseServer.getTables().traders[traderID].base.unlockedByDefault,
}; };
if (traderID === Traders.JAEGER) if (traderID === Traders.JAEGER)
@ -197,9 +199,9 @@ export class TraderHelper
{ {
const newStanding = currentStanding + standingToAdd; const newStanding = currentStanding + standingToAdd;
return newStanding < 0 return newStanding < 0 ?
? 0 0 :
: newStanding; newStanding;
} }
/** /**
@ -224,10 +226,12 @@ export class TraderHelper
{ {
const loyalty = loyaltyLevels[level]; const loyalty = loyaltyLevels[level];
if ((loyalty.minLevel <= pmcData.Info.Level if (
&& loyalty.minSalesSum <= pmcData.TradersInfo[traderID].salesSum (loyalty.minLevel <= pmcData.Info.Level &&
&& loyalty.minStanding <= pmcData.TradersInfo[traderID].standing) loyalty.minSalesSum <= pmcData.TradersInfo[traderID].salesSum &&
&& targetLevel < 4) loyalty.minStanding <= pmcData.TradersInfo[traderID].standing) &&
targetLevel < 4
)
{ {
// level reached // level reached
targetLevel++; targetLevel++;
@ -257,15 +261,20 @@ export class TraderHelper
*/ */
public getTraderUpdateSeconds(traderId: string): number public getTraderUpdateSeconds(traderId: string): number
{ {
const traderDetails = this.traderConfig.updateTime.find(x => x.traderId === traderId); const traderDetails = this.traderConfig.updateTime.find((x) => x.traderId === traderId);
if (!traderDetails) if (!traderDetails)
{ {
this.logger.warning(this.localisationService.getText("trader-missing_trader_details_using_default_refresh_time", {traderId: traderId, updateTime: this.traderConfig.updateTimeDefault})); this.logger.warning(
this.traderConfig.updateTime.push( // create temporary entry to prevent logger spam this.localisationService.getText("trader-missing_trader_details_using_default_refresh_time", {
traderId: traderId,
updateTime: this.traderConfig.updateTimeDefault,
}),
);
this.traderConfig.updateTime.push( // create temporary entry to prevent logger spam
{ {
traderId: traderId, traderId: traderId,
seconds: this.traderConfig.updateTimeDefault seconds: this.traderConfig.updateTimeDefault,
} },
); );
} }
else else
@ -298,7 +307,10 @@ export class TraderHelper
* @param newPurchaseDetails New item assort id + count * @param newPurchaseDetails New item assort id + count
*/ */
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
public addTraderPurchasesToPlayerProfile(sessionID: string, newPurchaseDetails: { items: { item_id: string; count: number; }[]; tid: string; }): void public addTraderPurchasesToPlayerProfile(
sessionID: string,
newPurchaseDetails: {items: {item_id: string; count: number;}[]; tid: string;},
): void
{ {
const profile = this.profileHelper.getFullProfile(sessionID); const profile = this.profileHelper.getFullProfile(sessionID);
const traderId = newPurchaseDetails.tid; const traderId = newPurchaseDetails.tid;
@ -320,10 +332,9 @@ export class TraderHelper
const currentTime = this.timeUtil.getTimestamp(); const currentTime = this.timeUtil.getTimestamp();
if (!profile.traderPurchases[traderId][purchasedItem.item_id]) if (!profile.traderPurchases[traderId][purchasedItem.item_id])
{ {
profile.traderPurchases[traderId][purchasedItem.item_id] = profile.traderPurchases[traderId][purchasedItem.item_id] = {
{
count: purchasedItem.count, count: purchasedItem.count,
purchaseTimestamp: currentTime purchaseTimestamp: currentTime,
}; };
continue; continue;
@ -369,15 +380,15 @@ export class TraderHelper
} }
// Get all item assorts that have parentid of hideout (base item and not a mod of other item) // Get all item assorts that have parentid of hideout (base item and not a mod of other item)
for (const item of traderAssorts.items.filter(x => x.parentId === "hideout")) for (const item of traderAssorts.items.filter((x) => x.parentId === "hideout"))
{ {
// Get barter scheme (contains cost of item) // Get barter scheme (contains cost of item)
const barterScheme = traderAssorts.barter_scheme[item._id][0][0]; const barterScheme = traderAssorts.barter_scheme[item._id][0][0];
// Convert into roubles // Convert into roubles
const roubleAmount = barterScheme._tpl === Money.ROUBLES const roubleAmount = barterScheme._tpl === Money.ROUBLES ?
? barterScheme.count barterScheme.count :
: this.handbookHelper.inRUB(barterScheme.count, barterScheme._tpl); this.handbookHelper.inRUB(barterScheme.count, barterScheme._tpl);
// Existing price smaller in dict than current iteration, overwrite // Existing price smaller in dict than current iteration, overwrite
if (this.highestTraderPriceItems[item._tpl] ?? 0 < roubleAmount) if (this.highestTraderPriceItems[item._tpl] ?? 0 < roubleAmount)
@ -409,7 +420,6 @@ export class TraderHelper
return this.highestTraderBuyPriceItems[tpl]; return this.highestTraderBuyPriceItems[tpl];
} }
// Find highest trader price for item // Find highest trader price for item
for (const traderName in Traders) for (const traderName in Traders)
{ {
@ -423,7 +433,9 @@ export class TraderHelper
const traderBuyBackPricePercent = relevantLoyaltyData.buy_price_coef; const traderBuyBackPricePercent = relevantLoyaltyData.buy_price_coef;
const itemHandbookPrice = this.handbookHelper.getTemplatePrice(tpl); const itemHandbookPrice = this.handbookHelper.getTemplatePrice(tpl);
const priceTraderBuysItemAt = Math.round(this.randomUtil.getPercentOfValue(traderBuyBackPricePercent, itemHandbookPrice)); const priceTraderBuysItemAt = Math.round(
this.randomUtil.getPercentOfValue(traderBuyBackPricePercent, itemHandbookPrice),
);
// Set new item to 1 rouble as default // Set new item to 1 rouble as default
if (!this.highestTraderBuyPriceItems[tpl]) if (!this.highestTraderBuyPriceItems[tpl])
@ -449,7 +461,7 @@ export class TraderHelper
*/ */
public getTraderById(traderId: string): Traders public getTraderById(traderId: string): Traders
{ {
const keys = Object.keys(Traders).filter(x => Traders[x] === traderId); const keys = Object.keys(Traders).filter((x) => Traders[x] === traderId);
if (keys.length === 0) if (keys.length === 0)
{ {
@ -462,16 +474,16 @@ export class TraderHelper
} }
/** /**
* Validates that the provided traderEnumValue exists in the Traders enum. If the value is valid, it returns the * Validates that the provided traderEnumValue exists in the Traders enum. If the value is valid, it returns the
* same enum value, effectively serving as a trader ID; otherwise, it logs an error and returns an empty string. * same enum value, effectively serving as a trader ID; otherwise, it logs an error and returns an empty string.
* This method provides a runtime check to prevent undefined behavior when using the enum as a dictionary key. * This method provides a runtime check to prevent undefined behavior when using the enum as a dictionary key.
* *
* For example, instead of this: * For example, instead of this:
* `const traderId = Traders[Traders.PRAPOR];` * `const traderId = Traders[Traders.PRAPOR];`
* *
* You can use safely use this: * You can use safely use this:
* `const traderId = this.traderHelper.getValidTraderIdByEnumValue(Traders.PRAPOR);` * `const traderId = this.traderHelper.getValidTraderIdByEnumValue(Traders.PRAPOR);`
* *
* @param traderEnumValue The trader enum value to validate * @param traderEnumValue The trader enum value to validate
* @returns The validated trader enum value as a string, or an empty string if invalid * @returns The validated trader enum value as a string, or an empty string if invalid
*/ */
@ -483,7 +495,7 @@ export class TraderHelper
return ""; return "";
} }
return Traders[traderEnumValue]; return Traders[traderEnumValue];
} }
@ -506,4 +518,4 @@ export class TraderHelper
{ {
return Object.values(Traders).some((x) => x === traderId); return Object.values(Traders).some((x) => x === traderId);
} }
} }

View File

@ -5,6 +5,6 @@ export class UtilityHelper
{ {
public arrayIntersect<T>(a: T[], b: T[]): T[] public arrayIntersect<T>(a: T[], b: T[]): T[]
{ {
return a.filter(x => b.includes(x)); return a.filter((x) => b.includes(x));
} }
} }

View File

@ -9,7 +9,7 @@ export class WeightedRandomHelper
* @param {tplId: weighting[]} itemArray * @param {tplId: weighting[]} itemArray
* @returns tplId * @returns tplId
*/ */
public getWeightedInventoryItem(itemArray: { [tplId: string]: unknown; } | ArrayLike<unknown>): string public getWeightedInventoryItem(itemArray: {[tplId: string]: unknown;} | ArrayLike<unknown>): string
{ {
const itemKeys = Object.keys(itemArray); const itemKeys = Object.keys(itemArray);
const weights = Object.values(itemArray); const weights = Object.values(itemArray);
@ -18,7 +18,7 @@ export class WeightedRandomHelper
return chosenItem.item; return chosenItem.item;
} }
public getWeightedValue<T>(itemArray: { [key: string]: unknown; } | ArrayLike<unknown>): T public getWeightedValue<T>(itemArray: {[key: string]: unknown;} | ArrayLike<unknown>): T
{ {
const itemKeys = Object.keys(itemArray); const itemKeys = Object.keys(itemArray);
const weights = Object.values(itemArray); const weights = Object.values(itemArray);
@ -41,7 +41,7 @@ export class WeightedRandomHelper
* @param {number[]} weights * @param {number[]} weights
* @returns {{item: any, index: number}} * @returns {{item: any, index: number}}
*/ */
public weightedRandom(items: string | any[], weights: string | any[]): { item: any; index: number; } public weightedRandom(items: string | any[], weights: string | any[]): {item: any; index: number;}
{ {
if (items.length !== weights.length) if (items.length !== weights.length)
{ {
@ -79,9 +79,9 @@ export class WeightedRandomHelper
{ {
return { return {
item: items[itemIndex], item: items[itemIndex],
index: itemIndex index: itemIndex,
}; };
} }
} }
} }
} }