2022-07-30 23:04:18 -04:00
import { IBotConfig } from "@spt-aki/models/spt/config/IBotConfig" ;
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer" ;
2022-08-03 21:57:37 -04:00
import { Logger } from "./logger" ;
import type { BossLocationSpawn } from "@spt-aki/models/eft/common/ILocationBase" ;
import { Money } from "@spt-aki/models/enums/Money"
2022-08-04 16:32:36 -04:00
import { Config } from "../config/config" ;
2022-08-04 18:05:23 -04:00
import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper" ;
import { ITrader } from "@spt-aki/models/eft/common/tables/ITrader" ;
2022-07-30 23:04:18 -04:00
export class Bots
{
2022-08-04 22:00:54 -04:00
private modConfig : Config = require ( "../config/config.json" ) ;
2022-08-03 21:57:37 -04:00
private logger : Logger ;
2022-07-30 23:04:18 -04:00
private botConfig : IBotConfig ;
2022-08-08 20:03:48 -04:00
private tables : DatabaseServer ;
2022-08-04 18:05:23 -04:00
private traders : Record < string , ITrader > ;
private weightedRandomHelper : WeightedRandomHelper ;
2022-07-30 23:04:18 -04:00
2022-08-04 18:05:23 -04:00
constructor ( logger : Logger , databaseServer : DatabaseServer , botConfig : IBotConfig , weightedRandomHelper : WeightedRandomHelper )
2022-07-30 23:04:18 -04:00
{
this . logger = logger ;
this . botConfig = botConfig ;
this . tables = databaseServer . getTables ( ) ;
2022-08-08 20:03:48 -04:00
this . traders = this . tables . traders ;
2022-08-04 18:05:23 -04:00
this . weightedRandomHelper = weightedRandomHelper ;
2022-07-30 23:04:18 -04:00
}
public updateBots ( ) : void
{
2022-08-08 20:03:48 -04:00
// modConfig variables
2022-08-03 21:57:37 -04:00
const mod = this . modConfig . bots ;
2022-08-08 20:03:48 -04:00
const modPMC = this . modConfig . bots . pmc ;
const modScav = this . modConfig . bots . scav ;
const preWipe = this . modConfig . prewipeEvents ;
// Server side variables
const pmc = this . botConfig . pmc ;
const lootNValue = this . botConfig . lootNValue ;
// Start modifications
2022-08-03 21:57:37 -04:00
2022-08-08 20:03:48 -04:00
// Checks if straight up difficulty selection is used or not to determine if it should use that or if it should then go to use weighted difficulties.
if ( modPMC . difficultyWeights . difficulty != "asonline" )
2022-07-30 23:04:18 -04:00
{
2022-08-08 20:03:48 -04:00
// Uses PMC difficulty weighting if straight up difficulty selection is not used.
if ( modPMC . difficultyWeights . useWeights )
2022-08-04 18:05:23 -04:00
{
const chosenDifficulty = this . chooseRandomWeightedDifficulty ( ) ;
this . logger . info ( "PMC Difficulty Chance Weights Patched" ) ;
this . logger . info ( ` PMC Difficulty Chosen: ${ chosenDifficulty } ` ) ;
}
2022-08-08 20:03:48 -04:00
// Uses PMC difficulty if weighting is disabled.
2022-08-04 18:05:23 -04:00
else
{
2022-08-08 20:03:48 -04:00
pmc . difficulty = modPMC . difficultyWeights . difficulty ;
this . logger . info ( ` PMC Bot Difficulty set to ${ modPMC . difficultyWeights . difficulty } ` ) ;
2022-08-04 18:05:23 -04:00
}
2022-07-30 23:04:18 -04:00
}
2022-08-08 20:03:48 -04:00
// Enables common and secure containers to spawn on PMCs by whitelisting the parent IDs. Rarity adjusted via PMC lootNValue. Default true.
if ( modPMC . containersOnPMCs )
2022-07-30 23:04:18 -04:00
{
2022-08-03 21:57:37 -04:00
this . containersOnPMCs ( ) ;
2022-08-08 20:03:48 -04:00
this . logger . info ( ` Containers On PMCs: ${ modPMC . containersOnPMCs } ` ) ;
2022-07-30 23:04:18 -04:00
}
2022-08-08 20:03:48 -04:00
// Chance that PMC bot will be USEC or BEAR. Default is 50%
if ( modPMC . isUsec != 50 )
2022-07-30 23:04:18 -04:00
{
2022-08-08 20:03:48 -04:00
pmc . isUsec = modPMC . isUsec ;
this . logger . info ( ` PMC isUsec Chance is: ${ modPMC . isUsec } ` ) ;
2022-07-30 23:04:18 -04:00
}
2022-08-08 20:03:48 -04:00
2022-08-03 21:57:37 -04:00
// Max Loot Value in Rubles for PMC bots in Backpack, Pockets, and Vest respectively. Default is 150,000/50,000/50,000
2022-08-08 20:03:48 -04:00
if ( modPMC . maxBackpackLootTotalRub != 150000
|| modPMC . maxPocketLootTotalRub != 50000
|| modPMC . maxVestLootTotalRub != 50000 )
2022-07-30 23:04:18 -04:00
{
2022-08-08 20:03:48 -04:00
this . changeMaxLootvalue ( ) ;
this . logger . info ( ` PMC Loot Value totals changed! \ n Max Backpack Total Value: ${ modPMC . maxBackpackLootTotalRub } \ n Max Pocket Total Value: ${ modPMC . maxPocketLootTotalRub } \ n Max Vest Total Value: ${ modPMC . maxVestLootTotalRub } ` ) ;
2022-07-30 23:04:18 -04:00
}
2022-08-08 20:03:48 -04:00
// Chance that the PMC bot of your faction (BEAR/USEC) will be hostile or not. Default is 50%.
if ( modPMC . chanceSameSideIsHostile != 50 )
2022-07-30 23:04:18 -04:00
{
2022-08-08 20:03:48 -04:00
pmc . chanceSameSideIsHostilePercent = modPMC . chanceSameSideIsHostile ;
this . logger . info ( ` Chance Same Side Is Hostle is ${ modPMC . chanceSameSideIsHostile } ` ) ;
2022-07-30 23:04:18 -04:00
}
2022-08-08 20:03:48 -04:00
// Adjusts the Max Bot Cap located in configs/bot.json/maxBotCap
2022-08-03 21:57:37 -04:00
if ( mod . maxBotCap != 20 )
2022-07-30 23:04:18 -04:00
{
2022-08-03 21:57:37 -04:00
this . botConfig . maxBotCap = mod . maxBotCap ;
this . logger . info ( ` Bot Cap is now ${ mod . maxBotCap } ` ) ;
2022-07-30 23:04:18 -04:00
}
2022-08-08 20:03:48 -04:00
// Modifies the lootNValue of PMC or Scav if configured outside of the defaults.
if ( modPMC . lootNValue != 3 || modScav . lootNValue != 4 )
2022-08-04 16:32:36 -04:00
{
2022-08-08 20:03:48 -04:00
lootNValue . scav = modScav . lootNValue ;
lootNValue . pmc = modPMC . lootNValue ;
this . logger . info ( ` lootNValue for bots has been changed! \ n Scav lootNValue set to ${ modScav . lootNValue } \ n PMC lootNValue set to ${ modPMC . lootNValue } ` ) ;
2022-08-04 16:32:36 -04:00
}
2022-08-04 15:35:21 -04:00
2022-08-08 20:03:48 -04:00
// Adjusts the chance for PMC to spawn instead of the default bot type if configured outside of the default values.
const pmcChance = modPMC . convertIntoPmcChance ;
if ( pmcChance . assault . min != 15 || pmcChance . assault . max != 40
|| pmcChance . cursedAssault . min != 15 || pmcChance . cursedAssault . max != 40
|| pmcChance . pmcBot . min != 15 || pmcChance . pmcBot . max != 30
|| pmcChance . exUsec . min != 5 || pmcChance . exUsec . max != 20 )
2022-08-03 21:57:37 -04:00
{
2022-08-08 20:03:48 -04:00
this . adjustPmcChance ( ) ;
2022-08-03 21:57:37 -04:00
this . logger . info ( "Chance to Convert Bots into PMC Patched" ) ;
2022-08-08 20:03:48 -04:00
}
2022-08-04 18:05:23 -04:00
2022-07-30 23:04:18 -04:00
2022-08-08 20:03:48 -04:00
// Makes *all* bosses spawn chance configurable.
2022-08-03 21:57:37 -04:00
if ( mod . bossChance . activated )
{
2022-08-08 20:03:48 -04:00
this . configureBossChance ( ) ;
this . logger . info ( ` Boss Chance set to ${ mod . bossChance . chance } ` )
2022-08-03 21:57:37 -04:00
}
2022-07-30 23:04:18 -04:00
2022-08-08 20:03:48 -04:00
2022-08-03 21:57:37 -04:00
// Prewipe Events
2022-08-08 20:03:48 -04:00
// Spawn Killa On Factory
if ( preWipe . killaOnFactory )
2022-08-03 21:57:37 -04:00
{
2022-08-08 20:03:48 -04:00
this . spawnKillaOnFactory ( ) ;
this . logger . info ( "Killa On Factry Enabled" ) ;
2022-08-03 21:57:37 -04:00
}
2022-07-30 23:04:18 -04:00
2022-08-08 20:03:48 -04:00
// Spawns All Bosses On Reserve
if ( preWipe . allBossesOnReserve )
2022-08-03 21:57:37 -04:00
{
2022-08-08 20:03:48 -04:00
this . spawnAllBossesOnReserve ( ) ;
2022-08-03 21:57:37 -04:00
this . logger . info ( "Bosses On Reserve Prewipe Event Enabled" ) ;
}
2022-08-08 20:03:48 -04:00
2022-08-03 21:57:37 -04:00
2022-08-08 20:03:48 -04:00
// Spawns Gluhar On Labs
if ( preWipe . gluharOnLabs )
2022-08-03 21:57:37 -04:00
{
2022-08-08 20:03:48 -04:00
this . spawnGluharOnLabs ( ) ;
this . logger . info ( "Gluhar On Labs Prewipe Event Enabled" ) ;
2022-08-03 21:57:37 -04:00
}
2022-07-30 23:04:18 -04:00
2022-08-08 20:03:48 -04:00
2022-08-03 21:57:37 -04:00
// All cheap items on traders
2022-08-08 20:03:48 -04:00
if ( preWipe . allTradersSellCheapItems )
2022-08-03 21:57:37 -04:00
{
2022-08-08 20:03:48 -04:00
this . allTradersSellCheapItems ( ) ;
2022-08-03 21:57:37 -04:00
this . logger . info ( "Cheap Items On Traders Prewipe Event Enabled" ) ;
}
2022-07-30 23:04:18 -04:00
2022-08-08 20:03:48 -04:00
// Makes Obdolbos Super Powered
if ( preWipe . makeObdolbosPowerful )
2022-08-03 21:57:37 -04:00
{
2022-08-08 20:03:48 -04:00
this . makeObdolbosPowerful ( ) ;
2022-08-03 21:57:37 -04:00
this . logger . info ( "Make Obdolbos Powerful Prewipe Event Enabled" ) ;
}
2022-08-08 20:03:48 -04:00
if ( modPMC . looseWeaponInBackpackChance != 15 || modPMC . looseWeaponInBackpackLoot . max != 1 || modPMC . looseWeaponInBackpackLoot . min != 1 )
{
this . changeLooseWeapon ( ) ;
this . logger . info ( "Loose Weapon In PMC Backpack Values Patched" ) ;
}
2022-07-30 23:04:18 -04:00
}
2022-08-08 20:03:48 -04:00
// Functions start here.
// Function to enable secured and common containers on PMCs.
2022-08-03 21:57:37 -04:00
private containersOnPMCs ( ) : void
2022-07-30 23:04:18 -04:00
{
2022-08-03 21:57:37 -04:00
const dynaLoot = this . botConfig . pmc . dynamicLoot . whitelist ;
2022-07-30 23:04:18 -04:00
2022-08-03 21:57:37 -04:00
dynaLoot . push ( "5448bf274bdc2dfc2f8b456a" ) ;
dynaLoot . push ( "5795f317245977243854e041" ) ;
}
2022-07-30 23:04:18 -04:00
2022-08-04 18:05:23 -04:00
private chooseRandomWeightedDifficulty ( ) : string
{
const chosenDifficulty = this . weightedRandomHelper . getWeightedInventoryItem ( this . modConfig . bots . pmc . difficultyWeights . weights ) ;
this . botConfig . pmc . difficulty = chosenDifficulty ;
return chosenDifficulty ;
}
2022-08-04 14:43:56 -04:00
2022-08-08 20:03:48 -04:00
public createBossWave ( role : string , chance : number , followers : string , escortAmount : number , zones : string ) : any
2022-08-04 14:43:56 -04:00
{
return {
"BossName" : role ,
"BossChance" : chance ,
"BossZone" : zones ,
"BossPlayer" : false ,
"BossDifficult" : "normal" ,
"BossEscortType" : followers ,
"BossEscortDifficult" : "normal" ,
"BossEscortAmount" : escortAmount ,
"Time" : - 1
}
}
2022-08-08 20:03:48 -04:00
private adjustPmcChance ( ) : void
{
const pmcConfig = this . botConfig . pmc . convertIntoPmcChance ;
const modConfig = this . modConfig . bots . pmc . convertIntoPmcChance ;
pmcConfig . assault . min = modConfig . assault . min ;
pmcConfig . assault . max = modConfig . assault . max ;
pmcConfig . cursedassault . min = modConfig . cursedAssault . min ;
pmcConfig . cursedassault . max = modConfig . cursedAssault . max ;
pmcConfig . pmcbot . min = modConfig . pmcBot . min ;
pmcConfig . pmcbot . max = modConfig . pmcBot . max ;
pmcConfig . exusec . min = modConfig . exUsec . min ;
pmcConfig . exusec . max = modConfig . exUsec . max ;
}
private changeMaxLootvalue ( ) : void
{
const lootConfig = this . botConfig . pmc ;
const modConfig = this . modConfig . bots . pmc ;
lootConfig . maxBackpackLootTotalRub = modConfig . maxBackpackLootTotalRub ;
lootConfig . maxPocketLootTotalRub = modConfig . maxPocketLootTotalRub ;
lootConfig . maxVestLootTotalRub = modConfig . maxVestLootTotalRub ;
}
private configureBossChance ( ) : void
{
const locations = this . tables . locations ;
for ( const i in locations )
{
if ( i !== "base" )
{
if ( locations [ i ] . base . BossLocationSpawn !== [ ] )
{
for ( const x in locations [ i ] . base . BossLocationSpawn )
{
locations [ i ] . base . BossLocationSpawn [ x ] . BossChance = this . modConfig . bots . bossChance . chance ;
}
}
}
}
}
private spawnKillaOnFactory ( ) : void
{
const locations = this . tables . locations ;
const killaWave = this . createBossWave ( "bossKilla" , 100 , "followerBully" , 0 , locations . factory4_day . base . OpenZones ) ;
this . tables . locations . factory4_day . base . BossLocationSpawn . push ( killaWave ) ;
locations . factory4_night . base . BossLocationSpawn . push ( killaWave ) ;
}
private spawnAllBossesOnReserve ( ) : void
{
const locations = this . tables . locations ;
let bossWave = this . createBossWave ( "bossKilla" , 100 , "followerBully" , 0 , locations . rezervbase . base . OpenZones ) ;
locations . rezervbase . base . BossLocationSpawn . push ( bossWave ) ;
bossWave = this . createBossWave ( "bossBully" , 100 , "followerBully" , 4 , locations . rezervbase . base . OpenZones ) ;
locations . rezervbase . base . BossLocationSpawn . push ( bossWave ) ;
bossWave = this . createBossWave ( "bossKojaniy" , 100 , "followerKojaniy" , 2 , locations . rezervbase . base . OpenZones ) ;
locations . rezervbase . base . BossLocationSpawn . push ( bossWave ) ;
bossWave = this . createBossWave ( "bossSanitar" , 100 , "followerSanitar" , 2 , locations . rezervbase . base . OpenZones ) ;
locations . rezervbase . base . BossLocationSpawn . push ( bossWave ) ;
}
private spawnGluharOnLabs ( ) : void
{
const locations = this . tables . locations ;
const glugluWave : BossLocationSpawn =
{
"BossName" : "bossGluhar" ,
"BossChance" : 43 ,
"BossZone" : "ZoneRailStrorage,ZoneRailStrorage,ZoneRailStrorage,ZonePTOR1,ZonePTOR2,ZoneBarrack,ZoneBarrack,ZoneBarrack,ZoneSubStorage" ,
"BossPlayer" : false ,
"BossDifficult" : "normal" ,
"BossEscortType" : "followerGluharAssault" ,
"BossEscortDifficult" : "normal" ,
"BossEscortAmount" : "0" ,
"Time" : - 1 ,
"TriggerId" : "" ,
"TriggerName" : "" ,
"Supports" : [
{
"BossEscortType" : "followerGluharAssault" ,
"BossEscortDifficult" : [
"normal"
] ,
"BossEscortAmount" : "2"
} ,
{
"BossEscortType" : "followerGluharSecurity" ,
"BossEscortDifficult" : [
"normal"
] ,
"BossEscortAmount" : "2"
} ,
{
"BossEscortType" : "followerGluharScout" ,
"BossEscortDifficult" : [
"normal"
] ,
"BossEscortAmount" : "2"
}
] ,
RandomTimeSpawn : false
}
glugluWave . BossZone = locations . laboratory . base . OpenZones ;
locations . laboratory . base . BossLocationSpawn . push ( glugluWave ) ;
}
private allTradersSellCheapItems ( ) : void
{
for ( const trader in this . traders )
{
for ( const assort in this . traders [ trader ] . assort . barter_scheme )
{
const itemScheme = this . traders [ trader ] . assort . barter_scheme [ assort ] ;
switch ( itemScheme [ 0 ] [ 0 ] . _tpl )
{
case Money . ROUBLES :
itemScheme [ 0 ] [ 0 ] . count = itemScheme [ 0 ] [ 0 ] . count * 0.01 ;
break ;
case Money . DOLLARS :
itemScheme [ 0 ] [ 0 ] . count = itemScheme [ 0 ] [ 0 ] . count * 0.1 ;
break ;
case Money . EUROS :
itemScheme [ 0 ] [ 0 ] . count = itemScheme [ 0 ] [ 0 ] . count * 0.05 ;
break ;
default :
break ;
}
}
}
}
private makeObdolbosPowerful ( ) : void
{
const obdolbosBuff = [
{
"BuffType" : "StaminaRate" ,
"Chance" : 1 ,
"Delay" : 1 ,
"Duration" : 1800 ,
"Value" : 0.5 ,
"AbsoluteValue" : true ,
"SkillName" : ""
} ,
{
"BuffType" : "SkillRate" ,
"Chance" : 1 ,
"Delay" : 1 ,
"Duration" : 1800 ,
"Value" : 10 ,
"AbsoluteValue" : true ,
"SkillName" : "Endurance"
} ,
{
"BuffType" : "SkillRate" ,
"Chance" : 1 ,
"Delay" : 1 ,
"Duration" : 1800 ,
"Value" : 10 ,
"AbsoluteValue" : true ,
"SkillName" : "Strength"
} ,
{
"BuffType" : "SkillRate" ,
"Chance" : 1 ,
"Delay" : 1 ,
"Duration" : 1800 ,
"Value" : 20 ,
"AbsoluteValue" : true ,
"SkillName" : "StressResistance"
} ,
{
"BuffType" : "SkillRate" ,
"Chance" : 1 ,
"Delay" : 1 ,
"Duration" : 1800 ,
"Value" : 20 ,
"AbsoluteValue" : true ,
"SkillName" : "Charisma"
} ,
{
"BuffType" : "SkillRate" ,
"Chance" : 1 ,
"Delay" : 1 ,
"Duration" : 1800 ,
"Value" : - 20 ,
"AbsoluteValue" : true ,
"SkillName" : "Memory"
} ,
{
"BuffType" : "SkillRate" ,
"Chance" : 1 ,
"Delay" : 1 ,
"Duration" : 1800 ,
"Value" : - 20 ,
"AbsoluteValue" : true ,
"SkillName" : "Intellect"
} ,
{
"BuffType" : "SkillRate" ,
"Chance" : 1 ,
"Delay" : 1 ,
"Duration" : 1800 ,
"Value" : - 20 ,
"AbsoluteValue" : true ,
"SkillName" : "Attention"
} ,
{
"BuffType" : "Pain" ,
"Chance" : 0.25 ,
"Delay" : 1 ,
"Duration" : 1800 ,
"Value" : 0 ,
"AbsoluteValue" : false ,
"SkillName" : ""
} ,
{
"BuffType" : "StomachBloodloss" ,
"Chance" : 0.25 ,
"Delay" : 1 ,
"Duration" : 1800 ,
"Value" : 0 ,
"AbsoluteValue" : false ,
"SkillName" : ""
} ,
{
"BuffType" : "HydrationRate" ,
"Chance" : 0.25 ,
"Delay" : 1 ,
"Duration" : 1800 ,
"Value" : - 0.05 ,
"AbsoluteValue" : true ,
"SkillName" : ""
} ,
{
"BuffType" : "EnergyRate" ,
"Chance" : 0.25 ,
"Delay" : 1 ,
"Duration" : 1800 ,
"Value" : - 0.05 ,
"AbsoluteValue" : true ,
"SkillName" : ""
} ,
{
"BuffType" : "DamageModifier" ,
"Chance" : 0.25 ,
"Delay" : 1 ,
"Duration" : 1800 ,
"Value" : 0.2 ,
"AbsoluteValue" : false ,
"SkillName" : ""
} ,
{
"BuffType" : "QuantumTunnelling" ,
"Chance" : 0.25 ,
"Delay" : 1 ,
"Duration" : 1800 ,
"Value" : 0 ,
"AbsoluteValue" : false ,
"SkillName" : ""
} ]
this . tables . globals . config . Health . Effects . Stimulator . Buffs . Buffs_Obdolbos = obdolbosBuff ;
}
private changeLooseWeapon ( ) : void
{
const pmcConfig = this . botConfig . pmc ;
const modConfig = this . modConfig . bots . pmc ;
pmcConfig . looseWeaponInBackpackChancePercent = modConfig . looseWeaponInBackpackChance ;
pmcConfig . looseWeaponInBackpackLootMinMax . min = modConfig . looseWeaponInBackpackLoot . min ;
pmcConfig . looseWeaponInBackpackLootMinMax . max = modConfig . looseWeaponInBackpackLoot . max ;
}
2022-08-03 21:57:37 -04:00
}