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()
export class AssortHelper
{
constructor(
@inject("WinstonLogger") protected logger: ILogger,
@inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
@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 traderId Traders id the assort belongs to
* @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)
* @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
if (!traderAssorts.loyal_level_items)
@ -67,20 +72,26 @@ export class AssortHelper
* @param assortId Assort to look for linked quest id
* @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)
{
// 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)
{
return { questId: mergedQuestAssorts.success[assortId], status: [QuestStatus.Success]};
return {questId: mergedQuestAssorts.success[assortId], status: [QuestStatus.Success]};
}
else if (assortId in mergedQuestAssorts.fail)
{
return { questId: mergedQuestAssorts.fail[assortId], status: [QuestStatus.Fail]};
return {questId: mergedQuestAssorts.fail[assortId], status: [QuestStatus.Fail]};
}
return undefined;
@ -152,4 +163,4 @@ export class AssortHelper
return assort;
}
}
}

View File

@ -23,22 +23,27 @@ export class BotDifficultyHelper
@inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("BotHelper") protected botHelper: BotHelper,
@inject("ConfigServer") protected configServer: ConfigServer
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
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 friendlyType = pmcType === "bear"
? bearType
: usecType;
const enemyType = pmcType === "bear"
? usecType
: bearType;
const friendlyType = pmcType === "bear" ?
bearType :
usecType;
const enemyType = pmcType === "bear" ?
usecType :
bearType;
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
@ -61,14 +66,23 @@ export class BotDifficultyHelper
{
// get fallback
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];
if (!difficultySettings)
{
this.logger.warning(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]);
this.logger.warning(
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);
@ -77,14 +91,14 @@ export class BotDifficultyHelper
/**
* Get difficulty settings for a PMC
* @param type "usec" / "bear"
* @param difficulty what difficulty to retrieve
* @param difficulty what difficulty to retrieve
* @returns Difficulty object
*/
protected getDifficultySettings(type: string, difficulty: string): Difficulty
{
let difficultySetting = this.pmcConfig.difficulty.toLowerCase() === "asonline"
? difficulty
: this.pmcConfig.difficulty.toLowerCase();
let difficultySetting = this.pmcConfig.difficulty.toLowerCase() === "asonline" ?
difficulty :
this.pmcConfig.difficulty.toLowerCase();
difficultySetting = this.convertBotDifficultyDropdownToBotDifficulty(difficultySetting);
@ -117,4 +131,4 @@ export class BotDifficultyHelper
{
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";
@injectable()
export class BotGeneratorHelper
export class BotGeneratorHelper
{
protected botConfig: IBotConfig;
protected pmcConfig: IPmcConfig;
@ -32,8 +32,8 @@ export class BotGeneratorHelper
@inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("ConfigServer") protected configServer: ConfigServer
)
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
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
* @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
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 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);
}
else if (itemTemplate._props.armorClass) // Is armor
{
else if (itemTemplate._props.armorClass)
{ // Is armor
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)
{
// Get chance from botconfig for bot type
const lightLaserActiveChance = raidIsNight
? this.getBotEquipmentSettingFromConfig(botRole, "lightIsActiveNightChancePercent", 50)
: this.getBotEquipmentSettingFromConfig(botRole, "lightIsActiveDayChancePercent", 25);
itemProperties.Light = { IsActive: (this.randomUtil.getChance100(lightLaserActiveChance)), SelectedMode: 0 };
const lightLaserActiveChance = raidIsNight ?
this.getBotEquipmentSettingFromConfig(botRole, "lightIsActiveNightChancePercent", 50) :
this.getBotEquipmentSettingFromConfig(botRole, "lightIsActiveDayChancePercent", 25);
itemProperties.Light = {IsActive: (this.randomUtil.getChance100(lightLaserActiveChance)), SelectedMode: 0};
}
else if (itemTemplate._parent === BaseClasses.TACTICAL_COMBO)
{
// Get chance from botconfig for bot type, use 50% if no value found
const lightLaserActiveChance = this.getBotEquipmentSettingFromConfig(botRole, "laserIsActiveChancePercent", 50);
itemProperties.Light = { IsActive: (this.randomUtil.getChance100(lightLaserActiveChance)), SelectedMode: 0 };
const lightLaserActiveChance = this.getBotEquipmentSettingFromConfig(
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
const nvgActiveChance = raidIsNight
? this.getBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChanceNightPercent", 90)
: this.getBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChanceDayPercent", 15);
itemProperties.Togglable = { On: (this.randomUtil.getChance100(nvgActiveChance)) };
const nvgActiveChance = raidIsNight ?
this.getBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChanceNightPercent", 90) :
this.getBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChanceDayPercent", 15);
itemProperties.Togglable = {On: (this.randomUtil.getChance100(nvgActiveChance))};
}
// 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
const faceShieldActiveChance = this.getBotEquipmentSettingFromConfig(botRole, "faceShieldIsActiveChancePercent", 75);
itemProperties.Togglable = { On: (this.randomUtil.getChance100(faceShieldActiveChance)) };
const faceShieldActiveChance = this.getBotEquipmentSettingFromConfig(
botRole,
"faceShieldIsActiveChancePercent",
75,
);
itemProperties.Togglable = {On: (this.randomUtil.getChance100(faceShieldActiveChance))};
}
return Object.keys(itemProperties).length
? { upd: itemProperties }
: {};
return Object.keys(itemProperties).length ?
{upd: itemProperties} :
{};
}
/**
@ -153,8 +173,10 @@ export class BotGeneratorHelper
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
* @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;
}
const botEquipmentSettings = this.botConfig.equipment[this.getBotEquipmentRole(botRole)];
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;
}
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;
}
@ -193,14 +231,18 @@ export class BotGeneratorHelper
* @param botRole type of bot being generated for
* @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 currentDurability = this.durabilityLimitsHelper.getRandomizedWeaponDurability(itemTemplate, botRole, maxDurability);
const currentDurability = this.durabilityLimitsHelper.getRandomizedWeaponDurability(
itemTemplate,
botRole,
maxDurability,
);
return {
Durability: currentDurability,
MaxDurability: maxDurability
MaxDurability: maxDurability,
};
}
@ -210,7 +252,7 @@ export class BotGeneratorHelper
* @param botRole type of bot being generated for
* @returns Repairable object
*/
protected generateArmorRepairableProperties(itemTemplate: ITemplateItem, botRole: string): Repairable
protected generateArmorRepairableProperties(itemTemplate: ITemplateItem, botRole: string): Repairable
{
let maxDurability: number;
let currentDurability: number;
@ -219,15 +261,19 @@ export class BotGeneratorHelper
maxDurability = itemTemplate._props.MaxDurability;
currentDurability = itemTemplate._props.MaxDurability;
}
else
else
{
maxDurability = this.durabilityLimitsHelper.getRandomizedMaxArmorDurability(itemTemplate, botRole);
currentDurability = this.durabilityLimitsHelper.getRandomizedArmorDurability(itemTemplate, botRole, maxDurability);
currentDurability = this.durabilityLimitsHelper.getRandomizedArmorDurability(
itemTemplate,
botRole,
maxDurability,
);
}
return {
Durability: currentDurability,
MaxDurability: maxDurability
MaxDurability: maxDurability,
};
}
@ -238,54 +284,81 @@ export class BotGeneratorHelper
* @param equipmentSlot Slot the item will be placed into
* @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
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
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 itemToCheck = item[1];
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)
{
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
let blockingItem = equippedItems.find(x => x._props[`Blocks${equipmentSlot}`]);
if (blockingItem)
let blockingItem = equippedItems.find((x) => x._props[`Blocks${equipmentSlot}`]);
if (blockingItem)
{
//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}` };
// 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}`,
};
}
// Check if any of the current inventory templates have the incoming item defined as incompatible
blockingItem = equippedItems.find(x => x._props.ConflictingItems?.includes(tplToCheck));
if (blockingItem)
blockingItem = equippedItems.find((x) => x._props.ConflictingItems?.includes(tplToCheck));
if (blockingItem)
{
//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}` };
// 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}`,
};
}
// Check if the incoming item has any inventory items defined as incompatible
const blockingInventoryItem = items.find(x => itemToCheck._props.ConflictingItems?.includes(x._tpl));
if (blockingInventoryItem)
const blockingInventoryItem = items.find((x) => itemToCheck._props.ConflictingItems?.includes(x._tpl));
if (blockingInventoryItem)
{
//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}` };
// 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: false, reason: "" };
return {incompatible: false, reason: ""};
}
/**
@ -293,11 +366,13 @@ export class BotGeneratorHelper
* @param botRole Role to convert
* @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()))
? "pmc"
: botRole;
return ([this.pmcConfig.usecType.toLowerCase(), this.pmcConfig.bearType.toLowerCase()].includes(
botRole.toLowerCase(),
)) ?
"pmc" :
botRole;
}
}
@ -309,15 +384,15 @@ export class ExhaustableArray<T>
constructor(
private itemPool: T[],
private randomUtil: RandomUtil,
private jsonUtil: JsonUtil
)
private jsonUtil: JsonUtil,
)
{
this.pool = this.jsonUtil.clone(itemPool);
}
public getRandomValue(): T
public getRandomValue(): T
{
if (!this.pool?.length)
if (!this.pool?.length)
{
return null;
}
@ -328,9 +403,9 @@ export class ExhaustableArray<T>
return toReturn;
}
public getFirstValue(): T
public getFirstValue(): T
{
if (!this.pool?.length)
if (!this.pool?.length)
{
return null;
}
@ -340,13 +415,13 @@ export class ExhaustableArray<T>
return toReturn;
}
public hasValues(): boolean
public hasValues(): boolean
{
if (this.pool?.length)
if (this.pool?.length)
{
return true;
}
return false;
}
}
}

View File

@ -24,15 +24,13 @@ export class BotHelper
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("ConfigServer") protected configServer: ConfigServer
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
}
/**
* Get a template object for the specified botRole from bots.types db
* @param role botRole to get template for
@ -71,7 +69,7 @@ export class BotHelper
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
@ -187,13 +185,15 @@ export class BotHelper
public rollChanceToBePmc(role: string, botConvertMinMax: MinMax): boolean
{
return role.toLowerCase() in this.pmcConfig.convertIntoPmcChance
&& this.randomUtil.getChance100(this.randomUtil.getInt(botConvertMinMax.min, botConvertMinMax.max));
return role.toLowerCase() in this.pmcConfig.convertIntoPmcChance &&
this.randomUtil.getChance100(this.randomUtil.getInt(botConvertMinMax.min, botConvertMinMax.max));
}
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 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
{
return (this.randomUtil.getChance100(this.pmcConfig.isUsec))
? this.pmcConfig.usecType
: this.pmcConfig.bearType;
return (this.randomUtil.getChance100(this.pmcConfig.isUsec)) ?
this.pmcConfig.usecType :
this.pmcConfig.bearType;
}
/**
@ -248,8 +248,8 @@ export class BotHelper
*/
protected getRandomizedPmcSide(): string
{
return (this.randomUtil.getChance100(this.pmcConfig.isUsec))
? "Usec"
: "Bear";
return (this.randomUtil.getChance100(this.pmcConfig.isUsec)) ?
"Usec" :
"Bear";
}
}
}

View File

