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

# Conflicts:
#	project/src/controllers/HealthController.ts
This commit is contained in:
Dev 2023-11-18 14:13:08 +00:00
commit 0d205ed50c
7 changed files with 93 additions and 47 deletions

View File

@ -58,7 +58,8 @@
"5a0c27731526d80618476ac4",
"57864c8c245977548867e7f1",
"6087e570b998180e9f76dc24",
"6391fcf5744e45201147080f"
"6391fcf5744e45201147080f",
"614451b71e5874611e2c7ae5"
]
},
"pocketLoot": {
@ -158,7 +159,8 @@
"5a0c27731526d80618476ac4",
"6087e570b998180e9f76dc24",
"63495c500c297e20065a08b1",
"6391fcf5744e45201147080f"
"6391fcf5744e45201147080f",
"614451b71e5874611e2c7ae5"
]
},
"dynamicLoot": {

View File

@ -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": [

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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
}
/**