diff --git a/project/assets/configs/location.json b/project/assets/configs/location.json index 7d938606..39409143 100644 --- a/project/assets/configs/location.json +++ b/project/assets/configs/location.json @@ -793,5 +793,106 @@ "minFillStaticMagazinePercent": 50, "makeWishingTreeAlwaysGiveGift": true, "allowDuplicateItemsInStaticContainers": true, - "looseLootBlacklist": {} + "looseLootBlacklist": {}, + "scavRaidTimeSettings": { + "bigmap": { + "reducedChancePercent": 40, + "reductionPercentWeights": { + "20": 12, + "40": 5, + "60": 2, + "80": 1 + } + }, + "factory4_day": { + "reducedChancePercent": 60, + "reductionPercentWeights": { + "20": 12, + "40": 5, + "60": 2, + "80": 1 + } + }, + "factory4_night": { + "reducedChancePercent": 40, + "reductionPercentWeights": { + "20": 12, + "40": 5, + "60": 2, + "80": 1 + } + }, + "interchange": { + "reducedChancePercent": 50, + "reductionPercentWeights": { + "20": 12, + "40": 5, + "60": 2, + "80": 1 + } + }, + "rezervbase": { + "reducedChancePercent": 40, + "reductionPercentWeights": { + "20": 12, + "40": 5, + "60": 2, + "80": 1 + } + }, + "laboratory": { + "reducedChancePercent": 30, + "reductionPercentWeights": { + "20": 12, + "40": 5, + "60": 2, + "80": 1 + } + }, + "lighthouse": { + "reducedChancePercent": 40, + "reductionPercentWeights": { + "20": 12, + "40": 5, + "60": 2, + "80": 1 + } + }, + "shoreline": { + "reducedChancePercent": 40, + "reductionPercentWeights": { + "20": 25, + "40": 10, + "60": 1, + "80": 1 + } + }, + "tarkovstreets": { + "reducedChancePercent": 40, + "reductionPercentWeights": { + "20": 25, + "40": 10, + "60": 1, + "80": 1 + } + }, + "woods": { + "reducedChancePercent": 40, + "reductionPercentWeights": { + "20": 25, + "40": 10, + "60": 1, + "80": 1 + } + }, + "default": { + "reducedChancePercent": 25, + "reductionPercentWeights": { + "20": 12, + "40": 5, + "60": 2, + "80": 1 + } + } + } } diff --git a/project/src/callbacks/GameCallbacks.ts b/project/src/callbacks/GameCallbacks.ts index bfa3f74d..c2938d06 100644 --- a/project/src/callbacks/GameCallbacks.ts +++ b/project/src/callbacks/GameCallbacks.ts @@ -10,6 +10,7 @@ import { IGameEmptyCrcRequestData } from "@spt-aki/models/eft/game/IGameEmptyCrc import { IGameKeepAliveResponse } from "@spt-aki/models/eft/game/IGameKeepAliveResponse"; import { IGameLogoutResponseData } from "@spt-aki/models/eft/game/IGameLogoutResponseData"; import { IGameStartResponse } from "@spt-aki/models/eft/game/IGameStartResponse"; +import { IGetRaidTimeRequest } from "@spt-aki/models/eft/game/IGetRaidTimeRequest"; import { IReportNicknameRequestData } from "@spt-aki/models/eft/game/IReportNicknameRequestData"; import { IServerDetails } from "@spt-aki/models/eft/game/IServerDetails"; import { IVersionValidateRequestData } from "@spt-aki/models/eft/game/IVersionValidateRequestData"; @@ -157,6 +158,16 @@ class GameCallbacks implements OnLoad { return this.httpResponse.nullResponse(); } + + /** + * Handle singleplayer/settings/getRaidTime + * @returns string + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public getRaidTime(url: string, request: IGetRaidTimeRequest, sessionID: string): any + { + return this.httpResponse.noBody(this.gameController.getRaidTime(sessionID, request)); + } } export {GameCallbacks}; diff --git a/project/src/context/ApplicationContext.ts b/project/src/context/ApplicationContext.ts index afb85008..da9655a2 100644 --- a/project/src/context/ApplicationContext.ts +++ b/project/src/context/ApplicationContext.ts @@ -17,7 +17,7 @@ export class ApplicationContext * * const activePlayerSessionId = this.applicationContext.getLatestValue(ContextVariableType.SESSION_ID).getValue(); * - * const matchInfo = this.applicationContext.getLatestValue(ContextVariableType.MATCH_INFO).getValue(); + * const matchInfo = this.applicationContext.getLatestValue(ContextVariableType.RAID_CONFIGURATION).getValue(); * @param type * @returns */ diff --git a/project/src/context/ContextVariableType.ts b/project/src/context/ContextVariableType.ts index 7914815c..f69f3630 100644 --- a/project/src/context/ContextVariableType.ts +++ b/project/src/context/ContextVariableType.ts @@ -1,5 +1,4 @@ -export enum ContextVariableType -{ +export enum ContextVariableType { /** Logged in users session id */ SESSION_ID = 0, /** Currently acive raid information */ @@ -7,5 +6,5 @@ export enum ContextVariableType /** Timestamp when client first connected */ CLIENT_START_TIMESTAMP = 2, /** When player is loading into map and loot is requested */ - REGISTER_PLAYER_REQUEST = 3, + REGISTER_PLAYER_REQUEST = 3 } diff --git a/project/src/controllers/GameController.ts b/project/src/controllers/GameController.ts index 90bdc6e6..c5fc0bfe 100644 --- a/project/src/controllers/GameController.ts +++ b/project/src/controllers/GameController.ts @@ -5,6 +5,7 @@ import { ContextVariableType } from "@spt-aki/context/ContextVariableType"; import { HideoutHelper } from "@spt-aki/helpers/HideoutHelper"; import { HttpServerHelper } from "@spt-aki/helpers/HttpServerHelper"; import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper"; +import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper"; import { PreAkiModLoader } from "@spt-aki/loaders/PreAkiModLoader"; import { IEmptyRequestData } from "@spt-aki/models/eft/common/IEmptyRequestData"; import { ILooseLoot } from "@spt-aki/models/eft/common/ILooseLoot"; @@ -14,7 +15,10 @@ import { ICheckVersionResponse } from "@spt-aki/models/eft/game/ICheckVersionRes import { ICurrentGroupResponse } from "@spt-aki/models/eft/game/ICurrentGroupResponse"; import { IGameConfigResponse } from "@spt-aki/models/eft/game/IGameConfigResponse"; import { IGameKeepAliveResponse } from "@spt-aki/models/eft/game/IGameKeepAliveResponse"; +import { IGetRaidTimeRequest } from "@spt-aki/models/eft/game/IGetRaidTimeRequest"; +import { IGetRaidTimeResponse } from "@spt-aki/models/eft/game/IGetRaidTimeResponse"; import { IServerDetails } from "@spt-aki/models/eft/game/IServerDetails"; +import { IGetRaidConfigurationRequestData } from "@spt-aki/models/eft/match/IGetRaidConfigurationRequestData"; import { IAkiProfile } from "@spt-aki/models/eft/profile/IAkiProfile"; import { AccountTypes } from "@spt-aki/models/enums/AccountTypes"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; @@ -68,6 +72,7 @@ export class GameController @inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService, @inject("ItemBaseClassService") protected itemBaseClassService: ItemBaseClassService, @inject("GiftService") protected giftService: GiftService, + @inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper, @inject("ApplicationContext") protected applicationContext: ApplicationContext, @inject("ConfigServer") protected configServer: ConfigServer, ) @@ -475,6 +480,53 @@ export class GameController }; } + /** + * singleplayer/settings/getRaidTime + */ + public getRaidTime(sessionId: string, request: IGetRaidTimeRequest): IGetRaidTimeResponse + { + const baseEscapeTimeMinutes = this.databaseServer.getTables().locations[request.Location.toLowerCase()].base.EscapeTimeLimit; + const result: IGetRaidTimeResponse = { + RaidTimeMinutes: baseEscapeTimeMinutes + } + + // Pmc raid, send default + if (request.Side.toLowerCase() === "pmc") + { + return result; + } + + // We're scav + let mapSettings = this.locationConfig.scavRaidTimeSettings[request.Location.toLowerCase()]; + if (!mapSettings) + { + this.logger.warning(`Unable to find scav raid time settings for map: ${request.Location}, using defaults`); + mapSettings = this.locationConfig.scavRaidTimeSettings.default; + } + + // Chance of reducing raid time for scav, not guaranteed + if (!this.randomUtil.getChance100(mapSettings.reducedChancePercent)) + { + // Send default + return result; + } + + // Get the weighted percent to reduce the raid time by + const chosenRaidReductionPercent = this.weightedRandomHelper.getWeightedValue( + mapSettings.reductionPercentWeights, + ); + + // How many minutes raid will be + const newRaidTime = Math.floor(this.randomUtil.reduceValueByPercent(baseEscapeTimeMinutes, Number.parseInt(chosenRaidReductionPercent))); + + // Update result object with new time + result.RaidTimeMinutes = newRaidTime; + + this.logger.debug(`Reduced: ${request.Location} raid time by: ${chosenRaidReductionPercent}% to ${newRaidTime} minutes`) + + return result; + } + /** * BSG have two values for shotgun dispersion, we make sure both have the same value */ diff --git a/project/src/models/spt/config/ILocationConfig.ts b/project/src/models/spt/config/ILocationConfig.ts index 91803ad2..c3f0c36c 100644 --- a/project/src/models/spt/config/ILocationConfig.ts +++ b/project/src/models/spt/config/ILocationConfig.ts @@ -38,6 +38,14 @@ export interface ILocationConfig extends IBaseConfig allowDuplicateItemsInStaticContainers: boolean; /** 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 +{ + reducedChancePercent: number + reductionPercentWeights: Record } export interface IContainerRandomistionSettings diff --git a/project/src/routers/static/GameStaticRouter.ts b/project/src/routers/static/GameStaticRouter.ts index 26ccb883..a470dadc 100644 --- a/project/src/routers/static/GameStaticRouter.ts +++ b/project/src/routers/static/GameStaticRouter.ts @@ -89,6 +89,14 @@ export class GameStaticRouter extends StaticRouter return this.gameCallbacks.reportNickname(url, info, sessionID); }, ), + new RouteAction( + "/singleplayer/settings/getRaidTime", + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (url: string, info: any, sessionID: string, output: string): any => + { + return this.gameCallbacks.getRaidTime(url, info, sessionID); + }, + ) ]); } } diff --git a/project/src/utils/HttpResponseUtil.ts b/project/src/utils/HttpResponseUtil.ts index c2ed1011..e543c2ae 100644 --- a/project/src/utils/HttpResponseUtil.ts +++ b/project/src/utils/HttpResponseUtil.ts @@ -34,6 +34,13 @@ export class HttpResponseUtil return this.clearString(this.jsonUtil.serialize(data)); } + /** + * Game client needs server responses in a particular format + * @param data + * @param err + * @param errmsg + * @returns + */ public getBody(data: T, err = 0, errmsg = null): IGetBodyResponseData { return this.clearString(this.getUnclearedBody(data, err, errmsg)); diff --git a/project/src/utils/RandomUtil.ts b/project/src/utils/RandomUtil.ts index 8323fa53..f0782da7 100644 --- a/project/src/utils/RandomUtil.ts +++ b/project/src/utils/RandomUtil.ts @@ -231,6 +231,18 @@ export class RandomUtil return Number.parseFloat(((percent * number) / 100).toFixed(toFixed)); } + /** + * Reduce a value by a percentage + * @param number Value to reduce + * @param percentage Percentage to reduce value by + * @returns Reduced value + */ + public reduceValueByPercent(number: number, percentage: number): number + { + const reductionAmount = number * (percentage / 100); + return number - reductionAmount; + } + /** * Check if number passes a check out of 100 * @param chancePercent value check needs to be above