2024-05-21 19:10:17 +01:00
using SPT.Common.Http ;
2023-10-10 10:58:33 +00:00
using BepInEx.Logging ;
using EFT ;
using Newtonsoft.Json ;
using System ;
using System.Collections.Generic ;
using System.Linq ;
2024-05-21 19:10:17 +01:00
namespace SPT.Custom.CustomAI
2023-10-10 10:58:33 +00:00
{
public class AIBrainSpawnWeightAdjustment
{
2024-08-28 12:52:54 +01:00
private static AIBrains _aiBrainsCache ;
private static DateTime _aiBrainCacheDate ;
private static readonly Random random = new ( ) ;
private readonly ManualLogSource _logger ;
2023-10-10 10:58:33 +00:00
public AIBrainSpawnWeightAdjustment ( ManualLogSource logger )
{
2024-08-28 12:52:54 +01:00
_logger = logger ;
2023-10-10 10:58:33 +00:00
}
2023-11-26 21:50:49 +00:00
public WildSpawnType GetRandomisedPlayerScavType ( BotOwner botOwner , string currentMapName )
2023-10-10 10:58:33 +00:00
{
2023-11-26 21:50:49 +00:00
// Get map brain weights from server and cache
2024-08-28 12:52:54 +01:00
if ( _aiBrainsCache = = null | | CacheIsStale ( ) )
2023-11-26 21:50:49 +00:00
{
ResetCacheDate ( ) ;
HydrateCacheWithServerData ( ) ;
2024-10-03 10:58:47 +01:00
if ( ! _aiBrainsCache ! . playerScav . TryGetValue ( currentMapName . ToLower ( ) , out _ ) )
2023-11-26 21:50:49 +00:00
{
2024-08-28 12:52:54 +01:00
throw new Exception ( $"Bots were refreshed from the server but the assault cache still doesn't contain data" ) ;
2023-11-26 21:50:49 +00:00
}
}
// Choose random weighted brain
2024-08-28 12:52:54 +01:00
var randomType = WeightedRandom ( _aiBrainsCache . playerScav [ currentMapName . ToLower ( ) ] . Keys . ToArray ( ) , _aiBrainsCache . playerScav [ currentMapName . ToLower ( ) ] . Values . ToArray ( ) ) ;
2023-11-26 21:50:49 +00:00
if ( Enum . TryParse ( randomType , out WildSpawnType newAiType ) )
{
2024-08-28 12:52:54 +01:00
_logger . LogWarning ( $"Updated player scav bot to use: {newAiType} brain" ) ;
2023-11-26 21:50:49 +00:00
return newAiType ;
}
else
{
2024-08-28 12:52:54 +01:00
_logger . LogWarning ( $"Updated player scav bot {botOwner.Profile.Info.Nickname}: {botOwner.Profile.Info.Settings.Role} to use: {newAiType} brain" ) ;
2023-11-26 21:50:49 +00:00
return newAiType ;
}
2023-10-10 10:58:33 +00:00
}
public WildSpawnType GetAssaultScavWildSpawnType ( BotOwner botOwner , string currentMapName )
{
// Get map brain weights from server and cache
2024-08-28 12:52:54 +01:00
if ( _aiBrainsCache = = null | | CacheIsStale ( ) )
2023-10-10 10:58:33 +00:00
{
ResetCacheDate ( ) ;
HydrateCacheWithServerData ( ) ;
2024-10-03 10:58:47 +01:00
if ( ! _aiBrainsCache ! . assault . TryGetValue ( currentMapName . ToLower ( ) , out _ ) )
2023-10-10 10:58:33 +00:00
{
throw new Exception ( $"Bots were refreshed from the server but the assault cache still doesnt contain data" ) ;
}
}
// Choose random weighted brain
2024-08-28 12:52:54 +01:00
var randomType = WeightedRandom ( _aiBrainsCache . assault [ currentMapName . ToLower ( ) ] . Keys . ToArray ( ) , _aiBrainsCache . assault [ currentMapName . ToLower ( ) ] . Values . ToArray ( ) ) ;
2023-10-10 10:58:33 +00:00
if ( Enum . TryParse ( randomType , out WildSpawnType newAiType ) )
{
2024-08-28 12:52:54 +01:00
_logger . LogWarning ( $"Updated assault bot to use: {newAiType} brain" ) ;
2023-10-10 10:58:33 +00:00
return newAiType ;
}
else
{
2024-08-28 12:52:54 +01:00
_logger . LogWarning ( $"Updated assault bot {botOwner.Profile.Info.Nickname}: {botOwner.Profile.Info.Settings.Role} to use: {newAiType} brain" ) ;
2023-10-10 10:58:33 +00:00
return newAiType ;
}
}
public WildSpawnType GetPmcWildSpawnType ( BotOwner botOwner_0 , WildSpawnType pmcType , string currentMapName )
{
2024-08-28 12:52:54 +01:00
if ( _aiBrainsCache = = null | | ! _aiBrainsCache . pmc . TryGetValue ( pmcType , out var botSettings ) | | CacheIsStale ( ) )
2023-10-10 10:58:33 +00:00
{
ResetCacheDate ( ) ;
HydrateCacheWithServerData ( ) ;
2024-10-03 10:58:47 +01:00
if ( ! _aiBrainsCache ! . pmc . TryGetValue ( pmcType , out botSettings ) )
2023-10-10 10:58:33 +00:00
{
throw new Exception ( $"Bots were refreshed from the server but the cache still doesnt contain an appropriate bot for type {botOwner_0.Profile.Info.Settings.Role}" ) ;
}
}
var mapSettings = botSettings [ currentMapName . ToLower ( ) ] ;
var randomType = WeightedRandom ( mapSettings . Keys . ToArray ( ) , mapSettings . Values . ToArray ( ) ) ;
if ( Enum . TryParse ( randomType , out WildSpawnType newAiType ) )
{
2024-08-28 12:52:54 +01:00
_logger . LogWarning ( $"Updated spt bot {botOwner_0.Profile.Info.Nickname}: {botOwner_0.Profile.Info.Settings.Role} to use: {newAiType} brain" ) ;
2023-10-10 10:58:33 +00:00
return newAiType ;
}
2024-08-28 12:52:54 +01:00
_logger . LogError ( $"Couldnt not update spt bot {botOwner_0.Profile.Info.Nickname} to random type {randomType}, does not exist for WildSpawnType enum, defaulting to 'assault'" ) ;
return WildSpawnType . assault ;
2023-10-10 10:58:33 +00:00
}
private void HydrateCacheWithServerData ( )
{
// Get weightings for PMCs from server and store in dict
var result = RequestHandler . GetJson ( $"/singleplayer/settings/bot/getBotBehaviours/" ) ;
2024-08-28 12:52:54 +01:00
_aiBrainsCache = JsonConvert . DeserializeObject < AIBrains > ( result ) ;
_logger . LogWarning ( $"Cached ai brain weights in client" ) ;
2023-10-10 10:58:33 +00:00
}
private void ResetCacheDate ( )
{
2024-08-28 12:52:54 +01:00
_aiBrainCacheDate = DateTime . Now ;
_aiBrainsCache ? . pmc ? . Clear ( ) ;
_aiBrainsCache ? . assault ? . Clear ( ) ;
_aiBrainsCache ? . playerScav ? . Clear ( ) ;
2023-10-10 10:58:33 +00:00
}
2024-08-28 12:52:54 +01:00
/// <summary>
/// Has the ai brain cache been around longer than 15 minutes
/// </summary>
/// <returns></returns>
2023-10-10 10:58:33 +00:00
private static bool CacheIsStale ( )
{
2024-08-28 12:52:54 +01:00
TimeSpan cacheAge = DateTime . Now - _aiBrainCacheDate ;
2023-10-10 10:58:33 +00:00
return cacheAge . Minutes > 15 ;
}
2024-08-28 12:52:54 +01:00
/// <summary>
/// poco structure of data sent by server
/// </summary>
2023-10-10 10:58:33 +00:00
public class AIBrains
{
public Dictionary < WildSpawnType , Dictionary < string , Dictionary < string , int > > > pmc { get ; set ; }
public Dictionary < string , Dictionary < string , int > > assault { get ; set ; }
2023-11-26 21:50:49 +00:00
public Dictionary < string , Dictionary < string , int > > playerScav { get ; set ; }
2023-10-10 10:58:33 +00:00
}
/// <summary>
/// Choose a value from a choice of values with weightings
/// </summary>
/// <param name="botTypes"></param>
/// <param name="weights"></param>
/// <returns></returns>
private string WeightedRandom ( string [ ] botTypes , int [ ] weights )
{
var cumulativeWeights = new int [ botTypes . Length ] ;
2024-08-28 12:52:54 +01:00
for ( var i = 0 ; i < weights . Length ; i + + )
2023-10-10 10:58:33 +00:00
{
cumulativeWeights [ i ] = weights [ i ] + ( i = = 0 ? 0 : cumulativeWeights [ i - 1 ] ) ;
}
var maxCumulativeWeight = cumulativeWeights [ cumulativeWeights . Length - 1 ] ;
var randomNumber = maxCumulativeWeight * random . NextDouble ( ) ;
for ( var itemIndex = 0 ; itemIndex < botTypes . Length ; itemIndex + + )
{
if ( cumulativeWeights [ itemIndex ] > = randomNumber )
{
return botTypes [ itemIndex ] ;
}
}
2024-08-28 12:52:54 +01:00
_logger . LogError ( "failed to get random bot brain weighting, returned assault" ) ;
2023-10-10 10:58:33 +00:00
return "assault" ;
}
}
}