diff --git a/project/assets/configs/pmc.json b/project/assets/configs/pmc.json index 47dd5be1..f7686df9 100644 --- a/project/assets/configs/pmc.json +++ b/project/assets/configs/pmc.json @@ -58,7 +58,8 @@ "5a0c27731526d80618476ac4", "57864c8c245977548867e7f1", "6087e570b998180e9f76dc24", - "6391fcf5744e45201147080f" + "6391fcf5744e45201147080f", + "614451b71e5874611e2c7ae5" ] }, "pocketLoot": { @@ -158,7 +159,8 @@ "5a0c27731526d80618476ac4", "6087e570b998180e9f76dc24", "63495c500c297e20065a08b1", - "6391fcf5744e45201147080f" + "6391fcf5744e45201147080f", + "614451b71e5874611e2c7ae5" ] }, "dynamicLoot": { diff --git a/project/assets/configs/quest.json b/project/assets/configs/quest.json index 189b4e40..adcb9f59 100644 --- a/project/assets/configs/quest.json +++ b/project/assets/configs/quest.json @@ -144,12 +144,12 @@ "minPlayerLevel": 5, "rewardScaling": { "levels": [1, 10, 20, 30, 40, 50, 60], - "experience": [1000, 2000, 8000, 14000, 20000, 24000, 28000], - "roubles": [11000, 20000, 45000, 60000, 77000, 95000, 115000], + "experience": [1000, 2000, 8000, 13000, 19000, 24000, 30000], + "roubles": [11000, 20000, 32000, 45000, 58000, 70000, 82000], "items": [2, 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], + "skillRewardChance": [0, 0.01, 0.05, 0.1, 0.15, 0.2, 0.2], "skillPointReward": [10, 15, 20, 25, 30, 35, 40] }, "locations": { @@ -262,7 +262,8 @@ ], "questConfig": { "Exploration": { - "maxExtracts": 3, + "maxExtracts": 5, + "maxExtractsWithSpecificExit": 3, "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "specificExits": { "probability": 0.25, @@ -279,7 +280,7 @@ "Completion": { "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "minRequestedAmount": 1, - "maxRequestedAmount": 5, + "maxRequestedAmount": 4, "minRequestedBulletAmount": 20, "maxRequestedBulletAmount": 60, "useWhitelist": true, @@ -782,12 +783,12 @@ "minPlayerLevel": 15, "rewardScaling": { "levels": [1, 10, 20, 30, 40, 50, 60], - "experience": [5000, 25000, 40000, 110000, 220000, 370000, 720000], - "roubles": [50000, 150000, 300000, 425000, 550000, 675000, 850000], + "experience": [5000, 15000, 27000, 80000, 142000, 220000, 290000], + "roubles": [20000, 50000, 175000, 350000, 540000, 710000, 880000], "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], + "skillRewardChance": [0, 0.05, 0.1, 0.15, 0.25, 0.3, 0.3], "skillPointReward": [25, 35, 45, 50, 55, 60, 65] }, "locations": { @@ -901,7 +902,8 @@ "questConfig": { "Exploration": { "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], - "maxExtracts": 10, + "maxExtracts": 25, + "maxExtractsWithSpecificExit": 12, "specificExits": { "probability": 0.4, "passageRequirementWhitelist": [ @@ -1486,11 +1488,11 @@ "numQuests": 1, "minPlayerLevel": 1, "rewardScaling": { - "levels": [1, 20, 45, 100], - "experience": [1000, 4000, 20000, 80000], - "roubles": [6000, 10000, 100000, 250000], - "items": [3, 3, 4, 4], - "reputation": [0.02, 0.02, 0.05, 0.05], + "levels": [1, 10, 20, 30, 40, 50, 60], + "experience": [0, 0, 0, 0, 0, 0, 0], + "roubles": [11000, 20000, 32000, 45000, 58000, 70000, 82000], + "items": [2, 3, 3, 3, 3, 4, 4], + "reputation": [0.02, 0.02, 0.03, 0.03, 0.04, 0.04, 0.05], "rewardSpread": 0.5, "skillRewardChance": [0, 0, 0, 0, 0, 0, 0], "skillPointReward": [10, 15, 20, 25, 30, 35, 40] @@ -1522,7 +1524,8 @@ "questConfig": { "Exploration": { "possibleSkillRewards": ["Endurance", "Strength", "Vitality"], - "maxExtracts": 3, + "maxExtracts": 4, + "maxExtractsWithSpecificExit": 2, "specificExits": { "probability": 0.25, "passageRequirementWhitelist": [ diff --git a/project/src/controllers/HealthController.ts b/project/src/controllers/HealthController.ts index afc3c39a..9f4108ed 100644 --- a/project/src/controllers/HealthController.ts +++ b/project/src/controllers/HealthController.ts @@ -91,8 +91,8 @@ export class HealthController else { // Get max healing from db - const maxHp = this.itemHelper.getItem(healingItemToUse._tpl)[1]._props.MaxHpResource; - healingItemToUse.upd.MedKit = { HpResource: maxHp - request.count }; // Subtract amount used from max + const maxhp = this.itemHelper.getItem(healingItemToUse._tpl)[1]._props.MaxHpResource; + healingItemToUse.upd.MedKit = { HpResource: maxhp - request.count }; // Subtract amout used from max } // Resource in medkit is spent, delete it @@ -169,10 +169,13 @@ export class HealthController const payMoneyRequest: IProcessBuyTradeRequestData = { Action: healthTreatmentRequest.Action, tid: Traders.THERAPIST, + // eslint-disable-next-line @typescript-eslint/naming-convention scheme_items: healthTreatmentRequest.items, type: "", + // eslint-disable-next-line @typescript-eslint/naming-convention item_id: "", count: 0, + // eslint-disable-next-line @typescript-eslint/naming-convention scheme_id: 0, }; @@ -188,8 +191,12 @@ export class HealthController const partRequest: BodyPart = healthTreatmentRequest.difference.BodyParts[bodyPartKey]; const profilePart = pmcData.Health.BodyParts[bodyPartKey]; - // Set profile body part to max - profilePart.Health.Current = profilePart.Health.Maximum; + // Bodypart healing is chosen when part request hp is above 0 + if (partRequest.Health > 0) + { + // Heal bodypart + profilePart.Health.Current = profilePart.Health.Maximum; + } // Check for effects to remove if (partRequest.Effects?.length > 0) @@ -220,6 +227,7 @@ export class HealthController * @param info Request data * @param sessionID */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars public applyWorkoutChanges(pmcData: IPmcData, info: IWorkoutData, sessionId: string): void { // https://dev.sp-tarkov.com/SPT-AKI/Server/issues/2674 diff --git a/project/src/generators/BotEquipmentModGenerator.ts b/project/src/generators/BotEquipmentModGenerator.ts index c666c8b9..a8b2cca4 100644 --- a/project/src/generators/BotEquipmentModGenerator.ts +++ b/project/src/generators/BotEquipmentModGenerator.ts @@ -654,7 +654,7 @@ export class BotEquipmentModGenerator modTpl, modSlot, ); - if (!modCompatibilityResult.incompatible) + if (!modCompatibilityResult.incompatible && !this.weaponModComboIsIncompatible(weapon, modTpl)) { found = true; @@ -666,6 +666,7 @@ export class BotEquipmentModGenerator if (modCompatibilityResult.incompatible && parentSlot._required) { this.logger.debug(modCompatibilityResult.reason); + this.logger.debug(`Weapon: ${weapon.map(x => `${x._tpl} ${x.slotId ?? ""}`).join(",")}`) } } @@ -699,6 +700,23 @@ export class BotEquipmentModGenerator return this.itemHelper.getItem(modTpl); } + /** + * Temp fix to prevent certain combinations of weapons with mods that are known to be incompatible + * @param weapon Weapon + * @param modTpl Mod to check compatibility with weapon + * @returns True if incompatible + */ + protected weaponModComboIsIncompatible(weapon: Item[], modTpl: string): boolean + { + // STM-9 + AR-15 Lone Star Ion Lite handguard + if (weapon[0]._tpl === "60339954d62c9b14ed777c06" && modTpl === "5d4405f0a4b9361e6a4e6bd9") + { + return true; + } + + return false; + } + /** * Create a mod item with parameters as properties * @param modId _id diff --git a/project/src/generators/RepeatableQuestGenerator.ts b/project/src/generators/RepeatableQuestGenerator.ts index 97a73fdc..75128408 100644 --- a/project/src/generators/RepeatableQuestGenerator.ts +++ b/project/src/generators/RepeatableQuestGenerator.ts @@ -620,25 +620,26 @@ export class RepeatableQuestGenerator * A repeatable quest, besides some more or less static components, exists of reward and condition (see assets/database/templates/repeatableQuests.json) * This is a helper method for GenerateCompletionQuest to create a completion condition (of which a completion quest theoretically can have many) * - * @param {string} targetItemId id of the item to request + * @param {string} itemTpl id of the item to request * @param {integer} value amount of items of this specific type to request * @returns {object} object of "Completion"-condition */ - protected generateCompletionAvailableForFinish(targetItemId: string, value: number): ICompletionAvailableFor + protected generateCompletionAvailableForFinish(itemTpl: string, value: number): ICompletionAvailableFor { let minDurability = 0; let onlyFoundInRaid = true; if ( - this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.WEAPON) - || this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.ARMOR) + this.itemHelper.isOfBaseclass(itemTpl, BaseClasses.WEAPON) + || this.itemHelper.isOfBaseclass(itemTpl, BaseClasses.ARMOR) ) { - minDurability = 80; + minDurability = this.randomUtil.getArrayValue([60, 80]); } + // By default all collected items must be FiR, except dog tags if ( - this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.DOG_TAG_USEC) - || this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.DOG_TAG_BEAR) + this.itemHelper.isOfBaseclass(itemTpl, BaseClasses.DOG_TAG_USEC) + || this.itemHelper.isOfBaseclass(itemTpl, BaseClasses.DOG_TAG_BEAR) ) { onlyFoundInRaid = false; @@ -651,7 +652,7 @@ export class RepeatableQuestGenerator dynamicLocale: true, index: 0, visibilityConditions: [], - target: [targetItemId], + target: [itemTpl], value: value, minDurability: minDurability, maxDurability: 100, @@ -680,6 +681,7 @@ export class RepeatableQuestGenerator ): IExploration { const explorationConfig = repeatableConfig.questConfig.Exploration; + const requiresSpecificExtract = Math.random() < repeatableConfig.questConfig.Exploration.specificExits.probability; if (Object.keys(questTypePool.pool.Exploration.locations).length === 0) { @@ -696,7 +698,8 @@ export class RepeatableQuestGenerator // remove the location from the available pool delete questTypePool.pool.Exploration.locations[locationKey]; - const numExtracts = this.randomUtil.randInt(1, explorationConfig.maxExtracts + 1); + // Different max extract count when specific extract needed + const numExtracts = this.randomUtil.randInt(1, requiresSpecificExtract ? explorationConfig.maxExtractsWithSpecificExit : explorationConfig.maxExtracts + 1); const quest = this.generateRepeatableTemplate("Exploration", traderId, repeatableConfig.side) as IExploration; @@ -715,7 +718,8 @@ export class RepeatableQuestGenerator quest.conditions.AvailableForFinish[0]._props.id = this.objectId.generate(); quest.location = this.getQuestLocationByMapId(locationKey); - if (Math.random() < repeatableConfig.questConfig.Exploration.specificExits.probability) + + if (requiresSpecificExtract) { // Filter by whitelist, it's also possible that the field "PassageRequirement" does not exist (e.g. Shoreline) // Scav exits are not listed at all in locations.base currently. If that changes at some point, additional filtering will be required @@ -875,27 +879,34 @@ export class RepeatableQuestGenerator let roublesBudget = rewardRoubles; let rewardItemPool = this.chooseRewardItemsWithinBudget(repeatableConfig, roublesBudget, traderId); - // Add xp reward const rewards: IRewards = { Started: [], - Success: [{ value: rewardXP, type: "Experience", index: 0 }], + Success: [], Fail: [], }; + let rewardIndex = 0; + // Add xp reward + if (rewardXP > 0) + { + rewards.Success.push({ value: rewardXP, type: "Experience", index: rewardIndex }); + rewardIndex++; + } + // Add money reward - if (traderId === Traders.PEACEKEEPER) + if (traderId === Traders.PEACEKEEPER || traderId === Traders.FENCE) { // convert to equivalent dollars rewards.Success.push( - this.generateRewardItem(Money.EUROS, this.handbookHelper.fromRUB(rewardRoubles, Money.EUROS), 1), + this.generateRewardItem(Money.EUROS, this.handbookHelper.fromRUB(rewardRoubles, Money.EUROS), rewardIndex), ); } else { - rewards.Success.push(this.generateRewardItem(Money.ROUBLES, rewardRoubles, 1)); + rewards.Success.push(this.generateRewardItem(Money.ROUBLES, rewardRoubles, rewardIndex)); } + rewardIndex++; - let index = 2; if (rewardItemPool.length > 0) { let weaponRewardCount = 0; @@ -946,14 +957,15 @@ export class RepeatableQuestGenerator itemCount = 2; } - rewards.Success.push(this.generateRewardItem(itemSelected._id, itemCount, index, children)); + rewards.Success.push(this.generateRewardItem(itemSelected._id, itemCount, rewardIndex, children)); + rewardIndex++; + const itemCost = (this.itemHelper.isOfBaseclass(itemSelected._id, BaseClasses.WEAPON)) ? this.itemHelper.getItemMaxPrice(children[0]._tpl) // use if preset is not default : this.itemHelper.getWeaponPresetPrice(children[0], children, this.itemHelper.getStaticItemPrice(itemSelected._id)) : this.itemHelper.getStaticItemPrice(itemSelected._id); roublesBudget -= itemCount * itemCost; - index += 1; - // if we still have budget narrow down the items + // If we still have budget narrow down possible items if (roublesBudget > 0) { // Filter possible reward items to only items with a price below the remaining budget @@ -975,18 +987,19 @@ export class RepeatableQuestGenerator // Add rep reward to rewards array if (rewardReputation > 0) { - const reward: IReward = { target: traderId, value: rewardReputation, type: "TraderStanding", index: index }; + const reward: IReward = { target: traderId, value: rewardReputation, type: "TraderStanding", index: rewardIndex }; rewards.Success.push(reward); + rewardIndex++; } + // Chance of adding skill reward if (this.randomUtil.getChance100(skillRewardChance * 100)) { - index++; const reward: IReward = { target: this.randomUtil.getArrayValue(questConfig.possibleSkillRewards), value: skillPointReward, type: "Skill", - index: index, + index: rewardIndex, }; rewards.Success.push(reward); } diff --git a/project/src/models/spt/config/IQuestConfig.ts b/project/src/models/spt/config/IQuestConfig.ts index 5e664496..a761743a 100644 --- a/project/src/models/spt/config/IQuestConfig.ts +++ b/project/src/models/spt/config/IQuestConfig.ts @@ -89,8 +89,9 @@ export interface IRepeatableQuestTypesConfig export interface IExploration extends IBaseQuestConfig { - maxExtracts: number; - specificExits: ISpecificExits; + maxExtracts: number + maxExtractsWithSpecificExit: number + specificExits: ISpecificExits } export interface ISpecificExits diff --git a/project/src/routers/EventOutputHolder.ts b/project/src/routers/EventOutputHolder.ts index 3903a037..17676f21 100644 --- a/project/src/routers/EventOutputHolder.ts +++ b/project/src/routers/EventOutputHolder.ts @@ -184,7 +184,8 @@ export class EventOutputHolder } } - return productions; + // Return null of there's no crafts to send to client to match live behaviour + return (Object.keys(productions).length > 0) ? productions : null } /**