0
0
mirror of https://github.com/sp-tarkov/server.git synced 2025-02-13 09:50:43 -05:00

Add support for daily quests to request specific weapon kill requirements on elimination quests

This commit is contained in:
Dev 2023-10-11 17:04:18 +01:00
parent 67324edd9c
commit c2a6ca678e
5 changed files with 512 additions and 26 deletions

View File

@ -219,7 +219,7 @@
}
}
],
"bodyPartProb": 0.4,
"bodyPartProb": 0.2,
"bodyParts": [{
"key": "Head",
"relativeProbability": 1,
@ -244,7 +244,77 @@
"maxDist": 100,
"minDist": 20,
"maxKills": 3,
"minKills": 2
"minKills": 2,
"weaponRequirementProb": 0.9,
"weaponCategoryRequirementProb": 0.2,
"weaponCategoryRequirements": [{
"key": "Shotgun",
"relativeProbability": 15,
"data": ["5b5f794b86f77409407a7f92"]
}, {
"key": "Pistol",
"relativeProbability": 10,
"data": ["5b5f792486f77447ed5636b3"]
}, {
"key": "AssaultRifle",
"relativeProbability": 3,
"data": ["5b5f78fc86f77409407a7f90"]
}, {
"key": "AssaultCarbine",
"relativeProbability": 15,
"data": ["5b5f78e986f77447ed5636b1"]
}, {
"key": "MarksmanRifle",
"relativeProbability": 1,
"data": ["5447b6194bdc2d67278b4567"]
}, {
"key": "SniperRifle",
"relativeProbability": 1,
"data": ["5447b6254bdc2dc3278b4568"]
}, {
"key": "SMG",
"relativeProbability": 1,
"data": ["5b5f796a86f774093f2ed3c0"]
}, {
"key": "Melee",
"relativeProbability": 0,
"data": ["5b5f7a0886f77409407a7f96"]
}, {
"key": "DMR",
"relativeProbability": 1,
"data": ["5b5f791486f774093f2ed3be"]
}
],
"weaponRequirements": [{
"key": "Shotgun",
"relativeProbability": 10,
"data": ["5447b6094bdc2dc3278b4567"]
}, {
"key": "Pistol",
"relativeProbability": 10,
"data": ["5447b5cf4bdc2d65278b4567"]
}, {
"key": "AssaultRifle",
"relativeProbability": 3,
"data": ["5447b5f14bdc2d61278b4567"]
}, {
"key": "AssaultCarbine",
"relativeProbability": 10,
"data": ["5447b5fc4bdc2d87278b4567"]
}, {
"key": "MarksmanRifle",
"relativeProbability": 1,
"data": ["5447b6194bdc2d67278b4567"]
}, {
"key": "SniperRifle",
"relativeProbability": 1,
"data": ["5447b6254bdc2dc3278b4568"]
}, {
"key": "SMG",
"relativeProbability": 1,
"data": ["5447b5e04bdc2d62278b4567"]
}
]
}, {
"levelRange": {
"min": 16,
@ -312,7 +382,7 @@
}
}, {
"key": "bossBoar",
"relativeProbability": 0.5,
"relativeProbability": 0,
"data": {
"isBoss": true
}
@ -349,7 +419,77 @@
"maxDist": 200,
"minDist": 20,
"maxKills": 5,
"minKills": 2
"minKills": 2,
"weaponRequirementProb": 0.9,
"weaponCategoryRequirementProb": 0.2,
"weaponCategoryRequirements": [{
"key": "Shotgun",
"relativeProbability": 10,
"data": ["5b5f794b86f77409407a7f92"]
}, {
"key": "Pistol",
"relativeProbability": 10,
"data": ["5b5f792486f77447ed5636b3"]
}, {
"key": "AssaultRifle",
"relativeProbability": 10,
"data": ["5b5f78fc86f77409407a7f90"]
}, {
"key": "AssaultCarbine",
"relativeProbability": 15,
"data": ["5b5f78e986f77447ed5636b1"]
}, {
"key": "MarksmanRifle",
"relativeProbability": 8,
"data": ["5447b6194bdc2d67278b4567"]
}, {
"key": "SniperRifle",
"relativeProbability": 8,
"data": ["5447b6254bdc2dc3278b4568"]
}, {
"key": "SMG",
"relativeProbability": 8,
"data": ["5b5f796a86f774093f2ed3c0"]
}, {
"key": "Melee",
"relativeProbability": 1,
"data": ["5b5f7a0886f77409407a7f96"]
}, {
"key": "DMR",
"relativeProbability": 8,
"data": ["5b5f791486f774093f2ed3be"]
}
],
"weaponRequirements": [{
"key": "Shotgun",
"relativeProbability": 10,
"data": ["5447b6094bdc2dc3278b4567"]
}, {
"key": "Pistol",
"relativeProbability": 10,
"data": ["5447b5cf4bdc2d65278b4567"]
}, {
"key": "AssaultRifle",
"relativeProbability": 3,
"data": ["5447b5f14bdc2d61278b4567"]
}, {
"key": "AssaultCarbine",
"relativeProbability": 5,
"data": ["5447b5fc4bdc2d87278b4567"]
}, {
"key": "MarksmanRifle",
"relativeProbability": 1,
"data": ["5447b6194bdc2d67278b4567"]
}, {
"key": "SniperRifle",
"relativeProbability": 1,
"data": ["5447b6254bdc2dc3278b4568"]
}, {
"key": "SMG",
"relativeProbability": 1,
"data": ["5447b5e04bdc2d62278b4567"]
}
]
}
]
},
@ -512,7 +652,7 @@
}
}
],
"bodyPartProb": 0.3,
"bodyPartProb": 0.2,
"bodyParts": [{
"key": "Head",
"relativeProbability": 1,
@ -537,7 +677,77 @@
"maxDist": 100,
"minDist": 20,
"maxKills": 10,
"minKills": 5
"minKills": 5,
"weaponRequirementProb": 0.9,
"weaponCategoryRequirementProb": 0.2,
"weaponCategoryRequirements": [{
"key": "Shotgun",
"relativeProbability": 10,
"data": ["5b5f794b86f77409407a7f92"]
}, {
"key": "Pistol",
"relativeProbability": 10,
"data": ["5b5f792486f77447ed5636b3"]
}, {
"key": "AssaultRifle",
"relativeProbability": 3,
"data": ["5b5f78fc86f77409407a7f90"]
}, {
"key": "AssaultCarbine",
"relativeProbability": 5,
"data": ["5b5f78e986f77447ed5636b1"]
}, {
"key": "MarksmanRifle",
"relativeProbability": 1,
"data": ["5447b6194bdc2d67278b4567"]
}, {
"key": "SniperRifle",
"relativeProbability": 1,
"data": ["5447b6254bdc2dc3278b4568"]
}, {
"key": "SMG",
"relativeProbability": 1,
"data": ["5b5f796a86f774093f2ed3c0"]
}, {
"key": "Melee",
"relativeProbability": 1,
"data": ["5b5f7a0886f77409407a7f96"]
}, {
"key": "DMR",
"relativeProbability": 1,
"data": ["5b5f791486f774093f2ed3be"]
}
],
"weaponRequirements": [{
"key": "Shotgun",
"relativeProbability": 10,
"data": ["5447b6094bdc2dc3278b4567"]
}, {
"key": "Pistol",
"relativeProbability": 10,
"data": ["5447b5cf4bdc2d65278b4567"]
}, {
"key": "AssaultRifle",
"relativeProbability": 3,
"data": ["5447b5f14bdc2d61278b4567"]
}, {
"key": "AssaultCarbine",
"relativeProbability": 5,
"data": ["5447b5fc4bdc2d87278b4567"]
}, {
"key": "MarksmanRifle",
"relativeProbability": 1,
"data": ["5447b6194bdc2d67278b4567"]
}, {
"key": "SniperRifle",
"relativeProbability": 1,
"data": ["5447b6254bdc2dc3278b4568"]
}, {
"key": "SMG",
"relativeProbability": 1,
"data": ["5447b5e04bdc2d62278b4567"]
}
]
}, {
"levelRange": {
"min": 16,
@ -605,7 +815,7 @@
}
}, {
"key": "bossBoar",
"relativeProbability": 0.5,
"relativeProbability": 0,
"data": {
"isBoss": true
}
@ -642,7 +852,77 @@
"maxDist": 200,
"minDist": 20,
"maxKills": 15,
"minKills": 5
"minKills": 5,
"weaponRequirementProb": 0.9,
"weaponCategoryRequirementProb": 0.2,
"weaponCategoryRequirements": [{
"key": "Shotgun",
"relativeProbability": 10,
"data": ["5b5f794b86f77409407a7f92"]
}, {
"key": "Pistol",
"relativeProbability": 10,
"data": ["5b5f792486f77447ed5636b3"]
}, {
"key": "AssaultRifle",
"relativeProbability": 3,
"data": ["5b5f78fc86f77409407a7f90"]
}, {
"key": "AssaultCarbine",
"relativeProbability": 5,
"data": ["5b5f78e986f77447ed5636b1"]
}, {
"key": "MarksmanRifle",
"relativeProbability": 1,
"data": ["5447b6194bdc2d67278b4567"]
}, {
"key": "SniperRifle",
"relativeProbability": 1,
"data": ["5447b6254bdc2dc3278b4568"]
}, {
"key": "SMG",
"relativeProbability": 1,
"data": ["5b5f796a86f774093f2ed3c0"]
}, {
"key": "Melee",
"relativeProbability": 1,
"data": ["5b5f7a0886f77409407a7f96"]
}, {
"key": "DMR",
"relativeProbability": 1,
"data": ["5b5f791486f774093f2ed3be"]
}
],
"weaponRequirements": [{
"key": "Shotgun",
"relativeProbability": 10,
"data": ["5447b6094bdc2dc3278b4567"]
}, {
"key": "Pistol",
"relativeProbability": 10,
"data": ["5447b5cf4bdc2d65278b4567"]
}, {
"key": "AssaultRifle",
"relativeProbability": 3,
"data": ["5447b5f14bdc2d61278b4567"]
}, {
"key": "AssaultCarbine",
"relativeProbability": 5,
"data": ["5447b5fc4bdc2d87278b4567"]
}, {
"key": "MarksmanRifle",
"relativeProbability": 1,
"data": ["5447b6194bdc2d67278b4567"]
}, {
"key": "SniperRifle",
"relativeProbability": 1,
"data": ["5447b6254bdc2dc3278b4568"]
}, {
"key": "SMG",
"relativeProbability": 1,
"data": ["5447b5e04bdc2d62278b4567"]
}
]
}
]
},
@ -719,7 +999,7 @@
}
}
],
"bodyPartProb": 0.4,
"bodyPartProb": 0.2,
"bodyParts": [{
"key": "Head",
"relativeProbability": 1,
@ -744,7 +1024,77 @@
"maxDist": 100,
"minDist": 20,
"maxKills": 3,
"minKills": 1
"minKills": 1,
"weaponRequirementProb": 0.9,
"weaponCategoryRequirementProb": 0.2,
"weaponCategoryRequirements": [{
"key": "Shotgun",
"relativeProbability": 10,
"data": ["5b5f794b86f77409407a7f92"]
}, {
"key": "Pistol",
"relativeProbability": 10,
"data": ["5b5f792486f77447ed5636b3"]
}, {
"key": "AssaultRifle",
"relativeProbability": 3,
"data": ["5b5f78fc86f77409407a7f90"]
}, {
"key": "AssaultCarbine",
"relativeProbability": 5,
"data": ["5b5f78e986f77447ed5636b1"]
}, {
"key": "MarksmanRifle",
"relativeProbability": 1,
"data": ["5447b6194bdc2d67278b4567"]
}, {
"key": "SniperRifle",
"relativeProbability": 1,
"data": ["5447b6254bdc2dc3278b4568"]
}, {
"key": "SMG",
"relativeProbability": 1,
"data": ["5b5f796a86f774093f2ed3c0"]
}, {
"key": "Melee",
"relativeProbability": 1,
"data": ["5b5f7a0886f77409407a7f96"]
}, {
"key": "DMR",
"relativeProbability": 1,
"data": ["5b5f791486f774093f2ed3be"]
}
],
"weaponRequirements": [{
"key": "Shotgun",
"relativeProbability": 10,
"data": ["5447b6094bdc2dc3278b4567"]
}, {
"key": "Pistol",
"relativeProbability": 10,
"data": ["5447b5cf4bdc2d65278b4567"]
}, {
"key": "AssaultRifle",
"relativeProbability": 3,
"data": ["5447b5f14bdc2d61278b4567"]
}, {
"key": "AssaultCarbine",
"relativeProbability": 5,
"data": ["5447b5fc4bdc2d87278b4567"]
}, {
"key": "MarksmanRifle",
"relativeProbability": 1,
"data": ["5447b6194bdc2d67278b4567"]
}, {
"key": "SniperRifle",
"relativeProbability": 1,
"data": ["5447b6254bdc2dc3278b4568"]
}, {
"key": "SMG",
"relativeProbability": 1,
"data": ["5447b5e04bdc2d62278b4567"]
}
]
}, {
"levelRange": {
"min": 16,
@ -784,7 +1134,77 @@
"maxDist": 200,
"minDist": 20,
"maxKills": 5,
"minKills": 2
"minKills": 2,
"weaponRequirementProb": 0.9,
"weaponCategoryRequirementProb": 0.2,
"weaponCategoryRequirements": [{
"key": "Shotgun",
"relativeProbability": 10,
"data": ["5b5f794b86f77409407a7f92"]
}, {
"key": "Pistol",
"relativeProbability": 10,
"data": ["5b5f792486f77447ed5636b3"]
}, {
"key": "AssaultRifle",
"relativeProbability": 3,
"data": ["5b5f78fc86f77409407a7f90"]
}, {
"key": "AssaultCarbine",
"relativeProbability": 5,
"data": ["5b5f78e986f77447ed5636b1"]
}, {
"key": "MarksmanRifle",
"relativeProbability": 1,
"data": ["5447b6194bdc2d67278b4567"]
}, {
"key": "SniperRifle",
"relativeProbability": 1,
"data": ["5447b6254bdc2dc3278b4568"]
}, {
"key": "SMG",
"relativeProbability": 1,
"data": ["5b5f796a86f774093f2ed3c0"]
}, {
"key": "Melee",
"relativeProbability": 1,
"data": ["5b5f7a0886f77409407a7f96"]
}, {
"key": "DMR",
"relativeProbability": 1,
"data": ["5b5f791486f774093f2ed3be"]
}
],
"weaponRequirements": [{
"key": "Shotgun",
"relativeProbability": 10,
"data": ["5447b6094bdc2dc3278b4567"]
}, {
"key": "Pistol",
"relativeProbability": 10,
"data": ["5447b5cf4bdc2d65278b4567"]
}, {
"key": "AssaultRifle",
"relativeProbability": 3,
"data": ["5447b5f14bdc2d61278b4567"]
}, {
"key": "AssaultCarbine",
"relativeProbability": 5,
"data": ["5447b5fc4bdc2d87278b4567"]
}, {
"key": "MarksmanRifle",
"relativeProbability": 1,
"data": ["5447b6194bdc2d67278b4567"]
}, {
"key": "SniperRifle",
"relativeProbability": 1,
"data": ["5447b6254bdc2dc3278b4568"]
}, {
"key": "SMG",
"relativeProbability": 1,
"data": ["5447b5e04bdc2d62278b4567"]
}
]
}
]
},
@ -804,6 +1224,6 @@
"Lighthouse": "5704e4dad2720bb55b8b4567",
"laboratory": "5b0fc42d86f7744a585f9105",
"RezervBase": "5704e5fad2720bc05b8b4567",
"TarkovStreets": "5714dc692459777137212e12"
"TarkovStreets": "5714dc692459777137212e12"
}
}