@ -29,9 +29,9 @@ export class BotWeaponGeneratorHelper
@inject("InventoryHelper") protected inventoryHelper: InventoryHelper,
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper,
@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
@ -61,7 +61,7 @@ export class BotWeaponGeneratorHelper
/* Get the amount of bullets that would fit in the internal magazine
* 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
{
//const range = magCounts.max - magCounts.min;
//return this.randomUtil.getBiasedRandomNumber(magCounts.min, magCounts.max, Math.round(range * 0.75), 4);
// const range = magCounts.max - magCounts.min;
// return this.randomUtil.getBiasedRandomNumber(magCounts.min, magCounts.max, Math.round(range * 0.75), 4);
return this.weightedRandomHelper.getWeightedValue(magCounts.weights);
}
@ -98,7 +98,7 @@ export class BotWeaponGeneratorHelper
{
const magazine: Item[] = [{
_id: this.hashUtil.generate(),
_tpl: magazineTpl
_tpl: magazineTpl,
}];
this.itemHelper.fillMagazineWithCartridge(magazine, magTemplate, ammoTpl, 1);
@ -113,12 +113,17 @@ export class BotWeaponGeneratorHelper
* @param inventory bot inventory to add cartridges to
* @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({
_id: this.hashUtil.generate(),
_tpl: ammoTpl,
upd: { StackObjectsCount: cartridgeCount }
upd: {StackObjectsCount: cartridgeCount},
});
for (const ammoItem of ammoItems)
@ -128,7 +133,8 @@ export class BotWeaponGeneratorHelper
ammoItem._id,
ammoItem._tpl,
[ammoItem],
inventory);
inventory,
);
if (result === ItemAddedResult.NO_SPACE)
{
@ -151,22 +157,32 @@ export class BotWeaponGeneratorHelper
* TODO - move into BotGeneratorHelper, this is not the class for it
* Adds an item with all its children into specified equipmentSlots, wherever it fits.
* @param equipmentSlots Slot to add item+children into
* @param parentId
* @param parentTpl
* @param parentId
* @param parentTpl
* @param itemWithChildren Item to add
* @param inventory Inventory to add item+children into
* @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)
{
// 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)
{
// 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;
}
@ -202,10 +218,12 @@ export class BotWeaponGeneratorHelper
}
// 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
const containerItemsToCheck = containerItems.filter(x => x.slotId === slotGrid._name);
const containerItemsToCheck = containerItems.filter((x) => x.slotId === slotGrid._name);
for (const item of containerItemsToCheck)
{
// 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
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
const findSlotResult = this.containerHelper.findSlotForItem(slotGridMap, itemSize[0], itemSize[1]);
// Open slot found, add item to inventory
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
parentItem.parentId = container._id;
@ -233,7 +256,7 @@ export class BotWeaponGeneratorHelper
parentItem.location = {
x: findSlotResult.x,
y: findSlotResult.y,
r: findSlotResult.rotation ? 1 : 0
r: findSlotResult.rotation ? 1 : 0,
};
inventory.items.push(...itemWithChildren);
@ -284,4 +307,4 @@ export class BotWeaponGeneratorHelper
return true;
}
}
}

View File

@ -43,7 +43,7 @@ export class ContainerHelper
/**
* Try to rotate if there is enough room for the item
* Only occupies one grid of items, no rotation required
* */
*/
if (!foundSlot && itemWidth * itemHeight > 1)
{
foundSlot = this.locateSlot(container2D, containerX, containerY, x, y, itemHeight, itemWidth);
@ -77,7 +77,15 @@ export class ContainerHelper
* @param itemH Items height
* @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;
@ -124,7 +132,14 @@ export class ContainerHelper
* @param rotate is item rotated
* @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 itemHeight = rotate ? itemW : itemH;
@ -146,4 +161,4 @@ export class ContainerHelper
return container2D;
}
}
}

View File

@ -4,7 +4,13 @@ import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
import { NotificationSendHelper } from "@spt-aki/helpers/NotificationSendHelper";
import { NotifierHelper } from "@spt-aki/helpers/NotifierHelper";
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 { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
@ -24,9 +30,9 @@ export class DialogueHelper
@inject("NotifierHelper") protected notifierHelper: NotifierHelper,
@inject("NotificationSendHelper") protected notificationSendHelper: NotificationSendHelper,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("ItemHelper") protected itemHelper: ItemHelper
@inject("ItemHelper") protected itemHelper: ItemHelper,
)
{ }
{}
/**
* @deprecated Use MailSendService.sendMessage() or helpers
@ -35,7 +41,7 @@ export class DialogueHelper
{
const result: MessageContent = {
templateId: templateId,
type: messageType
type: messageType,
};
if (maxStoreTime)
@ -49,7 +55,13 @@ export class DialogueHelper
/**
* @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 isNewDialogue = !(dialogueID in dialogueData);
@ -63,7 +75,7 @@ export class DialogueHelper
messages: [],
pinned: false,
new: 0,
attachmentsNew: 0
attachmentsNew: 0,
};
dialogueData[dialogueID] = dialogue;
@ -79,7 +91,7 @@ export class DialogueHelper
const stashId = this.hashUtil.generate();
items = {
stash: stashId,
data: []
data: [],
};
rewards = this.itemHelper.replaceIDs(null, rewards);
@ -95,7 +107,12 @@ export class DialogueHelper
if (!itemTemplate)
{
// 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;
}
@ -132,7 +149,9 @@ export class DialogueHelper
items: items,
maxStorageTime: messageContent.maxStorageTime,
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)
@ -145,7 +164,10 @@ export class DialogueHelper
// Offer Sold notifications are now separate from the main notification
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);
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.
* @param dialogue
* @param dialogue
* @returns MessagePreview
*/
public getMessagePreview(dialogue: Dialogue): MessagePreview
@ -167,7 +189,7 @@ export class DialogueHelper
dt: message?.dt,
type: message?.type,
templateId: message?.templateId,
uid: dialogue._id
uid: dialogue._id,
};
if (message?.text)
@ -185,17 +207,17 @@ export class DialogueHelper
/**
* Get the item contents for a particular message.
* @param messageID
* @param sessionID
* @param messageID
* @param sessionID
* @param itemId Item being moved to inventory
* @returns
* @returns
*/
public getMessageItemContents(messageID: string, sessionID: string, itemId: string): Item[]
{
const dialogueData = this.saveServer.getProfile(sessionID).dialogues;
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)
{
continue;
@ -211,7 +233,7 @@ export class DialogueHelper
// 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
const rewardItemCount = message.items.data.filter(x => x._id !== itemId );
const rewardItemCount = message.items.data.filter((x) => x._id !== itemId);
if (rewardItemCount.length === 0)
{
message.rewardCollected = true;
@ -240,4 +262,4 @@ export class DialogueHelper
return profile.dialogues;
}
}
}

View File

@ -15,7 +15,7 @@ export class DurabilityLimitsHelper
constructor(
@inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("BotHelper") protected botHelper: BotHelper,
@inject("ConfigServer") protected configServer: ConfigServer
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
@ -172,12 +172,14 @@ export class DurabilityLimitsHelper
const maxDelta = this.getMaxWeaponDeltaFromConfig(botRole);
const delta = this.randomUtil.getInt(minDelta, maxDelta);
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
return (result >= durabilityValueMinLimit)
? result
: durabilityValueMinLimit;
return (result >= durabilityValueMinLimit) ?
result :
durabilityValueMinLimit;
}
protected generateArmorDurability(botRole: string, maxDurability: number): number
@ -186,12 +188,14 @@ export class DurabilityLimitsHelper
const maxDelta = this.getMaxArmorDeltaFromConfig(botRole);
const delta = this.randomUtil.getInt(minDelta, maxDelta);
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
return (result >= durabilityValueMinLimit)
? result
: durabilityValueMinLimit;
return (result >= durabilityValueMinLimit) ?
result :
durabilityValueMinLimit;
}
protected getMinWeaponDeltaFromConfig(botRole: string): number
@ -234,7 +238,7 @@ export class DurabilityLimitsHelper
return this.botConfig.durability.default.armor.maxDelta;
}
protected getMinArmorLimitPercentFromConfig (botRole: string): number
protected getMinArmorLimitPercentFromConfig(botRole: string): number
{
if (this.botConfig.durability[botRole])
{
@ -244,7 +248,7 @@ export class DurabilityLimitsHelper
return this.botConfig.durability.default.armor.minLimitPercent;
}
protected getMinWeaponLimitPercentFromConfig (botRole: string): number
protected getMinWeaponLimitPercentFromConfig(botRole: string): number
{
if (this.botConfig.durability[botRole])
{
@ -253,4 +257,4 @@ export class DurabilityLimitsHelper
return this.botConfig.durability.default.weapon.minLimitPercent;
}
}
}

View File

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

View File

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

View File

@ -21,7 +21,7 @@ export class HealthHelper
@inject("WinstonLogger") protected logger: ILogger,
@inject("TimeUtil") protected timeUtil: TimeUtil,
@inject("SaveServer") protected saveServer: SaveServer,
@inject("ConfigServer") protected configServer: ConfigServer
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
this.healthConfig = this.configServer.getConfig(ConfigTypes.HEALTH);
@ -36,11 +36,11 @@ export class HealthHelper
{
const profile = this.saveServer.getProfile(sessionID);
if (!profile.vitality) // Occurs on newly created profiles
{
if (!profile.vitality)
{ // Occurs on newly created profiles
profile.vitality = {
health: null,
effects: null
effects: null,
};
}
profile.vitality.health = {
@ -53,7 +53,7 @@ export class HealthHelper
LeftArm: 0,
RightArm: 0,
LeftLeg: 0,
RightLeg: 0
RightLeg: 0,
};
profile.vitality.effects = {
@ -63,7 +63,7 @@ export class HealthHelper
LeftArm: {},
RightArm: {},
LeftLeg: {},
RightLeg: {}
RightLeg: {},
};
return profile;
@ -77,7 +77,13 @@ export class HealthHelper
* @param addEffects Should effects be added or removed (default - add)
* @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 profile = this.saveServer.getProfile(sessionID);
@ -96,20 +102,26 @@ export class HealthHelper
{
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;
}
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
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
@ -159,7 +171,10 @@ export class HealthHelper
if (target === 0)
{
// 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);
@ -176,7 +191,12 @@ export class HealthHelper
* @param bodyPartsWithEffects dict of body parts with effects that should be added 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)
{
@ -208,7 +228,9 @@ export class HealthHelper
// Sometimes the value can be Infinity instead of -1, blame HealthListener.cs in modules
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;
}
this.addEffect(pmcData, bodyPart, effectType, time);
@ -236,7 +258,7 @@ export class HealthHelper
profileBodyPart.Effects = {};
}
profileBodyPart.Effects[effectType] = { Time: duration };
profileBodyPart.Effects[effectType] = {Time: duration};
// Delete empty property to prevent client bugs
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)
{
@ -257,4 +279,4 @@ export class HealthHelper
return true;
}
}
}

View File

