Files
vampire-like/Assets/GameMain/Scripts/Runtime/UIBase/Shop/ShopUseCase.cs
T

331 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using SepCore.Definition;
using SepCore.Entity;
using SepCore.CustomUtility;
using UnityEngine;
using UnityGameFramework.Runtime;
using Random = UnityEngine.Random;
namespace SepCore.UI
{
public class ShopUseCase : IUIUseCase
{
private readonly Player _player;
private readonly int _currentLevel;
private readonly Action _onShopFinish;
private readonly IReadOnlyList<GoodsConfig> _allGoods;
private readonly List<GoodsConfig> _validGoods = new();
private readonly Dictionary<Rarity, List<GoodsConfig>> _goodsByRarity = new();
private readonly List<ShopGoodsSelection> _selections = new();
private int _refreshTime;
public ShopUseCase(Player player, int currentLevel, Action onShopFinish)
{
_player = player;
_currentLevel = currentLevel;
_onShopFinish = onShopFinish;
_allGoods = GameEntry.Luban.GetTable<GoodsConfig>();
BuildGoodsRarityPools();
}
public ShopRawData CreateInitialModel()
{
_refreshTime = 0;
return new ShopRawData
{
CurrentLevel = _currentLevel,
RefreshPrice = _currentLevel,
PlayerCoin = _player.Coin,
GoodsItems = BuildRandomGoodsItems(GameEntry.Luban.Global.ShopGoodsCount),
PropItems = _player.Props,
PropMaxCount = GameEntry.Luban.Global.PropMaxCount,
WeaponItems = _player.Weapons,
WeaponMaxCount = _player.WeaponCapacity
};
}
public ShopRefreshResult TryRefresh(int cost)
{
if (_player.Coin < cost)
{
return null;
}
_player.Coin -= cost;
int refreshPrice = (_refreshTime + 1) * _currentLevel;
_refreshTime++;
return new ShopRefreshResult
{
GoodsItems = BuildRandomGoodsItems(GameEntry.Luban.Global.ShopGoodsCount),
RefreshPrice = refreshPrice
};
}
public async UniTask<ShopPurchaseResult> TryPurchaseAsync(int goodsIndex)
{
if (goodsIndex < 0 || goodsIndex >= _selections.Count)
{
Log.Error("ShopFormUseCase::TryPurchase: Invalid index");
return null;
}
ShopGoodsSelection selection = _selections[goodsIndex];
if (selection == null)
{
Log.Warning("ShopFormUseCase::TryPurchase: Item already purchased.");
return null;
}
if (_player.Coin < selection.Price)
{
return null;
}
DisplayItemRawData displayItem = await ApplyGoodsPurchaseAsync(selection);
if (displayItem == null)
{
return null;
}
_player.Coin -= selection.Price;
_selections[goodsIndex] = null;
return new ShopPurchaseResult
{
GoodsIndex = goodsIndex,
DisplayItem = displayItem
};
}
public void Continue()
{
_onShopFinish?.Invoke();
}
private List<GoodsItemRawData> BuildRandomGoodsItems(int count)
{
if (_allGoods.Count == 0)
{
Log.Error("ShopFormUseCase::BuildRandomGoodsItems(): _allGoods is empty");
return null;
}
int finalCount = Mathf.Min(count, _allGoods.Count);
List<GoodsItemRawData> goodsItems = new List<GoodsItemRawData>(finalCount);
_selections.Clear();
int currentLevel = _currentLevel;
float priceRandomPercent = GameEntry.Luban.Global.ShopPriceRandomPercent;
for (int i = 0; i < finalCount; i++)
{
Rarity rarity = RollRarity(currentLevel);
GoodsConfig goodsConfig = PickGoodsByRarity(rarity);
if (goodsConfig == null)
{
Log.Error("ShopFormUseCase::BuildRandomGoodsItems: No available goods for selection.");
break;
}
GoodsItemRawData goodsItem = new GoodsItemRawData();
int price;
if (goodsConfig.GoodsType == GoodsType.Prop)
{
PropConfig propConfig = GameEntry.Luban.Get<PropConfig>(goodsConfig.PropId);
int basePrice = propConfig.Price;
price = CalculateRandomizedPrice(basePrice, priceRandomPercent);
goodsItem.ItemType = ItemType.Prop;
goodsItem.Rarity = propConfig.Rarity;
goodsItem.DataRow = propConfig;
}
else if (goodsConfig.GoodsType == GoodsType.Weapon)
{
WeaponConfig weaponConfig = GameEntry.Luban.Get<WeaponConfig>((int)goodsConfig.WeaponType);
int basePrice = weaponConfig.Price;
price = CalculateRandomizedPrice(basePrice, priceRandomPercent);
goodsItem.ItemType = ItemType.Weapon;
goodsItem.Rarity = weaponConfig.Rarity;
goodsItem.DataRow = weaponConfig;
}
else
{
Log.Warning(
$"ShopFormUseCase::BuildRandomGoodsItems: Unsupported goods type = {goodsConfig.GoodsType}");
continue;
}
goodsItem.Price = price;
_selections.Add(new ShopGoodsSelection
{
GoodsType = goodsConfig.GoodsType,
PropId = goodsConfig.PropId,
WeaponType = goodsConfig.WeaponType,
Price = price
});
goodsItems.Add(goodsItem);
}
return goodsItems;
}
private void BuildGoodsRarityPools()
{
_goodsByRarity.Clear();
_validGoods.Clear();
foreach (GoodsConfig goods in _allGoods)
{
if (!TryGetGoodsRarity(goods, out Rarity rarity))
{
continue;
}
_validGoods.Add(goods);
if (!_goodsByRarity.TryGetValue(rarity, out List<GoodsConfig> list))
{
list = new List<GoodsConfig>();
_goodsByRarity.Add(rarity, list);
}
list.Add(goods);
}
}
private GoodsConfig PickGoodsByRarity(Rarity rarity)
{
if (_goodsByRarity.TryGetValue(rarity, out List<GoodsConfig> list) && list.Count > 0)
{
return list[Random.Range(0, list.Count)];
}
if (_validGoods.Count > 0)
{
return _validGoods[Random.Range(0, _validGoods.Count)];
}
return null;
}
private bool TryGetGoodsRarity(GoodsConfig goods, out Rarity rarity)
{
rarity = Rarity.None;
if (goods.GoodsType == GoodsType.Prop)
{
PropConfig propConfig = GameEntry.Luban.Get<PropConfig>(goods.PropId);
rarity = propConfig.Rarity;
return true;
}
if (goods.GoodsType == GoodsType.Weapon)
{
WeaponConfig weaponConfig = GameEntry.Luban.Get<WeaponConfig>((int)goods.WeaponType);
rarity = weaponConfig.Rarity;
return true;
}
Log.Error("ShopUseCase.TryGetGoodsRarity: Unsupported goods type '{0}'.", goods.GoodsType);
return false;
}
private Rarity RollRarity(int level)
{
return RarityUtility.SelectRarityForLevel(level);
}
private static int CalculateRandomizedPrice(int basePrice, float priceRandomPercent)
{
float offsetPercent = Random.Range(-priceRandomPercent, priceRandomPercent);
int finalPrice = Mathf.RoundToInt(basePrice * (1f + offsetPercent));
return Mathf.Max(GameEntry.Luban.Global.ShopMinPrice, finalPrice);
}
private async UniTask<DisplayItemRawData> ApplyGoodsPurchaseAsync(ShopGoodsSelection goods)
{
if (goods.GoodsType == GoodsType.Prop)
{
PropConfig propConfig = GameEntry.Luban.Get<PropConfig>(goods.PropId);
_player.AddProp(new PropItem(propConfig));
return new DisplayItemRawData
{
IconAssetName = propConfig.Sprite,
Rarity = propConfig.Rarity,
IsWeapon = false
};
}
if (goods.GoodsType == GoodsType.Weapon)
{
if (_player.Weapons.Count >= _player.WeaponCapacity)
{
Log.Warning("ShopFormUseCase::ApplyGoodsPurchase: Weapon capacity is full.");
return null;
}
WeaponConfig weaponConfig = GameEntry.Luban.Get<WeaponConfig>((int)goods.WeaponType);
int id = GameEntry.Entity.NextId();
var weaponData = EntityDataFactory.Create(id, weaponConfig, _player.Id, CampType.Player);
WeaponBase weapon = await _player.AddWeaponAsync(weaponData);
if (weapon == null)
{
Log.Warning(
$"ShopFormUseCase::ApplyGoodsPurchase: Show weapon failed, weapon type id = {goods.WeaponType}");
return null;
}
return new DisplayItemRawData
{
IconAssetName = weaponConfig.Sprite,
Rarity = weaponConfig.Rarity,
IsWeapon = true
};
}
return null;
}
public bool TryRecycleWeapon(int argsIndex, int argsPrice)
{
var weapons = _player.Weapons;
if (weapons.Count <= 1)
{
Log.Warning("ShopFormUseCase::TryRecycleWeapon: Can not recycle the last weapon.");
return false;
}
var weapon = weapons[argsIndex];
int recyclePrice = Mathf.Max(0, argsPrice);
if (!_player.RemoveWeapon(weapon))
{
Log.Warning("ShopFormUseCase::TryRecycleWeapon: Remove weapon failed.");
return false;
}
_player.Coin += recyclePrice;
return true;
}
public int CalculateRecyclePrice(int basePrice)
{
return Mathf.FloorToInt(basePrice * GameEntry.Luban.Global.WeaponRecycleRate);
}
}
}