Merge branch 'main' of https://github.com/sp-tarkov/server-csharp
This commit is contained in:
+546
-37
@@ -1,4 +1,5 @@
|
||||
using Core.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Common;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
@@ -6,7 +7,9 @@ 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;
|
||||
@@ -17,20 +20,65 @@ namespace Core.Generators;
|
||||
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>
|
||||
@@ -49,7 +97,8 @@ public class BotGenerator
|
||||
bot.Info.Settings.Role = role;
|
||||
bot.Info.Side = "Savage";
|
||||
|
||||
var botGenDetails = new BotGenerationDetails{
|
||||
var botGenDetails = new BotGenerationDetails
|
||||
{
|
||||
IsPmc = false,
|
||||
Side = "Savage",
|
||||
Role = role,
|
||||
@@ -108,7 +157,23 @@ public class BotGenerator
|
||||
/// <returns>constructed bot</returns>
|
||||
public BotBase PrepareAndGenerateBot(string sessionId, BotGenerationDetails botGenerationDetails)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var preparedBotBase = GetPreparedBotBase(
|
||||
botGenerationDetails.EventRole ?? botGenerationDetails.Role, // Use eventRole if provided,
|
||||
botGenerationDetails.Side,
|
||||
botGenerationDetails.BotDifficulty
|
||||
);
|
||||
|
||||
// Get raw json data for bot (Cloned)
|
||||
var botRole = botGenerationDetails.IsPmc ?? false
|
||||
? preparedBotBase.Info.Side // Use side to get usec.json or bear.json when bot will be PMC
|
||||
: botGenerationDetails.Role;
|
||||
var botJsonTemplateClone = _cloner.Clone(_botHelper.GetBotTemplate(botRole));
|
||||
if (botJsonTemplateClone is not null)
|
||||
{
|
||||
_logger.Error($"Unable to retrieve: {botRole} bot template, cannot generate bot of this type");
|
||||
}
|
||||
|
||||
return GenerateBot(sessionId, preparedBotBase, botJsonTemplateClone, botGenerationDetails);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -120,7 +185,12 @@ public class BotGenerator
|
||||
/// <returns>Cloned bot base</returns>
|
||||
public BotBase GetPreparedBotBase(string botRole, string botSide, string difficulty)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var botBaseClone = GetCloneOfBotBase();
|
||||
botBaseClone.Info.Settings.Role = botRole;
|
||||
botBaseClone.Info.Side = botSide;
|
||||
botBaseClone.Info.Settings.BotDifficulty = difficulty;
|
||||
|
||||
return botBaseClone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -140,35 +210,154 @@ public class BotGenerator
|
||||
/// <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)
|
||||
public BotBase GenerateBot(
|
||||
string sessionId,
|
||||
BotBase bot,
|
||||
BotType botJsonTemplate,
|
||||
BotGenerationDetails botGenerationDetails)
|
||||
{
|
||||
_logger.Error("NOT IMPLEMENTED BotGenerator.GenerateBot");
|
||||
|
||||
bot.Inventory.Items = [];
|
||||
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(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
|
||||
/// Should this bot have a name like "name (Pmc Name)" and be altered 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();
|
||||
return botRole == "assault" && _randomUtil.GetChance100(_botConfig.ChanceAssaultScavHasPlayerScavName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get exp for kill by bot difficulty
|
||||
/// </summary>
|
||||
/// <param name="experience">Dict of difficulties and experience</param>
|
||||
/// <param name="experiences">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)
|
||||
public double GetExperienceRewardForKillByDifficulty(Dictionary<string, MinMax> experiences, string botDifficulty, string role)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var result = experiences[botDifficulty.ToLower()];
|
||||
if (result is null)
|
||||
{
|
||||
_logger.Debug("Unable to find experience for kill value for: ${ role} ${ botDifficulty}, falling back to `normal`");
|
||||
|
||||
return _randomUtil.GetDouble(experiences["normal"].Min.Value, experiences["normal"].Max.Value);
|
||||
}
|
||||
|
||||
return _randomUtil.GetDouble(result.Min.Value, result.Max.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -178,9 +367,16 @@ public class BotGenerator
|
||||
/// <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, int> standingForKill, string botDifficulty, string role)
|
||||
public double GetStandingChangeForKillByDifficulty(Dictionary<string, double> standingsForKill, string botDifficulty, string role)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (!standingsForKill.TryGetValue(botDifficulty.ToLower(), out var result))
|
||||
{
|
||||
_logger.Warning($"Unable to find standing for kill value for: {role} {botDifficulty}, falling back to `normal`");
|
||||
|
||||
return standingsForKill["normal"];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -190,9 +386,16 @@ public class BotGenerator
|
||||
/// <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, int> aggressorBonus, string botDifficulty, string role)
|
||||
public double GetAgressorBonusByDifficulty(Dictionary<string, double> aggressorBonuses, string botDifficulty, string role)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (!aggressorBonuses.TryGetValue(botDifficulty.ToLower(), out var result))
|
||||
{
|
||||
_logger.Warning($"Unable to find aggressor bonus for kill value for: {role} {botDifficulty}, falling back to `normal`");
|
||||
|
||||
return aggressorBonuses["normal"];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -202,7 +405,26 @@ public class BotGenerator
|
||||
/// <param name="botGenerationDetails">Generation details of bot</param>
|
||||
public void FilterBlacklistedGear(BotType botJsonTemplate, BotGenerationDetails botGenerationDetails)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var blacklist = _botEquipmentFilterService.GetBotEquipmentBlacklist(
|
||||
_botGeneratorHelper.GetBotEquipmentRole(botGenerationDetails.Role),
|
||||
botGenerationDetails.PlayerLevel.GetValueOrDefault(1));
|
||||
|
||||
if (blacklist?.Gear is null)
|
||||
{
|
||||
// Nothing to filter by
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var equipmentKvP in blacklist.Gear)
|
||||
{
|
||||
var equipmentDict = botJsonTemplate.BotInventory.Equipment[equipmentKvP.Key];
|
||||
|
||||
foreach (var blacklistedTpl in equipmentKvP.Value)
|
||||
{
|
||||
// Set weighting to 0, will never be picked
|
||||
equipmentDict[blacklistedTpl] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -211,7 +433,10 @@ public class BotGenerator
|
||||
/// <param name="botJsonTemplate">Bot data to adjust</param>
|
||||
public void AddAdditionalPocketLootWeightsForUnheardBot(BotType botJsonTemplate)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Adjust pocket loot weights to allow for 5 or 6 items
|
||||
var pocketWeights = botJsonTemplate.BotGeneration.Items["pocketLoot"].Weights;
|
||||
pocketWeights["5"] = 1;
|
||||
pocketWeights["6"] = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -220,7 +445,37 @@ public class BotGenerator
|
||||
/// <param name="botInventory">Bot to filter</param>
|
||||
public void RemoveBlacklistedLootFromBotTemplate(BotTypeInventory botInventory)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
List<string> lootContainersToFilter = ["Backpack", "Pockets", "TacticalVest"];
|
||||
var props = botInventory.Items.GetType().GetProperties();
|
||||
|
||||
// Remove blacklisted loot from loot containers
|
||||
foreach (var lootContainerKey in lootContainersToFilter)
|
||||
{
|
||||
var prop = props.FirstOrDefault(x => x.Name.ToLower() == lootContainerKey.ToLower());
|
||||
var propValue = (Dictionary<string, double>)prop.GetValue(botInventory.Items);
|
||||
|
||||
// No container, skip
|
||||
if (propValue?.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<string> tplsToRemove = [];
|
||||
foreach (var item in propValue)
|
||||
{
|
||||
if (_itemFilterService.IsLootableItemBlacklisted(item.Key))
|
||||
{
|
||||
tplsToRemove.Add(item.Key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var blacklistedTplToRemove in tplsToRemove)
|
||||
{
|
||||
propValue.Remove(blacklistedTplToRemove);
|
||||
}
|
||||
|
||||
prop.SetValue(botInventory.Items, propValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -231,7 +486,19 @@ public class BotGenerator
|
||||
/// <param name="botGenerationDetails">Generation details</param>
|
||||
public void SetBotAppearance(BotBase bot, Appearance appearance, BotGenerationDetails botGenerationDetails)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Choose random values by weight
|
||||
bot.Customization.Head = _weightedRandomHelper.GetWeightedValue<string>(appearance.Head);
|
||||
bot.Customization.Feet = _weightedRandomHelper.GetWeightedValue<string>(appearance.Feet);
|
||||
bot.Customization.Body = _weightedRandomHelper.GetWeightedValue<string>(appearance.Body);
|
||||
|
||||
var bodyGlobalDictDb = _databaseService.GetGlobals().Configuration.Customization.Body;
|
||||
var chosenBodyTemplate = _databaseService.GetCustomization()[bot.Customization.Body];
|
||||
|
||||
// Some bodies have matching hands, look up body to see if this is the case
|
||||
var chosenBody = bodyGlobalDictDb[chosenBodyTemplate?.Name.Trim()];
|
||||
bot.Customization.Hands = chosenBody?.IsNotRandom ?? false
|
||||
? chosenBody.Hands // Has fixed hands for chosen body, update to match
|
||||
: _weightedRandomHelper.GetWeightedValue<string>(appearance.Hands); // Hands can be random, choose any from weighted dict
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -240,7 +507,8 @@ public class BotGenerator
|
||||
/// <param name="output">Generated bot array, ready to send to client</param>
|
||||
public void LogPmcGeneratedCount(List<BotBase> output)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var pmcCount = output.Aggregate(0, (acc, cur) => { return cur.Info.Side == "Bear" || cur.Info.Side == "Usec" ? acc + 1 : acc; });
|
||||
_logger.Debug($"Generated {output.Count} total bots. Replaced ${pmcCount} with PMCs");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -249,9 +517,107 @@ public class BotGenerator
|
||||
/// <param name="healthObj">health object from bot json</param>
|
||||
/// <param name="playerScav">Is a pscav bot being generated</param>
|
||||
/// <returns>Health object</returns>
|
||||
public Health GenerateHealth(Health healthObj, bool playerScav = false)
|
||||
public BotBaseHealth GenerateHealth(BotTypeHealth healthObj, bool playerScav = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var bodyParts = playerScav
|
||||
? GetLowestHpBody(healthObj.BodyParts)
|
||||
: _randomUtil.GetArrayValue(healthObj.BodyParts);
|
||||
|
||||
BotBaseHealth health = new()
|
||||
{
|
||||
Hydration = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)healthObj.Hydration.Min, (int)healthObj.Hydration.Max),
|
||||
Maximum = healthObj.Hydration.Max
|
||||
},
|
||||
Energy = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)healthObj.Energy.Min, (int)healthObj.Energy.Max),
|
||||
Maximum = healthObj.Energy.Max
|
||||
},
|
||||
Temperature = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)healthObj.Temperature.Min, (int)healthObj.Temperature.Max),
|
||||
Maximum = healthObj.Temperature.Max
|
||||
},
|
||||
BodyParts = new Dictionary<string, BodyPartHealth>()
|
||||
{
|
||||
{
|
||||
"Head", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.Head.Min, (int)bodyParts.Head.Max),
|
||||
Maximum = Math.Round(bodyParts.Head.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Chest", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.Chest.Min, (int)bodyParts.Chest.Max),
|
||||
Maximum = Math.Round(bodyParts.Chest.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Stomach", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.Stomach.Min, (int)bodyParts.Stomach.Max),
|
||||
Maximum = Math.Round(bodyParts.Stomach.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"LeftArm", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.LeftArm.Min, (int)bodyParts.LeftArm.Max),
|
||||
Maximum = Math.Round(bodyParts.LeftArm.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"RightArm", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.RightArm.Min, (int)bodyParts.RightArm.Max),
|
||||
Maximum = Math.Round(bodyParts.RightArm.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"LeftLeg", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.LeftLeg.Min, (int)bodyParts.LeftLeg.Max),
|
||||
Maximum = Math.Round(bodyParts.LeftLeg.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"RightLeg", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.RightLeg.Min, (int)bodyParts.RightLeg.Max),
|
||||
Maximum = Math.Round(bodyParts.RightLeg.Max ?? 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
UpdateTime = _timeUtil.GetTimeStamp(),
|
||||
Immortal = false
|
||||
};
|
||||
|
||||
return health;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -259,9 +625,33 @@ public class BotGenerator
|
||||
/// </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
|
||||
public BodyPart? GetLowestHpBody(List<BodyPart> bodies)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (bodies.Count == 0)
|
||||
return null;
|
||||
|
||||
BodyPart result = new();
|
||||
var props = result.GetType().GetProperties();
|
||||
double? currentHighest = double.MaxValue;
|
||||
foreach (var bodyPart in bodies)
|
||||
{
|
||||
double? hpTotal = 0;
|
||||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
var value = (MinMax)prop.GetValue(bodyPart);
|
||||
hpTotal += value.Max;
|
||||
}
|
||||
|
||||
if (hpTotal < currentHighest)
|
||||
{
|
||||
// Found collection with lower value that previous, use it
|
||||
currentHighest = hpTotal;
|
||||
result = bodyPart;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -269,9 +659,16 @@ public class BotGenerator
|
||||
/// </summary>
|
||||
/// <param name="botSkills">Skills that should have their progress value randomised</param>
|
||||
/// <returns>Skills</returns>
|
||||
public Skills GenerateSkills(BaseJsonSkills botSkills)
|
||||
public Skills GenerateSkills(BotDbSkills botSkills)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var skillsToReturn = new Skills
|
||||
{
|
||||
Common = GetSkillsWithRandomisedProgressValue(botSkills.Common, true),
|
||||
Mastering = GetSkillsWithRandomisedProgressValue(botSkills.Mastering, false),
|
||||
Points = 0
|
||||
};
|
||||
|
||||
return skillsToReturn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -280,19 +677,49 @@ public class BotGenerator
|
||||
/// <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)
|
||||
public List<BaseSkill> GetSkillsWithRandomisedProgressValue(Dictionary<string, MinMax>? skills, bool isCommonSkills)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (skills is null)
|
||||
return [];
|
||||
|
||||
return skills.Select(kvp =>
|
||||
{
|
||||
// Get skill from dict, skip if not found
|
||||
var skill = kvp.Value;
|
||||
if (skill == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// All skills have id and progress props
|
||||
var skillToAdd = new BaseSkill
|
||||
{
|
||||
Id = kvp.Key,
|
||||
Progress = _randomUtil.GetInt((int)skill.Min, (int)skill.Max)
|
||||
};
|
||||
|
||||
// Common skills have additional props
|
||||
if (isCommonSkills)
|
||||
{
|
||||
((Common)skillToAdd).PointsEarnedDuringSession = 0;
|
||||
((Common)skillToAdd).LastAccess = 0;
|
||||
}
|
||||
|
||||
return skillToAdd;
|
||||
}).Where(baseSkill => baseSkill != null).ToList();
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// <returns></returns>
|
||||
public void AddIdsToBot(BotBase bot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var botId = _hashUtil.Generate();
|
||||
|
||||
bot.Id = botId;
|
||||
bot.Aid = _hashUtil.GenerateAccountId();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -302,7 +729,30 @@ public class BotGenerator
|
||||
/// <param name="profile">Profile to update</param>
|
||||
public void GenerateInventoryId(BotBase profile)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var newInventoryItemId = _hashUtil.Generate();
|
||||
|
||||
foreach (var item in profile.Inventory.Items) {
|
||||
// Root item found, update its _id value to newly generated id
|
||||
if (item.Template == ItemTpl.INVENTORY_DEFAULT) {
|
||||
item.Id = newInventoryItemId;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Optimisation - skip items without a parentId
|
||||
// They are never linked to root inventory item + we already handled root item above
|
||||
if (item.ParentId is null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Item is a child of root inventory item, update its parentId value to newly generated id
|
||||
if (item.ParentId == profile.Inventory.Equipment) {
|
||||
item.ParentId = newInventoryItemId;
|
||||
}
|
||||
}
|
||||
|
||||
// Update inventory equipment id to new one we generated
|
||||
profile.Inventory.Equipment = newInventoryItemId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -312,19 +762,57 @@ public class BotGenerator
|
||||
/// </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
|
||||
public string SetRandomisedGameVersionAndCategory(Info botInfo)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Special case
|
||||
if (botInfo.Nickname?.ToLower() == "nikita") {
|
||||
botInfo.GameVersion = GameEditions.UNHEARD;
|
||||
botInfo.MemberCategory = MemberCategory.DEVELOPER;
|
||||
|
||||
return botInfo.GameVersion;
|
||||
}
|
||||
|
||||
// Choose random weighted game version for bot
|
||||
botInfo.GameVersion = _weightedRandomHelper.GetWeightedValue(_pmcConfig.GameVersionWeight);
|
||||
|
||||
// Choose appropriate member category value
|
||||
switch (botInfo.GameVersion) {
|
||||
case GameEditions.EDGE_OF_DARKNESS:
|
||||
botInfo.MemberCategory = MemberCategory.UNIQUE_ID;
|
||||
break;
|
||||
case GameEditions.UNHEARD:
|
||||
botInfo.MemberCategory = MemberCategory.UNHEARD;
|
||||
break;
|
||||
default:
|
||||
// Everyone else gets a weighted randomised category
|
||||
botInfo.MemberCategory = _weightedRandomHelper.GetWeightedValue<MemberCategory>(_pmcConfig.AccountTypeWeight);
|
||||
break;
|
||||
}
|
||||
|
||||
// Ensure selected category matches
|
||||
botInfo.SelectedMemberCategory = botInfo.MemberCategory;
|
||||
|
||||
return botInfo.GameVersion;
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// <returns></returns>
|
||||
public void AddDogtagToBot(BotBase bot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
Item inventoryItem = new () {
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = GetDogtagTplByGameVersionAndSide(bot.Info.Side, bot.Info.GameVersion),
|
||||
ParentId = bot.Inventory.Equipment,
|
||||
SlotId = "Dogtag",
|
||||
Upd = new () {
|
||||
SpawnedInSession = true,
|
||||
},
|
||||
};
|
||||
|
||||
bot.Inventory.Items.Add(inventoryItem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -335,7 +823,25 @@ public class BotGenerator
|
||||
/// <returns>item tpl</returns>
|
||||
public string GetDogtagTplByGameVersionAndSide(string side, string gameVersion)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (side.ToLower() == "usec") {
|
||||
switch (gameVersion) {
|
||||
case GameEditions.EDGE_OF_DARKNESS:
|
||||
return ItemTpl.BARTER_DOGTAG_USEC_EOD;
|
||||
case GameEditions.UNHEARD:
|
||||
return ItemTpl.BARTER_DOGTAG_USEC_TUE;
|
||||
default:
|
||||
return ItemTpl.BARTER_DOGTAG_USEC;
|
||||
}
|
||||
}
|
||||
|
||||
switch (gameVersion) {
|
||||
case GameEditions.EDGE_OF_DARKNESS:
|
||||
return ItemTpl.BARTER_DOGTAG_BEAR_EOD;
|
||||
case GameEditions.UNHEARD:
|
||||
return ItemTpl.BARTER_DOGTAG_BEAR_TUE;
|
||||
default:
|
||||
return ItemTpl.BARTER_DOGTAG_BEAR;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -344,6 +850,9 @@ public class BotGenerator
|
||||
/// <param name="bot">Pmc object to adjust</param>
|
||||
public void SetPmcPocketsByGameVersion(BotBase bot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (bot.Info.GameVersion == GameEditions.UNHEARD) {
|
||||
var pockets = bot.Inventory.Items.FirstOrDefault((item) => item.SlotId == "Pockets");
|
||||
pockets.Template = ItemTpl.POCKETS_1X4_TUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,55 @@
|
||||
using Core.Annotations;
|
||||
using Core.Annotations;
|
||||
using Core.Context;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Match;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Bots;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using Core.Utils;
|
||||
using Equipment = Core.Models.Eft.Common.Tables.Equipment;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Generators;
|
||||
|
||||
[Injectable]
|
||||
public class BotInventoryGenerator
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly HashUtil _hashUtil;
|
||||
private readonly BotLootGenerator _botLootGenerator;
|
||||
private readonly BotHelper _botHelper;
|
||||
private readonly BotGeneratorHelper _botGeneratorHelper;
|
||||
private readonly WeatherHelper _weatherHelper;
|
||||
private readonly ProfileHelper _profileHelper;
|
||||
private readonly ConfigServer _configServer;
|
||||
private readonly ApplicationContext _applicationContext;
|
||||
private BotConfig _botConfig;
|
||||
|
||||
public BotInventoryGenerator()
|
||||
public BotInventoryGenerator(
|
||||
ILogger logger,
|
||||
HashUtil hashUtil,
|
||||
BotLootGenerator botLootGenerator,
|
||||
BotHelper botHelper,
|
||||
BotGeneratorHelper botGeneratorHelper,
|
||||
WeatherHelper weatherHelper,
|
||||
ProfileHelper profileHelper,
|
||||
ConfigServer configServer,
|
||||
ApplicationContext applicationContext
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_hashUtil = hashUtil;
|
||||
_botLootGenerator = botLootGenerator;
|
||||
_botHelper = botHelper;
|
||||
_botGeneratorHelper = botGeneratorHelper;
|
||||
_weatherHelper = weatherHelper;
|
||||
_profileHelper = profileHelper;
|
||||
_configServer = configServer;
|
||||
_applicationContext = applicationContext;
|
||||
|
||||
_botConfig = _configServer.GetConfig<BotConfig>(ConfigTypes.BOT);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -26,9 +62,45 @@ public class BotInventoryGenerator
|
||||
/// <param name="botLevel">Level of bot being generated</param>
|
||||
/// <param name="chosenGameVersion">Game version for bot, only really applies for PMCs</param>
|
||||
/// <returns>PmcInventory object with equipment/weapons/loot</returns>
|
||||
public BotBaseInventory generateInventory(string sessionId, BotType botJsonTemplate, string botRole, bool isPmc, int botLevel, string chosenGameVersion)
|
||||
public BotBaseInventory GenerateInventory(string sessionId, BotType botJsonTemplate, string botRole, bool isPmc, int botLevel, string chosenGameVersion)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var templateInventory = botJsonTemplate.BotInventory;
|
||||
var wornItemChances = botJsonTemplate.BotChances;
|
||||
var itemGenerationLimitsMinMax = botJsonTemplate.BotGeneration;
|
||||
|
||||
// Generate base inventory with no items
|
||||
var botInventory = GenerateInventoryBase();
|
||||
|
||||
// Get generated raid details bot will be spawned in
|
||||
var raidConfig = _applicationContext
|
||||
.GetLatestValue(ContextVariableType.RAID_CONFIGURATION)
|
||||
?.GetValue<GetRaidConfigurationRequestData>();
|
||||
|
||||
GenerateAndAddEquipmentToBot(
|
||||
sessionId,
|
||||
templateInventory,
|
||||
wornItemChances,
|
||||
botRole,
|
||||
botInventory,
|
||||
botLevel,
|
||||
chosenGameVersion,
|
||||
raidConfig);
|
||||
|
||||
// Roll weapon spawns (primary/secondary/holster) and generate a weapon for each roll that passed
|
||||
GenerateAndAddWeaponsToBot(
|
||||
templateInventory,
|
||||
wornItemChances,
|
||||
sessionId,
|
||||
botInventory,
|
||||
botRole,
|
||||
isPmc,
|
||||
itemGenerationLimitsMinMax,
|
||||
botLevel);
|
||||
|
||||
// Pick loot and add to bots containers (rig/backpack/pockets/secure)
|
||||
_botLootGenerator.GenerateLoot(sessionId, botJsonTemplate, isPmc, botRole, botInventory, botLevel);
|
||||
|
||||
return botInventory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -37,7 +109,31 @@ public class BotInventoryGenerator
|
||||
/// <returns>PmcInventory object</returns>
|
||||
public BotBaseInventory GenerateInventoryBase()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var equipmentId = _hashUtil.Generate();
|
||||
var stashId = _hashUtil.Generate();
|
||||
var questRaidItemsId = _hashUtil.Generate();
|
||||
var questStashItemsId = _hashUtil.Generate();
|
||||
var sortingTableId = _hashUtil.Generate();
|
||||
|
||||
return new BotBaseInventory{
|
||||
Items =
|
||||
[
|
||||
new() { Id = equipmentId, Template = ItemTpl.INVENTORY_DEFAULT },
|
||||
new() { Id = stashId, Template = ItemTpl.STASH_STANDARD_STASH_10X30 },
|
||||
new() { Id = questRaidItemsId, Template = ItemTpl.STASH_QUESTRAID },
|
||||
new() { Id = questStashItemsId, Template = ItemTpl.STASH_QUESTOFFLINE },
|
||||
new() { Id = sortingTableId, Template = ItemTpl.SORTINGTABLE_SORTING_TABLE }
|
||||
],
|
||||
Equipment = equipmentId,
|
||||
Stash = stashId,
|
||||
QuestRaidItems = questRaidItemsId,
|
||||
QuestStashItems = questStashItemsId,
|
||||
SortingTable = sortingTableId,
|
||||
HideoutAreaStashes = { },
|
||||
FastPanel = { },
|
||||
FavoriteItems = [],
|
||||
HideoutCustomizationStashId = "",
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -51,10 +147,168 @@ public class BotInventoryGenerator
|
||||
/// <param name="botLevel">Level of bot</param>
|
||||
/// <param name="chosenGameVersion">Game version for bot, only really applies for PMCs</param>
|
||||
/// <param name="raidConfig">RadiConfig</param>
|
||||
public void GenerateAndAddEquipmentToBot(string sessionId, BotBaseInventory templateInventory, Chances wornItemChances, string botRole,
|
||||
public void GenerateAndAddEquipmentToBot(string sessionId, BotTypeInventory templateInventory, Chances wornItemChances, string botRole,
|
||||
BotBaseInventory botInventory, int botLevel, string chosenGameVersion, GetRaidConfigurationRequestData raidConfig)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// These will be handled later
|
||||
var excludedSlots = new List<EquipmentSlots>(){
|
||||
EquipmentSlots.Pockets,
|
||||
EquipmentSlots.FirstPrimaryWeapon,
|
||||
EquipmentSlots.SecondPrimaryWeapon,
|
||||
EquipmentSlots.Holster,
|
||||
EquipmentSlots.ArmorVest,
|
||||
EquipmentSlots.TacticalVest,
|
||||
EquipmentSlots.FaceCover,
|
||||
EquipmentSlots.Headwear,
|
||||
EquipmentSlots.Earpiece
|
||||
};
|
||||
|
||||
_botConfig.Equipment.TryGetValue(_botGeneratorHelper.GetBotEquipmentRole(botRole), out var botEquipConfig);
|
||||
var randomistionDetails = _botHelper.GetBotRandomizationDetails(botLevel, botEquipConfig);
|
||||
|
||||
// Apply nighttime changes if its nighttime + there's changes to make
|
||||
if (
|
||||
randomistionDetails?.NighttimeChanges is not null &&
|
||||
raidConfig is not null &&
|
||||
_weatherHelper.IsNightTime(raidConfig.TimeVariant)
|
||||
)
|
||||
{
|
||||
foreach (var equipmentSlotKvP in (randomistionDetails.NighttimeChanges.EquipmentModsModifiers)) {
|
||||
// Never let mod chance go outside of 0 - 100
|
||||
randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] = Math.Min(
|
||||
Math.Max( randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] + equipmentSlotKvP.Value, 0), 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Get profile of player generating bots, we use their level later on
|
||||
var pmcProfile = _profileHelper.GetPmcProfile(sessionId);
|
||||
var botEquipmentRole = _botGeneratorHelper.GetBotEquipmentRole(botRole);
|
||||
|
||||
|
||||
// Iterate over all equipment slots of bot, do it in specifc order to reduce conflicts
|
||||
// e.g. ArmorVest should be generated after TactivalVest
|
||||
// or FACE_COVER before HEADWEAR
|
||||
foreach (var equipmentSlotKvP in templateInventory.Equipment) {
|
||||
// Skip some slots as they need to be done in a specific order + with specific parameter values
|
||||
// e.g. Weapons
|
||||
if (excludedSlots.Contains(equipmentSlotKvP.Key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GenerateEquipment( new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = equipmentSlotKvP.Key,
|
||||
RootEquipmentPool = equipmentSlotKvP.Value,
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
}
|
||||
|
||||
// Generate below in specific order
|
||||
GenerateEquipment( new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.Pockets,
|
||||
// Unheard profiles have unique sized pockets, TODO - handle this somewhere else in a better way
|
||||
RootEquipmentPool =
|
||||
chosenGameVersion == GameEditions.UNHEARD
|
||||
? new Dictionary<string, double>{ [ItemTpl.POCKETS_1X4_TUE] = 1 }
|
||||
: templateInventory.Equipment[EquipmentSlots.Pockets],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData{ Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GenerateModsBlacklist = [ItemTpl.POCKETS_1X4_TUE],
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
|
||||
GenerateEquipment( new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.FaceCover,
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.FaceCover],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
|
||||
GenerateEquipment( new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.Headwear,
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.Headwear],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
|
||||
GenerateEquipment(new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.Earpiece,
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.Earpiece],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
|
||||
var hasArmorVest = GenerateEquipment( new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.ArmorVest,
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.ArmorVest],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
|
||||
// Bot has no armor vest and flagged to be forced to wear armored rig in this event
|
||||
if (botEquipConfig.ForceOnlyArmoredRigWhenNoArmor.GetValueOrDefault(false) && !hasArmorVest) {
|
||||
// Filter rigs down to only those with armor
|
||||
FilterRigsToThoseWithProtection(templateInventory.Equipment, botRole);
|
||||
}
|
||||
|
||||
// Optimisation - Remove armored rigs from pool
|
||||
if (hasArmorVest) {
|
||||
// Filter rigs down to only those with armor
|
||||
FilterRigsToThoseWithoutProtection(templateInventory.Equipment, botRole);
|
||||
}
|
||||
|
||||
// Bot is flagged as always needing a vest
|
||||
if (botEquipConfig.ForceRigWhenNoVest.GetValueOrDefault(false) && !hasArmorVest) {
|
||||
wornItemChances.EquipmentChances["TacticalVest"] = 100;
|
||||
}
|
||||
|
||||
GenerateEquipment( new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.Earpiece,
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.Earpiece],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -62,7 +316,12 @@ public class BotInventoryGenerator
|
||||
/// </summary>
|
||||
/// <param name="templateEquipment">Equpiment to filter TacticalVest of</param>
|
||||
/// <param name="botRole">Role of bot vests are being filtered for</param>
|
||||
public void FilterRigsToThoseWithProtection(Equipment templateEquipment, string botRole)
|
||||
public void FilterRigsToThoseWithProtection(Dictionary<EquipmentSlots, Dictionary<string, double>> templateEquipment, string botRole)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void FilterRigsToThoseWithoutProtection(Dictionary<EquipmentSlots, Dictionary<string, double>> templateEquipment, string botRole, bool allowEmptyResult = true)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -85,7 +344,8 @@ public class BotInventoryGenerator
|
||||
/// <returns>true when item added</returns>
|
||||
public bool GenerateEquipment(GenerateEquipmentProperties settings)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_logger.Error("NOT IMPLEMENTED - GenerateEquipment");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -110,7 +370,7 @@ public class BotInventoryGenerator
|
||||
/// <param name="isPmc">Is the bot being generated as a pmc</param>
|
||||
/// <param name="itemGenerationLimitsMinMax">Limits for items the bot can have</param>
|
||||
/// <param name="botLevel">level of bot having weapon generated</param>
|
||||
public void GenerateAndAddWeaponsToBot(BotBaseInventory templateInventory, Chances equipmentChances, string sessionId, BotBaseInventory botInventory,
|
||||
public void GenerateAndAddWeaponsToBot(BotTypeInventory templateInventory, Chances equipmentChances, string sessionId, BotBaseInventory botInventory,
|
||||
string botRole,
|
||||
bool isPmc, Generation itemGenerationLimitsMinMax, int botLevel)
|
||||
{
|
||||
|
||||
@@ -1,16 +1,32 @@
|
||||
using Core.Annotations;
|
||||
using Core.Annotations;
|
||||
using Core.Models.Common;
|
||||
using Core.Models.Eft.Bot;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Spt.Bots;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Generators;
|
||||
|
||||
[Injectable]
|
||||
public class BotLevelGenerator
|
||||
{
|
||||
public BotLevelGenerator()
|
||||
private readonly ILogger _logger;
|
||||
private readonly RandomUtil _randomUtil;
|
||||
private readonly MathUtil _mathUtil;
|
||||
private readonly DatabaseService _databaseService;
|
||||
|
||||
public BotLevelGenerator(
|
||||
ILogger logger,
|
||||
RandomUtil randomUtil,
|
||||
MathUtil mathUtil,
|
||||
DatabaseService databaseService)
|
||||
{
|
||||
_logger = logger;
|
||||
_randomUtil = randomUtil;
|
||||
_mathUtil = mathUtil;
|
||||
_databaseService = databaseService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -22,12 +38,30 @@ public class BotLevelGenerator
|
||||
/// <returns>IRandomisedBotLevelResult object</returns>
|
||||
public RandomisedBotLevelResult GenerateBotLevel(MinMax levelDetails, BotGenerationDetails botGenerationDetails, BotBase bot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var expTable = _databaseService.GetGlobals().Configuration.Exp.Level.ExperienceTable;
|
||||
var botLevelRange = GetRelativeBotLevelRange(botGenerationDetails, levelDetails, expTable.Length);
|
||||
|
||||
// Get random level based on the exp table.
|
||||
int exp = 0;
|
||||
var level = int.Parse(ChooseBotLevel(botLevelRange.Min.Value, botLevelRange.Max.Value, 1, 1.15)
|
||||
.ToString()); // TODO - nasty double to string to int conversion
|
||||
for (var i = 0; i < level; i++)
|
||||
{
|
||||
exp += expTable[i].Experience.Value;
|
||||
}
|
||||
|
||||
// Sprinkle in some random exp within the level, unless we are at max level.
|
||||
if (level < expTable.Length - 1)
|
||||
{
|
||||
exp += _randomUtil.GetInt(0, expTable[level].Experience.Value - 1);
|
||||
}
|
||||
|
||||
return new RandomisedBotLevelResult { Level = level, Exp = exp };
|
||||
}
|
||||
|
||||
public int ChooseBotLevel(int min, int max, int shift, int number)
|
||||
public double ChooseBotLevel(double min, double max, int shift, double number)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _randomUtil.GetBiasedRandomNumber(min, max, shift, number);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,6 +73,35 @@ public class BotLevelGenerator
|
||||
/// <returns>A MinMax of the lowest and highest level to generate the bots</returns>
|
||||
public MinMax GetRelativeBotLevelRange(BotGenerationDetails botGenerationDetails, MinMax levelDetails, int maxAvailableLevel)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var isPmc = botGenerationDetails.IsPmc.GetValueOrDefault(false);
|
||||
var pmcOverride = botGenerationDetails.LocationSpecificPmcLevelOverride;
|
||||
|
||||
var minPossibleLevel = isPmc && pmcOverride is not null
|
||||
? Math.Min(
|
||||
Math.Max(levelDetails.Min.Value, pmcOverride.Min.Value), // Biggest between json min and the botgen min
|
||||
maxAvailableLevel // Fallback if value above is crazy (default is 79)
|
||||
)
|
||||
: Math.Min(levelDetails.Min.Value, maxAvailableLevel); // Not pmc with override or non-pmc
|
||||
|
||||
var maxPossibleLevel = isPmc && pmcOverride is not null
|
||||
? Math.Min(pmcOverride.Max.Value, maxAvailableLevel) // Was a PMC and they have a level override
|
||||
: Math.Min(levelDetails.Max.Value, maxAvailableLevel); // Not pmc with override or non-pmc
|
||||
|
||||
var minLevel = botGenerationDetails.PlayerLevel.HasValue
|
||||
? botGenerationDetails.PlayerLevel.Value
|
||||
: 0 - botGenerationDetails.BotRelativeLevelDeltaMin.Value;
|
||||
var maxLevel = botGenerationDetails.PlayerLevel.HasValue
|
||||
? botGenerationDetails.PlayerLevel.Value
|
||||
: 0 + botGenerationDetails.BotRelativeLevelDeltaMin.Value;
|
||||
|
||||
// Bound the level to the min/max possible
|
||||
maxLevel = Math.Min(Math.Max(maxLevel, minPossibleLevel), maxPossibleLevel);
|
||||
minLevel = Math.Min(Math.Max(minLevel, minPossibleLevel), maxPossibleLevel);
|
||||
|
||||
return new MinMax
|
||||
{
|
||||
Min = minLevel,
|
||||
Max = maxLevel,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,19 +16,25 @@ public class WeatherGenerator
|
||||
private readonly SeasonalEventService _seasonalEventService;
|
||||
private readonly WeatherHelper _weatherHelper;
|
||||
private readonly ConfigServer _configServer;
|
||||
|
||||
private readonly WeightedRandomHelper _weightedRandomHelper;
|
||||
private readonly RandomUtil _randomUtil;
|
||||
private readonly WeatherConfig _weatherConfig;
|
||||
|
||||
public WeatherGenerator(
|
||||
TimeUtil timeUtil,
|
||||
SeasonalEventService seasonalEventService,
|
||||
WeatherHelper weatherHelper,
|
||||
ConfigServer configServer)
|
||||
ConfigServer configServer,
|
||||
WeightedRandomHelper weightedRandomHelper,
|
||||
RandomUtil randomUtil
|
||||
)
|
||||
{
|
||||
_timeUtil = timeUtil;
|
||||
_seasonalEventService = seasonalEventService;
|
||||
_weatherHelper = weatherHelper;
|
||||
_configServer = configServer;
|
||||
_weightedRandomHelper = weightedRandomHelper;
|
||||
_randomUtil = randomUtil;
|
||||
|
||||
_weatherConfig = _configServer.GetConfig<WeatherConfig>();
|
||||
}
|
||||
@@ -81,7 +87,36 @@ public class WeatherGenerator
|
||||
*/
|
||||
public Weather GenerateWeather(Season currentSeason, long? timestamp = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var weatherValues = GetWeatherValuesBySeason(currentSeason);
|
||||
var clouds = GetWeightedClouds(weatherValues);
|
||||
|
||||
// Force rain to off if no clouds
|
||||
var rain = clouds <= 0.6 ? 0 : GetWeightedRain(weatherValues);
|
||||
|
||||
// TODO: Ensure Weather settings match Ts Server GetRandomDouble produces a decimal value way higher than ts server
|
||||
var result = new Weather
|
||||
{
|
||||
Pressure = GetRandomDouble(weatherValues.Pressure.Min ?? 0, weatherValues.Pressure.Max ?? 0),
|
||||
Temperature = 0,
|
||||
Fog = GetWeightedFog(weatherValues),
|
||||
RainIntensity =
|
||||
rain > 1 ? GetRandomDouble(weatherValues.RainIntensity.Min ?? 0, weatherValues.RainIntensity.Max ?? 0) : 0,
|
||||
Rain = rain,
|
||||
WindGustiness = GetRandomDouble(weatherValues.WindGustiness.Min ?? 0, weatherValues.WindGustiness.Max ?? 0, 2),
|
||||
WindDirection = GetWeightedWindDirection(weatherValues),
|
||||
WindSpeed = GetWeightedWindSpeed(weatherValues),
|
||||
Cloud = clouds,
|
||||
Time = "",
|
||||
Date = "",
|
||||
Timestamp = 0,
|
||||
SptInRaidTimestamp = 0
|
||||
};
|
||||
|
||||
SetCurrentDateTime(result, timestamp);
|
||||
|
||||
result.Temperature = GetRaidTemperature(weatherValues, result.SptInRaidTimestamp ?? 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected SeasonalValues GetWeatherValuesBySeason(Season currentSeason)
|
||||
@@ -92,7 +127,7 @@ public class WeatherGenerator
|
||||
return this._weatherConfig.Weather.SeasonValues["default"];
|
||||
}
|
||||
|
||||
return value;
|
||||
return value!;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,9 +136,15 @@ public class WeatherGenerator
|
||||
* @param inRaidTimestamp What time is the raid running at
|
||||
* @returns Timestamp
|
||||
*/
|
||||
protected double GetRaidTemperature(SeasonalValues weather, int inRaidTimestamp)
|
||||
protected double GetRaidTemperature(SeasonalValues weather, long inRaidTimestamp)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Convert timestamp to date so we can get current hour and check if its day or night
|
||||
var currentRaidTime = new DateTime(inRaidTimestamp);
|
||||
var minMax = _weatherHelper.IsHourAtNightTime(currentRaidTime.Hour)
|
||||
? weather.Temp.Night
|
||||
: weather.Temp.Day;
|
||||
|
||||
return Math.Round(_randomUtil.GetDouble(minMax.Min ?? 0, minMax.Max ?? 0), 2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,38 +152,46 @@ public class WeatherGenerator
|
||||
* @param weather Object to update
|
||||
* @param timestamp OPTIONAL, define timestamp used
|
||||
*/
|
||||
protected void SetCurrentDateTime(Weather weather, int? timestamp = null)
|
||||
protected void SetCurrentDateTime(Weather weather, long? timestamp = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var inRaidTime = _weatherHelper.GetInRaidTime(timestamp);
|
||||
var normalTime = GetBsgFormattedTime(inRaidTime);
|
||||
var formattedDate = _timeUtil.FormatDate(timestamp.HasValue ? _timeUtil.GetDateTimeFromTimeStamp(timestamp.Value) : DateTime.UtcNow);
|
||||
var datetimeBsgFormat = $"{formattedDate} {normalTime}";
|
||||
|
||||
weather.Timestamp = timestamp ?? _timeUtil.GetTimeStampFromEpoch(inRaidTime); // matches weather.date We use to divide by 1000
|
||||
weather.Date = formattedDate; // matches weather.timestamp
|
||||
weather.Time = datetimeBsgFormat; // matches weather.timestamp
|
||||
weather.SptInRaidTimestamp = _timeUtil.GetTimeStampFromEpoch(inRaidTime);
|
||||
}
|
||||
|
||||
protected WindDirection GetWeightedWindDirection(SeasonalValues weather)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _weightedRandomHelper.WeightedRandom(weather.WindDirection.Values, weather.WindDirection.Weights).Item;
|
||||
}
|
||||
|
||||
protected double GetWeightedClouds(SeasonalValues weather)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _weightedRandomHelper.WeightedRandom(weather.Clouds.Values, weather.Clouds.Weights).Item;
|
||||
}
|
||||
|
||||
protected double GetWeightedWindSpeed(SeasonalValues weather)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _weightedRandomHelper.WeightedRandom(weather.WindSpeed.Values, weather.WindSpeed.Weights).Item;
|
||||
}
|
||||
|
||||
protected double GetWeightedFog(SeasonalValues weather)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _weightedRandomHelper.WeightedRandom(weather.Fog.Values, weather.Fog.Weights).Item;
|
||||
}
|
||||
|
||||
protected double GetWeightedRain(SeasonalValues weather)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _weightedRandomHelper.WeightedRandom(weather.Rain.Values, weather.Rain.Weights).Item;
|
||||
}
|
||||
|
||||
protected double GetRandomFloat(double min, double max, int precision = 3)
|
||||
protected double GetRandomDouble(double min, double max, int precision = 3)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return Math.Round(_randomUtil.GetDouble(min, max), precision);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user