diff --git a/src/Prospect.Server.Api/Controllers/ClientController.cs b/src/Prospect.Server.Api/Controllers/ClientController.cs index d1fad77..3c9ca10 100644 --- a/src/Prospect.Server.Api/Controllers/ClientController.cs +++ b/src/Prospect.Server.Api/Controllers/ClientController.cs @@ -352,13 +352,15 @@ public class ClientController : Controller public async Task GetUserInventory(FGetUserInventoryRequest request) { var userId = User.FindAuthUserId(); - var userData = await _userDataService.FindAsync(userId, userId, new List{"Balance", "Inventory", "VanityItems"}); + var userData = await _userDataService.FindAsync(userId, userId, new List{"Balance", "Inventory", "VanityItems", "CharacterVanity", "GlobalVanity"}); var inventory = JsonSerializer.Deserialize(userData["Inventory"].Value); - // TODO: Per-user vanity - // var vanity = JsonSerializer.Deserialize(userData["VanityItems"].Value); - var titleData = _titleDataService.Find(new List{"Blueprints", "Vanities"}); + // Per-user vanity ownership: the player owns the cosmetics they've bought (VanityItems) + // plus whatever is currently equipped (CharacterVanity / GlobalVanity), so the starter + // outfit stays owned. Previously the whole Vanities catalogue was returned as owned, + // which made every item in the Aurum shop show up as already "owned". + var ownedVanityIds = CollectOwnedVanityIds(userData); + var titleData = _titleDataService.Find(new List{"Blueprints"}); var blueprints = JsonSerializer.Deserialize>(titleData["Blueprints"]); - var vanity = JsonSerializer.Deserialize(titleData["Vanities"]); List items = new List(); foreach (var item in inventory) { if (!blueprints.ContainsKey(item.BaseItemId)) { @@ -384,9 +386,9 @@ public class ClientController : Controller }); } - foreach (var item in vanity) { + foreach (var vanityId in ownedVanityIds) { items.Add(new FItemInstance { - ItemId = item.Name, + ItemId = vanityId, // ItemInstanceId is not required since it's not a unique item. ItemClass = "Vanity", // PurchaseDate = DateTime.Now, // TODO @@ -411,6 +413,60 @@ public class ClientController : Controller }); } + // Collects the vanity ids a player actually owns: everything they've purchased + // (VanityItems) plus everything currently equipped (CharacterVanity slots and the + // GlobalVanity sprays / banner / emotes / drop pod). Parsed defensively with + // JsonDocument so malformed / partial data never breaks the inventory response. + private static HashSet CollectOwnedVanityIds(Dictionary userData) + { + var owned = new HashSet(StringComparer.Ordinal); + void AddId(string? id) { if (!string.IsNullOrEmpty(id)) owned.Add(id); } + + if (userData.TryGetValue("VanityItems", out var viRec) && !string.IsNullOrWhiteSpace(viRec.Value)) + { + try + { + using var doc = JsonDocument.Parse(viRec.Value); + if (doc.RootElement.ValueKind == JsonValueKind.Array) + foreach (var el in doc.RootElement.EnumerateArray()) + if (el.ValueKind == JsonValueKind.Object && el.TryGetProperty("baseItemId", out var b)) + AddId(b.GetString()); + } + catch { /* ignore malformed VanityItems */ } + } + + if (userData.TryGetValue("CharacterVanity", out var cvRec) && !string.IsNullOrWhiteSpace(cvRec.Value)) + { + try + { + using var doc = JsonDocument.Parse(cvRec.Value); + if (doc.RootElement.ValueKind == JsonValueKind.Object) + foreach (var prop in doc.RootElement.EnumerateObject()) + if (prop.Value.ValueKind == JsonValueKind.Object && prop.Value.TryGetProperty("id", out var id)) + AddId(id.GetString()); + } + catch { /* ignore malformed CharacterVanity */ } + } + + if (userData.TryGetValue("GlobalVanity", out var gvRec) && !string.IsNullOrWhiteSpace(gvRec.Value)) + { + try + { + using var doc = JsonDocument.Parse(gvRec.Value); + var root = doc.RootElement; + if (root.ValueKind == JsonValueKind.Object) + { + if (root.TryGetProperty("activeGlobalVanityIds", out var arr) && arr.ValueKind == JsonValueKind.Array) + foreach (var el in arr.EnumerateArray()) AddId(el.GetString()); + if (root.TryGetProperty("droppodId", out var dp)) AddId(dp.GetString()); + } + } + catch { /* ignore malformed GlobalVanity */ } + } + + return owned; + } + [HttpPost("GetTitleData")] [Produces(MediaTypeNames.Application.Json)] [Authorize(AuthenticationSchemes = UserAuthenticationOptions.DefaultScheme)]