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

# Conflicts:
#	project/src/controllers/HideoutController.ts
This commit is contained in:
Dev 2023-12-03 11:13:22 +00:00
commit 1db690afb3
13 changed files with 475 additions and 309 deletions

View File

@ -512,7 +512,53 @@
"RandomTimeSpawn": false,
"ChanceGroup": 0
}
]
],
"rezervbase": [
{
"sptId": "sptBearReserveNormalSpawn",
"BossName": "sptBear",
"BossChance": 15,
"BossZone": "ZoneBarrack,ZonePTOR1,ZonePTOR2,ZoneSubCommand,ZoneSubStorage,ZoneRailStrorage",
"BossPlayer": false,
"BossDifficult": "normal",
"BossEscortType": "sptBear",
"BossEscortDifficult": "normal",
"BossEscortAmount": "1",
"Time": -1,
"Supports": [{
"BossEscortType": "sptBear",
"BossEscortDifficult": [
"normal"
],
"BossEscortAmount": "1"
}
],
"RandomTimeSpawn": false,
"ChanceGroup": 0
},
{
"sptId": "sptUsecReserveNormalSpawn",
"BossName": "sptUsec",
"BossChance": 15,
"BossZone": "ZoneBarrack,ZonePTOR1,ZonePTOR2,ZoneSubCommand,ZoneSubStorage,ZoneRailStrorage",
"BossPlayer": false,
"BossDifficult": "normal",
"BossEscortType": "sptUsec",
"BossEscortDifficult": "normal",
"BossEscortAmount": "1",
"Time": -1,
"Supports": [{
"BossEscortType": "sptUsec",
"BossEscortDifficult": [
"normal"
],
"BossEscortAmount": "1"
}
],
"RandomTimeSpawn": false,
"ChanceGroup": 0
}
]
},
"normal": {
"factory4_day": [{
@ -795,190 +841,194 @@
"allowDuplicateItemsInStaticContainers": true,
"looseLootBlacklist": {},
"scavRaidTimeSettings": {
"bigmap": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 70,
"reductionPercentWeights": {
"20": 1,
"25": 2,
"30": 4,
"35": 4,
"40": 4,
"45": 4,
"50": 4,
"60": 2,
"70": 2,
"80": 1
},
"adjustWaves": true
"settings": {
"trainArrivalDelayObservedSeconds": 90
},
"factory4_day": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 80,
"reductionPercentWeights": {
"5": 2,
"20": 3,
"25": 3,
"30": 5,
"40": 5,
"50": 5,
"60": 2,
"70": 2,
"80": 2,
"85": 1
"maps": {
"bigmap": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 95,
"reductionPercentWeights": {
"20": 1,
"25": 2,
"30": 4,
"35": 4,
"40": 4,
"45": 4,
"50": 4,
"60": 2,
"70": 2,
"80": 1
},
"adjustWaves": true
},
"adjustWaves": true
},
"factory4_night": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 70,
"reductionPercentWeights": {
"20": 4,
"30": 3,
"40": 3,
"60": 2,
"70": 2,
"80": 1
"factory4_day": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 95,
"reductionPercentWeights": {
"5": 2,
"20": 3,
"25": 3,
"30": 5,
"40": 5,
"50": 5,
"60": 2,
"70": 2,
"75": 2
},
"adjustWaves": true
},
"adjustWaves": true
},
"interchange": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 70,
"reductionPercentWeights": {
"20": 5,
"25": 5,
"30": 5,
"35": 5,
"40": 5,
"50": 5,
"60": 2,
"80": 1
"factory4_night": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 75,
"reductionPercentWeights": {
"20": 4,
"30": 3,
"40": 3,
"60": 2,
"70": 2,
"75": 1
},
"adjustWaves": true
},
"adjustWaves": true
},
"rezervbase": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 70,
"interchange": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 95,
"reductionPercentWeights": {
"20": 5,
"25": 5,
"30": 5,
"35": 5,
"40": 5,
"50": 5,
"60": 2,
"80": 1
},
"adjustWaves": true
},
"rezervbase": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 95,
"reductionPercentWeights": {
"20": 3,
"30": 3,
"40": 4,
"50": 4,
"60": 2,
"70": 1,
"80": 1
"reductionPercentWeights": {
"20": 3,
"30": 3,
"40": 4,
"50": 4,
"60": 2,
"70": 1,
"80": 1
},
"adjustWaves": true
},
"adjustWaves": true
},
"laboratory": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 70,
"reductionPercentWeights": {
"20": 3,
"30": 5,
"40": 5,
"50": 5,
"60": 2,
"80": 1
"laboratory": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 95,
"reductionPercentWeights": {
"20": 3,
"30": 5,
"40": 5,
"50": 5,
"60": 2,
"80": 1
},
"adjustWaves": true
},
"adjustWaves": true
},
"lighthouse": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 60,
"reductionPercentWeights": {
"20": 2,
"25": 2,
"30": 4,
"40": 4,
"50": 4,
"60": 2,
"80": 1
"lighthouse": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 95,
"reductionPercentWeights": {
"20": 2,
"25": 2,
"30": 4,
"40": 4,
"50": 4,
"60": 2,
"80": 1
},
"adjustWaves": true
},
"adjustWaves": true
},
"shoreline": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 60,
"reductionPercentWeights": {
"20": 2,
"25": 3,
"30": 5,
"35": 5,
"40": 5,
"50": 5,
"60": 2,
"70": 1,
"80": 1
"shoreline": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 95,
"reductionPercentWeights": {
"20": 2,
"25": 3,
"30": 5,
"35": 5,
"40": 5,
"50": 5,
"60": 2,
"70": 1,
"80": 1
},
"adjustWaves": true
},
"adjustWaves": true
},
"tarkovstreets": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 70,
"reductionPercentWeights": {
"20": 2,
"30": 4,
"40": 4,
"50": 4,
"60": 4,
"70": 1,
"80": 1
"tarkovstreets": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 95,
"reductionPercentWeights": {
"20": 2,
"30": 4,
"40": 4,
"50": 4,
"60": 4,
"70": 1,
"80": 1
},
"adjustWaves": true
},
"adjustWaves": true
},
"woods": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 60,
"reductionPercentWeights": {
"20": 3,
"30": 5,
"40": 5,
"50": 5,
"60": 1,
"70": 1,
"80": 1
"woods": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 95,
"reductionPercentWeights": {
"20": 3,
"30": 5,
"40": 5,
"50": 5,
"60": 1,
"70": 1,
"80": 1
},
"adjustWaves": true
},
"adjustWaves": true
},
"default": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 50,
"reducedChancePercent": 50,
"reductionPercentWeights": {
"10": 1,
"20": 2,
"30": 5,
"40": 5,
"50": 5,
"60": 2,
"70": 1,
"80": 1
},
"adjustWaves": true
"default": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 50,
"reducedChancePercent": 95,
"reductionPercentWeights": {
"10": 1,
"20": 2,
"30": 5,
"40": 5,
"50": 5,
"60": 2,
"70": 1,
"80": 1
},
"adjustWaves": true
}
}
}
}