@ -48,7 +48,7 @@ export class HideoutHelper
@inject("InventoryHelper") protected inventoryHelper: InventoryHelper,
@inject("PlayerService") protected playerService: PlayerService,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("ConfigServer") protected configServer: ConfigServer
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT);
@ -61,9 +61,13 @@ export class HideoutHelper
* @param sessionID Session id
* @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)
{
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));
}
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:
// - 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[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,
NeedFuelForAllProductionTime: needFuelForAllProductionTime, // Used when sending to client
needFuelForAllProductionTime: needFuelForAllProductionTime, // used when stored in production.json
SkipTime: 0
SkipTime: 0,
};
}
/**
* Is the provided object a Production type
* @param productive
* @returns
* @param productive
* @returns
*/
public isProductionType(productive: Productive): productive is Production
{
@ -124,12 +133,15 @@ export class HideoutHelper
// Handle additional changes some bonuses need before being added
switch (bonus.type)
{
case "StashSize": {
case "StashSize":
{
// 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)
{
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;
@ -140,7 +152,7 @@ export class HideoutHelper
pmcData.Health.Energy.Maximum += bonus.value;
break;
case "TextBonus":
//Delete values before they're added to profile
// Delete values before they're added to profile
delete bonus.value;
delete bonus.passive;
delete bonus.production;
@ -173,15 +185,19 @@ export class HideoutHelper
* @param pmcData Player profile
* @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 bitcoinCount = bitcoinFarm?.slots.filter(slot => slot.item).length ?? 0; // Get slots with an item property
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 hideoutProperties = {
btcFarmCGs: bitcoinCount,
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))
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),
),
};
return hideoutProperties;
@ -189,23 +205,27 @@ export class HideoutHelper
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
return waterCollector.slots.some(x => x.item);
return waterCollector.slots.some((x) => x.item);
}
// No Filter
return false;
}
/**
* Update progress timer for water collector
* @param pmcData profile to update
* @param productionId id of water collection production to update
* @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);
if (hideoutProperties.waterCollectorHasFilter)
@ -219,7 +239,10 @@ export class HideoutHelper
* @param pmcData Profile to check for productions and update
* @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;
@ -255,12 +278,16 @@ export class HideoutHelper
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;
}
// Other recipes not covered by above
const recipe = recipes.find(r => r._id === prodId);
const recipe = recipes.find((r) => r._id === prodId);
if (!recipe)
{
this.logger.error(this.localisationService.getText("hideout-missing_recipe_for_area", prodId));
@ -277,9 +304,14 @@ export class HideoutHelper
* @param pmcData Player profile
* @param prodId Production id 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
if (this.doesProgressMatchProductionTime(pmcData, prodId))
@ -321,7 +353,8 @@ export class HideoutHelper
*/
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;
}
@ -331,7 +364,11 @@ export class HideoutHelper
* @param pmcData Profile to update areas of
* @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)
{
@ -361,9 +398,10 @@ export class HideoutHelper
{
// 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
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
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;
fuelDrainRate *= fuelBonusPercent;
// Hideout management resource consumption bonus:
@ -376,9 +414,9 @@ export class HideoutHelper
{
if (generatorArea.slots[i].item)
{
let resourceValue = (generatorArea.slots[i].item[0].upd?.Resource)
? generatorArea.slots[i].item[0].upd.Resource.Value
: null;
let resourceValue = (generatorArea.slots[i].item[0].upd?.Resource) ?
generatorArea.slots[i].item[0].upd.Resource.Value :
null;
if (resourceValue === 0)
{
continue;
@ -386,9 +424,9 @@ export class HideoutHelper
else if (!resourceValue)
{
const fuelItem = HideoutHelper.expeditionaryFuelTank;
resourceValue = generatorArea.slots[i].item[0]._tpl === fuelItem
? 60 - fuelDrainRate
: 100 - fuelDrainRate;
resourceValue = generatorArea.slots[i].item[0]._tpl === fuelItem ?
60 - fuelDrainRate :
100 - fuelDrainRate;
pointsConsumed = fuelDrainRate;
}
else
@ -400,7 +438,7 @@ export class HideoutHelper
resourceValue = Math.round(resourceValue * 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)
{
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)
if (area.level !== 3)
@ -454,7 +497,7 @@ export class HideoutHelper
recipeId: HideoutHelper.waterCollector,
Action: "HideoutSingleProductionStart",
items: [],
timestamp: this.timeUtil.getTimestamp()
timestamp: this.timeUtil.getTimestamp(),
};
this.registerProduction(pmcData, recipe, sessionId);
@ -469,14 +512,24 @@ export class HideoutHelper
* @param pmcData Player profile
* @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);
const productionTime = this.getTotalProductionTimeSeconds(HideoutHelper.waterCollector);
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
let pointsConsumed = 0;
if (production.Progress < productionTime)
@ -489,9 +542,9 @@ export class HideoutHelper
if (waterFilterArea.slots[i].item)
{
// How many units of filter are left
let resourceValue = (waterFilterArea.slots[i].item[0].upd?.Resource)
? waterFilterArea.slots[i].item[0].upd.Resource.Value
: null;
let resourceValue = (waterFilterArea.slots[i].item[0].upd?.Resource) ?
waterFilterArea.slots[i].item[0].upd.Resource.Value :
null;
if (!resourceValue)
{
// None left
@ -500,7 +553,8 @@ export class HideoutHelper
}
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;
}
@ -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
* @param secondsSinceServerTick Time passed
* @param totalProductionTime Total time collecting water
* @param productionProgress how far water collector has progressed
* @param baseFilterDrainRate Base drain rate
* @returns
* @param productionProgress how far water collector has progressed
* @param baseFilterDrainRate Base drain rate
* @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
? (totalProductionTime - productionProgress) // more time passed than prod time, get total minus the current progress
: secondsSinceServerTick;
const drainRateMultiplier = secondsSinceServerTick > totalProductionTime ?
(totalProductionTime - productionProgress) // more time passed than prod time, get total minus the current progress
:
secondsSinceServerTick;
// Multiply drain rate by calculated multiplier
baseFilterDrainRate *= drainRateMultiplier;
@ -578,16 +638,16 @@ export class HideoutHelper
*/
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);
}
/**
* Create a upd object using passed in parameters
* @param stackCount
* @param resourceValue
* @param resourceUnitsConsumed
* @param stackCount
* @param resourceValue
* @param resourceUnitsConsumed
* @returns Upd
*/
protected getAreaUpdObject(stackCount: number, resourceValue: number, resourceUnitsConsumed: number): Upd
@ -596,8 +656,8 @@ export class HideoutHelper
StackObjectsCount: stackCount,
Resource: {
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),
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:
const hideoutManagementConsumptionBonus = 1.0 - this.getHideoutManagementConsumptionBonus(pmcData);
filterDrainRate *= hideoutManagementConsumptionBonus;
@ -618,9 +679,9 @@ export class HideoutHelper
{
if (airFilterArea.slots[i].item)
{
let resourceValue = (airFilterArea.slots[i].item[0].upd?.Resource)
? airFilterArea.slots[i].item[0].upd.Resource.Value
: null;
let resourceValue = (airFilterArea.slots[i].item[0].upd?.Resource) ?
airFilterArea.slots[i].item[0].upd.Resource.Value :
null;
if (!resourceValue)
{
resourceValue = 300 - filterDrainRate;
@ -634,7 +695,7 @@ export class HideoutHelper
resourceValue = Math.round(resourceValue * 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)
{
this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, 1);
@ -647,8 +708,8 @@ export class HideoutHelper
StackObjectsCount: 1,
Resource: {
Value: resourceValue,
UnitsConsumed: pointsConsumed
}
UnitsConsumed: pointsConsumed,
},
};
this.logger.debug(`Air filter: ${resourceValue} filter left on slot ${i + 1}`);
break; // Break here to avoid updating all filters
@ -662,11 +723,13 @@ export class HideoutHelper
}
}
}
protected updateBitcoinFarm(pmcData: IPmcData, btcFarmCGs: number, isGeneratorOn: boolean): Production
{
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);
// 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
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)
{
if (btcProd.Products.length < coinSlotCount)
@ -753,8 +817,8 @@ export class HideoutHelper
_id: this.hashUtil.generate(),
_tpl: "59faff1d86f7746c51718c9c",
upd: {
StackObjectsCount: 1
}
StackObjectsCount: 1,
},
});
btcProd.Progress -= coinCraftTimeSeconds;
@ -767,13 +831,17 @@ export class HideoutHelper
* @param recipe Hideout production recipe being crafted we need the ticks for
* @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
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;
}
@ -792,7 +860,9 @@ export class HideoutHelper
*/
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 hasManagementSkillSlots = this.profileHelper.hasEliteSkillLevel(SkillTypes.HIDEOUT_MANAGEMENT, pmcData);
const managementSlotsCount = this.getBitcoinMinerContainerSlotSize() || 2;
@ -805,7 +875,8 @@ export class HideoutHelper
*/
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
// 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);
roundedLevel = (roundedLevel === 51)
? roundedLevel - 1
: roundedLevel;
roundedLevel = (roundedLevel === 51) ?
roundedLevel - 1 :
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
{
const craftingSkill = pmcData.Skills.Common.find(x=> x.Id === SkillTypes.CRAFTING);
const craftingSkill = pmcData.Skills.Common.find((x) => x.Id === SkillTypes.CRAFTING);
if (!craftingSkill)
{
return productionTime;
@ -865,7 +938,11 @@ export class HideoutHelper
* @param sessionId Session id
* @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);
@ -875,7 +952,7 @@ export class HideoutHelper
{
const errorMsg = this.localisationService.getText("hideout-no_bitcoins_to_collect");
this.logger.error(errorMsg);
return this.httpResponse.appendErrorToOutput(output, errorMsg);
}
@ -911,9 +988,9 @@ export class HideoutHelper
items: [{
// eslint-disable-next-line @typescript-eslint/naming-convention
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
{
const waterCollector = pmcProfile.Hideout.Areas.find(x => x.type === HideoutAreas.WATER_COLLECTOR);
const medStation = pmcProfile.Hideout.Areas.find(x => x.type === HideoutAreas.MEDSTATION);
const wall = pmcProfile.Hideout.Areas.find(x => x.type === HideoutAreas.EMERGENCY_WALL);
const waterCollector = pmcProfile.Hideout.Areas.find((x) => x.type === HideoutAreas.WATER_COLLECTOR);
const medStation = pmcProfile.Hideout.Areas.find((x) => x.type === HideoutAreas.MEDSTATION);
const wall = pmcProfile.Hideout.Areas.find((x) => x.type === HideoutAreas.EMERGENCY_WALL);
// No collector or med station, skip
if (!(waterCollector && medStation))
@ -947,9 +1024,9 @@ export class HideoutHelper
*/
protected hideoutImprovementIsComplete(improvement: IHideoutImprovement): boolean
{
return improvement?.completed
? true
: false;
return improvement?.completed ?
true :
false;
}
/**
@ -961,10 +1038,13 @@ export class HideoutHelper
for (const improvementId in pmcProfile.Hideout.Improvement)
{
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;
}
}
}
}
}

View File

@ -18,11 +18,11 @@ export class HttpServerHelper
json: "application/json",
png: "image/png",
svg: "image/svg+xml",
txt: "text/plain"
txt: "text/plain",
};
constructor(
@inject("ConfigServer") protected configServer: ConfigServer
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP);
@ -60,7 +60,7 @@ export class HttpServerHelper
public sendTextJson(resp: any, output: any): void
{
// 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);
}
}
}

View File

@ -39,7 +39,7 @@ export class InRaidHelper
@inject("PaymentHelper") protected paymentHelper: PaymentHelper,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("ProfileFixerService") protected profileFixerService: ProfileFixerService,
@inject("ConfigServer") protected configServer: ConfigServer
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
this.lostOnDeathConfig = this.configServer.getConfig(ConfigTypes.LOST_ON_DEATH);
@ -95,8 +95,13 @@ export class InRaidHelper
{
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;
}, existingFenceStanding);
@ -116,7 +121,7 @@ export class InRaidHelper
// Scavs and bosses
return botTypes[victim.Role.toLowerCase()]?.experience?.standingForKill;
}
// PMCs - get by bear/usec
return botTypes[victim.Side.toLowerCase()]?.experience?.standingForKill;
}
@ -131,7 +136,11 @@ export class InRaidHelper
* @param sessionID Session id
* @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
this.resetSkillPointsEarnedDuringRaid(saveProgressRequest.profile);
@ -168,7 +177,9 @@ export class InRaidHelper
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;
}
/**
* Look for quests not are now status = fail that were not failed pre-raid and run the failQuest() function
* @param sessionId Player id
@ -207,7 +217,12 @@ export class InRaidHelper
* @param preRaidQuests Quests prior to starting 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)
{
@ -219,7 +234,7 @@ export class InRaidHelper
for (const postRaidQuest of postRaidQuests)
{
// 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)
{
// Post-raid quest is failed but wasn't pre-raid
@ -230,12 +245,11 @@ export class InRaidHelper
const failBody: IFailQuestRequestData = {
Action: "QuestComplete",
qid: postRaidQuest.qid,
removeExcessItems: true
removeExcessItems: true,
};
this.questHelper.failQuest(pmcData, failBody, sessionId);
}
}
}
}
@ -252,7 +266,10 @@ export class InRaidHelper
* @param saveProgressRequest post-raid request
* @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
for (const bodyPartId in saveProgressRequest.profile.Health.BodyParts)
@ -287,7 +304,10 @@ export class InRaidHelper
* @param tradersServerProfile Server
* @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)
{
@ -356,12 +376,13 @@ export class InRaidHelper
public removeSpawnedInSessionPropertyFromItems(postRaidProfile: IPostRaidPmcData): IPostRaidPmcData
{
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
return "upd" in x && "SpawnedInSession" in x.upd
&& !dbItems[x._tpl]._props.QuestItem
&& !(this.inRaidConfig.keepFiRSecureContainerOnDeath && this.itemHelper.itemIsInsideContainer(x, "SecuredContainer", postRaidProfile.Inventory.items));
return "upd" in x && "SpawnedInSession" in x.upd &&
!dbItems[x._tpl]._props.QuestItem &&
!(this.inRaidConfig.keepFiRSecureContainerOnDeath &&
this.itemHelper.itemIsInsideContainer(x, "SecuredContainer", postRaidProfile.Inventory.items));
});
for (const item of itemsToRemovePropertyFrom)
@ -409,7 +430,7 @@ export class InRaidHelper
public deleteInventory(pmcData: IPmcData, sessionID: string): void
{
// 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)
{
this.inventoryHelper.removeItem(pmcData, itemId, sessionID);
@ -426,30 +447,30 @@ export class InRaidHelper
*/
protected getInventoryItemsLostOnDeath(pmcProfile: IPmcData): Item[]
{
const inventoryItems = pmcProfile.Inventory.items ?? [];
const inventoryItems = pmcProfile.Inventory.items ?? [];
const equipment = pmcProfile?.Inventory?.equipment;
const questRaidItems = pmcProfile?.Inventory?.questRaidItems;
return inventoryItems.filter(x =>
return inventoryItems.filter((x) =>
{
// Keep items flagged as kept after death
if (this.isItemKeptAfterDeath(pmcProfile, x))
{
return false;
}
// Remove normal items or quest raid items
if (x.parentId === equipment || x.parentId === questRaidItems)
{
return true;
}
// Pocket items are not lost on death
if (x.slotId.startsWith("pocket"))
{
return true;
}
return false;
});
}
@ -461,13 +482,13 @@ export class InRaidHelper
*/
protected getBaseItemsInRigPocketAndBackpack(pmcData: IPmcData): Item[]
{
const rig = pmcData.Inventory.items.find(x => x.slotId === "TacticalVest");
const pockets = pmcData.Inventory.items.find(x => x.slotId === "Pockets");
const backpack = pmcData.Inventory.items.find(x => x.slotId === "Backpack");
const rig = pmcData.Inventory.items.find((x) => x.slotId === "TacticalVest");
const pockets = pmcData.Inventory.items.find((x) => x.slotId === "Pockets");
const backpack = pmcData.Inventory.items.find((x) => x.slotId === "Backpack");
const baseItemsInRig = pmcData.Inventory.items.filter(x => x.parentId === rig?._id);
const baseItemsInPockets = pmcData.Inventory.items.filter(x => x.parentId === pockets?._id);
const baseItemsInBackpack = pmcData.Inventory.items.filter(x => x.parentId === backpack?._id);
const baseItemsInRig = pmcData.Inventory.items.filter((x) => x.parentId === rig?._id);
const baseItemsInPockets = pmcData.Inventory.items.filter((x) => x.parentId === pockets?._id);
const baseItemsInBackpack = pmcData.Inventory.items.filter((x) => x.parentId === backpack?._id);
return [...baseItemsInRig, ...baseItemsInPockets, ...baseItemsInBackpack];
}
@ -539,7 +560,7 @@ export class InRaidHelper
"pocket1",
"pocket2",
"pocket3",
"pocket4"
"pocket4",
];
let inventoryItems: Item[] = [];
@ -574,7 +595,7 @@ export class InRaidHelper
// Add these new found items to our list of inventory items
inventoryItems = [
...inventoryItems,
...foundItems
...foundItems,
];
// Now find the children of these items
@ -583,4 +604,4 @@ export class InRaidHelper
return inventoryItems;
}
}
}

View File

@ -32,11 +32,11 @@ import { JsonUtil } from "@spt-aki/utils/JsonUtil";
export interface OwnerInventoryItems
{
/** Inventory items from source */
from: Item[]
from: Item[];
/** Inventory items at destination */
to: Item[]
sameInventory: boolean,
isMail: boolean
to: Item[];
sameInventory: boolean;
isMail: boolean;
}
@injectable()
@ -58,7 +58,7 @@ export class InventoryHelper
@inject("ContainerHelper") protected containerHelper: ContainerHelper,
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("ConfigServer") protected configServer: ConfigServer
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
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
* @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 itemsToAdd: IAddItemTempObject[] = [];
@ -85,19 +94,21 @@ export class InventoryHelper
{
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);
requestItem.isPreset = true;
requestItem.item_id = presetItems[0]._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)
{
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)
{
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);
}
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)
itemLib.push(...purchasedItemWithChildren);
}
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
{
// Only grab the relevant trader items and add unique values
const traderItems = this.traderAssortHelper.getAssort(sessionID, request.tid).items;
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);
}
@ -133,7 +149,15 @@ export class InventoryHelper
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)
{
return errorOutput;
@ -151,9 +175,9 @@ export class InventoryHelper
catch (err)
{
// Callback failed
const message = typeof err === "string"
? err
: this.localisationService.getText("http-unknown_error");
const message = typeof err === "string" ?
err :
this.localisationService.getText("http-unknown_error");
return this.httpResponse.appendErrorToOutput(output, message);
}
@ -163,7 +187,7 @@ export class InventoryHelper
{
let idForItemToAdd = this.hashUtil.generate();
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 (itemToAdd.isPreset)
@ -191,7 +215,7 @@ export class InventoryHelper
// add ragfair upd properties
if (addUpd)
{
upd = { ...addUpd, ...upd };
upd = {...addUpd, ...upd};
}
// Hideout items need to be marked as found in raid
@ -200,7 +224,7 @@ export class InventoryHelper
{
upd.SpawnedInSession = true;
}
// Remove invalid properties prior to adding to inventory
if (upd.UnlimitedCount !== undefined)
{
@ -222,8 +246,8 @@ export class InventoryHelper
_tpl: itemToAdd.itemRef._tpl,
parentId: itemToAdd.containerId,
slotId: "hideout",
location: { x: itemToAdd.location.x, y: itemToAdd.location.y, r: itemToAdd.location.rotation ? 1 : 0 },
upd: this.jsonUtil.clone(upd)
location: {x: itemToAdd.location.x, y: itemToAdd.location.y, r: itemToAdd.location.rotation ? 1 : 0},
upd: this.jsonUtil.clone(upd),
});
pmcData.Inventory.items.push({
@ -231,8 +255,8 @@ export class InventoryHelper
_tpl: itemToAdd.itemRef._tpl,
parentId: itemToAdd.containerId,
slotId: "hideout",
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
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
});
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 (itemToAdd.isPreset)
{
upd = { StackObjectsCount: itemToAdd.count };
upd = {StackObjectsCount: itemToAdd.count};
for (const updID in itemLib[tmpKey].upd)
{
@ -278,8 +302,9 @@ export class InventoryHelper
location: {
x: itemToAdd.location.x,
y: itemToAdd.location.y,
r: "Horizontal" },
upd: this.jsonUtil.clone(upd)
r: "Horizontal",
},
upd: this.jsonUtil.clone(upd),
});
pmcData.Inventory.items.push({
@ -290,8 +315,9 @@ export class InventoryHelper
location: {
x: itemToAdd.location.x,
y: itemToAdd.location.y,
r: "Horizontal" },
upd: this.jsonUtil.clone(upd)
r: "Horizontal",
},
upd: this.jsonUtil.clone(upd),
});
}
else
@ -310,7 +336,7 @@ export class InventoryHelper
parentId: toDo[0][1],
slotId: slotID,
...itemLocation,
upd: this.jsonUtil.clone(upd)
upd: this.jsonUtil.clone(upd),
});
pmcData.Inventory.items.push({
@ -319,7 +345,7 @@ export class InventoryHelper
parentId: toDo[0][1],
slotId: itemLib[tmpKey].slotId,
...itemLocation,
upd: this.jsonUtil.clone(upd)
upd: this.jsonUtil.clone(upd),
});
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 stashFS2D Two dimentional stash map
* @param sortingTableFS2D Two dimentional sorting table stash map
* @param itemLib
* @param itemLib
* @param pmcData Player profile
* @param useSortingTable Should sorting table be used for overflow items when no inventory space for item
* @param output Client output object
* @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);
@ -360,16 +394,26 @@ export class InventoryHelper
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)
{
const errorText = typeof err === "string"
? ` -> ${err}`
: "";
const errorText = typeof err === "string" ?
` -> ${err}` :
"";
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
itemToAdd.containerId = playerInventory.stash;
@ -377,7 +421,7 @@ export class InventoryHelper
x: findSlotResult.x,
y: findSlotResult.y,
r: findSlotResult.rotation ? 1 : 0,
rotation: findSlotResult.rotation
rotation: findSlotResult.rotation,
};
// Success! exit
@ -387,19 +431,33 @@ export class InventoryHelper
// Space not found in main stash, use sorting table
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 itemSizeY = findSortingSlotResult.rotation ? itemSize[0] : itemSize[1];
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)
{
const errorText = typeof err === "string" ? ` -> ${err}` : "";
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
@ -408,12 +466,15 @@ export class InventoryHelper
x: findSortingSlotResult.x,
y: findSortingSlotResult.y,
r: findSortingSlotResult.rotation ? 1 : 0,
rotation: findSortingSlotResult.rotation
rotation: findSortingSlotResult.rotation,
};
}
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 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 stackSlots = itemInfo._props.StackSlots;
@ -450,7 +518,7 @@ export class InventoryHelper
parentId: parentId,
slotId: "cartridges",
location: location,
upd: { StackObjectsCount: ammoStackSize }
upd: {StackObjectsCount: ammoStackSize},
};
if (foundInRaid)
@ -472,7 +540,6 @@ export class InventoryHelper
}
/**
*
* @param assortItems Items to add to inventory
* @param requestItem Details of purchased item to add to inventory
* @param result Array split stacks are added to
@ -488,18 +555,21 @@ export class InventoryHelper
const itemToAdd: IAddItemTempObject = {
itemRef: item,
count: requestItem.count,
isPreset: requestItem.isPreset };
isPreset: requestItem.isPreset,
};
// Split stacks if the size is higher than allowed by items StackMaxSize property
let maxStackCount = 1;
if (requestItem.count > itemDetails._props.StackMaxSize)
{
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 + Math.floor(remainingCountOfItemToAdd / itemDetails._props.StackMaxSize)
: Math.floor(remainingCountOfItemToAdd / itemDetails._props.StackMaxSize);
maxStackCount = (calc > 0) ?
maxStackCount + Math.floor(remainingCountOfItemToAdd / itemDetails._props.StackMaxSize) :
Math.floor(remainingCountOfItemToAdd / itemDetails._props.StackMaxSize);
// Iterate until totalCountOfPurchasedItem is 0
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
* @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)
{
@ -559,14 +634,14 @@ export class InventoryHelper
// We have output object, inform client of item deletion
if (output)
{
output.profileChanges[sessionID].items.del.push({ _id: itemId });
output.profileChanges[sessionID].items.del.push({_id: itemId});
}
for (const childId of itemToRemoveWithChildren)
{
// 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.
const inventoryIndex = inventoryItems.findIndex(item => item._id === childId);
const inventoryIndex = inventoryItems.findIndex((item) => item._id === childId);
if (inventoryIndex > -1)
{
inventoryItems.splice(inventoryIndex, 1);
@ -574,10 +649,12 @@ export class InventoryHelper
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)
{
insuredItems.splice(insuredIndex, 1);
@ -587,7 +664,11 @@ export class InventoryHelper
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);
@ -595,18 +676,23 @@ export class InventoryHelper
const dialogs = Object.values(fullProfile.dialogues);
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)
{
// 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)
{
// Get index of item to remove from reward array + remove it
const indexOfItemToRemove = messageWithReward.items.data.indexOf(itemToDelete);
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;
}
messageWithReward.items.data.splice(indexOfItemToRemove, 1);
@ -622,10 +708,18 @@ export class InventoryHelper
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)
{
return output;
}
const itemsToReduce = this.itemHelper.findAndReturnChildrenAsItems(pmcData.Inventory.items, itemId);
let remainingCount = count;
@ -644,11 +738,15 @@ export class InventoryHelper
itemToReduce.upd.StackObjectsCount -= remainingCount;
remainingCount = 0;
if (output)
{
output.profileChanges[sessionID].items.change.push(itemToReduce);
}
}
if (remainingCount === 0)
{
break;
}
}
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
// -> 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 result = this.itemHelper.getItem(itemTpl);
@ -681,7 +783,10 @@ export class InventoryHelper
// Item found but no _props property
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
@ -711,11 +816,11 @@ export class InventoryHelper
const skipThisItems: string[] = [
BaseClasses.BACKPACK,
BaseClasses.SEARCHABLE_ITEM,
BaseClasses.SIMPLE_CONTAINER
BaseClasses.SIMPLE_CONTAINER,
];
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)
{
outX -= tmpItem._props.SizeReduceRight;
@ -729,7 +834,7 @@ export class InventoryHelper
{
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)
{
continue;
@ -741,7 +846,12 @@ export class InventoryHelper
const itemResult = this.itemHelper.getItem(item._tpl);
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];
@ -781,7 +891,7 @@ export class InventoryHelper
return [
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 = {
byItemId: {},
byParentId: {}
byParentId: {},
};
for (const item of inventoryItem)
@ -832,8 +942,14 @@ export class InventoryHelper
const tmpSize = this.getSizeByInventoryItemHash(item._tpl, item._id, inventoryItemHash);
const iW = tmpSize[0]; // x
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 fW = (((item.location as Location).r === 1 || (item.location as Location).r === "Vertical" || (item.location as Location).rotation === "Vertical") ? iH : iW);
const fH = ((item.location as Location).r === 1 || (item.location as Location).r === "Vertical" ||
(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;
for (let y = 0; y < fH; y++)
@ -844,7 +960,12 @@ export class InventoryHelper
}
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
* @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;
const pmcItems = this.profileHelper.getPmcProfile(sessionId).Inventory.items;
@ -878,9 +1002,9 @@ export class InventoryHelper
else if (request.fromOwner.type.toLocaleLowerCase() === "mail")
{
// Split requests dont use 'use' but 'splitItem' property
const item = "splitItem" in request
? request.splitItem
: request.item;
const item = "splitItem" in request ?
request.splitItem :
request.item;
fromInventoryItems = this.dialogueHelper.getMessageItemContents(request.fromOwner.id, sessionId, item);
fromType = "mail";
}
@ -908,7 +1032,7 @@ export class InventoryHelper
from: fromInventoryItems,
to: toInventoryItems,
sameInventory: isSameInventory,
isMail: fromType === "mail"
isMail: fromType === "mail",
};
}
@ -921,7 +1045,12 @@ export class InventoryHelper
protected getStashSlotMap(pmcData: IPmcData, sessionID: string): number[][]
{
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[][]
@ -936,7 +1065,7 @@ export class InventoryHelper
*/
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);
if (!stashTPL)
{
@ -948,12 +1077,12 @@ export class InventoryHelper
this.logger.error(this.localisationService.getText("inventory-stash_not_found", stashTPL));
}
const stashX = stashItemDetails[1]._props.Grids[0]._props.cellsH !== 0
? stashItemDetails[1]._props.Grids[0]._props.cellsH
: 10;
const stashY = stashItemDetails[1]._props.Grids[0]._props.cellsV !== 0
? stashItemDetails[1]._props.Grids[0]._props.cellsV
: 66;
const stashX = stashItemDetails[1]._props.Grids[0]._props.cellsH !== 0 ?
stashItemDetails[1]._props.Grids[0]._props.cellsH :
10;
const stashY = stashItemDetails[1]._props.Grids[0]._props.cellsV !== 0 ?
stashItemDetails[1]._props.Grids[0]._props.cellsV :
66;
return [stashX, stashY];
}
@ -965,7 +1094,7 @@ export class InventoryHelper
protected getStashType(sessionID: string): string
{
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)
{
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);
for (const itemId of idsToMove)
{
const itemToMove = fromItems.find(x => x._id === itemId);
const itemToMove = fromItems.find((x) => x._id === itemId);
if (!itemToMove)
{
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.
* @param pmcData profile to edit
* @param inventoryItems
* @param moveRequest
* @param inventoryItems
* @param moveRequest
* @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);
// 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)
{
const errorMesage = `Unable to move item: ${moveRequest.item}, cannot find in inventory`;
@ -1040,12 +1173,19 @@ export class InventoryHelper
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)
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};
}
@ -1059,7 +1199,6 @@ export class InventoryHelper
if ("location" in moveRequest.to)
{
matchingInventoryItem.location = moveRequest.to.location;
}
else
{
@ -1085,8 +1224,8 @@ export class InventoryHelper
if (pmcData.Inventory.fastPanel[itemKey] === itemBeingMoved._id)
{
// 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
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
{
// -> Move item to different place - counts with equipping filling magazine etc
@ -1140,7 +1279,7 @@ namespace InventoryHelper
{
export interface InventoryItemHash
{
byItemId: Record<string, Item>
byParentId: Record<string, Item[]>
byItemId: Record<string, Item>;
byParentId: Record<string, Item[]>;
}
}
}

View File

@ -29,7 +29,7 @@ class ItemHelper
BaseClasses.SORTING_TABLE,
BaseClasses.INVENTORY,
BaseClasses.STATIONARY_CONTAINER,
BaseClasses.POCKETS
BaseClasses.POCKETS,
];
constructor(
@ -44,7 +44,7 @@ class ItemHelper
@inject("ItemBaseClassService") protected itemBaseClassService: ItemBaseClassService,
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("LocaleService") protected localeService: LocaleService
@inject("LocaleService") protected localeService: LocaleService,
)
{}
@ -68,11 +68,11 @@ class ItemHelper
}
// Is item valid
return !itemDetails[1]._props.QuestItem
&& itemDetails[1]._type === "Item"
&& invalidBaseTypes.every(x => !this.isOfBaseclass(tpl, x))
&& this.getItemPrice(tpl) > 0
&& !this.itemFilterService.isItemBlacklisted(tpl);
return !itemDetails[1]._props.QuestItem &&
itemDetails[1]._type === "Item" &&
invalidBaseTypes.every((x) => !this.isOfBaseclass(tpl, x)) &&
this.getItemPrice(tpl) > 0 &&
!this.itemFilterService.isItemBlacklisted(tpl);
}
/**
@ -177,7 +177,7 @@ class ItemHelper
if (item.upd === undefined)
{
item.upd = {
StackObjectsCount: 1
StackObjectsCount: 1,
};
}
@ -244,8 +244,8 @@ class ItemHelper
slotId: slotId,
location: 0,
upd: {
StackObjectsCount: count
}
StackObjectsCount: count,
},
};
stackSlotItems.push(stackSlotItem);
}
@ -352,7 +352,6 @@ class ItemHelper
return result;
}
/**
* 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
@ -365,7 +364,9 @@ class ItemHelper
// Edge case, max durability is below durability
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;
}
@ -377,9 +378,9 @@ class ItemHelper
// Weapon
// Get max dura from props, if it isnt there use repairable max dura value
const maxDurability = (itemDetails._props.MaxDurability)
? itemDetails._props.MaxDurability
: repairable.MaxDurability;
const maxDurability = (itemDetails._props.MaxDurability) ?
itemDetails._props.MaxDurability :
repairable.MaxDurability;
const durability = repairable.Durability / maxDurability;
if (!durability)
@ -434,7 +435,7 @@ class ItemHelper
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));
}
@ -455,7 +456,7 @@ class ItemHelper
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 = list.concat(this.findAndReturnChildrenByAssort(itemFromAssort._id, assort));
@ -472,8 +473,10 @@ class ItemHelper
*/
public hasBuyRestrictions(itemToCheck: Item): boolean
{
if (itemToCheck.upd?.BuyRestrictionCurrent !== undefined
&& itemToCheck.upd?.BuyRestrictionMax !== undefined)
if (
itemToCheck.upd?.BuyRestrictionCurrent !== undefined &&
itemToCheck.upd?.BuyRestrictionMax !== undefined
)
{
return true;
}
@ -571,18 +574,18 @@ class ItemHelper
public findBarterItems(by: "tpl" | "id", items: Item[], barterItemId: string): Item[]
{
// find required items to take after buying (handles multiple items)
const barterIDs = typeof barterItemId === "string"
? [barterItemId]
: barterItemId;
const barterIDs = typeof barterItemId === "string" ?
[barterItemId] :
barterItemId;
let barterItems: Item[] = [];
for (const barterID of barterIDs)
{
const filterResult = items.filter(item =>
const filterResult = items.filter((item) =>
{
return by === "tpl"
? (item._tpl === barterID)
: (item._id === barterID);
return by === "tpl" ?
(item._tpl === barterID) :
(item._id === barterID);
});
barterItems = Object.assign(barterItems, filterResult);
@ -615,17 +618,19 @@ class ItemHelper
{
// Insured items shouldn't be renamed
// only works for pmcs.
if (insuredItems?.find(insuredItem => insuredItem.itemId === item._id))
if (insuredItems?.find((insuredItem) => insuredItem.itemId === item._id))
{
continue;
}
// Do not replace important ID's
if (item._id === pmcData.Inventory.equipment
|| item._id === pmcData.Inventory.questRaidItems
|| item._id === pmcData.Inventory.questStashItems
|| item._id === pmcData.Inventory.sortingTable
|| item._id === pmcData.Inventory.stash)
if (
item._id === pmcData.Inventory.equipment ||
item._id === pmcData.Inventory.questRaidItems ||
item._id === pmcData.Inventory.questStashItems ||
item._id === pmcData.Inventory.sortingTable ||
item._id === pmcData.Inventory.stash
)
{
continue;
}
@ -804,7 +809,9 @@ class ItemHelper
let isRequiredSlot = false;
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);
@ -865,7 +872,7 @@ class ItemHelper
*/
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 height = rootTemplate._props.Height;
@ -897,13 +904,15 @@ class ItemHelper
sizeUp = sizeUp < itemTemplate._props.ExtraSizeUp ? itemTemplate._props.ExtraSizeUp : sizeUp;
sizeDown = sizeDown < itemTemplate._props.ExtraSizeDown ? itemTemplate._props.ExtraSizeDown : sizeDown;
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 {
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)
{
const remainingSpace = ammoBoxMaxCartridgeCount - currentStoredCartridgeCount;
const cartridgeCountToAdd = (remainingSpace < maxPerStack)
? remainingSpace
: maxPerStack;
const cartridgeCountToAdd = (remainingSpace < maxPerStack) ?
remainingSpace :
maxPerStack;
// Add cartridge item into items array
ammoBox.push(this.createCartridges(ammoBox[0]._id, cartridgeTpl, cartridgeCountToAdd, location));
currentStoredCartridgeCount += cartridgeCountToAdd;
location ++;
location++;
}
}
@ -967,7 +976,7 @@ class ItemHelper
public itemIsInsideContainer(item: Item, desiredContainerSlotId: string, items: Item[]): boolean
{
// Get items parent
const parent = items.find(x => x._id === item.parentId);
const parent = items.find((x) => x._id === item.parentId);
if (!parent)
{
// No parent, end of line, not inside container
@ -997,7 +1006,7 @@ class ItemHelper
magTemplate: ITemplateItem,
staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
caliber: string = undefined,
minSizePercent = 0.25
minSizePercent = 0.25,
): void
{
// no caliber defined, choose one at random
@ -1028,7 +1037,7 @@ class ItemHelper
magazine: Item[],
magTemplate: ITemplateItem,
cartridgeTpl: string,
minSizePercent = 0.25
minSizePercent = 0.25,
): void
{
// 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
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
let currentStoredCartridgeCount = 0;
@ -1045,9 +1057,9 @@ class ItemHelper
while (currentStoredCartridgeCount < desiredStackCount)
{
// Get stack size of cartridges
let cartridgeCountToAdd = (desiredStackCount <= cartridgeMaxStackSize)
? desiredStackCount
: cartridgeMaxStackSize;
let cartridgeCountToAdd = (desiredStackCount <= cartridgeMaxStackSize) ?
desiredStackCount :
cartridgeMaxStackSize;
// Ensure we don't go over the max stackcount size
const remainingSpace = desiredStackCount - currentStoredCartridgeCount;
@ -1060,7 +1072,7 @@ class ItemHelper
magazine.push(this.createCartridges(magazine[0]._id, cartridgeTpl, cartridgeCountToAdd, location));
currentStoredCartridgeCount += cartridgeCountToAdd;
location ++;
location++;
}
}
@ -1075,11 +1087,11 @@ class ItemHelper
const calibers = [
...new Set(
ammoTpls.filter(
(x: string) => this.getItem(x)[0]
(x: string) => this.getItem(x)[0],
).map(
(x: string) => this.getItem(x)[1]._props.Caliber
)
)
(x: string) => this.getItem(x)[1]._props.Caliber,
),
),
];
return this.randomUtil.drawRandomFromList(calibers)[0];
}
@ -1096,7 +1108,7 @@ class ItemHelper
for (const icd of staticAmmoDist[caliber])
{
ammoArray.push(
new ProbabilityObject(icd.tpl, icd.relativeProbability)
new ProbabilityObject(icd.tpl, icd.relativeProbability),
);
}
return ammoArray.draw(1)[0];
@ -1118,7 +1130,7 @@ class ItemHelper
parentId: parentId,
slotId: "cartridges",
location: location,
upd: { StackObjectsCount: stackCount }
upd: {StackObjectsCount: stackCount},
};
}
@ -1149,7 +1161,9 @@ class ItemHelper
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
{
width: number
height: number
width: number;
height: number;
}
}
export { ItemHelper };
export {ItemHelper};

