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 FYClaimGeneratorIncomeClientResult { [JsonPropertyName("userId")] public string UserId { get; set; } [JsonPropertyName("error")] public string Error { get; set; } [JsonPropertyName("generatorId")] public string GeneratorID { get; set; } [JsonPropertyName("changedCurrenciesBalances")] public FYCurrencyItem[] ChangedCurrenciesBalances { get; set; } [JsonPropertyName("grantedItems")] public List GrantedItems { get; set; } [JsonPropertyName("lastClaimTime")] public FYTimestamp LastClaimTime { get; set; } }; public class FYClaimGeneratorIncomeClientRequest { [JsonPropertyName("generatorId")] public string GeneratorID { get; set; } }; public class FYPassiveGenerator { [JsonPropertyName("generatorId")] public string GeneratorID { get; set; } [JsonPropertyName("lastClaimTime")] public FYTimestamp LastClaimTime { get; set; } } [CloudScriptFunction("ClaimGeneratorIncomeClient")] public class ClaimGeneratorIncomeClient : ICloudScriptFunction { private readonly IHttpContextAccessor _httpContextAccessor; private readonly UserDataService _userDataService; private readonly TitleDataService _titleDataService; static readonly Random random = new(); private TitleDataDailyCrateInfo[] PickItems(TitleDataDailyCrateInfo[] items, int numPicks) { var itemList = new (TitleDataDailyCrateInfo item, float cumulativeWeight)[items.Length]; float totalWeight = 0; for (int i = 0; i < items.Length; i++) { var item = items[i]; totalWeight += item.Weight; itemList[i] = (item, totalWeight); } var selectedItems = new TitleDataDailyCrateInfo[numPicks]; for (int i = 0; i < numPicks; i++) { float pick = (float)random.NextDouble() * totalWeight; int left = 0, right = itemList.Length - 1; while (left < right) { int mid = (left + right) / 2; if (itemList[mid].cumulativeWeight < pick) { left = mid + 1; } else { right = mid; } } selectedItems[i] = itemList[left].item; } return selectedItems; } public ClaimGeneratorIncomeClient(IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService) { _httpContextAccessor = httpContextAccessor; _userDataService = userDataService; _titleDataService = titleDataService; } public async Task ExecuteAsync(FYClaimGeneratorIncomeClientRequest 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{"Generators__2021_09_09", "CharacterTechTreeBonuses", "Balance", "Inventory"} ); var userGenerators = JsonSerializer.Deserialize(userData["Generators__2021_09_09"].Value); var generator = Array.Find(userGenerators, item => item.GeneratorID == request.GeneratorID); if (generator == null) { return new FYClaimGeneratorIncomeClientResult { UserId = userId, Error = "Invalid generator ID", }; } var balance = JsonSerializer.Deserialize(userData["Balance"].Value); var inventory = JsonSerializer.Deserialize>(userData["Inventory"].Value); var techTreeBonuses = JsonSerializer.Deserialize(userData["CharacterTechTreeBonuses"].Value); var titleData = _titleDataService.Find(new List{"PassiveGenerators", "DailyCrateRewardsPools", "Blueprints"}); var passiveGenerators = JsonSerializer.Deserialize>(titleData["PassiveGenerators"]); var now = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); FYCurrencyItem[] changedCurrencies = []; List grantedItems = []; switch (generator.GeneratorID) { case "playerquarters_gen_kmarks": { var rate = techTreeBonuses.KmarksRate; if (rate == 0) { return new FYClaimGeneratorIncomeClientResult { UserId = userId, Error = "K-Marks Generator Upgrade is not constructed", }; } var generatorData = passiveGenerators[generator.GeneratorID]; var hours = (now - generator.LastClaimTime.Seconds) / (60 * generatorData.BaseGenIntervalMinutes); var cap = techTreeBonuses.KmarksCap + generatorData.BaseCap; var claimableAmount = Math.Min(hours * rate, cap); if (claimableAmount == 0) { return new FYClaimGeneratorIncomeClientResult { UserId = userId, Error = "Generator is not ready yet", }; } balance.SoftCurrency += claimableAmount; changedCurrencies = [new FYCurrencyItem { CurrencyName = "SoftCurrency", Amount = balance.SoftCurrency }]; break; } case "playerquarters_gen_aurum": { var rate = techTreeBonuses.AurumRate; if (rate == 0) { return new FYClaimGeneratorIncomeClientResult { UserId = userId, Error = "Aurum Generator Upgrade is not constructed", }; } var generatorData = passiveGenerators[generator.GeneratorID]; var hours = (now - generator.LastClaimTime.Seconds) / (60 * generatorData.BaseGenIntervalMinutes); var cap = techTreeBonuses.AurumCap + generatorData.BaseCap; var claimableAmount = (int)Math.Min(hours * rate, cap); if (claimableAmount == 0) { return new FYClaimGeneratorIncomeClientResult { UserId = userId, Error = "Generator is not ready yet", }; } balance.HardCurrency += claimableAmount; changedCurrencies = [new FYCurrencyItem { CurrencyName = "Aurum", Amount = balance.HardCurrency }]; break; } case "playerquarters_gen_crate": { var crateTier = techTreeBonuses.CrateTier; if (crateTier == 0) { return new FYClaimGeneratorIncomeClientResult { UserId = userId, Error = "Crate Generator Upgrade is not constructed", }; } var generatorData = passiveGenerators[generator.GeneratorID]; var nextClaimTime = generator.LastClaimTime.Seconds + 60 * generatorData.BaseGenIntervalMinutes; if (now < nextClaimTime) { return new FYClaimGeneratorIncomeClientResult { UserId = userId, Error = "Generator is not ready yet", }; } var dailyCrateRewards = JsonSerializer.Deserialize(titleData["DailyCrateRewardsPools"]); var blueprints = JsonSerializer.Deserialize>(titleData["Blueprints"]); var rewardsPool = dailyCrateRewards[crateTier - 1]; // TODO: Get rewardGrants from PassiveGenerators_CrateRewards_DT var items = PickItems(rewardsPool, 5); // TODO: Stackable? GrantedItems doesn't imply granted OR updated. foreach (var item in items) { var itemInfo = blueprints[item.Name]; var grantedItem = new FYCustomItemInfo { ItemId = Guid.NewGuid().ToString(), Amount = item.Amount, BaseItemId = item.Name, Durability = itemInfo.DurabilityMax, Insurance = "", InsuranceOwnerPlayfabId = "", ModData = new FYModItems { M = [] }, Origin = new FYItemOriginBackend { G = "", P = "", T = "" }, InsuredAttachmentId = "", PrimaryVanityId = 0, SecondaryVanityId = 0, RolledPerks = [], }; inventory.Add(grantedItem); grantedItems.Add(grantedItem); } break; } default: { return new FYClaimGeneratorIncomeClientResult { UserId = userId, Error = $"Unknown generator ID: {request.GeneratorID}", }; } } generator.LastClaimTime.Seconds = now; await _userDataService.UpdateAsync( userId, userId, new Dictionary{ ["Inventory"] = JsonSerializer.Serialize(inventory), ["Balance"] = JsonSerializer.Serialize(balance), ["Generators__2021_09_09"] = JsonSerializer.Serialize(userGenerators), } ); return new FYClaimGeneratorIncomeClientResult { UserId = userId, Error = "", GeneratorID = request.GeneratorID, ChangedCurrenciesBalances = changedCurrencies, GrantedItems = grantedItems, LastClaimTime = generator.LastClaimTime, }; } }