整理 ShopUseCase 的相关脚本
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
using SepCore.Definition;
|
||||
|
||||
namespace SepCore.UI
|
||||
{
|
||||
public class DisplayItemRawData
|
||||
{
|
||||
public string IconAssetName;
|
||||
public Rarity Rarity;
|
||||
public bool IsWeapon;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a8892ea402a6ca4b943466ac00676e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.UI
|
||||
{
|
||||
public class DisplayListAreaRawData
|
||||
{
|
||||
public string Title;
|
||||
public int CurrentCount;
|
||||
public int MaxCount = -1;
|
||||
public DisplayItemRawData[] ItemContexts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06d87c8d30eefa645b7d84c306bf2cb8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
using SepCore.Definition;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace SepCore.UI
|
||||
{
|
||||
public class GoodsItemRawData
|
||||
{
|
||||
public Rarity Rarity;
|
||||
public ItemType ItemType;
|
||||
public int Price;
|
||||
public object DataRow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b9270fdc0ddab146bd3b5d9b2261a39
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using SepCore.Definition;
|
||||
|
||||
namespace SepCore.UI
|
||||
{
|
||||
public class ShopGoodsSelection
|
||||
{
|
||||
public GoodsType GoodsType;
|
||||
public int PropId;
|
||||
public WeaponType WeaponType;
|
||||
public int Price;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df119fea99cc4e3e9fd080a7be4ec3ab
|
||||
timeCreated: 1789377913
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace SepCore.UI
|
||||
{
|
||||
public sealed class ShopPurchaseResult
|
||||
{
|
||||
public int GoodsIndex;
|
||||
public DisplayItemRawData DisplayItem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a48aa67c1024d86b79cf1f5550084a0
|
||||
timeCreated: 1789377892
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using SepCore.Definition;
|
||||
using SepCore.Entity;
|
||||
|
||||
namespace SepCore.UI
|
||||
{
|
||||
public class ShopRawData
|
||||
{
|
||||
public int CurrentLevel;
|
||||
public int RefreshPrice;
|
||||
public int PlayerCoin;
|
||||
public List<GoodsItemRawData> GoodsItems;
|
||||
public IReadOnlyList<PropItem> PropItems;
|
||||
public int PropMaxCount = -1;
|
||||
public IReadOnlyList<WeaponBase> WeaponItems;
|
||||
public int WeaponMaxCount = -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 533bff5f9d3bc7847af44dcd1cdffba3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SepCore.UI
|
||||
{
|
||||
public sealed class ShopRefreshResult
|
||||
{
|
||||
public List<GoodsItemRawData> GoodsItems;
|
||||
public int RefreshPrice;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0870748189e94cfa8eec2645bec5fd39
|
||||
timeCreated: 1789377879
|
||||
@@ -0,0 +1,330 @@
|
||||
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 / 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<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 / 1000f));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e973748d1bd846349d42e219a80f07c2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user