Merge branch 'master' of https://dev.sp-tarkov.com/SPT-AKI/Server into 3.8.0

This commit is contained in:
Dev 2023-11-07 20:58:56 +00:00
commit 123df5c595
6 changed files with 85 additions and 36 deletions

View File

@ -18,6 +18,7 @@ import { IServerDetails } from "@spt-aki/models/eft/game/IServerDetails";
import { IAkiProfile } from "@spt-aki/models/eft/profile/IAkiProfile"; import { IAkiProfile } from "@spt-aki/models/eft/profile/IAkiProfile";
import { AccountTypes } from "@spt-aki/models/enums/AccountTypes"; import { AccountTypes } from "@spt-aki/models/enums/AccountTypes";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { SkillTypes } from "@spt-aki/models/enums/SkillTypes";
import { Traders } from "@spt-aki/models/enums/Traders"; import { Traders } from "@spt-aki/models/enums/Traders";
import { ICoreConfig } from "@spt-aki/models/spt/config/ICoreConfig"; import { ICoreConfig } from "@spt-aki/models/spt/config/ICoreConfig";
import { IHttpConfig } from "@spt-aki/models/spt/config/IHttpConfig"; import { IHttpConfig } from "@spt-aki/models/spt/config/IHttpConfig";
@ -118,6 +119,8 @@ export class GameController
this.adjustLooseLootSpawnProbabilities(); this.adjustLooseLootSpawnProbabilities();
this.checkTraderRepairValuesExist();
// repeatableQuests are stored by in profile.Quests due to the responses of the client (e.g. Quests in offraidData) // repeatableQuests are stored by in profile.Quests due to the responses of the client (e.g. Quests in offraidData)
// Since we don't want to clutter the Quests list, we need to remove all completed (failed / successful) repeatable quests. // Since we don't want to clutter the Quests list, we need to remove all completed (failed / successful) repeatable quests.
// We also have to remove the Counters from the repeatableQuests // We also have to remove the Counters from the repeatableQuests
@ -227,6 +230,30 @@ export class GameController
} }
} }
/**
* Out of date/incorrectly made trader mods forget this data
*/
protected checkTraderRepairValuesExist(): void
{
for (const traderKey in this.databaseServer.getTables().traders)
{
const trader = this.databaseServer.getTables().traders[traderKey];
if (!trader?.base?.repair)
{
this.logger.warning(`Trader ${trader.base._id} ${trader.base.name} is missing a repair object, adding in default values`);
trader.base.repair = this.jsonUtil.clone(this.databaseServer.getTables().traders.ragfair.base.repair);
return;
}
if (trader?.base?.repair?.quality)
{
this.logger.warning(`Trader ${trader.base._id} ${trader.base.name} is missing a repair quality value, adding in default value`);
trader.base.repair.quality = this.jsonUtil.clone(this.databaseServer.getTables().traders.ragfair.base.repair.quality);
}
}
}
protected addCustomLooseLootPositions(): void protected addCustomLooseLootPositions(): void
{ {
const looseLootPositionsToAdd = this.lootConfig.looseLoot; const looseLootPositionsToAdd = this.lootConfig.looseLoot;
@ -472,7 +499,7 @@ export class GameController
*/ */
protected warnOnActiveBotReloadSkill(pmcProfile: IPmcData): void protected warnOnActiveBotReloadSkill(pmcProfile: IPmcData): void
{ {
const botReloadSkill = pmcProfile.Skills.Common.find(x => x.Id === "BotReload"); const botReloadSkill = this.profileHelper.getSkillFromProfile(pmcProfile, SkillTypes.BOT_RELOAD);
if (botReloadSkill?.Progress > 0) if (botReloadSkill?.Progress > 0)
{ {
this.logger.warning(this.localisationService.getText("server_start_player_active_botreload_skill")); this.logger.warning(this.localisationService.getText("server_start_player_active_botreload_skill"));

View File

@ -3,7 +3,7 @@ import { inject, injectable } from "tsyringe";
import { InventoryHelper } from "@spt-aki/helpers/InventoryHelper"; import { InventoryHelper } from "@spt-aki/helpers/InventoryHelper";
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper"; import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData"; import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
import { Common, HideoutArea, IHideoutImprovement, Production, Productive } from "@spt-aki/models/eft/common/tables/IBotBase"; import { HideoutArea, IHideoutImprovement, Production, Productive } from "@spt-aki/models/eft/common/tables/IBotBase";
import { Upd } from "@spt-aki/models/eft/common/tables/IItem"; import { Upd } from "@spt-aki/models/eft/common/tables/IItem";
import { StageBonus } from "@spt-aki/models/eft/hideout/IHideoutArea"; import { StageBonus } from "@spt-aki/models/eft/hideout/IHideoutArea";
import { IHideoutContinuousProductionStartRequestData } from "@spt-aki/models/eft/hideout/IHideoutContinuousProductionStartRequestData"; import { IHideoutContinuousProductionStartRequestData } from "@spt-aki/models/eft/hideout/IHideoutContinuousProductionStartRequestData";
@ -800,16 +800,6 @@ export class HideoutHelper
return productionSlots + (hasManagementSkillSlots ? managementSlotsCount : 0); return productionSlots + (hasManagementSkillSlots ? managementSlotsCount : 0);
} }
/**
* Does profile have elite hideout management skill
* @param pmcData Profile to look at
* @returns True if profile has skill
*/
protected hasEliteHideoutManagementSkill(pmcData: IPmcData): boolean
{
return this.getHideoutManagementSkill(pmcData)?.Progress >= 5100; // level 51+
}
/** /**
* Get a count of bitcoins player miner can hold * Get a count of bitcoins player miner can hold
*/ */
@ -818,16 +808,6 @@ export class HideoutHelper
return this.databaseServer.getTables().globals.config.SkillsSettings.HideoutManagement.EliteSlots.BitcoinFarm.Container; return this.databaseServer.getTables().globals.config.SkillsSettings.HideoutManagement.EliteSlots.BitcoinFarm.Container;
} }
/**
* Get the hideout management skill from player profile
* @param pmcData Profile to look at
* @returns Hideout management skill object
*/
protected getHideoutManagementSkill(pmcData: IPmcData): Common
{
return pmcData.Skills.Common.find(x => x.Id === SkillTypes.HIDEOUT_MANAGEMENT);
}
/** /**
* HideoutManagement skill gives a consumption bonus the higher the level * HideoutManagement skill gives a consumption bonus the higher the level
* 0.5% per level per 1-51, (25.5% at max) * 0.5% per level per 1-51, (25.5% at max)
@ -836,7 +816,7 @@ export class HideoutHelper
*/ */
protected getHideoutManagementConsumptionBonus(pmcData: IPmcData): number protected getHideoutManagementConsumptionBonus(pmcData: IPmcData): number
{ {
const hideoutManagementSkill = this.getHideoutManagementSkill(pmcData); const hideoutManagementSkill = this.profileHelper.getSkillFromProfile(pmcData, SkillTypes.HIDEOUT_MANAGEMENT);
if (!hideoutManagementSkill) if (!hideoutManagementSkill)
{ {
return 0; return 0;

View File

@ -2,7 +2,7 @@ import { inject, injectable } from "tsyringe";
import { ItemHelper } from "@spt-aki/helpers/ItemHelper"; import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData"; import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
import { CounterKeyValue, Stats } from "@spt-aki/models/eft/common/tables/IBotBase"; import { Common, CounterKeyValue, Stats } from "@spt-aki/models/eft/common/tables/IBotBase";
import { IAkiProfile } from "@spt-aki/models/eft/profile/IAkiProfile"; import { IAkiProfile } from "@spt-aki/models/eft/profile/IAkiProfile";
import { IValidateNicknameRequestData } from "@spt-aki/models/eft/profile/IValidateNicknameRequestData"; import { IValidateNicknameRequestData } from "@spt-aki/models/eft/profile/IValidateNicknameRequestData";
import { SkillTypes } from "@spt-aki/models/enums/SkillTypes"; import { SkillTypes } from "@spt-aki/models/enums/SkillTypes";
@ -423,4 +423,16 @@ export class ProfileHelper
profileSkill.Progress += pointsToAdd; profileSkill.Progress += pointsToAdd;
profileSkill.LastAccess = this.timeUtil.getTimestamp(); profileSkill.LastAccess = this.timeUtil.getTimestamp();
} }
public getSkillFromProfile(pmcData: IPmcData, skill: SkillTypes): Common
{
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}`);
return undefined;
}
return skillToReturn;
}
} }

View File

@ -818,7 +818,7 @@ export class QuestHelper
let moneyRewardBonus = moneyRewardBonuses.reduce((acc, cur) => acc + cur.value, 0); let moneyRewardBonus = moneyRewardBonuses.reduce((acc, cur) => acc + cur.value, 0);
// Apply hideout management bonus to money reward (up to 51% bonus) // Apply hideout management bonus to money reward (up to 51% bonus)
const hideoutManagementSkill = pmcData.Skills.Common.find(x => x.Id === SkillTypes.HIDEOUT_MANAGEMENT); const hideoutManagementSkill = this.profileHelper.getSkillFromProfile(pmcData, SkillTypes.HIDEOUT_MANAGEMENT);
if (hideoutManagementSkill) if (hideoutManagementSkill)
{ {
moneyRewardBonus *= (1 + (hideoutManagementSkill.Progress / 10000)); // 5100 becomes 0.51, add 1 to it, 1.51, multiply the moneyreward bonus by it (e.g. 15 x 51) moneyRewardBonus *= (1 + (hideoutManagementSkill.Progress / 10000)); // 5100 becomes 0.51, add 1 to it, 1.51, multiply the moneyreward bonus by it (e.g. 15 x 51)

View File

@ -1,5 +1,7 @@
export enum SkillTypes export enum SkillTypes
{ {
BOT_RELOAD = "BotReload",
BOT_SOUND = "BotSound",
HIDEOUT_MANAGEMENT = "HideoutManagement", HIDEOUT_MANAGEMENT = "HideoutManagement",
CRAFTING = "Crafting", CRAFTING = "Crafting",
METABOLISM = "Metabolism", METABOLISM = "Metabolism",
@ -20,6 +22,7 @@ export enum SkillTypes
ATTENTION = "Attention", ATTENTION = "Attention",
CHARISMA = "Charisma", CHARISMA = "Charisma",
MEMORY = "Memory", MEMORY = "Memory",
MELEE = "Melee",
SURGERY = "Surgery", SURGERY = "Surgery",
AIM_DRILLS = "AimDrills", AIM_DRILLS = "AimDrills",
TROUBLESHOOTING = "TroubleShooting", TROUBLESHOOTING = "TroubleShooting",
@ -32,6 +35,7 @@ export enum SkillTypes
NIGHT_OPS = "NightOps", NIGHT_OPS = "NightOps",
SILENT_OPS = "SilentOps", SILENT_OPS = "SilentOps",
LOCKPICKING = "Lockpicking", LOCKPICKING = "Lockpicking",
/** Also called Weapon Maintenance*/
WEAPON_TREATMENT = "WeaponTreatment", WEAPON_TREATMENT = "WeaponTreatment",
MAG_DRILLS = "MagDrills", MAG_DRILLS = "MagDrills",
FREE_TRADING = "Freetrading", FREE_TRADING = "Freetrading",

View File

@ -259,6 +259,7 @@ export class RepairService
const itemToRepairDetails = itemsDb[itemToRepair._tpl]; const itemToRepairDetails = itemsDb[itemToRepair._tpl];
const repairItemIsArmor = (!!itemToRepairDetails._props.ArmorMaterial); const repairItemIsArmor = (!!itemToRepairDetails._props.ArmorMaterial);
const repairAmount = repairKits[0].count / this.getKitDivisor(itemToRepairDetails, repairItemIsArmor, pmcData); const repairAmount = repairKits[0].count / this.getKitDivisor(itemToRepairDetails, repairItemIsArmor, pmcData);
const shouldApplyDurabilityLoss = this.shouldRepairKitApplyDurabilityLoss(pmcData, this.repairConfig.applyRandomizeDurabilityLoss);
this.repairHelper.updateItemDurability( this.repairHelper.updateItemDurability(
itemToRepair, itemToRepair,
@ -267,7 +268,7 @@ export class RepairService
repairAmount, repairAmount,
true, true,
1, 1,
this.repairConfig.applyRandomizeDurabilityLoss); shouldApplyDurabilityLoss);
// Find and use repair kit defined in body // Find and use repair kit defined in body
for (const repairKit of repairKits) for (const repairKit of repairKits)
@ -306,7 +307,7 @@ export class RepairService
const globalRepairSettings = globals.config.RepairSettings; const globalRepairSettings = globals.config.RepairSettings;
const intellectRepairPointsPerLevel = globals.config.SkillsSettings.Intellect.RepairPointsCostReduction; const intellectRepairPointsPerLevel = globals.config.SkillsSettings.Intellect.RepairPointsCostReduction;
const profileIntellectLevel = pmcData.Skills?.Common?.find(s => s.Id === SkillTypes.INTELLECT)?.Progress ?? 0; const profileIntellectLevel = this.profileHelper.getSkillFromProfile(pmcData, SkillTypes.INTELLECT)?.Progress ?? 0;
const intellectPointReduction = intellectRepairPointsPerLevel * Math.trunc(profileIntellectLevel / 100); const intellectPointReduction = intellectRepairPointsPerLevel * Math.trunc(profileIntellectLevel / 100);
if (isArmor) if (isArmor)
@ -352,6 +353,29 @@ export class RepairService
return value; return value;
} }
/**
* Should a repair kit apply total durability loss on repair
* @param pmcData Player profile
* @param applyRandomizeDurabilityLoss Value from repair config
* @returns True if loss should be applied
*/
protected shouldRepairKitApplyDurabilityLoss(pmcData: IPmcData, applyRandomizeDurabilityLoss: boolean): boolean
{
let shouldApplyDurabilityLoss = applyRandomizeDurabilityLoss;
if (shouldApplyDurabilityLoss)
{
// Random loss not disabled via config, perform charisma check
const hasEliteCharisma = this.profileHelper.hasEliteSkillLevel(SkillTypes.CHARISMA, pmcData);
if (hasEliteCharisma)
{
// 50/50 chance of loss being ignored at elite level
shouldApplyDurabilityLoss = this.randomUtil.getChance100(50);
}
}
return shouldApplyDurabilityLoss;
}
/** /**
* Update repair kits Resource object if it doesn't exist * Update repair kits Resource object if it doesn't exist
* @param repairKitDetails Repair kit details from db * @param repairKitDetails Repair kit details from db
@ -448,13 +472,15 @@ export class RepairService
const itemSkillType = this.getItemSkillType(template); const itemSkillType = this.getItemSkillType(template);
if (!itemSkillType) if (!itemSkillType)
{
return false; return false;
}
const commonBuffMinChanceValue = globals.config.SkillsSettings[itemSkillType].BuffSettings.CommonBuffMinChanceValue; const commonBuffMinChanceValue = globals.config.SkillsSettings[itemSkillType as string].BuffSettings.CommonBuffMinChanceValue;
const commonBuffChanceLevelBonus = globals.config.SkillsSettings[itemSkillType].BuffSettings.CommonBuffChanceLevelBonus; const commonBuffChanceLevelBonus = globals.config.SkillsSettings[itemSkillType as string].BuffSettings.CommonBuffChanceLevelBonus;
const receivedDurabilityMaxPercent = globals.config.SkillsSettings[itemSkillType].BuffSettings.ReceivedDurabilityMaxPercent; const receivedDurabilityMaxPercent = globals.config.SkillsSettings[itemSkillType as string].BuffSettings.ReceivedDurabilityMaxPercent;
const skillLevel = Math.trunc((pmcData?.Skills?.Common?.find(s => s.Id === itemSkillType)?.Progress ?? 0) / 100); const skillLevel = Math.trunc((this.profileHelper.getSkillFromProfile(pmcData, itemSkillType)?.Progress ?? 0) / 100);
const durabilityToRestorePercent = repairDetails.repairPoints / template._props.MaxDurability; const durabilityToRestorePercent = repairDetails.repairPoints / template._props.MaxDurability;
const durabilityMultiplier = this.getDurabilityMultiplier(receivedDurabilityMaxPercent, durabilityToRestorePercent); const durabilityMultiplier = this.getDurabilityMultiplier(receivedDurabilityMaxPercent, durabilityToRestorePercent);
@ -474,26 +500,26 @@ export class RepairService
* @param itemTemplate Item to check for skill * @param itemTemplate Item to check for skill
* @returns Skill name * @returns Skill name
*/ */
protected getItemSkillType(itemTemplate: ITemplateItem): string protected getItemSkillType(itemTemplate: ITemplateItem): SkillTypes
{ {
if (this.itemHelper.isOfBaseclass(itemTemplate._id, BaseClasses.ARMOR)) if (this.itemHelper.isOfBaseclass(itemTemplate._id, BaseClasses.ARMOR))
{ {
if (itemTemplate._props.ArmorType === "Light") if (itemTemplate._props.ArmorType === "Light")
{ {
return "LightVests"; return SkillTypes.LIGHT_VESTS;
} }
else if (itemTemplate._props.ArmorType === "Heavy") else if (itemTemplate._props.ArmorType === "Heavy")
{ {
return "HeavyVests"; return SkillTypes.HEAVY_VESTS;
} }
} }
else if (this.itemHelper.isOfBaseclass(itemTemplate._id, BaseClasses.WEAPON)) else if (this.itemHelper.isOfBaseclass(itemTemplate._id, BaseClasses.WEAPON))
{ {
return "WeaponTreatment"; return SkillTypes.WEAPON_TREATMENT;
} }
else if (this.itemHelper.isOfBaseclass(itemTemplate._id, BaseClasses.KNIFE)) else if (this.itemHelper.isOfBaseclass(itemTemplate._id, BaseClasses.KNIFE))
{ {
return "Melee"; return SkillTypes.MELEE;
} }
return undefined; return undefined;