From e29f10c406a5a166daae8a546bdd21dfa38c3c46 Mon Sep 17 00:00:00 2001 From: Spirtix Date: Thu, 25 Jul 2024 22:54:48 +0200 Subject: [PATCH] Purchase optimization (#3) * purchase optimization * purchase: simplify wallet update * fix purchase: swap coins and gems * purchase: rename mysteryBox to addAsMysteryBox * purchase: adjust item quantity note * purchase: change GetItemQuantity API --- src/Controllers/Common/ContentController.cs | 209 ++++++++++---------- src/Services/InventoryService.cs | 17 ++ src/Services/ItemService.cs | 8 +- 3 files changed, 127 insertions(+), 107 deletions(-) diff --git a/src/Controllers/Common/ContentController.cs b/src/Controllers/Common/ContentController.cs index d281168..fd328be 100644 --- a/src/Controllers/Common/ContentController.cs +++ b/src/Controllers/Common/ContentController.cs @@ -1,14 +1,12 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Microsoft.EntityFrameworkCore; -using Org.BouncyCastle.Security.Certificates; using sodoff.Attributes; using sodoff.Model; using sodoff.Schema; using sodoff.Services; using sodoff.Util; using sodoff.Configuration; -using System; using System.Globalization; namespace sodoff.Controllers.Common; @@ -1210,64 +1208,9 @@ public class ContentController : Controller { [VikingSession(UseLock = true)] public IActionResult PurchaseItems(Viking viking, [FromForm] string purchaseItemRequest) { PurchaseStoreItemRequest request = XmlUtil.DeserializeXml(purchaseItemRequest); - List items = new List(); - Gender gender = XmlUtil.DeserializeXml(viking.AvatarSerialized).GenderType; - bool success = true; - for (int i = 0; i < request.Items.Length; i++) { - int itemId = request.Items[i]; - ItemData item = itemService.GetItem(itemId); - UserGameCurrency currency = achievementService.GetUserCurrency(viking); - int coinCost = (int)Math.Round(item.FinalDiscoutModifier * item.Cost); - int gemCost = (int)Math.Round(item.FinalDiscoutModifier * item.CashCost); - if (currency.GameCurrency - coinCost < 0 && currency.CashCurrency - gemCost < 0) { - success = false; - break; - } - achievementService.AddAchievementPoints(viking, AchievementPointTypes.GameCurrency, -coinCost); - achievementService.AddAchievementPoints(viking, AchievementPointTypes.CashCurrency, -gemCost); - if (request.AddMysteryBoxToInventory) { - // force add boxes as item (without "opening") - items.Add(inventoryService.AddItemToInventoryAndGetResponse(viking, itemId, 1)); - } else if (itemService.IsBundleItem(itemId)) { - // open and add bundle - ItemData bundleItem = itemService.GetItem(itemId); - foreach (var reward in bundleItem.Relationship.Where(e => e.Type == "Bundle")) { - int quantity = itemService.GetItemQuantity(reward); - for (int j=0; j id).ToDictionary(g => g.Key, g => g.Count()); - CommonInventoryResponse response = new CommonInventoryResponse { - Success = success, - CommonInventoryIDs = items.ToArray(), - UserGameCurrency = achievementService.GetUserCurrency(viking) - }; - return Ok(response); + return Ok(PurchaseItemsImpl(viking, itemsToPurchase, request.AddMysteryBoxToInventory)); } [HttpPost] @@ -1276,51 +1219,9 @@ public class ContentController : Controller { [VikingSession(UseLock = true)] public IActionResult PurchaseItemsV1(Viking viking, [FromForm] string itemIDArrayXml) { int[] itemIdArr = XmlUtil.DeserializeXml(itemIDArrayXml); - List items = new List(); - Gender gender = XmlUtil.DeserializeXml(viking.AvatarSerialized).GenderType; - bool success = true; - for (int i = 0; i < itemIdArr.Length; i++) { - ItemData item = itemService.GetItem(itemIdArr[i]); - UserGameCurrency currency = achievementService.GetUserCurrency(viking); - int coinCost = (int)Math.Round(item.FinalDiscoutModifier * item.Cost); - int gemCost = (int)Math.Round(item.FinalDiscoutModifier * item.CashCost); - if (currency.GameCurrency - coinCost < 0 && currency.CashCurrency - gemCost < 0) { - success = false; - break; - } - achievementService.AddAchievementPoints(viking, AchievementPointTypes.GameCurrency, -coinCost); - achievementService.AddAchievementPoints(viking, AchievementPointTypes.CashCurrency, -gemCost); - if (itemService.IsGemBundle(itemIdArr[i], out int gems)) { - achievementService.AddAchievementPoints(viking, AchievementPointTypes.CashCurrency, gems); - items.Add(new CommonInventoryResponseItem { - CommonInventoryID = 0, - ItemID = itemIdArr[i], - Quantity = 0 - }); - } else if (itemService.IsCoinBundle(itemIdArr[i], out int coins)) { - achievementService.AddAchievementPoints(viking, AchievementPointTypes.GameCurrency, coins); - items.Add(new CommonInventoryResponseItem { - CommonInventoryID = 0, - ItemID = itemIdArr[i], - Quantity = 0 - }); - } - else { - itemService.CheckAndOpenBox(itemIdArr[i], gender, out int itemId, out int quantity); - for (int j=0; j id).ToDictionary(g => g.Key, g => g.Count()); - CommonInventoryResponse response = new CommonInventoryResponse { - Success = success, - CommonInventoryIDs = items.ToArray(), - UserGameCurrency = achievementService.GetUserCurrency(viking) - }; - return Ok(response); + return Ok(PurchaseItemsImpl(viking, itemsToPurchase, false)); } [HttpPost] @@ -2326,4 +2227,106 @@ public class ContentController : Controller { return adjectives[rand.Next(adjectives.Length)] + name; return name; } + + private CommonInventoryResponse PurchaseItemsImpl(Viking viking, Dictionary itemsToPurchase, bool addAsMysteryBox) { + // Viking information + UserGameCurrency currency = achievementService.GetUserCurrency(viking); + Gender gender = XmlUtil.DeserializeXml(viking.AvatarSerialized).GenderType; + + // Purchase information + int totalCoinCost = 0, totalGemCost = 0, coinsToAdd = 0, gemsToAdd = 0; + Dictionary inventoryItemsToAdd = new(); // dict of items to add to the inventory + Dictionary itemsToSendBack = new(); // dict of items that are sent back in the response + + foreach (var i in itemsToPurchase) { + ItemData item = itemService.GetItem(i.Key); + // Calculate cost + totalCoinCost += (int)Math.Round(item.FinalDiscoutModifier * item.Cost) * i.Value; + totalGemCost += (int)Math.Round(item.FinalDiscoutModifier * item.CashCost) * i.Value; + + // Resolve items to purchase + if (addAsMysteryBox) { + // add mystery box to inventory + inventoryItemsToAdd.TryAdd(i.Key, 0); + inventoryItemsToAdd[i.Key] += i.Value; + itemsToSendBack.TryAdd(i.Key, 0); + itemsToSendBack[i.Key] += i.Value; + } + else if (itemService.IsGemBundle(i.Key, out int gemValue)) { + // get gem value + gemsToAdd += gemValue * i.Value; + itemsToSendBack.TryAdd(i.Key, 0); + itemsToSendBack[i.Key] += i.Value; + } + else if (itemService.IsCoinBundle(i.Key, out int coinValue)) { + // get coin value + coinsToAdd += coinValue * i.Value; + itemsToSendBack.TryAdd(i.Key, 0); + itemsToSendBack[i.Key] += i.Value; + } + else if (itemService.IsBundleItem(i.Key)) { + ItemData bundleItem = itemService.GetItem(i.Key); + // resolve items in the bundle + foreach (var reward in bundleItem.Relationship.Where(e => e.Type == "Bundle")) { + int quantity = itemService.GetItemQuantity(reward, i.Value); + inventoryItemsToAdd.TryAdd(reward.ItemId, 0); + inventoryItemsToAdd[reward.ItemId] += quantity; + itemsToSendBack.TryAdd(reward.ItemId, 0); + itemsToSendBack[reward.ItemId] += quantity; + } + } + else if (itemService.IsBoxItem(i.Key)) { + // open boxes individually + for (int j = 0; j < i.Value; j++) { + itemService.OpenBox(i.Key, gender, out int itemId, out int quantity); + inventoryItemsToAdd.TryAdd(itemId, 0); + inventoryItemsToAdd[itemId] += quantity; + itemsToSendBack.TryAdd(itemId, 0); + itemsToSendBack[itemId] += quantity; + } + } + else { + // add item to inventory + inventoryItemsToAdd.TryAdd(i.Key, 0); + inventoryItemsToAdd[i.Key] += i.Value; + itemsToSendBack.TryAdd(i.Key, 0); + itemsToSendBack[i.Key] += i.Value; + } + } + + // check if the user can afford the purchase + if (currency.GameCurrency - totalCoinCost < 0 && currency.CashCurrency - totalGemCost < 0) { + return new CommonInventoryResponse { + Success = false, + CommonInventoryIDs = new CommonInventoryResponseItem[0], + UserGameCurrency = achievementService.GetUserCurrency(viking) + }; + } + + // deduct the cost of the purchase + achievementService.AddAchievementPoints(viking, AchievementPointTypes.GameCurrency, -totalCoinCost + coinsToAdd); + achievementService.AddAchievementPoints(viking, AchievementPointTypes.CashCurrency, -totalGemCost + gemsToAdd); + + // add items to the inventory (database) + var addedItems = inventoryService.AddItemsToInventoryBulk(viking, inventoryItemsToAdd); + + // build response + List items = new List(); + foreach (var i in itemsToSendBack) { + items.AddRange(Enumerable.Repeat( + new CommonInventoryResponseItem { + CommonInventoryID = addedItems.ContainsKey(i.Key) ? addedItems[i.Key] : 0, // return inventory id if this item was added to the DB + ItemID = i.Key, + Quantity = 0 + }, i.Value)); + } + // NOTE: The quantity of purchased items can always be 0 and the items are instead duplicated in both the request and the response. + // Item quantities are used for non-store related requests/responses. + + return new CommonInventoryResponse { + Success = true, + CommonInventoryIDs = items.ToArray(), + UserGameCurrency = achievementService.GetUserCurrency(viking) + }; + } } diff --git a/src/Services/InventoryService.cs b/src/Services/InventoryService.cs index 663bfa0..7f518ea 100644 --- a/src/Services/InventoryService.cs +++ b/src/Services/InventoryService.cs @@ -57,6 +57,23 @@ namespace sodoff.Services { }; } + public Dictionary AddItemsToInventoryBulk(Viking viking, Dictionary itemsWithQuantity) { + List items = new(); + Dictionary itemsWithInventoryId = new(); + List responses = new(); + foreach (var i in itemsWithQuantity) { + items.Add(AddItemToInventory(viking, i.Key, i.Value)); + } + + ctx.SaveChanges(); + + foreach (var item in items) { + itemsWithInventoryId[item.ItemId] = item.Id; + } + + return itemsWithInventoryId; + } + public InventoryItemStatsMap AddBattleItemToInventory(Viking viking, int itemId, int itemTier, ItemStat[] itemStat = null) { // get item data ItemData itemData = itemService.GetItem(itemId); diff --git a/src/Services/ItemService.cs b/src/Services/ItemService.cs index 1b0ee70..a0ab4ea 100644 --- a/src/Services/ItemService.cs +++ b/src/Services/ItemService.cs @@ -41,14 +41,14 @@ namespace sodoff.Services { return items[itemID]; } - public int GetItemQuantity(ItemDataRelationship itemData) { + public int GetItemQuantity(ItemDataRelationship itemData, int bulkSize = 1) { if (itemData.MaxQuantity is null || itemData.MaxQuantity < 2 || itemData.MaxQuantity == itemData.Quantity) { if (itemData.Quantity == 0) - return 1; + return 1 * bulkSize; else - return itemData.Quantity; + return itemData.Quantity * bulkSize; } - return random.Next(1, (int)itemData.MaxQuantity + 1); + return random.Next(1 * bulkSize, (int)itemData.MaxQuantity * bulkSize + 1); } public ItemDataRelationship OpenBox(ItemData boxItem, Gender gender) {