diff --git a/Libraries/SPTarkov.Server.Core/Helpers/HealthHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/HealthHelper.cs
index 025ad36c..d186b641 100644
--- a/Libraries/SPTarkov.Server.Core/Helpers/HealthHelper.cs
+++ b/Libraries/SPTarkov.Server.Core/Helpers/HealthHelper.cs
@@ -37,40 +37,40 @@ public class HealthHelper(
///
/// Update player profile vitality values with changes from client request object
///
- /// Player profile
- /// Post raid data
/// Session id
- /// Is player dead
- public void UpdateProfileHealthPostRaid(
- PmcData pmcData,
- BotBaseHealth postRaidHealth,
+ /// Player profile to apply changes to
+ /// Changes to apply
+ /// OPTIONAL - Is player dead
+ public void ApplyHealthChangesToProfile(
string sessionID,
- bool isDead
+ 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
+ // Get matching 'side' e.g. USEC
var matchingSide = _profileHelper.GetProfileTemplateForSide(profileEdition, profileSide);
var defaultTemperature =
matchingSide?.Character?.Health?.Temperature ?? new CurrentMinMax { Current = 36.6 };
fullProfile.StoreHydrationEnergyTempInProfile(
- postRaidHealth.Hydration.Current ?? 0,
- postRaidHealth.Energy.Current ?? 0,
+ healthChanges.Hydration.Current ?? 0,
+ healthChanges.Energy.Current ?? 0,
defaultTemperature.Current ?? 0 // Reset profile temp to the default to prevent very cold/hot temps persisting into next raid
);
// Store limb effects from post-raid in profile
- foreach (var bodyPart in postRaidHealth.BodyParts)
+ foreach (var bodyPart in healthChanges.BodyParts)
{
// Effects
- if (postRaidHealth.BodyParts[bodyPart.Key].Effects is not null)
+ if (healthChanges.BodyParts[bodyPart.Key].Effects is not null)
{
- fullProfile.VitalityData.Health[bodyPart.Key].Effects = postRaidHealth
+ fullProfile.VitalityData.Health[bodyPart.Key].Effects = healthChanges
.BodyParts[bodyPart.Key]
.Effects;
}
@@ -80,75 +80,77 @@ public class HealthHelper(
// Player alive, not is limb alive
{
fullProfile.VitalityData.Health[bodyPart.Key].Health.Current =
- postRaidHealth.BodyParts[bodyPart.Key].Health.Current ?? 0;
+ healthChanges.BodyParts[bodyPart.Key].Health.Current ?? 0;
}
else
{
fullProfile.VitalityData.Health[bodyPart.Key].Health.Current =
- pmcData.Health.BodyParts[bodyPart.Key].Health.Maximum
+ pmcProfileToUpdate.Health.BodyParts[bodyPart.Key].Health.Maximum
* _healthConfig.HealthMultipliers.Death
?? 0;
}
}
-
- TransferPostRaidLimbEffectsToProfile(postRaidHealth.BodyParts, pmcData);
+ // Alter saved profiles Health with values from post-raid client data
+ ModifyProfileHeathProperties(
+ healthChanges.BodyParts,
+ pmcProfileToUpdate,
+ ["Dehydration", "Exhaustion"]
+ );
// Adjust hydration/energy/temp and limb hp using temp storage hydrated above
- SaveHealth(pmcData, sessionID);
+ SaveHealth(pmcProfileToUpdate, sessionID);
// Reset temp storage
ResetVitality(sessionID);
// Update last edited timestamp
- pmcData.Health.UpdateTime = _timeUtil.GetTimeStamp();
+ pmcProfileToUpdate.Health.UpdateTime = _timeUtil.GetTimeStamp();
}
///
- /// Take body part effects from client profile and apply to server profile
+ /// Apply Health values to profile
///
- /// Post-raid body part data
- /// Player profile on server
- protected void TransferPostRaidLimbEffectsToProfile(
- Dictionary postRaidBodyParts,
- PmcData profileData
+ /// Changes to apply
+ /// Player profile on server
+ ///
+ protected void ModifyProfileHeathProperties(
+ Dictionary bodyPartChanges,
+ PmcData profileToAdjust,
+ HashSet? effectsToSkip = null
)
{
- // Iterate over each body part
- HashSet effectsToIgnore = ["Dehydration", "Exhaustion"];
- foreach (var bodyPartId in postRaidBodyParts)
+ foreach (var (partName, partProperties) in bodyPartChanges)
{
- // Get effects on body part from profile
- var bodyPartEffects = postRaidBodyParts[bodyPartId.Key].Effects;
- foreach (var (key, effectDetails) in bodyPartEffects)
+ // Process each effect for each part
+ foreach (var (key, effectDetails) in partProperties.Effects)
{
// Null guard
- profileData.Health.BodyParts[bodyPartId.Key].Effects ??=
- new Dictionary();
+ var matchingProfilePart = profileToAdjust.Health.BodyParts[partName];
+ matchingProfilePart.Effects ??= new Dictionary();
// Effect already exists on limb in server profile, skip
- var profileBodyPartEffects = profileData.Health.BodyParts[bodyPartId.Key].Effects;
- if (profileBodyPartEffects.ContainsKey(key))
+ if (matchingProfilePart.Effects.ContainsKey(key))
{
- if (effectsToIgnore.Contains(key))
- // Get rid of certain effects we don't want to persist out of raid
+ // Edge case - effect already exists at destination, but we don't want to overwrite details
+ if (effectsToSkip is not null && effectsToSkip.Contains(key))
{
- profileBodyPartEffects[key] = null;
+ matchingProfilePart.Effects[key] = null;
}
continue;
}
- if (effectsToIgnore.Contains(key))
- // Do not pass some effects to out of raid profile
+ 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 (profileBodyPartEffects.TryAdd(key, effectToAdd))
+ if (matchingProfilePart.Effects.TryAdd(key, effectToAdd))
{
- profileBodyPartEffects[key] = effectToAdd;
+ matchingProfilePart.Effects[key] = effectToAdd;
}
}
}
@@ -187,22 +189,21 @@ public class HealthHelper(
pmcData.Health.Energy.Current = Math.Round(profileHealth.Energy ?? 0);
pmcData.Health.Temperature.Current = Math.Round(profileHealth.Temperature ?? 0);
- foreach (var bodyPart in pmcData.Health.BodyParts)
+ foreach (var (partName, partProperties) in pmcData.Health.BodyParts)
{
- if (profileHealth.Health[bodyPart.Key].Health.Maximum > bodyPart.Value.Health.Maximum)
+ var matchingProfilePart = profileHealth.Health[partName];
+ if (matchingProfilePart.Health.Maximum > partProperties.Health.Maximum)
{
- profileHealth.Health[bodyPart.Key].Health.Maximum = bodyPart.Value.Health.Maximum;
+ matchingProfilePart.Health.Maximum = partProperties.Health.Maximum;
}
- if (profileHealth.Health[bodyPart.Key].Health.Current == 0)
+ if (matchingProfilePart.Health.Current == 0)
{
- profileHealth.Health[bodyPart.Key].Health.Current =
- bodyPart.Value.Health.Maximum * _healthConfig.HealthMultipliers.Blacked;
+ matchingProfilePart.Health.Current =
+ partProperties.Health.Maximum * _healthConfig.HealthMultipliers.Blacked;
}
- bodyPart.Value.Health.Current = Math.Round(
- profileHealth.Health[bodyPart.Key].Health.Current ?? 0
- );
+ partProperties.Health.Current = Math.Round(matchingProfilePart.Health.Current ?? 0);
}
}
diff --git a/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs b/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs
index 576803e3..1d9882d3 100644
--- a/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs
+++ b/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs
@@ -459,7 +459,7 @@ public class LocationLifecycleService
{
// Manually store the map player just left
request.LocationTransit.SptLastVisitedLocation = locationName;
- // TODO - Persist each players last visited location history over multiple transits, e.g using InMemoryCacheService, need to take care to not let data get stored forever
+ // TODO - Persist each players last visited location history over multiple transits, e.g. using InMemoryCacheService, need to take care to not let data get stored forever
// Store transfer data for later use in `startLocalRaid()` when next raid starts
request.LocationTransit.SptExitName = request.Results.ExitName;
_profileActivityService.GetProfileActivityRaidData(sessionId).LocationTransit =
@@ -881,7 +881,7 @@ public class LocationLifecycleService
/// Handles PMC Profile after the raid
///
/// Player id
- /// Pmc profile
+ /// Pmc profile from server
/// Scav profile
/// Player died/got left behind in raid
/// Not same as opposite of `isDead`, specific status
@@ -890,7 +890,7 @@ public class LocationLifecycleService
/// Current finished Raid location
protected void HandlePostRaidPmc(
string sessionId,
- SptProfile fullProfile,
+ SptProfile fullServerProfile,
PmcData scavProfile,
bool isDead,
bool isSurvived,
@@ -899,78 +899,84 @@ public class LocationLifecycleService
string locationName
)
{
- var pmcProfile = fullProfile.CharacterData.PmcData;
+ var serverPmcProfile = fullServerProfile.CharacterData.PmcData;
var postRaidProfile = request.Results.Profile;
- var preRaidProfileQuestDataClone = _cloner.Clone(pmcProfile.Quests);
+ var preRaidProfileQuestDataClone = _cloner.Clone(serverPmcProfile.Quests);
// MUST occur BEFORE inventory actions (setInventory()) occur
// Player died, get quest items they lost for use later
var lostQuestItems = postRaidProfile.GetQuestItemsInProfile();
// Update inventory
- _inRaidHelper.SetInventory(sessionId, pmcProfile, postRaidProfile, isSurvived, isTransfer);
+ _inRaidHelper.SetInventory(
+ sessionId,
+ serverPmcProfile,
+ postRaidProfile,
+ isSurvived,
+ isTransfer
+ );
- pmcProfile.Info.Level = postRaidProfile.Info.Level;
- pmcProfile.Skills = postRaidProfile.Skills;
- pmcProfile.Stats.Eft = postRaidProfile.Stats.Eft;
- pmcProfile.Encyclopedia = postRaidProfile.Encyclopedia;
- pmcProfile.TaskConditionCounters = postRaidProfile.TaskConditionCounters;
- pmcProfile.SurvivorClass = postRaidProfile.SurvivorClass;
+ serverPmcProfile.Info.Level = postRaidProfile.Info.Level;
+ serverPmcProfile.Skills = postRaidProfile.Skills;
+ serverPmcProfile.Stats.Eft = postRaidProfile.Stats.Eft;
+ serverPmcProfile.Encyclopedia = postRaidProfile.Encyclopedia;
+ serverPmcProfile.TaskConditionCounters = postRaidProfile.TaskConditionCounters;
+ serverPmcProfile.SurvivorClass = postRaidProfile.SurvivorClass;
// MUST occur prior to profile achievements being overwritten by post-raid achievements
- ProcessAchievementRewards(fullProfile, postRaidProfile.Achievements);
+ ProcessAchievementRewards(fullServerProfile, postRaidProfile.Achievements);
- pmcProfile.Achievements = postRaidProfile.Achievements;
- pmcProfile.Quests = ProcessPostRaidQuests(postRaidProfile.Quests);
+ serverPmcProfile.Achievements = postRaidProfile.Achievements;
+ serverPmcProfile.Quests = ProcessPostRaidQuests(postRaidProfile.Quests);
// Handle edge case - must occur AFTER processPostRaidQuests()
LightkeeperQuestWorkaround(
sessionId,
postRaidProfile.Quests,
preRaidProfileQuestDataClone,
- pmcProfile
+ serverPmcProfile
);
- pmcProfile.WishList = postRaidProfile.WishList;
+ serverPmcProfile.WishList = postRaidProfile.WishList;
- pmcProfile.Info.Experience = postRaidProfile.Info.Experience;
+ serverPmcProfile.Info.Experience = postRaidProfile.Info.Experience;
- ApplyTraderStandingAdjustments(pmcProfile.TradersInfo, postRaidProfile.TradersInfo);
+ ApplyTraderStandingAdjustments(serverPmcProfile.TradersInfo, postRaidProfile.TradersInfo);
// Must occur AFTER experience is set and stats copied over
- pmcProfile.Stats.Eft.TotalSessionExperience = 0;
+ serverPmcProfile.Stats.Eft.TotalSessionExperience = 0;
const string fenceId = Traders.FENCE;
// Clamp fence standing
var currentFenceStanding = postRaidProfile.TradersInfo[fenceId].Standing;
- pmcProfile.TradersInfo[fenceId].Standing = Math.Min(
+ serverPmcProfile.TradersInfo[fenceId].Standing = Math.Min(
Math.Max((double)currentFenceStanding, -7),
15
); // Ensure it stays between -7 and 15
// Copy fence values to Scav
- scavProfile.TradersInfo[fenceId] = pmcProfile.TradersInfo[fenceId];
+ scavProfile.TradersInfo[fenceId] = serverPmcProfile.TradersInfo[fenceId];
// MUST occur AFTER encyclopedia updated
- MergePmcAndScavEncyclopedias(pmcProfile, scavProfile);
+ MergePmcAndScavEncyclopedias(serverPmcProfile, scavProfile);
// Handle temp, hydration, limb hp/effects
- _healthHelper.UpdateProfileHealthPostRaid(
- pmcProfile,
- postRaidProfile.Health,
+ _healthHelper.ApplyHealthChangesToProfile(
sessionId,
+ serverPmcProfile,
+ postRaidProfile.Health,
isDead
);
if (isTransfer)
{
// Adjust limb hp and effects while transiting
- UpdateLimbValuesAfterTransit(pmcProfile.Health);
+ UpdateLimbValuesAfterTransit(serverPmcProfile.Health);
}
// This must occur _BEFORE_ `deleteInventory`, as that method clears insured items
- HandleInsuredItemLostEvent(sessionId, pmcProfile, request, locationName);
+ HandleInsuredItemLostEvent(sessionId, serverPmcProfile, request, locationName);
if (isDead)
{
@@ -978,7 +984,11 @@ public class LocationLifecycleService
// MUST occur AFTER quests have post raid quest data has been merged "processPostRaidQuests()"
// Player is dead + had quest items, check and fix any broken find item quests
{
- CheckForAndFixPickupQuestsAfterDeath(sessionId, lostQuestItems, pmcProfile.Quests);
+ CheckForAndFixPickupQuestsAfterDeath(
+ sessionId,
+ lostQuestItems,
+ serverPmcProfile.Quests
+ );
}
if (postRaidProfile.Stats.Eft.Aggressor is not null)
@@ -987,16 +997,16 @@ public class LocationLifecycleService
postRaidProfile.Stats.Eft.Aggressor.ProfileId = request.Results.KillerId;
_pmcChatResponseService.SendKillerResponse(
sessionId,
- pmcProfile,
+ serverPmcProfile,
postRaidProfile.Stats.Eft.Aggressor
);
}
- _inRaidHelper.DeleteInventory(pmcProfile, sessionId);
+ _inRaidHelper.DeleteInventory(serverPmcProfile, sessionId);
_inRaidHelper.RemoveFiRStatusFromItemsInContainer(
sessionId,
- pmcProfile,
+ serverPmcProfile,
"SecuredContainer"
);
}
@@ -1012,7 +1022,7 @@ public class LocationLifecycleService
if (victims?.Count > 0)
// Player killed PMCs, send some mail responses to them
{
- _pmcChatResponseService.SendVictimResponse(sessionId, victims, pmcProfile);
+ _pmcChatResponseService.SendVictimResponse(sessionId, victims, serverPmcProfile);
}
}