160 lines
6.9 KiB
C#
160 lines
6.9 KiB
C#
using SPTarkov.DI.Annotations;
|
|
using SPTarkov.Server.Core.Exceptions.Helpers;
|
|
using SPTarkov.Server.Core.Models.Common;
|
|
using SPTarkov.Server.Core.Models.Eft.Common;
|
|
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
|
|
using SPTarkov.Server.Core.Models.Spt.Config;
|
|
using SPTarkov.Server.Core.Models.Utils;
|
|
using SPTarkov.Server.Core.Servers;
|
|
using SPTarkov.Server.Core.Utils;
|
|
using BodyPartHealth = SPTarkov.Server.Core.Models.Eft.Common.Tables.BodyPartHealth;
|
|
|
|
namespace SPTarkov.Server.Core.Helpers;
|
|
|
|
[Injectable]
|
|
public class HealthHelper(ISptLogger<HealthHelper> logger, TimeUtil timeUtil, ConfigServer configServer)
|
|
{
|
|
protected readonly HealthConfig HealthConfig = configServer.GetConfig<HealthConfig>();
|
|
protected readonly HashSet<string> EffectsToSkip = ["Dehydration", "Exhaustion"];
|
|
|
|
/// <summary>
|
|
/// Update player profile vitality values with changes from client request object
|
|
/// </summary>
|
|
/// <param name="sessionId">Session id</param>
|
|
/// <param name="pmcProfileToUpdate">Player profile to apply changes to</param>
|
|
/// <param name="healthChanges">Changes to apply </param>
|
|
public void ApplyHealthChangesToProfile(MongoId sessionId, PmcData pmcProfileToUpdate, BotBaseHealth healthChanges)
|
|
{
|
|
/* TODO: Not used here, need to check node or a live profile, commented out for now to avoid the potential alloc - Cj
|
|
var fullProfile = saveServer.GetProfile(sessionId);
|
|
var profileEdition = fullProfile.ProfileInfo?.Edition;
|
|
var profileSide = fullProfile.CharacterData?.PmcData?.Info?.Side;
|
|
// Get matching 'side' e.g. USEC
|
|
var matchingSide = profileHelper.GetProfileTemplateForSide(profileEdition, profileSide);
|
|
var defaultTemperature = matchingSide?.Character?.Health?.Temperature ?? new CurrentMinMax { Current = 36.6 };
|
|
*/
|
|
|
|
if (healthChanges.BodyParts is null)
|
|
{
|
|
const string message = "healthChanges.BodyParts is null when trying to apply health changes";
|
|
logger.Error(message);
|
|
throw new HealthHelperException(message);
|
|
}
|
|
|
|
// Alter saved profiles Health with values from post-raid client data
|
|
ModifyProfileHealthProperties(pmcProfileToUpdate, healthChanges.BodyParts, EffectsToSkip);
|
|
|
|
// Adjust hydration/energy/temperature
|
|
AdjustProfileHydrationEnergyTemperature(pmcProfileToUpdate, healthChanges);
|
|
|
|
if (pmcProfileToUpdate.Health is null)
|
|
{
|
|
const string message = "pmcProfileToUpdate.Health is null when trying to apply health changes";
|
|
logger.Error(message);
|
|
throw new HealthHelperException(message);
|
|
}
|
|
|
|
// Update last edited timestamp
|
|
pmcProfileToUpdate.Health.UpdateTime = timeUtil.GetTimeStamp();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply Health values to profile
|
|
/// </summary>
|
|
/// <param name="profileToAdjust">Player profile on server</param>
|
|
/// <param name="bodyPartChanges">Changes to apply</param>
|
|
/// <param name="effectsToSkip"></param>
|
|
protected void ModifyProfileHealthProperties(
|
|
PmcData profileToAdjust,
|
|
Dictionary<string, BodyPartHealth> bodyPartChanges,
|
|
HashSet<string>? effectsToSkip = null
|
|
)
|
|
{
|
|
foreach (var (partName, partProperties) in bodyPartChanges)
|
|
{
|
|
// Pattern matching null and false because otherwise the compiler throws a fit because `matchingProfilePart`
|
|
// might not be initialized, very cool
|
|
if (profileToAdjust.Health?.BodyParts?.TryGetValue(partName, out var matchingProfilePart) is null or false)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (partProperties.Health is null || matchingProfilePart.Health is null)
|
|
{
|
|
const string message =
|
|
"partProperties.Health or matchingBodyPart.Health is null when trying to modify profile health properties";
|
|
logger.Error(message);
|
|
throw new HealthHelperException(message);
|
|
}
|
|
|
|
if (HealthConfig.Save.Health)
|
|
{
|
|
// Apply hp changes to profile
|
|
matchingProfilePart.Health.Current =
|
|
partProperties.Health.Current == 0
|
|
? partProperties.Health.Maximum * HealthConfig.HealthMultipliers.Blacked
|
|
: partProperties.Health.Current;
|
|
|
|
matchingProfilePart.Health.Maximum = partProperties.Health.Maximum;
|
|
}
|
|
|
|
// Process each effect for each part
|
|
foreach (var (key, effectDetails) in partProperties.Effects ?? [])
|
|
{
|
|
// Null guard
|
|
matchingProfilePart.Effects ??= new Dictionary<string, BodyPartEffectProperties?>();
|
|
|
|
// Effect already exists on limb in server profile, skip
|
|
if (matchingProfilePart.Effects.ContainsKey(key))
|
|
{
|
|
// Edge case - effect already exists at destination, but we don't want to overwrite details
|
|
if (effectsToSkip is not null && effectsToSkip.Contains(key))
|
|
{
|
|
matchingProfilePart.Effects[key] = null;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (effectsToSkip is not null && effectsToSkip.Contains(key))
|
|
// Do not pass skipped effect into profile
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var effectToAdd = new BodyPartEffectProperties { Time = effectDetails?.Time ?? -1 };
|
|
// Add effect to server profile
|
|
if (matchingProfilePart.Effects.TryAdd(key, effectToAdd))
|
|
{
|
|
matchingProfilePart.Effects[key] = effectToAdd;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adjust hydration/energy/temperate
|
|
/// </summary>
|
|
/// <param name="profileToUpdate">Profile to update</param>
|
|
/// <param name="healthChanges"></param>
|
|
protected void AdjustProfileHydrationEnergyTemperature(PmcData profileToUpdate, BotBaseHealth healthChanges)
|
|
{
|
|
// Ensure current hydration/energy/temp are copied over and don't exceed maximum
|
|
var profileHealth = profileToUpdate.Health;
|
|
profileHealth.Hydration.Current =
|
|
profileHealth.Hydration.Current > healthChanges.Hydration.Maximum
|
|
? healthChanges.Hydration.Maximum
|
|
: Math.Round(healthChanges.Hydration.Current ?? 0);
|
|
|
|
profileHealth.Energy.Current =
|
|
profileHealth.Energy.Current > healthChanges.Energy.Maximum
|
|
? healthChanges.Energy.Maximum
|
|
: Math.Round(healthChanges.Energy.Current ?? 0);
|
|
|
|
profileHealth.Temperature.Current =
|
|
profileHealth.Temperature.Current > healthChanges.Temperature.Maximum
|
|
? healthChanges.Temperature.Maximum
|
|
: Math.Round(healthChanges.Temperature.Current ?? 0);
|
|
}
|
|
}
|