View File

@ -1,33 +1,33 @@
{
"fail": {},
"started": {},
"success": {
"6507ff21644a656aee0f758f": "596b455186f77457cb50eccb",
"6507ff21644a656aee0f74b7": "5b4794cb86f774598100d5d4",
"6507ff20644a656aee0f744d": "5c0bbaa886f7746941031d82",
"6507ff22644a656aee0f75d3": "5c0bbaa886f7746941031d82",
"6507ff20644a656aee0f73e4": "596b36c586f77450d6045ad2",
"6507ff22644a656aee0f76e1": "596b36c586f77450d6045ad2",
"6507ff20644a656aee0f743f": "5979eee086f774311955e614",
"6507ff22644a656aee0f75e5": "5b478ff486f7744d184ecbbf",
"6507ff21644a656aee0f75a8": "5a27d2af86f7744e1115b323",
"6507ff21644a656aee0f7481": "5c0bdb5286f774166e38eed4",
"6507ff20644a656aee0f744b": "5c1234c286f77406fa13baeb",
"6507ff20644a656aee0f7465": "596b43fb86f77457ca186186",
"6507ff20644a656aee0f7447": "5a27b87686f77460de0252a8",
"6507ff20644a656aee0f73d6": "5967725e86f774601a446662",
"652376e2f6c67195e4061382": "6179b5eabca27a099552e052",
"6507ff20644a656aee0f73bc": "6193850f60b34236ee0483de",
"6492e44bf4287b13040fcbae": "6179b5eabca27a099552e052",
"6492e44bf4287b13040fccf6": "647710905320c660d91c15a5",
"64a8578f0e9876295f0f83ed": "649af47d717cb30e7e4b5e26",
"64a8578f0e9876295f0f83ee": "649af47d717cb30e7e4b5e26",
"64a8578f0e9876295f0f83ef": "649af47d717cb30e7e4b5e26",
"6507ff20644a656aee0f738e": "6179b4f16e9dd54ac275e407",
"6507ff20644a656aee0f73bc": "6193850f60b34236ee0483de",
"6507ff20644a656aee0f73d4": "6179b4f16e9dd54ac275e407",
"6507ff20644a656aee0f73d6": "5967725e86f774601a446662",
"6507ff20644a656aee0f73e4": "596b36c586f77450d6045ad2",
"6507ff20644a656aee0f743f": "5979eee086f774311955e614",
"6507ff20644a656aee0f7447": "5a27b87686f77460de0252a8",
"6507ff20644a656aee0f744b": "5c1234c286f77406fa13baeb",
"6507ff20644a656aee0f744d": "5c0bbaa886f7746941031d82",
"6507ff20644a656aee0f7465": "596b43fb86f77457ca186186",
"6507ff21644a656aee0f7481": "5c0bdb5286f774166e38eed4",
"6507ff21644a656aee0f7498": "6179b4f16e9dd54ac275e407",
"6507ff21644a656aee0f74a6": "5ac242ab86f77412464f68b4",
"6507ff21644a656aee0f757d": "639873003693c63d86328f25",
"6507ff22644a656aee0f7624": "64f5e20652fc01298e2c61e3",
"6507ff21644a656aee0f74b7": "5b4794cb86f774598100d5d4",
"6507ff21644a656aee0f751c": "64f6aafd67e11a7c6206e0d0",
"64cac5c1d45ace5bc90c74a8": "649af47d717cb30e7e4b5e26",
"64cac5c1d45ace5bc90c74a9": "649af47d717cb30e7e4b5e26",
"64cac5c1d45ace5bc90c74aa": "649af47d717cb30e7e4b5e26",
"64cac5c1d45ace5bc90c74ab": "647710905320c660d91c15a5"
},
"fail": {}
"6507ff21644a656aee0f757d": "639873003693c63d86328f25",
"6507ff21644a656aee0f758f": "596b455186f77457cb50eccb",
"6507ff21644a656aee0f75a8": "5a27d2af86f7744e1115b323",
"6507ff22644a656aee0f75d3": "5c0bbaa886f7746941031d82",
"6507ff22644a656aee0f75e5": "5b478ff486f7744d184ecbbf",
"6507ff22644a656aee0f7624": "64f5e20652fc01298e2c61e3",
"6507ff22644a656aee0f76e1": "596b36c586f77450d6045ad2"
}
}

