388 lines
14 KiB
C#
388 lines
14 KiB
C#
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 = LocalizationService.Get("store.error.player_data_missing", "玩家数据丢失");
|
|
return false;
|
|
}
|
|
|
|
if (itemSO == null)
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.item_data_missing", "商品数据丢失");
|
|
return false;
|
|
}
|
|
|
|
if (!itemSO.isOnShelf || !itemSO.canbepurchased)
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.item_not_on_shelf", "物品未上架");
|
|
return false;
|
|
}
|
|
|
|
if (packageCount <= 0)
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.invalid_purchase_count", "本店禁止无实物交易");
|
|
return false;
|
|
}
|
|
|
|
if (RequiresSinglePurchase(itemSO) && packageCount > 1)
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.single_purchase_only", "该物品仅支持单份购买");
|
|
return false;
|
|
}
|
|
|
|
if (itemSO.costRequirements == null || itemSO.costRequirements.Count == 0)
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.cannot_purchase", "不可购买");
|
|
return false;
|
|
}
|
|
|
|
var primaryCost = itemSO.costRequirements[0];
|
|
if (itemSO.associatedSelectableEquipmentRewardSource != null)
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.selector_required", "该物品需要通过选择器购买");
|
|
return false;
|
|
}
|
|
|
|
if (!IsSupportedCurrency(primaryCost.currencyType))
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.cannot_purchase", "不可购买");
|
|
return false;
|
|
}
|
|
|
|
long longGrantedCount = (long)Mathf.Max(1, itemSO.itemSinglePurchaseQty) * packageCount;
|
|
if (longGrantedCount > int.MaxValue)
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.purchase_limit_per_transaction", "达到单次购买限额");
|
|
return false;
|
|
}
|
|
|
|
grantedCount = (int)longGrantedCount;
|
|
if (itemSO.itemPurchaseQuota >= 0 && itemSO.purchasedCount + grantedCount > itemSO.itemPurchaseQuota)
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.purchase_quota_exceeded", "超过限购额度");
|
|
return false;
|
|
}
|
|
|
|
long totalCostLong = (long)primaryCost.amount * packageCount;
|
|
if (totalCostLong > int.MaxValue)
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.purchase_limit_per_transaction", "达到单次购买限额");
|
|
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 = GetInsufficientCurrencyMessage(primaryCost.currencyType);
|
|
return false;
|
|
}
|
|
|
|
if (!TrySpendCurrency(primaryCost.currencyType, totalCost))
|
|
{
|
|
failureMessage = GetInsufficientCurrencyMessage(primaryCost.currencyType);
|
|
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 = LocalizationService.Get("store.error.item_data_missing", "商品数据丢失");
|
|
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 = LocalizationService.Get("store.error.grant_logic_missing", "当前未配置发放逻辑");
|
|
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 = LocalizationService.Get("store.error.grant_logic_missing", "当前未配置发放逻辑");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static bool GrantPurchasedItem(Player_SO playerData, storeItemSO itemSO, int grantedCount, out string failureMessage)
|
|
{
|
|
failureMessage = string.Empty;
|
|
if (itemSO == null)
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.item_data_missing", "商品数据丢失");
|
|
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 = LocalizationService.Get("store.error.grant_logic_missing", "当前未配置发放逻辑");
|
|
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 = LocalizationService.Get("store.error.grant_logic_missing", "当前未配置发放逻辑");
|
|
return false;
|
|
|
|
case storeItemSO.ItemType.character:
|
|
case storeItemSO.ItemType.song:
|
|
case storeItemSO.ItemType.storyPassage:
|
|
return StoreOwnershipLedger.EnsureInstance().TryGrantOwnership(itemSO, out failureMessage);
|
|
|
|
default:
|
|
failureMessage = LocalizationService.Get("store.error.grant_logic_missing", "当前未配置发放逻辑");
|
|
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 : LocalizationService.Get("item.unknown.store_item", "Unknown Item"));
|
|
builder.Append(" | 花费算力=");
|
|
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 string GetInsufficientCurrencyMessage(storeItemSO.CurrencyType currencyType)
|
|
{
|
|
return 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;
|
|
}
|
|
}
|
|
}
|