using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using asap.core; using cfg; using DataCenter; using EnhancedUI.EnhancedScroller; using EventPartnerGather; using game; using GameCore; using UI.PartnerGather.ScrollItems; using UniRx; using UnityEngine; using UnityEngine.UI; // using ScrollViewItemInfo = EventPartnerScrollViewItem.ScrollViewItemInfo; using EScrollViewType = EventPartnerScrollViewItem.EScrollViewType; using Random = UnityEngine.Random; using EEventBuildState = EventPartnerGatherRequests.EEventBuildState; namespace UI.PartnerGather { public class EventPartnerGatherInvitePanel : MonoBehaviour, IEnhancedScrollerDelegate { [SerializeField] private EnhancedScroller scrollView; [SerializeField] private Button btnClose, btnAddFriends; [SerializeField] private EventPartnerGatherScrollViewItem scrollViewItemPrefab; [SerializeField] private EventPartnerScrollViewTitle scrollViewTitlePrefab; [SerializeField] private GameObject emptyGo, loadingGo; // 自动匹配 [SerializeField] private EventPartnerGatherScrollViewAutoMatch scrollViewItemAutoMatch; private const string RequestTitleKey = "UI_EventPartnerMiningPanel_7"; private const string RecommendTitleKey = "UI_EventPartnerFishbowlPanel_30"; private const string FriendTitleKey = "UI_EventPartnerFishbowlPanel_31"; private const string InvitationTitleKey = "UI_EventPartnerFishbowlPanel_36"; private readonly string PlayerFullKey = "UI_ToastPanel_80"; private readonly string CommonErrorKey = "UI_EventPartnerMiningPanel_12"; private readonly string AlreadyExistKey = "UI_EventPartnerMiningPanel_10"; private readonly string PartnerFullKey = "UI_EventPartnerMiningPanel_11"; private List _scrollViewInfoList, _requestList, _recommendList, _friendList, _invitationList; private readonly EventPartnerService _eventPartnerService = GContext.container.Resolve(); private readonly FriendService _friendService = GContext.container.Resolve(); private readonly IUserService _userService = GContext.container.Resolve(); private Tables _tables; private CompositeDisposable _disposables = new(); // 管理 private EventPartnerGatherManager _manager; // 管理Partner信息 private PartnerInfoManager _partnerInfoManager; private EventPartnerGatherScrollViewAutoMatch _autoMatchView; private class GroupSection { public string TitleKey; public List Items; } private List _sections; [SerializeField] private float jiandaTitleHeight = 72f, jiandaItemHeight = 150f; /// Section 内 Item 行间距,须与 Prefab 中 VerticalLayoutGroup.spacing 等一致;与 GetSectionCellHeight / Title.Init 使用同一数值。 [SerializeField] private float sectionItemSpacing = 0f; [SerializeField] private float AutoMatchHeight = 144f; //- private void Start() { btnClose.onClick.AddListener(OnClickClose); btnAddFriends.onClick.AddListener(OnClickAddFriends); scrollView.lookAheadBefore = 500f; scrollView.lookAheadAfter = 500f; _tables = GContext.container.Resolve(); _manager = GContext.container.Resolve(); _partnerInfoManager = _manager.PartnerInfoManager; _scrollViewInfoList = new List(); _recommendList = new List(); _friendList = new List(); _invitationList = new List(); emptyGo.SetActive(false); loadingGo.SetActive(false); InitScrollViewAsync(); GContext.OnEvent().Subscribe(e => OnInvite(e.Info)).AddTo(_disposables); GContext.OnEvent().Subscribe(e => OnAcceptRequest(e.Info)).AddTo(_disposables); GContext.OnEvent().Subscribe(OnClickBtnNo).AddTo(_disposables); GContext.OnEvent().Subscribe(OnAddPartner).AddTo(_disposables); GContext.OnEvent().Subscribe(OnAutoMatchStateChange).AddTo(_disposables); GContext.OnEvent().Subscribe(OnAutoMatchFailed).AddTo(_disposables); //- GContext.container.Resolve().InspectTriggerGuide("EventPartnerGatherInvitePanel", curPanelName: gameObject.name); } private void OnDestroy() { ELog($"OnDestroy eventId={_manager?.EventId}"); _disposables.Dispose(); _disposables = null; btnClose.onClick.RemoveAllListeners(); btnAddFriends.onClick.RemoveAllListeners(); } private void OnClickClose() { UIManager.Instance.DestroyUI(gameObject.name); } private void OnClickAddFriends() { //添加好友 GContext.container.Resolve().OpenFishingSocialPanel(1); } private async void InitScrollViewAsync() { await InitScrollView(); } private async Task InitScrollView() { try { loadingGo.SetActive(true); // ELog("LoadRequestList"); _requestList = GetRequestList(); // ELog("LoadRecommendList"); _recommendList = GetRecommendList(); // ELog("LoadFriendList"); _friendList = GetFriendList(); // ELog("LoadInvitationList"); _invitationList = GetInvitationList(); // ELog("DoLastFilter"); DoLastFilter(); _sections = new List { new() { TitleKey = RequestTitleKey, Items = _requestList }, new() { TitleKey = RecommendTitleKey, Items = _recommendList }, new() { TitleKey = FriendTitleKey, Items = _friendList }, new() { TitleKey = InvitationTitleKey, Items = _invitationList }, }; // ELog("BuildListData"); BuildScrollViewInfoList(); // ELog("SetScrollerDelegate"); scrollView.Delegate = this; // ELog("ResetScroller"); loadingGo.SetActive(false); ReloadScrollViewData(); UpdateAutoMatchStatus(); } catch (Exception e) { LogError($"InitInvitePanelFailed error={e.Message}"); throw; } } private async void OnInvite(ScrollViewItemInfo invitationInfo) { Log($"InviteRequest partnerId={invitationInfo.PlayfabId}"); try { if (_partnerInfoManager.IsFullyMatched) { Log($"InviteBlocked reason=FullyMatched eventId={_manager?.EventId}"); ToastPanel.Show(LocalizationMgr.GetText(PlayerFullKey)); OnClickClose(); return; } var partnerId = invitationInfo.PlayfabId; // var slotId = -1; // 机器人直接 同意 if (partnerId.EndsWith(EventPartnerBot.RobotIdentifier)) { _manager.RobotInvitationSystem.OnInviteRobot(invitationInfo); } else { var response = await _eventPartnerService.InvitePartner(partnerId, _manager.EventId); if (response.State != EEventBuildState.Success) { LogWarning($"InviteResponse state={response.State} reason={response.ErrorMessage}"); // ToastPanel.Show(LocalizationMgr.GetText("UI_ToastPanel_80")); // OnRemoveItemAfterInvited(partnerId); OnErrorMessage(response.State); invitationInfo.IsFull = response.State == EEventBuildState.Full; ActivateScrollerItemButtons(); return; } } OnRemoveItemAfterInvited(partnerId); var bRet = _partnerInfoManager.AddInvitationInfo(invitationInfo); if (bRet) { // invitationInfo.Type = EScrollViewType.MyInvitation; _invitationList.Add(invitationInfo); BuildScrollViewInfoList(); ReloadScrollViewData(); _manager.AddInvitationCount(); } ActivateScrollerItemButtons(); } catch (Exception ex) { LogError($"InviteException error={ex.Message}"); ActivateScrollerItemButtons(); } } // 忽略 // public void OnIgnore(EventPartnerGatherClickIgnore evt) // { // var partnerId = evt.Info.PlayfabId; // OnIgnore(partnerId); // } public void OnIgnore(string partnerId) { Log($"IgnoreRequest partnerId={partnerId}"); RemoveItemFromInvites(partnerId); RemoveItemFromRequested(partnerId); RemoveItemFromRecommended(partnerId); RemoveItemFromFriend(partnerId); _partnerInfoManager.AddRefusePartner(partnerId); _partnerInfoManager.RemoveInvitePartner(partnerId); _partnerInfoManager.OnNoLongerUsedPartner(partnerId); BuildScrollViewInfoList(); ReloadScrollViewData(); ActivateScrollerItemButtons(); } // 邀请 private List GetRequestList() { // var excludeSet = _partnerInfoManager.GetExcludeList(); // var requestPartners = _partnerInfoManager.EventBuildInfo.RequestPartners; var refusePartners = _partnerInfoManager.Cache.RefusedPartnerIds; if (refusePartners is { Count: > 0 }) { _partnerInfoManager.EventBuildInfo.RequestPartners.RemoveAll(item => refusePartners.Contains(item.PlayFabId)); } return _partnerInfoManager.EventBuildInfo.RequestPartners.Select(playerInfo => playerInfo.ToScrollViewItemInfo(EScrollViewType.Partner2Accept)).ToList(); } private List GetRecommendList() { var count = _partnerInfoManager.GetRecommendCount(); var random = new System.Random(); var recommendList = new List(); var excludeSet = _partnerInfoManager.GetExcludeList(); // if (true) // { // recommendList.AddRange(GetBotRecommendList(count)); // return recommendList; // } if (_manager.GetDoRecommendBot()) { var botCount = (int)(count * _tables.TbEventPartnerConfig.RobotRecommendCount); recommendList = _partnerInfoManager.EventBuildInfo.RecommendInfoList .Where(info => !excludeSet.Contains(info.PlayFabId)) .OrderBy(_ => random.Next()) .Take(count - botCount) .Select(info => info.ToScrollViewItemInfo(EScrollViewType.Recommendation)) .ToList(); recommendList.AddRange(GetBotRecommendList(botCount)); } else { recommendList = _partnerInfoManager.EventBuildInfo.RecommendInfoList .Where(info => !excludeSet.Contains(info.PlayFabId)) .OrderBy(_ => random.Next()) .Take(count) .Select(info => info.ToScrollViewItemInfo(EScrollViewType.Recommendation)) .ToList(); } // _partnerInfoManager.Cache.Recommend = recommendList; return recommendList; } private HashSet GetBotRecommendList(int count) { var botTable = _tables.TbRobot; var recommendList = new HashSet(); var partnerIdSet = _partnerInfoManager.BuildPartners.Where(c => c != null) .Select(c => c.PartnerId).ToHashSet(); while (recommendList.Count < count) { var randomBot = botTable.DataList[Random.Range(0, botTable.DataList.Count)]; if (partnerIdSet.Contains(EventPartnerBot.Idx2Id(randomBot.ID))) continue; recommendList.Add(new ScrollViewItemInfo { Type = EScrollViewType.Recommendation, AvatarUrl = randomBot.Avatar, DisplayName = randomBot.Name, PlayfabId = EventPartnerBot.Idx2Id(randomBot.ID), Level = Random.Range(60, 120), }); ELog($"PickRobotCandidate robotId={randomBot.ID} name={randomBot.Name}"); } return recommendList; } private List GetFriendList() { var friendList = _friendService.FriendList; // var _eventPartnerData = GContext.container.Resolve(); var eventBuildInfo = _partnerInfoManager.EventBuildInfo; var pendingSet = eventBuildInfo.InvitePartners; var requestPartners = eventBuildInfo.RequestPartners; var refusePartners = _partnerInfoManager.Cache.RefusedPartnerIds; var buildPartners = _partnerInfoManager.BuildPartners; var newList = new List(); var userId = _userService.UserId; foreach (var friend in friendList) { var friendId = friend.playFabId; if (userId.Equals(friendId)) continue; if (requestPartners != null && requestPartners.Select(p => p.PlayFabId).Contains(friendId)) continue; if (refusePartners != null && refusePartners.Contains(friendId)) continue; if (pendingSet != null && pendingSet.Select(p => p.PlayFabId).Contains(friendId)) continue; if (buildPartners != null && buildPartners.Any(c => c is { PartnerId: not null } && c.PartnerId.Equals(friend.playFabId))) continue; var friendLastLogin = friend.LastLogin ?? DateTime.MinValue; var info = new ScrollViewItemInfo() { Type = EScrollViewType.Friend, AvatarUrl = friend.avatarUrl, DisplayName = friend.displayName, PlayfabId = friend.playFabId, Level = friend.value / LeadboardData.LV_MODELING, LastLoginTime = friendLastLogin }; newList.Add(info); } return new List(newList.OrderByDescending(info => info.LastLoginTime)); } private List GetInvitationList() { var res = new List(); var eventBuildInfo = _partnerInfoManager.EventBuildInfo; foreach (var partnerInfo in eventBuildInfo.InvitePartners) { res.Add(partnerInfo.ToScrollViewItemInfo(EScrollViewType.MyInvitation)); } return res; } private void DoLastFilter() { foreach (var buildPartner in _partnerInfoManager.BuildPartners) { if (buildPartner != null && buildPartner.IsValid()) { RemoveInvitedPartner(buildPartner.PartnerId); } } } public int GetNumberOfCells(EnhancedScroller scroller) { return _scrollViewInfoList.Count; } /// 与 EventPartnerScrollViewTitle.Init 内 Item 区域总高度一致,避免 EnhancedScroller 错位导致 Section 重叠。 private float GetSectionCellHeight(int itemCount) { if (itemCount <= 0) return jiandaTitleHeight; return jiandaTitleHeight + itemCount * jiandaItemHeight + Mathf.Max(0, itemCount - 1) * sectionItemSpacing; } public float GetCellViewSize(EnhancedScroller scroller, int dataIndex) { var info = _scrollViewInfoList[dataIndex]; switch (info.Type) { case EScrollViewType.AutoMatch: return AutoMatchHeight; case EScrollViewType.Section: return GetSectionCellHeight(info.SectionItems?.Count ?? 0); default: return jiandaItemHeight; } } public EnhancedScrollerCellView GetCellView(EnhancedScroller scroller, int dataIndex, int cellIndex) { var info = _scrollViewInfoList[dataIndex]; switch (info.Type) { case EScrollViewType.Section: if (scroller.GetCellView(scrollViewTitlePrefab) is not EventPartnerScrollViewTitle sectionCell) { LogError("CellViewNull type=Section"); return null; } sectionCell.Init(info, jiandaItemHeight, sectionItemSpacing); return sectionCell; case EScrollViewType.AutoMatch: if (scroller.GetCellView(scrollViewItemAutoMatch) is not EventPartnerGatherScrollViewAutoMatch cellViewAutoMatch) { LogError("CellViewNull type=AutoMatch"); return null; } SetAutoMatchView(cellViewAutoMatch); return cellViewAutoMatch; default: throw new ArgumentOutOfRangeException(); } } private void SetAutoMatchView(EventPartnerGatherScrollViewAutoMatch cellViewAutoMatch) { _autoMatchView = cellViewAutoMatch; var state = _manager.IsAutoMatchEnabled ? AutoMatchState.AutoMatchOn : AutoMatchState.AutoMatchOff; _autoMatchView.ApplyHostBinding(state); } private void OnRemoveItemAfterInvited(string id) { if (_recommendList is not null) RemoveItemFromRecommended(id); if (_friendList is not null) RemoveItemFromFriend(id); BuildScrollViewInfoList(); ReloadScrollViewData(); } private void BuildScrollViewInfoList() { _scrollViewInfoList.Clear(); _scrollViewInfoList.Add(new ScrollViewItemInfo { Type = EScrollViewType.AutoMatch }); foreach (var section in _sections) { if (section.Items is not { Count: > 0 }) continue; _scrollViewInfoList.Add(new ScrollViewItemInfo { Type = EScrollViewType.Section, Title = LocalizationMgr.GetText(section.TitleKey), SectionItems = section.Items, }); } } private void ReloadScrollViewData() { ELog($"ReloadScrollViewData eventId={_manager?.EventId}"); scrollView.ReloadData(); Canvas.ForceUpdateCanvases(); var onlyAutoMatch = _scrollViewInfoList.Count <= 1; emptyGo.SetActive(onlyAutoMatch); } private static void ELog(string message) { #if UNITY_EDITOR Debug.Log($"EventPartnerGatherInvitePanel -> {message} "); #endif } [System.Diagnostics.Conditional("UNITY_EDITOR")] private static void Trace(string message) { // Keep invite panel default logs focused on actionable warnings/errors. ELog(message); } private static void Log(object t) { Debug.Log($"EventPartnerGatherInvitePanel -> {t} "); } private static void LogWarning(object t) { Debug.LogWarning($"EventPartnerGatherInvitePanel -> {t} "); } private static void LogError(object t) { Debug.LogError($"EventPartnerGatherInvitePanel -> {t} "); } private async void OnAcceptRequest(ScrollViewItemInfo info) { Log($"AcceptRequest partnerId={info.PlayfabId} type={info.Type}"); try { if (_partnerInfoManager.IsFullyMatched) { Log($"AcceptBlocked reason=FullyMatched eventId={_manager?.EventId}"); ToastPanel.Show(LocalizationMgr.GetText(PlayerFullKey)); return; } var response = await _eventPartnerService.AcceptPartner(info.PlayfabId, _manager.EventId); if (response.State == EEventBuildState.Success) { var partnerList = response.PartnerList; var positionId = -1; for (var i = 0; i < partnerList.Count; ++i) { var partner = partnerList[i]; if (i >= _partnerInfoManager.BuildPartners.Length) return; AddPartner(i, partner); if (partner.PlayFabId == info.PlayfabId) { positionId = i; } } // await Awaiters.Seconds(0.5f); #if AGG using (var e = GEvent.GameEvent("event_partners_gather_invite")) { e.AddContent("teammate_id", info.PlayfabId) .AddContent("position_id", positionId) .AddContent("inviter_id", _userService.UserId); } #endif } else { LogWarning($"AcceptResponse state={response.State} reason={response.Message}"); OnErrorMessage(response.State); var playFabId = info.PlayfabId; OnIgnore(playFabId); GContext.Publish(new EventPartnerRefreshPartnerUI()); } } catch (Exception e) { LogError($"AcceptException error={e.Message}"); } finally { ActivateScrollerItemButtons(); } } private async void OnIgnoreRequest(string partnerId) { Log($"IgnoreRequest partnerId={partnerId}"); try { var response = await _eventPartnerService.RefusePartner(partnerId, _manager.EventId); if (response.State != EEventBuildState.Success) LogWarning($"IgnoreResponse state={response.State} reason={response.ErrorMessage}"); else { OnIgnore(partnerId); GContext.Publish(new EventPartnerRefreshPartnerUI()); } } catch (Exception e) { LogError($"IgnoreException error={e.Message}"); } finally { ActivateScrollerItemButtons(); } if (_scrollViewInfoList.Count <= 1) { OnClickClose(); } } // private void AddPartner(int i, PlayerInfo partner) { var buildPartner = _partnerInfoManager.BuildPartners[i]; if (buildPartner != null && buildPartner.IsValid()) return; if (partner.PlayFabId.EndsWith(EventPartnerBot.RobotIdentifier)) return; var builder = new BuildPartner { SlotId = i, PartnerId = partner.PlayFabId, AvatarUrl = partner.AvatarUrl, DisplayName = partner.DisplayName, Cache = _partnerInfoManager.Cache, }; var bRet = _partnerInfoManager.AddBuildPartner(builder); if (bRet) { // OnAddPartner(new EventPartnerGatherAddPartner // { // SlotId = i, // }); // RemoveInvitePartner(builder.PartnerId); // 可能重复处理。用事件的方式可能漏了 GContext.Publish(new EventPartnerGatherAddPartner { SlotId = i, }); } } private void OnAddPartner(EventPartnerGatherAddPartner evt) { Log($"OnAddPartner slotId={evt.SlotId}"); var buildPartners = _manager.PartnerInfoManager.BuildPartners; if (evt.SlotId < buildPartners.Length) { var buildPartner = buildPartners[evt.SlotId]; var playFabId = buildPartner.PartnerId; RemoveInvitedPartner(playFabId); BuildScrollViewInfoList(); ReloadScrollViewData(); // scrollView.ReloadData(); } else { LogError($"OnAddPartnerFailed reason=SlotOutOfRange slotId={evt.SlotId}"); } } private void RemoveInvitedPartner(string playFabId) { // 删除邀请和以及邀请中的数据 RemoveItemFromInvites(playFabId); RemoveItemFromRequested(playFabId); RemoveItemFromRecommended(playFabId); RemoveItemFromFriend(playFabId); if (_partnerInfoManager != null) { _partnerInfoManager.RemoveInvitePartner(playFabId); _partnerInfoManager.OnNoLongerUsedPartner(playFabId); } } private void RemoveItemFromFriend(string playFabId) { if (string.IsNullOrEmpty(playFabId)) return; if (_friendList is { Count: > 0 }) { _friendList.RemoveAll(item => item.PlayfabId.Equals(playFabId)); } } private void RemoveItemFromRecommended(string playFabId) { if (string.IsNullOrEmpty(playFabId)) return; if (_recommendList is { Count: > 0 }) { _recommendList.RemoveAll(item => item.PlayfabId.Equals(playFabId)); } } private void RemoveItemFromRequested(string playFabId) { if (string.IsNullOrEmpty(playFabId)) return; if (_requestList is { Count: > 0 }) { _requestList.RemoveAll(item => item.PlayfabId.Equals(playFabId)); } } private void RemoveItemFromInvites(string playFabId) { if (string.IsNullOrEmpty(playFabId)) return; if (_invitationList is { Count: > 0 }) { _invitationList?.RemoveAll(item => item.PlayfabId.Equals(playFabId)); } } private void OnErrorMessage(EEventBuildState responseState) { switch (responseState) { case EEventBuildState.Full: ToastPanel.Show(LocalizationMgr.GetText(PartnerFullKey)); break; case EEventBuildState.AlreadyExist: ToastPanel.Show(LocalizationMgr.GetText(AlreadyExistKey)); break; // case EEventBuildState.Success: case EEventBuildState.Failed: case EEventBuildState.InviteNotFound: case EEventBuildState.NoActiveEvent: default: ToastPanel.Show(LocalizationMgr.GetText(CommonErrorKey)); break; } } private void OnClickBtnNo(EventPartnerGatherClickIgnore e) { if (e.Info.Type == EScrollViewType.Partner2Accept) { OnIgnoreRequest(e.Info.PlayfabId); } else { OnIgnore(e.Info.PlayfabId); } } private void ActivateScrollerItemButtons() { ELog($"ActivateScrollerItemButtons eventId={_manager?.EventId}"); foreach (var item in scrollView.GetComponentsInChildren()) { item.ActivateButton(); } } #region 自动匹配相关方法 /// /// 更新自动匹配 UI /// private void UpdateAutoMatchUI(AutoMatchState state) { Trace($"UpdateAutoMatchUI state={state}"); // 通知 ScrollView 更新状态 if (_autoMatchView) { _autoMatchView.SetupState(state); } } private void UpdateAutoMatchStatus() { var state = _manager.IsAutoMatchEnabled ? AutoMatchState.AutoMatchOn : AutoMatchState.AutoMatchOff; UpdateAutoMatchUI(state); } private void OnAutoMatchStateChange(EventForAutoMatchChangeStage e) { UpdateAutoMatchUI(e.State); } private async void OnAutoMatchFailed(EventAutoMatchFailed e) { LogError($"AutoMatchFailed eventId={_manager?.EventId} reason={e.Reason}"); _manager?.SetAutoMatchEnabled(false); await Awaiters.Seconds(0.2f); UpdateAutoMatchUI(AutoMatchState.AutoMatchOff); } #endregion } } public class EventPartnerGatherAcceptInvitation { }