View File

@ -73,7 +73,10 @@ export interface IEliminationTargetPool
bossKilla?: ITargetLocation
bossSanitar?: ITargetLocation
bossTagilla?: ITargetLocation
bossKojaniy?: ITargetLocation
bossKnight?: ITargetLocation
bossZryachiy?: ITargetLocation
bossBoar?: ITargetLocation
bossBoarSniper?: ITargetLocation
}
export interface ITargetLocation
@ -595,6 +598,8 @@ export class RepeatableQuestController
const locationsConfig = repeatableConfig.locations;
let targetsConfig = this.probabilityObjectArray(eliminationConfig.targets);
const bodypartsConfig = this.probabilityObjectArray(eliminationConfig.bodyParts);
const weaponCategoryRequirementConfig = this.probabilityObjectArray(eliminationConfig.weaponCategoryRequirements);
const weaponRequirementConfig = this.probabilityObjectArray(eliminationConfig.weaponRequirements);
// the difficulty of the quest varies in difficulty depending on the condition
// possible conditions are
@ -628,15 +633,15 @@ export class RepeatableQuestController
const maxKillDifficulty = eliminationConfig.maxKills;
function difficultyWeighing(target: number, bodyPart: number, dist: number, kill: number): number
function difficultyWeighing(target: number, bodyPart: number, dist: number, kill: number, weaponRequirement: number): number
{
return Math.sqrt(Math.sqrt(target) + bodyPart + dist) * kill;
return Math.sqrt(Math.sqrt(target) + bodyPart + dist + weaponRequirement) * kill;
}
targetsConfig = targetsConfig.filter(x => Object.keys(questTypePool.pool.Elimination.targets).includes(x.key));
if (targetsConfig.length === 0 || targetsConfig.every(x => x.data.isBoss))
{
// there are no more targets left for elimination; delete it as a possible quest type
// There are no more targets left for elimination; delete it as a possible quest type
// also if only bosses are left we need to leave otherwise it's a guaranteed boss elimination
// -> then it would not be a quest with low probability anymore
questTypePool.types = questTypePool.types.filter(t => t !== "Elimination");
@ -646,7 +651,8 @@ export class RepeatableQuestController
const targetKey = targetsConfig.draw()[0];
const targetDifficulty = 1 / targetsConfig.probability(targetKey);
let locations = questTypePool.pool.Elimination.targets[targetKey].locations;
let locations: string[] = questTypePool.pool.Elimination.targets[targetKey].locations;
// we use any as location if "any" is in the pool and we do not hit the specific location random
// we use any also if the random condition is not met in case only "any" was in the pool
let locationKey = "any";
@ -726,18 +732,39 @@ export class RepeatableQuestController
distanceDifficulty = maxDistDifficulty * distance / eliminationConfig.maxDist;
}
// draw how many npcs are required to be killed
let allowedWeaponsCategory: string = undefined;
if (eliminationConfig.weaponCategoryRequirementProb > Math.random())
{
// Pick a weighted weapon categroy
const weaponRequirement = weaponCategoryRequirementConfig.draw(1, false);
// Get the hideout id value stored in the .data array
allowedWeaponsCategory = weaponCategoryRequirementConfig.data(weaponRequirement[0])[0];
}
// Only allow a specific weapon requirement if a weapon category was not chosen
let allowedWeapon: string = undefined;
if (!allowedWeaponsCategory && eliminationConfig.weaponRequirementProb > Math.random())
{
const weaponRequirement = weaponRequirementConfig.draw(1, false);
const allowedWeaponsCategory = weaponRequirementConfig.data(weaponRequirement[0])[0];
const allowedWeapons = this.itemHelper.getItemTplsOfBaseType(allowedWeaponsCategory);
allowedWeapon = this.randomUtil.getArrayValue(allowedWeapons);
}
// Draw how many npm kills are required
const kills = this.randomUtil.randInt(eliminationConfig.minKills, eliminationConfig.maxKills + 1);
const killDifficulty = kills;
// not perfectly happy here; we give difficulty = 1 to the quest reward generation when we have the most diffucult mission
// e.g. killing reshala 5 times from a distance of 200m with a headshot.
const maxDifficulty = difficultyWeighing(1, 1, 1, 1);
const maxDifficulty = difficultyWeighing(1, 1, 1, 1, 1);
const curDifficulty = difficultyWeighing(
targetDifficulty / maxTargetDifficulty,
bodyPartDifficulty / maxBodyPartsDifficulty,
distanceDifficulty / maxDistDifficulty,
killDifficulty / maxKillDifficulty
killDifficulty / maxKillDifficulty,
(allowedWeaponsCategory || allowedWeapon) ? 1 : 0
);
// aforementioned issue makes it a bit crazy since now all easier quests give significantly lower rewards than Completion / Exploration
@ -751,9 +778,9 @@ export class RepeatableQuestController
quest.conditions.AvailableForFinish[0]._props.counter.conditions = [];
if (locationKey !== "any")
{
quest.conditions.AvailableForFinish[0]._props.counter.conditions.push(this.generateEliminationLocation(locationsConfig[locationKey]));
quest.conditions.AvailableForFinish[0]._props.counter.conditions.push(this.generateEliminationLocation(locationsConfig[locationKey], allowedWeapon, allowedWeaponsCategory));
}
quest.conditions.AvailableForFinish[0]._props.counter.conditions.push(this.generateEliminationCondition(targetKey, bodyPartsToClient, distance));
quest.conditions.AvailableForFinish[0]._props.counter.conditions.push(this.generateEliminationCondition(targetKey, bodyPartsToClient, distance, allowedWeapon, allowedWeaponsCategory));
quest.conditions.AvailableForFinish[0]._props.value = kills;
quest.conditions.AvailableForFinish[0]._props.id = this.objectId.generate();
quest.location = this.getQuestLocationByMapId(locationKey);
@ -852,10 +879,9 @@ export class RepeatableQuestController
* @param {string} location the location on which to fulfill the elimination quest
* @returns {object} object of "Elimination"-location-subcondition
*/
protected generateEliminationLocation(location: string[]): IEliminationCondition
protected generateEliminationLocation(location: string[], allowedWeapon: string, allowedWeaponCategory: string): IEliminationCondition
{
return {
const propsObject: IEliminationCondition = {
_props: {
target: location,
id: this.objectId.generate(),
@ -863,6 +889,18 @@ export class RepeatableQuestController
},
_parent: "Location"
};
if (allowedWeapon)
{
propsObject._props.weapon = [allowedWeapon];
}
if (allowedWeaponCategory)
{
propsObject._props.weaponCategories = [allowedWeaponCategory];
}
return propsObject;
}
/**
@ -874,7 +912,7 @@ export class RepeatableQuestController
* @param {number} distance distance from which to kill (currently only >= supported)
* @returns {object} object of "Elimination"-kill-subcondition
*/
protected generateEliminationCondition(target: string, bodyPart: string[], distance: number): IEliminationCondition
protected generateEliminationCondition(target: string, bodyPart: string[], distance: number, allowedWeapon: string, allowedWeaponCategory: string): IEliminationCondition
{
const killConditionProps: IKillConditionProps = {
target: target,
@ -902,6 +940,16 @@ export class RepeatableQuestController
};
}
if (allowedWeapon)
{
killConditionProps.weapon = [allowedWeapon];
}
if (allowedWeaponCategory?.length > 0)
{
killConditionProps.weaponCategories = [allowedWeaponCategory];
}
return {
_props: killConditionProps,
_parent: "Kills"

View File

@ -1050,6 +1050,11 @@ class ItemHelper
{
return this.localeService.getLocaleDb()[`${itemTpl} Name`];
}
public getItemTplsOfBaseType(desiredBaseType: string): string[]
{
return Object.values(this.databaseServer.getTables().templates.items).filter(x => x._parent === desiredBaseType).map(x =>x._id);
}
}
namespace ItemHelper

View File

@ -234,6 +234,8 @@ export interface ICompletionAvailableForProps extends IAvailableForProps
export interface ILocationConditionProps extends IConditionProps
{
target: string[],
weapon?: string[]
weaponCategories?: string[]
}
export interface IKillConditionProps extends IConditionProps
@ -243,6 +245,8 @@ export interface IKillConditionProps extends IConditionProps
savageRole?: string[]
bodyPart?: string[]
distance?: IDistanceCheck
weapon?: string[]
weaponCategories? : string[]
}
export interface IDistanceCheck

View File

@ -117,6 +117,10 @@ export interface IEliminationConfig
minDist: number
maxKills: number
minKills: number
weaponCategoryRequirementProb: number
weaponCategoryRequirements: IWeaponRequirement[]
weaponRequirementProb: number
weaponRequirements: IWeaponRequirement[]
}
export interface ITarget extends IProbabilityObject
@ -134,6 +138,11 @@ export interface IBodyPart extends IProbabilityObject
data: string[]
}
export interface IWeaponRequirement extends IProbabilityObject
{
data: string[]
}
export interface IProbabilityObject
{
key: string