using SPTarkov.DI.Annotations; 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.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(TimeUtil timeUtil, SaveServer saveServer, ProfileHelper profileHelper, ConfigServer configServer) { protected readonly HealthConfig _healthConfig = configServer.GetConfig(); /// /// Update player profile vitality values with changes from client request object /// /// Session id /// Player profile to apply changes to /// Changes to apply /// OPTIONAL - Is player dead public void ApplyHealthChangesToProfile(MongoId sessionID, PmcData pmcProfileToUpdate, BotBaseHealth healthChanges, bool isDead = false) { 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 }; // Alter saved profiles Health with values from post-raid client data ModifyProfileHealthProperties(pmcProfileToUpdate, healthChanges.BodyParts, ["Dehydration", "Exhaustion"]); // Adjust hydration/energy/temperature AdjustProfileHydrationEnergyTemperature(pmcProfileToUpdate, healthChanges); // Update last edited timestamp pmcProfileToUpdate.Health.UpdateTime = timeUtil.GetTimeStamp(); } /// /// Apply Health values to profile /// /// Player profile on server /// Changes to apply /// protected void ModifyProfileHealthProperties( PmcData profileToAdjust, Dictionary bodyPartChanges, HashSet? effectsToSkip = null ) { foreach (var (partName, partProperties) in bodyPartChanges) { if (!profileToAdjust.Health.BodyParts.TryGetValue(partName, out var matchingProfilePart)) { continue; } 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(); // 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; } } } } /// /// Adjust hydration/energy/temperate /// /// Profile to update /// 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); } }