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 _allGoods; private readonly List _validGoods = new(); private readonly Dictionary> _goodsByRarity = new(); private readonly List _selections = new(); private int _refreshTime; public ShopUseCase(Player player, int currentLevel, Action onShopFinish) { _player = player; _currentLevel = currentLevel; _onShopFinish = onShopFinish; _allGoods = GameEntry.Luban.GetTable(); 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 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 BuildRandomGoodsItems(int count) { if (_allGoods.Count == 0) { Log.Error("ShopFormUseCase::BuildRandomGoodsItems(): _allGoods is empty"); return null; } int finalCount = Mathf.Min(count, _allGoods.Count); List goodsItems = new List(finalCount); _selections.Clear(); int currentLevel = _currentLevel; float priceRandomPercent = GameEntry.Luban.Global.ShopPriceRandomPercent / 1000f; 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(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((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 list)) { list = new List(); _goodsByRarity.Add(rarity, list); } list.Add(goods); } } private GoodsConfig PickGoodsByRarity(Rarity rarity) { if (_goodsByRarity.TryGetValue(rarity, out List 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(goods.PropId); rarity = propConfig.Rarity; return true; } if (goods.GoodsType == GoodsType.Weapon) { WeaponConfig weaponConfig = GameEntry.Luban.Get((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 ApplyGoodsPurchaseAsync(ShopGoodsSelection goods) { if (goods.GoodsType == GoodsType.Prop) { PropConfig propConfig = GameEntry.Luban.Get(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((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 / 1000f)); } } }