View File

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

View File

@ -12,11 +12,11 @@ export class NotifierHelper
*/
protected defaultNotification: INotification = {
type: NotificationType.PING,
eventId: "ping"
eventId: "ping",
};
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
* @param dialogueMessage Message from dialog that was sent
* @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 {
type: NotificationType.RAGFAIR_OFFER_SOLD,
eventId: dialogueMessage._id,
dialogId: dialogueMessage.uid,
...ragfairData
...ragfairData,
};
}
/**
* Create a new notification with the specified dialogueMessage object
* @param dialogueMessage
* @returns
* @param dialogueMessage
* @returns
*/
public createNewMessageNotification(dialogueMessage: Message): INotification
{
@ -52,7 +55,7 @@ export class NotifierHelper
type: NotificationType.NEW_MESSAGE,
eventId: dialogueMessage._id,
dialogId: dialogueMessage.uid,
message: dialogueMessage
message: dialogueMessage,
};
}
@ -60,4 +63,4 @@ export class NotifierHelper
{
return `${this.httpServerHelper.getWebsocketUrl()}/notifierServer/getwebsocket/${sessionID}`;
}
}
}

View File

@ -11,7 +11,7 @@ export class PaymentHelper
protected inventoryConfig: IInventoryConfig;
constructor(
@inject("ConfigServer") protected configServer: ConfigServer
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
this.inventoryConfig = this.configServer.getConfig(ConfigTypes.INVENTORY);
@ -24,14 +24,16 @@ export class PaymentHelper
*/
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
* @param {string} currency
* @returns string
*/
* Gets currency TPL from TAG
* @param {string} currency
* @returns string
*/
public getCurrency(currency: string): string
{
switch (currency)
@ -46,4 +48,4 @@ export class PaymentHelper
return "";
}
}
}
}

