Files
bansonic_beta_main/Assets/scripts/roomSystem/roomDetails.cs
T
2026-07-20 22:19:25 +08:00

1934 lines
58 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using Bansonic;
using GameServer.Client;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class roomDetails : MonoBehaviour
{
private sealed class RoomSystemFeedEntry
{
public string richText;
public long createdAtUnixMs;
public long sequence;
}
private sealed class RoomFeedEntry
{
public object entry;
public long timestampMs;
public int order;
public string key;
public long sequence;
}
[Header("room Info")]
public Text roomID;
public Text roomRemainTime;
public Button quitRoom;
public Button quitRoom2;
public Button getReady;
public Button showRoomRankingList;
[Header("song info")]
public Text thisSongName;
public Text thisSongDifficulty;
[Header("change songs")]
public Dropdown songDropdown;
public Dropdown difficultyDropdown;
[Header("objs")]
public GameObject roommatePrefab;
public Transform roommateParent;
[Header("sprites")]
public Sprite[] btmSprites;
[Header("roomChatSystem")]
public GameObject playerMessagePrefab;
public GameObject selfMessagePrefab;
public GameObject systemMessagePrefab;
public Transform messageParent;
[SerializeField] private int maxMessageHistory = 500;
[SerializeField] private int messageDisplayWindow = 20;
[SerializeField] private Color[] messageTypeColors;
public InputField playerMessageInputField;
public Button sendMessageButton;
private ArenaRoomService _service;
private Coroutine _countdownRoutine;
private GameObject _roomRankingPrefabTemplate;
private Transform _roomRankingSpawnParent;
private GameObject _spawnedRoomRankingInstance;
private ScrollRect _messageScrollRect;
private Coroutine _chatCooldownRoutine;
private Text _chatPlaceholderText;
private string _defaultChatPlaceholder = string.Empty;
private float _nextChatSendAllowedAt;
private bool _isSendingRoomMessage;
private string _pendingRoomMessageContent = string.Empty;
private readonly List<RoomSystemFeedEntry> _systemFeedEntries = new List<RoomSystemFeedEntry>();
private readonly Dictionary<string, long> _feedSequences = new Dictionary<string, long>(StringComparer.Ordinal);
private long _nextFeedSequence;
private bool _isChatViewPinnedToBottom = true;
private bool _suppressChatScrollEvents;
private int _chatWindowStartIndex = -1;
private readonly List<string> _renderedChatKeys = new List<string>();
private readonly List<GameObject> _renderedChatObjects = new List<GameObject>();
private Coroutine _messageLayoutRefreshCoroutine;
private bool _messageLayoutRefreshDirty;
private bool _scrollChatToBottomAfterLayout;
private readonly List<RectTransform> _pendingMessageLayoutDirtyRoots = new List<RectTransform>();
private Coroutine _refocusChatInputCoroutine;
private bool _refocusChatInputRequested;
private readonly List<SongData> _selectableSongs = new List<SongData>();
private readonly List<int> _selectableDifficulties = new List<int>();
private Coroutine _songLibraryWaitRoutine;
private bool _suppressSongDropdownEvents;
private bool _isChangingRoomSong;
private bool _isClosingSelf;
private const float ChatSendCooldownSeconds = 3f;
private const int InitialHistoryLoadLimit = 20;
private const int MaxDisplayWindowLimit = 20;
private const string CooldownPlaceholderFormat = "\u53d1\u9001\u51b7\u5374\u4e2d {0}s";
private void Awake()
{
EnsurePopupTween();
}
public void ConfigureRoomRanking(GameObject roomRankingPrefab, Transform roomRankingSpawnParent)
{
_roomRankingPrefabTemplate = roomRankingPrefab;
_roomRankingSpawnParent = roomRankingSpawnParent;
}
public void Bind(ArenaRoomService service)
{
EnsurePopupTween();
_service = service;
UIBackStack.RegisterOrBump(
this,
() => this != null && gameObject != null && gameObject.activeInHierarchy,
() =>
{
if (_spawnedRoomRankingInstance != null)
{
CloseRoomRankingWithExitAnim();
return true;
}
CloseSelfWithExitAnim();
return true;
});
if (_service == null)
{
Debug.LogWarning("[roomDetails] ArenaRoomService is missing.");
return;
}
_service.OnRoomSnapshotChanged -= HandleRoomSnapshotChanged;
_service.OnRoomSnapshotChanged += HandleRoomSnapshotChanged;
_service.OnKicked -= HandleKicked;
_service.OnKicked += HandleKicked;
_service.OnRoomDismissed -= HandleRoomDismissed;
_service.OnRoomDismissed += HandleRoomDismissed;
_service.OnRoomChatMessagesChanged -= HandleRoomChatMessagesChanged;
_service.OnRoomChatMessagesChanged += HandleRoomChatMessagesChanged;
_service.OnRoomSystemMessageReceived -= HandleRoomSystemMessageReceived;
_service.OnRoomSystemMessageReceived += HandleRoomSystemMessageReceived;
if (quitRoom != null)
{
quitRoom.onClick.RemoveListener(OnQuitClicked);
quitRoom.onClick.AddListener(OnQuitClicked);
}
if (quitRoom2 != null)
{
quitRoom2.onClick.RemoveListener(OnQuitClicked);
quitRoom2.onClick.AddListener(OnQuitClicked);
}
if (getReady != null)
{
getReady.onClick.RemoveListener(OnReadyButtonClicked);
getReady.onClick.AddListener(OnReadyButtonClicked);
}
if (showRoomRankingList != null)
{
showRoomRankingList.onClick.RemoveListener(OnShowRoomRankingClicked);
showRoomRankingList.onClick.AddListener(OnShowRoomRankingClicked);
}
if (sendMessageButton != null)
{
sendMessageButton.onClick.RemoveListener(OnSendMessageClicked);
sendMessageButton.onClick.AddListener(OnSendMessageClicked);
}
if (songDropdown != null)
{
songDropdown.onValueChanged.RemoveListener(OnSongDropdownChanged);
songDropdown.onValueChanged.AddListener(OnSongDropdownChanged);
}
if (difficultyDropdown != null)
{
difficultyDropdown.onValueChanged.RemoveListener(OnDifficultyDropdownChanged);
difficultyDropdown.onValueChanged.AddListener(OnDifficultyDropdownChanged);
}
if (playerMessageInputField != null)
{
playerMessageInputField.onEndEdit.RemoveListener(OnChatInputEndEdit);
playerMessageInputField.onEndEdit.AddListener(OnChatInputEndEdit);
_chatPlaceholderText = playerMessageInputField.placeholder as Text;
if (_chatPlaceholderText != null)
{
_defaultChatPlaceholder = _chatPlaceholderText.text;
}
}
_messageScrollRect = messageParent != null ? messageParent.GetComponentInParent<ScrollRect>() : null;
if (_messageScrollRect != null)
{
_messageScrollRect.onValueChanged.RemoveListener(OnChatScrollValueChanged);
_messageScrollRect.onValueChanged.AddListener(OnChatScrollValueChanged);
}
_service.SetRoomChatCacheLimit(GetEffectiveCacheLimit());
ClearDisplayedMessages();
UpdateChatSendUiState();
_ = InitializeRoomChatAsync();
Refresh(_service.CurrentRoom);
EnsureSongDropdownsReady();
}
private void OnDestroy()
{
UIBackStack.Unregister(this);
if (_service != null)
{
_service.OnRoomSnapshotChanged -= HandleRoomSnapshotChanged;
_service.OnKicked -= HandleKicked;
_service.OnRoomDismissed -= HandleRoomDismissed;
_service.OnRoomChatMessagesChanged -= HandleRoomChatMessagesChanged;
_service.OnRoomSystemMessageReceived -= HandleRoomSystemMessageReceived;
}
if (quitRoom != null)
{
quitRoom.onClick.RemoveListener(OnQuitClicked);
}
if (quitRoom2 != null)
{
quitRoom2.onClick.RemoveListener(OnQuitClicked);
}
if (getReady != null)
{
getReady.onClick.RemoveListener(OnReadyButtonClicked);
}
if (showRoomRankingList != null)
{
showRoomRankingList.onClick.RemoveListener(OnShowRoomRankingClicked);
}
if (sendMessageButton != null)
{
sendMessageButton.onClick.RemoveListener(OnSendMessageClicked);
}
if (songDropdown != null)
{
songDropdown.onValueChanged.RemoveListener(OnSongDropdownChanged);
}
if (difficultyDropdown != null)
{
difficultyDropdown.onValueChanged.RemoveListener(OnDifficultyDropdownChanged);
}
if (playerMessageInputField != null)
{
playerMessageInputField.onEndEdit.RemoveListener(OnChatInputEndEdit);
}
if (_countdownRoutine != null)
{
StopCoroutine(_countdownRoutine);
_countdownRoutine = null;
}
if (_messageScrollRect != null)
{
_messageScrollRect.onValueChanged.RemoveListener(OnChatScrollValueChanged);
}
if (_chatCooldownRoutine != null)
{
StopCoroutine(_chatCooldownRoutine);
_chatCooldownRoutine = null;
}
if (_messageLayoutRefreshCoroutine != null)
{
StopCoroutine(_messageLayoutRefreshCoroutine);
_messageLayoutRefreshCoroutine = null;
}
if (_refocusChatInputCoroutine != null)
{
StopCoroutine(_refocusChatInputCoroutine);
_refocusChatInputCoroutine = null;
}
if (_songLibraryWaitRoutine != null)
{
StopCoroutine(_songLibraryWaitRoutine);
_songLibraryWaitRoutine = null;
}
RestoreDefaultChatPlaceholder();
if (_spawnedRoomRankingInstance != null)
{
UIBackStack.Unregister(_spawnedRoomRankingInstance);
Destroy(_spawnedRoomRankingInstance);
_spawnedRoomRankingInstance = null;
}
}
private void HandleRoomSnapshotChanged(ArenaRoomSnapshot snapshot)
{
if (snapshot == null)
{
CloseSelfWithExitAnim();
return;
}
Refresh(snapshot);
}
private void HandleKicked(string kickedSteamId, string bySteamId)
{
CloseSelfWithExitAnim();
}
private void HandleRoomDismissed(string roomCode, string bySteamId)
{
CloseSelfWithExitAnim();
}
private void HandleRoomChatMessagesChanged(IReadOnlyList<ArenaRoomChatMessage> messages)
{
RenderChatMessages(messages, true);
}
private void HandleRoomSystemMessageReceived(string richText)
{
if (string.IsNullOrWhiteSpace(richText))
{
return;
}
long createdAtUnixMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
string key = $"system:{createdAtUnixMs}:{richText}";
_systemFeedEntries.Add(new RoomSystemFeedEntry
{
richText = richText,
createdAtUnixMs = createdAtUnixMs,
sequence = GetOrAssignFeedSequence(key)
});
TrimSystemFeedCache();
RenderChatMessages(_service != null ? _service.GetRoomChatMessages() : null, true);
}
private async System.Threading.Tasks.Task InitializeRoomChatAsync()
{
if (_service == null)
{
return;
}
try
{
await _service.EnsureRoomChatSubscribed();
RenderChatMessages(_service.GetRoomChatMessages(), false);
}
catch (Exception ex)
{
Debug.LogWarning($"[roomDetails] Init room chat failed: {ex.Message}");
}
}
private async void OnQuitClicked()
{
if (_service == null)
{
CloseSelfWithExitAnim();
return;
}
try
{
string localSteamId = NetworkManager.Instance != null ? NetworkManager.Instance.SteamId : string.Empty;
bool localIsHost = IsLocalHost(_service.CurrentRoom, localSteamId);
if (localIsHost)
{
await _service.DismissRoom();
}
else
{
await _service.LeaveRoom();
}
}
catch (Exception ex)
{
Debug.LogWarning($"[roomDetails] Leave room failed: {ex.Message}");
}
finally
{
CloseSelfWithExitAnim();
}
}
private void Refresh(ArenaRoomSnapshot snapshot)
{
if (snapshot == null)
{
return;
}
if (roomID != null)
{
roomID.text = snapshot.room_code;
}
if (thisSongName != null)
{
thisSongName.text = !string.IsNullOrWhiteSpace(snapshot.song_name)
? snapshot.song_name
: snapshot.song_id ?? string.Empty;
}
if (thisSongDifficulty != null)
{
thisSongDifficulty.text = FormatDifficulty(snapshot.difficulty);
}
if (_countdownRoutine != null)
{
StopCoroutine(_countdownRoutine);
}
_countdownRoutine = StartCoroutine(UpdateRemainTime(snapshot));
UpdateQuitButtonTexts(snapshot);
UpdateReadyButton(snapshot);
RenderParticipants(snapshot);
RefreshSongDropdowns(snapshot);
}
private IEnumerator UpdateRemainTime(ArenaRoomSnapshot snapshot)
{
while (snapshot != null && roomRemainTime != null)
{
long nowUnix = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
long remainSeconds = Math.Max(0L, snapshot.expires_at_unix - nowUnix);
int remainMinutes = Mathf.CeilToInt(remainSeconds / 60f);
roomRemainTime.text = $"{remainMinutes}分钟内不开始游戏将自动关闭房间";
yield return new WaitForSeconds(1f);
}
}
private void RenderParticipants(ArenaRoomSnapshot snapshot)
{
if (roommateParent == null || roommatePrefab == null)
{
return;
}
for (int i = roommateParent.childCount - 1; i >= 0; i--)
{
Destroy(roommateParent.GetChild(i).gameObject);
}
string localSteamId = _service != null && NetworkManager.Instance != null
? NetworkManager.Instance.SteamId
: string.Empty;
bool localIsHost = IsLocalHost(snapshot, localSteamId);
if (snapshot.participants == null)
{
return;
}
for (int i = 0; i < snapshot.participants.Count; i++)
{
ArenaRoomParticipant participant = snapshot.participants[i];
GameObject item = Instantiate(roommatePrefab, roommateParent);
UI_OrderedEntryAnimator.PlayFadeOnly(item, i, 0.16f, 0.012f, true);
roomPrefab itemController = item.GetComponent<roomPrefab>();
if (itemController == null)
{
continue;
}
Sprite background = null;
if (participant != null && participant.is_host)
{
if (btmSprites != null && btmSprites.Length > 0)
{
background = btmSprites[0];
}
}
else if (btmSprites != null && btmSprites.Length > 1)
{
background = btmSprites[1];
}
itemController.Bind(participant, i + 1, localIsHost, localSteamId, background, HandleKickRequested);
}
}
private async void HandleKickRequested(string targetSteamId)
{
if (_service == null || string.IsNullOrWhiteSpace(targetSteamId))
{
return;
}
try
{
await _service.KickPlayer(targetSteamId);
}
catch (Exception ex)
{
Debug.LogWarning($"[roomDetails] Kick failed: {ex.Message}");
}
}
private async void OnReadyButtonClicked()
{
if (_service == null || _service.CurrentRoom == null)
{
return;
}
try
{
string localSteamId = NetworkManager.Instance != null ? NetworkManager.Instance.SteamId : string.Empty;
bool localIsHost = IsLocalHost(_service.CurrentRoom, localSteamId);
if (localIsHost)
{
if (CanHostStartGame(_service.CurrentRoom))
{
await _service.StartGame();
}
}
else
{
await _service.ToggleReady();
}
}
catch (Exception ex)
{
Debug.LogWarning($"[roomDetails] Ready/start failed: {ex.Message}");
}
}
private async void OnSendMessageClicked()
{
if (_service == null || playerMessageInputField == null)
{
return;
}
if (Time.unscaledTime < _nextChatSendAllowedAt)
{
RefocusChatInputNextFrame();
UpdateChatSendUiState();
return;
}
string content = playerMessageInputField.text != null ? playerMessageInputField.text.Trim() : string.Empty;
if (string.IsNullOrWhiteSpace(content))
{
RefocusChatInputNextFrame();
return;
}
if (_isSendingRoomMessage && string.Equals(content, _pendingRoomMessageContent, StringComparison.Ordinal))
{
RefocusChatInputNextFrame();
return;
}
BeginChatCooldown();
_isSendingRoomMessage = true;
_pendingRoomMessageContent = content;
try
{
playerMessageInputField.text = string.Empty;
RefocusChatInputNextFrame();
await _service.SendRoomMessage(content);
}
catch (Exception ex)
{
if (playerMessageInputField != null && string.IsNullOrEmpty(playerMessageInputField.text))
{
playerMessageInputField.text = content;
RefocusChatInputNextFrame();
}
Debug.LogWarning($"[roomDetails] Send room message failed: {ex.Message}");
}
finally
{
_isSendingRoomMessage = false;
_pendingRoomMessageContent = string.Empty;
}
}
private void OnChatInputEndEdit(string value)
{
if (!isActiveAndEnabled || playerMessageInputField == null)
{
return;
}
if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter))
{
OnSendMessageClicked();
}
}
private void BeginChatCooldown()
{
_nextChatSendAllowedAt = Time.unscaledTime + ChatSendCooldownSeconds;
if (_chatCooldownRoutine != null)
{
StopCoroutine(_chatCooldownRoutine);
}
_chatCooldownRoutine = StartCoroutine(ChatCooldownCoroutine());
UpdateChatSendUiState();
}
private IEnumerator ChatCooldownCoroutine()
{
while (Time.unscaledTime < _nextChatSendAllowedAt)
{
UpdateChatSendUiState();
yield return null;
}
_chatCooldownRoutine = null;
UpdateChatSendUiState();
}
private void UpdateChatSendUiState()
{
bool coolingDown = Time.unscaledTime < _nextChatSendAllowedAt;
if (sendMessageButton != null)
{
sendMessageButton.interactable = !coolingDown;
}
if (_chatPlaceholderText != null)
{
if (coolingDown)
{
float remain = Mathf.Max(0f, _nextChatSendAllowedAt - Time.unscaledTime);
_chatPlaceholderText.text = string.Format(CooldownPlaceholderFormat, Mathf.CeilToInt(remain));
}
else
{
_chatPlaceholderText.text = _defaultChatPlaceholder;
}
}
}
private void RestoreDefaultChatPlaceholder()
{
if (_chatPlaceholderText != null)
{
_chatPlaceholderText.text = _defaultChatPlaceholder;
}
}
private void RenderChatMessages(IReadOnlyList<ArenaRoomChatMessage> messages, bool preserveWindow)
{
if (messageParent == null || messages == null || playerMessagePrefab == null)
{
ClearDisplayedMessages();
return;
}
List<RoomFeedEntry> feed = BuildRoomFeed(messages);
UpdateChatWindowState(feed.Count, preserveWindow);
int startIndex = Mathf.Clamp(_chatWindowStartIndex, 0, Mathf.Max(0, feed.Count - 1));
int endIndex = Mathf.Min(feed.Count, startIndex + GetRenderedChatWindowCapacity());
var visibleEntries = new List<RoomFeedEntry>(Mathf.Max(0, endIndex - startIndex));
for (int i = startIndex; i < endIndex; i++)
{
visibleEntries.Add(feed[i]);
}
bool changed = ApplyRenderedChatEntries(visibleEntries);
if (changed)
{
ScheduleMessageLayoutRefresh(_isChatViewPinnedToBottom);
}
}
private bool ApplyRenderedChatEntries(List<RoomFeedEntry> visibleEntries)
{
var desiredKeys = new List<string>(visibleEntries.Count);
for (int i = 0; i < visibleEntries.Count; i++)
{
desiredKeys.Add(visibleEntries[i].key);
}
if (desiredKeys.Count == _renderedChatKeys.Count)
{
bool identical = true;
for (int i = 0; i < desiredKeys.Count; i++)
{
if (!string.Equals(desiredKeys[i], _renderedChatKeys[i], StringComparison.Ordinal))
{
identical = false;
break;
}
}
if (identical)
{
return false;
}
}
int prefix = 0;
int sharedCount = Mathf.Min(_renderedChatKeys.Count, desiredKeys.Count);
while (prefix < sharedCount && string.Equals(_renderedChatKeys[prefix], desiredKeys[prefix], StringComparison.Ordinal))
{
prefix++;
}
int suffix = 0;
while (suffix < (_renderedChatKeys.Count - prefix)
&& suffix < (desiredKeys.Count - prefix)
&& string.Equals(
_renderedChatKeys[_renderedChatKeys.Count - 1 - suffix],
desiredKeys[desiredKeys.Count - 1 - suffix],
StringComparison.Ordinal))
{
suffix++;
}
for (int i = _renderedChatObjects.Count - suffix - 1; i >= prefix; i--)
{
GameObject instance = _renderedChatObjects[i];
if (instance != null)
{
Destroy(instance);
}
_renderedChatObjects.RemoveAt(i);
_renderedChatKeys.RemoveAt(i);
}
for (int i = prefix; i < desiredKeys.Count - suffix; i++)
{
GameObject instance = CreateChatFeedObject(visibleEntries[i]);
_renderedChatObjects.Insert(i, instance);
_renderedChatKeys.Insert(i, desiredKeys[i]);
RegisterMessageLayoutDirtyRoot(instance);
}
for (int i = 0; i < _renderedChatObjects.Count; i++)
{
if (_renderedChatObjects[i] != null)
{
_renderedChatObjects[i].transform.SetSiblingIndex(i);
}
}
return true;
}
private GameObject CreateChatFeedObject(RoomFeedEntry feedEntry)
{
object entry = feedEntry.entry;
if (entry is RoomSystemFeedEntry systemEntry)
{
if (systemMessagePrefab == null)
{
return null;
}
GameObject systemInstance = Instantiate(systemMessagePrefab, messageParent, false);
systemMessagePrefab systemController = systemInstance.GetComponent<systemMessagePrefab>();
if (systemController != null)
{
systemController.Bind(systemEntry.richText);
}
UI_OrderedEntryAnimator.PlayFadeOnly(systemInstance, Mathf.Max(0, messageParent.childCount - 1), 0.14f, 0.01f, true);
return systemInstance;
}
ArenaRoomChatMessage message = entry as ArenaRoomChatMessage;
if (message == null)
{
return null;
}
string localSteamId = NetworkManager.Instance != null ? NetworkManager.Instance.SteamId : string.Empty;
string senderSteamId = GetMessageSenderSteamId(message);
bool isSelf = !string.IsNullOrWhiteSpace(localSteamId)
&& string.Equals(senderSteamId, localSteamId, StringComparison.Ordinal);
Color bubbleColor = ResolveMessageBubbleColor(isSelf);
string displayName = ResolveMessageDisplayName(message, isSelf);
string title = ResolveMessageTitle(message);
GameObject instance = Instantiate(playerMessagePrefab, messageParent, false);
playerMessagePrefab controller = instance.GetComponent<playerMessagePrefab>();
if (controller != null)
{
string avatarUrl = GameServer.Client.NetworkManager.ResolveAvatarUrl(message.avatar_url, senderSteamId);
controller.Bind(senderSteamId, avatarUrl, displayName, title, message.content, bubbleColor);
}
UI_OrderedEntryAnimator.PlayFadeOnly(instance, Mathf.Max(0, messageParent.childCount - 1), 0.14f, 0.01f, true);
return instance;
}
private static string BuildRoomSystemKey(RoomSystemFeedEntry entry)
{
return $"system:{entry.createdAtUnixMs}:{entry.richText}";
}
private static string BuildRoomMessageKey(ArenaRoomChatMessage message)
{
if (message == null)
{
return "message:null";
}
if (message.id > 0)
{
return $"message:id:{message.id}";
}
string sender = GetMessageSenderSteamId(message);
return $"message:fallback:{message.room_code}:{sender}:{message.created_at}:{message.content}";
}
private void TrimSystemFeedCache()
{
int overflow = _systemFeedEntries.Count - GetEffectiveCacheLimit();
if (overflow > 0)
{
_systemFeedEntries.RemoveRange(0, overflow);
}
}
private List<RoomFeedEntry> BuildRoomFeed(IReadOnlyList<ArenaRoomChatMessage> messages)
{
List<RoomFeedEntry> feed = new List<RoomFeedEntry>();
int order = 0;
int startIndex = Mathf.Max(0, messages.Count - GetEffectiveCacheLimit());
for (int i = startIndex; i < messages.Count; i++)
{
ArenaRoomChatMessage message = messages[i];
if (message != null)
{
string key = BuildRoomMessageKey(message);
feed.Add(new RoomFeedEntry
{
entry = message,
timestampMs = ParseMessageUnixTimestamp(message.created_at),
order = order++,
key = key,
sequence = GetOrAssignFeedSequence(key)
});
}
}
for (int i = 0; i < _systemFeedEntries.Count; i++)
{
RoomSystemFeedEntry systemEntry = _systemFeedEntries[i];
if (systemEntry == null)
{
continue;
}
feed.Add(new RoomFeedEntry
{
entry = systemEntry,
timestampMs = systemEntry.createdAtUnixMs,
order = order++,
key = BuildRoomSystemKey(systemEntry),
sequence = systemEntry.sequence
});
}
feed.Sort(CompareFeedEntries);
return feed;
}
private void UpdateChatWindowState(int feedCount, bool preserveWindow)
{
int windowCapacity = GetRenderedChatWindowCapacity();
int maxStart = Mathf.Max(0, feedCount - windowCapacity);
if (!preserveWindow || _chatWindowStartIndex < 0 || _isChatViewPinnedToBottom)
{
_chatWindowStartIndex = maxStart;
_isChatViewPinnedToBottom = true;
return;
}
_chatWindowStartIndex = Mathf.Clamp(_chatWindowStartIndex, 0, maxStart);
}
private void OnChatScrollValueChanged(Vector2 normalizedPosition)
{
if (_suppressChatScrollEvents || _service == null)
{
return;
}
List<RoomFeedEntry> feed = BuildRoomFeed(_service.GetRoomChatMessages());
if (feed.Count <= GetRenderedChatWindowCapacity())
{
_isChatViewPinnedToBottom = true;
return;
}
if (normalizedPosition.y <= 0.05f)
{
int maxStart = Mathf.Max(0, feed.Count - GetRenderedChatWindowCapacity());
if (_chatWindowStartIndex < maxStart)
{
_chatWindowStartIndex = Mathf.Min(maxStart, _chatWindowStartIndex + GetEffectiveDisplayWindow());
_isChatViewPinnedToBottom = _chatWindowStartIndex >= maxStart;
RenderChatMessages(_service.GetRoomChatMessages(), true);
SetChatScrollNormalizedPosition(0.15f);
return;
}
_isChatViewPinnedToBottom = true;
}
else if (normalizedPosition.y >= 0.95f && _chatWindowStartIndex > 0)
{
_chatWindowStartIndex = Mathf.Max(0, _chatWindowStartIndex - GetEffectiveDisplayWindow());
_isChatViewPinnedToBottom = false;
RenderChatMessages(_service.GetRoomChatMessages(), true);
SetChatScrollNormalizedPosition(0.85f);
}
else
{
_isChatViewPinnedToBottom = false;
}
}
private static int CompareFeedEntries(RoomFeedEntry left, RoomFeedEntry right)
{
int compare = left.sequence.CompareTo(right.sequence);
if (compare != 0)
{
return compare;
}
compare = left.timestampMs.CompareTo(right.timestampMs);
if (compare != 0)
{
return compare;
}
return left.order.CompareTo(right.order);
}
private long GetOrAssignFeedSequence(string key)
{
if (string.IsNullOrWhiteSpace(key))
{
return _nextFeedSequence++;
}
if (_feedSequences.TryGetValue(key, out long sequence))
{
return sequence;
}
sequence = _nextFeedSequence++;
_feedSequences[key] = sequence;
return sequence;
}
private static long ParseMessageUnixTimestamp(string createdAt)
{
if (string.IsNullOrWhiteSpace(createdAt))
{
return long.MaxValue;
}
if (long.TryParse(createdAt, out long unixValue))
{
return unixValue >= 1_000_000_000_000L ? unixValue : unixValue * 1000L;
}
if (DateTimeOffset.TryParse(createdAt, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out DateTimeOffset parsed))
{
return parsed.ToUnixTimeMilliseconds();
}
return long.MaxValue;
}
private void ClearDisplayedMessages()
{
if (messageParent == null)
{
_renderedChatKeys.Clear();
_renderedChatObjects.Clear();
return;
}
for (int i = messageParent.childCount - 1; i >= 0; i--)
{
Transform child = messageParent.GetChild(i);
child.SetParent(null, false);
child.gameObject.SetActive(false);
Destroy(child.gameObject);
}
_renderedChatKeys.Clear();
_renderedChatObjects.Clear();
}
private Color ResolveMessageBubbleColor(bool isSelf)
{
if (messageTypeColors == null || messageTypeColors.Length == 0)
{
return Color.white;
}
if (isSelf && messageTypeColors.Length > 1)
{
return messageTypeColors[1];
}
return messageTypeColors[0];
}
private string ResolveMessageDisplayName(ArenaRoomChatMessage message, bool isSelf)
{
if (message != null && !string.IsNullOrWhiteSpace(message.display_name))
{
string senderSteamId = GetMessageSenderSteamId(message);
if (GameServer.Client.NetworkManager.IsUsableDisplayName(message.display_name, senderSteamId))
{
return message.display_name;
}
}
string messageSteamId = GetMessageSenderSteamId(message);
if (isSelf && NetworkManager.Instance != null && GameServer.Client.NetworkManager.IsUsableDisplayName(NetworkManager.Instance.SteamDisplayName, messageSteamId))
{
return NetworkManager.Instance.SteamDisplayName;
}
if (_service != null && _service.CurrentRoom != null && _service.CurrentRoom.participants != null && message != null)
{
foreach (ArenaRoomParticipant participant in _service.CurrentRoom.participants)
{
if (participant != null
&& string.Equals(participant.steam_id, messageSteamId, StringComparison.Ordinal)
&& GameServer.Client.NetworkManager.IsUsableDisplayName(participant.display_name, messageSteamId))
{
return participant.display_name;
}
}
}
return GameServer.Client.NetworkManager.ResolveDisplayName(message != null ? message.display_name : string.Empty, messageSteamId, false);
}
private static string ResolveMessageTitle(ArenaRoomChatMessage message)
{
return message != null && !string.IsNullOrWhiteSpace(message.player_title)
? message.player_title
: string.Empty;
}
private static string GetMessageSenderSteamId(ArenaRoomChatMessage message)
{
if (message == null)
{
return string.Empty;
}
if (!string.IsNullOrWhiteSpace(message.sender_id))
{
return message.sender_id;
}
return message.steam_id ?? string.Empty;
}
private void ScheduleMessageLayoutRefresh(bool scrollToBottom)
{
_messageLayoutRefreshDirty = true;
_scrollChatToBottomAfterLayout |= scrollToBottom;
if (_messageLayoutRefreshCoroutine == null)
{
_messageLayoutRefreshCoroutine = StartCoroutine(DeferredMessageLayoutRefreshCoroutine());
}
}
private IEnumerator DeferredMessageLayoutRefreshCoroutine()
{
while (true)
{
yield return null;
bool shouldRebuild = _messageLayoutRefreshDirty;
bool shouldScrollToBottom = _scrollChatToBottomAfterLayout;
_messageLayoutRefreshDirty = false;
_scrollChatToBottomAfterLayout = false;
if (shouldRebuild)
{
ForceRebuildMessageLayoutImmediate();
if (shouldScrollToBottom && _messageScrollRect != null)
{
Canvas.ForceUpdateCanvases();
_messageScrollRect.verticalNormalizedPosition = 0f;
}
}
if (!_messageLayoutRefreshDirty)
{
break;
}
}
_messageLayoutRefreshCoroutine = null;
}
private void ForceRebuildMessageLayoutImmediate()
{
if (!(messageParent is RectTransform messageParentRect))
{
return;
}
for (int i = 0; i < _pendingMessageLayoutDirtyRoots.Count; i++)
{
RectTransform childRect = _pendingMessageLayoutDirtyRoots[i];
if (childRect != null)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(childRect);
}
}
_pendingMessageLayoutDirtyRoots.Clear();
LayoutRebuilder.ForceRebuildLayoutImmediate(messageParentRect);
if (messageParentRect.parent is RectTransform parentRect)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(parentRect);
}
Canvas.ForceUpdateCanvases();
}
private void RefocusChatInputNextFrame()
{
if (playerMessageInputField == null)
{
return;
}
_refocusChatInputRequested = true;
if (_refocusChatInputCoroutine == null)
{
_refocusChatInputCoroutine = StartCoroutine(RefocusChatInputCoroutine());
}
}
private IEnumerator RefocusChatInputCoroutine()
{
while (true)
{
yield return null;
if (!_refocusChatInputRequested)
{
break;
}
_refocusChatInputRequested = false;
if (playerMessageInputField == null)
{
continue;
}
EventSystem.current?.SetSelectedGameObject(playerMessageInputField.gameObject);
playerMessageInputField.Select();
playerMessageInputField.ActivateInputField();
playerMessageInputField.MoveTextEnd(false);
}
_refocusChatInputCoroutine = null;
}
private void SetChatScrollNormalizedPosition(float value)
{
if (_messageScrollRect == null)
{
return;
}
_suppressChatScrollEvents = true;
_messageScrollRect.verticalNormalizedPosition = Mathf.Clamp01(value);
_suppressChatScrollEvents = false;
}
private void RegisterMessageLayoutDirtyRoot(GameObject instance)
{
if (instance == null)
{
return;
}
RectTransform rect = instance.transform as RectTransform;
if (rect == null)
{
return;
}
for (int i = 0; i < _pendingMessageLayoutDirtyRoots.Count; i++)
{
if (_pendingMessageLayoutDirtyRoots[i] == rect)
{
return;
}
}
_pendingMessageLayoutDirtyRoots.Add(rect);
}
private void OnShowRoomRankingClicked()
{
if (_service == null)
{
return;
}
GameObject template = _roomRankingPrefabTemplate;
Transform parent = _roomRankingSpawnParent != null ? _roomRankingSpawnParent : transform.parent;
if (template == null || parent == null)
{
Debug.LogWarning("[roomDetails] Room ranking prefab or parent is missing.");
return;
}
if (_spawnedRoomRankingInstance == null)
{
_spawnedRoomRankingInstance = Instantiate(template, parent, false);
RectTransform rect = _spawnedRoomRankingInstance.GetComponent<RectTransform>();
if (rect != null)
{
rect.localScale = Vector3.one;
rect.anchoredPosition3D = Vector3.zero;
}
}
UIBackStack.RegisterOrBump(
_spawnedRoomRankingInstance,
() => _spawnedRoomRankingInstance != null && _spawnedRoomRankingInstance.activeInHierarchy,
() =>
{
if (_spawnedRoomRankingInstance != null)
{
CloseRoomRankingWithExitAnim();
}
return true;
});
rankingListPrefab ranking = _spawnedRoomRankingInstance.GetComponent<rankingListPrefab>();
if (ranking == null)
{
Debug.LogWarning("[roomDetails] Room ranking prefab is missing rankingListPrefab component.");
return;
}
ArenaRoomSnapshot snapshot = _service.CurrentRoom;
LeaderboardData roomRanking = _service.GetRoomRankingLeaderboardData();
string localSteamId = NetworkManager.Instance != null ? NetworkManager.Instance.SteamId : string.Empty;
ranking.BindRoomRanking(
snapshot != null ? snapshot.song_id : string.Empty,
snapshot != null ? snapshot.song_name : string.Empty,
roomRanking,
localSteamId);
}
private void CloseSelfWithExitAnim()
{
if (_isClosingSelf)
{
return;
}
_isClosingSelf = true;
UI_PanelExitUtility.PlayExitThen(gameObject, () =>
{
if (gameObject != null)
{
Destroy(gameObject);
}
});
}
private void CloseRoomRankingWithExitAnim()
{
GameObject rankingInstance = _spawnedRoomRankingInstance;
if (rankingInstance == null)
{
return;
}
UIBackStack.Unregister(rankingInstance);
_spawnedRoomRankingInstance = null;
UI_PanelExitUtility.PlayExitThen(rankingInstance, () =>
{
if (rankingInstance != null)
{
Destroy(rankingInstance);
}
});
}
private void EnsurePopupTween()
{
if (GetComponent<UI_PanelPopupTween>() == null)
{
gameObject.AddComponent<UI_PanelPopupTween>();
}
}
private void UpdateQuitButtonTexts(ArenaRoomSnapshot snapshot)
{
string localSteamId = NetworkManager.Instance != null ? NetworkManager.Instance.SteamId : string.Empty;
bool localIsHost = IsLocalHost(snapshot, localSteamId);
string buttonText = localIsHost ? "解散房间" : "退出房间";
SetButtonText(quitRoom, buttonText);
SetButtonText(quitRoom2, buttonText);
}
private void UpdateReadyButton(ArenaRoomSnapshot snapshot)
{
if (getReady == null)
{
return;
}
string localSteamId = NetworkManager.Instance != null ? NetworkManager.Instance.SteamId : string.Empty;
bool localIsHost = IsLocalHost(snapshot, localSteamId);
int readyCount = CountReadyPlayers(snapshot);
int totalCount = snapshot != null
? Mathf.Max(snapshot.player_count, snapshot.participants != null ? snapshot.participants.Count : 0)
: 0;
bool roomStarted = IsRoomStarted(snapshot);
string buttonText;
bool interactable;
if (localIsHost)
{
buttonText = $"开始游戏({readyCount}/{totalCount}";
interactable = !roomStarted && CanHostStartGame(snapshot);
}
else
{
bool localReady = IsLocalReady(snapshot, localSteamId);
buttonText = localReady ? "取消准备" : "准备";
interactable = !roomStarted;
}
getReady.interactable = interactable;
SetButtonText(getReady, buttonText);
}
private static bool IsLocalHost(ArenaRoomSnapshot snapshot, string localSteamId)
{
if (snapshot == null || string.IsNullOrWhiteSpace(localSteamId))
{
return false;
}
if (string.Equals(snapshot.host_steam_id, localSteamId, StringComparison.Ordinal))
{
return true;
}
if (snapshot.participants == null)
{
return false;
}
foreach (ArenaRoomParticipant participant in snapshot.participants)
{
if (participant != null
&& string.Equals(participant.steam_id, localSteamId, StringComparison.Ordinal)
&& participant.is_host)
{
return true;
}
}
return false;
}
private static bool IsLocalReady(ArenaRoomSnapshot snapshot, string localSteamId)
{
if (snapshot == null || snapshot.participants == null || string.IsNullOrWhiteSpace(localSteamId))
{
return false;
}
foreach (ArenaRoomParticipant participant in snapshot.participants)
{
if (participant != null && string.Equals(participant.steam_id, localSteamId, StringComparison.Ordinal))
{
return participant.is_ready;
}
}
return false;
}
private static int CountReadyPlayers(ArenaRoomSnapshot snapshot)
{
if (snapshot == null || snapshot.participants == null)
{
return 0;
}
int count = 0;
foreach (ArenaRoomParticipant participant in snapshot.participants)
{
if (participant != null && participant.is_ready)
{
count++;
}
}
return count;
}
private static bool CanHostStartGame(ArenaRoomSnapshot snapshot)
{
if (snapshot == null || snapshot.participants == null || snapshot.participants.Count < 2)
{
return false;
}
foreach (ArenaRoomParticipant participant in snapshot.participants)
{
if (participant == null)
{
continue;
}
if (!participant.is_host && !participant.is_ready)
{
return false;
}
}
return true;
}
private static bool IsRoomStarted(ArenaRoomSnapshot snapshot)
{
if (snapshot == null || string.IsNullOrWhiteSpace(snapshot.status))
{
return false;
}
return string.Equals(snapshot.status, "STARTED", StringComparison.OrdinalIgnoreCase)
|| string.Equals(snapshot.status, "PLAYING", StringComparison.OrdinalIgnoreCase);
}
private void EnsureSongDropdownsReady()
{
if (songDropdown == null && difficultyDropdown == null)
{
return;
}
SongDataLibrary library = SongDataLibrary.Instance;
if (library != null && library.IsLoaded)
{
RefreshSongDropdowns(_service != null ? _service.CurrentRoom : null);
return;
}
if (_songLibraryWaitRoutine == null)
{
_songLibraryWaitRoutine = StartCoroutine(WaitForSongLibraryAndRefresh());
}
}
private IEnumerator WaitForSongLibraryAndRefresh()
{
float timeoutAt = Time.realtimeSinceStartup + 8f;
while (Time.realtimeSinceStartup < timeoutAt)
{
SongDataLibrary library = SongDataLibrary.Instance;
if (library != null && library.IsLoaded)
{
_songLibraryWaitRoutine = null;
RefreshSongDropdowns(_service != null ? _service.CurrentRoom : null);
yield break;
}
yield return null;
}
_songLibraryWaitRoutine = null;
Debug.LogWarning("[roomDetails] SongDataLibrary was not ready; song dropdowns were left empty.");
}
private void RefreshSongDropdowns(ArenaRoomSnapshot snapshot)
{
if (songDropdown == null && difficultyDropdown == null)
{
return;
}
SongDataLibrary library = SongDataLibrary.Instance;
if (library == null || !library.IsLoaded)
{
EnsureSongDropdownsReady();
UpdateSongDropdownInteractable(snapshot);
return;
}
RebuildSelectableSongs(library);
SongData selectedSong = ResolveSnapshotSong(snapshot);
if (selectedSong == null && _selectableSongs.Count > 0)
{
selectedSong = _selectableSongs[0];
}
int selectedDifficulty = ConvertDifficultyKeyToId(snapshot != null ? snapshot.difficulty : null);
if (selectedSong != null)
{
RebuildSelectableDifficulties(selectedSong);
if (!_selectableDifficulties.Contains(selectedDifficulty))
{
selectedDifficulty = _selectableDifficulties.Count > 0 ? _selectableDifficulties[0] : selectedDifficulty;
}
}
else
{
_selectableDifficulties.Clear();
}
_suppressSongDropdownEvents = true;
try
{
PopulateSongDropdown(selectedSong);
PopulateDifficultyDropdown(selectedSong, selectedDifficulty);
}
finally
{
_suppressSongDropdownEvents = false;
}
UpdateSongDropdownInteractable(snapshot);
}
private void RebuildSelectableSongs(SongDataLibrary library)
{
_selectableSongs.Clear();
List<SongData> songs = library != null ? library.GetAllSongs() : null;
if (songs == null)
{
return;
}
songs.Sort((left, right) =>
{
int idCompare = (left != null ? left.songID : int.MaxValue).CompareTo(right != null ? right.songID : int.MaxValue);
if (idCompare != 0)
{
return idCompare;
}
return string.Compare(left != null ? left.songName : string.Empty,
right != null ? right.songName : string.Empty,
StringComparison.CurrentCulture);
});
foreach (SongData song in songs)
{
if (song != null && HasPlayableDifficulty(song))
{
_selectableSongs.Add(song);
}
}
}
private SongData ResolveSnapshotSong(ArenaRoomSnapshot snapshot)
{
if (snapshot != null && int.TryParse(snapshot.song_id, out int songId))
{
for (int i = 0; i < _selectableSongs.Count; i++)
{
SongData song = _selectableSongs[i];
if (song != null && song.songID == songId)
{
return song;
}
}
}
return null;
}
private void RebuildSelectableDifficulties(SongData song)
{
_selectableDifficulties.Clear();
if (song == null || song.chartFiles == null)
{
return;
}
foreach (ChartFileEntry entry in song.chartFiles)
{
if (entry == null || entry.chartFile == null || _selectableDifficulties.Contains(entry.difficulty))
{
continue;
}
_selectableDifficulties.Add(entry.difficulty);
}
_selectableDifficulties.Sort();
}
private void PopulateSongDropdown(SongData selectedSong)
{
if (songDropdown == null)
{
return;
}
songDropdown.ClearOptions();
List<string> options = new List<string>();
for (int i = 0; i < _selectableSongs.Count; i++)
{
SongData song = _selectableSongs[i];
options.Add(song != null && !string.IsNullOrWhiteSpace(song.songName) ? song.songName : $"Song {song?.songID ?? 0}");
}
songDropdown.AddOptions(options);
int selectedIndex = selectedSong != null ? _selectableSongs.IndexOf(selectedSong) : -1;
songDropdown.value = Mathf.Clamp(selectedIndex, 0, Mathf.Max(0, _selectableSongs.Count - 1));
songDropdown.RefreshShownValue();
}
private void PopulateDifficultyDropdown(SongData selectedSong, int selectedDifficulty)
{
if (difficultyDropdown == null)
{
return;
}
difficultyDropdown.ClearOptions();
List<string> options = new List<string>();
for (int i = 0; i < _selectableDifficulties.Count; i++)
{
options.Add(FormatDifficultyOption(selectedSong, _selectableDifficulties[i]));
}
difficultyDropdown.AddOptions(options);
int selectedIndex = _selectableDifficulties.IndexOf(selectedDifficulty);
difficultyDropdown.value = Mathf.Clamp(selectedIndex, 0, Mathf.Max(0, _selectableDifficulties.Count - 1));
difficultyDropdown.RefreshShownValue();
}
private void UpdateSongDropdownInteractable(ArenaRoomSnapshot snapshot)
{
string localSteamId = NetworkManager.Instance != null ? NetworkManager.Instance.SteamId : string.Empty;
bool canChange = !_isChangingRoomSong
&& snapshot != null
&& IsLocalHost(snapshot, localSteamId)
&& !IsRoomStarted(snapshot)
&& _selectableSongs.Count > 0;
if (songDropdown != null)
{
songDropdown.interactable = canChange;
}
if (difficultyDropdown != null)
{
difficultyDropdown.interactable = canChange && _selectableDifficulties.Count > 0;
}
}
private void OnSongDropdownChanged(int index)
{
if (_suppressSongDropdownEvents || index < 0 || index >= _selectableSongs.Count)
{
return;
}
SongData song = _selectableSongs[index];
int currentDifficulty = GetSelectedDifficultyId();
RebuildSelectableDifficulties(song);
if (!_selectableDifficulties.Contains(currentDifficulty))
{
currentDifficulty = _selectableDifficulties.Count > 0 ? _selectableDifficulties[0] : 2;
}
_suppressSongDropdownEvents = true;
try
{
PopulateDifficultyDropdown(song, currentDifficulty);
}
finally
{
_suppressSongDropdownEvents = false;
}
RequestChangeRoomSong(song, currentDifficulty);
}
private void OnDifficultyDropdownChanged(int index)
{
if (_suppressSongDropdownEvents || index < 0 || index >= _selectableDifficulties.Count)
{
return;
}
SongData song = GetSelectedSong();
RequestChangeRoomSong(song, _selectableDifficulties[index]);
}
private async void RequestChangeRoomSong(SongData song, int difficultyId)
{
if (_service == null || song == null || _isChangingRoomSong)
{
return;
}
ArenaRoomSnapshot snapshot = _service.CurrentRoom;
string localSteamId = NetworkManager.Instance != null ? NetworkManager.Instance.SteamId : string.Empty;
if (!IsLocalHost(snapshot, localSteamId) || IsRoomStarted(snapshot))
{
RefreshSongDropdowns(snapshot);
return;
}
string difficultyKey = ConvertDifficultyIdToKey(difficultyId);
if (snapshot != null
&& string.Equals(snapshot.song_id, song.songID.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal)
&& string.Equals((snapshot.difficulty ?? string.Empty).Trim(), difficultyKey, StringComparison.OrdinalIgnoreCase))
{
return;
}
_isChangingRoomSong = true;
UpdateSongDropdownInteractable(snapshot);
try
{
await _service.ChangeRoomSong(
song.songID.ToString(CultureInfo.InvariantCulture),
song.songName,
difficultyKey);
}
catch (Exception ex)
{
Debug.LogWarning($"[roomDetails] Change room song failed: {ex.Message}");
RefreshSongDropdowns(_service.CurrentRoom);
}
finally
{
_isChangingRoomSong = false;
UpdateSongDropdownInteractable(_service.CurrentRoom);
}
}
private SongData GetSelectedSong()
{
if (songDropdown == null)
{
return ResolveSnapshotSong(_service != null ? _service.CurrentRoom : null);
}
int index = songDropdown.value;
return index >= 0 && index < _selectableSongs.Count ? _selectableSongs[index] : null;
}
private int GetSelectedDifficultyId()
{
if (difficultyDropdown != null)
{
int index = difficultyDropdown.value;
if (index >= 0 && index < _selectableDifficulties.Count)
{
return _selectableDifficulties[index];
}
}
return ConvertDifficultyKeyToId(_service != null && _service.CurrentRoom != null ? _service.CurrentRoom.difficulty : null);
}
private static bool HasPlayableDifficulty(SongData song)
{
if (song == null || song.chartFiles == null)
{
return false;
}
foreach (ChartFileEntry entry in song.chartFiles)
{
if (entry != null && entry.chartFile != null)
{
return true;
}
}
return false;
}
private static string FormatDifficultyOption(SongData song, int difficultyId)
{
string shortName = FormatDifficulty(ConvertDifficultyIdToKey(difficultyId));
ChartFileEntry entry = song != null && song.chartFiles != null
? song.chartFiles.Find(item => item != null && item.difficulty == difficultyId)
: null;
if (entry != null && entry.difficultyLEVEL > 0f)
{
return $"{shortName} Lv.{entry.difficultyLEVEL:0.#}";
}
return shortName;
}
private static int ConvertDifficultyKeyToId(string difficultyKey)
{
if (string.IsNullOrWhiteSpace(difficultyKey))
{
return 2;
}
switch (difficultyKey.Trim().ToLowerInvariant())
{
case "ez":
return 0;
case "hd":
return 1;
case "im":
return 3;
case "in":
default:
return 2;
}
}
private static string ConvertDifficultyIdToKey(int difficultyId)
{
switch (difficultyId)
{
case 0:
return "ez";
case 1:
return "hd";
case 3:
return "im";
case 2:
default:
return "in";
}
}
private static void SetButtonText(Button button, string text)
{
if (button == null)
{
return;
}
Text label = button.GetComponentInChildren<Text>(true);
if (label != null)
{
label.text = text;
}
}
private static string FormatDifficulty(string difficulty)
{
if (string.IsNullOrWhiteSpace(difficulty))
{
return string.Empty;
}
switch (difficulty.Trim().ToLowerInvariant())
{
case "ez":
return "EZ";
case "hd":
return "HD";
case "in":
return "IN";
case "im":
return "IM";
default:
return difficulty;
}
}
private int GetEffectiveCacheLimit()
{
return Mathf.Max(InitialHistoryLoadLimit, Mathf.Max(1, maxMessageHistory));
}
private int GetEffectiveDisplayWindow()
{
return Mathf.Clamp(messageDisplayWindow, 1, MaxDisplayWindowLimit);
}
private int GetRenderedChatWindowCapacity()
{
return Mathf.Max(GetEffectiveDisplayWindow(), Mathf.Min(GetEffectiveCacheLimit(), GetEffectiveDisplayWindow() * 3));
}
}