using System.Text.Json; using System.Text.Json.Serialization; using Prospect.Server.Api.Models.Data; using Prospect.Server.Api.Services.Auth.Extensions; using Prospect.Server.Api.Services.CloudScript.Models.Data; using Prospect.Server.Api.Services.UserData; namespace Prospect.Server.Api.Services.CloudScript.Functions; public class FYStartTechTreeNodeUpgradeClientRequest { [JsonPropertyName("nodeId")] public string NodeID { get; set; } } public class FYStartTechTreeNodeUpgradeClientResult { [JsonPropertyName("userId")] public string UserID { get; set; } [JsonPropertyName("error")] public string Error { get; set; } [JsonPropertyName("upgradedNode")] public FYTechTreeNodeStatus UpgradedNode { get; set; } [JsonPropertyName("remainingTimeInSeconds")] public int RemainingTimeInSeconds { get; set; } [JsonPropertyName("changedCurrencies")] public FYCurrencyItem[] ChangedCurrencies { get; set; } [JsonPropertyName("changedItems")] public List ChangedItems { get; set; } [JsonPropertyName("deletedItems")] public HashSet DeletedItemsIds { get; set; } } [CloudScriptFunction("StartTechTreeNodeUpgradeClient")] public class StartTechTreeNodeUpgradeClient : ICloudScriptFunction { private readonly IHttpContextAccessor _httpContextAccessor; private readonly UserDataService _userDataService; private readonly TitleDataService _titleDataService; public StartTechTreeNodeUpgradeClient(IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService) { _httpContextAccessor = httpContextAccessor; _userDataService = userDataService; _titleDataService = titleDataService; } public async Task ExecuteAsync(FYStartTechTreeNodeUpgradeClientRequest request) { var context = _httpContextAccessor.HttpContext; if (context == null) { throw new CloudScriptException("CloudScript was not called within a http request"); } var userId = context.User.FindAuthUserId(); var userData = await _userDataService.FindAsync( userId, userId, new List{"TechTreeNodeData", "Balance", "Inventory", "PlayerQuartersLevel"} ); var userTechTree = JsonSerializer.Deserialize(userData["TechTreeNodeData"].Value); var currentNode = userTechTree.NodeInProgress; if (currentNode != "") { return new FYStartTechTreeNodeUpgradeClientResult { UserID = userId, Error = $"{currentNode} is in progress", }; } var balance = JsonSerializer.Deserialize(userData["Balance"].Value); var inventory = JsonSerializer.Deserialize>(userData["Inventory"].Value); var titleData = _titleDataService.Find(new List{"TechTreeNodes"}); var techTreeNodes = JsonSerializer.Deserialize>(titleData["TechTreeNodes"]); // Current level contains the information about next level var currentLevel = userTechTree.Nodes[request.NodeID].Level; var techTreeNodeData = techTreeNodes[request.NodeID]; var nextTreeNodeLevel = techTreeNodeData.PerkLevels[currentLevel]; // Tech tree node levels start from 0 // Dependency check: the player's quarters must be high enough to unlock this node. var playerQuarters = JsonSerializer.Deserialize(userData["PlayerQuartersLevel"].Value); if (playerQuarters != null && playerQuarters.Level < techTreeNodeData.PlayerQuarterLevelRequired) { return new FYStartTechTreeNodeUpgradeClientResult { UserID = userId, Error = $"Requires player quarters level {techTreeNodeData.PlayerQuarterLevelRequired}", }; } // Validate price // TODO: Refactor List changedItems = []; HashSet deletedItemsIds = []; List changedCurrencies = []; foreach (var ingredient in nextTreeNodeLevel.UpgradeCosts) { int remaining = ingredient.Amount; if (ingredient.Currency == "SoftCurrency") { remaining -= ingredient.Amount; balance.SoftCurrency -= ingredient.Amount; changedCurrencies.Add(new FYCurrencyItem { CurrencyName = "SoftCurrency", Amount = balance.SoftCurrency }); } else if (ingredient.Currency == "Aurum") { remaining -= ingredient.Amount; balance.HardCurrency -= ingredient.Amount; changedCurrencies.Add(new FYCurrencyItem { CurrencyName = "Aurum", Amount = balance.HardCurrency }); } else { foreach (var item in inventory) { if (item.BaseItemId != ingredient.Currency) { continue; } var localRemaining = remaining; remaining -= item.Amount; item.Amount -= localRemaining; if (item.Amount <= 0) { item.Amount = 0; deletedItemsIds.Add(item.ItemId); } else { changedItems.Add(item); } if (remaining <= 0) { break; } } } if (remaining > 0) { return new FYStartTechTreeNodeUpgradeClientResult { UserID = userId, Error = "Missing required items", }; } } // TODO: Optimize var newInventory = new List(inventory.Count); foreach (var item in inventory) { if (!deletedItemsIds.Contains(item.ItemId)) { newInventory.Add(item); } } var startTime = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); userTechTree.NodeInProgress = request.NodeID; var upgradeStartedTime = new FYTimestamp { Seconds = startTime }; var upgradedNode = new FYTechTreeNodeStatus { Level = currentLevel, NodeID = request.NodeID, UpgradeStartedTime = upgradeStartedTime, }; userTechTree.Nodes[request.NodeID] = upgradedNode; await _userDataService.UpdateAsync( userId, userId, new Dictionary{ ["Balance"] = JsonSerializer.Serialize(balance), ["Inventory"] = JsonSerializer.Serialize(newInventory), ["TechTreeNodeData"] = JsonSerializer.Serialize(userTechTree), } ); return new FYStartTechTreeNodeUpgradeClientResult { UserID = userId, Error = "", UpgradedNode = upgradedNode, RemainingTimeInSeconds = nextTreeNodeLevel.UpgradeSeconds, ChangedItems = changedItems, DeletedItemsIds = deletedItemsIds, ChangedCurrencies = changedCurrencies.ToArray(), }; } }