继续重构调整(UI)
- 引入 UniTask 和异步版本的 UIModule - 调整 Runtime/UIBase 和 Presentation 下的脚本命名空间到 SepCore.UI - 将原来的 5 层 UI 迁移到异步实现 - 规范各层的 UI 脚本命名
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using SepCore.Definition;
|
||||
using Entity.Weapon;
|
||||
|
||||
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,447 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SepCore.DataTable;
|
||||
using SepCore.Definition;
|
||||
using Entity;
|
||||
using Entity.EntityData;
|
||||
using CustomUtility;
|
||||
using GameFramework.DataTable;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace SepCore.UI
|
||||
{
|
||||
public sealed class ShopRefreshResult
|
||||
{
|
||||
public List<GoodsItemRawData> GoodsItems;
|
||||
public int RefreshPrice;
|
||||
}
|
||||
|
||||
public sealed class ShopPurchaseResult
|
||||
{
|
||||
public int GoodsIndex;
|
||||
public DisplayItemRawData DisplayItem;
|
||||
}
|
||||
|
||||
public class ShopUseCase : IUIUseCase
|
||||
{
|
||||
private readonly Player _player;
|
||||
private readonly int _currentLevel;
|
||||
private readonly Action _onShopFinish;
|
||||
|
||||
private readonly DRGoods[] _allGoods;
|
||||
private readonly IDataTable<DRProp> _propDataTable;
|
||||
private readonly IDataTable<DRWeapon> _weaponDataTable;
|
||||
private readonly IDataTable<DRLevelRarity> _levelRarityTable;
|
||||
|
||||
private readonly List<DRGoods> _validGoods = new();
|
||||
private readonly Dictionary<ItemRarity, List<DRGoods>> _goodsByRarity = new();
|
||||
|
||||
private readonly List<ShopGoodsSelection> _selections = new();
|
||||
private int _refreshTime;
|
||||
|
||||
private Player Player => _player;
|
||||
|
||||
private sealed class ShopGoodsSelection
|
||||
{
|
||||
public GoodsType GoodsType;
|
||||
public int GoodsTypeId;
|
||||
public int Price;
|
||||
}
|
||||
|
||||
public ShopUseCase(Player player, int currentLevel, Action onShopFinish)
|
||||
{
|
||||
_player = player;
|
||||
_currentLevel = currentLevel;
|
||||
_onShopFinish = onShopFinish;
|
||||
_allGoods = GameEntry.DataTable.GetDataTable<DRGoods>().ToArray();
|
||||
_propDataTable = GameEntry.DataTable.GetDataTable<DRProp>();
|
||||
_weaponDataTable = GameEntry.DataTable.GetDataTable<DRWeapon>();
|
||||
_levelRarityTable = GameEntry.DataTable.GetDataTable<DRLevelRarity>();
|
||||
|
||||
BuildGoodsRarityPools();
|
||||
}
|
||||
|
||||
public ShopRawData CreateInitialModel()
|
||||
{
|
||||
_refreshTime = 0;
|
||||
|
||||
Player player = Player;
|
||||
if (_player == null || player == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int currentLevel = _currentLevel;
|
||||
return new ShopRawData
|
||||
{
|
||||
CurrentLevel = currentLevel,
|
||||
RefreshPrice = currentLevel,
|
||||
PlayerCoin = player.Coin,
|
||||
GoodsItems = BuildRandomGoodsItems(4),
|
||||
PropItems = player.Props,
|
||||
PropMaxCount = -1,
|
||||
WeaponItems = player.Weapons,
|
||||
WeaponMaxCount = player.WeaponCapacity
|
||||
};
|
||||
}
|
||||
|
||||
public ShopRefreshResult TryRefresh(int cost)
|
||||
{
|
||||
Player player = Player;
|
||||
if (player == null || _player == null || player.Coin < cost)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
player.Coin -= cost;
|
||||
|
||||
int refreshPrice = (_refreshTime + 1) * _currentLevel;
|
||||
_refreshTime++;
|
||||
|
||||
return new ShopRefreshResult
|
||||
{
|
||||
GoodsItems = BuildRandomGoodsItems(4),
|
||||
RefreshPrice = refreshPrice
|
||||
};
|
||||
}
|
||||
|
||||
public ShopPurchaseResult TryPurchase(int goodsIndex)
|
||||
{
|
||||
if (goodsIndex < 0 || goodsIndex >= _selections.Count)
|
||||
{
|
||||
Log.Warning("ShopFormUseCase::TryPurchase: Invalid index");
|
||||
return null;
|
||||
}
|
||||
|
||||
ShopGoodsSelection selection = _selections[goodsIndex];
|
||||
if (selection == null)
|
||||
{
|
||||
Log.Warning("ShopFormUseCase::TryPurchase: Item already purchased.");
|
||||
return null;
|
||||
}
|
||||
|
||||
Player player = Player;
|
||||
if (player == null || player.Coin < selection.Price)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
DisplayItemRawData displayItem = ApplyGoodsPurchase(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 == null || _allGoods.Length == 0)
|
||||
{
|
||||
Log.Error("ShopFormUseCase::BuildRandomGoodsItems(): _allGoods is empty");
|
||||
return null;
|
||||
}
|
||||
|
||||
int finalCount = Mathf.Min(count, _allGoods.Length);
|
||||
List<GoodsItemRawData> goodsItems = new List<GoodsItemRawData>(finalCount);
|
||||
_selections.Clear();
|
||||
|
||||
int currentLevel = _currentLevel > 0 ? _currentLevel : 1;
|
||||
|
||||
for (int i = 0; i < finalCount; i++)
|
||||
{
|
||||
ItemRarity rarity = RarityUtility.SelectRarityForLevel(_levelRarityTable, currentLevel);
|
||||
DRGoods drGoods = PickGoodsByRarity(rarity);
|
||||
if (drGoods == null)
|
||||
{
|
||||
Log.Warning("ShopFormUseCase::BuildRandomGoodsItems: No available goods for selection.");
|
||||
break;
|
||||
}
|
||||
|
||||
GoodsItemRawData goodsItem = new GoodsItemRawData();
|
||||
int price;
|
||||
|
||||
if (drGoods.GoodsType == GoodsType.Prop)
|
||||
{
|
||||
DRProp drProp = _propDataTable.GetDataRow(drGoods.GoodsTypeId);
|
||||
if (drProp == null)
|
||||
{
|
||||
Log.Warning($"ShopFormUseCase::BuildRandomGoodsItems: Missing DRProp, id = {drGoods.GoodsTypeId}");
|
||||
continue;
|
||||
}
|
||||
|
||||
price = CalculateRandomizedPrice(drProp.Price, drProp.PriceRandomPercent);
|
||||
goodsItem.Title = drProp.Title;
|
||||
goodsItem.Type = "Prop";
|
||||
goodsItem.Rarity = drProp.Rarity;
|
||||
GameEntry.SpriteCache.GetSprite(drProp.IconAssetName, sprite => goodsItem.Icon = sprite);
|
||||
goodsItem.Description = ItemDescUtility.CreatePropDescription(drProp.Modifiers);
|
||||
}
|
||||
else if (drGoods.GoodsType == GoodsType.Weapon)
|
||||
{
|
||||
DRWeapon drWeapon = _weaponDataTable.GetDataRow(drGoods.GoodsTypeId);
|
||||
if (drWeapon == null)
|
||||
{
|
||||
Log.Warning($"ShopFormUseCase::BuildRandomGoodsItems: Missing DRWeapon, id = {drGoods.GoodsTypeId}");
|
||||
continue;
|
||||
}
|
||||
|
||||
price = CalculateRandomizedPrice(drWeapon.Price, drWeapon.PriceRandomPercent);
|
||||
goodsItem.Title = drWeapon.Title;
|
||||
goodsItem.Type = "Weapon";
|
||||
goodsItem.Rarity = drWeapon.Rarity;
|
||||
GameEntry.SpriteCache.GetSprite(drWeapon.IconAssetName, sprite => goodsItem.Icon = sprite);
|
||||
goodsItem.Description = ItemDescUtility.CreateWeaponDescription(drWeapon);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"ShopFormUseCase::BuildRandomGoodsItems: Unsupported goods type = {drGoods.GoodsType}");
|
||||
continue;
|
||||
}
|
||||
|
||||
goodsItem.Price = price;
|
||||
|
||||
_selections.Add(new ShopGoodsSelection
|
||||
{
|
||||
GoodsType = drGoods.GoodsType,
|
||||
GoodsTypeId = drGoods.GoodsTypeId,
|
||||
Price = price
|
||||
});
|
||||
|
||||
goodsItems.Add(goodsItem);
|
||||
}
|
||||
|
||||
return goodsItems;
|
||||
}
|
||||
|
||||
private void BuildGoodsRarityPools()
|
||||
{
|
||||
_goodsByRarity.Clear();
|
||||
_validGoods.Clear();
|
||||
|
||||
if (_allGoods == null) return;
|
||||
|
||||
foreach (DRGoods goods in _allGoods)
|
||||
{
|
||||
if (goods == null) continue;
|
||||
|
||||
if (!TryGetGoodsRarity(goods, out ItemRarity rarity))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_validGoods.Add(goods);
|
||||
|
||||
if (!_goodsByRarity.TryGetValue(rarity, out List<DRGoods> list))
|
||||
{
|
||||
list = new List<DRGoods>();
|
||||
_goodsByRarity.Add(rarity, list);
|
||||
}
|
||||
|
||||
list.Add(goods);
|
||||
}
|
||||
}
|
||||
|
||||
private DRGoods PickGoodsByRarity(ItemRarity rarity)
|
||||
{
|
||||
if (_goodsByRarity.TryGetValue(rarity, out List<DRGoods> 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(DRGoods goods, out ItemRarity rarity)
|
||||
{
|
||||
rarity = ItemRarity.None;
|
||||
if (goods == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (goods.GoodsType == GoodsType.Prop)
|
||||
{
|
||||
DRProp drProp = _propDataTable != null ? _propDataTable.GetDataRow(goods.GoodsTypeId) : null;
|
||||
if (drProp == null)
|
||||
{
|
||||
Log.Warning($"ShopFormUseCase::TryGetGoodsRarity: Missing DRProp, id = {goods.GoodsTypeId}");
|
||||
return false;
|
||||
}
|
||||
|
||||
rarity = drProp.Rarity;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (goods.GoodsType == GoodsType.Weapon)
|
||||
{
|
||||
DRWeapon drWeapon = _weaponDataTable != null ? _weaponDataTable.GetDataRow(goods.GoodsTypeId) : null;
|
||||
if (drWeapon == null)
|
||||
{
|
||||
Log.Warning($"ShopFormUseCase::TryGetGoodsRarity: Missing DRWeapon, id = {goods.GoodsTypeId}");
|
||||
return false;
|
||||
}
|
||||
|
||||
rarity = drWeapon.Rarity;
|
||||
return true;
|
||||
}
|
||||
|
||||
Log.Warning($"ShopFormUseCase::TryGetGoodsRarity: Unsupported goods type = {goods.GoodsType}");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int CalculateRandomizedPrice(int basePrice, float priceRandomPercent)
|
||||
{
|
||||
float normalizedPercent = priceRandomPercent > 1f ? priceRandomPercent / 100f : priceRandomPercent;
|
||||
normalizedPercent = Mathf.Max(0f, normalizedPercent);
|
||||
float offsetPercent = Random.Range(-normalizedPercent, normalizedPercent);
|
||||
int finalPrice = Mathf.RoundToInt(basePrice * (1f + offsetPercent));
|
||||
return Mathf.Max(1, finalPrice);
|
||||
}
|
||||
|
||||
private DisplayItemRawData ApplyGoodsPurchase(ShopGoodsSelection goods)
|
||||
{
|
||||
if (goods == null || Player == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (goods.GoodsType == GoodsType.Prop)
|
||||
{
|
||||
DRProp drProp = _propDataTable != null ? _propDataTable.GetDataRow(goods.GoodsTypeId) : null;
|
||||
if (drProp == null || drProp.Modifiers == null || drProp.Modifiers.Length == 0)
|
||||
{
|
||||
Log.Warning("ShopFormUseCase::ApplyGoodsPurchase: Prop modifiers are empty");
|
||||
return null;
|
||||
}
|
||||
|
||||
Player.AddProp(new PropItem(drProp.Modifiers, drProp.Rarity, drProp.Title, drProp.IconAssetName));
|
||||
return new DisplayItemRawData
|
||||
{
|
||||
IconAssetName = drProp.IconAssetName,
|
||||
Rarity = drProp.Rarity,
|
||||
IsWeapon = false
|
||||
};
|
||||
}
|
||||
|
||||
if (goods.GoodsType == GoodsType.Weapon)
|
||||
{
|
||||
if (Player.Weapons != null && Player.Weapons.Count >= Player.WeaponCapacity)
|
||||
{
|
||||
Log.Warning("ShopFormUseCase::ApplyGoodsPurchase: Weapon capacity is full.");
|
||||
return null;
|
||||
}
|
||||
|
||||
DRWeapon drWeapon = _weaponDataTable != null ? _weaponDataTable.GetDataRow(goods.GoodsTypeId) : null;
|
||||
if (drWeapon == null)
|
||||
{
|
||||
Log.Warning($"ShopFormUseCase::ApplyGoodsPurchase: Missing DRWeapon, id = {goods.GoodsTypeId}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var weaponData = CreateWeaponData(goods.GoodsTypeId);
|
||||
if (weaponData == null)
|
||||
{
|
||||
Log.Warning(
|
||||
$"ShopFormUseCase::ApplyGoodsPurchase: Unsupported weapon type id = {goods.GoodsTypeId}");
|
||||
return null;
|
||||
}
|
||||
|
||||
GameEntry.Entity.ShowWeapon(weaponData);
|
||||
|
||||
return new DisplayItemRawData
|
||||
{
|
||||
IconAssetName = drWeapon.IconAssetName,
|
||||
Rarity = drWeapon.Rarity,
|
||||
IsWeapon = true
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private WeaponData CreateWeaponData(int weaponTypeId)
|
||||
{
|
||||
int entityId = GameEntry.Entity.GenerateSerialId();
|
||||
int ownerId = Player.Id;
|
||||
CampType ownerCamp = CampType.Player;
|
||||
|
||||
WeaponType weaponType = (WeaponType)weaponTypeId;
|
||||
switch (weaponType)
|
||||
{
|
||||
case WeaponType.WeaponKnife:
|
||||
return new WeaponKnifeData(entityId, ownerId, ownerCamp);
|
||||
case WeaponType.WeaponHandgun:
|
||||
return new WeaponHandgunData(entityId, ownerId, ownerCamp);
|
||||
case WeaponType.WeaponSlash:
|
||||
return new WeaponSlashData(entityId, ownerId, ownerCamp);
|
||||
case WeaponType.WeaponLightning:
|
||||
return new WeaponLightningData(entityId, ownerId, ownerCamp);
|
||||
case WeaponType.WeaponLance:
|
||||
return new WeaponLanceData(entityId, ownerId, ownerCamp);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryRecycleWeapon(int argsIndex, int argsPrice)
|
||||
{
|
||||
Player player = Player;
|
||||
if (player == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var weapons = player.Weapons;
|
||||
if (weapons == null || weapons.Count <= 1)
|
||||
{
|
||||
Log.Warning("ShopFormUseCase::TryRecycleWeapon: Can not recycle the last weapon.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (argsIndex < 0 || argsIndex >= weapons.Count)
|
||||
{
|
||||
Log.Warning($"ShopFormUseCase::TryRecycleWeapon: Invalid weapon index = {argsIndex}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var weapon = weapons[argsIndex];
|
||||
if (weapon == null)
|
||||
{
|
||||
Log.Warning("ShopFormUseCase::TryRecycleWeapon: Weapon is null.");
|
||||
return false;
|
||||
}
|
||||
|
||||
int recyclePrice = Mathf.Max(0, argsPrice);
|
||||
if (!player.RemoveWeapon(weapon))
|
||||
{
|
||||
Log.Warning("ShopFormUseCase::TryRecycleWeapon: Remove weapon failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
player.Coin += recyclePrice;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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