ui基本完毕,修了一大把的bug
This commit is contained in:
@@ -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 = 角色 30206;77003 = 歌曲1;77004 = 歌曲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>()
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user