347 lines
14 KiB
C#
347 lines
14 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using Prospect.Server.Api.Models.Client.Data;
|
|
using Prospect.Server.Api.Models.Data;
|
|
using Prospect.Server.Api.Services.CloudScript.Functions;
|
|
using Prospect.Server.Api.Services.CloudScript.Models.Data;
|
|
using Prospect.Server.Api.Services.Database;
|
|
|
|
namespace Prospect.Server.Api.Services.UserData;
|
|
|
|
public class UserTechTreeNodeData {
|
|
[JsonPropertyName("nodeInProgress")]
|
|
public string NodeInProgress { get; set; }
|
|
[JsonPropertyName("totalUpgrades")]
|
|
public int TotalUpgrades { get; set; }
|
|
[JsonPropertyName("nodes")]
|
|
public Dictionary<string, FYTechTreeNodeStatus> Nodes { get; set; }
|
|
}
|
|
|
|
public class CharacterTechTreeBonuses {
|
|
[JsonPropertyName("aurumCap")]
|
|
public int AurumCap { get; set; }
|
|
[JsonPropertyName("aurumRate")]
|
|
public float AurumRate { get; set; }
|
|
[JsonPropertyName("kmarksCap")]
|
|
public int KmarksCap { get; set; }
|
|
[JsonPropertyName("kmarksRate")]
|
|
public int KmarksRate { get; set; }
|
|
[JsonPropertyName("crateTier")]
|
|
public int CrateTier { get; set; }
|
|
[JsonPropertyName("addStashSize")]
|
|
public int AddStashSize { get; set; }
|
|
[JsonPropertyName("addSafePocketSize")]
|
|
public int AddSafePocketSize { get; set; }
|
|
[JsonPropertyName("upgradeSpeedFactor")]
|
|
public float UpgradeSpeedFactor { get; set; }
|
|
}
|
|
|
|
public class UserDataService
|
|
{
|
|
private readonly ILogger<UserDataService> _logger;
|
|
private readonly DbUserDataService _dbUserDataService;
|
|
private readonly TitleDataService _titleDataService;
|
|
|
|
public UserDataService(ILogger<UserDataService> logger, DbUserDataService dbUserDataService, TitleDataService titleDataService)
|
|
{
|
|
_logger = logger;
|
|
_dbUserDataService = dbUserDataService;
|
|
_titleDataService = titleDataService;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize data for the given PlayFabId.
|
|
/// </summary>
|
|
public async Task InitAsync(string playFabId)
|
|
{
|
|
// TODO: Do not call this on each InitAsync
|
|
var titleData = _titleDataService.Find(new List<string>{"TechTreeNodes", "Blueprints"});
|
|
var techTreeNodes = JsonSerializer.Deserialize<Dictionary<string, TitleDataTechTreeNodeInfo>>(titleData["TechTreeNodes"]);
|
|
|
|
UserTechTreeNodeData userTechTreeNodesData = new UserTechTreeNodeData {
|
|
NodeInProgress = "",
|
|
TotalUpgrades = 0,
|
|
Nodes = [],
|
|
};
|
|
foreach (var nodeData in techTreeNodes) {
|
|
userTechTreeNodesData.Nodes[nodeData.Key] = new FYTechTreeNodeStatus {
|
|
NodeID = nodeData.Key,
|
|
Level = 0,
|
|
UpgradeStartedTime = new CloudScript.Models.Data.FYTimestamp {
|
|
Seconds = 0,
|
|
}
|
|
};
|
|
}
|
|
|
|
var defaultVanity = new FYCharacterVanity {
|
|
UserID = playFabId,
|
|
ArchetypeID = "E01_G02",
|
|
SlotIndex = 0,
|
|
BaseSuitItem = new FYVanityMaterialItem {
|
|
ID = "StarterOutfit01M_BaseSuit",
|
|
MaterialIndex = 0,
|
|
},
|
|
HeadItem = new FYVanityMaterialItem {
|
|
ID = "E01_G02_Head01",
|
|
MaterialIndex = 0,
|
|
},
|
|
MeleeWeaponItem = new FYVanityMaterialItem {
|
|
ID = "Melee_Omega",
|
|
MaterialIndex = 0,
|
|
},
|
|
BodyType = 1,
|
|
};
|
|
#if SEASON_2_RELEASE || SEASON_2_DEBUG
|
|
defaultVanity.BootsItem = new FYVanityMaterialItem {
|
|
ID = "StarterOutfit01_Boots_M",
|
|
MaterialIndex = 0,
|
|
};
|
|
defaultVanity.ChestItem = new FYVanityMaterialItem {
|
|
ID = "StarterOutfit01_Chest_M",
|
|
MaterialIndex = 0,
|
|
};
|
|
defaultVanity.GloveItem = new FYVanityMaterialItem {
|
|
ID = "StarterOutfit01_Gloves_M",
|
|
MaterialIndex = 0,
|
|
};
|
|
#elif SEASON_3_RELEASE || SEASON_3_DEBUG
|
|
defaultVanity.BootsItem = new FYVanityMaterialItem {
|
|
ID = "StarterOutfit01_Boots",
|
|
MaterialIndex = 0,
|
|
};
|
|
defaultVanity.ChestItem = new FYVanityMaterialItem {
|
|
ID = "StarterOutfit01_Chest",
|
|
MaterialIndex = 0,
|
|
};
|
|
defaultVanity.GloveItem = new FYVanityMaterialItem {
|
|
ID = "StarterOutfit01_Gloves",
|
|
MaterialIndex = 0,
|
|
};
|
|
#else
|
|
#error Unsupported build type
|
|
#endif
|
|
|
|
// TODO: Proper objects.
|
|
var defaultData = new Dictionary<string, (bool isPublic, string value)>
|
|
{
|
|
// GET /Client/GetUserData & GET /Client/GetUserReadOnlyData
|
|
// TODO: Contracts and factions progression
|
|
["FactionProgressionKorolev"] = (false, "0"),
|
|
["FactionProgressionICA"] = (false, "0"),
|
|
["FactionProgressionOsiris"] = (false, "0"),
|
|
["ContractsActive"] = (false, $"{{\"userId\":\"{playFabId}\",\"error\":\"\",\"contracts\":[]}}"),
|
|
["ContractsOneTimeCompleted"] = (false, "{\"contractsIds\":[]}"),
|
|
["OnboardingProgression"] = (false, $"{{\"userId\":\"{playFabId}\",\"currentMissionID\":\"TalkToBadum\",\"progress\":0,\"showPopup\":true}}"),
|
|
|
|
// TODO: Notifications
|
|
["Notifications"] = (false, "{\"notifications\":[]}"), // FYNotificationDescription?
|
|
["Notifications_Account"] = (false, "{\"notifications\":[]}"), // FYNotificationDescription?
|
|
|
|
// TODO: Fortuna Pass Season 2
|
|
["FortunaPass2_DailyCompletedActions"] = (false, "{\"actions\":{},\"lastDailyCapResetTimeUtc\":{\"seconds\":0}}"),
|
|
["FortunaPass2_ClaimedRewards"] = (false, "{\"rewardsIds\":[]}"),
|
|
["FortunaPass2_PremiumUnlock"] = (false, "false"),
|
|
["FortunaPass2_SeasonXp"] = (false, "0"),
|
|
|
|
// TODO: Fortuna Pass Season 3
|
|
["FortunaPass3_DailyCompletedActions"] = (false, "{\"actions\":{},\"lastDailyCapResetTimeUtc\":{\"seconds\":0}}"),
|
|
["FortunaPass3_ClaimedRewards"] = (false, "{\"rewardsIds\":[]}"),
|
|
["FortunaPass3_PremiumUnlock"] = (false, "false"),
|
|
["FortunaPass3_SeasonXp"] = (false, "0"),
|
|
|
|
// TODO: Quarters
|
|
["TechTreeNodeData"] = (false, JsonSerializer.Serialize(userTechTreeNodesData)),
|
|
["CraftingTimer__2022_05_12"] = (false, "{\"itemId\":\"\",\"utcTimestampWhenCraftingStarted\":{\"seconds\":0}}"),
|
|
["Generators__2021_09_09"] = (false, "[{\"generatorId\":\"playerquarters_gen_aurum\",\"lastClaimTime\":{\"seconds\":0}},{\"generatorId\":\"playerquarters_gen_kmarks\",\"lastClaimTime\":{\"seconds\":0}},{\"generatorId\":\"playerquarters_gen_crate\",\"lastClaimTime\":{\"seconds\":0}}]"),
|
|
["ClaimableStarterPacks"] = (false, "{\"packages\":[]}"), // FYClaimableStarterPackPackageResult?
|
|
["ClaimableVictimCompensations"] = (false, "{\"packages\":[]}"), // FYBackendClaimableVictimCompensationWrapper?
|
|
["PlayerQuartersLevel"] = (false, "{\"level\":1,\"upgradeStartedTime\":{\"seconds\":0}}"),
|
|
["TwitchDropRewards"] = (false, "{\"grantedItems\":[]}"), // FYInventoryItem
|
|
["InsuranceInvoice"] = (false, "{}"),
|
|
["InsuranceClaims"] = (false, "{\"packages\":[]}"), // FYBackendInsurancePayoutPackage. Limited to 20.
|
|
|
|
// TODO: Player data
|
|
["LOADOUT"] = (false, $"{{\"shield\":\"\",\"helmet\":\"\",\"weaponOne\":\"\",\"weaponTwo\":\"\",\"bag\":\"\",\"bagItemsAsJsonStr\":\"\",\"safeItemsAsJsonStr\":\"\"}}"),
|
|
["SessionData"] = (false, "{}"),
|
|
["SeasonWipe"] = (false, "{}"),
|
|
["VeteranPoints"] = (false, "{}"),
|
|
["FreeLoadout"] = (false, "{}"), // Season 3
|
|
|
|
// Function-specific data
|
|
// Used to optimize bonuses calculation and further validations without getting the user data and computing bonuses in runtime.
|
|
["CharacterTechTreeBonuses"] = (false, "{\"aurumCap\":0,\"aurumRate\":0,\"kmarksCap\":0,\"kmarksRate\":0,\"crateTier\":0,\"stashSize\":0,\"safePocketSize\":0,\"upgradeSpeed\":1.0}"),
|
|
["GlobalVanity"] = (false, $"{{\"activeGlobalVanityIds\":[\"Season03_Spray_13\",\"Banner_MIne\",\"Emote_Chill_01\",\"\",\"\",\"\",\"\",\"\"], \"droppodId\": \"VDP_Omega02\"}}"),
|
|
["CharacterVanity"] = (false, JsonSerializer.Serialize(defaultVanity)),
|
|
["Inventory"] = (false, "[]"),
|
|
["JobBoardsData"] = (false, JsonSerializer.Serialize(
|
|
new JobBoardsData {
|
|
LastBoardRefreshTimeUtc = new CloudScript.Models.Data.FYTimestamp { Seconds = 0 },
|
|
Boards = [
|
|
new FYFactionContractsData {
|
|
FactionId = "Korolev",
|
|
Contracts = [ new FYFactionContractData { }, new FYFactionContractData { }, new FYFactionContractData { }]
|
|
},
|
|
new FYFactionContractsData {
|
|
FactionId = "Osiris",
|
|
Contracts = [ new FYFactionContractData { }, new FYFactionContractData { }, new FYFactionContractData { }]
|
|
},
|
|
new FYFactionContractsData {
|
|
FactionId = "ICA",
|
|
Contracts = [ new FYFactionContractData { }, new FYFactionContractData { }, new FYFactionContractData { }]
|
|
}
|
|
]
|
|
}
|
|
)),
|
|
["VanityItems"] = (false, "[]"),
|
|
["RetentionBonus"] = (false, "{\"claimedAll\":false,\"daysClaimed\":0,\"lastClaimTime\":{\"seconds\":0}}"),
|
|
["Balance"] = (false, "{\"IN\": 0,\"AU\": 800,\"SC\": 30000}"),
|
|
};
|
|
|
|
foreach (var (key, (isPublic, value)) in defaultData)
|
|
{
|
|
if (!await _dbUserDataService.HasAsync(playFabId, key))
|
|
{
|
|
await _dbUserDataService.InsertAsync(playFabId, key, value, isPublic);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="currentUserId">
|
|
/// The authenticated user id.
|
|
/// </param>
|
|
/// <param name="requestUserId">
|
|
/// The requested user id.
|
|
/// </param>
|
|
/// <param name="keys"></param>
|
|
/// <returns></returns>
|
|
public async Task<Dictionary<string, FUserDataRecord>> FindAsync(
|
|
string currentUserId,
|
|
string requestUserId,
|
|
List<string> keys)
|
|
{
|
|
if (string.IsNullOrEmpty(currentUserId))
|
|
{
|
|
throw new ArgumentNullException(nameof(currentUserId));
|
|
}
|
|
|
|
if (requestUserId == null)
|
|
{
|
|
requestUserId = currentUserId;
|
|
}
|
|
|
|
var other = currentUserId != requestUserId;
|
|
var result = new Dictionary<string, FUserDataRecord>();
|
|
|
|
if (keys != null && keys.Count > 0)
|
|
{
|
|
foreach (var key in keys)
|
|
{
|
|
var data = await _dbUserDataService.FindAsync(requestUserId, key);
|
|
if (data == null)
|
|
{
|
|
// TODO: Error?
|
|
continue;
|
|
}
|
|
|
|
if (!data.Public && other)
|
|
{
|
|
// TODO: Error?
|
|
continue;
|
|
}
|
|
|
|
result.Add(data.Key, new FUserDataRecord
|
|
{
|
|
LastUpdated = data.LastUpdated,
|
|
Permission = data.Public ? UserDataPermission.Public : UserDataPermission.Private,
|
|
Value = data.Value
|
|
});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO: Getting ALL user data seems to be required only for fetching the player's passive generators data.
|
|
// This is not OK and normally should not be allowed.
|
|
var cursor = await _dbUserDataService.AllAsync(requestUserId, other);
|
|
|
|
while (await cursor.MoveNextAsync())
|
|
{
|
|
foreach (var data in cursor.Current)
|
|
{
|
|
result.Add(data.Key, new FUserDataRecord
|
|
{
|
|
LastUpdated = data.LastUpdated,
|
|
Permission = data.Public ? UserDataPermission.Public : UserDataPermission.Private,
|
|
Value = data.Value
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="currentUserId">
|
|
/// The authenticated user id.
|
|
/// </param>
|
|
/// <param name="targetUserId">
|
|
/// The target user id.
|
|
/// </param>
|
|
/// <param name="changes"></param>
|
|
/// <returns></returns>
|
|
public async Task UpdateAsync(
|
|
string currentUserId,
|
|
string? targetUserId,
|
|
Dictionary<string, string> changes)
|
|
{
|
|
if (changes.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(currentUserId))
|
|
{
|
|
throw new ArgumentNullException(nameof(currentUserId));
|
|
}
|
|
|
|
if (targetUserId == null)
|
|
{
|
|
targetUserId = currentUserId;
|
|
}
|
|
|
|
// Whether we are updating someone else.
|
|
var other = currentUserId != targetUserId;
|
|
|
|
foreach (var (key, value) in changes)
|
|
{
|
|
var data = await _dbUserDataService.FindAsync(currentUserId, key);
|
|
if (data == null)
|
|
{
|
|
if (other)
|
|
{
|
|
_logger.LogWarning("User {PlayFabId} attempted to update non-existing key {Key} of another user {PlayFabIdOther}", currentUserId, key, targetUserId);
|
|
}
|
|
else
|
|
{
|
|
_logger.LogDebug("User {PlayFabId} created key {Key}", currentUserId, key);
|
|
await _dbUserDataService.InsertAsync(currentUserId, key, value, false);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (!data.Public && other)
|
|
{
|
|
_logger.LogWarning("User {PlayFabId} attempted to update non-public key {Key} of another user {PlayFabIdOther}", currentUserId, key, data.PlayFabId);
|
|
continue;
|
|
}
|
|
|
|
await _dbUserDataService.UpdateValueAsync(data.Id, value);
|
|
}
|
|
}
|
|
} |