View File

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

View File

@ -8,9 +8,9 @@ export class ProbabilityHelper
{
constructor(
@inject("WinstonLogger") protected logger: ILogger,
@inject("RandomUtil") protected randomUtil: RandomUtil
@inject("RandomUtil") protected randomUtil: RandomUtil,
)
{ }
{}
/**
* Chance to roll a number out of 100
@ -20,6 +20,6 @@ export class ProbabilityHelper
*/
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("ItemHelper") protected itemHelper: ItemHelper,
@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
@ -41,7 +41,7 @@ export class ProfileHelper
for (const questId in questConditionId)
{
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
const index = profileQuest.completedConditions.indexOf(conditionId);
@ -51,7 +51,7 @@ export class ProfileHelper
profileQuest.completedConditions.splice(index, 1);
}
}
}
}
/**
* Get all profiles from server
@ -97,7 +97,12 @@ export class ProfileHelper
* @param scavProfile post-raid scav profile
* @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 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
* @param nicknameRequest
* @param nicknameRequest
* @param sessionID Session id
* @returns True if already used
*/
@ -133,8 +138,10 @@ export class ProfileHelper
continue;
}
if (!this.sessionIdMatchesProfileId(profile.info.id, sessionID)
&& this.nicknameMatches(profile.characters.pmc.Info.LowerNickname, nicknameRequest.nickname))
if (
!this.sessionIdMatchesProfileId(profile.info.id, sessionID) &&
this.nicknameMatches(profile.characters.pmc.Info.LowerNickname, nicknameRequest.nickname)
)
{
return true;
}
@ -211,7 +218,7 @@ export class ProfileHelper
public getDefaultAkiDataObject(): any
{
return {
version: this.getServerVersion()
version: this.getServerVersion(),
};
}
@ -221,10 +228,10 @@ export class ProfileHelper
{
return undefined;
}
return this.saveServer.getProfile(sessionID);
}
public getPmcProfile(sessionID: string): IPmcData
{
const fullProfile = this.getFullProfile(sessionID);
@ -232,10 +239,10 @@ export class ProfileHelper
{
return undefined;
}
return this.saveServer.getProfile(sessionID).characters.pmc;
}
public getScavProfile(sessionID: string): IPmcData
{
return this.saveServer.getProfile(sessionID).characters.scav;
@ -253,24 +260,24 @@ export class ProfileHelper
DamageHistory: {
LethalDamagePart: "Head",
LethalDamage: undefined,
BodyParts: <any>[]
BodyParts: <any>[],
},
DroppedItems: [],
ExperienceBonusMult: 0,
FoundInRaidItems: [],
LastPlayerState: undefined,
LastSessionDate: 0,
OverallCounters: { Items: [] },
SessionCounters: { Items: [] },
OverallCounters: {Items: []},
SessionCounters: {Items: []},
SessionExperienceMult: 0,
SurvivorClass: "Unknown",
TotalInGameTime: 0,
TotalSessionExperience: 0,
Victims: []
}
Victims: [],
},
};
}
protected isWiped(sessionID: string): boolean
{
return this.saveServer.getProfile(sessionID).info.wipe;
@ -289,14 +296,17 @@ export class ProfileHelper
public removeSecureContainer(profile: IPmcData): IPmcData
{
const items = profile.Inventory.items;
const secureContainer = items.find(x => x.slotId === "SecuredContainer");
const secureContainer = items.find((x) => x.slotId === "SecuredContainer");
if (secureContainer)
{
// 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
profile.Inventory.items = items.filter(x => !childItemsInSecureContainer.includes(x._id));
profile.Inventory.items = items.filter((x) => !childItemsInSecureContainer.includes(x._id));
}
return profile;
@ -340,7 +350,7 @@ export class ProfileHelper
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
{
const stat = counters.find(x => x.Key.includes(keyToIncrement));
const stat = counters.find((x) => x.Key.includes(keyToIncrement));
if (stat)
{
stat.Value++;
@ -371,7 +381,7 @@ export class ProfileHelper
return false;
}
const profileSkill = profileSkills.find(x => x.Id === skillType);
const profileSkill = profileSkills.find((x) => x.Id === skillType);
if (!profileSkill)
{
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 pmcProfile Player profile with skill
* @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)
{
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;
}
@ -405,7 +422,7 @@ export class ProfileHelper
return;
}
const profileSkill = profileSkills.find(x => x.Id === skill);
const profileSkill = profileSkills.find((x) => x.Id === skill);
if (!profileSkill)
{
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
{
const skillToReturn = pmcData.Skills.Common.find(x => x.Id === skill);
const skillToReturn = pmcData.Skills.Common.find((x) => x.Id === skill);
if (!skillToReturn)
{
this.logger.warning(`Profile ${pmcData.sessionId} does not have a skill named: ${skill}`);
@ -435,4 +452,4 @@ export class ProfileHelper
return skillToReturn;
}
}
}

View File

@ -1,4 +1,3 @@
import { injectable } from "tsyringe";
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()
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);
}
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);
}
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);
}
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);
}
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)
{
@ -43,4 +58,4 @@ export class QuestConditionHelper
return filteredQuests;
}
}
}

