diff --git a/Libraries/Core/Callbacks/HideoutCallbacks.cs b/Libraries/Core/Callbacks/HideoutCallbacks.cs
index b60bf383..d385a14d 100644
--- a/Libraries/Core/Callbacks/HideoutCallbacks.cs
+++ b/Libraries/Core/Callbacks/HideoutCallbacks.cs
@@ -125,7 +125,7 @@ public class HideoutCallbacks(
///
/// Handle client/game/profile/items/moving - HideoutCancelProductionCommand
///
- public ItemEventRouterResponse CancelProduction(PmcData pmcData, HideoutImproveAreaRequestData request, string sessionID)
+ public ItemEventRouterResponse CancelProduction(PmcData pmcData, HideoutCancelProductionRequestData request, string sessionID)
{
return _hideoutController.CancelProduction(sessionID, pmcData, request);
}
diff --git a/Libraries/Core/Controllers/HideoutController.cs b/Libraries/Core/Controllers/HideoutController.cs
index c8be08d3..0c290c77 100644
--- a/Libraries/Core/Controllers/HideoutController.cs
+++ b/Libraries/Core/Controllers/HideoutController.cs
@@ -1,11 +1,14 @@
using SptCommon.Annotations;
using Core.Generators;
using Core.Helpers;
+using Core.Models.Common;
using Core.Models.Eft.Common;
using Core.Models.Eft.Common.Tables;
using Core.Models.Eft.Hideout;
+using Core.Models.Eft.Inventory;
using Core.Models.Eft.ItemEvent;
using Core.Models.Enums;
+using Core.Models.Enums.Hideout;
using Core.Models.Spt.Config;
using Core.Models.Utils;
using Core.Routers;
@@ -44,7 +47,15 @@ public class HideoutController(
)
{
protected HideoutConfig _hideoutConfig = _configServer.GetConfig();
- protected string _nameTaskConditionCountersCraftingId = "673f5d6fdd6ed700c703afdc";
+ public const string NameTaskConditionCountersCraftingId = "673f5d6fdd6ed700c703afdc";
+
+ protected List _hideoutAreas =
+ [
+ HideoutAreas.AIR_FILTERING,
+ HideoutAreas.WATER_COLLECTOR,
+ HideoutAreas.GENERATOR,
+ HideoutAreas.BITCOIN_FARM,
+ ];
public void StartUpgrade(PmcData pmcData, HideoutUpgradeRequestData request, string sessionID, ItemEventRouterResponse output)
{
@@ -266,91 +277,991 @@ public class HideoutController(
}
}
- private void AddMissingPresetStandItemsToProfile(string sessionId, Stage hideoutStage, PmcData pmcData, HideoutArea dbHideoutArea, ItemEventRouterResponse output)
+ private void AddUpdateInventoryItemToProfile(string sessionId, PmcData pmcData, HideoutArea dbHideoutArea, Stage hideoutStage)
{
- throw new NotImplementedException();
+ var existingInventoryItem = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == dbHideoutArea.Id);
+ if (existingInventoryItem is not null)
+ {
+ // Update existing items container tpl to point to new id (tpl)
+ existingInventoryItem.Template = hideoutStage.Container;
+
+ return;
+ }
+
+ // Add new item as none exists (don't inform client of newContainerItem, will be done in `profileChanges.changedHideoutStashes`)
+ var newContainerItem = new Item { Id = dbHideoutArea.Id, Template = hideoutStage.Container };
+ pmcData.Inventory.Items.Add(newContainerItem);
}
- private void AddUpdateInventoryItemToProfile(string sessionId, PmcData pmcData, HideoutArea childDbArea, Stage childDbAreaStage)
+ private void AddContainerUpgradeToClientOutput(string sessionID, HideoutAreas? areaType, HideoutArea hideoutDbData, Stage hideoutStage,
+ ItemEventRouterResponse output)
{
- throw new NotImplementedException();
+ if (output.ProfileChanges[sessionID].ChangedHideoutStashes is null)
+ {
+ output.ProfileChanges[sessionID].ChangedHideoutStashes = new Dictionary();
+ }
+
+ // Inform client of changes
+ output.ProfileChanges[sessionID].ChangedHideoutStashes[areaType.ToString()] = new HideoutStashItem
+ {
+ Id = hideoutDbData.Id,
+ Template = hideoutStage.Container,
+ };
}
- private void AddContainerUpgradeToClientOutput(string sessionId, HideoutAreas? type, HideoutArea childDbArea, Stage childDbAreaStage, ItemEventRouterResponse output)
+ public ItemEventRouterResponse PutItemsInAreaSlots(PmcData pmcData, HideoutPutItemInRequestData addItemToHideoutRequest, string sessionID)
{
- throw new NotImplementedException();
+ var output = _eventOutputHolder.GetOutput(sessionID);
+
+ var itemsToAdd = addItemToHideoutRequest.Items.Select(
+ kvp =>
+ {
+ var item = pmcData.Inventory.Items.FirstOrDefault((invItem) => invItem.Id == kvp.Value.Id);
+ return new { inventoryItem = item, requestedItem = kvp.Value, slot = kvp.Key };
+ }
+ );
+
+ var hideoutArea = pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == addItemToHideoutRequest.AreaType);
+ if (hideoutArea is null)
+ {
+ _logger.Error(
+ _localisationService.GetText(
+ "hideout-unable_to_find_area_in_database",
+ addItemToHideoutRequest.AreaType
+ )
+ );
+ return _httpResponseUtil.AppendErrorToOutput(output);
+ }
+
+ foreach (var item in itemsToAdd)
+ {
+ if (item.inventoryItem is null)
+ {
+ _logger.Error(
+ _localisationService.GetText(
+ "hideout-unable_to_find_item_in_inventory",
+ new
+ {
+ itemId = item.requestedItem.Id,
+ area = hideoutArea.Type
+ }
+ )
+ );
+ return _httpResponseUtil.AppendErrorToOutput(output);
+ }
+
+ // Add item to area.slots
+ var destinationLocationIndex = int.Parse(item.slot);
+ var hideoutSlotIndex = hideoutArea.Slots.FindIndex(
+ (slot) => slot.LocationIndex == destinationLocationIndex
+ );
+ if (hideoutSlotIndex == -1)
+ {
+ _logger.Error(
+ $"Unable to put item: {item.requestedItem.Id} into slot as slot cannot be found for area: {addItemToHideoutRequest.AreaType}, skipping"
+ );
+ continue;
+ }
+
+ hideoutArea.Slots[hideoutSlotIndex].Items =
+ [
+ new HideoutItem()
+ {
+ Id = item.inventoryItem.Id,
+ Template = item.inventoryItem.Template,
+ Upd = item.inventoryItem.Upd
+ },
+ ];
+
+ _inventoryHelper.RemoveItem(pmcData, item.inventoryItem.Id, sessionID, output);
+ }
+
+ // Trigger a forced update
+ _hideoutHelper.UpdatePlayerHideout(sessionID);
+
+ return output;
}
- public ItemEventRouterResponse PutItemsInAreaSlots(PmcData pmcData, HideoutPutItemInRequestData request, string sessionId)
+ public ItemEventRouterResponse TakeItemsFromAreaSlots(PmcData pmcData, HideoutTakeItemOutRequestData request, string sessionID)
{
- throw new NotImplementedException();
+ var output = _eventOutputHolder.GetOutput(sessionID);
+
+ var hideoutArea = pmcData.Hideout.Areas.FirstOrDefault((area) => area.Type == request.AreaType);
+ if (hideoutArea is null)
+ {
+ _logger.Error(_localisationService.GetText("hideout-unable_to_find_area", request.AreaType));
+ return _httpResponseUtil.AppendErrorToOutput(output);
+ }
+
+ if (hideoutArea.Slots is null || hideoutArea.Slots.Count == 0)
+ {
+ _logger.Error(
+ _localisationService.GetText("hideout-unable_to_find_item_to_remove_from_area", hideoutArea.Type)
+ );
+ return _httpResponseUtil.AppendErrorToOutput(output);
+ }
+
+ // Handle areas that have resources that can be placed in/taken out of slots from the area
+ if (
+ _hideoutAreas.Contains(hideoutArea.Type ?? HideoutAreas.NOTSET)
+ )
+ {
+ var response = RemoveResourceFromArea(sessionID, pmcData, request, output, hideoutArea);
+
+ // Force a refresh of productions/hideout areas with resources
+ _hideoutHelper.UpdatePlayerHideout(sessionID);
+ return response;
+ }
+
+ throw new Exception(
+ _localisationService.GetText("hideout-unhandled_remove_item_from_area_request", hideoutArea.Type)
+ );
}
- public ItemEventRouterResponse TakeItemsFromAreaSlots(PmcData pmcData, HideoutTakeItemOutRequestData request, string sessionId)
+ private ItemEventRouterResponse RemoveResourceFromArea(string sessionID, PmcData pmcData, HideoutTakeItemOutRequestData removeResourceRequest,
+ ItemEventRouterResponse output, BotHideoutArea hideoutArea)
{
- throw new NotImplementedException();
+ var slotIndexToRemove = removeResourceRequest?.Slots.FirstOrDefault();
+ if (slotIndexToRemove is null)
+ {
+ _logger.Warning(
+ $"Unable to remove resource from area: {removeResourceRequest.AreaType} slot as no slots found in request, RESTART CLIENT IMMEDIATELY"
+ );
+
+ return output;
+ }
+
+ // Assume only one item in slot
+ var itemToReturn = hideoutArea.Slots.FirstOrDefault(slot => slot.LocationIndex == slotIndexToRemove)?.Items.FirstOrDefault();
+ if (itemToReturn is null)
+ {
+ _logger.Warning($"Unable to remove resource from area: {removeResourceRequest.AreaType} slot as no item found, RESTART CLIENT IMMEDIATELY");
+
+ return output;
+ }
+
+ AddItemDirectRequest request = new AddItemDirectRequest
+ {
+ ItemWithModsToAdd = [itemToReturn],
+ FoundInRaid = itemToReturn.Upd?.SpawnedInSession,
+ Callback = null,
+ UseSortingTable = false,
+ };
+
+ _inventoryHelper.AddItemToStash(sessionID, request, pmcData, output);
+ if (output.Warnings?.Count > 0)
+ {
+ // Adding to stash failed, drop out - dont remove item from hideout area slot
+ return output;
+ }
+
+ // Remove items from slot, locationIndex remains
+ var hideoutSlotIndex = hideoutArea.Slots.FindIndex((slot) => slot.LocationIndex == slotIndexToRemove);
+ hideoutArea.Slots[hideoutSlotIndex].Items = null;
+
+ return output;
}
- public ItemEventRouterResponse ToggleArea(PmcData pmcData, HideoutToggleAreaRequestData request, string sessionId)
+ public ItemEventRouterResponse ToggleArea(PmcData pmcData, HideoutToggleAreaRequestData request, string sessionID)
{
- throw new NotImplementedException();
+ var output = _eventOutputHolder.GetOutput(sessionID);
+
+ // Force a production update (occur before area is toggled as it could be generator and doing it after generator enabled would cause incorrect calculaton of production progress)
+ _hideoutHelper.UpdatePlayerHideout(sessionID);
+
+ var hideoutArea = pmcData.Hideout.Areas.FirstOrDefault((area) => area.Type == request.AreaType);
+ if (hideoutArea is null)
+ {
+ _logger.Error(_localisationService.GetText("hideout-unable_to_find_area", request.AreaType));
+ return _httpResponseUtil.AppendErrorToOutput(output);
+ }
+
+ hideoutArea.Active = request.Enabled;
+
+ return output;
}
- public ItemEventRouterResponse SingleProductionStart(PmcData pmcData, HideoutSingleProductionStartRequestData request, string sessionId)
+ public ItemEventRouterResponse SingleProductionStart(PmcData pmcData, HideoutSingleProductionStartRequestData body, string sessionID)
{
- throw new NotImplementedException();
+ // Start production
+ _hideoutHelper.RegisterProduction(pmcData, body, sessionID);
+
+ // Find the recipe of the production
+ var recipe = _databaseService
+ .GetHideout()
+ .Production.Recipes.FirstOrDefault(production => production.Id == body.RecipeId);
+
+ // Find the actual amount of items we need to remove because body can send weird data
+ var recipeRequirementsClone = _cloner.Clone(
+ recipe.Requirements.Where((r) => r.Type == "Item" || r.Type == "Tool")
+ );
+
+ List itemsToDelete = new List();
+ var output = _eventOutputHolder.GetOutput(sessionID);
+ itemsToDelete.AddRange(body.Tools);
+ itemsToDelete.AddRange(body.Items);
+
+ foreach (var itemToDelete in itemsToDelete)
+ {
+ var itemToCheck = pmcData.Inventory.Items.FirstOrDefault(i => i.Id == itemToDelete.Id);
+ var requirement = recipeRequirementsClone.FirstOrDefault(
+ requirement => requirement.TemplateId == itemToCheck.Template
+ );
+
+ // Handle tools not having a `count`, but always only requiring 1
+ var requiredCount = requirement.Count ?? 1;
+ if (requiredCount <= 0)
+ {
+ continue;
+ }
+
+ _inventoryHelper.RemoveItemByCount(pmcData, itemToDelete.Id, requiredCount, sessionID, output);
+
+ // Tools don't have a count
+ if (requirement.Type != "Tool")
+ {
+ requirement.Count -= (int)itemToDelete.Count;
+ }
+ }
+
+ return output;
}
- public ItemEventRouterResponse ScavCaseProductionStart(PmcData pmcData, HideoutScavCaseStartRequestData request, string sessionId)
+ public ItemEventRouterResponse ScavCaseProductionStart(PmcData pmcData, HideoutScavCaseStartRequestData body, string sessionID)
{
- throw new NotImplementedException();
+ var output = _eventOutputHolder.GetOutput(sessionID);
+
+ foreach (var requestedItem in body.Items)
+ {
+ var inventoryItem = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == requestedItem.Id);
+ if (inventoryItem is null)
+ {
+ _logger.Error(
+ _localisationService.GetText(
+ "hideout-unable_to_find_scavcase_requested_item_in_profile_inventory",
+ requestedItem.Id
+ )
+ );
+ return _httpResponseUtil.AppendErrorToOutput(output);
+ }
+
+ if (inventoryItem.Upd?.StackObjectsCount is not null && inventoryItem.Upd.StackObjectsCount > requestedItem.Count)
+ {
+ inventoryItem.Upd.StackObjectsCount -= requestedItem.Count;
+ }
+ else
+ {
+ _inventoryHelper.RemoveItem(pmcData, requestedItem.Id, sessionID, output);
+ }
+ }
+
+ var recipe = _databaseService.GetHideout().Production.ScavRecipes.FirstOrDefault(r => r.Id == body.RecipeId);
+ if (recipe is null)
+ {
+ _logger.Error(
+ _localisationService.GetText("hideout-unable_to_find_scav_case_recipie_in_database", body.RecipeId)
+ );
+
+ return _httpResponseUtil.AppendErrorToOutput(output);
+ }
+
+ // @Important: Here we need to be very exact:
+ // - normal recipe: Production time value is stored in attribute "productionTime" with small "p"
+ // - scav case recipe: Production time value is stored in attribute "ProductionTime" with capital "P"
+ var adjustedCraftTime =
+ recipe.ProductionTime -
+ _hideoutHelper.GetSkillProductionTimeReduction(
+ pmcData,
+ recipe.ProductionTime ?? 0,
+ SkillTypes.Crafting,
+ _databaseService.GetGlobals().Configuration.SkillsSettings.Crafting.CraftTimeReductionPerLevel ?? 0
+ );
+
+ var modifiedScavCaseTime = GetScavCaseTime(pmcData, adjustedCraftTime);
+
+ pmcData.Hideout.Production[body.RecipeId] = _hideoutHelper.InitProduction(
+ body.RecipeId,
+ (int)(_profileHelper.IsDeveloperAccount(sessionID) ? 40 : modifiedScavCaseTime),
+ false
+ );
+ pmcData.Hideout.Production[body.RecipeId].SptIsScavCase = true;
+
+ return output;
}
- public ItemEventRouterResponse ContinuousProductionStart(PmcData pmcData, HideoutContinuousProductionStartRequestData request, string sessionId)
+ private double? GetScavCaseTime(PmcData pmcData, double? productionTime)
{
- throw new NotImplementedException();
+ var fenceLevel = _fenceService.GetFenceInfo(pmcData);
+ if (fenceLevel is null)
+ {
+ return productionTime;
+ }
+
+ return productionTime * fenceLevel.ScavCaseTimeModifier;
}
- public ItemEventRouterResponse TakeProduction(PmcData pmcData, HideoutTakeProductionRequestData request, string sessionId)
+ public void AddScavCaseRewardsToProfile(PmcData pmcData, List rewards, string recipeId)
{
- throw new NotImplementedException();
+ pmcData.Hideout.Production[$"ScavCase{recipeId}"] = new Production { Products = rewards, RecipeId = recipeId };
+ }
+
+ public ItemEventRouterResponse ContinuousProductionStart(PmcData pmcData, HideoutContinuousProductionStartRequestData request, string sessionID)
+ {
+ _hideoutHelper.RegisterProduction(pmcData, request, sessionID);
+
+ return _eventOutputHolder.GetOutput(sessionID);
+ }
+
+ public ItemEventRouterResponse TakeProduction(PmcData pmcData, HideoutTakeProductionRequestData request, string sessionID)
+ {
+ var output = _eventOutputHolder.GetOutput(sessionID);
+ var hideoutDb = _databaseService.GetHideout();
+
+ if (request.RecipeId == HideoutHelper.BitcoinFarm)
+ {
+ // Ensure server and client are in-sync when player presses 'get items' on farm
+ _hideoutHelper.UpdatePlayerHideout(sessionID);
+ _hideoutHelper.GetBTC(pmcData, request, sessionID, output);
+
+ return output;
+ }
+
+ var recipe = hideoutDb.Production.Recipes.FirstOrDefault(r => r.Id == request.RecipeId);
+ if (recipe is not null)
+ {
+ HandleRecipe(sessionID, recipe, pmcData, request, output);
+
+ return output;
+ }
+
+ var scavCase = hideoutDb.Production.ScavRecipes.FirstOrDefault(r => r.Id == request.RecipeId);
+ if (scavCase is not null)
+ {
+ HandleScavCase(sessionID, pmcData, request, output);
+
+ return output;
+ }
+
+ _logger.Error(
+ _localisationService.GetText(
+ "hideout-unable_to_find_production_in_profile_by_recipie_id",
+ request.RecipeId
+ )
+ );
+
+ return _httpResponseUtil.AppendErrorToOutput(output);
+ }
+
+ private void HandleRecipe(string sessionID, HideoutProduction recipe, PmcData pmcData, HideoutTakeProductionRequestData request,
+ ItemEventRouterResponse output)
+ {
+ // Validate that we have a matching production
+ var productionDict = pmcData.Hideout.Production;
+ string? prodId = null;
+ foreach (var production in productionDict)
+ {
+ // Skip undefined production objects
+ if (production.Value is null)
+ {
+ continue;
+ }
+
+ if (_hideoutHelper.IsProductionType(production.Value))
+ {
+ // Production or ScavCase
+ if (production.Value.RecipeId == request.RecipeId)
+ {
+ prodId = production.Key; // Set to objects key
+ break;
+ }
+ }
+ }
+
+ // If we're unable to find the production, send an error to the client
+ if (prodId is null)
+ {
+ _logger.Error(
+ _localisationService.GetText(
+ "hideout-unable_to_find_production_in_profile_by_recipie_id",
+ request.RecipeId
+ )
+ );
+
+ _httpResponseUtil.AppendErrorToOutput(
+ output,
+ _localisationService.GetText(
+ "hideout-unable_to_find_production_in_profile_by_recipie_id",
+ request.RecipeId
+ )
+ );
+
+ return;
+ }
+
+ // Variables for managemnet of skill
+ var craftingExpAmount = 0;
+
+ var counterHoursCrafting = GetHoursCraftingTaskConditionCounter(pmcData, recipe);
+ var hoursCrafting = counterHoursCrafting.Value;
+
+ // Array of arrays of item + children
+ List> itemAndChildrenToSendToPlayer = [];
+
+ // Reward is weapon/armor preset, handle differently compared to 'normal' items
+ var rewardIsPreset = _presetHelper.HasPreset(recipe.EndProduct);
+ if (rewardIsPreset)
+ {
+ var defaultPreset = _presetHelper.GetDefaultPreset(recipe.EndProduct);
+
+ // Ensure preset has unique ids and is cloned so we don't alter the preset data stored in memory
+ List- presetAndMods = _itemHelper.ReplaceIDs(defaultPreset.Items);
+
+ _itemHelper.RemapRootItemId(presetAndMods);
+
+ // Store preset items in array
+ itemAndChildrenToSendToPlayer = [presetAndMods];
+ }
+
+ var rewardIsStackable = _itemHelper.IsItemTplStackable(recipe.EndProduct);
+ if (rewardIsStackable ?? false)
+ {
+ // Create root item
+ Item rewardToAdd = new Item
+ {
+ Id = _hashUtil.Generate(),
+ Template = recipe.EndProduct,
+ Upd = new Upd { StackObjectsCount = recipe.Count },
+ };
+
+ // Split item into separate items with acceptable stack sizes
+ var splitReward = _itemHelper.SplitStackIntoSeparateItems(rewardToAdd);
+ itemAndChildrenToSendToPlayer.AddRange(splitReward);
+ }
+ else
+ {
+ // Not stackable, may have to send send multiple of reward
+
+ // Add the first reward item to array when not a preset (first preset added above earlier)
+ if (!rewardIsPreset)
+ {
+ itemAndChildrenToSendToPlayer.Add([new Item { Id = _hashUtil.Generate(), Template = recipe.EndProduct }]);
+ }
+
+ // Add multiple of item if recipe requests it
+ // Start index at one so we ignore first item in array
+ var countOfItemsToReward = recipe.Count;
+ for (var index = 1; index < countOfItemsToReward; index++)
+ {
+ List
- itemAndMods = _itemHelper.ReplaceIDs(itemAndChildrenToSendToPlayer.FirstOrDefault());
+ itemAndChildrenToSendToPlayer.AddRange([itemAndMods]);
+ }
+ }
+
+ // Recipe has an `isEncoded` requirement for reward(s), Add `RecodableComponent` property
+ if (recipe.IsEncoded ?? false)
+ {
+ foreach (var reward in itemAndChildrenToSendToPlayer)
+ {
+ _itemHelper.AddUpdObjectToItem(reward.FirstOrDefault());
+
+ reward.FirstOrDefault().Upd.RecodableComponent = new UpdRecodableComponent { IsEncoded = true };
+ }
+ }
+
+ // Build an array of the tools that need to be returned to the player
+ List
> toolsToSendToPlayer = [];
+ var hideoutProduction = pmcData.Hideout.Production[prodId];
+ if (hideoutProduction.SptRequiredTools?.Count > 0)
+ {
+ foreach (var tool in hideoutProduction.SptRequiredTools)
+ {
+ toolsToSendToPlayer.AddRange([tool]);
+ }
+ }
+
+ // Check if the recipe is the same as the last one - get bonus when crafting same thing multiple times
+ var area = pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == recipe.AreaType);
+ if (area is not null && request.RecipeId != area.LastRecipe)
+ {
+ // 1 point per craft upon the end of production for alternating between 2 different crafting recipes in the same module
+ craftingExpAmount += _hideoutConfig.ExpCraftAmount; // Default is 10
+ }
+
+ // Update variable with time spent crafting item(s)
+ // 1 point per 8 hours of crafting
+ hoursCrafting += recipe.ProductionTime;
+ if (hoursCrafting / _hideoutConfig.HoursForSkillCrafting >= 1)
+ {
+ // Spent enough time crafting to get a bonus xp multipler
+ var multiplierCrafting = Math.Floor((double)hoursCrafting / _hideoutConfig.HoursForSkillCrafting);
+ craftingExpAmount += (int)(1 * multiplierCrafting);
+ hoursCrafting -= _hideoutConfig.HoursForSkillCrafting * multiplierCrafting;
+ }
+
+ // Make sure we can fit both the craft result and tools in the stash
+ var totalResultItems = new List>();
+ totalResultItems.AddRange(itemAndChildrenToSendToPlayer);
+ totalResultItems.AddRange(toolsToSendToPlayer);
+
+ if (!_inventoryHelper.CanPlaceItemsInInventory(sessionID, totalResultItems))
+ {
+ _httpResponseUtil.AppendErrorToOutput(
+ output,
+ _localisationService.GetText("inventory-no_stash_space"),
+ BackendErrorCodes.NotEnoughSpace
+ );
+ return;
+ }
+
+ // Add the tools to the stash, we have to do this individually due to FiR state potentially being different
+ foreach (var toolItem in toolsToSendToPlayer)
+ {
+ // Note: FIR state will be based on the first item's SpawnedInSession property per item group
+ AddItemsDirectRequest addToolsRequest = new AddItemsDirectRequest
+ {
+ ItemsWithModsToAdd = [toolItem],
+ FoundInRaid = toolItem[0].Upd?.SpawnedInSession ?? false,
+ UseSortingTable = false,
+ Callback = null,
+ };
+
+ _inventoryHelper.AddItemsToStash(sessionID, addToolsRequest, pmcData, output);
+ if (output.Warnings?.Count > 0)
+ {
+ return;
+ }
+ }
+
+ // Add the crafting result to the stash, marked as FiR
+ AddItemsDirectRequest addItemsRequest = new AddItemsDirectRequest
+ {
+ ItemsWithModsToAdd = itemAndChildrenToSendToPlayer,
+ FoundInRaid = true,
+ UseSortingTable = false,
+ Callback = null,
+ };
+ _inventoryHelper.AddItemsToStash(sessionID, addItemsRequest, pmcData, output);
+ if (output.Warnings?.Count > 0)
+ {
+ return;
+ }
+
+ // - increment skill point for crafting
+ // - delete the production in profile Hideout.Production
+ // Hideout Management skill
+ // ? use a configuration variable for the value?
+ var globals = _databaseService.GetGlobals();
+ _profileHelper.AddSkillPointsToPlayer(
+ pmcData,
+ SkillTypes.HideoutManagement,
+ globals.Configuration.SkillsSettings.HideoutManagement.SkillPointsPerCraft,
+ true
+ );
+
+ // Add Crafting skill to player profile
+ if (craftingExpAmount > 0)
+ {
+ _profileHelper.AddSkillPointsToPlayer(pmcData, SkillTypes.Crafting, craftingExpAmount);
+
+ var intellectAmountToGive = 0.5 * Math.Round((double)(craftingExpAmount / 15));
+ if (intellectAmountToGive > 0)
+ {
+ _profileHelper.AddSkillPointsToPlayer(pmcData, SkillTypes.Intellect, intellectAmountToGive);
+ }
+ }
+
+ area.LastRecipe = request.RecipeId;
+
+ // Update profiles hours crafting value
+ counterHoursCrafting.Value = hoursCrafting;
+
+ // Continuous crafts have special handling in EventOutputHolder.updateOutputProperties()
+ pmcData.Hideout.Production[prodId].SptIsComplete = true;
+ pmcData.Hideout.Production[prodId].SptIsContinuous = recipe.Continuous;
+
+ // Continious recipies need the craft time refreshed as it gets created once on initial craft and stays the same regardless of what
+ // production.json is set to
+ if (recipe.Continuous ?? false)
+ {
+ pmcData.Hideout.Production[prodId].ProductionTime = _hideoutHelper.GetAdjustedCraftTimeWithSkills(
+ pmcData,
+ recipe.Id,
+ true
+ );
+ }
+
+ // Flag normal (non continious) crafts as complete
+ if (!recipe.Continuous ?? false)
+ {
+ pmcData.Hideout.Production[prodId].InProgress = false;
+ }
+ }
+
+ private TaskConditionCounter GetHoursCraftingTaskConditionCounter(PmcData pmcData, HideoutProduction recipe)
+ {
+ var counterHoursCrafting = pmcData.TaskConditionCounters[HideoutController.NameTaskConditionCountersCraftingId];
+ if (counterHoursCrafting is null)
+ {
+ // Doesn't exist, create
+ pmcData.TaskConditionCounters[HideoutController.NameTaskConditionCountersCraftingId] = new TaskConditionCounter
+ {
+ Id = recipe.Id,
+ Type = HideoutController.NameTaskConditionCountersCraftingId,
+ SourceId = "CounterCrafting",
+ Value = 0,
+ };
+ counterHoursCrafting = pmcData.TaskConditionCounters[HideoutController.NameTaskConditionCountersCraftingId];
+ }
+
+ return counterHoursCrafting;
+ }
+
+ private void HandleScavCase(string sessionID, PmcData pmcData, HideoutTakeProductionRequestData request, ItemEventRouterResponse output)
+ {
+ var ongoingProductions = pmcData.Hideout.Production;
+ string? prodId = null;
+ foreach (var production in ongoingProductions)
+ {
+ if (_hideoutHelper.IsProductionType(production.Value))
+ {
+ // Production or ScavCase
+ if ((production.Value).RecipeId == request.RecipeId)
+ {
+ prodId = production.Key; // Set to objects key
+ break;
+ }
+ }
+ }
+
+ if (prodId == null)
+ {
+ _logger.Error(
+ _localisationService.GetText(
+ "hideout-unable_to_find_production_in_profile_by_recipie_id",
+ request.RecipeId
+ )
+ );
+
+ _httpResponseUtil.AppendErrorToOutput(output);
+
+ return;
+ }
+
+ // Create rewards for scav case
+ var scavCaseRewards = _scavCaseRewardGenerator.Generate(request.RecipeId);
+
+ AddItemsDirectRequest addItemsRequest = new AddItemsDirectRequest
+ {
+ ItemsWithModsToAdd = scavCaseRewards,
+ FoundInRaid = true,
+ Callback = null,
+ UseSortingTable = false,
+ };
+
+ _inventoryHelper.AddItemsToStash(sessionID, addItemsRequest, pmcData, output);
+ if (output.Warnings?.Count > 0)
+ {
+ return;
+ }
+
+ // Remove the old production from output object before its sent to client
+ output.ProfileChanges[sessionID].Production.Remove(request.RecipeId);
+
+ // Flag as complete - will be cleaned up later by hideoutController.update()
+ pmcData.Hideout.Production[prodId].SptIsComplete = true;
+
+ // Crafting complete, flag
+ pmcData.Hideout.Production[prodId].InProgress = false;
}
public void HandleQTEEventOutcome(string sessionId, PmcData pmcData, HandleQTEEventRequestData request, ItemEventRouterResponse output)
{
- throw new NotImplementedException();
+ // {
+ // "Action": "HideoutQuickTimeEvent",
+ // "results": [true, false, true, true, true, true, true, true, true, false, false, false, false, false, false],
+ // "id": "63b16feb5d012c402c01f6ef",
+ // "timestamp": 1672585349
+ // }
+
+ // Skill changes are done in
+ // /client/hideout/workout (applyWorkoutChanges).
+
+ var qteDb = _databaseService.GetHideout().Qte;
+ var relevantQte = qteDb.FirstOrDefault(qte => qte.Id == request.Id);
+ foreach (var outcome in request.Results)
+ {
+ if (outcome)
+ {
+ // Success
+ pmcData.Health.Energy.Current += relevantQte.Results[QteEffectType.singleSuccessEffect].Energy;
+ pmcData.Health.Hydration.Current += relevantQte.Results[QteEffectType.singleSuccessEffect].Hydration;
+ }
+ else
+ {
+ // Failed
+ pmcData.Health.Energy.Current += relevantQte.Results[QteEffectType.singleFailEffect].Energy;
+ pmcData.Health.Hydration.Current += relevantQte.Results[QteEffectType.singleFailEffect].Hydration;
+ }
+ }
+
+ if (pmcData.Health.Energy.Current < 1)
+ {
+ pmcData.Health.Energy.Current = 1;
+ }
+
+ if (pmcData.Health.Hydration.Current < 1)
+ {
+ pmcData.Health.Hydration.Current = 1;
+ }
+
+ HandleMusclePain(pmcData, relevantQte.Results[QteEffectType.finishEffect]);
+ }
+
+ private void HandleMusclePain(PmcData pmcData, QteResult finishEffect)
+ {
+ var hasMildPain = pmcData.Health.BodyParts["Chest"].Effects?["MildMusclePain"];
+ var hasSeverePain = pmcData.Health.BodyParts["Chest"].Effects?["SevereMusclePain"];
+
+ // Has no muscle pain at all, add mild
+ if (hasMildPain is null && hasSeverePain is null)
+ {
+ // nullguard
+ pmcData.Health.BodyParts["Chest"].Effects ??= new Dictionary();
+ pmcData.Health.BodyParts["Chest"].Effects["MildMusclePain"] = new BodyPartEffectProperties
+ {
+ Time = finishEffect.RewardEffects.FirstOrDefault().Time, // TODO - remove hard coded access, get value properly
+ };
+
+ return;
+ }
+
+ if (hasMildPain is not null)
+ {
+ // Already has mild pain, remove mild and add severe
+ pmcData.Health.BodyParts["Chest"].Effects.Remove("MildMusclePain");
+
+ pmcData.Health.BodyParts["Chest"].Effects["SevereMusclePain"] = new BodyPartEffectProperties
+ {
+ Time = finishEffect.RewardEffects.FirstOrDefault().Time,
+ };
+ }
}
public void RecordShootingRangePoints(string sessionId, PmcData pmcData, RecordShootingRangePoints request)
{
- throw new NotImplementedException();
+ var shootingRangeKey = "ShootingRangePoints";
+ var overallCounterItems = pmcData.Stats.Eft.OverallCounters.Items;
+
+ // Find counter by key
+ var shootingRangeHighScore = overallCounterItems.FirstOrDefault((counter) => counter.Key.Contains(shootingRangeKey));
+ if (shootingRangeHighScore is null)
+ {
+ // Counter not found, add blank one
+ overallCounterItems.Add(new CounterKeyValue { Key = [shootingRangeKey], Value = 0 });
+ shootingRangeHighScore = overallCounterItems.FirstOrDefault((counter) => counter.Key.Contains(shootingRangeKey));
+ }
+
+ shootingRangeHighScore.Value = request.Points;
}
public ItemEventRouterResponse ImproveArea(string sessionId, PmcData pmcData, HideoutImproveAreaRequestData request)
{
- throw new NotImplementedException();
+ var output = _eventOutputHolder.GetOutput(sessionId);
+
+ // Create mapping of required item with corrisponding item from player inventory
+ var items = request.Items.Select(
+ (reqItem) =>
+ {
+ var item = pmcData.Inventory.Items.FirstOrDefault(invItem => invItem.Id == reqItem.Id);
+ return new { inventoryItem = item, requestedItem = reqItem };
+ }
+ );
+
+ // If it's not money, its construction / barter items
+ foreach (var item in items)
+ {
+ if (item.inventoryItem is null)
+ {
+ _logger.Error(
+ _localisationService.GetText("hideout-unable_to_find_item_in_inventory", item.requestedItem.Id)
+ );
+ return _httpResponseUtil.AppendErrorToOutput(output);
+ }
+
+ if (
+ _paymentHelper.IsMoneyTpl(item.inventoryItem.Template) &&
+ item.inventoryItem.Upd is not null &&
+ item.inventoryItem.Upd.StackObjectsCount is not null &&
+ item.inventoryItem.Upd.StackObjectsCount > item.requestedItem.Count
+ )
+ {
+ item.inventoryItem.Upd.StackObjectsCount -= item.requestedItem.Count;
+ }
+ else
+ {
+ _inventoryHelper.RemoveItem(pmcData, item.inventoryItem.Id, sessionId, output);
+ }
+ }
+
+ var profileHideoutArea = pmcData.Hideout.Areas.FirstOrDefault(x => x.Type == request.AreaType);
+ if (profileHideoutArea is null)
+ {
+ _logger.Error(_localisationService.GetText("hideout-unable_to_find_area", request.AreaType));
+ return _httpResponseUtil.AppendErrorToOutput(output);
+ }
+
+ var hideoutDbData = _databaseService.GetHideout().Areas.FirstOrDefault((area) => area.Type == request.AreaType);
+ if (hideoutDbData is null)
+ {
+ _logger.Error(
+ _localisationService.GetText("hideout-unable_to_find_area_in_database", request.AreaType)
+ );
+ return _httpResponseUtil.AppendErrorToOutput(output);
+ }
+
+ // Add all improvemets to output object
+ var improvements = hideoutDbData.Stages[profileHideoutArea.Level.ToString()].Improvements;
+ var timestamp = _timeUtil.GetTimeStamp();
+
+ if (output.ProfileChanges[sessionId].Improvements is null)
+ {
+ output.ProfileChanges[sessionId].Improvements = new Dictionary();
+ }
+
+ foreach (var improvement in improvements)
+ {
+ var improvementDetails = new HideoutImprovement
+ {
+ Completed = false,
+ ImproveCompleteTimestamp = (long)(timestamp + improvement.ImprovementTime),
+ };
+ output.ProfileChanges[sessionId].Improvements[improvement.Id] = improvementDetails;
+
+ pmcData.Hideout.Improvements ??= new Dictionary();
+ pmcData.Hideout.Improvements[improvement.Id] = improvementDetails;
+ }
+
+ return output;
}
- public ItemEventRouterResponse CancelProduction(string sessionId, PmcData pmcData, HideoutImproveAreaRequestData request)
+ public ItemEventRouterResponse CancelProduction(string sessionId, PmcData pmcData, HideoutCancelProductionRequestData request)
{
- throw new NotImplementedException();
+ var output = _eventOutputHolder.GetOutput(sessionId);
+
+ var craftToCancel = pmcData.Hideout.Production[request.RecipeId];
+ if (craftToCancel is null)
+ {
+ var errorMessage = $"Unable to find craft {request.RecipeId} to cancel";
+ _logger.Error(errorMessage);
+
+ return _httpResponseUtil.AppendErrorToOutput(output, errorMessage);
+ }
+
+ // Null out production data so client gets informed when response send back
+ pmcData.Hideout.Production[request.RecipeId] = null;
+
+ // TODO - handle timestamp somehow?
+
+ return output;
}
public ItemEventRouterResponse CicleOfCultistProductionStart(string sessionId, PmcData pmcData, HideoutCircleOfCultistProductionStartRequestData request)
{
- throw new NotImplementedException();
+ return _circleOfCultistService.StartSacrifice(sessionId, pmcData, request);
}
public ItemEventRouterResponse HideoutDeleteProductionCommand(string sessionId, PmcData pmcData, HideoutDeleteProductionRequestData request)
{
- throw new NotImplementedException();
+ var output = _eventOutputHolder.GetOutput(sessionId);
+
+ pmcData.Hideout.Production[request.RecipeId] = null;
+ output.ProfileChanges[sessionId].Production = null;
+
+ return output;
}
public ItemEventRouterResponse HideoutCustomizationApply(string sessionId, PmcData pmcData, HideoutCustomizationApplyRequestData request)
{
- throw new NotImplementedException();
+ var output = _eventOutputHolder.GetOutput(sessionId);
+
+ var itemDetails = _databaseService
+ .GetHideout()
+ .Customisation.Globals.FirstOrDefault((cust) => cust.Id == request.OfferId);
+ if (itemDetails is null)
+ {
+ _logger.Error($"Unable to find customisation: {request.OfferId} in db, cannot apply to hideout");
+
+ return output;
+ }
+
+ // pmcData.Hideout.Customization[GetHideoutCustomisationType(itemDetails.Type)];
+ // this is in the Node server, doesnt do anything
+
+ return output;
}
+ private string? GetHideoutCustomisationType(string? type)
+ {
+ switch (type)
+ {
+ case "wall":
+ return "Wall";
+ case "floor":
+ return "Floor";
+ case "light":
+ return "Light";
+ case "ceiling":
+ return "Ceiling";
+ case "shootingRangeMark":
+ return "ShootingRangeMark";
+ default:
+ _logger.Warning($"Unknown {type}, unable to map");
+ return type;
+ }
+ }
+
+ private void AddMissingPresetStandItemsToProfile(string sessionId, Stage equipmentPresetStage, PmcData pmcData, HideoutArea equipmentPresetHideoutArea,
+ ItemEventRouterResponse output)
+ {
+ // Each slot is a single Mannequin
+ var slots = _itemHelper.GetItem(equipmentPresetStage.Container).Value.Properties.Slots;
+ foreach (var mannequinSlot in slots)
+ {
+ // Chek if we've already added this manniquin
+ var existingMannequin = pmcData.Inventory.Items.FirstOrDefault(
+ (item) => item.ParentId == equipmentPresetHideoutArea.Id && item.SlotId == mannequinSlot.Name
+ );
+
+ // No child, add it
+ if (existingMannequin is null)
+ {
+ var standId = _hashUtil.Generate();
+ var mannequinToAdd = new Product
+ {
+ Id = standId,
+ Template = ItemTpl.INVENTORY_DEFAULT,
+ ParentId = equipmentPresetHideoutArea.Id,
+ SlotId = mannequinSlot.Name,
+ };
+ pmcData.Inventory.Items.Add(mannequinToAdd);
+
+ // Add pocket child item
+ var mannequinPocketItemToAdd = new Product
+ {
+ Id = _hashUtil.Generate(),
+ Template = pmcData.Inventory.Items.FirstOrDefault(
+ item => item.SlotId == "Pockets" && item.ParentId == pmcData.Inventory.Equipment
+ )
+ .Template, // Same pocket tpl as players profile (unheard get bigger, matching pockets etc)
+ ParentId = standId,
+ SlotId = "Pockets",
+ };
+ pmcData.Inventory.Items.Add(mannequinPocketItemToAdd);
+ output.ProfileChanges[sessionId].Items.NewItems.Add(mannequinToAdd);
+ output.ProfileChanges[sessionId].Items.NewItems.Add(mannequinPocketItemToAdd);
+ }
+ }
+ }
///
/// Handle HideoutCustomizationSetMannequinPose event
diff --git a/Libraries/Core/Helpers/HideoutHelper.cs b/Libraries/Core/Helpers/HideoutHelper.cs
index e20703af..5737ede2 100644
--- a/Libraries/Core/Helpers/HideoutHelper.cs
+++ b/Libraries/Core/Helpers/HideoutHelper.cs
@@ -17,6 +17,12 @@ public class HideoutHelper(
LocalisationService _localisationService
)
{
+ public const string BitcoinFarm = "5d5c205bd582a50d042a3c0e";
+ public const string CultistCircleCraftId = "66827062405f392b203a44cf";
+ public const string BitcoinProductionId = "5d5c205bd582a50d042a3c0e";
+ public const string WaterCollector = "5d5589c1f934db045e6c5492";
+ public const int MaxSkillPoint = 5000;
+
///
/// Add production to profiles' Hideout.Production array
///
@@ -31,12 +37,27 @@ public class HideoutHelper(
{
throw new NotImplementedException();
}
+
+ ///
+ /// Add production to profiles' Hideout.Production array
+ ///
+ /// Profile to add production to
+ /// Production request
+ /// Session id
+ /// client response
+ public void RegisterProduction(
+ PmcData profileData,
+ HideoutContinuousProductionStartRequestData productionRequest,
+ string sessionId)
+ {
+ throw new NotImplementedException();
+ }
///
/// This convenience function initializes new Production Object
/// with all the constants.
///
- public void InitProduction(
+ public Production InitProduction(
string recipeId,
int productionTime,
bool needFuelForAllProductionTime)
diff --git a/Libraries/Core/Models/Eft/Common/Tables/BotBase.cs b/Libraries/Core/Models/Eft/Common/Tables/BotBase.cs
index 3b670cf1..da150253 100644
--- a/Libraries/Core/Models/Eft/Common/Tables/BotBase.cs
+++ b/Libraries/Core/Models/Eft/Common/Tables/BotBase.cs
@@ -536,7 +536,7 @@ public record InsuredItem
public record Hideout
{
- public Dictionary? Production { get; set; }
+ public Dictionary? Production { get; set; }
public List? Areas { get; set; }
public Dictionary? Improvements { get; set; }
diff --git a/Libraries/Core/Models/Eft/Hideout/HideoutCancelProductionRequestData.cs b/Libraries/Core/Models/Eft/Hideout/HideoutCancelProductionRequestData.cs
new file mode 100644
index 00000000..967efc4a
--- /dev/null
+++ b/Libraries/Core/Models/Eft/Hideout/HideoutCancelProductionRequestData.cs
@@ -0,0 +1,13 @@
+using System.Text.Json.Serialization;
+using Core.Models.Eft.Common.Request;
+
+namespace Core.Models.Eft.Hideout;
+
+public record HideoutCancelProductionRequestData : BaseInteractionRequestData
+{
+ [JsonPropertyName("recipeId")]
+ public string? RecipeId { get; set; }
+
+ [JsonPropertyName("timestamp")]
+ public long? Timestamp { get; set; }
+}
diff --git a/Libraries/Core/Models/Eft/Hideout/HideoutImproveAreaRequestData.cs b/Libraries/Core/Models/Eft/Hideout/HideoutImproveAreaRequestData.cs
index 9044f94c..7724c3c4 100644
--- a/Libraries/Core/Models/Eft/Hideout/HideoutImproveAreaRequestData.cs
+++ b/Libraries/Core/Models/Eft/Hideout/HideoutImproveAreaRequestData.cs
@@ -1,6 +1,7 @@
using System.Text.Json.Serialization;
using Core.Models.Eft.Common.Request;
using Core.Models.Eft.Common.Tables;
+using Core.Models.Enums;
namespace Core.Models.Eft.Hideout;
@@ -11,7 +12,7 @@ public record HideoutImproveAreaRequestData : BaseInteractionRequestData
public string? AreaId { get; set; }
[JsonPropertyName("areaType")]
- public int? AreaType { get; set; }
+ public HideoutAreas? AreaType { get; set; }
[JsonPropertyName("items")]
public List? Items { get; set; }
diff --git a/Libraries/Core/Models/Eft/Hideout/HideoutProduction.cs b/Libraries/Core/Models/Eft/Hideout/HideoutProduction.cs
index 885eebf6..2a8c4478 100644
--- a/Libraries/Core/Models/Eft/Hideout/HideoutProduction.cs
+++ b/Libraries/Core/Models/Eft/Hideout/HideoutProduction.cs
@@ -1,5 +1,6 @@
using System.Text.Json.Serialization;
using Core.Models.Common;
+using Core.Models.Enums;
namespace Core.Models.Eft.Hideout;
@@ -21,7 +22,7 @@ public record HideoutProduction
public string? Id { get; set; }
[JsonPropertyName("areaType")]
- public int? AreaType { get; set; }
+ public HideoutAreas? AreaType { get; set; }
[JsonPropertyName("requirements")]
public List? Requirements { get; set; }
diff --git a/Libraries/Core/Models/Eft/Hideout/HideoutPutItemInRequestData.cs b/Libraries/Core/Models/Eft/Hideout/HideoutPutItemInRequestData.cs
index 43b822eb..be3e971f 100644
--- a/Libraries/Core/Models/Eft/Hideout/HideoutPutItemInRequestData.cs
+++ b/Libraries/Core/Models/Eft/Hideout/HideoutPutItemInRequestData.cs
@@ -1,6 +1,7 @@
using System.Text.Json.Serialization;
using Core.Models.Common;
using Core.Models.Eft.Common.Request;
+using Core.Models.Enums;
namespace Core.Models.Eft.Hideout;
@@ -8,7 +9,7 @@ public record HideoutPutItemInRequestData : BaseInteractionRequestData
{
[JsonPropertyName("areaType")]
- public int? AreaType { get; set; }
+ public HideoutAreas? AreaType { get; set; }
[JsonPropertyName("items")]
public Dictionary? Items { get; set; }
diff --git a/Libraries/Core/Models/Eft/Hideout/HideoutTakeItemOutRequestData.cs b/Libraries/Core/Models/Eft/Hideout/HideoutTakeItemOutRequestData.cs
index c54c826a..0b534670 100644
--- a/Libraries/Core/Models/Eft/Hideout/HideoutTakeItemOutRequestData.cs
+++ b/Libraries/Core/Models/Eft/Hideout/HideoutTakeItemOutRequestData.cs
@@ -1,12 +1,13 @@
using System.Text.Json.Serialization;
using Core.Models.Eft.Common.Request;
+using Core.Models.Enums;
namespace Core.Models.Eft.Hideout;
public record HideoutTakeItemOutRequestData : BaseInteractionRequestData
{
[JsonPropertyName("areaType")]
- public int? AreaType { get; set; }
+ public HideoutAreas? AreaType { get; set; }
[JsonPropertyName("slots")]
public List? Slots { get; set; }
diff --git a/Libraries/Core/Models/Eft/Hideout/HideoutToggleAreaRequestData.cs b/Libraries/Core/Models/Eft/Hideout/HideoutToggleAreaRequestData.cs
index 837b619b..3d17c0bf 100644
--- a/Libraries/Core/Models/Eft/Hideout/HideoutToggleAreaRequestData.cs
+++ b/Libraries/Core/Models/Eft/Hideout/HideoutToggleAreaRequestData.cs
@@ -1,12 +1,13 @@
using System.Text.Json.Serialization;
using Core.Models.Eft.Common.Request;
+using Core.Models.Enums;
namespace Core.Models.Eft.Hideout;
public record HideoutToggleAreaRequestData : BaseInteractionRequestData
{
[JsonPropertyName("areaType")]
- public int? AreaType { get; set; }
+ public HideoutAreas? AreaType { get; set; }
[JsonPropertyName("enabled")]
public bool? Enabled { get; set; }
diff --git a/Libraries/Core/Models/Eft/Hideout/QteData.cs b/Libraries/Core/Models/Eft/Hideout/QteData.cs
index 7a98290f..4edec65e 100644
--- a/Libraries/Core/Models/Eft/Hideout/QteData.cs
+++ b/Libraries/Core/Models/Eft/Hideout/QteData.cs
@@ -89,24 +89,24 @@ public record QteEffect
{
[JsonPropertyName("type")]
[JsonConverter(typeof(JsonStringEnumConverter))]
- public QteRewardType? EffectType { get; set; }
+ public QteRewardType? Type { get; set; }
[JsonPropertyName("skillId")]
[JsonConverter(typeof(JsonStringEnumConverter))]
- public SkillTypes? SkillIdentifier { get; set; }
+ public SkillTypes? SkillId { get; set; }
[JsonPropertyName("levelMultipliers")]
public List? LevelMultipliers { get; set; }
[JsonPropertyName("time")]
- public int? DurationInMilliseconds { get; set; }
+ public int? Time { get; set; }
[JsonPropertyName("weight")]
- public float? EffectWeight { get; set; }
+ public float? Weight { get; set; }
[JsonPropertyName("result")]
[JsonConverter(typeof(JsonStringEnumConverter))]
- public QteResultType? ResultType { get; set; }
+ public QteResultType? Result { get; set; }
}
public record SkillLevelMultiplier
diff --git a/Libraries/Core/Models/Eft/Inventory/AddItemDirectRequest.cs b/Libraries/Core/Models/Eft/Inventory/AddItemDirectRequest.cs
index 6f37f98e..5bda3ae0 100644
--- a/Libraries/Core/Models/Eft/Inventory/AddItemDirectRequest.cs
+++ b/Libraries/Core/Models/Eft/Inventory/AddItemDirectRequest.cs
@@ -9,7 +9,7 @@ public record AddItemDirectRequest
/// Item and child mods to add to player inventory
///
[JsonPropertyName("itemWithModsToAdd")]
- public List- ? ItemWithModsToAdd { get; set; }
+ public List? ItemWithModsToAdd { get; set; }
[JsonPropertyName("foundInRaid")]
public bool? FoundInRaid { get; set; }
diff --git a/Libraries/Core/Models/Eft/ItemEvent/ItemEventRouterBase.cs b/Libraries/Core/Models/Eft/ItemEvent/ItemEventRouterBase.cs
index b8ff7d7b..1be839ac 100644
--- a/Libraries/Core/Models/Eft/ItemEvent/ItemEventRouterBase.cs
+++ b/Libraries/Core/Models/Eft/ItemEvent/ItemEventRouterBase.cs
@@ -164,26 +164,6 @@ public record TraderData
public bool? Disabled { get; set; }
}
-public record Product
+public record Product : Item
{
- [JsonPropertyName("_id")]
- public string? Id { get; set; }
-
- ///
- /// _tpl
- ///
- [JsonPropertyName("_tpl")]
- public string? Template { get; set; }
-
- [JsonPropertyName("parentId")]
- public string? ParentId { get; set; }
-
- [JsonPropertyName("slotId")]
- public string? SlotId { get; set; }
-
- [JsonPropertyName("location")]
- public ItemLocation? Location { get; set; }
-
- [JsonPropertyName("upd")]
- public Upd? Upd { get; set; }
}