Add system that adjusts the loot multipliers for a scav raid based on the percentage of the raid time remaining

Controlled per location

Most maps are capped at 40/50% min

Added multiple config proeprties to configure how system works
Added ability to remove all AppContext values by key

During `getRaidTime()` loot % modifier values are stored in app context,
Then, during `generate()` those values are retreived and applied if found
After loot generation values are reset to their original value
This commit is contained in:
Dev 2023-11-28 12:42:58 +00:00
parent f9cf3242c8
commit 9c2725e2fe
7 changed files with 112 additions and 9 deletions

View File

@ -796,6 +796,9 @@
"looseLootBlacklist": {}, "looseLootBlacklist": {},
"scavRaidTimeSettings": { "scavRaidTimeSettings": {
"bigmap": { "bigmap": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 40, "reducedChancePercent": 40,
"reductionPercentWeights": { "reductionPercentWeights": {
"20": 12, "20": 12,
@ -806,7 +809,10 @@
} }
}, },
"factory4_day": { "factory4_day": {
"reducedChancePercent": 60, "reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 50,
"reductionPercentWeights": { "reductionPercentWeights": {
"20": 12, "20": 12,
"30": 7, "30": 7,
@ -816,6 +822,9 @@
} }
}, },
"factory4_night": { "factory4_night": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 40, "reducedChancePercent": 40,
"reductionPercentWeights": { "reductionPercentWeights": {
"20": 12, "20": 12,
@ -826,6 +835,9 @@
} }
}, },
"interchange": { "interchange": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 50, "reducedChancePercent": 50,
"reductionPercentWeights": { "reductionPercentWeights": {
"20": 12, "20": 12,
@ -836,7 +848,11 @@
} }
}, },
"rezervbase": { "rezervbase": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 40, "reducedChancePercent": 40,
"reductionPercentWeights": { "reductionPercentWeights": {
"20": 12, "20": 12,
"30": 7, "30": 7,
@ -846,6 +862,9 @@
} }
}, },
"laboratory": { "laboratory": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 30, "reducedChancePercent": 30,
"reductionPercentWeights": { "reductionPercentWeights": {
"20": 12, "20": 12,
@ -856,6 +875,9 @@
} }
}, },
"lighthouse": { "lighthouse": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 40, "reducedChancePercent": 40,
"reductionPercentWeights": { "reductionPercentWeights": {
"20": 12, "20": 12,
@ -866,6 +888,9 @@
} }
}, },
"shoreline": { "shoreline": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 40, "reducedChancePercent": 40,
"reductionPercentWeights": { "reductionPercentWeights": {
"20": 25, "20": 25,
@ -876,6 +901,9 @@
} }
}, },
"tarkovstreets": { "tarkovstreets": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 40, "reducedChancePercent": 40,
"reductionPercentWeights": { "reductionPercentWeights": {
"20": 25, "20": 25,
@ -886,6 +914,9 @@
} }
}, },
"woods": { "woods": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 40, "reducedChancePercent": 40,
"reductionPercentWeights": { "reductionPercentWeights": {
"20": 25, "20": 25,
@ -896,6 +927,9 @@
} }
}, },
"default": { "default": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 50,
"reducedChancePercent": 25, "reducedChancePercent": 25,
"reductionPercentWeights": { "reductionPercentWeights": {
"20": 12, "20": 12,

View File

@ -61,4 +61,12 @@ export class ApplicationContext
list.add(new ContextVariable(value, type)); list.add(new ContextVariable(value, type));
this.variables.set(type, list); this.variables.set(type, list);
} }
public clearValues(type: ContextVariableType): void
{
this.variables.has(type)
{
this.variables.delete(type);
}
}
} }

View File

@ -6,5 +6,6 @@ export enum ContextVariableType {
/** Timestamp when client first connected */ /** Timestamp when client first connected */
CLIENT_START_TIMESTAMP = 2, CLIENT_START_TIMESTAMP = 2,
/** When player is loading into map and loot is requested */ /** When player is loading into map and loot is requested */
REGISTER_PLAYER_REQUEST = 3 REGISTER_PLAYER_REQUEST = 3,
LOOT_MULTIPLER_CHANGE = 4,
} }

View File

@ -598,12 +598,22 @@ export class GameController
} }
// Get the weighted percent to reduce the raid time by // Get the weighted percent to reduce the raid time by
const chosenRaidReductionPercent = this.weightedRandomHelper.getWeightedValue<string>( const chosenRaidReductionPercent = Number.parseInt(this.weightedRandomHelper.getWeightedValue<string>(
mapSettings.reductionPercentWeights, mapSettings.reductionPercentWeights,
); ));
if (mapSettings.reduceLootByPercent)
{
// Store time reduction percent in app context so loot gen can pick it up later
this.applicationContext.addValue(ContextVariableType.LOOT_MULTIPLER_CHANGE,
{
dynamicLootPercent: Math.max(chosenRaidReductionPercent, mapSettings.minDynamicLootPercent),
staticLootPercent: Math.max(chosenRaidReductionPercent, mapSettings.minStaticLootPercent)
});
}
// How many minutes raid will be // How many minutes raid will be
const newRaidTimeMinutes = Math.floor(this.randomUtil.reduceValueByPercent(baseEscapeTimeMinutes, Number.parseInt(chosenRaidReductionPercent))); const newRaidTimeMinutes = Math.floor(this.randomUtil.reduceValueByPercent(baseEscapeTimeMinutes, chosenRaidReductionPercent));
// Update result object with new time // Update result object with new time
result.RaidTimeMinutes = newRaidTimeMinutes; result.RaidTimeMinutes = newRaidTimeMinutes;

