using Google.Protobuf; using Google.Protobuf.WellKnownTypes; using ServerCore; using ServerBase; using ServerCommon; using ServerCommon.BusinessLogDomain; using MetaAssets; using GameServer.PacketHandler; using META_ID = System.UInt32; namespace GameServer; public class CartAction : EntityActionBase { private Cart? m_Cart = null; public CartAction(Player owner) : base(owner) { } public override async Task onInit() { var result = new Result(); var player = getOwner() as Player; NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!"); m_Cart = new(player); await m_Cart.onInit(); return result; } public override void onClear() { NullReferenceCheckHelper.throwIfNull(m_Cart, () => $"m_Cart is null !!!"); m_Cart.onCearAll(); } public Result AddCartFromDoc(CartDoc? cartDoc) { var result = new Result(); var err_msg = string.Empty; if (cartDoc == null) return result; var player = getOwner() as Player; NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!"); NullReferenceCheckHelper.throwIfNull(m_Cart, () => $"m_Cart is null !!!"); var cart_attribute = m_Cart.getEntityAttribute(); if (cart_attribute == null) { err_msg = $"Failed to get cart attribute : {nameof(CartAttribute)}"; result.setFail(ServerErrorCode.EntityAttributeIsNull, err_msg); Log.getLogger().error(err_msg); return result; } if(cart_attribute.copyEntityAttributeFromDoc(cartDoc) == false) { err_msg = $"Failed copyEntityAttributeFromDoc !!! - {typeof(CartAction).Name}"; result.setFail(ServerErrorCode.DynamoDbDocCopyToEntityAttributeFailed, err_msg); Log.getLogger().error(result.toBasicString()); return result; } return result; } public Result GetCart4Client() { var result = new Result(); var player = getOwner() as Player; NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!"); GetCartPacketHandler.send_S2C_ACK_GET_CART(player, result, m_Cart); return result; } public async Task AddCartProcess(META_ID item_meta_id, int count, CurrencyType currency_type) { var result = new Result(); var err_msg = string.Empty; var player = getOwner() as Player; NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!"); var server_logic = GameServerApp.getServerLogic(); NullReferenceCheckHelper.throwIfNull(server_logic, () => $"server_logic is null !!!"); var fn_add_cart = async delegate () { var result = new Result(); var err_msg = string.Empty; var invokers = new List(); (result, int item_sum_count) = AddCart(item_meta_id, count, currency_type); if (result.isFail()) { PacketHandler.AddCartPacketHandler.send_S2C_ACK_ADD_CART(player, result, 0, 0); return result; } List<(META_ID, int)> deltaCartList = new List<(META_ID, int)>() { (item_meta_id, count) }; var task_log_data = CartBusinessLogHelper.toLogInfo(deltaCartList); invokers.Add(new CartBusinessLog(task_log_data)); var batch = new QueryBatchEx(player, LogActionType.CartAdd , server_logic.getDynamoDbClient()); { batch.addQuery(new DBQWriteToAttributeAllWithTransactionRunner()); } batch.appendBusinessLogs(invokers); result = await QueryHelper.sendQueryAndBusinessLog(batch); if (result.isFail()) { AddCartPacketHandler.send_S2C_ACK_ADD_CART(player, result, 0, 0); return result; } AddCartPacketHandler.send_S2C_ACK_ADD_CART(player, result, item_meta_id, item_sum_count); return result; }; result = await player.runTransactionRunnerSafely(TransactionIdType.PrivateContents, "AddCart", fn_add_cart); if (result.isFail()) { err_msg = $"Failed to runTransactionRunnerSafely() !!! : {result.toBasicString()} - {player.toBasicString()}"; Log.getLogger().error(err_msg); } return result; } public async Task DeleteCartProcess(List req_del_cart_items) { var result = new Result(); var err_msg = string.Empty; var player = getOwner() as Player; NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!"); var server_logic = GameServerApp.getServerLogic(); NullReferenceCheckHelper.throwIfNull(server_logic, () => $"server_logic is null !!!"); var fn_delete_cart = async delegate () { var result = new Result(); var err_msg = string.Empty; var invokers = new List(); (result, var isRemoveItem) = DeleteCart(req_del_cart_items); if (result.isFail()) { DelCartPacketHandler.send_S2C_ACK_DEL_CART(player, result, false); return result; } List<(META_ID, int)> deltaCartList = req_del_cart_items.Select(x => ((META_ID)x.ItemId, x.Count)).ToList(); var task_log_data = CartBusinessLogHelper.toLogInfo(deltaCartList); invokers.Add(new CartBusinessLog(task_log_data)); var batch = new QueryBatchEx(player, LogActionType.CartDelete , server_logic.getDynamoDbClient()); { batch.addQuery(new DBQWriteToAttributeAllWithTransactionRunner()); } batch.appendBusinessLogs(invokers); result = await QueryHelper.sendQueryAndBusinessLog(batch); if (result.isFail()) { DelCartPacketHandler.send_S2C_ACK_DEL_CART(player, result, false); return result; } DelCartPacketHandler.send_S2C_ACK_DEL_CART(player, result, isRemoveItem); return result; }; result = await player.runTransactionRunnerSafely(TransactionIdType.PrivateContents, "DeleteCart", fn_delete_cart); if (result.isFail()) { err_msg = $"Failed to runTransactionRunnerSafely() !!! : {result.toBasicString()} - {player.toBasicString()}"; Log.getLogger().error(err_msg); } return result; } public async Task BuyCartProcess(List req_buy_cart_items) { var result = new Result(); var err_msg = string.Empty; var player = getOwner() as Player; NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!"); var server_logic = GameServerApp.getServerLogic(); if (req_buy_cart_items.Count == 0) { BuyCartPacketHandler.send_S2C_ACK_BUY_CART(player, result, null); return result; } var fn_buy_cart = async delegate () { var result = new Result(); var err_msg = string.Empty; var invokers = new List(); // 카트에서 제거 var deleteResult = DeleteCart(req_buy_cart_items); if (deleteResult.result.isFail()) { BuyCartPacketHandler.send_S2C_ACK_BUY_CART(player, result, null); return result; } List<(META_ID, int)> deltaCartList = req_buy_cart_items.Select(x => ((META_ID)x.ItemId, x.Count)).ToList(); var task_log_data = CartBusinessLogHelper.toLogInfo(deltaCartList); invokers.Add(new CartBusinessLog(task_log_data)); // 재화 차감 result = await PayCurrencyForItem(req_buy_cart_items); if (result.isFail()) { BuyCartPacketHandler.send_S2C_ACK_BUY_CART(player, result, null); return result; } // 아이템 지급 var takeItemResult = await TakeCartItem(req_buy_cart_items); if (takeItemResult.result.isFail()) { BuyCartPacketHandler.send_S2C_ACK_BUY_CART(player, result, null); return result; } result = await updateItemFirstPurchaseHistory(req_buy_cart_items); if (result.isFail()) { BuyCartPacketHandler.send_S2C_ACK_BUY_CART(player, result, null); return result; } var batch = new QueryBatchEx(player, LogActionType.CartPurchase , server_logic.getDynamoDbClient(), true); { batch.addQuery(new DBQWriteToAttributeAllWithTransactionRunner()); } batch.appendBusinessLogs(invokers); result = await QueryHelper.sendQueryAndBusinessLog(batch); if (result.isFail()) { BuyCartPacketHandler.send_S2C_ACK_BUY_CART(player, result, null); return result; } BuyCartPacketHandler.send_S2C_ACK_BUY_CART(player, result, takeItemResult.changed_Items); return result; }; result = await player.runTransactionRunnerSafely(TransactionIdType.PrivateContents, "BuyCart", fn_buy_cart); if (result.isFail()) { err_msg = $"Failed to runTransactionRunnerSafely() !!! : {result.toBasicString()} - {player.toBasicString()}"; Log.getLogger().error(err_msg); } return result; } private (Result result, Int32 item_sum_count) AddCart(META_ID item_meta_id, int count, CurrencyType currency_type) { var result = new Result(); var err_msg = string.Empty; NullReferenceCheckHelper.throwIfNull(m_Cart, () => $"m_Cart is null !!!"); var cart_attribute = m_Cart.getEntityAttribute(); if (cart_attribute == null) { err_msg = $"Failed to get cart attribute : {nameof(CartAttribute)}"; result.setFail(ServerErrorCode.EntityAttributeIsNull, err_msg); Log.getLogger().error(err_msg); return (result, 0); } cart_attribute.CartItems.TryGetValue(currency_type, out var category_cart_items); NullReferenceCheckHelper.throwIfNull(category_cart_items, () => $"category_cart_items is null !!!"); if (category_cart_items.Count >= 20) { err_msg = $"cart is Full. currency_type : {currency_type}"; result.setFail(ServerErrorCode.CartMaxCountExceed, err_msg); Log.getLogger().error(err_msg); return (result, 0); } int sum_count = count; if (category_cart_items.TryGetValue(item_meta_id, out var cur_count) == false) { category_cart_items.TryAdd(item_meta_id, count); } else { sum_count += cur_count; category_cart_items[item_meta_id] = sum_count; } var player = m_Cart.getRootParent() as Player; NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!"); result = player.checkItemFirstPurchaseItemCount((int)item_meta_id, sum_count); if (result.isFail()) { return (result, 0); } if (sum_count > 99) { err_msg = $"FullStack of this item in cart. itemId : {item_meta_id}"; result.setFail(ServerErrorCode.CartStackCountInvalid, err_msg); Log.getLogger().error(err_msg); return (result, 0); } cart_attribute.modifiedEntityAttribute(); return (result, sum_count); } private (Result result, bool isRemovedItem) DeleteCart(List del_cart_items) { var result = new Result(); var err_msg = string.Empty; bool isRemovedItem = false; NullReferenceCheckHelper.throwIfNull(m_Cart, () => $"m_Cart is null !!!"); var cart_attribute = m_Cart.getEntityAttribute(); if (cart_attribute == null) { err_msg = $"Failed to get cart attribute : {nameof(CartAttribute)}"; result.setFail(ServerErrorCode.EntityAttributeIsNull, err_msg); Log.getLogger().error(err_msg); return (result, isRemovedItem); } foreach (var del_cart_item in del_cart_items) { if (EnumHelper.tryParse(del_cart_item.BuyType, out var currencyType) == false) { err_msg = $"Enum Pase Failed. BuyType : {del_cart_item.BuyType}"; result.setFail(ServerErrorCode.CartInvalidCurrencyType, err_msg); Log.getLogger().error(err_msg); return (result, isRemovedItem); } if(MetaData.Instance._ItemTable.TryGetValue(del_cart_item.ItemId, out var itemData) == false) { err_msg = $"Not found meta of Item !!! : ItemMetaId:{del_cart_item.ItemId} - {getOwner().toBasicString()}"; result.setFail(ServerErrorCode.CartMetaDataNotFound, err_msg); Log.getLogger().error(err_msg); return (result, isRemovedItem); } int cur_count = 0; foreach(var category_cart_items in cart_attribute.CartItems.Values) { if (category_cart_items.TryGetValue((META_ID)del_cart_item.ItemId, out cur_count) == false) continue; if (del_cart_item.Count > cur_count) { err_msg = $"Argument count is bigger then cart Item count. cur_count : {cur_count}, req_buy_count : {del_cart_item.Count}"; result.setFail(ServerErrorCode.CartStackCountNotEnough, err_msg); Log.getLogger().error(err_msg); return (result, isRemovedItem); } category_cart_items[(META_ID)del_cart_item.ItemId] = cur_count - del_cart_item.Count; if (cur_count - del_cart_item.Count == 0) { category_cart_items.Remove((META_ID)del_cart_item.ItemId); isRemovedItem = true; } break; } if(cur_count == 0) { err_msg = $"Not found itemid from category_cart_items. itemId : {del_cart_item.ItemId}"; result.setFail(ServerErrorCode.CartItemNotFound, err_msg); Log.getLogger().error(err_msg); return (result, isRemovedItem); } } cart_attribute.modifiedEntityAttribute(); return (result, isRemovedItem); } private async Task PayCurrencyForItem(List buy_cart_items) { var result = new Result(); var my_shop_product = getOwner(); var player = my_shop_product.getRootParent() as Player; NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!"); var money_action = player.getEntityAction(); if (null == money_action) { var err_msg = $"Fail to get Money Action : {nameof(MoneyAction)}."; result.setFail(ServerErrorCode.EntityActionNotFound, err_msg); Log.getLogger().error(err_msg); return result; } Dictionary changeMoneys = new Dictionary(); foreach(var buy_item in buy_cart_items) { if(MetaData.Instance._ItemTable.TryGetValue(buy_item.ItemId, out var item_data) == false) { var err_msg = $"Not Found ItemDataTable Item Id : {buy_item.ItemId}"; result.setFail(ServerErrorCode.CartMetaDataNotFound, err_msg); Log.getLogger().error(err_msg); return result; } result = player.checkItemFirstPurchaseItemCount(buy_item.ItemId, buy_item.Count); if (result.isFail()) return result; var check_currency_type = ShopHelper.checkCurrencyTypeFromCurrencyId(item_data.Buy_id); if (check_currency_type.result.isFail()) return check_currency_type.result; if (changeMoneys.TryGetValue(check_currency_type.currencyType, out var price) == false) { changeMoneys.Add(check_currency_type.currencyType, 0); } var item_first_purchase_history_agent_action = player.getEntityAction(); NullReferenceCheckHelper.throwIfNull(item_first_purchase_history_agent_action, () => $"item_first_purchase_history_agent_action is null !!!"); var item_price = item_data.BuyPrice; if (item_first_purchase_history_agent_action.isItemFirstPurchase(buy_item.ItemId)) { item_price = ((item_data.BuyPrice * 100) - (item_data.BuyPrice * item_data.Buy_Discount_Rate)) / 100; } changeMoneys[check_currency_type.currencyType] += (-1 * item_price * buy_item.Count); } foreach(var changeMoney in changeMoneys) { result = await money_action.changeMoney(changeMoney.Key, changeMoney.Value); if (result.isFail()) return result; } return result; } private async Task<(Result result, List? changed_Items)> TakeCartItem(List buy_cart_items) { var result = new Result(); var player = getOwner() as Player; NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!"); var inventory_action = player.getEntityAction(); if (null == inventory_action) { var err_msg = $"Fail to get Inventory Action : {nameof(InventoryActionBase)}."; result.setFail(ServerErrorCode.EntityActionNotFound, err_msg); Log.getLogger().error(err_msg); return (result, null); } List buy_changed_items = new List(); foreach(var buy_item in buy_cart_items) { (result, var changed_items) = await inventory_action.tryTakalbleToBag((META_ID)buy_item.ItemId, (ushort)buy_item.Count); if (result.isFail()) return (result, null); buy_changed_items.AddRange(changed_items); } return (result, buy_changed_items); } public Cart getCart() { NullReferenceCheckHelper.throwIfNull(m_Cart, () => $"m_Cart is null !!!"); return m_Cart; } async Task updateItemFirstPurchaseHistory(List buy_cart_items) { var result = new Result(); var player = getOwner() as Player; NullReferenceCheckHelper.throwIfNull(player, () => $"player is null !!!"); var item_first_purchase_history_agent_action = player.getEntityAction(); NullReferenceCheckHelper.throwIfNull(item_first_purchase_history_agent_action, () => $"item_first_purchase_history_agent_action is null !!!"); foreach (var buy_item in buy_cart_items) { if (!item_first_purchase_history_agent_action.isItemFirstPurchase(buy_item.ItemId)) continue; result = await item_first_purchase_history_agent_action.tryAddItemFirstPurchaseHistoryFromMetaId(buy_item.ItemId); if (result.isFail()) { return result; } } return result; } }