View File

@ -53,25 +53,25 @@ export class QuestHelper
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("TraderHelper") protected traderHelper: TraderHelper,
@inject("MailSendService") protected mailSendService: MailSendService,
@inject("ConfigServer") protected configServer: ConfigServer
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST);
}
/**
* Get status of a quest in player profile by its id
* @param pmcData Profile to search
* @param questId Quest id to look up
* @returns QuestStatus enum
*/
* Get status of a quest in player profile by its id
* @param pmcData Profile to search
* @param questId Quest id to look up
* @returns QuestStatus enum
*/
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
? quest.status
: QuestStatus.Locked;
return quest ?
quest.status :
QuestStatus.Locked;
}
/**
@ -97,7 +97,12 @@ export class QuestHelper
case "=":
return playerLevel === <number>condition._props.value;
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;
}
}
@ -181,7 +186,7 @@ export class QuestHelper
/**
* Get quest name by quest id
* @param questId id to get
* @returns
* @returns
*/
public getQuestNameFromLocale(questId: string): string
{
@ -189,7 +194,6 @@ export class QuestHelper
return this.localeService.getLocaleDb()[questNameKey];
}
/**
* Check if trader has sufficient loyalty to fulfill quest requirement
* @param questProperties Quest props
@ -242,7 +246,7 @@ export class QuestHelper
return current !== required;
case "==":
return current === required;
default:
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
if (item._id === reward.target)
{
if ((item.parentId !== undefined) && (item.parentId === "hideout")
&& (item.upd !== undefined) && (item.upd.StackObjectsCount !== undefined)
&& (item.upd.StackObjectsCount > 1))
if (
(item.parentId !== undefined) && (item.parentId === "hideout") &&
(item.upd !== undefined) && (item.upd.StackObjectsCount !== undefined) &&
(item.upd.StackObjectsCount > 1)
)
{
item.upd.StackObjectsCount = 1;
}
@ -307,7 +313,7 @@ export class QuestHelper
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;
@ -323,9 +329,11 @@ export class QuestHelper
{
// Iterate over all rewards with the desired status, flatten out items that have a type of Item
const questRewards = quest.rewards[QuestStatus[status]]
.flatMap((reward: Reward) => reward.type === "Item"
? this.processReward(reward)
: []);
.flatMap((reward: Reward) =>
reward.type === "Item" ?
this.processReward(reward) :
[]
);
return questRewards;
}
@ -336,9 +344,13 @@ export class QuestHelper
* @param newState State the new quest should be in when returned
* @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)
{
// Quest exists, update its status
@ -360,12 +372,12 @@ export class QuestHelper
qid: acceptedQuest.qid,
startTime: this.timeUtil.getTimestamp(),
status: newState,
statusTimers: {}
statusTimers: {},
};
// Check if quest has a prereq to be placed in a 'pending' state
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")
{
// Quest should be put into 'pending' state
@ -392,18 +404,18 @@ export class QuestHelper
{
// Get quest acceptance data from profile
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) =>
{
// 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
const acceptedQuestCondition = quest.conditions.AvailableForStart.find(x =>
const acceptedQuestCondition = quest.conditions.AvailableForStart.find((x) =>
{
return x._parent === "Quest"
&& x._props.target === startedQuestId
&& x._props.status[0] === QuestStatus.Started;
return x._parent === "Quest" &&
x._props.target === startedQuestId &&
x._props.status[0] === QuestStatus.Started;
});
// Not found, skip quest
@ -412,7 +424,9 @@ export class QuestHelper
return false;
}
const standingRequirements = this.questConditionHelper.getStandingConditions(quest.conditions.AvailableForStart);
const standingRequirements = this.questConditionHelper.getStandingConditions(
quest.conditions.AvailableForStart,
);
for (const condition of standingRequirements)
{
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)
{
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
return startedQuestInProfile && ([QuestStatus.Started, QuestStatus.AvailableForFinish].includes(startedQuestInProfile.status));
return startedQuestInProfile &&
([QuestStatus.Started, QuestStatus.AvailableForFinish].includes(startedQuestInProfile.status));
});
return this.getQuestsWithOnlyLevelRequirementStartCondition(eligibleQuests);
@ -446,17 +463,18 @@ export class QuestHelper
public failedUnlocked(failedQuestId: string, sessionId: string): IQuest[]
{
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 acceptedQuestCondition = q.conditions.AvailableForStart.find(
c =>
(c) =>
{
return c._parent === "Quest"
&& c._props.target === failedQuestId
&& c._props.status[0] === QuestStatus.Fail;
});
return c._parent === "Quest" &&
c._props.target === failedQuestId &&
c._props.status[0] === QuestStatus.Fail;
},
);
if (!acceptedQuestCondition)
{
@ -490,7 +508,9 @@ export class QuestHelper
{
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 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)
{
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
// 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);
}
}
@ -543,7 +569,11 @@ export class QuestHelper
* @param sessionId Session id
* @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({
_id: item._id,
@ -552,8 +582,8 @@ export class QuestHelper
slotId: item.slotId,
location: item.location,
upd: {
StackObjectsCount: item.upd.StackObjectsCount
}
StackObjectsCount: item.upd.StackObjectsCount,
},
});
}
@ -580,7 +610,7 @@ export class QuestHelper
public getQuestWithOnlyLevelRequirementStartCondition(quest: IQuest): IQuest
{
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;
}
@ -593,7 +623,12 @@ export class QuestHelper
* @param output Client output
* @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
if (!output)
@ -613,7 +648,7 @@ export class QuestHelper
MessageType.QUEST_FAIL,
quest.failMessageText,
questRewards,
this.timeUtil.getHoursAsSeconds(this.questConfig.redeemTime)
this.timeUtil.getHoursAsSeconds(this.questConfig.redeemTime),
);
output.profileChanges[sessionID].quests.push(this.failedUnlocked(failRequest.qid, sessionID));
@ -647,7 +682,7 @@ export class QuestHelper
// Check daily/weekly objects
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)
{
break;
@ -668,7 +703,10 @@ export class QuestHelper
{
// blank or is a guid, use description instead
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;
}
@ -696,7 +734,7 @@ export class QuestHelper
public updateQuestState(pmcData: IPmcData, newQuestState: QuestStatus, questId: string): void
{
// 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)
{
questToUpdate.status = newQuestState;
@ -713,8 +751,14 @@ export class QuestHelper
* @param questResponse Response to send back to client
* @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);
if (!questDetails)
{
@ -722,7 +766,7 @@ export class QuestHelper
return [];
}
// Check for and apply intel center money bonus if it exists
const questMoneyRewardBonus = this.getQuestMoneyRewardBonus(pmcData);
if (questMoneyRewardBonus > 0)
@ -738,7 +782,11 @@ export class QuestHelper
switch (reward.type)
{
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;
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
@ -759,10 +807,22 @@ export class QuestHelper
this.logger.debug("Not implemented stash rows reward yet");
break;
case QuestRewardType.PRODUCTIONS_SCHEME:
this.findAndAddHideoutProductionIdToProfile(pmcData, reward, questDetails, sessionId, questResponse);
this.findAndAddHideoutProductionIdToProfile(
pmcData,
reward,
questDetails,
sessionId,
questResponse,
);
break;
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;
}
}
@ -779,19 +839,31 @@ export class QuestHelper
* @param sessionID Session id
* @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
const hideoutProductions = this.databaseServer.getTables().hideout.production;
const matchingProductions = hideoutProductions.filter(x =>
x.areaType === Number.parseInt(craftUnlockReward.traderId)
&& x.requirements.some(x => x.requiredLevel === craftUnlockReward.loyaltyLevel)
&& x.endProduct === craftUnlockReward.items[0]._tpl);
const matchingProductions = hideoutProductions.filter((x) =>
x.areaType === Number.parseInt(craftUnlockReward.traderId) &&
x.requirements.some((x) => x.requiredLevel === craftUnlockReward.loyaltyLevel) &&
x.endProduct === craftUnlockReward.items[0]._tpl
);
// More/less than 1 match, above filtering wasn't strict enough
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;
}
@ -810,7 +882,7 @@ export class QuestHelper
protected getQuestMoneyRewardBonus(pmcData: IPmcData): number
{
// 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)
{
return 0;
@ -823,7 +895,7 @@ export class QuestHelper
const hideoutManagementSkill = this.profileHelper.getSkillFromProfile(pmcData, SkillTypes.HIDEOUT_MANAGEMENT);
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;
@ -835,19 +907,27 @@ export class QuestHelper
* @param questIds Quests to search through for the findItem condition
* @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> = {};
for (const questId of questIds)
{
const questInDb = allQuests.find(x => x._id === questId);
const questInDb = allQuests.find((x) => x._id === questId);
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;
}
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)
{
result[questId] = condition._props.id;
@ -872,7 +952,7 @@ export class QuestHelper
{
// Quest from db matches quests in profile, skip
const questData = quests[questKey];
if (pmcProfile.Quests.find(x => x.qid === questData._id))
if (pmcProfile.Quests.find((x) => x.qid === questData._id))
{
continue;
}
@ -889,13 +969,13 @@ export class QuestHelper
status: statuses[statuses.length - 1],
statusTimers: statusesDict,
completedConditions: [],
availableAfter: 0
availableAfter: 0,
};
if (pmcProfile.Quests.some(x => x.qid === questKey))
if (pmcProfile.Quests.some((x) => x.qid === questKey))
{
// 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.statusTimers = questRecordToAdd.statusTimers;
}
@ -909,10 +989,10 @@ export class QuestHelper
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)
{
quests.splice(quests.indexOf(pmcQuestToReplaceStatus, 1));
}
}
}
}

View File

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

View File

@ -60,7 +60,7 @@ export class RagfairOfferHelper
@inject("LocaleService") protected localeService: LocaleService,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("MailSendService") protected mailSendService: MailSendService,
@inject("ConfigServer") protected configServer: ConfigServer
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
@ -75,9 +75,16 @@ export class RagfairOfferHelper
* @param pmcProfile Player profile
* @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
* @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 offers: IRagfairOffer[] = [];
@ -137,13 +149,13 @@ export class RagfairOfferHelper
if (possibleOffers.length > 1)
{
const lockedOffers = this.getLoyaltyLockedOffers(possibleOffers, pmcProfile);
// 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)
{
possibleOffers = availableOffers;
}
}
}
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
{
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
* @returns true if out of stock
*/
protected traderOutOfStock(offer: IRagfairOffer): boolean
protected traderOutOfStock(offer: IRagfairOffer): boolean
{
if (offer?.items?.length === 0)
{
@ -200,17 +214,21 @@ export class RagfairOfferHelper
protected traderBuyRestrictionReached(offer: IRagfairOffer): boolean
{
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
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;
}
// 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)
{
return false;
@ -279,7 +297,10 @@ export class RagfairOfferHelper
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);
offer.sellResult.splice(0, 1);
@ -331,7 +352,7 @@ export class RagfairOfferHelper
protected deleteOfferById(sessionID: string, offerId: string): void
{
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);
// Also delete from ragfair
this.ragfairOfferService.removeOfferById(offerId);
@ -357,7 +378,7 @@ export class RagfairOfferHelper
else
{
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);
let removeCount = boughtAmount;
@ -384,11 +405,12 @@ export class RagfairOfferHelper
while (foundNewItems)
{
foundNewItems = false;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
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)
{
foundNewItems = true;
@ -399,7 +421,7 @@ export class RagfairOfferHelper
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 = {
_id: this.hashUtil.generate(),
_tpl: requirement._tpl,
upd: { StackObjectsCount: requirement.count * boughtAmount }
upd: {StackObjectsCount: requirement.count * boughtAmount},
};
const stacks = this.itemHelper.splitStack(requestedItem);
@ -436,7 +458,7 @@ export class RagfairOfferHelper
const ragfairDetails = {
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
handbookId: itemTpl
handbookId: itemTpl,
};
this.mailSendService.sendDirectNpcMessageToPlayer(
@ -447,7 +469,8 @@ export class RagfairOfferHelper
itemsToSend,
this.timeUtil.getHoursAsSeconds(this.questConfig.redeemTime),
null,
ragfairDetails);
ragfairDetails,
);
return this.eventOutputHolder.getOutput(sessionID);
}
@ -465,14 +488,19 @@ export class RagfairOfferHelper
const soldMessageLocaleGuid = globalLocales[RagfairOfferHelper.goodSoldTemplate];
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
const tplVars: ISystemData = {
soldItem: globalLocales[`${itemTpl} Name`] || itemTpl,
buyerNickname: this.ragfairServerHelper.getNickname(this.hashUtil.generate()),
itemCount: boughtAmount
itemCount: boughtAmount,
};
const offerSoldMessageText = soldMessageLocaleGuid.replace(/{\w+}/g, (matched) =>
@ -492,14 +520,23 @@ export class RagfairOfferHelper
* @param pmcProfile Player profile
* @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 money = offer.requirements[0]._tpl;
const isTraderOffer = offer.user.memberType === MemberCategory.TRADER;
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
return false;
@ -512,7 +549,7 @@ export class RagfairOfferHelper
}
// 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;
}
@ -559,7 +596,10 @@ export class RagfairOfferHelper
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;
}
@ -612,10 +652,12 @@ export class RagfairOfferHelper
return false;
}
if (!traderAssorts[offer.user.id].items.find((item) =>
{
return item._id === offer.root;
}))
if (
!traderAssorts[offer.user.id].items.find((item) =>
{
return item._id === offer.root;
})
)
{
// skip (quest) locked items
return false;
@ -649,4 +691,4 @@ export class RagfairOfferHelper
return true;
}
}
}

