diff --git a/project/assets/configs/quest.json b/project/assets/configs/quest.json index 57a03bbc..b197865d 100644 --- a/project/assets/configs/quest.json +++ b/project/assets/configs/quest.json @@ -146,9 +146,11 @@ "levels": [1, 10, 20, 30, 40, 50, 60], "experience": [1000, 12000, 42000, 89000, 177000, 300000, 500000], "roubles": [15000, 40000, 75000, 100000, 140000, 170000, 210000], - "items": [4, 4, 5, 5, 5, 6, 6], - "reputation": [0.01, 0.01, 0.02, 0.02], - "rewardSpread": 0.5 + "items": [3, 4, 5, 5, 5, 5, 5], + "reputation": [0.01, 0.01, 0.02, 0.02, 0.03, 0.03, 0.03], + "rewardSpread": 0.5, + "skillRewardChance": [0, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25], + "skillPointReward": [10, 15, 20, 25, 30, 35, 40] }, "locations": { "any": ["any"], @@ -188,6 +190,7 @@ "questConfig": { "Exploration": { "maxExtracts": 3, + "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "specificExits": { "probability": 0.25, "passageRequirementWhitelist": [ @@ -201,6 +204,7 @@ } }, "Completion": { + "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "minRequestedAmount": 1, "maxRequestedAmount": 5, "minRequestedBulletAmount": 20, @@ -213,6 +217,7 @@ "min": 1, "max": 15 }, + "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "targets": [{ "key": "Savage", "relativeProbability": 1, @@ -326,6 +331,7 @@ "min": 16, "max": 40 }, + "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "targets": [{ "key": "Savage", "relativeProbability": 9, @@ -507,6 +513,7 @@ "min": 41, "max": 100 }, + "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "targets": [{ "key": "Savage", "relativeProbability": 9, @@ -704,9 +711,11 @@ "levels": [1, 10, 20, 30, 40, 50, 60], "experience": [5000, 25000, 60000, 130000, 240000, 390000, 750000], "roubles": [50000, 150000, 300000, 425000, 550000, 675000, 850000], - "items": [5, 5, 5, 6, 6, 7, 7], - "reputation": [0.02, 0.02, 0.03, 0.03, 0.03, 0.03, 0.03], - "rewardSpread": 0.5 + "items": [4, 5, 5, 6, 6, 7, 7], + "reputation": [0.02, 0.03, 0.04, 0.04, 0.05, 0.05, 0.05], + "rewardSpread": 0.5, + "skillRewardChance": [0, 0.05, 0.1, 0.2, 0.3, 0.35, 0.4], + "skillPointReward": [25, 35, 45, 50, 55, 60, 65] }, "locations": { "any": ["any"], @@ -745,6 +754,7 @@ ], "questConfig": { "Exploration": { + "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "maxExtracts": 10, "specificExits": { "probability": 0.4, @@ -759,6 +769,7 @@ } }, "Completion": { + "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "minRequestedAmount": 2, "maxRequestedAmount": 10, "minRequestedBulletAmount": 20, @@ -771,6 +782,7 @@ "min": 1, "max": 15 }, + "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "targets": [{ "key": "Savage", "relativeProbability": 15, @@ -951,6 +963,7 @@ "min": 16, "max": 40 }, + "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "targets": [{ "key": "Savage", "relativeProbability": 7, @@ -1131,6 +1144,7 @@ "min": 41, "max": 100 }, + "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "targets": [{ "key": "Savage", "relativeProbability": 7, @@ -1331,7 +1345,9 @@ "roubles": [6000, 10000, 100000, 250000], "items": [2, 3, 4, 4], "reputation": [0.01, 0.02, 0.05, 0.05], - "rewardSpread": 0.5 + "rewardSpread": 0.5, + "skillRewardChance": [0, 0, 0, 0, 0, 0, 0], + "skillPointReward": [10, 15, 20, 25, 30, 35, 40] }, "locations": { "any": ["any"], @@ -1351,6 +1367,7 @@ ], "questConfig": { "Exploration": { + "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "maxExtracts": 3, "specificExits": { "probability": 0.25, @@ -1364,6 +1381,7 @@ } }, "Pickup": { + "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "ItemTypeToFetchWithMaxCount": [{ "itemType": "5b47574386f77428ca22b335", "minPickupCount": 2, @@ -1410,6 +1428,7 @@ "maxItemFetchCount": 3 }, "Completion": { + "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "minRequestedAmount": 1, "maxRequestedAmount": 5, "minRequestedBulletAmount": 20, @@ -1422,6 +1441,7 @@ "min": 1, "max": 15 }, + "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "targets": [{ "key": "AnyPmc", @@ -1460,7 +1480,7 @@ "minKills": 1, "maxBossKills": 1, "minBossKills": 1, - "maxPmcKills": 2, + "maxPmcKills": 2, "minPmcKills": 1, "weaponRequirementProb": 0, "weaponCategoryRequirementProb": 0.3, @@ -1537,6 +1557,7 @@ "min": 16, "max": 100 }, + "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "targets": [{ "key": "AnyPmc", @@ -1575,7 +1596,7 @@ "minKills": 3, "maxBossKills": 3, "minBossKills": 1, - "maxPmcKills": 5, + "maxPmcKills": 5, "minPmcKills": 2, "weaponRequirementProb": 0, "weaponCategoryRequirementProb": 0.3, diff --git a/project/src/controllers/HideoutController.ts b/project/src/controllers/HideoutController.ts index fb2d7e0d..e14c37d7 100644 --- a/project/src/controllers/HideoutController.ts +++ b/project/src/controllers/HideoutController.ts @@ -200,7 +200,7 @@ export class HideoutController } // Add Skill Points Per Area Upgrade - this.playerService.incrementSkillLevel(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, db.globals.config.SkillsSettings.HideoutManagement.SkillPointsPerAreaUpgrade); + this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, db.globals.config.SkillsSettings.HideoutManagement.SkillPointsPerAreaUpgrade); return output; } @@ -723,12 +723,12 @@ export class HideoutController // manager Hideout skill // ? use a configuration variable for the value? const globals = this.databaseServer.getTables().globals; - this.playerService.incrementSkillLevel(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, globals.config.SkillsSettings.HideoutManagement.SkillPointsPerCraft, true); + this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, globals.config.SkillsSettings.HideoutManagement.SkillPointsPerCraft, true); //manager Crafting skill if (craftingExpAmount > 0) { - this.playerService.incrementSkillLevel(pmcData, SkillTypes.CRAFTING, craftingExpAmount); - this.playerService.incrementSkillLevel(pmcData, SkillTypes.INTELLECT, 0.5 * (Math.round(craftingExpAmount / 15))); + this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.CRAFTING, craftingExpAmount); + this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.INTELLECT, 0.5 * (Math.round(craftingExpAmount / 15))); } area.lastRecipe = request.recipeId; counterHoursCrafting.value = hoursCrafting; diff --git a/project/src/controllers/InsuranceController.ts b/project/src/controllers/InsuranceController.ts index 0b40e7d7..a2593187 100644 --- a/project/src/controllers/InsuranceController.ts +++ b/project/src/controllers/InsuranceController.ts @@ -10,10 +10,11 @@ import { IGetInsuranceCostRequestData } from "@spt-aki/models/eft/insurance/IGet import { IGetInsuranceCostResponseData } from "@spt-aki/models/eft/insurance/IGetInsuranceCostResponseData"; import { IInsureRequestData } from "@spt-aki/models/eft/insurance/IInsureRequestData"; import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse"; -import { Insurance, ISystemData } from "@spt-aki/models/eft/profile/IAkiProfile"; +import { ISystemData, Insurance } from "@spt-aki/models/eft/profile/IAkiProfile"; import { IProcessBuyTradeRequestData } from "@spt-aki/models/eft/trade/IProcessBuyTradeRequestData"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { MessageType } from "@spt-aki/models/enums/MessageType"; +import { SkillTypes } from "@spt-aki/models/enums/SkillTypes"; import { IInsuranceConfig } from "@spt-aki/models/spt/config/IInsuranceConfig"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { EventOutputHolder } from "@spt-aki/routers/EventOutputHolder"; @@ -518,6 +519,7 @@ export class InsuranceController public insure(pmcData: IPmcData, body: IInsureRequestData, sessionID: string): IItemEventRouterResponse { let output = this.eventOutputHolder.getOutput(sessionID); + const itemsToInsureCount = body.items.length; const itemsToPay = []; const inventoryItemsHash = {}; @@ -564,6 +566,8 @@ export class InsuranceController }); } + this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.CHARISMA, itemsToInsureCount * 0.01); + return output; } diff --git a/project/src/controllers/InventoryController.ts b/project/src/controllers/InventoryController.ts index 715ec150..73ab6778 100644 --- a/project/src/controllers/InventoryController.ts +++ b/project/src/controllers/InventoryController.ts @@ -599,7 +599,7 @@ export class InventoryController pmcData.Encyclopedia[itemId] = true; // TODO: update this with correct calculation using values from globals json - this.questHelper.rewardSkillPoints(sessionID, pmcData, SkillTypes.INTELLECT, 0.5); + this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.INTELLECT, 0.5); } return this.eventOutputHolder.getOutput(sessionID); diff --git a/project/src/controllers/RepeatableQuestController.ts b/project/src/controllers/RepeatableQuestController.ts index bae6ecac..e9337c0a 100644 --- a/project/src/controllers/RepeatableQuestController.ts +++ b/project/src/controllers/RepeatableQuestController.ts @@ -14,11 +14,13 @@ import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { ELocationName } from "@spt-aki/models/enums/ELocationName"; import { HideoutAreas } from "@spt-aki/models/enums/HideoutAreas"; import { QuestStatus } from "@spt-aki/models/enums/QuestStatus"; +import { SkillTypes } from "@spt-aki/models/enums/SkillTypes"; import { IQuestConfig, IRepeatableQuestConfig } from "@spt-aki/models/spt/config/IQuestConfig"; import { IQuestTypePool } from "@spt-aki/models/spt/repeatable/IQuestTypePool"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { EventOutputHolder } from "@spt-aki/routers/EventOutputHolder"; import { ConfigServer } from "@spt-aki/servers/ConfigServer"; +import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { PaymentService } from "@spt-aki/services/PaymentService"; import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService"; import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil"; @@ -33,8 +35,9 @@ export class RepeatableQuestController protected questConfig: IQuestConfig; constructor( - @inject("TimeUtil") protected timeUtil: TimeUtil, @inject("WinstonLogger") protected logger: ILogger, + @inject("DatabaseServer") protected databaseServer: DatabaseServer, + @inject("TimeUtil") protected timeUtil: TimeUtil, @inject("RandomUtil") protected randomUtil: RandomUtil, @inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil, @inject("JsonUtil") protected jsonUtil: JsonUtil, @@ -131,7 +134,7 @@ export class RepeatableQuestController const questTypePool = this.generateQuestPool(repeatableConfig, pmcData.Info.Level); // Add daily quests - for (let i = 0; i < repeatableConfig.numQuests; i++) + for (let i = 0; i < this.getQuestCount(repeatableConfig, pmcData); i++) { let quest = null; let lifeline = 0; @@ -188,6 +191,23 @@ export class RepeatableQuestController return returnData; } + /** + * Get the number of quests to generate - takes into account charisma state of player + * @param repeatableConfig Config + * @param pmcData Player profile + * @returns Quest count + */ + protected getQuestCount(repeatableConfig: IRepeatableQuestConfig, pmcData: IPmcData): number + { + if (repeatableConfig.name.toLowerCase() === "daily" && this.profileHelper.hasEliteSkillLevel(SkillTypes.CHARISMA, pmcData)) + { + // Elite charisma skill gives extra daily quest(s) + return repeatableConfig.numQuests + this.databaseServer.getTables().globals.config.SkillsSettings.Charisma.BonusSettings.EliteBonusSettings.RepeatableQuestExtraCount; + } + + return repeatableConfig.numQuests; + } + /** * Get repeatable quest data from profile from name (daily/weekly), creates base repeatable quest object if none exists * @param repeatableConfig daily/weekly config diff --git a/project/src/generators/RepeatableQuestGenerator.ts b/project/src/generators/RepeatableQuestGenerator.ts index 35aef305..f85fe623 100644 --- a/project/src/generators/RepeatableQuestGenerator.ts +++ b/project/src/generators/RepeatableQuestGenerator.ts @@ -24,7 +24,7 @@ import { BaseClasses } from "@spt-aki/models/enums/BaseClasses"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { Money } from "@spt-aki/models/enums/Money"; import { Traders } from "@spt-aki/models/enums/Traders"; -import { IBossInfo, IEliminationConfig, IQuestConfig, IRepeatableQuestConfig } from "@spt-aki/models/spt/config/IQuestConfig"; +import { IBaseQuestConfig, IBossInfo, IEliminationConfig, IQuestConfig, IRepeatableQuestConfig } from "@spt-aki/models/spt/config/IQuestConfig"; import { IQuestTypePool } from "@spt-aki/models/spt/repeatable/IQuestTypePool"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { EventOutputHolder } from "@spt-aki/routers/EventOutputHolder"; @@ -326,7 +326,7 @@ export class RepeatableQuestGenerator availableForFinishCondition._props.id = this.objectId.generate(); quest.location = this.getQuestLocationByMapId(locationKey); - quest.rewards = this.generateReward(pmcLevel, Math.min(difficulty, 1), traderId, repeatableConfig); + quest.rewards = this.generateReward(pmcLevel, Math.min(difficulty, 1), traderId, repeatableConfig, eliminationConfig); return quest; } @@ -544,7 +544,7 @@ export class RepeatableQuestGenerator } } - quest.rewards = this.generateReward(pmcLevel, 1, traderId, repeatableConfig); + quest.rewards = this.generateReward(pmcLevel, 1, traderId, repeatableConfig, completionConfig); return quest; } @@ -673,7 +673,7 @@ export class RepeatableQuestGenerator // Difficulty for exploration goes from 1 extract to maxExtracts // Difficulty for reward goes from 0.2...1 -> map const difficulty = this.mathUtil.mapToRange(numExtracts, 1, explorationConfig.maxExtracts, 0.2, 1); - quest.rewards = this.generateReward(pmcLevel, difficulty, traderId, repeatableConfig); + quest.rewards = this.generateReward(pmcLevel, difficulty, traderId, repeatableConfig, explorationConfig); return quest; } @@ -707,7 +707,7 @@ export class RepeatableQuestGenerator (equipmentCondition._props as IEquipmentConditionProps).equipmentInclusive = [[itemTypeToFetchWithCount.itemType]]; // Add rewards - quest.rewards = this.generateReward(pmcLevel, 1, traderId, repeatableConfig); + quest.rewards = this.generateReward(pmcLevel, 1, traderId, repeatableConfig, pickupConfig); return quest; } @@ -765,7 +765,8 @@ export class RepeatableQuestGenerator pmcLevel: number, difficulty: number, traderId: string, - repeatableConfig: IRepeatableQuestConfig + repeatableConfig: IRepeatableQuestConfig, + questConfig: IBaseQuestConfig ): IRewards { // difficulty could go from 0.2 ... -> for lowest diffuculty receive 0.2*nominal reward @@ -774,6 +775,8 @@ export class RepeatableQuestGenerator const xpConfig = repeatableConfig.rewardScaling.experience; const itemsConfig = repeatableConfig.rewardScaling.items; const rewardSpreadConfig = repeatableConfig.rewardScaling.rewardSpread; + const skillRewardChanceConfig = repeatableConfig.rewardScaling.skillRewardChance; + const skillPointRewardConfig = repeatableConfig.rewardScaling.skillPointReward; const reputationConfig = repeatableConfig.rewardScaling.reputation; if (Number.isNaN(difficulty)) @@ -788,6 +791,8 @@ export class RepeatableQuestGenerator const rewardNumItems = this.randomUtil.randInt(1, Math.round(this.mathUtil.interp1(pmcLevel, levelsConfig, itemsConfig)) + 1); const rewardReputation = Math.round(100 * difficulty * this.mathUtil.interp1(pmcLevel, levelsConfig, reputationConfig) * this.randomUtil.getFloat(1 - rewardSpreadConfig, 1 + rewardSpreadConfig)) / 100; + const skillRewardChance = this.mathUtil.interp1(pmcLevel, levelsConfig, skillRewardChanceConfig); + const skillPointReward = this.mathUtil.interp1(pmcLevel, levelsConfig, skillPointRewardConfig); // Possible improvement -> draw trader-specific items e.g. with this.itemHelper.isOfBaseclass(val._id, ItemHelper.BASECLASS.FoodDrink) let roublesBudget = rewardRoubles; @@ -879,6 +884,18 @@ export class RepeatableQuestGenerator rewards.Success.push(reward); } + if (this.randomUtil.getChance100(skillRewardChance * 100)) + { + index++; + const reward: IReward = { + target: this.randomUtil.getArrayValue(questConfig.possibleSkillRewards), + value: skillPointReward, + type: "Skill", + index: index + }; + rewards.Success.push(reward); + } + return rewards; } diff --git a/project/src/helpers/HideoutHelper.ts b/project/src/helpers/HideoutHelper.ts index 5dcf48c5..6a76cb19 100644 --- a/project/src/helpers/HideoutHelper.ts +++ b/project/src/helpers/HideoutHelper.ts @@ -403,7 +403,7 @@ export class HideoutHelper //check unit consumed for increment skill point if (pmcData && Math.floor(pointsConsumed / 10) >= 1) { - this.playerService.incrementSkillLevel(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, 1); + this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, 1); pointsConsumed -= 10; } @@ -511,7 +511,7 @@ export class HideoutHelper // Check amount of units consumed for possible increment of hideout mgmt skill point if (pmcData && Math.floor(pointsConsumed / 10) >= 1) { - this.playerService.incrementSkillLevel(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, 1); + this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, 1); pointsConsumed -= 10; } @@ -637,7 +637,7 @@ export class HideoutHelper //check unit consumed for increment skill point if (pmcData && Math.floor(pointsConsumed / 10) >= 1) { - this.playerService.incrementSkillLevel(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, 1); + this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, 1); pointsConsumed -= 10; } @@ -794,7 +794,7 @@ export class HideoutHelper { const bitcoinProduction = this.databaseServer.getTables().hideout.production.find(p => p._id === HideoutHelper.bitcoinFarm); const productionSlots = bitcoinProduction?.productionLimitCount || 3; - const hasManagementSkillSlots = this.hasEliteHideoutManagementSkill(pmcData); + const hasManagementSkillSlots = this.profileHelper.hasEliteSkillLevel(SkillTypes.HIDEOUT_MANAGEMENT, pmcData); const managementSlotsCount = this.getBitcoinMinerContainerSlotSize() || 2; return productionSlots + (hasManagementSkillSlots ? managementSlotsCount : 0); diff --git a/project/src/helpers/ProfileHelper.ts b/project/src/helpers/ProfileHelper.ts index 5bd043fe..fad76acb 100644 --- a/project/src/helpers/ProfileHelper.ts +++ b/project/src/helpers/ProfileHelper.ts @@ -5,9 +5,11 @@ import { IPmcData } from "@spt-aki/models/eft/common/IPmcData"; import { CounterKeyValue, Stats } from "@spt-aki/models/eft/common/tables/IBotBase"; import { IAkiProfile } from "@spt-aki/models/eft/profile/IAkiProfile"; import { IValidateNicknameRequestData } from "@spt-aki/models/eft/profile/IValidateNicknameRequestData"; +import { SkillTypes } from "@spt-aki/models/enums/SkillTypes"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { SaveServer } from "@spt-aki/servers/SaveServer"; +import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { ProfileSnapshotService } from "@spt-aki/services/ProfileSnapshotService"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil"; @@ -24,7 +26,8 @@ export class ProfileHelper @inject("SaveServer") protected saveServer: SaveServer, @inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("ItemHelper") protected itemHelper: ItemHelper, - @inject("ProfileSnapshotService") protected profileSnapshotService: ProfileSnapshotService + @inject("ProfileSnapshotService") protected profileSnapshotService: ProfileSnapshotService, + @inject("LocalisationService") protected localisationService: LocalisationService ) { } @@ -353,4 +356,71 @@ export class ProfileHelper stat.Value++; } } + + /** + * Check if player has a skill at elite level + * @param skillType Skill to check + * @param pmcProfile Profile to find skill in + * @returns True if player has skill at elite level + */ + public hasEliteSkillLevel(skillType: SkillTypes, pmcProfile: IPmcData): boolean + { + const profileSkills = pmcProfile?.Skills?.Common; + if (!profileSkills) + { + return false; + } + + const profileSkill = profileSkills.find(x => x.Id === skillType); + if (!profileSkill) + { + this.logger.warning(`Unable to check for elite skill ${skillType}, not found in profile`); + + return false; + } + return profileSkill.Progress >= 5100; // level 51 + } + + /** + * Add points to a specific skill in player profile + * @param skill Skill to add points to + * @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 + */ + 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)); + return; + } + + const profileSkills = pmcProfile?.Skills?.Common; + if (!profileSkills) + { + this.logger.warning(`Unable to add ${pointsToAdd} points to ${skill}, profile has no skills`); + + return; + } + + const profileSkill = profileSkills.find(x => x.Id === skill); + if (!profileSkill) + { + this.logger.error(this.localisationService.getText("quest-no_skill_found", skill)); + + return; + } + + if (useSkillProgressRateMultipler) + { + const globals = this.databaseServer.getTables().globals; + const skillProgressRate = globals.config.SkillsSettings.SkillProgressRate; + pointsToAdd = skillProgressRate * pointsToAdd; + } + + profileSkill.Progress += pointsToAdd; + profileSkill.LastAccess = this.timeUtil.getTimestamp(); + } } \ No newline at end of file diff --git a/project/src/helpers/QuestHelper.ts b/project/src/helpers/QuestHelper.ts index 93edf145..5bc3e932 100644 --- a/project/src/helpers/QuestHelper.ts +++ b/project/src/helpers/QuestHelper.ts @@ -18,6 +18,7 @@ import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { MessageType } from "@spt-aki/models/enums/MessageType"; import { QuestRewardType } from "@spt-aki/models/enums/QuestRewardType"; import { QuestStatus } from "@spt-aki/models/enums/QuestStatus"; +import { SkillTypes } from "@spt-aki/models/enums/SkillTypes"; import { IQuestConfig } from "@spt-aki/models/spt/config/IQuestConfig"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { EventOutputHolder } from "@spt-aki/routers/EventOutputHolder"; @@ -127,42 +128,6 @@ export class QuestHelper return after; } - /** - * Increase skill points of a skill on player profile - * Dupe of PlayerService.incrementSkillLevel() - * @param sessionID Session id - * @param pmcData Player profile - * @param skillName Name of skill to increase skill points of - * @param progressAmount Amount of skill points to add to skill - */ - public rewardSkillPoints(sessionID: string, pmcData: IPmcData, skillName: string, progressAmount: number, scaleToSkillLevel: boolean = false): void - { - const indexOfSkillToUpdate = pmcData.Skills.Common.findIndex(s => s.Id === skillName); - if (indexOfSkillToUpdate === -1) - { - this.logger.error(this.localisationService.getText("quest-no_skill_found", skillName)); - - return; - } - - const profileSkill = pmcData.Skills.Common[indexOfSkillToUpdate]; - if (!profileSkill) - { - this.logger.error(this.localisationService.getText("quest-no_skill_found", skillName)); - - return; - } - - // Tarkov has special handling of skills under level 9 to scale them to the lower XP requirement - if (scaleToSkillLevel) - { - progressAmount = this.adjustSkillExpForLowLevels(profileSkill, progressAmount); - } - - profileSkill.Progress += progressAmount; - profileSkill.LastAccess = this.timeUtil.getTimestamp(); - } - /** * Adjust skill experience for low skill levels, mimicing the official client * @param profileSkill the skill experience is being added to @@ -771,7 +736,7 @@ export class QuestHelper switch (reward.type) { case QuestRewardType.SKILL: - this.rewardSkillPoints(sessionId, pmcData, reward.target, Number(reward.value)); + this.profileHelper.addSkillPointsToPlayer(pmcData, reward.target as SkillTypes, Number(reward.value)); break; case QuestRewardType.EXPERIENCE: this.profileHelper.addExperienceToPmc(sessionId, parseInt(reward.value)); // this must occur first as the output object needs to take the modified profile exp value @@ -853,7 +818,7 @@ export class QuestHelper let moneyRewardBonus = moneyRewardBonuses.reduce((acc, cur) => acc + cur.value, 0); // Apply hideout management bonus to money reward (up to 51% bonus) - const hideoutManagementSkill = pmcData.Skills.Common.find(x => x.Id === "HideoutManagement"); + const hideoutManagementSkill = pmcData.Skills.Common.find(x => x.Id === 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) diff --git a/project/src/models/spt/config/IQuestConfig.ts b/project/src/models/spt/config/IQuestConfig.ts index 39a82437..34753847 100644 --- a/project/src/models/spt/config/IQuestConfig.ts +++ b/project/src/models/spt/config/IQuestConfig.ts @@ -68,6 +68,8 @@ export interface IRewardScaling items: number[] reputation: number[] rewardSpread: number + skillRewardChance: number[] + skillPointReward: number[] } export interface ITraderWhitelist @@ -84,7 +86,7 @@ export interface IRepeatableQuestTypesConfig Elimination: IEliminationConfig[] } -export interface IExploration +export interface IExploration extends IBaseQuestConfig { maxExtracts: number specificExits: ISpecificExits @@ -96,7 +98,7 @@ export interface ISpecificExits passageRequirementWhitelist: string[] } -export interface ICompletion +export interface ICompletion extends IBaseQuestConfig { minRequestedAmount: number maxRequestedAmount: number @@ -106,7 +108,7 @@ export interface ICompletion useBlacklist: boolean } -export interface IPickup +export interface IPickup extends IBaseQuestConfig { ItemTypeToFetchWithMaxCount: IPickupTypeWithMaxCount[] } @@ -118,7 +120,7 @@ export interface IPickupTypeWithMaxCount minPickupCount: number } -export interface IEliminationConfig +export interface IEliminationConfig extends IBaseQuestConfig { levelRange: MinMax targets: ITarget[] @@ -141,6 +143,11 @@ export interface IEliminationConfig weaponRequirements: IWeaponRequirement[] } +export interface IBaseQuestConfig +{ + possibleSkillRewards: string[] +} + export interface ITarget extends IProbabilityObject { data: IBossInfo diff --git a/project/src/services/PlayerService.ts b/project/src/services/PlayerService.ts index 190ce346..8ef07062 100644 --- a/project/src/services/PlayerService.ts +++ b/project/src/services/PlayerService.ts @@ -18,41 +18,6 @@ export class PlayerService ) { } - /** - * Dupe of QuestHelper.rewardsSkillPoints() - * Add xp to a player skill - * @param pmcData Player profile - * @param skillName Name of skill to increment - * @param amount Amount of skill points to add to skill - * @param useSkillProgressRateMultipler Skills are multiplied by a value in globals, default is off to maintain compatibility with legacy code - */ - public incrementSkillLevel(pmcData: IPmcData, skillName: string, amount: number, useSkillProgressRateMultipler = false): void - { - if (!amount || amount < 0) - { - this.logger.error(this.localisationService.getText("player-attempt_to_increment_skill_with_negative_value", skillName)); - return; - } - - const profileSkill = pmcData.Skills.Common.find(skill => skill.Id === skillName); - if (!profileSkill) - { - this.logger.error(this.localisationService.getText("quest-no_skill_found", skillName)); - - return; - } - - if (useSkillProgressRateMultipler) - { - const globals = this.databaseServer.getTables().globals; - const skillProgressRate = globals.config.SkillsSettings.SkillProgressRate; - amount = skillProgressRate * amount; - } - - profileSkill.Progress += amount; - profileSkill.LastAccess = this.timeUtil.getTimestamp(); - } - /** * Get level of player * @param pmcData Player profile diff --git a/project/src/services/RepairService.ts b/project/src/services/RepairService.ts index 5b49b577..cf1b8ee5 100644 --- a/project/src/services/RepairService.ts +++ b/project/src/services/RepairService.ts @@ -1,7 +1,7 @@ import { inject, injectable } from "tsyringe"; import { ItemHelper } from "@spt-aki/helpers/ItemHelper"; -import { QuestHelper } from "@spt-aki/helpers/QuestHelper"; +import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper"; import { RepairHelper } from "@spt-aki/helpers/RepairHelper"; import { TraderHelper } from "@spt-aki/helpers/TraderHelper"; import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper"; @@ -31,7 +31,7 @@ export class RepairService constructor( @inject("WinstonLogger") protected logger: ILogger, @inject("DatabaseServer") protected databaseServer: DatabaseServer, - @inject("QuestHelper") protected questHelper: QuestHelper, + @inject("ProfileHelper") protected profileHelper: ProfileHelper, @inject("RandomUtil") protected randomUtil: RandomUtil, @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("TraderHelper") protected traderHelper: TraderHelper, @@ -151,7 +151,7 @@ export class RepairService { const skillPoints = this.getWeaponRepairSkillPoints(repairDetails); - this.questHelper.rewardSkillPoints(sessionId, pmcData, "WeaponTreatment", skillPoints, true); + this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.WEAPON_TREATMENT, skillPoints, true); } // Handle kit repairs of armor @@ -167,10 +167,12 @@ export class RepairService } const isHeavyArmor = itemDetails[1]._props.ArmorType === "Heavy"; - const vestSkillToLevel = (isHeavyArmor) ? "HeavyVests" : "LightVests"; + const vestSkillToLevel = (isHeavyArmor) + ? SkillTypes.HEAVY_VESTS + : SkillTypes.LIGHT_VESTS; const pointsToAddToVestSkill = repairDetails.repairPoints * this.repairConfig.armorKitSkillPointGainPerRepairPointMultiplier; - this.questHelper.rewardSkillPoints(sessionId, pmcData, vestSkillToLevel, pointsToAddToVestSkill); + this.profileHelper.addSkillPointsToPlayer(pmcData, vestSkillToLevel, pointsToAddToVestSkill); } // Handle giving INT to player - differs if using kit/trader and weapon vs armor @@ -190,7 +192,7 @@ export class RepairService intellectGainedFromRepair = Math.min(repairDetails.repairAmount / 10, this.repairConfig.maxIntellectGainPerRepair.trader); } - this.questHelper.rewardSkillPoints(sessionId, pmcData, SkillTypes.INTELLECT, intellectGainedFromRepair); + this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.INTELLECT, intellectGainedFromRepair); } /**