using System.Text; using UnityEngine; public static class StoreExpBottlePurchaseService { private enum ConsumableGrantType { None, ExpBottle, GrowthMaterial, EquipmentConsumable } public static bool TryPurchase(Player_SO playerData, storeItemSO itemSO, int packageCount, out string failureMessage, out int grantedCount) { failureMessage = string.Empty; grantedCount = 0; if (playerData == null) { failureMessage = "玩家数据丢失"; return false; } if (itemSO == null) { failureMessage = "商品数据丢失"; return false; } if (!itemSO.isOnShelf || !itemSO.canbepurchased) { failureMessage = "物品未上架"; return false; } if (packageCount <= 0) { failureMessage = "本店禁止无实物交易"; return false; } if (RequiresSinglePurchase(itemSO) && packageCount > 1) { failureMessage = "该物品仅支持单份购买"; return false; } if (itemSO.costRequirements == null || itemSO.costRequirements.Count == 0) { failureMessage = "不可购买"; return false; } var primaryCost = itemSO.costRequirements[0]; if (itemSO.associatedSelectableEquipmentRewardSource != null) { failureMessage = "该物品需要通过选择器购买"; return false; } if (!IsSupportedCurrency(primaryCost.currencyType)) { failureMessage = "不可购买"; return false; } long longGrantedCount = (long)Mathf.Max(1, itemSO.itemSinglePurchaseQty) * packageCount; if (longGrantedCount > int.MaxValue) { failureMessage = "达到单次购买限额"; return false; } grantedCount = (int)longGrantedCount; if (itemSO.itemPurchaseQuota >= 0 && itemSO.purchasedCount + grantedCount > itemSO.itemPurchaseQuota) { failureMessage = "超过限购额度"; return false; } long totalCostLong = (long)primaryCost.amount * packageCount; if (totalCostLong > int.MaxValue) { failureMessage = "达到单次购买限额"; return false; } int totalCost = (int)totalCostLong; if (!ValidateGrantTarget(itemSO, out failureMessage)) { return false; } PlayerEconomyLedger.EnsureInstance().AttachPlayerData(playerData); StoreOwnershipLedger.EnsureInstance().InitializeIfNeeded(); if (!HasEnoughCurrency(primaryCost.currencyType, totalCost)) { failureMessage = "货币不足"; return false; } if (!TrySpendCurrency(primaryCost.currencyType, totalCost)) { failureMessage = "货币不足"; return false; } if (!GrantPurchasedItem(playerData, itemSO, grantedCount, out failureMessage)) { RefundCurrency(primaryCost.currencyType, totalCost); return false; } DebugPurchaseSuccess(itemSO, totalCost, grantedCount); return true; } private static bool RequiresSinglePurchase(storeItemSO itemSO) { if (itemSO == null) { return false; } return itemSO.itemType == storeItemSO.ItemType.character || itemSO.itemType == storeItemSO.ItemType.song || itemSO.itemType == storeItemSO.ItemType.storyPassage; } private static bool ValidateGrantTarget(storeItemSO itemSO, out string failureMessage) { failureMessage = string.Empty; if (itemSO == null) { failureMessage = "商品数据丢失"; return false; } switch (itemSO.itemType) { case storeItemSO.ItemType.consumable: ConsumableGrantType grantType; ExpBottleKind bottleKind; DushMaterialKind materialKind; EquipmentConsumableKind equipmentConsumableKind; if (!TryResolveConsumableGrant(itemSO, out grantType, out bottleKind, out materialKind, out equipmentConsumableKind)) { failureMessage = "当前未配置发放逻辑"; return false; } return true; case storeItemSO.ItemType.character: case storeItemSO.ItemType.song: case storeItemSO.ItemType.storyPassage: return StoreOwnershipLedger.EnsureInstance().TryGrantOwnershipPreview(itemSO, out failureMessage); default: failureMessage = "当前未配置发放逻辑"; return false; } } private static bool GrantPurchasedItem(Player_SO playerData, storeItemSO itemSO, int grantedCount, out string failureMessage) { failureMessage = string.Empty; if (itemSO == null) { failureMessage = "商品数据丢失"; return false; } switch (itemSO.itemType) { case storeItemSO.ItemType.consumable: ConsumableGrantType grantType; ExpBottleKind bottleKind; DushMaterialKind materialKind; EquipmentConsumableKind equipmentConsumableKind; if (!TryResolveConsumableGrant(itemSO, out grantType, out bottleKind, out materialKind, out equipmentConsumableKind)) { failureMessage = "当前未配置发放逻辑"; return false; } if (grantType == ConsumableGrantType.ExpBottle) { ExpBottleLedger.EnsureInstance().AttachPlayerData(playerData); ExpBottleLedger.EnsureInstance().Add(bottleKind, grantedCount); return true; } if (grantType == ConsumableGrantType.GrowthMaterial) { DushMaterialLedger.EnsureInstance().AttachPlayerData(playerData); DushMaterialLedger.EnsureInstance().Add(materialKind, grantedCount); return true; } if (grantType == ConsumableGrantType.EquipmentConsumable) { EquipmentConsumableLedger.EnsureInstance().Add(equipmentConsumableKind, grantedCount); return true; } failureMessage = "当前未配置发放逻辑"; return false; case storeItemSO.ItemType.character: case storeItemSO.ItemType.song: case storeItemSO.ItemType.storyPassage: return StoreOwnershipLedger.EnsureInstance().TryGrantOwnership(itemSO, out failureMessage); default: failureMessage = "当前未配置发放逻辑"; return false; } } private static void DebugPurchaseSuccess(storeItemSO itemSO, int totalCost, int grantedCount) { var builder = new StringBuilder(); builder.Append("[StorePurchase] 已成功购买:"); builder.Append(itemSO != null ? itemSO.itemName : "未知物品"); builder.Append(" | 花费Coins="); builder.Append(totalCost); builder.Append(" | 发放数量="); builder.Append(grantedCount); if (itemSO != null && itemSO.itemType != storeItemSO.ItemType.consumable) { builder.Append(" | 已写入OwnershipSave"); Debug.Log(builder.ToString()); return; } ConsumableGrantType grantType; ExpBottleKind bottleKind; DushMaterialKind materialKind; EquipmentConsumableKind equipmentConsumableKind; if (!TryResolveConsumableGrant(itemSO, out grantType, out bottleKind, out materialKind, out equipmentConsumableKind)) { Debug.Log(builder.ToString()); return; } if (grantType == ConsumableGrantType.GrowthMaterial) { builder.Append(" | 突破材料库存="); builder.Append(DushMaterialCatalog.GetDisplayName(materialKind)); builder.Append(":"); builder.Append(DushMaterialLedger.EnsureInstance().GetCount(materialKind)); Debug.Log(builder.ToString()); return; } if (grantType == ConsumableGrantType.EquipmentConsumable) { builder.Append(" | 装备材料库存="); builder.Append(EquipmentConsumableCatalog.GetDisplayName(equipmentConsumableKind)); builder.Append(":"); builder.Append(EquipmentConsumableLedger.EnsureInstance().GetCount(equipmentConsumableKind)); Debug.Log(builder.ToString()); return; } var snapshot = ExpBottleLedger.EnsureInstance().GetSnapshot(); builder.Append(" | 经验瓶库存:"); bool appendedAny = false; for (int i = 0; i < snapshot.Count; i++) { var entry = snapshot[i]; ExpBottleKind kind; if (!ExpBottleCatalog.TryGetKind(entry.key, out kind)) { continue; } if (appendedAny) { builder.Append(" ; "); } builder.Append(ExpBottleCatalog.GetDisplayName(kind)); builder.Append("="); builder.Append(entry.count); appendedAny = true; } Debug.Log(builder.ToString()); } private static bool TryResolveConsumableGrant(storeItemSO itemSO, out ConsumableGrantType grantType, out ExpBottleKind bottleKind, out DushMaterialKind materialKind, out EquipmentConsumableKind equipmentConsumableKind) { grantType = ConsumableGrantType.None; bottleKind = default(ExpBottleKind); materialKind = default(DushMaterialKind); equipmentConsumableKind = default(EquipmentConsumableKind); if (itemSO == null) { return false; } if (itemSO.associatedExpBottle != null) { grantType = ConsumableGrantType.ExpBottle; bottleKind = itemSO.associatedExpBottle.bottleKind; return true; } if (itemSO.associatedGrowthMaterial != null) { grantType = ConsumableGrantType.GrowthMaterial; materialKind = itemSO.associatedGrowthMaterial.materialKind; return true; } if (itemSO.associatedEquipmentConsumable != null) { grantType = ConsumableGrantType.EquipmentConsumable; equipmentConsumableKind = itemSO.associatedEquipmentConsumable.consumableKind; return true; } if (StoreExpBottleGrantResolver.TryResolve(itemSO, out bottleKind)) { grantType = ConsumableGrantType.ExpBottle; return true; } return false; } private static bool IsSupportedCurrency(storeItemSO.CurrencyType currencyType) { return currencyType == storeItemSO.CurrencyType.coins || currencyType == storeItemSO.CurrencyType.material; } private static bool HasEnoughCurrency(storeItemSO.CurrencyType currencyType, int amount) { switch (currencyType) { case storeItemSO.CurrencyType.coins: return PlayerEconomyLedger.EnsureInstance().HasEnoughCoins(amount); case storeItemSO.CurrencyType.material: return PlayerEconomyLedger.EnsureInstance().HasEnoughMaterial(amount); default: return false; } } private static bool TrySpendCurrency(storeItemSO.CurrencyType currencyType, int amount) { switch (currencyType) { case storeItemSO.CurrencyType.coins: return PlayerEconomyLedger.EnsureInstance().TrySpendCoins(amount); case storeItemSO.CurrencyType.material: return PlayerEconomyLedger.EnsureInstance().TrySpendMaterial(amount); default: return false; } } private static void RefundCurrency(storeItemSO.CurrencyType currencyType, int amount) { switch (currencyType) { case storeItemSO.CurrencyType.coins: PlayerEconomyLedger.EnsureInstance().AddCoins(amount); break; case storeItemSO.CurrencyType.material: PlayerEconomyLedger.EnsureInstance().AddMaterial(amount); break; } } }