using Google.Protobuf; using Google.Protobuf.WellKnownTypes; using ServerCore; using ServerBase; using ServerCommon; using ServerCommon.BusinessLogDomain; using MetaAssets; using USER_GUID = System.String; namespace GameServer; public class ShopPurchaseInfo { public int m_shop_id { get; set; } public int m_shop_product_id { get; set; } public int m_shop_product_count { get; set; } public SpentInfo? m_spent { get; set; } public PurchaseInfo? m_purchase { get; set; } } public class SpentInfo { public ShopBuyType m_buy_type { get; set; } public CurrencyType m_currency_type { get; set; } public int m_item_id { get; set; } public double m_spent { get; set; } } public class PurchaseInfo { public ShopBuyType m_purchase_type { get; set; } public List? m_purchase_items { get; set; } public List? m_spend_items { get; set; } public CurrencyType m_purchase_currency_type { get; set; } = CurrencyType.None; public double m_purchase_currency { get; set; } } public static class ShopHelper { public static (Result result, MetaAssets.ShopMetaData? shop_data) checkShopIdFromTableData(int shop_id) { var result = new Result(); if (MetaData.Instance._ShopMetaTable.TryGetValue(shop_id, out var shop_products) == false) { result.setFail(ServerErrorCode.NotFoundShopId, $"Not Found ShopDataTable Shop Id : {shop_id}"); } return (result, shop_products); } public static IEnumerable getShopMetaData() => MetaData.Instance._ShopMetaTable.Select(data => data.Value).ToList(); public static (Result result, MetaAssets.ShopProductMetaData? product_data) checkProductIdFromTableData(int product_id) { var result = new Result(); if (MetaData.Instance._ShopProductMetaTable.TryGetValue(product_id, out var product_data) == false) { result.setFail(ServerErrorCode.NotFoundProductId, $"Not Found ProductDataTable Product Id : {product_id}"); } return (result, product_data); } public static (Result result, MetaAssets.ItemMetaData? item_data) checkItemIdFromTableData(int item_id) { var result = new Result(); if (MetaData.Instance._ItemTable.TryGetValue(item_id, out var item_data) == false) { result.setFail(ServerErrorCode.NotFoundItemTableId, $"Not Found ItemDataTable Item Id : {item_id}"); } return (result, item_data); } public static (Result result, MetaAssets.CurrencyMetaData? currency_data) checkCurrencyDataFromProductIdInTableData(int product_id) { var result = new Result(); if (MetaData.Instance.Meta.CurrencyMetaTable.CurrencyMetaDataListbyId.TryGetValue(product_id, out var currency_data) == false) { result.setFail(ServerErrorCode.NotFoundItemTableId, $"Not Found CurrencyData Product Id : {product_id}"); } return (result, currency_data); } public static (Result result, CurrencyType currencyType) checkCurrencyTypeFromCurrencyId(int currencyId) { var result = new Result(); (result, var currency_type) = checkCurrencyDataFromProductIdInTableData(currencyId); if (result.isFail()) return (result, CurrencyType.None); NullReferenceCheckHelper.throwIfNull(currency_type, () => $"currency_type is null !!!"); return (result, currency_type.CurrencyType); } public static (Result result, MetaAssets.BasicStyleMetaData? basic_style_data) checkBasicStyleDataFromTableData(UInt32 basic_style_id) { var result = new Result(); if (MetaData.Instance._BasicStyleMetaTable.TryGetValue((int)basic_style_id, out var basic_style_data) == false) { result.setFail(ServerErrorCode.NotFoundTable, $"Not Found BasicStyleDataTable basic style id : {basic_style_id}"); } return (result, basic_style_data); } public static bool createNewShopProductTradingMetersAttribute(ShopProductTradingMeterAttribute attribute, int shop_id, USER_GUID owner_guid) { var check = checkShopIdFromTableData(shop_id); if (check.result.isFail()) return false; attribute.ShopId = shop_id; // 1. 신규 생성 여부 체크 var is_create = attribute.ShopProductTradingMeterSubs.Count <= 0; // 2. sub data 초기화 attribute.ShopProductTradingMeterSubs.Clear(); // 3. 판매 종료시간 갱신 attribute.EndTime = calculateSaleEndTime(check.shop_data!.ResetTime); var product_max_count = check.shop_data.ShopProduct_List; // 4. 판매 물품 갱신 product_max_count = addNecessaryProductByGroupIdTable(check.shop_data.ShopProduct_Group_Id, product_max_count, attribute); addRandomProductByGroupIdTable(check.shop_data.ShopProduct_Group_Id, product_max_count, attribute); // 5. renewalCount 초기화 attribute.initRenewalCount(); if (is_create) { attribute.newEntityAttribute(); } else { attribute.modifiedEntityAttribute(); } return true; } private static int addNecessaryProductByGroupIdTable(int group_id, int max_count, ShopProductTradingMeterAttribute attribute) { if (MetaData.Instance._ShopNecessaryProductByGroupIdTable.TryGetValue(group_id, out var shopNecessaryProductList) != true) return max_count; foreach (var productInfo in shopNecessaryProductList) { if (max_count <= 0) { break; } var sub_attribute = new ShopProductTradingMeterSubAttribute { ProductId = productInfo.ID, LeftCount = productInfo.ProductType_BuyCount }; attribute.ShopProductTradingMeterSubs.Add(sub_attribute); max_count -= 1; } return max_count; } private static void addRandomProductByGroupIdTable(int group_id, int max_count, ShopProductTradingMeterAttribute attribute) { if (MetaData.Instance._ShopRandomProductByGroupIdTable.TryGetValue(group_id, out var shop_random_group) != true) return; var total_weight = shop_random_group.TotalWeight; List added_products = new(); while (true) { if (max_count <= 0 || total_weight <= 0) { break; } // randomProduct 에서 선택되지 않을 수 있음 ( 선택이 되지 않아도 상점 리스트는 차감됨 ) var spent_weight = selectRandomProduct(added_products, shop_random_group, total_weight); total_weight -= spent_weight; max_count -= 1; } attribute.ShopProductTradingMeterSubs.AddRange(added_products); } private static int selectRandomProduct(List added_list, ShopProductRandomGroupInfo shop_random_group, int total_weight) { var weight = 0; var random_weight = RandomHelper.next(0, total_weight); var spend_weight = 0; foreach (var productInfo in shop_random_group.RandomGroupList) { // 이미 추가되어 있으면 패스 var breakCheck = added_list.Any(randomProduct => randomProduct.ProductId == productInfo.ID); if (breakCheck) continue; weight += productInfo.Weight; // productInfo.Weight 가 0 인 경우 선택 자체가 되지 않음 ( random_weight >= 0 ) if (weight <= random_weight) continue; var sub_attribute = new ShopProductTradingMeterSubAttribute { ProductId = productInfo.ID, LeftCount = productInfo.ProductType_BuyCount }; added_list.Add(sub_attribute); spend_weight = productInfo.Weight; break; } return spend_weight; } public static Timestamp calculateSaleEndTime(int resetTime) { var interval_count = (DateTime.UtcNow.Ticks - ServerCommon.Constant.SHOP_DEFINE_TIME.Ticks) / (resetTime * TimeSpan.TicksPerMinute); return ServerCommon.Constant.SHOP_DEFINE_TIME.AddMinutes((interval_count + 1) * resetTime).ToTimestamp(); } public static async Task<(Result result, SpentInfo? spent_info)> checkRequiredPurchaseFromProductMeta(ShopProductMetaData productData, int purchaseCount, int discountRate) { var result = new Result(); var spent_info = new SpentInfo(); spent_info.m_buy_type = productData.Buy_Price_Type; switch (productData.Buy_Price_Type) { case ShopBuyType.Currency: var currency_data = ShopHelper.checkCurrencyDataFromProductIdInTableData(productData.Buy_Id); if (currency_data.result.isFail()) return (currency_data.result, null); NullReferenceCheckHelper.throwIfNull(currency_data.currency_data, () => $"currency_data.currency_data is null !!!"); spent_info.m_currency_type = currency_data.currency_data.CurrencyType; var price = await checkCaliumCurrency(spent_info.m_currency_type, productData.Buy_Price, discountRate); spent_info.m_spent = purchaseCount * price; break; case ShopBuyType.Item: spent_info.m_item_id = productData.Buy_Id; spent_info.m_spent = purchaseCount * productData.Buy_Price; break; case ShopBuyType.None: default: var err_msg = $"fail to get shop product meta data !!! : invalid shop buy type {productData.Buy_Price_Type}"; result.setFail(ServerErrorCode.InvalidShopBuyType, err_msg); Log.getLogger().error(err_msg); return (result, null); } return (result, spent_info); } public static bool checkRenewallableTime(ShopProductTradingMeterAttribute attribute) { bool is_renewallable_time = false; var now = DateTimeHelper.Current; var end_time = attribute.EndTime.ToDateTime(); var timeDifference = Math.Abs((now - end_time).TotalSeconds); // 차이가 ShopRenewalBlockTime초 이상이면 true, 그렇지 않으면 false if (timeDifference >= MetaHelper.GameConfigMeta.ShopRenewalBlockTime) { is_renewallable_time = true; } return is_renewallable_time; } public static bool renewalNewShopProductTradingMetersAttribute(USER_GUID owner_guid, ShopProductTradingMeterAttribute attribute, ShopMetaData shopData) { attribute.ShopId = shopData.Id; // 1. 신규 생성 여부 체크 //var is_create = attribute.ShopProductTradingMeterSubs.Count <= 0; // 2. sub data 초기화 attribute.ShopProductTradingMeterSubs.Clear(); var product_max_count = shopData.ShopProduct_List; // 3. 판매 물품 갱신 product_max_count = addNecessaryProductByGroupIdTable(shopData.ShopProduct_Group_Id, product_max_count, attribute); addRandomProductByGroupIdTable(shopData.ShopProduct_Group_Id, product_max_count, attribute); // 4. renewal count 중가 attribute.increaseRenewalCount(); // 리뉴얼은 무조건 상점이 존재해야 하므로 modified만 체크 attribute.modifiedEntityAttribute(); return true; } public static async Task checkCaliumCurrency(CurrencyType currencyType, double price, int discountRate) { var calculate_price = price; if (currencyType == CurrencyType.Calium) { calculate_price = await CaliumStorageHelper.calculateCaliumFromSapphire(price, true); } return (calculate_price * 100 - calculate_price * discountRate) / 100; } }