diff --git a/project/assets/configs/location.json b/project/assets/configs/location.json index 8d08f25d..6bd02326 100644 --- a/project/assets/configs/location.json +++ b/project/assets/configs/location.json @@ -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 + } } } } diff --git a/project/assets/database/traders/58330581ace78e27b8b10cee/questassort.json b/project/assets/database/traders/58330581ace78e27b8b10cee/questassort.json index bdddcb79..65ac3860 100644 --- a/project/assets/database/traders/58330581ace78e27b8b10cee/questassort.json +++ b/project/assets/database/traders/58330581ace78e27b8b10cee/questassort.json @@ -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": {} -} \ No newline at end of file + "6507ff21644a656aee0f757d": "639873003693c63d86328f25", + "6507ff21644a656aee0f758f": "596b455186f77457cb50eccb", + "6507ff21644a656aee0f75a8": "5a27d2af86f7744e1115b323", + "6507ff22644a656aee0f75d3": "5c0bbaa886f7746941031d82", + "6507ff22644a656aee0f75e5": "5b478ff486f7744d184ecbbf", + "6507ff22644a656aee0f7624": "64f5e20652fc01298e2c61e3", + "6507ff22644a656aee0f76e1": "596b36c586f77450d6045ad2" + } +} diff --git a/project/src/controllers/HideoutController.ts b/project/src/controllers/HideoutController.ts index a95e5246..cfb5af65 100644 --- a/project/src/controllers/HideoutController.ts +++ b/project/src/controllers/HideoutController.ts @@ -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; } diff --git a/project/src/controllers/InraidController.ts b/project/src/controllers/InraidController.ts index 267e0228..a4313c8d 100644 --- a/project/src/controllers/InraidController.ts +++ b/project/src/controllers/InraidController.ts @@ -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); } diff --git a/project/src/helpers/BotWeaponGeneratorHelper.ts b/project/src/helpers/BotWeaponGeneratorHelper.ts index 011a5a05..29a0586e 100644 --- a/project/src/helpers/BotWeaponGeneratorHelper.ts +++ b/project/src/helpers/BotWeaponGeneratorHelper.ts @@ -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; } diff --git a/project/src/helpers/InRaidHelper.ts b/project/src/helpers/InRaidHelper.ts index 8267e3c6..6ea2c06e 100644 --- a/project/src/helpers/InRaidHelper.ts +++ b/project/src/helpers/InRaidHelper.ts @@ -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 (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 = 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 = []; } } } diff --git a/project/src/helpers/QuestHelper.ts b/project/src/helpers/QuestHelper.ts index ffaafc40..b3e2a0b9 100644 --- a/project/src/helpers/QuestHelper.ts +++ b/project/src/helpers/QuestHelper.ts @@ -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; diff --git a/project/src/helpers/WeightedRandomHelper.ts b/project/src/helpers/WeightedRandomHelper.ts index 7b5f8169..4c3ba03c 100644 --- a/project/src/helpers/WeightedRandomHelper.ts +++ b/project/src/helpers/WeightedRandomHelper.ts @@ -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] diff --git a/project/src/models/eft/common/tables/IBotBase.ts b/project/src/models/eft/common/tables/IBotBase.ts index 27efc6eb..c6283c5f 100644 --- a/project/src/models/eft/common/tables/IBotBase.ts +++ b/project/src/models/eft/common/tables/IBotBase.ts @@ -468,7 +468,7 @@ export interface IQuestStatus startTime: number; status: QuestStatus; statusTimers?: Record; - /** 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; diff --git a/project/src/models/eft/itemEvent/IItemEventRouterBase.ts b/project/src/models/eft/itemEvent/IItemEventRouterBase.ts index d3ceeea1..86ebf3a9 100644 --- a/project/src/models/eft/itemEvent/IItemEventRouterBase.ts +++ b/project/src/models/eft/itemEvent/IItemEventRouterBase.ts @@ -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; diff --git a/project/src/models/spt/config/ILocationConfig.ts b/project/src/models/spt/config/ILocationConfig.ts index 59ba8542..0b5fb127 100644 --- a/project/src/models/spt/config/ILocationConfig.ts +++ b/project/src/models/spt/config/ILocationConfig.ts @@ -39,31 +39,7 @@ export interface ILocationConfig extends IBaseConfig /** Key: map, value: loose loot ids to ignore */ looseLootBlacklist: Record; /** Key: map, value: settings to control how long scav raids are*/ - scavRaidTimeSettings: Record; -} - -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; - /** 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; - /** 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; + /** Some container types don't work when randomised */ + containerTypesToNotRandomise: string[]; + containerGroupMinSizeMultiplier: number; + containerGroupMaxSizeMultiplier: number; +} + +export interface IScavRaidTimeSettings +{ + settings: IScavRaidTimeConfigSettings + maps: Record +} + +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; + /** Should bot waves be removed / spawn times be adjusted */ + adjustWaves: boolean; +} diff --git a/project/src/routers/EventOutputHolder.ts b/project/src/routers/EventOutputHolder.ts index 27a9beed..21a3d56b 100644 --- a/project/src/routers/EventOutputHolder.ts +++ b/project/src/routers/EventOutputHolder.ts @@ -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"; diff --git a/project/src/services/RaidTimeAdjustmentService.ts b/project/src/services/RaidTimeAdjustmentService.ts index 0fd2d27a..fbcad8b2 100644 --- a/project/src/services/RaidTimeAdjustmentService.ts +++ b/project/src/services/RaidTimeAdjustmentService.ts @@ -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; @@ -205,28 +205,48 @@ export class RaidTimeAdjustmentService MaxTime: null, 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`); @@ -237,4 +257,4 @@ export class RaidTimeAdjustmentService ? result : null ; } -} \ No newline at end of file +}