using Aki.Common.Http; using Aki.Reflection.Patching; using Comfort.Common; using EFT; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Aki.PrePatch; using Random = System.Random; namespace Aki.Custom.Patches { public class CustomAiPatch : ModulePatch { private static readonly Random random = new Random(); private static Dictionary>> botTypeCache = new Dictionary>>(); private static DateTime cacheDate = new DateTime(); protected override MethodBase GetTargetMethod() { return typeof(BotBrainClass).GetMethod("Activate", BindingFlags.Public | BindingFlags.Instance); } /// /// Get a randomly picked wildspawntype from server and change PMC bot to use it, this ensures the bot is generated with that random type altering its behaviour /// /// state to save for postfix to use later /// /// botOwner_0 property [PatchPrefix] private static bool PatchPrefix(out WildSpawnType __state, object __instance, BotOwner ___botOwner_0) { // Store original type in state param __state = ___botOwner_0.Profile.Info.Settings.Role; //Console.WriteLine($"Processing bot {___botOwner_0.Profile.Info.Nickname} with role {___botOwner_0.Profile.Info.Settings.Role}"); try { if (BotIsSptPmc(___botOwner_0.Profile.Info.Settings.Role)) { string currentMapName = GetCurrentMap(); if (!botTypeCache.TryGetValue(___botOwner_0.Profile.Info.Settings.Role, out var botSettings) || CacheIsStale()) { ResetCacheDate(); HydrateCacheWithServerData(); if (!botTypeCache.TryGetValue(___botOwner_0.Profile.Info.Settings.Role, out botSettings)) { 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)) { Console.WriteLine($"Updated spt bot {___botOwner_0.Profile.Info.Nickname}: {___botOwner_0.Profile.Info.Settings.Role} to {newAiType}"); ___botOwner_0.Profile.Info.Settings.Role = newAiType; } else { Console.WriteLine($"Couldnt not update spt bot {___botOwner_0.Profile.Info.Nickname} to the new type, random type {randomType} does not exist for WildSpawnType"); } } } catch (Exception ex) { Console.WriteLine($"Error processing log: {ex.Message}"); Console.WriteLine(ex.StackTrace); } return true; // Do original } /// /// Revert prefix change, get bots type back to what it was before changes /// /// Saved state from prefix patch /// botOwner_0 property [PatchPostfix] private static void PatchPostFix(WildSpawnType __state, BotOwner ___botOwner_0) { if (BotIsSptPmc(__state)) { // Set spt bot bot back to original type ___botOwner_0.Profile.Info.Settings.Role = __state; } } private static bool BotIsSptPmc(WildSpawnType role) { return (long)role == -2147483648 || (long)role == 0; } private static string GetCurrentMap() { var gameWorld = Singleton.Instance; return gameWorld.RegisteredPlayers[0].Location; } private static bool CacheIsStale() { TimeSpan cacheAge = DateTime.Now - cacheDate; return cacheAge.Minutes > 20; } private static void ResetCacheDate() { cacheDate = DateTime.Now; } private static void HydrateCacheWithServerData() { // Get weightings for PMCs from server and store in dict var result = RequestHandler.GetJson($"/singleplayer/settings/bot/getBotBehaviours/"); botTypeCache = JsonConvert.DeserializeObject>>>(result); Console.WriteLine($"cached: {botTypeCache.Count} bots"); } private static string WeightedRandom(string[] botTypes, int[] weights) { var cumulativeWeights = new int[botTypes.Length]; for (int i = 0; i < weights.Length; i++) { 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]; } } Console.WriteLine("failed to get random bot weighting, returned assault"); return "assault"; } } }