View File

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

View File

@ -48,7 +48,7 @@ export class RagfairServerHelper
@inject("JsonUtil") protected jsonUtil: JsonUtil,
@inject("MailSendService") protected mailSendService: MailSendService,
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
@inject("ConfigServer") protected configServer: ConfigServer
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
@ -57,7 +57,7 @@ export class RagfairServerHelper
/**
* Is item valid / on blacklist / quest item
* @param itemDetails
* @param itemDetails
* @returns boolean
*/
public isItemValidRagfairItem(itemDetails: [boolean, ITemplateItem]): boolean
@ -95,7 +95,10 @@ export class RagfairServerHelper
}
// 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;
}
@ -120,7 +123,7 @@ export class RagfairServerHelper
/**
* is supplied id a trader
* @param traderId
* @param traderId
* @returns True if id was a trader
*/
public isTrader(traderId: string): boolean
@ -155,7 +158,7 @@ export class RagfairServerHelper
MessageType.MESSAGE_WITH_ITEMS,
RagfairServerHelper.goodsReturnedTemplate,
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
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;
}
@ -185,7 +191,9 @@ export class RagfairServerHelper
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);
}
@ -263,7 +271,9 @@ export class RagfairServerHelper
{
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));
}
}
@ -274,7 +284,7 @@ export class RagfairServerHelper
/**
* Generate new unique ids for the children while preserving hierarchy
* @param item base item
* @param preset
* @param preset
* @returns Item array with new IDs
*/
public reparentPresets(item: Item, preset: Item[]): Item[]
@ -296,11 +306,11 @@ export class RagfairServerHelper
idMappings[mod.parentId] = this.hashUtil.generate();
}
mod._id = idMappings[mod._id];
mod._id = idMappings[mod._id];
if (mod.parentId !== undefined)
{
mod.parentId = idMappings[mod.parentId];
mod.parentId = idMappings[mod.parentId];
}
}
@ -309,4 +319,4 @@ export class RagfairServerHelper
return preset;
}
}
}

