378 lines
14 KiB
C#
378 lines
14 KiB
C#
using Core.Annotations;
|
|
using Core.Helpers;
|
|
using Core.Models.Eft.Common;
|
|
using Core.Models.Eft.Common.Tables;
|
|
using Core.Models.Enums;
|
|
using Core.Models.Spt.Config;
|
|
using Core.Servers;
|
|
using Core.Services;
|
|
using Core.Utils;
|
|
using Core.Utils.Cloners;
|
|
using ILogger = Core.Models.Utils.ILogger;
|
|
|
|
namespace Core.Generators;
|
|
|
|
[Injectable]
|
|
public class PlayerScavGenerator
|
|
{
|
|
private readonly ILogger _logger;
|
|
private readonly RandomUtil _randomUtil;
|
|
private readonly DatabaseService _databaseService;
|
|
private readonly HashUtil _hashUtil;
|
|
private readonly ItemHelper _itemHelper;
|
|
private readonly BotGeneratorHelper _botGeneratorHelper;
|
|
private readonly SaveServer _saveServer;
|
|
private readonly ProfileHelper _profileHelper;
|
|
private readonly BotHelper _botHelper;
|
|
private readonly FenceService _fenceService;
|
|
private readonly BotLootCacheService _botLootCacheService;
|
|
private readonly LocalisationService _localisationService;
|
|
private readonly BotGenerator _botGenerator;
|
|
private readonly ConfigServer _configServer;
|
|
private readonly ICloner _cloner;
|
|
private readonly TimeUtil _timeUtil;
|
|
|
|
private PlayerScavConfig _playerScavConfig;
|
|
|
|
public PlayerScavGenerator
|
|
(
|
|
ILogger logger,
|
|
RandomUtil randomUtil,
|
|
DatabaseService databaseService,
|
|
HashUtil hashUtil,
|
|
ItemHelper itemHelper,
|
|
BotGeneratorHelper botGeneratorHelper,
|
|
SaveServer saveServer,
|
|
ProfileHelper profileHelper,
|
|
BotHelper botHelper,
|
|
FenceService fenceService,
|
|
BotLootCacheService botLootCacheService,
|
|
LocalisationService localisationService,
|
|
BotGenerator botGenerator,
|
|
ConfigServer configServer,
|
|
ICloner cloner,
|
|
TimeUtil timeUtil
|
|
)
|
|
{
|
|
_logger = logger;
|
|
_randomUtil = randomUtil;
|
|
_databaseService = databaseService;
|
|
_hashUtil = hashUtil;
|
|
_itemHelper = itemHelper;
|
|
_botGeneratorHelper = botGeneratorHelper;
|
|
_saveServer = saveServer;
|
|
_profileHelper = profileHelper;
|
|
_botHelper = botHelper;
|
|
_fenceService = fenceService;
|
|
_botLootCacheService = botLootCacheService;
|
|
_localisationService = localisationService;
|
|
_botGenerator = botGenerator;
|
|
_configServer = configServer;
|
|
_cloner = cloner;
|
|
_timeUtil = timeUtil;
|
|
|
|
_playerScavConfig = configServer.GetConfig<PlayerScavConfig>(ConfigTypes.PLAYERSCAV);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update a player profile to include a new player scav profile
|
|
/// </summary>
|
|
/// <param name="sessionID">session id to specify what profile is updated</param>
|
|
/// <returns>profile object</returns>
|
|
public PmcData Generate(string sessionID)
|
|
{
|
|
// get karma level from profile
|
|
var profile = _saveServer.GetProfile(sessionID);
|
|
var profileCharactersClone = _cloner.Clone(profile.CharacterData);
|
|
var pmcDataClone = _cloner.Clone(profileCharactersClone.PmcData);
|
|
var existingScavDataClone = _cloner.Clone(profileCharactersClone.ScavData);
|
|
|
|
var scavKarmaLevel = GetScavKarmaLevel(pmcDataClone);
|
|
|
|
// use karma level to get correct karmaSettings
|
|
var playerScavKarmaSettings = _playerScavConfig.KarmaLevel[scavKarmaLevel.ToString()];
|
|
if (playerScavKarmaSettings == null)
|
|
_logger.Error(_localisationService.GetText("scav-missing_karma_settings", scavKarmaLevel));
|
|
|
|
_logger.Debug($"generated player scav loadout with karma level {scavKarmaLevel}");
|
|
|
|
// Edit baseBotNode values
|
|
var baseBotNode = ConstructBotBaseTemplate(playerScavKarmaSettings.BotTypeForLoot);
|
|
AdjustBotTemplateWithKarmaSpecificSettings(playerScavKarmaSettings, baseBotNode);
|
|
|
|
var scavData = (PmcData)_botGenerator.GeneratePlayerScav(
|
|
sessionID,
|
|
playerScavKarmaSettings.BotTypeForLoot.ToLower(),
|
|
"easy",
|
|
baseBotNode,
|
|
pmcDataClone);
|
|
|
|
// Remove cached bot data after scav was generated
|
|
_botLootCacheService.ClearCache();
|
|
|
|
// Add scav metadata
|
|
scavData.Savage = null;
|
|
scavData.Aid = pmcDataClone.Aid;
|
|
scavData.TradersInfo = pmcDataClone.TradersInfo;
|
|
scavData.Info.Settings = new();
|
|
scavData.Info.Bans = [];
|
|
scavData.Info.RegistrationDate = pmcDataClone.Info.RegistrationDate;
|
|
scavData.Info.GameVersion = pmcDataClone.Info.GameVersion;
|
|
scavData.Info.MemberCategory = MemberCategory.UNIQUE_ID;
|
|
scavData.Info.LockedMoveCommands = true;
|
|
scavData.RagfairInfo = pmcDataClone.RagfairInfo;
|
|
scavData.UnlockedInfo = pmcDataClone.UnlockedInfo;
|
|
|
|
// Persist previous scav data into new scav
|
|
scavData.Id = existingScavDataClone.Id ?? pmcDataClone.Savage;
|
|
scavData.SessionId = existingScavDataClone.SessionId ?? pmcDataClone.SessionId;
|
|
scavData.Skills = this.GetScavSkills(existingScavDataClone);
|
|
scavData.Stats = this.GetScavStats(existingScavDataClone);
|
|
scavData.Info.Level = this.GetScavLevel(existingScavDataClone);
|
|
scavData.Info.Experience = this.GetScavExperience(existingScavDataClone);
|
|
scavData.Quests = existingScavDataClone.Quests ?? [];
|
|
scavData.TaskConditionCounters = existingScavDataClone.TaskConditionCounters ?? new();
|
|
scavData.Notes = existingScavDataClone.Notes ?? new() { DataNotes = new() };
|
|
scavData.WishList = existingScavDataClone.WishList ?? new();
|
|
scavData.Encyclopedia = pmcDataClone.Encyclopedia ?? new();
|
|
|
|
// Add additional items to player scav as loot
|
|
AddAdditionalLootToPlayerScavContainers(playerScavKarmaSettings.LootItemsToAddChancePercent, scavData, [
|
|
"TacticalVest",
|
|
"Pockets",
|
|
"Backpack"
|
|
]);
|
|
|
|
// Remove secure container
|
|
scavData = _profileHelper.RemoveSecureContainer(scavData);
|
|
|
|
// set cooldown timer
|
|
scavData = SetScavCooldownTimer(scavData, pmcDataClone);
|
|
|
|
// add scav to profile
|
|
_saveServer.GetProfile(sessionID).CharacterData.ScavData = scavData;
|
|
|
|
return scavData;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add items picked from `playerscav.lootItemsToAddChancePercent`
|
|
/// </summary>
|
|
/// <param name="possibleItemsToAdd">dict of tpl + % chance to be added</param>
|
|
/// <param name="scavData"></param>
|
|
/// <param name="containersToAddTo">Possible slotIds to add loot to</param>
|
|
protected void AddAdditionalLootToPlayerScavContainers(Dictionary<string, double> possibleItemsToAdd, BotBase scavData, List<string> containersToAddTo)
|
|
{
|
|
foreach (var tpl in possibleItemsToAdd)
|
|
{
|
|
var shouldAdd = _randomUtil.GetChance100(tpl.Value);
|
|
if (!shouldAdd)
|
|
continue;
|
|
|
|
var itemResult = _itemHelper.GetItem(tpl.Key);
|
|
if (!itemResult.Key)
|
|
{
|
|
_logger.Warning(_localisationService.GetText("scav-unable_to_add_item_to_player_scav", tpl));
|
|
continue;
|
|
}
|
|
|
|
var itemTemplate = itemResult.Value;
|
|
var itemsToAdd = new List<Item>()
|
|
{
|
|
new Item()
|
|
{
|
|
Id = _hashUtil.Generate(),
|
|
Template = itemTemplate.Id,
|
|
Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(itemTemplate)
|
|
}
|
|
};
|
|
|
|
var result = _botGeneratorHelper.AddItemWithChildrenToEquipmentSlot(
|
|
containersToAddTo,
|
|
itemsToAdd[0].Id,
|
|
itemTemplate.Id,
|
|
itemsToAdd,
|
|
scavData.Inventory);
|
|
|
|
if (result != ItemAddedResult.SUCCESS)
|
|
_logger.Debug($"Unable to add keycard to bot. Reason: {result.ToString()}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the scav karama level for a profile
|
|
/// Is also the fence trader rep level
|
|
/// </summary>
|
|
/// <param name="pmcData">pmc profile</param>
|
|
/// <returns>karma level</returns>
|
|
protected double GetScavKarmaLevel(PmcData pmcData)
|
|
{
|
|
// can be empty during profile creation
|
|
if (!pmcData.TradersInfo.TryGetValue(Traders.FENCE, out var fenceInfo))
|
|
{
|
|
_logger.Warning(_localisationService.GetText("scav-missing_karma_level_getting_default"));
|
|
return 0;
|
|
}
|
|
|
|
if (fenceInfo.Standing > 6)
|
|
return 6;
|
|
|
|
return Math.Floor(fenceInfo.Standing ?? 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a baseBot template
|
|
/// If the parameter doesnt match "assault", take parts from the loot type and apply to the return bot template
|
|
/// </summary>
|
|
/// <param name="botTypeForLoot">bot type to use for inventory/chances</param>
|
|
/// <returns>IBotType object</returns>
|
|
protected BotType ConstructBotBaseTemplate(string botTypeForLoot)
|
|
{
|
|
var baseScavType = "assault";
|
|
var asssaultBase = _cloner.Clone(_botHelper.GetBotTemplate(baseScavType));
|
|
|
|
// Loot bot is same as base bot, return base with no modification
|
|
if (botTypeForLoot == baseScavType)
|
|
return asssaultBase;
|
|
|
|
var lootBase = _cloner.Clone(_botHelper.GetBotTemplate(botTypeForLoot));
|
|
asssaultBase.BotInventory = lootBase.BotInventory;
|
|
asssaultBase.BotChances = lootBase.BotChances;
|
|
asssaultBase.BotGeneration = lootBase.BotGeneration;
|
|
|
|
return asssaultBase;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adjust equipment/mod/item generation values based on scav karma levels
|
|
/// </summary>
|
|
/// <param name="karmaSettings">Values to modify the bot template with</param>
|
|
/// <param name="baseBotNode">bot template to modify according to karama level settings</param>
|
|
protected void AdjustBotTemplateWithKarmaSpecificSettings(KarmaLevel karmaSettings, BotType baseBotNode)
|
|
{
|
|
// Adjust equipment chance values
|
|
foreach (var equipmentKvP in karmaSettings.Modifiers.Equipment) {
|
|
|
|
// Adjustment value zero, nothing to do
|
|
if (equipmentKvP.Value == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Try add new key with value
|
|
if (!baseBotNode.BotChances.EquipmentChances.TryAdd(equipmentKvP.Key, equipmentKvP.Value))
|
|
{
|
|
// Unable to add new, update existing
|
|
baseBotNode.BotChances.EquipmentChances[equipmentKvP.Key] += equipmentKvP.Value;
|
|
}
|
|
}
|
|
|
|
// Adjust mod chance values
|
|
foreach (var modKvP in karmaSettings.Modifiers.Mod)
|
|
{
|
|
// Adjustment value zero, nothing to do
|
|
if (modKvP.Value == 0)
|
|
continue;
|
|
|
|
baseBotNode.BotChances.WeaponModsChances[modKvP.Key] += karmaSettings.Modifiers.Mod[modKvP.Key];
|
|
}
|
|
|
|
// Adjust item spawn quantity values
|
|
foreach (var itemLimitKvP in karmaSettings.ItemLimits)
|
|
{
|
|
baseBotNode.BotGeneration.Items[itemLimitKvP.Key] = itemLimitKvP.Value;
|
|
}
|
|
|
|
// Blacklist equipment, keyed by equipment slot
|
|
foreach (var equipmentBlacklistKvP in karmaSettings.EquipmentBlacklist) {
|
|
baseBotNode.BotInventory.Equipment.TryGetValue(equipmentBlacklistKvP.Key, out var equipmentDict);
|
|
foreach (var itemToRemove in equipmentBlacklistKvP.Value)
|
|
{
|
|
equipmentDict.Remove(itemToRemove);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected Skills GetScavSkills(PmcData scavProfile)
|
|
{
|
|
if (scavProfile?.Skills != null)
|
|
return scavProfile.Skills;
|
|
|
|
return GetDefaultScavSkills();
|
|
}
|
|
|
|
protected Skills GetDefaultScavSkills()
|
|
{
|
|
return new()
|
|
{
|
|
Common = new(new(), new()),
|
|
Mastering = new(),
|
|
Points = 0
|
|
};
|
|
}
|
|
|
|
protected Stats GetScavStats(PmcData scavProfile)
|
|
{
|
|
if (scavProfile?.Stats != null)
|
|
return scavProfile.Stats;
|
|
|
|
return _profileHelper.GetDefaultCounters();
|
|
}
|
|
|
|
protected int GetScavLevel(PmcData scavProfile)
|
|
{
|
|
// Info can be null on initial account creation
|
|
if (scavProfile?.Info?.Level == null)
|
|
return 1;
|
|
|
|
return scavProfile?.Info?.Level ?? 1;
|
|
}
|
|
|
|
protected double GetScavExperience(PmcData scavProfile)
|
|
{
|
|
// Info can be null on initial account creation
|
|
if (scavProfile?.Info?.Experience == null)
|
|
return 0;
|
|
|
|
return scavProfile?.Info?.Experience ?? 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set cooldown till scav is playable
|
|
/// take into account scav cooldown bonus
|
|
/// </summary>
|
|
/// <param name="scavData">scav profile</param>
|
|
/// <param name="pmcData">pmc profile</param>
|
|
/// <returns></returns>
|
|
protected PmcData SetScavCooldownTimer(PmcData scavData, PmcData pmcData)
|
|
{
|
|
// Set cooldown time.
|
|
// Make sure to apply ScavCooldownTimer bonus from Hideout if the player has it.
|
|
var scavLockDuration = _databaseService.GetGlobals().Configuration.SavagePlayCooldown;
|
|
var modifier = 1D;
|
|
|
|
foreach (var bonus in pmcData.Bonuses)
|
|
{
|
|
if (bonus.Type == BonusType.ScavCooldownTimer)
|
|
{
|
|
// Value is negative, so add.
|
|
// Also note that for scav cooldown, multiple bonuses stack additively.
|
|
modifier += (bonus?.Value ?? 1) / 100;
|
|
}
|
|
}
|
|
|
|
var fenceInfo = _fenceService.GetFenceInfo(pmcData);
|
|
modifier *= fenceInfo.SavageCooldownModifier ?? 1;
|
|
scavLockDuration *= modifier;
|
|
|
|
var fullProfile = _profileHelper.GetFullProfile(pmcData?.SessionId);
|
|
if (fullProfile?.ProfileInfo?.Edition.ToLower().StartsWith(AccountTypes.SPT_DEVELOPER) ?? false)
|
|
scavLockDuration = 10;
|
|
|
|
if (scavData?.Info != null)
|
|
scavData.Info.SavageLockTime = _timeUtil.GetTimeStamp() / 1000 + (long)scavLockDuration;
|
|
|
|
return scavData;
|
|
}
|
|
}
|