2023-11-29 17:52:13 +01:00
import { inject , injectable } from "tsyringe" ;
import { ApplicationContext } from "@spt-aki/context/ApplicationContext" ;
import { ContextVariableType } from "@spt-aki/context/ContextVariableType" ;
import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper" ;
import { ILocationBase } from "@spt-aki/models/eft/common/ILocationBase" ;
import { IGetRaidTimeRequest } from "@spt-aki/models/eft/game/IGetRaidTimeRequest" ;
import { ExtractChange , IGetRaidTimeResponse } from "@spt-aki/models/eft/game/IGetRaidTimeResponse" ;
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes" ;
2023-11-29 18:18:45 +01:00
import { ILocationConfig , IScavRaidTimeLocationSettings , LootMultiplier } from "@spt-aki/models/spt/config/ILocationConfig" ;
2023-11-29 17:52:13 +01:00
import { IRaidChanges } from "@spt-aki/models/spt/location/IRaidChanges" ;
import { ILogger } from "@spt-aki/models/spt/utils/ILogger" ;
import { ConfigServer } from "@spt-aki/servers/ConfigServer" ;
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer" ;
import { RandomUtil } from "@spt-aki/utils/RandomUtil" ;
@injectable ( )
export class RaidTimeAdjustmentService
{
protected locationConfig : ILocationConfig ;
constructor (
@inject ( "WinstonLogger" ) protected logger : ILogger ,
@inject ( "DatabaseServer" ) protected databaseServer : DatabaseServer ,
@inject ( "RandomUtil" ) protected randomUtil : RandomUtil ,
@inject ( "WeightedRandomHelper" ) protected weightedRandomHelper : WeightedRandomHelper ,
@inject ( "ApplicationContext" ) protected applicationContext : ApplicationContext ,
@inject ( "ConfigServer" ) protected configServer : ConfigServer ,
)
{
this . locationConfig = this . configServer . getConfig ( ConfigTypes . LOCATION ) ;
}
/ * *
* Make alterations to the base map data passed in
* Loot multipliers / waves / wave start times
* @param raidAdjustments Changes to process on map
* @param mapBase Map to adjust
* /
public makeAdjustmentsToMap ( raidAdjustments : IRaidChanges , mapBase : ILocationBase ) : void
{
this . logger . debug ( ` Adjusting dynamic loot multipliers to ${ raidAdjustments . dynamicLootPercent } % and static loot multipliers to ${ raidAdjustments . staticLootPercent } % of original ` ) ;
// Change loot multipler values before they're used below
this . adjustLootMultipliers ( this . locationConfig . looseLootMultiplier , raidAdjustments . dynamicLootPercent ) ;
this . adjustLootMultipliers ( this . locationConfig . staticLootMultiplier , raidAdjustments . staticLootPercent ) ;
2023-11-29 18:18:45 +01:00
const mapSettings = this . getMapSettings ( mapBase . Id ) ;
if ( mapSettings . adjustWaves )
{
2023-11-29 17:52:13 +01:00
// Make alterations to bot spawn waves now player is simulated spawning later
this . adjustWaves ( mapBase , raidAdjustments )
2023-11-29 18:18:45 +01:00
}
2023-11-29 17:52:13 +01:00
}
/ * *
* Adjust the loot multiplier values passed in to be a % of their original value
* @param mapLootMultiplers Multiplers to adjust
* @param loosePercent Percent to change values to
* /
protected adjustLootMultipliers ( mapLootMultiplers : LootMultiplier , loosePercent : number ) : void
{
for ( const key in mapLootMultiplers )
{
mapLootMultiplers [ key ] = this . randomUtil . getPercentOfValue ( mapLootMultiplers [ key ] , loosePercent ) ;
}
}
/ * *
* Adjust bot waves to act as if player spawned later
* @param mapBase map to adjust
* @param raidAdjustments Map adjustments
* /
protected adjustWaves ( mapBase : ILocationBase , raidAdjustments : IRaidChanges ) : void
{
// Remove waves that spawned before the player joined
const originalWaveCount = mapBase . waves . length ;
mapBase . waves = mapBase . waves . filter ( x = > x . time_max > raidAdjustments . simulatedRaidStartSeconds ) ;
// Adjust wave min/max times to match new simulated start
for ( const wave of mapBase . waves )
{
2023-11-29 18:18:45 +01:00
// Dont let time fall below 0
wave . time_min -= Math . max ( raidAdjustments . simulatedRaidStartSeconds , 0 ) ;
wave . time_max -= Math . max ( raidAdjustments . simulatedRaidStartSeconds , 0 ) ;
2023-11-29 17:52:13 +01:00
}
this . logger . debug ( ` Removed ${ originalWaveCount - mapBase . waves . length } wave from map due to simulated raid start time of ${ raidAdjustments . simulatedRaidStartSeconds / 60 } minutes ` ) ;
}
/ * *
* Create a randomised adjustment to the raid based on map data in location . json
* @param sessionId Session id
* @param request Raid adjustment request
* @returns Response to send to client
* /
public getRaidAdjustments ( sessionId : string , request : IGetRaidTimeRequest ) : IGetRaidTimeResponse
{
2023-11-29 18:18:45 +01:00
const db = this . databaseServer . getTables ( ) ;
2023-11-29 17:52:13 +01:00
const mapBase : ILocationBase = db . locations [ request . Location . toLowerCase ( ) ] . base ;
const baseEscapeTimeMinutes = mapBase . EscapeTimeLimit ;
// Prep result object to return
const result : IGetRaidTimeResponse = {
RaidTimeMinutes : baseEscapeTimeMinutes ,
ExitChanges : [ ] ,
NewSurviveTimeSeconds : null ,
OriginalSurvivalTimeSeconds : db.globals.config.exp.match_end.survived_seconds_requirement
}
// Pmc raid, send default
if ( request . Side . toLowerCase ( ) === "pmc" )
{
return result ;
}
// We're scav adjust values
2023-11-29 18:18:45 +01:00
const mapSettings = this . getMapSettings ( request . Location ) ;
2023-11-29 17:52:13 +01:00
// 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 = Number . parseInt ( this . weightedRandomHelper . getWeightedValue < string > (
mapSettings . reductionPercentWeights ,
) ) ;
2023-12-11 15:42:04 +01:00
const raidTimeRemainingPercent = 100 - chosenRaidReductionPercent ;
2023-11-29 17:52:13 +01:00
// How many minutes raid will last
const newRaidTimeMinutes = Math . floor ( this . randomUtil . reduceValueByPercent ( baseEscapeTimeMinutes , chosenRaidReductionPercent ) ) ;
// Time player spawns into the raid if it was online
const simulatedRaidStartTimeMinutes = baseEscapeTimeMinutes - newRaidTimeMinutes ;
if ( mapSettings . reduceLootByPercent )
{
// Store time reduction percent in app context so loot gen can pick it up later
this . applicationContext . addValue ( ContextVariableType . RAID_ADJUSTMENTS ,
{
2023-12-11 15:42:04 +01:00
dynamicLootPercent : Math.max ( raidTimeRemainingPercent , mapSettings . minDynamicLootPercent ) ,
staticLootPercent : Math.max ( raidTimeRemainingPercent , mapSettings . minStaticLootPercent ) ,
2023-11-29 17:52:13 +01:00
simulatedRaidStartSeconds : simulatedRaidStartTimeMinutes * 60
} ) ;
}
// Update result object with new time
result . RaidTimeMinutes = newRaidTimeMinutes ;
this . logger . debug ( ` Reduced: ${ request . Location } raid time by: ${ chosenRaidReductionPercent } % to ${ newRaidTimeMinutes } minutes ` )
// Calculate how long player needs to be in raid to get a `survived` extract status
result . NewSurviveTimeSeconds = Math . max ( result . OriginalSurvivalTimeSeconds - ( ( baseEscapeTimeMinutes - newRaidTimeMinutes ) * 60 ) , 0 ) ;
const exitAdjustments = this . getExitAdjustments ( mapBase , newRaidTimeMinutes ) ;
if ( exitAdjustments )
{
result . ExitChanges . push ( . . . exitAdjustments ) ;
}
return result ;
}
2023-11-29 18:18:45 +01:00
/ * *
* Get raid start time settings for specific map
* @param location Map Location e . g . bigmap
* @returns IScavRaidTimeLocationSettings
* /
protected getMapSettings ( location : string ) : IScavRaidTimeLocationSettings
{
2023-12-01 10:03:14 +01:00
const mapSettings = this . locationConfig . scavRaidTimeSettings . maps [ location . toLowerCase ( ) ] ;
2023-11-29 18:18:45 +01:00
if ( ! mapSettings )
{
this . logger . warning ( ` Unable to find scav raid time settings for map: ${ location } , using defaults ` ) ;
2023-12-01 10:03:14 +01:00
return this . locationConfig . scavRaidTimeSettings . maps . default ;
2023-11-29 18:18:45 +01:00
}
return mapSettings ;
}
2023-11-29 17:52:13 +01:00
/ * *
* Adjust exit times to handle scavs entering raids part - way through
* @param mapBase Map base file player is on
* @param newRaidTimeMinutes How long raid is in minutes
* @returns List of exit changes to send to client
* /
protected getExitAdjustments ( mapBase : ILocationBase , newRaidTimeMinutes : number ) : ExtractChange [ ]
{
const result = [ ] ;
// Adjust train exits only
for ( const exit of mapBase . exits )
{
if ( exit . PassageRequirement !== "Train" )
{
continue ;
}
// Prepare train adjustment object
const exitChange : ExtractChange = {
Name : exit.Name ,
MinTime : null ,
MaxTime : null ,
Chance : null
}
2023-12-01 09:37:52 +01:00
// At what minute we simulate the player joining the raid
const simulatedRaidEntryTimeMinutes = mapBase . EscapeTimeLimit - newRaidTimeMinutes ;
2023-11-29 17:52:13 +01:00
2023-12-01 09:37:52 +01:00
// 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...
//
2023-12-02 20:41:27 +01:00
const trainArrivalDelaySeconds = this . locationConfig . scavRaidTimeSettings . settings . trainArrivalDelayObservedSeconds ;
2023-12-01 09:37:52 +01:00
// Determine the earliest possible time in the raid when the train would leave
const earliestPossibleDepartureMinutes = ( exit . MinTime + exit . Count + exit . ExfiltrationTime + trainArrivalDelaySeconds ) / 60 ;
2023-11-29 17:52:13 +01:00
// If raid is after last moment train can leave, assume train has already left, disable extract
2023-12-01 09:37:52 +01:00
const mostPossibleTimeRemainingAfterDeparture = mapBase . EscapeTimeLimit - earliestPossibleDepartureMinutes ;
if ( newRaidTimeMinutes < mostPossibleTimeRemainingAfterDeparture )
2023-11-29 17:52:13 +01:00
{
exitChange . Chance = 0 ;
2023-12-01 09:37:52 +01:00
this . logger . debug ( ` Train Exit: ${ exit . Name } disabled as new raid time ${ newRaidTimeMinutes } minutes is below ${ mostPossibleTimeRemainingAfterDeparture } minutes ` ) ;
2023-11-29 17:52:13 +01:00
result . push ( exitChange ) ;
continue ;
}
2023-12-01 09:37:52 +01:00
// 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 ) ;
2023-11-29 17:52:13 +01:00
this . logger . debug ( ` Train appears between: ${ exitChange . MinTime } and ${ exitChange . MaxTime } seconds raid time ` ) ;
result . push ( exitChange ) ;
}
return result . length > 0
? result
: null ;
}
2023-12-01 09:37:52 +01:00
}