forked from SoDOff-Project/sodoff
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
This commit is contained in:
parent
28e275bcaa
commit
e29f10c406
@ -1,14 +1,12 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Org.BouncyCastle.Security.Certificates;
|
|
||||||
using sodoff.Attributes;
|
using sodoff.Attributes;
|
||||||
using sodoff.Model;
|
using sodoff.Model;
|
||||||
using sodoff.Schema;
|
using sodoff.Schema;
|
||||||
using sodoff.Services;
|
using sodoff.Services;
|
||||||
using sodoff.Util;
|
using sodoff.Util;
|
||||||
using sodoff.Configuration;
|
using sodoff.Configuration;
|
||||||
using System;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
|
||||||
namespace sodoff.Controllers.Common;
|
namespace sodoff.Controllers.Common;
|
||||||
@ -1210,64 +1208,9 @@ public class ContentController : Controller {
|
|||||||
[VikingSession(UseLock = true)]
|
[VikingSession(UseLock = true)]
|
||||||
public IActionResult PurchaseItems(Viking viking, [FromForm] string purchaseItemRequest) {
|
public IActionResult PurchaseItems(Viking viking, [FromForm] string purchaseItemRequest) {
|
||||||
PurchaseStoreItemRequest request = XmlUtil.DeserializeXml<PurchaseStoreItemRequest>(purchaseItemRequest);
|
PurchaseStoreItemRequest request = XmlUtil.DeserializeXml<PurchaseStoreItemRequest>(purchaseItemRequest);
|
||||||
List<CommonInventoryResponseItem> items = new List<CommonInventoryResponseItem>();
|
var itemsToPurchase = request.Items.GroupBy(id => id).ToDictionary(g => g.Key, g => g.Count());
|
||||||
Gender gender = XmlUtil.DeserializeXml<AvatarData>(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<quantity; ++j)
|
|
||||||
items.Add(inventoryService.AddItemToInventoryAndGetResponse(viking, reward.ItemId, 1));
|
|
||||||
}
|
|
||||||
} else if (itemService.IsGemBundle(itemId, out int gems)) {
|
|
||||||
achievementService.AddAchievementPoints(viking, AchievementPointTypes.CashCurrency, gems);
|
|
||||||
items.Add(new CommonInventoryResponseItem {
|
|
||||||
CommonInventoryID = 0,
|
|
||||||
ItemID = itemId,
|
|
||||||
Quantity = 0
|
|
||||||
});
|
|
||||||
} else if (itemService.IsCoinBundle(itemId, out int coins)) {
|
|
||||||
achievementService.AddAchievementPoints(viking, AchievementPointTypes.GameCurrency, coins);
|
|
||||||
items.Add(new CommonInventoryResponseItem {
|
|
||||||
CommonInventoryID = 0,
|
|
||||||
ItemID = itemId,
|
|
||||||
Quantity = 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// check for mystery box ... open if need
|
|
||||||
itemService.CheckAndOpenBox(itemId, gender, out itemId, out int quantity);
|
|
||||||
for (int j=0; j<quantity; ++j) {
|
|
||||||
items.Add(inventoryService.AddItemToInventoryAndGetResponse(viking, itemId, 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// NOTE: The quantity of purchased items is always 0 and the items are instead duplicated in both the request and the response.
|
|
||||||
// Call AddItemToInventoryAndGetResponse with Quantity == 1 we get response with quantity == 0.
|
|
||||||
}
|
|
||||||
|
|
||||||
CommonInventoryResponse response = new CommonInventoryResponse {
|
return Ok(PurchaseItemsImpl(viking, itemsToPurchase, request.AddMysteryBoxToInventory));
|
||||||
Success = success,
|
|
||||||
CommonInventoryIDs = items.ToArray(),
|
|
||||||
UserGameCurrency = achievementService.GetUserCurrency(viking)
|
|
||||||
};
|
|
||||||
return Ok(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@ -1276,51 +1219,9 @@ public class ContentController : Controller {
|
|||||||
[VikingSession(UseLock = true)]
|
[VikingSession(UseLock = true)]
|
||||||
public IActionResult PurchaseItemsV1(Viking viking, [FromForm] string itemIDArrayXml) {
|
public IActionResult PurchaseItemsV1(Viking viking, [FromForm] string itemIDArrayXml) {
|
||||||
int[] itemIdArr = XmlUtil.DeserializeXml<int[]>(itemIDArrayXml);
|
int[] itemIdArr = XmlUtil.DeserializeXml<int[]>(itemIDArrayXml);
|
||||||
List<CommonInventoryResponseItem> items = new List<CommonInventoryResponseItem>();
|
var itemsToPurchase = itemIdArr.GroupBy(id => id).ToDictionary(g => g.Key, g => g.Count());
|
||||||
Gender gender = XmlUtil.DeserializeXml<AvatarData>(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<quantity; ++j) {
|
|
||||||
items.Add(inventoryService.AddItemToInventoryAndGetResponse(viking, itemId, 1));
|
|
||||||
// NOTE: The quantity of purchased items is always 0 and the items are instead duplicated in both the request and the response.
|
|
||||||
// Call AddItemToInventoryAndGetResponse with Quantity == 1 we get response with quantity == 0.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CommonInventoryResponse response = new CommonInventoryResponse {
|
return Ok(PurchaseItemsImpl(viking, itemsToPurchase, false));
|
||||||
Success = success,
|
|
||||||
CommonInventoryIDs = items.ToArray(),
|
|
||||||
UserGameCurrency = achievementService.GetUserCurrency(viking)
|
|
||||||
};
|
|
||||||
return Ok(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@ -2326,4 +2227,106 @@ public class ContentController : Controller {
|
|||||||
return adjectives[rand.Next(adjectives.Length)] + name;
|
return adjectives[rand.Next(adjectives.Length)] + name;
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CommonInventoryResponse PurchaseItemsImpl(Viking viking, Dictionary<int, int> itemsToPurchase, bool addAsMysteryBox) {
|
||||||
|
// Viking information
|
||||||
|
UserGameCurrency currency = achievementService.GetUserCurrency(viking);
|
||||||
|
Gender gender = XmlUtil.DeserializeXml<AvatarData>(viking.AvatarSerialized).GenderType;
|
||||||
|
|
||||||
|
// Purchase information
|
||||||
|
int totalCoinCost = 0, totalGemCost = 0, coinsToAdd = 0, gemsToAdd = 0;
|
||||||
|
Dictionary<int, int> inventoryItemsToAdd = new(); // dict of items to add to the inventory
|
||||||
|
Dictionary<int, int> 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<CommonInventoryResponseItem> items = new List<CommonInventoryResponseItem>();
|
||||||
|
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)
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,23 @@ namespace sodoff.Services {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Dictionary<int, int> AddItemsToInventoryBulk(Viking viking, Dictionary<int,int> itemsWithQuantity) {
|
||||||
|
List<InventoryItem> items = new();
|
||||||
|
Dictionary<int, int> itemsWithInventoryId = new();
|
||||||
|
List<CommonInventoryResponseItem> 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) {
|
public InventoryItemStatsMap AddBattleItemToInventory(Viking viking, int itemId, int itemTier, ItemStat[] itemStat = null) {
|
||||||
// get item data
|
// get item data
|
||||||
ItemData itemData = itemService.GetItem(itemId);
|
ItemData itemData = itemService.GetItem(itemId);
|
||||||
|
@ -41,14 +41,14 @@ namespace sodoff.Services {
|
|||||||
return items[itemID];
|
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.MaxQuantity is null || itemData.MaxQuantity < 2 || itemData.MaxQuantity == itemData.Quantity) {
|
||||||
if (itemData.Quantity == 0)
|
if (itemData.Quantity == 0)
|
||||||
return 1;
|
return 1 * bulkSize;
|
||||||
else
|
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) {
|
public ItemDataRelationship OpenBox(ItemData boxItem, Gender gender) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user