Files
SPT-Server-Build/Core/Generators/BotGenerator.cs
T
2025-01-14 15:01:19 +00:00

509 lines
20 KiB
C#

using Core.Annotations;
using Core.Helpers;
using Core.Models.Common;
using Core.Models.Eft.Common;
using Core.Models.Eft.Common.Tables;
using Core.Models.Enums;
using Core.Models.Enums.RaidSettings;
using Core.Models.Spt.Bots;
using Core.Models.Spt.Config;
using Core.Servers;
using Core.Services;
using Core.Utils;
using Core.Utils.Cloners;
using BodyPart = Core.Models.Eft.Common.Tables.BodyPart;
using ILogger = Core.Models.Utils.ILogger;
namespace Core.Generators;
[Injectable]
public class BotGenerator
{
private readonly ILogger _logger;
private readonly HashUtil _hashUtil;
private readonly RandomUtil _randomUtil;
private readonly TimeUtil _timeUtil;
private readonly ProfileHelper _profileHelper;
private readonly DatabaseService _databaseService;
private readonly BotInventoryGenerator _botInventoryGenerator;
private readonly BotLevelGenerator _botLevelGenerator;
private readonly BotEquipmentFilterService _botEquipmentFilterService;
private readonly WeightedRandomHelper _weightedRandomHelper;
private readonly BotHelper _botHelper;
private readonly BotGeneratorHelper _botGeneratorHelper;
private readonly SeasonalEventService _seasonalEventService;
private readonly ItemFilterService _itemFilterService;
private readonly BotNameService _botNameService;
private readonly ConfigServer _configServer;
private readonly ICloner _cloner;
private BotConfig _botConfig;
private PmcConfig _pmcConfig;
public BotGenerator(
ILogger logger,
HashUtil hashUtil,
RandomUtil randomUtil,
TimeUtil timeUtil,
ProfileHelper profileHelper,
DatabaseService databaseService,
BotInventoryGenerator botInventoryGenerator,
BotLevelGenerator botLevelGenerator,
BotEquipmentFilterService botEquipmentFilterService,
WeightedRandomHelper weightedRandomHelper,
BotHelper botHelper,
BotGeneratorHelper botGeneratorHelper,
SeasonalEventService seasonalEventService,
ItemFilterService itemFilterService,
BotNameService botNameService,
ConfigServer configServer,
ICloner cloner
)
{
_logger = logger;
_hashUtil = hashUtil;
_randomUtil = randomUtil;
_timeUtil = timeUtil;
_profileHelper = profileHelper;
_databaseService = databaseService;
_botInventoryGenerator = botInventoryGenerator;
_botLevelGenerator = botLevelGenerator;
_botEquipmentFilterService = botEquipmentFilterService;
_weightedRandomHelper = weightedRandomHelper;
_botHelper = botHelper;
_botGeneratorHelper = botGeneratorHelper;
_seasonalEventService = seasonalEventService;
_itemFilterService = itemFilterService;
_botNameService = botNameService;
_configServer = configServer;
_cloner = cloner;
_botConfig = _configServer.GetConfig<BotConfig>(ConfigTypes.BOT);
_pmcConfig = _configServer.GetConfig<PmcConfig>(ConfigTypes.PMC);
}
/// <summary>
/// Generate a player scav bot object
/// </summary>
/// <param name="sessionId">Session id</param>
/// <param name="role">e.g. assault / pmcbot</param>
/// <param name="difficulty">easy/normal/hard/impossible</param>
/// <param name="botTemplate">base bot template to use (e.g. assault/pmcbot)</param>
/// <param name="profile">profile of player generating pscav</param>
/// <returns>BotBase</returns>
public PmcData GeneratePlayerScav(string sessionId, string role, string difficulty, BotType botTemplate, PmcData profile)
{
var bot = GetCloneOfBotBase();
bot.Info.Settings.BotDifficulty = difficulty;
bot.Info.Settings.Role = role;
bot.Info.Side = "Savage";
var botGenDetails = new BotGenerationDetails{
IsPmc = false,
Side = "Savage",
Role = role,
BotRelativeLevelDeltaMax = 0,
BotRelativeLevelDeltaMin = 0,
BotCountToGenerate = 1,
BotDifficulty = difficulty,
IsPlayerScav = true,
};
bot = GenerateBot(sessionId, bot, botTemplate, botGenDetails);
// Sets the name after scav name shown in parentheses
bot.Info.MainProfileNickname = profile.Info.Nickname;
return new PmcData
{
Id = bot.Id,
Aid = bot.Aid,
SessionId = bot.SessionId,
Savage = bot.Savage,
KarmaValue = bot.KarmaValue,
Info = bot.Info,
Customization = bot.Customization,
Health = bot.Health,
Inventory = bot.Inventory,
Skills = bot.Skills,
Stats = bot.Stats,
Encyclopedia = bot.Encyclopedia,
TaskConditionCounters = bot.TaskConditionCounters,
InsuredItems = bot.InsuredItems,
Hideout = bot.Hideout,
Quests = bot.Quests,
TradersInfo = bot.TradersInfo,
UnlockedInfo = bot.UnlockedInfo,
RagfairInfo = bot.RagfairInfo,
Achievements = bot.Achievements,
RepeatableQuests = bot.RepeatableQuests,
Bonuses = bot.Bonuses,
Notes = bot.Notes,
CarExtractCounts = bot.CarExtractCounts,
CoopExtractCounts = bot.CoopExtractCounts,
SurvivorClass = bot.SurvivorClass,
WishList = bot.WishList,
MoneyTransferLimitData = bot.MoneyTransferLimitData,
IsPmc = bot.IsPmc,
Prestige = new Prestige()
};
}
/// <summary>
/// Create 1 bot of the type/side/difficulty defined in botGenerationDetails
/// </summary>
/// <param name="sessionId">Session id</param>
/// <param name="botGenerationDetails">details on how to generate bots</param>
/// <returns>constructed bot</returns>
public BotBase PrepareAndGenerateBot(string sessionId, BotGenerationDetails botGenerationDetails)
{
throw new NotImplementedException();
}
/// <summary>
/// Get a clone of the default bot base object and adjust its role/side/difficulty values
/// </summary>
/// <param name="botRole">Role bot should have</param>
/// <param name="botSide">Side bot should have</param>
/// <param name="difficulty">Difficult bot should have</param>
/// <returns>Cloned bot base</returns>
public BotBase GetPreparedBotBase(string botRole, string botSide, string difficulty)
{
throw new NotImplementedException();
}
/// <summary>
/// Get a clone of the database\bots\base.json file
/// </summary>
/// <returns>BotBase object</returns>
public BotBase GetCloneOfBotBase()
{
return _cloner.Clone(_databaseService.GetBots().Base);
}
/// <summary>
/// Create a IBotBase object with equipment/loot/exp etc
/// </summary>
/// <param name="sessionId">Session id</param>
/// <param name="bot">Bots base file</param>
/// <param name="botJsonTemplate">Bot template from db/bots/x.json</param>
/// <param name="botGenerationDetails">details on how to generate the bot</param>
/// <returns>BotBase object</returns>
public BotBase GenerateBot(
string sessionId,
BotBase bot,
BotType botJsonTemplate,
BotGenerationDetails botGenerationDetails)
{
_logger.Error("NOT IMPLEMENTED BotGenerator.GenerateBot");
var botRoleLowercase = botGenerationDetails.Role.ToLower();
var botLevel = _botLevelGenerator.GenerateBotLevel(
botJsonTemplate.BotExperience.Level,
botGenerationDetails,
bot);
// Only filter bot equipment, never players
if (!botGenerationDetails.IsPlayerScav.GetValueOrDefault(false))
{
_botEquipmentFilterService.FilterBotEquipment(
sessionId,
botJsonTemplate,
botLevel.Level.Value,
botGenerationDetails);
}
bot.Info.Nickname = _botNameService.GenerateUniqueBotNickname(
botJsonTemplate,
botGenerationDetails,
botRoleLowercase,
_botConfig.BotRolesThatMustHaveUniqueName);
bot.Info.LowerNickname = bot.Info.Nickname.ToLower();
// Only run when generating a 'fake' playerscav, not actual player scav
if (!botGenerationDetails.IsPlayerScav.GetValueOrDefault(false) && ShouldSimulatePlayerScav(botRoleLowercase))
{
_botNameService.AddRandomPmcNameToBotMainProfileNicknameProperty(bot);
SetRandomisedGameVersionAndCategory(bot.Info);
}
if (!_seasonalEventService.ChristmasEventEnabled())
{
// Process all bots EXCEPT gifter, he needs christmas items
if (botGenerationDetails.Role != "gifter")
{
_seasonalEventService.RemoveChristmasItemsFromBotInventory(
botJsonTemplate.BotInventory,
botGenerationDetails.Role);
}
}
RemoveBlacklistedLootFromBotTemplate(botJsonTemplate.BotInventory);
// Remove hideout data if bot is not a PMC or pscav - match what live sends
if (!(botGenerationDetails.IsPmc.GetValueOrDefault(false) || botGenerationDetails.IsPlayerScav.GetValueOrDefault(false)))
{
bot.Hideout = null;
}
bot.Info.Experience = botLevel.Exp;
bot.Info.Level = botLevel.Level;
bot.Info.Settings.Experience = GetExperienceRewardForKillByDifficulty(
botJsonTemplate.BotExperience.Reward,
botGenerationDetails.BotDifficulty,
botGenerationDetails.Role);
bot.Info.Settings.StandingForKill = GetStandingChangeForKillByDifficulty(
botJsonTemplate.BotExperience.StandingForKill,
botGenerationDetails.BotDifficulty,
botGenerationDetails.Role);
bot.Info.Settings.AggressorBonus = GetAgressorBonusByDifficulty(
botJsonTemplate.BotExperience.StandingForKill,
botGenerationDetails.BotDifficulty,
botGenerationDetails.Role);
bot.Info.Settings.UseSimpleAnimator = botJsonTemplate.BotExperience.UseSimpleAnimator ?? false;
bot.Info.Voice = _weightedRandomHelper.GetWeightedValue<string, int>(botJsonTemplate.BotAppearance.Voice);
bot.Health = GenerateHealth(botJsonTemplate.BotHealth, botGenerationDetails.IsPlayerScav.GetValueOrDefault(false));
bot.Skills = GenerateSkills(botJsonTemplate.BotSkills); // TODO: fix bad type, bot jsons store skills in dict, output needs to be array
if (botGenerationDetails.IsPmc.GetValueOrDefault(false))
{
bot.Info.IsStreamerModeAvailable = true; // Set to true so client patches can pick it up later - client sometimes alters botrole to assaultGroup
SetRandomisedGameVersionAndCategory(bot.Info);
if (bot.Info.GameVersion == GameEditions.UNHEARD)
{
AddAdditionalPocketLootWeightsForUnheardBot(botJsonTemplate);
}
}
// Add drip
SetBotAppearance(bot, botJsonTemplate.BotAppearance, botGenerationDetails);
// Filter out blacklisted gear from the base template
FilterBlacklistedGear(botJsonTemplate, botGenerationDetails);
bot.Inventory = _botInventoryGenerator.generateInventory(
sessionId,
botJsonTemplate,
botRoleLowercase,
botGenerationDetails.IsPmc.GetValueOrDefault(false),
botLevel.Level.Value,
bot.Info.GameVersion);
if (_botConfig.BotRolesWithDogTags.Contains(botRoleLowercase))
{
AddDogtagToBot(bot);
}
// Generate new bot ID
AddIdsToBot(bot);
// Generate new inventory ID
GenerateInventoryId(bot);
// Set role back to originally requested now its been generated
if (botGenerationDetails.EventRole is not null)
{
bot.Info.Settings.Role = botGenerationDetails.EventRole;
}
return bot;
}
/// <summary>
/// Should this bot have a name like "name (Pmc Name)" and be alterd by client patch to be hostile to player
/// </summary>
/// <param name="botRole">Role bot has</param>
/// <returns>True if name should be simulated pscav</returns>
public bool ShouldSimulatePlayerScav(string botRole)
{
throw new NotImplementedException();
}
/// <summary>
/// Get exp for kill by bot difficulty
/// </summary>
/// <param name="experience">Dict of difficulties and experience</param>
/// <param name="botDifficulty">the killed bots difficulty</param>
/// <param name="role">Role of bot (optional, used for error logging)</param>
/// <returns>Experience for kill</returns>
public int GetExperienceRewardForKillByDifficulty(Dictionary<string, MinMax> experience, string botDifficulty, string role)
{
throw new NotImplementedException();
}
/// <summary>
/// Get the standing value change when player kills a bot
/// </summary>
/// <param name="standingForKill">Dictionary of standing values keyed by bot difficulty</param>
/// <param name="botDifficulty">Difficulty of bot to look up</param>
/// <param name="role">Role of bot (optional, used for error logging)</param>
/// <returns>Standing change value</returns>
public int GetStandingChangeForKillByDifficulty(Dictionary<string, double> standingForKill, string botDifficulty, string role)
{
throw new NotImplementedException();
}
/// <summary>
/// Get the agressor bonus value when player kills a bot
/// </summary>
/// <param name="aggressorBonus">Dictionary of standing values keyed by bot difficulty</param>
/// <param name="botDifficulty">Difficulty of bot to look up</param>
/// <param name="role">Role of bot (optional, used for error logging)</param>
/// <returns>Standing change value</returns>
public int GetAgressorBonusByDifficulty(Dictionary<string, double> aggressorBonus, string botDifficulty, string role)
{
throw new NotImplementedException();
}
/// <summary>
/// Set weighting of flagged equipment to 0
/// </summary>
/// <param name="botJsonTemplate">Bot data to adjust</param>
/// <param name="botGenerationDetails">Generation details of bot</param>
public void FilterBlacklistedGear(BotType botJsonTemplate, BotGenerationDetails botGenerationDetails)
{
throw new NotImplementedException();
}
/// <summary>
/// TODO: Complete Summary
/// </summary>
/// <param name="botJsonTemplate">Bot data to adjust</param>
public void AddAdditionalPocketLootWeightsForUnheardBot(BotType botJsonTemplate)
{
throw new NotImplementedException();
}
/// <summary>
/// Remove items from item.json/lootableItemBlacklist from bots inventory
/// </summary>
/// <param name="botInventory">Bot to filter</param>
public void RemoveBlacklistedLootFromBotTemplate(BotTypeInventory botInventory)
{
throw new NotImplementedException();
}
/// <summary>
/// Choose various appearance settings for a bot using weights: head/body/feet/hands
/// </summary>
/// <param name="bot">Bot to adjust</param>
/// <param name="appearance">Appearance settings to choose from</param>
/// <param name="botGenerationDetails">Generation details</param>
public void SetBotAppearance(BotBase bot, Appearance appearance, BotGenerationDetails botGenerationDetails)
{
throw new NotImplementedException();
}
/// <summary>
/// Log the number of PMCs generated to the debug console
/// </summary>
/// <param name="output">Generated bot array, ready to send to client</param>
public void LogPmcGeneratedCount(List<BotBase> output)
{
throw new NotImplementedException();
}
/// <summary>
/// Converts health object to the required format
/// </summary>
/// <param name="healthObj">health object from bot json</param>
/// <param name="playerScav">Is a pscav bot being generated</param>
/// <returns>Health object</returns>
public BotBaseHealth GenerateHealth(BotTypeHealth healthObj, bool playerScav = false)
{
throw new NotImplementedException();
}
/// <summary>
/// Sum up body parts max hp values, return the bodypart collection with lowest value
/// </summary>
/// <param name="bodies">Body parts to sum up</param>
/// <returns>Lowest hp collection</returns>
public BodyPart? GetLowestHpBody(List<BodyPart> bodies) // TODO: there are two types of body parts
{
throw new NotImplementedException();
}
/// <summary>
/// Get a bots skills with randomsied progress value between the min and max values
/// </summary>
/// <param name="botSkills">Skills that should have their progress value randomised</param>
/// <returns>Skills</returns>
public Skills GenerateSkills(BotDbSkills botSkills)
{
throw new NotImplementedException();
}
/// <summary>
/// Randomise the progress value of passed in skills based on the min/max value
/// </summary>
/// <param name="skills">Skills to randomise</param>
/// <param name="isCommonSkills">Are the skills 'common' skills</param>
/// <returns>Skills with randomised progress values as an array</returns>
public List<BaseSkill> GetSkillsWithRandomisedProgressValue(Dictionary<string, BaseSkill> skills, bool isCommonSkills)
{
throw new NotImplementedException();
}
/// <summary>
/// Generate an id+aid for a bot and apply
/// </summary>
/// <param name="bot">bot to update</param>
/// <returns>updated IBotBase object</returns> // TODO: Node server claims this in summary but is void
public void AddIdsToBot(BotBase bot)
{
throw new NotImplementedException();
}
/// <summary>
/// Update a profiles profile.Inventory.equipment value with a freshly generated one.
/// Update all inventory items that make use of this value too.
/// </summary>
/// <param name="profile">Profile to update</param>
public void GenerateInventoryId(BotBase profile)
{
throw new NotImplementedException();
}
/// <summary>
/// Randomise a bots game version and account category.
/// Chooses from all the game versions (standard, eod etc).
/// Chooses account type (default, Sherpa, etc).
/// </summary>
/// <param name="botInfo">bot info object to update</param>
/// <returns>Chosen game version</returns>
public string SetRandomisedGameVersionAndCategory(Info botInfo) // TODO: there are two types of Info
{
throw new NotImplementedException();
}
/// <summary>
/// Add a side-specific (usec/bear) dogtag item to a bots inventory
/// </summary>
/// <param name="bot">bot to add dogtag to</param>
/// <returns>Bot with dogtag added</returns> // TODO: Node server claims this in summary but is void
public void AddDogtagToBot(BotBase bot)
{
throw new NotImplementedException();
}
/// <summary>
/// Get a dogtag tpl that matches the bots game version and side
/// </summary>
/// <param name="side">Usec/Bear</param>
/// <param name="gameVersion">edge_of_darkness / standard</param>
/// <returns>item tpl</returns>
public string GetDogtagTplByGameVersionAndSide(string side, string gameVersion)
{
throw new NotImplementedException();
}
/// <summary>
/// Adjust a PMCs pocket tpl to UHD if necessary, otherwise do nothing
/// </summary>
/// <param name="bot">Pmc object to adjust</param>
public void SetPmcPocketsByGameVersion(BotBase bot)
{
throw new NotImplementedException();
}
}