View File

@ -1,5 +1,7 @@
import { inject, injectable } from "tsyringe"; import { inject, injectable } from "tsyringe";
import { ApplicationContext } from "@spt-aki/context/ApplicationContext";
import { ContextVariableType } from "@spt-aki/context/ContextVariableType";
import { LocationGenerator } from "@spt-aki/generators/LocationGenerator"; import { LocationGenerator } from "@spt-aki/generators/LocationGenerator";
import { LootGenerator } from "@spt-aki/generators/LootGenerator"; import { LootGenerator } from "@spt-aki/generators/LootGenerator";
import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper"; import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper";
@ -12,7 +14,8 @@ import { IGetLocationRequestData } from "@spt-aki/models/eft/location/IGetLocati
import { AirdropTypeEnum } from "@spt-aki/models/enums/AirdropType"; import { AirdropTypeEnum } from "@spt-aki/models/enums/AirdropType";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { IAirdropConfig } from "@spt-aki/models/spt/config/IAirdropConfig"; import { IAirdropConfig } from "@spt-aki/models/spt/config/IAirdropConfig";
import { ILocationConfig } from "@spt-aki/models/spt/config/ILocationConfig"; import { ILocationConfig, LootMultiplier } from "@spt-aki/models/spt/config/ILocationConfig";
import { ILootMultiplerChange } from "@spt-aki/models/spt/location/ILootMultiplerChange";
import { ILocations } from "@spt-aki/models/spt/server/ILocations"; import { ILocations } from "@spt-aki/models/spt/server/ILocations";
import { LootRequest } from "@spt-aki/models/spt/services/LootRequest"; import { LootRequest } from "@spt-aki/models/spt/services/LootRequest";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
@ -42,6 +45,7 @@ export class LocationController
@inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("TimeUtil") protected timeUtil: TimeUtil, @inject("TimeUtil") protected timeUtil: TimeUtil,
@inject("ConfigServer") protected configServer: ConfigServer, @inject("ConfigServer") protected configServer: ConfigServer,
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
) )
{ {
this.airdropConfig = this.configServer.getConfig(ConfigTypes.AIRDROP); this.airdropConfig = this.configServer.getConfig(ConfigTypes.AIRDROP);
@ -83,10 +87,23 @@ export class LocationController
return output; return output;
} }
// Check for a loot multipler adjustment in app context and apply if one is found
let locationConfigCopy: ILocationConfig;
const lootMultiplierAdjustment = this.applicationContext.getLatestValue(ContextVariableType.LOOT_MULTIPLER_CHANGE)?.getValue<ILootMultiplerChange>();
if (lootMultiplierAdjustment)
{
this.logger.debug(`Adjusting dynamic loot multipliers to ${lootMultiplierAdjustment.dynamicLootPercent}% and static loot multipliers to ${lootMultiplierAdjustment.staticLootPercent}% of original`)
locationConfigCopy = this.jsonUtil.clone(this.locationConfig); // Clone values so they can be used to reset originals later
// Change loot multipler values before they're used below
this.adjustLootMultipliers(this.locationConfig.looseLootMultiplier, lootMultiplierAdjustment.dynamicLootPercent);
this.adjustLootMultipliers(this.locationConfig.staticLootMultiplier, lootMultiplierAdjustment.staticLootPercent);
}
const staticAmmoDist = this.jsonUtil.clone(db.loot.staticAmmo); const staticAmmoDist = this.jsonUtil.clone(db.loot.staticAmmo);
// Create containers and add loot to them // Create containers and add loot to them
const staticLoot = this.locationGenerator.generateStaticContainers(location.base, staticAmmoDist); const staticLoot = this.locationGenerator.generateStaticContainers(output, staticAmmoDist);
output.Loot.push(...staticLoot); output.Loot.push(...staticLoot);
// Add dyanmic loot to output loot // Add dyanmic loot to output loot
@ -107,9 +124,32 @@ export class LocationController
); );
this.logger.success(this.localisationService.getText("location-generated_success", name)); this.logger.success(this.localisationService.getText("location-generated_success", name));
// Reset loot multipliers back to original values
if (lootMultiplierAdjustment)
{
this.logger.debug("Resetting loot multipliers back to their original values");
this.locationConfig.staticLootMultiplier = locationConfigCopy.staticLootMultiplier;
this.locationConfig.looseLootMultiplier = locationConfigCopy.looseLootMultiplier;
this.applicationContext.clearValues(ContextVariableType.LOOT_MULTIPLER_CHANGE);
}
return output; return output;
} }
/**
* 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);
}
}
/** /**
* Handle client/locations * Handle client/locations
* Get all maps base location properties without loot data * Get all maps base location properties without loot data

View File

@ -44,8 +44,13 @@ export interface ILocationConfig extends IBaseConfig
export interface IScavRaidTimeLocationSettings export interface IScavRaidTimeLocationSettings
{ {
reducedChancePercent: number /** Should loot be reduced by same percent length of raid is reduced by */
reductionPercentWeights: Record<string, number> reduceLootByPercent: boolean;
minStaticLootPercent: number;
minDynamicLootPercent: number;
/** Chance raid time is reduced */
reducedChancePercent: number;
reductionPercentWeights: Record<string, number>;
} }
export interface IContainerRandomistionSettings export interface IContainerRandomistionSettings

View File

@ -0,0 +1,5 @@
export interface ILootMultiplerChange
{
dynamicLootPercent: number
staticLootPercent: number
}