using Core.Helpers; using Core.Models.Eft.Common; using Core.Models.Eft.Common.Tables; using Core.Models.Eft.Health; using Core.Models.Eft.ItemEvent; using Core.Models.Eft.Trade; using Core.Models.Enums; using Core.Models.Utils; using Core.Routers; using Core.Services; using Core.Utils; using Core.Utils.Cloners; using SptCommon.Annotations; using SptCommon.Extensions; namespace Core.Controllers; [Injectable] public class HealthController( ISptLogger _logger, EventOutputHolder _eventOutputHolder, ItemHelper _itemHelper, PaymentService _paymentService, InventoryHelper _inventoryHelper, LocalisationService _localisationService, HttpResponseUtil _httpResponseUtil, HealthHelper _healthHelper, ICloner _cloner) { /// /// When healing in menu /// /// Player profile /// Healing request /// Player id /// ItemEventRouterResponse public ItemEventRouterResponse OffRaidHeal( PmcData pmcData, OffraidHealRequestData request, string sessionId) { throw new NotImplementedException(); } /// /// Handle Eat event /// Consume food/water outside of a raid /// /// Player profile /// Eat request /// Session id /// ItemEventRouterResponse public ItemEventRouterResponse OffRaidEat( PmcData pmcData, OffraidEatRequestData request, string sessionID) { var output = _eventOutputHolder.GetOutput(sessionID); var resourceLeft = 0d; var itemToConsume = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == request.Item); if (itemToConsume is null) { // Item not found, very bad return _httpResponseUtil.AppendErrorToOutput( output, _localisationService.GetText("health-unable_to_find_item_to_consume", request.Item) ); } var consumedItemMaxResource = _itemHelper.GetItem(itemToConsume.Template).Value.Properties.MaxResource; if (consumedItemMaxResource > 1) { // Ensure item has a upd object _itemHelper.AddUpdObjectToItem(itemToConsume); if (itemToConsume.Upd.FoodDrink is null) { itemToConsume.Upd.FoodDrink = new UpdFoodDrink { HpPercent = consumedItemMaxResource - request.Count }; } else { itemToConsume.Upd.FoodDrink.HpPercent -= request.Count; } resourceLeft = itemToConsume.Upd.FoodDrink.HpPercent.Value; } // Remove item from inventory if resource has dropped below threshold if (consumedItemMaxResource == 1 || resourceLeft < 1) { _inventoryHelper.RemoveItem(pmcData, request.Item, sessionID, output); } // Check what effect eating item has and handle var foodItemDbDetails = _itemHelper.GetItem(itemToConsume.Template).Value; var foodItemEffectDetails = foodItemDbDetails.Properties.EffectsHealth; var foodIsSingleUse = foodItemDbDetails.Properties.MaxResource == 1; foreach (var (key, effectProps) in foodItemEffectDetails) switch (key) { case "Hydration": ApplyEdibleEffect(pmcData.Health.Hydration, effectProps, foodIsSingleUse, request); break; case "Energy": ApplyEdibleEffect(pmcData.Health.Energy, effectProps, foodIsSingleUse, request); break; default: _logger.Warning($"Unhandled effect after consuming: ${itemToConsume.Template}, ${key}"); break; } return output; } protected void ApplyEdibleEffect(CurrentMinMax bodyValue, EffectsHealthProps consumptionDetails, bool foodIsSingleUse, OffraidEatRequestData request) { if (foodIsSingleUse) { // Apply whole value from passed in parameter bodyValue.Current += consumptionDetails.Value; } else { bodyValue.Current += request.Count; } // Ensure current never goes over max if (bodyValue.Current > bodyValue.Maximum) { bodyValue.Current = bodyValue.Maximum; return; } // Same as above but for the lower bound if (bodyValue.Current < 0) { bodyValue.Current = 0; } } /// /// Handle RestoreHealth event /// Occurs on post-raid healing page /// /// player profile /// Request data from client /// Session id /// public ItemEventRouterResponse HealthTreatment( PmcData pmcData, HealthTreatmentRequestData healthTreatmentRequest, string sessionID) { var output = _eventOutputHolder.GetOutput(sessionID); var payMoneyRequest = new ProcessBuyTradeRequestData { Action = healthTreatmentRequest.Action, TransactionId = Traders.THERAPIST, SchemeItems = healthTreatmentRequest.Items, Type = "", ItemId = "", Count = 0, SchemeId = 0 }; _paymentService.PayMoney(pmcData, payMoneyRequest, sessionID, output); if (output.Warnings.Count > 0) { return output; } foreach (var bodyPartKvP in healthTreatmentRequest.Difference.BodyParts.GetAllPropsAsDict()) { // Get body part from request + from pmc profile var partRequest = (BodyPartEffects)bodyPartKvP.Value; var profilePart = pmcData.Health.BodyParts[bodyPartKvP.Key]; // Bodypart healing is chosen when part request hp is above 0 if (partRequest.Health > 0) { // Heal bodypart profilePart.Health.Current = profilePart.Health.Maximum; } // Check for effects to remove if (partRequest.Effects?.Count > 0) { // Found some, loop over them and remove from pmc profile foreach (var effect in partRequest.Effects) pmcData.Health.BodyParts[bodyPartKvP.Key].Effects.Remove(effect); // Remove empty effect object if (pmcData.Health.BodyParts[bodyPartKvP.Key].Effects.Count == 0) { pmcData.Health.BodyParts[bodyPartKvP.Key].Effects = null; } } } // Inform client of new post-raid, post-therapist heal values output.ProfileChanges[sessionID].Health = _cloner.Clone(pmcData.Health); return output; } /// /// applies skills from hideout workout. /// /// Player profile /// Request data /// session id public void ApplyWorkoutChanges( PmcData? pmcData, WorkoutData request, string sessionId) { pmcData.Skills.Common = request.Skills.Common; } }