View File

@ -320,6 +320,7 @@ export class HideoutController
{
// Update existing items container tpl to point to new id (tpl)
existingInventoryItem._tpl = hideoutStage.container;
return;
}
@ -357,7 +358,7 @@ export class HideoutController
* Handle HideoutPutItemsInAreaSlots
* Create item in hideout slot item array, remove item from player inventory
* @param pmcData Profile data
* @param addItemToHideoutRequest request from client to place item in area slot
* @param addItemToHideoutRequest reqeust from client to place item in area slot
* @param sessionID Session id
* @returns IItemEventRouterResponse object
*/
@ -490,7 +491,14 @@ export class HideoutController
const itemToReturn = hideoutArea.slots.find((x) => x.locationIndex === slotIndexToRemove).item[0];
const newReq = { items: [{ item_id: itemToReturn._tpl, count: 1 }], tid: "ragfair" };
const newReq = {
items: [{
// eslint-disable-next-line @typescript-eslint/naming-convention
item_id: itemToReturn._tpl,
count: 1,
}],
tid: "ragfair",
};
output = this.inventoryHelper.addItem(
pmcData,
@ -550,7 +558,7 @@ export class HideoutController
* Handle HideoutSingleProductionStart event
* Start production for an item from hideout area
* @param pmcData Player profile
* @param body Start production of single item request
* @param body Start prodution of single item request
* @param sessionID Session id
* @returns IItemEventRouterResponse
*/
@ -665,6 +673,7 @@ export class HideoutController
{
return productionTime;
}
return productionTime * fenceLevel.ScavCaseTimeModifier;
}
@ -682,7 +691,7 @@ export class HideoutController
/**
* Start production of continuously created item
* @param pmcData Player profile
* @param request Continuous production request
* @param request Continious production request
* @param sessionID Session id
* @returns IItemEventRouterResponse
*/
@ -693,6 +702,7 @@ export class HideoutController
): IItemEventRouterResponse
{
this.registerProduction(pmcData, request, sessionID);
return this.eventOutputHolder.getOutput(sessionID);
}
@ -757,7 +767,7 @@ export class HideoutController
output: IItemEventRouterResponse,
): IItemEventRouterResponse
{
// Variables for management of skill
// Variables for managemnet of skill
let craftingExpAmount = 0;
// ? move the logic of BackendCounters in new method?
@ -781,7 +791,14 @@ export class HideoutController
id = this.presetHelper.getDefaultPreset(id)._id;
}
const newReq = { items: [{ item_id: id, count: recipe.count }], tid: "ragfair" };
const newReq = {
items: [{
// eslint-disable-next-line @typescript-eslint/naming-convention
item_id: id,
count: recipe.count,
}],
tid: "ragfair",
};
const entries = Object.entries(pmcData.Hideout.Production);
let prodId: string;
@ -901,7 +918,6 @@ export class HideoutController
let prodId: string;
for (const production of ongoingProductions)
{
// Production or ScavCase
if (this.hideoutHelper.isProductionType(production[1]))
{ // Production or ScavCase
if ((production[1] as ScavCase).RecipeId === request.recipeId)
@ -927,34 +943,38 @@ export class HideoutController
// Create rewards for scav case
const scavCaseRewards = this.scavCaseRewardGenerator.generate(request.recipeId);
// Add scav case rewards to player profile
pmcData.Hideout.Production[prodId].Products = scavCaseRewards;
// Remove the old production from output object before its sent to client
delete output.profileChanges[sessionID].production[request.recipeId];
// Get array of item created + count of them after completing hideout craft
const itemsToAdd = pmcData.Hideout.Production[prodId].Products.map(
(x: { _tpl: string; upd?: { StackObjectsCount?: number; }; }) =>
{
let id = x._tpl;
if (this.presetHelper.hasPreset(id))
{
id = this.presetHelper.getDefaultPreset(id)._id;
}
const itemTpl = this.presetHelper.hasPreset(x._tpl)
? this.presetHelper.getDefaultPreset(x._tpl)._id
: x._tpl;
// Count of items crafted
const numOfItems = !x.upd?.StackObjectsCount ? 1 : x.upd.StackObjectsCount;
return { item_id: id, count: numOfItems };
// eslint-disable-next-line @typescript-eslint/naming-convention
return { item_id: itemTpl, count: numOfItems };
},
);
const newReq = { items: itemsToAdd, tid: "ragfair" };
const callback = () =>
{
// Flag as complete - will be cleaned up later by update() process
// Flag as complete - will be cleaned up later by hideoutController.update()
pmcData.Hideout.Production[prodId].sptIsComplete = true;
// Crafting complete, flag as such
pmcData.Hideout.Production[prodId].inProgress = false;
};
// Add crafted item to player inventory
return this.inventoryHelper.addItem(pmcData, newReq, output, sessionID, callback, true);
}
@ -976,10 +996,11 @@ export class HideoutController
/**
* Get quick time event list for hideout
* // TODO: Implement this
* // TODO - implement this
* @param sessionId Session id
* @returns IQteData array
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public getQteList(sessionId: string): IQteData[]
{
return this.databaseServer.getTables().hideout.qte;
@ -992,6 +1013,7 @@ export class HideoutController
* @param pmcData Profile to adjust
* @param request QTE result object
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public handleQTEEventOutcome(
sessionId: string,
pmcData: IPmcData,
@ -999,10 +1021,10 @@ export class HideoutController
): IItemEventRouterResponse
{
// {
// Action: "HideoutQuickTimeEvent",
// results: [true, false, true, true, true, true, true, true, true, false, false, false, false, false, false],
// id: "63b16feb5d012c402c01f6ef",
// timestamp: 1672585349
// "Action": "HideoutQuickTimeEvent",
// "results": [true, false, true, true, true, true, true, true, true, false, false, false, false, false, false],
// "id": "63b16feb5d012c402c01f6ef",
// "timestamp": 1672585349
// }
// Skill changes are done in
@ -1036,7 +1058,7 @@ export class HideoutController
request: IRecordShootingRangePoints,
): IItemEventRouterResponse
{
// Check if counter exists, add placeholder if it doesn't
// Check if counter exists, add placeholder if it doesnt
if (!pmcData.Stats.Eft.OverallCounters.Items.find((x) => x.Key.includes("ShootingRangePoints")))
{
pmcData.Stats.Eft.OverallCounters.Items.push({ Key: ["ShootingRangePoints"], Value: 0 });
@ -1048,7 +1070,7 @@ export class HideoutController
);
shootingRangeHighScore.Value = request.points;
// Check against live, maybe a response isn't necessary
// Check against live, maybe a response isnt necessary
return this.eventOutputHolder.getOutput(sessionId);
}
@ -1066,7 +1088,7 @@ export class HideoutController
{
const output = this.eventOutputHolder.getOutput(sessionId);
// Create mapping of required item with corresponding item from player inventory
// Create mapping of required item with corrisponding item from player inventory
const items = request.items.map((reqItem) =>
{
const item = pmcData.Inventory.items.find((invItem) => invItem._id === reqItem.id);
@ -1115,7 +1137,7 @@ export class HideoutController
return this.httpResponse.appendErrorToOutput(output);
}
// Add all improvements to output object
// Add all improvemets to output object
const improvements = hideoutDbData.stages[profileHideoutArea.level].improvements;
const timestamp = this.timeUtil.getTimestamp();
@ -1164,7 +1186,7 @@ export class HideoutController
// Null out production data so client gets informed when response send back
pmcData.Hideout.Production[request.recipeId] = null;
// TODO: handle timestamp somehow?
// TODO - handle timestamp somehow?
return output;
}

View File

@ -107,17 +107,17 @@ export class InraidController
*/
protected savePmcProgress(sessionID: string, postRaidRequest: ISaveProgressRequestData): void
{
const serveProfile = this.saveServer.getProfile(sessionID);
const locationName = serveProfile.inraid.location.toLowerCase();
const serverProfile = this.saveServer.getProfile(sessionID);
const locationName = serverProfile.inraid.location.toLowerCase();
const map: ILocationBase = this.databaseServer.getTables().locations[locationName].base;
const mapHasInsuranceEnabled = map.Insurance;
let serverPmcProfile = serveProfile.characters.pmc;
let serverPmcProfile = serverProfile.characters.pmc;
const isDead = this.isPlayerDead(postRaidRequest.exit);
const preRaidGear = this.inRaidHelper.getPlayerGear(serverPmcProfile.Inventory.items);
serveProfile.inraid.character = "pmc";
serverProfile.inraid.character = "pmc";
this.inRaidHelper.updateProfileBaseStats(serverPmcProfile, postRaidRequest, sessionID);
this.inRaidHelper.updatePmcProfileDataPostRaid(serverPmcProfile, postRaidRequest, sessionID);
@ -220,7 +220,7 @@ export class InraidController
activeQuestIdsInProfile,
allQuests,
);
if (questAndFindItemConditionId)
if (Object.keys(questAndFindItemConditionId)?.length > 0)
{
this.profileHelper.removeCompletedQuestConditionFromProfile(pmcData, questAndFindItemConditionId);
}

View File

@ -216,7 +216,7 @@ export class BotWeaponGeneratorHelper
// Iterate over each grid in the container and look for a big enough space for the item to be placed in
let currentGridCount = 1;
const slotGridCount = containerTemplate[1]._props.Grids.length;
const totalSlotGridCount = containerTemplate[1]._props.Grids.length;
for (const slotGrid of containerTemplate[1]._props.Grids)
{
// Grid is empty, skip
@ -280,7 +280,7 @@ export class BotWeaponGeneratorHelper
}
// If we've checked all grids in container and reached this point, there's no space for item
if (slotGridCount >= currentGridCount)
if (currentGridCount >= totalSlotGridCount)
{
return ItemAddedResult.NO_SPACE;
}

View File

@ -218,7 +218,7 @@ export class InRaidHelper
public updatePmcProfileDataPostRaid(pmcData: IPmcData, saveProgressRequest: ISaveProgressRequestData, sessionId: string): void
{
// Process failed quests then copy everything
this.processFailedQuests(sessionId, pmcData, pmcData.Quests, saveProgressRequest.profile.Quests);
this.processFailedQuests(sessionId, pmcData, pmcData.Quests, saveProgressRequest.profile);
pmcData.Quests = saveProgressRequest.profile.Quests;
// No need to do this for scav, old scav is deleted and new one generated
@ -254,13 +254,13 @@ export class InRaidHelper
* @param sessionId Player id
* @param pmcData Player profile
* @param preRaidQuests Quests prior to starting raid
* @param postRaidQuests Quest after raid
* @param postRaidProfile Profile sent by client
*/
protected processFailedQuests(
sessionId: string,
pmcData: IPmcData,
preRaidQuests: IQuestStatus[],
postRaidQuests: IQuestStatus[],
postRaidProfile: IPostRaidPmcData,
): void
{
if (!preRaidQuests)
@ -270,24 +270,81 @@ export class InRaidHelper
}
// Loop over all quests from post-raid profile
for (const postRaidQuest of postRaidQuests)
for (const postRaidQuest of postRaidProfile.Quests)
{
// Find matching pre-raid quest
// Find matching pre-raid quest + not already failed
const preRaidQuest = preRaidQuests?.find((x) => x.qid === postRaidQuest.qid);
if (preRaidQuest)
if (!preRaidQuest)
{
// Post-raid quest is failed but wasn't pre-raid
// postRaidQuest.status has a weird value, need to do some nasty casting to compare it
if (<string><unknown>postRaidQuest.status === "Fail" && preRaidQuest.status !== QuestStatus.Fail)
continue;
}
// Already failed before raid, skip
if (preRaidQuest.status === QuestStatus.Fail)
{
continue;
}
if (preRaidQuest.status === QuestStatus.Success)
{
continue;
}
// Quest failed inside raid, need to handle
// postRaidQuest.status has a weird value, need to do some nasty casting to compare it
const postRaidQuestStatus = <string><unknown>postRaidQuest.status;
if (postRaidQuestStatus === "Fail")
{
// Send failed message
const failBody: IFailQuestRequestData = {
Action: "QuestComplete",
qid: postRaidQuest.qid,
removeExcessItems: true,
};
this.questHelper.failQuest(pmcData, failBody, sessionId);
}
// Restartable quests need special actions
else if (postRaidQuestStatus === "FailRestartable")
{
// Does failed quest have requirement to collect items from raid
const questDbData = this.questHelper.getQuestFromDb(postRaidQuest.qid, pmcData);
// AvailableForFinish
const matchingAffFindConditions = questDbData.conditions.AvailableForFinish.filter(x => x._parent === "FindItem");
const itemsToCollect: string[] = [];
if (matchingAffFindConditions)
{
// Send failed message
const failBody: IFailQuestRequestData = {
Action: "QuestComplete",
qid: postRaidQuest.qid,
removeExcessItems: true,
};
this.questHelper.failQuest(pmcData, failBody, sessionId);
// Find all items the failed quest wanted
for (const condition of matchingAffFindConditions)
{
itemsToCollect.push(...condition._props.target);
}
}
// Remove quest items from profile as quest has failed and may still be alive
// Required as restarting the quest from main menu does not remove value from CarriedQuestItems array
postRaidProfile.Stats.Eft.CarriedQuestItems = postRaidProfile.Stats.Eft.CarriedQuestItems.filter(x => !itemsToCollect.includes(x))
// Remove quest item from profile now quest is failed
// updateProfileBaseStats() has already passed by ref EFT.Stats, all changes applied to postRaid profile also apply to server profile
for (const itemTpl of itemsToCollect)
{
// Look for sessioncounter and remove it
const counterIndex = postRaidProfile.Stats.Eft.SessionCounters.Items.findIndex(x => x.Key.includes(itemTpl) && x.Key.includes("LootItem"));
if (counterIndex > -1)
{
postRaidProfile.Stats.Eft.SessionCounters.Items.splice(counterIndex, 1);
}
// Look for quest item and remove it
const inventoryItemIndex = postRaidProfile.Inventory.items.findIndex(x => x._tpl === itemTpl);
if (inventoryItemIndex > -1)
{
postRaidProfile.Inventory.items.splice(inventoryItemIndex, 1);
}
}
// Clear out any completed conditions
postRaidQuest.completedConditions = [];
}
}
}

View File

@ -951,7 +951,7 @@ export class QuestHelper
const questInDb = allQuests.find((x) => x._id === questId);
if (!questInDb)
{
this.logger.warning(
this.logger.debug(
`Unable to find quest: ${questId} in db, cannot get 'FindItem' condition, skipping`,
);
continue;

View File

@ -22,6 +22,7 @@ export class WeightedRandomHelper
{
const itemKeys = Object.keys(itemArray);
const weights = Object.values(itemArray);
const chosenItem = this.weightedRandom(itemKeys, weights);
return chosenItem.item;
@ -41,18 +42,23 @@ export class WeightedRandomHelper
* @param {number[]} weights
* @returns {{item: any, index: number}}
*/
public weightedRandom(items: string | any[], weights: string | any[]): { item: any; index: number; }
public weightedRandom(items: any[], weights: any[]): { item: any; index: number; }
{
if (!items || items.length === 0)
{
throw new Error("Items must not be empty");
}
if (!weights || weights.length === 0)
{
throw new Error("Item weights must not be empty");
}
if (items.length !== weights.length)
{
throw new Error("Items and weight inputs must be of the same length");
}
if (!items.length)
{
throw new Error("Items must not be empty");
}
// Preparing the cumulative weights array.
// For example:
// - weights = [1, 4, 3]

View File

@ -468,7 +468,7 @@ export interface IQuestStatus
startTime: number;
status: QuestStatus;
statusTimers?: Record<string, number>;
/** SPT specific property */
/** Property does not exist in live profile data, but is used by ProfileChanges.questsStatus when sent to client*/
completedConditions?: string[];
availableAfter?: number;
}
@ -483,16 +483,6 @@ export interface TraderInfo
disabled: boolean;
}
/** This object is sent to the client as part of traderRelations */
export interface TraderData
{
salesSum: number;
standing: number;
loyalty: number;
unlocked: boolean;
disabled: boolean;
}
export interface RagfairInfo
{
rating: number;

View File

@ -1,4 +1,4 @@
import { Health, IQuestStatus, Productive, Skills, TraderData } from "@spt-aki/models/eft/common/tables/IBotBase";
import { Health, IQuestStatus, Productive, Skills } from "@spt-aki/models/eft/common/tables/IBotBase";
import { Item, Upd } from "@spt-aki/models/eft/common/tables/IItem";
import { IQuest } from "@spt-aki/models/eft/common/tables/IQuest";
import { IPmcDataRepeatableQuest } from "@spt-aki/models/eft/common/tables/IRepeatableQuests";
@ -80,6 +80,16 @@ export interface Improvement
improveCompleteTimestamp: number;
}
/** Related to TraderInfo */
export interface TraderData
{
salesSum: number;
standing: number;
loyalty: number;
unlocked: boolean;
disabled: boolean;
}
export interface Product
{
_id: string;

View File

@ -39,31 +39,7 @@ export interface ILocationConfig extends IBaseConfig
/** Key: map, value: loose loot ids to ignore */
looseLootBlacklist: Record<string, string[]>;
/** Key: map, value: settings to control how long scav raids are*/
scavRaidTimeSettings: Record<string, IScavRaidTimeLocationSettings>;
}
export interface IScavRaidTimeLocationSettings
{
/** Should loot be reduced by same percent length of raid is reduced by */
reduceLootByPercent: boolean;
minStaticLootPercent: number;
minDynamicLootPercent: number;
/** Chance raid time is reduced */
reducedChancePercent: number;
reductionPercentWeights: Record<string, number>;
/** Should bot waves be removed / spawn times be adjusted */
adjustWaves: boolean;
}
export interface IContainerRandomistionSettings
{
enabled: boolean;
/** What maps can use the container randomisation feature */
maps: Record<string, boolean>;
/** Some container types don't work when randomised */
containerTypesToNotRandomise: string[];
containerGroupMinSizeMultiplier: number;
containerGroupMaxSizeMultiplier: number;
scavRaidTimeSettings: IScavRaidTimeSettings;
}
export interface IFixEmptyBotWavesSettings
@ -117,3 +93,38 @@ export interface LootMultiplier
terminal: number;
town: number;
}
export interface IContainerRandomistionSettings
{
enabled: boolean;
/** What maps can use the container randomisation feature */
maps: Record<string, boolean>;
/** Some container types don't work when randomised */
containerTypesToNotRandomise: string[];
containerGroupMinSizeMultiplier: number;
containerGroupMaxSizeMultiplier: number;
}
export interface IScavRaidTimeSettings
{
settings: IScavRaidTimeConfigSettings
maps: Record<string, IScavRaidTimeLocationSettings>
}
export interface IScavRaidTimeConfigSettings
{
trainArrivalDelayObservedSeconds: number
}
export interface IScavRaidTimeLocationSettings
{
/** Should loot be reduced by same percent length of raid is reduced by */
reduceLootByPercent: boolean;
minStaticLootPercent: number;
minDynamicLootPercent: number;
/** Chance raid time is reduced */
reducedChancePercent: number;
reductionPercentWeights: Record<string, number>;
/** Should bot waves be removed / spawn times be adjusted */
adjustWaves: boolean;
}

View File

@ -2,8 +2,8 @@ import { inject, injectable } from "tsyringe";
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
import { IHideoutImprovement, Productive, TraderData, TraderInfo } from "@spt-aki/models/eft/common/tables/IBotBase";
import { ProfileChange } from "@spt-aki/models/eft/itemEvent/IItemEventRouterBase";
import { IHideoutImprovement, Productive, TraderInfo } from "@spt-aki/models/eft/common/tables/IBotBase";
import { ProfileChange, TraderData } from "@spt-aki/models/eft/itemEvent/IItemEventRouterBase";
import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse";
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
import { TimeUtil } from "@spt-aki/utils/TimeUtil";

View File

@ -171,11 +171,11 @@ export class RaidTimeAdjustmentService
*/
protected getMapSettings(location: string): IScavRaidTimeLocationSettings
{
const mapSettings = this.locationConfig.scavRaidTimeSettings[location.toLowerCase()];
const mapSettings = this.locationConfig.scavRaidTimeSettings.maps[location.toLowerCase()];
if (!mapSettings)
{
this.logger.warning(`Unable to find scav raid time settings for map: ${location}, using defaults`);
return this.locationConfig.scavRaidTimeSettings.default;
return this.locationConfig.scavRaidTimeSettings.maps.default;
}
return mapSettings;
@ -206,27 +206,47 @@ export class RaidTimeAdjustmentService
Chance: null
}
// At what minute we simulate the player joining the raid
const simulatedRaidEntryTimeMinutes = mapBase.EscapeTimeLimit - newRaidTimeMinutes;
// How many seconds have elapsed in the raid when the player joins
const reductionSeconds = simulatedRaidEntryTimeMinutes * 60;
// Delay between the train extract activating and it becoming available to board
//
// Test method for determining this value:
// 1) Set MinTime, MaxTime, and Count for the train extract all to 120
// 2) Load into Reserve or Lighthouse as a PMC (both have the same result)
// 3) Board the train when it arrives
// 4) Check the raid time on the Raid Ended Screen (it should always be the same)
//
// trainArrivalDelaySeconds = [raid time on raid-ended screen] - MaxTime - Count - ExfiltrationTime
// Example: Raid Time = 5:33 = 333 seconds
// trainArrivalDelaySeconds = 333 - 120 - 120 - 5 = 88
//
// I added 2 seconds just to be safe...
//
const trainArrivalDelaySeconds = this.locationConfig.scavRaidTimeSettings.settings.trainArrivalDelayObservedSeconds;
// Determine the earliest possible time in the raid when the train would leave
const earliestPossibleDepartureMinutes = (exit.MinTime + exit.Count + exit.ExfiltrationTime + trainArrivalDelaySeconds) / 60;
// If raid is after last moment train can leave, assume train has already left, disable extract
const latestPossibleDepartureMinutes = (exit.MaxTime + exit.Count) / 60;
if (newRaidTimeMinutes < latestPossibleDepartureMinutes)
const mostPossibleTimeRemainingAfterDeparture = mapBase.EscapeTimeLimit - earliestPossibleDepartureMinutes;
if (newRaidTimeMinutes < mostPossibleTimeRemainingAfterDeparture)
{
exitChange.Chance = 0;
this.logger.debug(`Train Exit: ${exit.Name} disabled as new raid time ${newRaidTimeMinutes} minutes is below ${latestPossibleDepartureMinutes} minutes`);
this.logger.debug(`Train Exit: ${exit.Name} disabled as new raid time ${newRaidTimeMinutes} minutes is below ${mostPossibleTimeRemainingAfterDeparture} minutes`);
result.push(exitChange);
continue;
}
// What minute we simulate the player joining a raid at
const simulatedRaidEntryTimeMinutes = mapBase.EscapeTimeLimit - newRaidTimeMinutes;
// How many seconds to reduce extract arrival times by, negative values seem to make extract turn red in game
const reductionSeconds = simulatedRaidEntryTimeMinutes * 60;
exitChange.MinTime = exit.MinTime - reductionSeconds;
exitChange.MaxTime = exit.MaxTime - reductionSeconds;
// Reduce extract arrival times. Negative values seem to make extract turn red in game.
exitChange.MinTime = Math.max(exit.MinTime - reductionSeconds, 0);
exitChange.MaxTime = Math.max(exit.MaxTime - reductionSeconds, 0);
this.logger.debug(`Train appears between: ${exitChange.MinTime} and ${exitChange.MaxTime} seconds raid time`);