View File

@ -10,15 +10,15 @@ export class RagfairSortHelper
{
constructor(
@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)
* @param offers Offers to sort
* @param type How to sort it
* @param direction Ascending/descending
* @param direction Ascending/descending
* @returns Sorted offers
*/
public sortOffers(offers: IRagfairOffer[], type: RagfairSort, direction = 0): IRagfairOffer[]
@ -75,16 +75,18 @@ export class RagfairSortHelper
const nameA = locale[`${tplA} Name`] || tplA;
const nameB = locale[`${tplB} Name`] || tplB;
return (nameA < nameB)
? -1
: (nameA > nameB) ? 1 : 0;
return (nameA < nameB) ?
-1 :
(nameA > nameB) ?
1 :
0;
}
/**
* Order two offers by rouble price value
* @param a Offer a
* @param b Offer b
* @returns
* @returns
*/
protected sortOffersByPrice(a: IRagfairOffer, b: IRagfairOffer): number
{
@ -95,4 +97,4 @@ export class RagfairSortHelper
{
return a.endTime - b.endTime;
}
}
}

View File

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

View File

@ -15,7 +15,7 @@ export class RepeatableQuestHelper
constructor(
@inject("MathUtil") protected mathUtil: MathUtil,
@inject("JsonUtil") protected jsonUtil: JsonUtil,
@inject("ConfigServer") protected configServer: ConfigServer
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST);
@ -27,9 +27,14 @@ export class RepeatableQuestHelper
* @param repeatableConfig Main repeatable config
* @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>
@ -38,8 +43,10 @@ export class RepeatableQuestHelper
const probabilityArray = new ProbabilityObjectArray<K, V>(this.mathUtil, this.jsonUtil);
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;
}
}
}

View File

@ -5,24 +5,23 @@ import { Item } from "@spt-aki/models/eft/common/tables/IItem";
export interface OwnerInventoryItems
{
from: Item[]
to: Item[]
sameInventory: boolean,
isMail: boolean
from: Item[];
to: Item[];
sameInventory: boolean;
isMail: boolean;
}
@injectable()
export class SecureContainerHelper
{
constructor(
@inject("ItemHelper") protected itemHelper: ItemHelper
@inject("ItemHelper") protected itemHelper: ItemHelper,
)
{ }
{}
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
if (!secureContainer)
@ -33,6 +32,6 @@ export class SecureContainerHelper
const itemsInSecureContainer = this.itemHelper.findAndReturnChildrenByItems(items, secureContainer._id);
// 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("InventoryHelper") protected inventoryHelper: InventoryHelper,
@inject("RagfairServer") protected ragfairServer: RagfairServer,
@inject("ConfigServer") protected configServer: ConfigServer
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
@ -47,9 +47,15 @@ export class TradeHelper
* @param sessionID Session id
* @param foundInRaid Should item be found in raid
* @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);
@ -58,10 +64,10 @@ export class TradeHelper
{
// eslint-disable-next-line @typescript-eslint/naming-convention
item_id: buyRequestData.item_id,
count: buyRequestData.count
}
count: buyRequestData.count,
},
],
tid: buyRequestData.tid
tid: buyRequestData.tid,
};
const callback = () =>
@ -71,13 +77,13 @@ export class TradeHelper
if (isRagfair)
{
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];
}
else
{
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
@ -130,7 +136,12 @@ export class TradeHelper
* @param sessionID Session id
* @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);
@ -140,7 +151,7 @@ export class TradeHelper
const itemIdToFind = itemToBeRemoved.id.replace(/\s+/g, ""); // Strip out whitespace
// 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)
{
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)
{
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>> = {
started: {},
success: {},
fail: {}
fail: {},
};
protected createdMergedQuestAssorts = false;
@ -46,10 +46,11 @@ export class TraderAssortHelper
@inject("RagfairOfferGenerator") protected ragfairOfferGenerator: RagfairOfferGenerator,
@inject("TraderAssortService") protected traderAssortService: TraderAssortService,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("TraderPurchasePersisterService") protected traderPurchasePersisterService: TraderPurchasePersisterService,
@inject("TraderPurchasePersisterService") protected traderPurchasePersisterService:
TraderPurchasePersisterService,
@inject("TraderHelper") protected traderHelper: TraderHelper,
@inject("FenceService") protected fenceService: FenceService,
@inject("ConfigServer") protected configServer: ConfigServer
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
@ -81,7 +82,7 @@ export class TraderAssortHelper
// Strip assorts player should not see yet
if (!flea)
{
{
trader.assort = this.assortHelper.stripLockedLoyaltyAssort(pmcProfile, traderId, trader.assort);
}
@ -89,21 +90,28 @@ export class TraderAssortHelper
trader.assort.nextResupply = trader.base.nextResupply;
// 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)
{
// 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)
{
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;
}
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;
}
@ -117,7 +125,13 @@ export class TraderAssortHelper
this.hydrateMergedQuestAssorts();
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
if (this.traderConfig.traderPriceMultipler !== 1)
@ -234,7 +248,7 @@ export class TraderAssortHelper
barter_scheme: {},
// eslint-disable-next-line @typescript-eslint/naming-convention
loyal_level_items: {},
nextResupply: null
nextResupply: null,
};
}
}
}

View File

@ -42,14 +42,14 @@ export class TraderHelper
@inject("FenceService") protected fenceService: FenceService,
@inject("TimeUtil") protected timeUtil: TimeUtil,
@inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("ConfigServer") protected configServer: ConfigServer
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
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
* @param traderID Traders Id to get
* @param sessionID Players id
@ -62,7 +62,7 @@ export class TraderHelper
{
this.logger.error(`No profile with sessionId: ${sessionID}`);
}
// Profile has traderInfo dict (profile beyond creation stage) but no requested trader in profile
if (pmcData.TradersInfo && !(traderID in pmcData.TradersInfo))
{
@ -70,7 +70,7 @@ export class TraderHelper
this.resetTrader(sessionID, traderID);
this.lvlUp(traderID, pmcData);
}
const trader = this.databaseServer.getTables().traders?.[traderID]?.base;
if (!trader)
{
@ -87,9 +87,9 @@ export class TraderHelper
*/
public getTraderAssortsByTraderId(traderId: string): ITraderAssort
{
return traderId === Traders.FENCE
? this.fenceService.getRawFenceAssorts()
: this.databaseServer.getTables().traders[traderId].assort;
return traderId === Traders.FENCE ?
this.fenceService.getRawFenceAssorts() :
this.databaseServer.getTables().traders[traderId].assort;
}
/**
@ -109,7 +109,7 @@ export class TraderHelper
}
// 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)
{
this.logger.debug(`No assort ${assortId} on trader: ${traderId} found`);
@ -130,15 +130,17 @@ export class TraderHelper
{
const account = this.saveServer.getProfile(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] = {
disabled: false,
loyaltyLevel: rawProfileTemplate.initialLoyaltyLevel,
salesSum: rawProfileTemplate.initialSalesSum,
standing: this.getStartingStanding(traderID, rawProfileTemplate),
standing: this.getStartingStanding(traderID, rawProfileTemplate),
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)
@ -197,9 +199,9 @@ export class TraderHelper
{
const newStanding = currentStanding + standingToAdd;
return newStanding < 0
? 0
: newStanding;
return newStanding < 0 ?
0 :
newStanding;
}
/**
@ -224,10 +226,12 @@ export class TraderHelper
{
const loyalty = loyaltyLevels[level];
if ((loyalty.minLevel <= pmcData.Info.Level
&& loyalty.minSalesSum <= pmcData.TradersInfo[traderID].salesSum
&& loyalty.minStanding <= pmcData.TradersInfo[traderID].standing)
&& targetLevel < 4)
if (
(loyalty.minLevel <= pmcData.Info.Level &&
loyalty.minSalesSum <= pmcData.TradersInfo[traderID].salesSum &&
loyalty.minStanding <= pmcData.TradersInfo[traderID].standing) &&
targetLevel < 4
)
{
// level reached
targetLevel++;
@ -257,15 +261,20 @@ export class TraderHelper
*/
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)
{
this.logger.warning(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
this.logger.warning(
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,
seconds: this.traderConfig.updateTimeDefault
}
seconds: this.traderConfig.updateTimeDefault,
},
);
}
else
@ -298,7 +307,10 @@ export class TraderHelper
* @param newPurchaseDetails New item assort id + count
*/
// 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 traderId = newPurchaseDetails.tid;
@ -320,10 +332,9 @@ export class TraderHelper
const currentTime = this.timeUtil.getTimestamp();
if (!profile.traderPurchases[traderId][purchasedItem.item_id])
{
profile.traderPurchases[traderId][purchasedItem.item_id] =
{
profile.traderPurchases[traderId][purchasedItem.item_id] = {
count: purchasedItem.count,
purchaseTimestamp: currentTime
purchaseTimestamp: currentTime,
};
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)
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)
const barterScheme = traderAssorts.barter_scheme[item._id][0][0];
// Convert into roubles
const roubleAmount = barterScheme._tpl === Money.ROUBLES
? barterScheme.count
: this.handbookHelper.inRUB(barterScheme.count, barterScheme._tpl);
const roubleAmount = barterScheme._tpl === Money.ROUBLES ?
barterScheme.count :
this.handbookHelper.inRUB(barterScheme.count, barterScheme._tpl);
// Existing price smaller in dict than current iteration, overwrite
if (this.highestTraderPriceItems[item._tpl] ?? 0 < roubleAmount)
@ -409,7 +420,6 @@ export class TraderHelper
return this.highestTraderBuyPriceItems[tpl];
}
// Find highest trader price for item
for (const traderName in Traders)
{
@ -423,7 +433,9 @@ export class TraderHelper
const traderBuyBackPricePercent = relevantLoyaltyData.buy_price_coef;
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
if (!this.highestTraderBuyPriceItems[tpl])
@ -449,7 +461,7 @@ export class TraderHelper
*/
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)
{
@ -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
* same enum value, effectively serving as a trader ID; otherwise, it logs an error and returns an empty string.
* 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.
* This method provides a runtime check to prevent undefined behavior when using the enum as a dictionary key.
*
*
* For example, instead of this:
* `const traderId = Traders[Traders.PRAPOR];`
*
*
* You can use safely use this:
* `const traderId = this.traderHelper.getValidTraderIdByEnumValue(Traders.PRAPOR);`
*
*
* @param traderEnumValue The trader enum value to validate
* @returns The validated trader enum value as a string, or an empty string if invalid
*/
@ -483,7 +495,7 @@ export class TraderHelper
return "";
}
return Traders[traderEnumValue];
}
@ -506,4 +518,4 @@ export class TraderHelper
{
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[]
{
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
* @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 weights = Object.values(itemArray);
@ -18,7 +18,7 @@ export class WeightedRandomHelper
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 weights = Object.values(itemArray);
@ -41,7 +41,7 @@ export class WeightedRandomHelper
* @param {number[]} weights
* @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)
{
@ -79,9 +79,9 @@ export class WeightedRandomHelper
{
return {
item: items[itemIndex],
index: itemIndex
index: itemIndex,
};
}
}
}
}
}