using System.Collections.Generic; using Cysharp.Threading.Tasks; using SepCore.Event; using SepCore.Definition; using SepCore.Entity.Weapon; using GameFramework.Event; using SepCore.DataTable; using UnityEngine; using UnityGameFramework.Runtime; namespace SepCore.UI { public class ShopController : UIControllerBase { private ShopUseCase _useCase; private ShopRawData _rawData; private bool _tooltipLocked = false; protected override UIFormType UIFormType => UIFormType.ShopForm; protected override void RefreshUI(ShopForm form, ShopContext context) { form.RefreshUI(context); } protected override void SubscribeCustomEvents() { GameEntry.Event.Subscribe(ShopRefreshEventArgs.EventId, ShopRefresh); GameEntry.Event.Subscribe(ShopPurchaseEventArgs.EventId, ShopPurchase); GameEntry.Event.Subscribe(ShopContinueEventArgs.EventId, ShopContinue); GameEntry.Event.Subscribe(DisplayItemHoverEventArgs.EventId, DisplayItemHover); GameEntry.Event.Subscribe(DisplayItemLockEventArgs.EventId, DisplayItemLock); GameEntry.Event.Subscribe(DisplayItemHideEventArgs.EventId, DisplayItemHide); GameEntry.Event.Subscribe(PlayerCoinChangeEventArgs.EventId, PlayerCoinChange); GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormComplete); } protected override void UnsubscribeCustomEvents() { GameEntry.Event.Unsubscribe(ShopRefreshEventArgs.EventId, ShopRefresh); GameEntry.Event.Unsubscribe(ShopPurchaseEventArgs.EventId, ShopPurchase); GameEntry.Event.Unsubscribe(ShopContinueEventArgs.EventId, ShopContinue); GameEntry.Event.Unsubscribe(DisplayItemHoverEventArgs.EventId, DisplayItemHover); GameEntry.Event.Unsubscribe(DisplayItemLockEventArgs.EventId, DisplayItemLock); GameEntry.Event.Unsubscribe(DisplayItemHideEventArgs.EventId, DisplayItemHide); GameEntry.Event.Unsubscribe(PlayerCoinChangeEventArgs.EventId, PlayerCoinChange); GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormComplete); } #region BuildContext private async UniTask BuildContext(ShopRawData rawData) { if (rawData == null) { Log.Error("ShopFormController.BuildContext() rawData is null."); return null; } _rawData = rawData; List goodsItems = new List(); foreach (var item in rawData.GoodsItems) { var context = await CreateGoodsItemContextAsync(item); goodsItems.Add(context); } return new ShopContext { CurrentLevel = rawData.CurrentLevel, RefreshPrice = rawData.RefreshPrice, PlayerCoin = rawData.PlayerCoin, GoodsItems = goodsItems, PropListContext = BuildDisplayListAreaContext(DisplayListAreaType.Prop, rawData.PropItems, rawData.PropMaxCount), WeaponListContext = BuildDisplayListAreaContext(DisplayListAreaType.Weapon, rawData.WeaponItems, rawData.WeaponMaxCount), NeedRefreshGoodsItems = true, NeedRefreshPlayerCoin = true, NeedRefreshPropList = true, NeedRefreshWeaponList = true, NeedRefreshPrice = true }; } private static DisplayListAreaContext BuildDisplayListAreaContext(DisplayListAreaType listType, IReadOnlyList items, int maxCount) { string title = GetDisplayListTitle(listType); if (items == null) { return new DisplayListAreaContext { Title = title, CurrentCount = 0, MaxCount = maxCount, ItemContexts = System.Array.Empty() }; } DisplayItemContext[] itemContexts = new DisplayItemContext[items.Count]; switch (listType) { case DisplayListAreaType.Weapon: if (items is IReadOnlyList weapons) { for (int i = 0; i < weapons.Count; i++) { WeaponBase weapon = weapons[i]; if (weapon == null) break; itemContexts[i] = BuildWeaponItem(weapon); } } break; case DisplayListAreaType.Prop: if (items is IReadOnlyList propItems) { for (int i = 0; i < propItems.Count; i++) { PropItem propItem = propItems[i]; if (propItem == null) break; itemContexts[i] = BuildPropItem(propItem); } } break; } int currentCount = itemContexts.Length; return new DisplayListAreaContext { Title = title, CurrentCount = currentCount, MaxCount = maxCount, ItemContexts = itemContexts }; } private static string GetDisplayListTitle(DisplayListAreaType listType) { return listType switch { DisplayListAreaType.Weapon => "武器", DisplayListAreaType.Prop => "道具", _ => string.Empty }; } private static DisplayItemContext BuildPropItem(PropItem propItem) { string iconAssetName = null; ItemRarity rarity = ItemRarity.None; if (propItem != null) { iconAssetName = propItem.IconAssetName; rarity = propItem.Rarity; } return new DisplayItemContext { IconAssetName = iconAssetName, Rarity = rarity, IsWeapon = false }; } private static DisplayItemContext BuildWeaponItem(WeaponBase weaponBase) { string iconAssetName = null; ItemRarity rarity = ItemRarity.None; if (weaponBase != null && weaponBase.WeaponData != null) { iconAssetName = weaponBase.WeaponData.IconAssetName; rarity = weaponBase.WeaponData.Rarity; } return new DisplayItemContext { IconAssetName = iconAssetName, Rarity = rarity, IsWeapon = true }; } private static void AppendDisplayItemContext(DisplayListAreaContext listContext, DisplayItemContext newItem) { if (listContext == null) { Log.Error("ShopFormController.AppendDisplayItemContext() listContext is null."); return; } if (newItem == null) { Log.Warning("ShopFormController.AppendDisplayItemContext() newItem is null."); return; } int oldCount = listContext.ItemContexts != null ? listContext.ItemContexts.Length : 0; DisplayItemContext[] newContexts = new DisplayItemContext[oldCount + 1]; if (oldCount > 0) { System.Array.Copy(listContext.ItemContexts, newContexts, oldCount); } newContexts[oldCount] = newItem; listContext.ItemContexts = newContexts; listContext.CurrentCount = oldCount + 1; } private async UniTask CreateGoodsItemContextAsync(GoodsItemRawData rawData, float timeout = 30f) { var context = new GoodsItemContext(rawData); context.Icon = context.ItemType switch { ItemType.Weapon => await GameEntry.SpriteCache.GetSprite(((DRWeapon)rawData.DataRow).IconAssetName), ItemType.Prop => await GameEntry.SpriteCache.GetSprite(((DRProp)rawData.DataRow).IconAssetName), _ => null }; return context; } #endregion #region UI Methods public override async UniTask CloseUIAsync(object userData = null, float timeout = 30f) { GameEntry.UIRouter.CloseUIAsync(UIFormType.ItemTooltipForm).Forget(); _rawData = null; _tooltipLocked = false; await base.CloseUIAsync(userData, timeout); } public async UniTask OpenUIAsync(ShopRawData rawData, float timeout = 30f) { ShopContext context = await BuildContext(rawData); return await OpenFormAsync(context, timeout); } public override async UniTask OpenUIAsync(object userData = null, float timeout = 30f) { if (userData is ShopRawData rawData) { await OpenUIAsync(rawData, timeout); } if (userData != null) { Log.Warning("ShopController.OpenUIAsync() userData type is invalid."); return; } if (_useCase == null) { Log.Error("ShopController.OpenUIAsync() useCase is null."); return; } ShopRawData initialRawData = _useCase.CreateInitialModel(); await OpenUIAsync(initialRawData, timeout); } public override void BindUseCase(IUIUseCase useCase) { if (useCase is not ShopUseCase shopFormUseCase) { Log.Error("ShopForm.BindUseCase() useCase is invalid."); return; } _useCase = shopFormUseCase; } #endregion #region Service private async UniTask RefreshGoodsItems(ShopRefreshResult result) { if (result == null) { Log.Error("ShopFormController.RefreshGoodsItems() result is null."); return; } if (Context == null) { Log.Error("ShopFormController.RefreshGoodsItems() Context is null."); return; } for (int i = 0; i < result.GoodsItems.Count; i++) { if (i < Context.GoodsItems.Count) { Context.GoodsItems[i] = await CreateGoodsItemContextAsync(result.GoodsItems[i]); } else Context.GoodsItems.Add(await CreateGoodsItemContextAsync(result.GoodsItems[i])); } if (Context.GoodsItems.Count != result.GoodsItems.Count) { Context.GoodsItems.RemoveRange(Context.GoodsItems.Count, Context.GoodsItems.Count - Context.GoodsItems.Count); } Context.RefreshPrice = result.RefreshPrice; Context.NeedRefreshGoodsItems = true; Context.NeedRefreshPrice = true; RefreshUI(Form, Context); } private void ApplyGoodsPurchased(ShopPurchaseResult result) { if (result == null) { Log.Error("ShopFormController.ApplyGoodsPurchased() result is null."); return; } if (Context == null) { Log.Error("ShopFormController.ApplyGoodsPurchased() Context is null."); return; } if (Context.GoodsItems != null && result.GoodsIndex >= 0 && result.GoodsIndex < Context.GoodsItems.Count) { Context.GoodsItems[result.GoodsIndex] = null; } DisplayItemContext context = new DisplayItemContext(result.DisplayItem); if (result.DisplayItem != null) { if (result.DisplayItem.IsWeapon) { AppendDisplayItemContext(Context.WeaponListContext, context); Context.NeedRefreshWeaponList = true; } else { AppendDisplayItemContext(Context.PropListContext, context); Context.NeedRefreshPropList = true; } } Context.NeedRefreshGoodsItems = true; RefreshUI(Form, Context); } private bool TryGetWeaponInfoRawData(int index, Vector3 targetPos, out ItemTooltipRawData rawData) { rawData = null; if (_rawData?.WeaponItems == null) { Log.Error("ShopFormController.TryGetWeaponInfoRawData() WeaponItems is null."); return false; } if (Context == null) { Log.Error("ShopFormController.TryGetWeaponInfoRawData() Context is null."); return false; } if (index < 0 || index >= _rawData.WeaponItems.Count) { Log.Error($"ShopFormController.TryGetWeaponInfoRawData() invalid weapon index: {index}."); return false; } WeaponBase weapon = _rawData.WeaponItems[index]; if (weapon?.WeaponData == null) { Log.Error($"ShopFormController.TryGetWeaponInfoRawData() weapon data is null at index {index}."); return false; } rawData = new ItemTooltipRawData { TargetPos = targetPos, Index = index, WeaponData = weapon.WeaponData, Rarity = weapon.WeaponData.Rarity, ItemType = ItemType.Weapon, Price = _useCase.CalculateRecyclePrice(weapon.WeaponData.Price), OnRecycle = RecycleWeapon, }; return true; } private bool TryGetPropInfoRawData(int index, Vector3 targetPos, out ItemTooltipRawData rawData) { rawData = null; if (_rawData?.PropItems == null) { Log.Error("ShopFormController.TryGetPropInfoRawData() PropItems is null."); return false; } if (index < 0 || index >= _rawData.PropItems.Count) { Log.Error($"ShopFormController.TryGetPropInfoRawData() invalid prop index: {index}."); return false; } PropItem propItem = _rawData.PropItems[index]; if (propItem == null) { Log.Error($"ShopFormController.TryGetPropInfoRawData() prop item is null at index {index}."); return false; } rawData = new ItemTooltipRawData { TargetPos = targetPos, Index = index, PropData = propItem, Rarity = propItem.Rarity, ItemType = ItemType.Prop, Price = 0 }; return true; } private void RecycleWeapon(int index, int price) { if (_useCase == null || Context == null) { Log.Error("ShopFormController.RecycleWeapon() controller state is invalid."); return; } bool success = _useCase.TryRecycleWeapon(index, price); if (!success) { return; } if (Context.WeaponListContext != null) { int currentCount = Mathf.Max(0, Context.WeaponListContext.CurrentCount - 1); Context.WeaponListContext.CurrentCount = currentCount; } Context.NeedRefreshWeaponList = true; RefreshUI(Form, Context); GameEntry.UIRouter.CloseUIAsync(UIFormType.ItemTooltipForm).Forget(); } #endregion #region Event Handlers private async void ShopRefresh(object sender, GameEventArgs e) { if (e is not ShopRefreshEventArgs args) { return; } ShopRefreshResult result = _useCase.TryRefresh(args.Cost); if (result == null) { return; } await RefreshGoodsItems(result); } private void ShopPurchase(object sender, GameEventArgs e) { OnPurchaseRequestAsync(sender, e).Forget(); } private async UniTaskVoid OnPurchaseRequestAsync(object sender, GameEventArgs e) { if (e is not ShopPurchaseEventArgs args) { return; } ShopPurchaseResult result = await _useCase.TryPurchaseAsync(args.GoodsIndex); if (result == null) { return; } ApplyGoodsPurchased(result); } private void ShopContinue(object sender, GameEventArgs e) { if (e is not ShopContinueEventArgs) { return; } _useCase?.Continue(); } private void DisplayItemHover(object sender, GameEventArgs e) { if (e is not DisplayItemHoverEventArgs args) { return; } if (_rawData == null) { Log.Error("ShopFormController.OnDisplayItemHover() _rawData is null."); return; } ItemTooltipRawData rawData; bool success = args.IsWeapon ? TryGetWeaponInfoRawData(args.Index, args.TargetPos, out rawData) : TryGetPropInfoRawData(args.Index, args.TargetPos, out rawData); if (!success) { return; } _tooltipLocked = false; GameEntry.UIRouter.OpenUIAsync(UIFormType.ItemTooltipForm, rawData).Forget(); } private void DisplayItemLock(object sender, GameEventArgs e) { if (e is not DisplayItemLockEventArgs) { return; } _tooltipLocked = true; } private void DisplayItemHide(object sender, GameEventArgs e) { if (e is not DisplayItemHideEventArgs args) { return; } if (_tooltipLocked) { return; } GameEntry.UIRouter.CloseUIAsync(UIFormType.ItemTooltipForm).Forget(); } private void PlayerCoinChange(object sender, GameEventArgs e) { if (e is not PlayerCoinChangeEventArgs args) return; if (Context == null) return; Context.PlayerCoin = args.CoinCount; Context.NeedRefreshPlayerCoin = true; RefreshUI(Form, Context); } private void OpenUIFormComplete(object sender, GameEventArgs e) { if (e is not OpenUIFormSuccessEventArgs ne) return; if (ne.UIForm.Logic is ItemTooltipForm) { _tooltipLocked = false; } } #endregion } }