ui基本完毕,修了一大把的bug

This commit is contained in:
FloatGaming
2026-07-13 02:28:39 +08:00
parent 1e20d73e90
commit fd22501f71
958 changed files with 378289 additions and 41038 deletions
@@ -445,6 +445,10 @@ namespace GameServer.Client
{
song_id = CurrentRoom != null ? CurrentRoom.song_id : string.Empty,
cycle_id = 0,
server_name = "雅莉梦璃高新产业园",
leaderboard_cycle_days = 3,
rank_version_id = 20260101,
next_settle_at = "2026-01-01 00:00:00",
rankings = BuildRoomRankingEntries()
};
return result;
@@ -628,10 +632,24 @@ namespace GameServer.Client
}
await EnsureSocialConnected();
await SendSocialAction("SEND_WORLD_MESSAGE", new
JObject resp = await SendSocialAction("SEND_WORLD_MESSAGE", new
{
content = trimmed
});
JObject messageObject = resp["message"] as JObject ?? resp;
ArenaRoomChatMessage message = messageObject.ToObject<ArenaRoomChatMessage>();
if (message == null)
{
return;
}
if (string.IsNullOrWhiteSpace(message.sender_id))
{
message.sender_id = NetworkManager.Instance != null ? NetworkManager.Instance.SteamId : string.Empty;
}
AppendWorldChatMessage(message);
}
public async Task<List<ArenaRoomChatMessage>> LoadPrivateHistory(string partnerId, int limit, long? beforeId = null)
@@ -62,6 +62,13 @@ public class GameServerBridge : MonoBehaviour
return;
}
if (!LeaderboardConsentUtility.HasLeaderboardConsent())
{
Debug.Log("[Bridge] 玩家未同意加入排行榜,跳过世界排行榜提交");
_hasSubmitted = true;
return;
}
_hasSubmitted = true;
try
@@ -143,6 +150,10 @@ public class GameServerBridge : MonoBehaviour
await Task.Delay(3000);
SubmitCurrentSettlement();
}
else if (result == "OPT_OUT")
{
Debug.Log("[Bridge] 当前已关闭排行榜加入选项,未提交世界排行榜。");
}
else
{
Debug.LogWarning($"╔══════════════════════════════════════════════════════╗");
@@ -105,6 +105,36 @@ namespace GameServer.Client
}
}
public async Task<LeaderboardData> ForceRefresh(string songId)
{
if (string.IsNullOrWhiteSpace(songId))
{
throw new ArgumentException("songId is required", nameof(songId));
}
NetworkManager nm = NetworkManager.Instance;
if (nm == null)
{
throw new InvalidOperationException("NetworkManager is not available.");
}
Invalidate(songId);
Task<LeaderboardData> task = FetchAndCache(songId, nm);
_inflight[songId] = task;
try
{
return await task;
}
finally
{
if (_inflight.TryGetValue(songId, out Task<LeaderboardData> current) && current == task)
{
_inflight.Remove(songId);
}
}
}
public void Invalidate(string songId)
{
if (string.IsNullOrWhiteSpace(songId))
@@ -113,6 +143,7 @@ namespace GameServer.Client
}
_cache.Remove(songId);
_inflight.Remove(songId);
}
private async void HandleSceneLoaded(Scene scene, LoadSceneMode mode)
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;
using UnityEngine;
namespace GameServer.Client
{
@@ -88,12 +90,35 @@ namespace GameServer.Client
[JsonProperty("avatar_url")] public string avatar_url;
[JsonProperty("player_level")] public int player_level;
[JsonProperty("leaderboard_opt_out")] public bool leaderboard_opt_out;
[JsonProperty("leaderboard_can_change_today")] public bool leaderboard_can_change_today;
[JsonProperty("leaderboard_changed_today")] public bool leaderboard_changed_today;
[JsonProperty("leaderboard_next_change_at")] public string leaderboard_next_change_at;
[JsonProperty("leaderboard_cycle_id")] public int leaderboard_cycle_id;
[JsonProperty("total_play_seconds")] public double total_play_seconds;
[JsonProperty("total_plays")] public int total_plays;
[JsonProperty("actime")] public string actime;
[JsonProperty("u_reg_id")] public int u_reg_id;
}
[Serializable]
public class LeaderboardMembershipResponse
{
[JsonProperty("success")] public bool success;
[JsonProperty("error_code")] public string error_code;
[JsonProperty("message")] public string message;
[JsonProperty("steam_id")] public string steam_id;
[JsonProperty("cycle_id")] public int cycle_id;
[JsonProperty("is_joined")] public bool is_joined;
[JsonProperty("leaderboard_opt_out")] public bool leaderboard_opt_out;
[JsonProperty("changed_today")] public bool changed_today;
[JsonProperty("can_change_today")] public bool can_change_today;
[JsonProperty("next_change_at")] public string next_change_at;
[JsonProperty("server_name")] public string server_name;
[JsonProperty("leaderboard_cycle_days")] public int leaderboard_cycle_days;
[JsonProperty("rank_version_id")] public int rank_version_id;
[JsonProperty("next_settle_at")] public string next_settle_at;
}
[Serializable]
public class LeaderboardEntry
{
@@ -118,6 +143,10 @@ namespace GameServer.Client
[JsonProperty("song_id")] public string song_id;
[JsonProperty("cycle_id")] public int cycle_id;
[JsonProperty("is_locked")] public bool is_locked;
[JsonProperty("server_name")] public string server_name;
[JsonProperty("leaderboard_cycle_days")] public int leaderboard_cycle_days;
[JsonProperty("rank_version_id")] public int rank_version_id;
[JsonProperty("next_settle_at")] public string next_settle_at;
[JsonProperty("rankings")] public List<LeaderboardEntry> rankings;
}
@@ -499,4 +528,124 @@ namespace GameServer.Client
// itemID 77001 = 角色 3020677003 = 歌曲177004 = 歌曲2(见 Resources/so/storeSO/
}
public static class LeaderboardConsentUtility
{
public const string RankingConsentPrefKey = "before_everything_agree_ranking";
private const string WarningPrefix = "下次排行榜更新时间:";
public static bool HasLeaderboardConsent()
{
return PlayerPrefs.GetInt(RankingConsentPrefKey, 0) == 1;
}
public static void SetLeaderboardConsent(bool accepted)
{
PlayerPrefs.SetInt(RankingConsentPrefKey, accepted ? 1 : 0);
PlayerPrefs.Save();
}
public static async Task<string> BuildNextSettleWarningTextAsync()
{
string nextSettleAt = await TryGetNextSettleAtAsync();
return string.IsNullOrWhiteSpace(nextSettleAt)
? WarningPrefix + "--"
: WarningPrefix + nextSettleAt;
}
public static async Task<string> TryGetNextSettleAtAsync()
{
LeaderboardData data = await TryGetLeaderboardMetaAsync();
return data != null && !string.IsNullOrWhiteSpace(data.next_settle_at)
? data.next_settle_at.Trim()
: string.Empty;
}
public static async Task<LeaderboardData> TryGetLeaderboardMetaAsync()
{
string songId = ResolveReferenceSongId();
if (string.IsNullOrWhiteSpace(songId))
{
return null;
}
LeaderboardCacheService cache = LeaderboardCacheService.Instance;
if (cache != null
&& cache.TryGetCached(songId, out LeaderboardData cached)
&& cached != null
&& !string.IsNullOrWhiteSpace(cached.next_settle_at))
{
return cached;
}
NetworkManager networkManager = NetworkManager.Instance;
if (networkManager == null)
{
return null;
}
try
{
return await networkManager.FetchLeaderboardFromServer(songId);
}
catch (Exception ex)
{
Debug.LogWarning($"[LeaderboardConsentUtility] Failed to fetch leaderboard meta: {ex.Message}");
return null;
}
}
private static string ResolveReferenceSongId()
{
SongData selectedSong = SongDataHolder.SelectedSongData;
if (selectedSong != null && selectedSong.songID > 0)
{
return selectedSong.songID.ToString();
}
if (BeatmapManager.pendingSongData != null && BeatmapManager.pendingSongData.songID > 0)
{
return BeatmapManager.pendingSongData.songID.ToString();
}
BeatmapManager beatmapManager = BeatmapManager.Instance;
if (beatmapManager != null
&& beatmapManager.assignedSongData != null
&& beatmapManager.assignedSongData.songID > 0)
{
return beatmapManager.assignedSongData.songID.ToString();
}
SongDataLibrary library = SongDataLibrary.Instance;
if (library != null && library.IsLoaded)
{
List<SongData> songs = library.GetAllSongs();
if (songs != null)
{
for (int i = 0; i < songs.Count; i++)
{
SongData song = songs[i];
if (song != null && song.songID > 0)
{
return song.songID.ToString();
}
}
}
}
SongData[] indexedSongs = RuntimeResourcesCache.LoadSongIndex();
if (indexedSongs != null)
{
for (int i = 0; i < indexedSongs.Length; i++)
{
SongData song = indexedSongs[i];
if (song != null && song.songID > 0)
{
return song.songID.ToString();
}
}
}
return string.Empty;
}
}
}
@@ -239,6 +239,57 @@ public class NetworkManager : MonoBehaviour
return data;
}
public async Task<LeaderboardMembershipResponse> GetLeaderboardMembership()
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
return new LeaderboardMembershipResponse
{
success = true,
steam_id = SteamId,
is_joined = LeaderboardConsentUtility.HasLeaderboardConsent(),
leaderboard_opt_out = !LeaderboardConsentUtility.HasLeaderboardConsent(),
can_change_today = true,
changed_today = false,
next_change_at = string.Empty,
};
}
string steamIdQuery = Uri.EscapeDataString(SteamId ?? string.Empty);
return await GetJson<LeaderboardMembershipResponse>(
BuildApiUrl($"/api/profile/leaderboard-membership?steam_id={steamIdQuery}"),
CancellationToken.None);
}
public async Task<LeaderboardMembershipResponse> UpdateLeaderboardMembership(bool isJoined)
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
LeaderboardConsentUtility.SetLeaderboardConsent(isJoined);
return new LeaderboardMembershipResponse
{
success = true,
steam_id = SteamId,
is_joined = isJoined,
leaderboard_opt_out = !isJoined,
can_change_today = true,
changed_today = false,
next_change_at = string.Empty,
};
}
var req = new
{
steam_id = SteamId,
is_joined = isJoined,
leaderboard_opt_out = !isJoined
};
return await PutJson<LeaderboardMembershipResponse>(
BuildApiUrl("/api/profile/leaderboard-membership"),
req,
CancellationToken.None);
}
public async Task<ProfileData> GetPlayerByUid(int uid)
{
if (uid <= 0)
@@ -525,6 +576,12 @@ public class NetworkManager : MonoBehaviour
return "LOCAL_ONLY";
}
if (!LeaderboardConsentUtility.HasLeaderboardConsent())
{
Debug.Log("[NetworkManager] Leaderboard submission skipped because the player has not consented.");
return "OPT_OUT";
}
try
{
await PerformHandshakeAsync(CancellationToken.None);
@@ -673,7 +730,7 @@ public class NetworkManager : MonoBehaviour
if (VerboseLogs) Debug.Log($"[NetworkManager] Ping: {resp.message}");
}
public async Task<LeaderboardData> GetLeaderboard(string songId)
public async Task<LeaderboardData> GetLeaderboard(string songId, bool forceRefresh = false)
{
if (string.IsNullOrWhiteSpace(songId))
{
@@ -692,9 +749,17 @@ public class NetworkManager : MonoBehaviour
{
SetState(ConnectionState.LoadingLeaderboard);
LeaderboardCacheService cache = LeaderboardCacheService.Instance;
LeaderboardData data = cache != null
? await cache.GetOrFetch(songId)
: await FetchLeaderboardFromServer(songId);
LeaderboardData data;
if (cache != null)
{
data = forceRefresh
? await cache.ForceRefresh(songId)
: await cache.GetOrFetch(songId);
}
else
{
data = await FetchLeaderboardFromServer(songId);
}
OnLeaderboardLoaded?.Invoke(data);
SetState(ConnectionState.Ready);
return data;
@@ -1503,6 +1568,42 @@ public class NetworkManager : MonoBehaviour
}
}
private async Task<T> PutJson<T>(string url, object payload, CancellationToken token)
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
throw CreateLocalOnlyException($"HTTP PUT is disabled in local-only mode. url={url}");
}
string json = JsonConvert.SerializeObject(payload);
using (UnityWebRequest request = new UnityWebRequest(url, UnityWebRequest.kHttpVerbPUT))
{
byte[] body = Encoding.UTF8.GetBytes(json);
request.uploadHandler = new UploadHandlerRaw(body);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
request.timeout = 15;
if (VerboseLogs) Debug.Log($"[NetworkManager] HTTP PUT {url}");
await SendRequestAsync(request.SendWebRequest(), token);
string responseText = request.downloadHandler != null ? request.downloadHandler.text : string.Empty;
if (request.result != UnityWebRequest.Result.Success && string.IsNullOrWhiteSpace(responseText))
{
throw new Exception($"{request.result}: {request.error}");
}
try
{
return JsonConvert.DeserializeObject<T>(responseText);
}
catch (Exception ex)
{
throw new Exception($"Failed to parse response: {ex.Message}. Body={responseText}");
}
}
}
private async Task<T> GetJson<T>(string url, CancellationToken token)
{
if (OnlineModeSettings.IsLocalOnlyMode)
@@ -1612,6 +1713,10 @@ public class NetworkManager : MonoBehaviour
song_id = songId,
cycle_id = 0,
is_locked = false,
server_name = "雅莉梦璃高新产业园",
leaderboard_cycle_days = 3,
rank_version_id = 20260101,
next_settle_at = "2026-01-01 00:00:00",
rankings = new List<LeaderboardEntry>()
};
}