UI换了很多,spine主视觉停用

This commit is contained in:
2026-06-15 20:17:25 +08:00
parent 5eec879582
commit 216ab08e8d
3634 changed files with 814422 additions and 382766 deletions
@@ -176,7 +176,7 @@ public class AnimationController : MonoBehaviour
// Next try common names if tag not used
if (hit_particular_object == null)
{
var byName = GameObject.Find("HitParticleSource") ?? GameObject.Find("hit_particular_object") ?? GameObject.Find("Hit_Particle_Source");
var byName = SceneObjectLookupCache.Find("HitParticleSource") ?? SceneObjectLookupCache.Find("hit_particular_object") ?? SceneObjectLookupCache.Find("Hit_Particle_Source");
if (byName != null)
{
hit_particular_object = byName;
@@ -6,7 +6,7 @@ using TMPro;
public class BeatmapManager : MonoBehaviour
{
private const bool VerboseLogs = false;
private static readonly bool VerboseLogs = false;
public static BeatmapManager Instance { get; private set; }
private void Awake()
@@ -339,7 +339,7 @@ public class BeatmapManager : MonoBehaviour
if (VerboseLogs) Debug.Log("BeatmapManager.Start: chart parsed from SongData, attempting to assign audio and pause system");
// try to assign audio to GameManager.musicSource
var gm = FindAnyObjectByType<GameManager>();
var gm = SceneObjectLookupCache.FindAny<GameManager>();
if (gm != null && gm.musicSource != null)
{
if (assignedSongData != null && assignedSongData.audioFile != null)
@@ -406,18 +406,18 @@ public class BeatmapManager : MonoBehaviour
}
}
// Pause the system to show pause overlay (PauseManager will enable overlayRoot)
var pauseMgr = PauseManager.Instance ?? FindAnyObjectByType<PauseManager>();
if (pauseMgr != null)
{
pauseMgr.Pause(true);
// Pause the system to show pause overlay (PauseManager will enable overlayRoot)
var pauseMgr = PauseManager.Instance ?? SceneObjectLookupCache.FindAny<PauseManager>();
if (pauseMgr != null)
{
pauseMgr.Pause(true);
if (VerboseLogs) Debug.Log("System paused after loading chart and audio to allow player to start");
}
else
{
}
else
{
if (VerboseLogs) Debug.Log("PauseManager not found in scene; cannot pause system automatically");
}
}
}
}
// clear pending so it doesn't run again
pendingSongData = null;
@@ -895,7 +895,7 @@ public class BeatmapManager : MonoBehaviour
go = ui.GetAllyObjectBySlot(slotIndex);
if (go == null)
{
var byName = GameObject.Find($"ally_0{slotIndex + 1}");
var byName = SceneObjectLookupCache.Find($"ally_0{slotIndex + 1}");
if (byName != null) go = byName;
}
if (go == null) return null;
+12 -18
View File
@@ -117,7 +117,7 @@ public class GameManager : MonoBehaviour
{
if (pauseSubscribed) return;
// prefer the singleton, but try to find in scene if null
var pm = PauseManager.Instance ?? FindAnyObjectByType<PauseManager>();
var pm = PauseManager.Instance ?? SceneObjectLookupCache.FindAny<PauseManager>();
if (pm != null)
{
pm.OnPauseStateChanged += HandlePauseStateChanged;
@@ -128,7 +128,7 @@ public class GameManager : MonoBehaviour
private void UnsubscribeFromPauseManager()
{
if (!pauseSubscribed) return;
var pm = PauseManager.Instance ?? FindAnyObjectByType<PauseManager>();
var pm = PauseManager.Instance ?? SceneObjectLookupCache.FindAny<PauseManager>();
if (pm != null)
{
pm.OnPauseStateChanged -= HandlePauseStateChanged;
@@ -503,16 +503,16 @@ public class GameManager : MonoBehaviour
HoldNote.PrewarmRuntimeCaches();
var judgeFx = Animation_GenerateJudgementSituationPrefab.Instance
?? FindAnyObjectByType<Animation_GenerateJudgementSituationPrefab>();
?? SceneObjectLookupCache.FindAny<Animation_GenerateJudgementSituationPrefab>();
judgeFx?.PrewarmJudgePrefabs(1);
var fxEvent = effectEventController.Instance ?? FindAnyObjectByType<effectEventController>();
var fxEvent = effectEventController.Instance ?? SceneObjectLookupCache.FindAny<effectEventController>();
if (fxEvent != null && fxEvent.isActiveAndEnabled)
{
// Cache target and pre-create SmoothShake via OnEnable/Awake path already present.
}
var globalFx = globalNoteEffect.Instance ?? FindAnyObjectByType<globalNoteEffect>();
var globalFx = globalNoteEffect.Instance ?? SceneObjectLookupCache.FindAny<globalNoteEffect>();
globalFx?.PrewarmRuntime();
}
catch (System.Exception ex)
@@ -1138,7 +1138,7 @@ public class GameManager : MonoBehaviour
StartCoroutine(StopWarmupAfterFrame());
// After loading audio, enable overlay and pause via PauseManager
var pm = PauseManager.Instance ?? FindAnyObjectByType<PauseManager>();
var pm = PauseManager.Instance ?? SceneObjectLookupCache.FindAny<PauseManager>();
if (pm != null)
{
pm.Pause(true);
@@ -1279,12 +1279,10 @@ public class GameManager : MonoBehaviour
yield return null;
}
}
gNotice.recommendation.display("游戏开始!");
gNotice.recommendation.display(LocalizationService.Get("gameplay.notice.game_start", "游戏开始!"));
// Resume using PauseManager
PauseManager.Instance?.Pause(false);
// Use scaled wait so pause (Escape) will pause this delay
// yield return new WaitForSeconds(Mathf.Max(0f, playbackStartDelay));
// Start spawning using the parsed beatmap
if (beatmapManager.beatmap != null)
@@ -1340,12 +1338,10 @@ public class GameManager : MonoBehaviour
yield return null;
}
}
gNotice.recommendation.display("游戏开始!");
gNotice.recommendation.display(LocalizationService.Get("gameplay.notice.game_start", "游戏开始!"));
// Resume
PauseManager.Instance?.Pause(false);
// Use scaled wait so pause (Escape) will pause this delay
// yield return new WaitForSeconds(Mathf.Max(0f, playbackStartDelay));
// Start spawning using the existing parsed beatmap
if (beatmapManager.beatmap != null)
@@ -1381,7 +1377,7 @@ public class GameManager : MonoBehaviour
PlayMusicWithDelay(delay);
// ensure overlay shows and pause logic runs after loading the clip
var pm2 = PauseManager.Instance ?? FindAnyObjectByType<PauseManager>();
var pm2 = PauseManager.Instance ?? SceneObjectLookupCache.FindAny<PauseManager>();
if (pm2 != null)
{
pm2.Pause(true);
@@ -1440,12 +1436,10 @@ public class GameManager : MonoBehaviour
yield return null;
}
}
gNotice.recommendation.display("游戏开始!");
gNotice.recommendation.display(LocalizationService.Get("gameplay.notice.game_start", "游戏开始!"));
// Resume
PauseManager.Instance?.Pause(false);
// Use scaled wait so pause (Escape) will pause this delay
// yield return new WaitForSeconds(Mathf.Max(0f, playbackStartDelay));
// Start spawning
if (beatmapManager.beatmap != null)
@@ -1475,7 +1469,7 @@ public class GameManager : MonoBehaviour
float delay = beatmapManager.globalDelaySeconds;
PlayMusicWithDelay(delay);
var pm3 = PauseManager.Instance ?? FindAnyObjectByType<PauseManager>();
var pm3 = PauseManager.Instance ?? SceneObjectLookupCache.FindAny<PauseManager>();
if (pm3 != null)
{
pm3.Pause(true);
@@ -1524,7 +1518,7 @@ public class GameManager : MonoBehaviour
private IEnumerator TriggerOnGameStartNextFrame()
{
yield return null;
var builder = FindAnyObjectByType<SkillBuilder>();
var builder = SceneObjectLookupCache.FindAny<SkillBuilder>();
if (builder == null)
{
Debug.LogWarning("[GameManager] TriggerOnGameStartSkills: SkillBuilder not found.");
+3 -3
View File
@@ -84,7 +84,7 @@ public class HoldNote : BaseNote
if (allyCache == null || allyCache.Length < 10) allyCache = new AllyCombatant[10];
if (allyCache[trackIndex] != null) return allyCache[trackIndex];
var allyGo = GameObject.Find($"ally_0{trackIndex + 1}");
var allyGo = SceneObjectLookupCache.Find($"ally_0{trackIndex + 1}");
if (allyGo != null)
{
allyCache[trackIndex] = allyGo.GetComponent<AllyCombatant>();
@@ -95,7 +95,7 @@ public class HoldNote : BaseNote
private static BeatmapManager GetBeatmapManagerCached()
{
if (beatmapManagerCache != null) return beatmapManagerCache;
beatmapManagerCache = Object.FindAnyObjectByType<BeatmapManager>();
beatmapManagerCache = SceneObjectLookupCache.FindAny<BeatmapManager>();
return beatmapManagerCache;
}
@@ -190,7 +190,7 @@ public class HoldNote : BaseNote
anim = GetComponent<AnimationController>();
if (anim == null)
{
anim = FindAnyObjectByType<AnimationController>();
anim = SceneObjectLookupCache.FindAny<AnimationController>();
}
}
+2 -2
View File
@@ -28,7 +28,7 @@ public class Note : BaseNote
if (allyCache == null || allyCache.Length < 10) allyCache = new AllyCombatant[10];
if (allyCache[trackIndex] != null) return allyCache[trackIndex];
var allyGo = GameObject.Find($"ally_0{trackIndex + 1}");
var allyGo = SceneObjectLookupCache.Find($"ally_0{trackIndex + 1}");
if (allyGo != null)
{
allyCache[trackIndex] = allyGo.GetComponent<AllyCombatant>();
@@ -39,7 +39,7 @@ public class Note : BaseNote
private static BeatmapManager GetBeatmapManagerCached()
{
if (beatmapManagerCache != null) return beatmapManagerCache;
beatmapManagerCache = Object.FindAnyObjectByType<BeatmapManager>();
beatmapManagerCache = SceneObjectLookupCache.FindAny<BeatmapManager>();
return beatmapManagerCache;
}
+2 -2
View File
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using UnityEngine;
using GamePlay; // for PoolItem
using System.Collections;
@@ -112,7 +112,7 @@ public class NotePool : MonoBehaviour
}
// Also ensure particle and judge prefab warm-up if AnimationController exists in scene.
var anim = AnimationController.Global ?? FindAnyObjectByType<AnimationController>();
var anim = AnimationController.Global ?? SceneObjectLookupCache.FindAny<AnimationController>();
if (anim != null)
{
anim.PrewarmParticles(2);
@@ -160,10 +160,10 @@ public static class SkillTriggerFeedUI
{
if (HasBoundText()) return;
GameObject skillRoot = GameObject.Find("score/Skill");
GameObject skillRoot = SceneObjectLookupCache.Find("score/Skill");
if (skillRoot == null)
{
var score = GameObject.Find("score");
var score = SceneObjectLookupCache.Find("score");
if (score == null)
{
if (!s_warnedMissingScore)
@@ -271,7 +271,7 @@ public static class SkillTriggerFeedUI
private static void TryHideLegacyFeedRoot()
{
GameObject feed = GameObject.Find("score/Skill");
GameObject feed = SceneObjectLookupCache.Find("score/Skill");
if (feed != null && feed.activeSelf)
feed.SetActive(false);
}
@@ -248,7 +248,7 @@ public class effectEventController : MonoBehaviour
public static void RefreshPlayerSkillCameraOffset()
{
effectEventController controller = Instance != null ? Instance : FindAnyObjectByType<effectEventController>();
effectEventController controller = Instance != null ? Instance : SceneObjectLookupCache.FindAny<effectEventController>();
if (controller == null)
return;
@@ -258,7 +258,7 @@ public class effectEventController : MonoBehaviour
public static void TryTriggerMultiNoteShake()
{
effectEventController controller = Instance != null ? Instance : FindAnyObjectByType<effectEventController>();
effectEventController controller = Instance != null ? Instance : SceneObjectLookupCache.FindAny<effectEventController>();
if (controller == null)
return;
@@ -137,7 +137,7 @@ public class globalNoteEffect : MonoBehaviour
if (Instance != null)
return Instance;
globalNoteEffect found = FindAnyObjectByType<globalNoteEffect>();
globalNoteEffect found = SceneObjectLookupCache.FindAny<globalNoteEffect>();
if (found != null)
{
Instance = found;
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -135,7 +135,7 @@ namespace GameServer.Client
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
private static void TriggerStartupLeaveOnce()
{
if (Instance == null || _startupLeaveScheduled || _startupLeaveCompleted)
if (Instance == null || _startupLeaveScheduled || _startupLeaveCompleted || OnlineModeSettings.IsLocalOnlyMode)
{
return;
}
@@ -155,10 +155,14 @@ namespace GameServer.Client
Instance = this;
DontDestroyOnLoad(gameObject);
SceneManager.sceneLoaded += HandleSceneLoaded;
OnlineModeSettings.ModeChanged += HandleOnlineModeChanged;
if (!_startupSocialConnectStarted)
{
_startupSocialConnectStarted = true;
StartCoroutine(StartupSocialConnectCoroutine());
if (!OnlineModeSettings.IsLocalOnlyMode)
{
StartCoroutine(StartupSocialConnectCoroutine());
}
}
}
@@ -187,6 +191,7 @@ namespace GameServer.Client
_ = _client?.Close();
_ = _socialClient?.Close();
SceneManager.sceneLoaded -= HandleSceneLoaded;
OnlineModeSettings.ModeChanged -= HandleOnlineModeChanged;
if (Instance == this)
{
Instance = null;
@@ -275,6 +280,11 @@ namespace GameServer.Client
private IEnumerator StartupLeaveRoomCoroutine()
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
yield break;
}
float timeout = Time.realtimeSinceStartup + 8f;
while ((NetworkManager.Instance == null || string.IsNullOrWhiteSpace(NetworkManager.Instance.SteamId))
&& Time.realtimeSinceStartup < timeout)
@@ -297,6 +307,11 @@ namespace GameServer.Client
private async Task LeaveAnyRoomAtStartupAsync()
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
return;
}
if (_startupLeaveCompleted)
{
return;
@@ -750,6 +765,11 @@ namespace GameServer.Client
private async Task EnsureConnected()
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
throw new InvalidOperationException("Arena online features are disabled in local-only mode.");
}
if (_client != null && _client.IsConnected)
{
return;
@@ -805,6 +825,11 @@ namespace GameServer.Client
private async Task EnsureSocialConnected()
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
throw new InvalidOperationException("Social online features are disabled in local-only mode.");
}
if (_socialClient != null && _socialClient.IsConnected)
{
return;
@@ -3413,6 +3438,11 @@ namespace GameServer.Client
private IEnumerator StartupSocialConnectCoroutine()
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
yield break;
}
float timeout = Time.realtimeSinceStartup + 12f;
while ((NetworkManager.Instance == null || string.IsNullOrWhiteSpace(NetworkManager.Instance.SteamId))
&& Time.realtimeSinceStartup < timeout)
@@ -3437,6 +3467,32 @@ namespace GameServer.Client
}
}
private void HandleOnlineModeChanged(bool enabled)
{
if (!enabled)
{
_manualClose = true;
_manualSocialClose = true;
_heartbeatCts?.Cancel();
_socialHeartbeatCts?.Cancel();
StopArenaReconnectLoop();
StopSocialReconnectLoop();
_ = _client?.Close();
_ = _socialClient?.Close();
_client = null;
_socialClient = null;
_isConnecting = false;
_isSocialConnecting = false;
_joinedSocialRoomCode = null;
return;
}
if (_startupSocialConnectStarted && _socialReconnectCoroutine == null && _socialClient == null && Application.isPlaying)
{
StartCoroutine(StartupSocialConnectCoroutine());
}
}
private void RefreshLocalPresenceSilently()
{
_ = RefreshLocalPresenceSilentlyAsync();
@@ -3528,7 +3584,7 @@ namespace GameServer.Client
yield break;
}
SongDetailsUI ui = FindAnyObjectByType<SongDetailsUI>();
SongDetailsUI ui = SceneObjectLookupCache.FindAny<SongDetailsUI>();
if (ui != null)
{
ui.OpenRoomDetailsForCurrentRoom();
@@ -3,6 +3,7 @@ namespace GameServer.Client
public enum ConnectionState
{
Disconnected,
LocalOnly,
Connecting,
Handshaking,
LoadingProfile,
@@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Collections;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Networking;
@@ -22,7 +21,7 @@ namespace GameServer.Client
{
public class NetworkManager : MonoBehaviour
{
private const bool VerboseLogs = false;
private static readonly bool VerboseLogs = false;
public static NetworkManager Instance { get; private set; }
private static string _startupSteamId = string.Empty;
private static string _startupSteamDisplayName = string.Empty;
@@ -57,6 +56,7 @@ public class NetworkManager : MonoBehaviour
private bool _startupHandshakeStarted;
private bool _startupHandshakeCompleted;
private Task<string> _avatarUploadTask;
private bool _isRefreshingSteamIdentity;
private struct CachedRemoteIdentity
{
@@ -69,6 +69,8 @@ public class NetworkManager : MonoBehaviour
public bool IsReady => _state == ConnectionState.Ready;
public bool IsConnectedAndHandshaked => _state == ConnectionState.Ready;
public string ServerUrl => serverUrl;
public bool OnlineFeaturesEnabled => OnlineModeSettings.IsOnlineEnabled;
public bool IsLocalOnlyMode => OnlineModeSettings.IsLocalOnlyMode;
public string SteamId
{
get
@@ -134,12 +136,20 @@ public class NetworkManager : MonoBehaviour
}
RefreshSteamIdentity(false);
CacheIdentity(steamId, steamDisplayName, steamAvatarUrl);
OnlineModeSettings.ModeChanged += HandleOnlineModeChanged;
ApplyRuntimeModeState();
if (VerboseLogs) Debug.Log("[NetworkManager] HTTP lazy mode singleton initialized");
}
private void Start()
{
RefreshSteamIdentity(false);
if (OnlineModeSettings.IsLocalOnlyMode)
{
SetState(ConnectionState.LocalOnly);
return;
}
StartCoroutine(StartupHandshakeRoutine());
if (VerboseLogs) Debug.Log($"[NetworkManager] Startup handshake enabled. Settlement submit will use {BuildApiUrl("/api/submit")}");
}
@@ -152,6 +162,7 @@ public class NetworkManager : MonoBehaviour
private void OnDestroy()
{
_requestCts?.Cancel();
OnlineModeSettings.ModeChanged -= HandleOnlineModeChanged;
if (!_isApplicationQuitting && Instance == this)
{
Instance = null;
@@ -160,6 +171,12 @@ public class NetworkManager : MonoBehaviour
public void Connect()
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
SetState(ConnectionState.LocalOnly);
return;
}
_requestCts?.Cancel();
_requestCts = new CancellationTokenSource();
ConnectWithErrorHandling(_requestCts.Token);
@@ -178,6 +195,12 @@ public class NetworkManager : MonoBehaviour
public async Task SendHandshake()
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
SetState(ConnectionState.LocalOnly);
return;
}
await PerformHandshakeAsync(CancellationToken.None);
}
@@ -206,6 +229,11 @@ public class NetworkManager : MonoBehaviour
throw new ArgumentException("targetSteamId is empty", nameof(targetSteamId));
}
if (OnlineModeSettings.IsLocalOnlyMode)
{
return BuildLocalOnlyProfile(targetSteamId);
}
ProfileData data = await GetJson<ProfileData>(BuildApiUrl($"/api/profile/{targetSteamId}"), CancellationToken.None);
CacheIdentity(targetSteamId, data != null ? data.display_name : null, data != null ? data.avatar_url : null);
return data;
@@ -218,6 +246,16 @@ public class NetworkManager : MonoBehaviour
throw new ArgumentOutOfRangeException(nameof(uid), "uid must be positive");
}
if (OnlineModeSettings.IsLocalOnlyMode)
{
return new ProfileData
{
success = false,
error_code = "LOCAL_ONLY_MODE",
message = "Online profile lookup is unavailable in local-only mode."
};
}
ProfileData data = await GetJson<ProfileData>(BuildApiUrl($"/api/players/uid/{uid}"), CancellationToken.None);
string identitySteamId = data != null ? data.steam_id : null;
CacheIdentity(identitySteamId, data != null ? data.display_name : null, data != null ? data.avatar_url : null);
@@ -226,6 +264,18 @@ public class NetworkManager : MonoBehaviour
public async Task<SteamFriendsImportResponse> ImportSteamFriends(IEnumerable<string> steamFriends)
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
return new SteamFriendsImportResponse
{
success = false,
added = 0,
skipped = 0,
error_code = "LOCAL_ONLY_MODE",
message = "Steam friend import is unavailable in local-only mode."
};
}
var unique = new HashSet<string>(StringComparer.Ordinal);
var normalized = new List<string>();
if (steamFriends != null)
@@ -253,6 +303,19 @@ public class NetworkManager : MonoBehaviour
public async Task<SocialFriendsApiResponse> GetFriendsList(IEnumerable<string> steamFriends)
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
return new SocialFriendsApiResponse
{
success = true,
friends = new List<SocialFriendEntry>(),
added = 0,
skipped = 0,
error_code = "LOCAL_ONLY_MODE",
message = "Online friends are disabled in local-only mode."
};
}
var unique = new HashSet<string>(StringComparer.Ordinal);
var normalized = new List<string>();
if (steamFriends != null)
@@ -294,12 +357,22 @@ public class NetworkManager : MonoBehaviour
public async Task<MailApiVersionResponse> GetMailVersion()
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
return BuildLocalOnlyMailVersionResponse();
}
string steamIdQuery = Uri.EscapeDataString(SteamId ?? string.Empty);
return await GetJson<MailApiVersionResponse>(BuildApiUrl($"/api/mail/version?steam_id={steamIdQuery}"), CancellationToken.None);
}
public async Task<MailApiListResponse> GetMailList()
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
return BuildLocalOnlyMailListResponse();
}
string steamIdQuery = Uri.EscapeDataString(SteamId ?? string.Empty);
return await GetJson<MailApiListResponse>(BuildApiUrl($"/api/mail/list?steam_id={steamIdQuery}"), CancellationToken.None);
}
@@ -311,6 +384,18 @@ public class NetworkManager : MonoBehaviour
throw new ArgumentOutOfRangeException(nameof(mailId), "mailId must be positive");
}
if (OnlineModeSettings.IsLocalOnlyMode)
{
return new MailClaimResponse
{
success = false,
mail_id = mailId,
steam_id = SteamId,
error_code = "LOCAL_ONLY_MODE",
message = "Online mail sync is unavailable in local-only mode."
};
}
var req = new MailClaimRequest
{
steam_id = SteamId,
@@ -322,12 +407,24 @@ public class NetworkManager : MonoBehaviour
public IEnumerator GetMailVersionRoutine(Action<MailApiVersionResponse, Exception> onCompleted)
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
onCompleted?.Invoke(BuildLocalOnlyMailVersionResponse(), null);
yield break;
}
string steamIdQuery = Uri.EscapeDataString(SteamId ?? string.Empty);
yield return GetJsonRoutine(BuildApiUrl($"/api/mail/version?steam_id={steamIdQuery}"), onCompleted);
}
public IEnumerator GetMailListRoutine(Action<MailApiListResponse, Exception> onCompleted)
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
onCompleted?.Invoke(BuildLocalOnlyMailListResponse(), null);
yield break;
}
string steamIdQuery = Uri.EscapeDataString(SteamId ?? string.Empty);
yield return GetJsonRoutine(BuildApiUrl($"/api/mail/list?steam_id={steamIdQuery}"), onCompleted);
}
@@ -422,6 +519,12 @@ public class NetworkManager : MonoBehaviour
int chartScore, int idolScore, long totalScore, string grade,
double runtimeSeconds, string playedAt, string hmac = "")
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
SetState(ConnectionState.LocalOnly);
return "LOCAL_ONLY";
}
try
{
await PerformHandshakeAsync(CancellationToken.None);
@@ -469,6 +572,16 @@ public class NetworkManager : MonoBehaviour
public async Task<ArenaCreateResponse> CreateArenaRoom(string songId, string difficulty, string password = null)
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
return new ArenaCreateResponse
{
success = false,
error_code = "LOCAL_ONLY_MODE",
message = "Arena rooms are unavailable in local-only mode."
};
}
var req = new ArenaCreateRequest
{
steam_id = SteamId,
@@ -483,6 +596,17 @@ public class NetworkManager : MonoBehaviour
public async Task<ArenaJoinResponse> JoinArenaRoom(string roomCode, string password = null)
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
return new ArenaJoinResponse
{
success = false,
room_code = roomCode,
error_code = "LOCAL_ONLY_MODE",
message = "Arena rooms are unavailable in local-only mode."
};
}
var req = new ArenaJoinRequest
{
steam_id = SteamId,
@@ -496,6 +620,11 @@ public class NetworkManager : MonoBehaviour
public async Task StartArenaGame()
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
throw CreateLocalOnlyException("Arena gameplay start is unavailable in local-only mode.");
}
var req = new ArenaStartRequest
{
steam_id = SteamId
@@ -509,6 +638,11 @@ public class NetworkManager : MonoBehaviour
public async Task SubmitArenaScore(string roomCode, long totalScore, string grade)
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
throw CreateLocalOnlyException("Arena score submit is unavailable in local-only mode.");
}
var req = new ArenaSubmitRequest
{
steam_id = SteamId,
@@ -529,6 +663,12 @@ public class NetworkManager : MonoBehaviour
public async Task SendHeartbeat()
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
SetState(ConnectionState.LocalOnly);
return;
}
HeartbeatResponse resp = await GetJson<HeartbeatResponse>(BuildApiUrl("/api/ping"), CancellationToken.None);
if (VerboseLogs) Debug.Log($"[NetworkManager] Ping: {resp.message}");
}
@@ -540,6 +680,14 @@ public class NetworkManager : MonoBehaviour
throw new ArgumentException("songId is required", nameof(songId));
}
if (OnlineModeSettings.IsLocalOnlyMode)
{
SetState(ConnectionState.LocalOnly);
LeaderboardData localData = BuildLocalOnlyLeaderboard(songId);
OnLeaderboardLoaded?.Invoke(localData);
return localData;
}
try
{
SetState(ConnectionState.LoadingLeaderboard);
@@ -562,6 +710,11 @@ public class NetworkManager : MonoBehaviour
internal Task<LeaderboardData> FetchLeaderboardFromServer(string songId)
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
return Task.FromResult(BuildLocalOnlyLeaderboard(songId));
}
return GetJson<LeaderboardData>(BuildApiUrl($"/api/leaderboard/{songId}"), CancellationToken.None);
}
@@ -585,6 +738,19 @@ public class NetworkManager : MonoBehaviour
private async Task PerformHandshakeAsync(CancellationToken token)
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
SetState(ConnectionState.LocalOnly);
OnHandshakeResult?.Invoke(new HandshakeResponse
{
success = false,
steam_id = SteamId,
error_code = "LOCAL_ONLY_MODE",
message = "Online handshake is disabled in local-only mode."
});
return;
}
HandshakeRequest req = await PrepareHandshakeRequestAsync(token, true);
SetState(ConnectionState.Handshaking);
@@ -626,6 +792,13 @@ public class NetworkManager : MonoBehaviour
private IEnumerator StartupHandshakeRoutine()
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
_startupHandshakeStarted = true;
SetState(ConnectionState.LocalOnly);
yield break;
}
if (_startupHandshakeStarted || _startupHandshakeCompleted)
{
yield break;
@@ -684,36 +857,49 @@ public class NetworkManager : MonoBehaviour
public bool RefreshSteamIdentity(bool logWarnings)
{
bool refreshed = TryReadSteamIdentity(out string currentSteamId, out string currentDisplayName, logWarnings);
if (!string.IsNullOrWhiteSpace(currentSteamId))
if (_isRefreshingSteamIdentity)
{
steamId = currentSteamId;
_startupSteamId = currentSteamId;
}
else if (string.IsNullOrWhiteSpace(steamId))
{
steamId = _startupSteamId;
return false;
}
if (IsUsableDisplayName(currentDisplayName, steamId))
_isRefreshingSteamIdentity = true;
try
{
steamDisplayName = currentDisplayName;
_startupSteamDisplayName = currentDisplayName;
bool refreshed = TryReadSteamIdentity(out string currentSteamId, out string currentDisplayName, logWarnings);
if (!string.IsNullOrWhiteSpace(currentSteamId))
{
steamId = currentSteamId;
_startupSteamId = currentSteamId;
}
else if (string.IsNullOrWhiteSpace(steamId))
{
steamId = _startupSteamId;
}
if (IsUsableDisplayName(currentDisplayName, steamId))
{
steamDisplayName = currentDisplayName;
_startupSteamDisplayName = currentDisplayName;
}
else if (!IsUsableDisplayName(steamDisplayName, steamId))
{
steamDisplayName = ResolveDisplayName(_startupSteamDisplayName, steamId, false);
}
if (!IsUsableAvatarUrl(steamAvatarUrl))
{
steamAvatarUrl = BuildSteamAvatarUrl(steamId);
}
CacheIdentity(steamId, steamDisplayName, steamAvatarUrl);
return refreshed;
}
else if (!IsUsableDisplayName(steamDisplayName, steamId))
finally
{
steamDisplayName = ResolveDisplayName(_startupSteamDisplayName, steamId, false);
_isRefreshingSteamIdentity = false;
}
if (!IsUsableAvatarUrl(steamAvatarUrl))
{
steamAvatarUrl = BuildSteamAvatarUrl(steamId);
}
CacheIdentity(steamId, steamDisplayName, steamAvatarUrl);
return refreshed;
}
private async Task<string> EnsureAvatarUploadedAsync(CancellationToken token)
@@ -780,11 +966,12 @@ public class NetworkManager : MonoBehaviour
NetworkManager instance = Instance;
if (instance != null)
{
instance.RefreshSteamIdentity(false);
if (string.Equals(instance.steamId, normalizedSteamId, StringComparison.Ordinal)
&& IsUsableDisplayName(instance.steamDisplayName, normalizedSteamId))
string instanceSteamId = instance.steamId;
string instanceDisplayName = instance.steamDisplayName;
if (string.Equals(instanceSteamId, normalizedSteamId, StringComparison.Ordinal)
&& IsUsableDisplayName(instanceDisplayName, normalizedSteamId))
{
return instance.steamDisplayName.Trim();
return instanceDisplayName.Trim();
}
}
@@ -1238,6 +1425,11 @@ public class NetworkManager : MonoBehaviour
private async Task<AvatarUploadResponse> UploadAvatarAsync(string targetSteamId, byte[] avatarBytes, CancellationToken token)
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
throw CreateLocalOnlyException("Avatar upload is disabled in local-only mode.");
}
if (string.IsNullOrWhiteSpace(targetSteamId))
{
throw new ArgumentException("targetSteamId is empty", nameof(targetSteamId));
@@ -1277,6 +1469,11 @@ public class NetworkManager : MonoBehaviour
private async Task<T> PostJson<T>(string url, object payload, CancellationToken token)
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
throw CreateLocalOnlyException($"HTTP POST is disabled in local-only mode. url={url}");
}
string json = JsonConvert.SerializeObject(payload);
using (UnityWebRequest request = new UnityWebRequest(url, UnityWebRequest.kHttpVerbPOST))
{
@@ -1308,6 +1505,11 @@ public class NetworkManager : MonoBehaviour
private async Task<T> GetJson<T>(string url, CancellationToken token)
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
throw CreateLocalOnlyException($"HTTP GET is disabled in local-only mode. url={url}");
}
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
request.timeout = 15;
@@ -1331,6 +1533,89 @@ public class NetworkManager : MonoBehaviour
}
}
private void HandleOnlineModeChanged(bool enabled)
{
if (!enabled)
{
_requestCts?.Cancel();
SetState(ConnectionState.LocalOnly);
return;
}
if (_state == ConnectionState.LocalOnly)
{
SetState(ConnectionState.Disconnected);
}
}
private void ApplyRuntimeModeState()
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
SetState(ConnectionState.LocalOnly);
}
}
private static Exception CreateLocalOnlyException(string message)
{
return new InvalidOperationException(message);
}
private ProfileData BuildLocalOnlyProfile(string targetSteamId)
{
string normalizedSteamId = string.IsNullOrWhiteSpace(targetSteamId) ? string.Empty : targetSteamId.Trim();
bool isSelf = string.Equals(normalizedSteamId, SteamId, StringComparison.Ordinal);
string displayName = ResolveDisplayName(string.Empty, normalizedSteamId, false);
string avatarUrl = ResolveAvatarUrl(null, normalizedSteamId);
return new ProfileData
{
success = isSelf,
error_code = isSelf ? null : "LOCAL_ONLY_MODE",
message = isSelf ? "Loaded local profile in local-only mode." : "Remote profiles are unavailable in local-only mode.",
steam_id = normalizedSteamId,
display_name = displayName,
avatar_url = avatarUrl
};
}
private static MailApiVersionResponse BuildLocalOnlyMailVersionResponse()
{
return new MailApiVersionResponse
{
success = true,
revision = "local-only",
count = 0,
error_code = "LOCAL_ONLY_MODE",
message = "Server mail is disabled in local-only mode."
};
}
private static MailApiListResponse BuildLocalOnlyMailListResponse()
{
return new MailApiListResponse
{
success = true,
revision = "local-only",
count = 0,
mails = new List<MailApiEntry>(),
deletions = new List<MailApiDeletionEntry>(),
error_code = "LOCAL_ONLY_MODE",
message = "Server mail is disabled in local-only mode."
};
}
private static LeaderboardData BuildLocalOnlyLeaderboard(string songId)
{
return new LeaderboardData
{
song_id = songId,
cycle_id = 0,
is_locked = false,
rankings = new List<LeaderboardEntry>()
};
}
private IEnumerator GetJsonRoutine<T>(string url, Action<T, Exception> onCompleted)
{
using (UnityWebRequest request = UnityWebRequest.Get(url))
@@ -0,0 +1,60 @@
using System;
using UnityEngine;
namespace GameServer.Client
{
public static class OnlineModeSettings
{
public const string PlayerPrefsKey = "bansonic_online_enabled";
private const int DefaultOnlineEnabled = 1;
private static bool _initialized;
public static event Action<bool> ModeChanged;
public static bool IsOnlineEnabled
{
get
{
EnsureInitialized();
return PlayerPrefs.GetInt(PlayerPrefsKey, DefaultOnlineEnabled) != 0;
}
set => SetOnlineEnabled(value);
}
public static bool IsLocalOnlyMode => !IsOnlineEnabled;
public static void SetOnlineEnabled(bool enabled)
{
EnsureInitialized();
bool previous = IsOnlineEnabled;
int persistedValue = enabled ? 1 : 0;
if (PlayerPrefs.GetInt(PlayerPrefsKey, DefaultOnlineEnabled) == persistedValue)
{
return;
}
PlayerPrefs.SetInt(PlayerPrefsKey, persistedValue);
PlayerPrefs.Save();
if (previous != enabled)
{
ModeChanged?.Invoke(enabled);
}
}
private static void EnsureInitialized()
{
if (_initialized)
{
return;
}
_initialized = true;
if (!PlayerPrefs.HasKey(PlayerPrefsKey))
{
PlayerPrefs.SetInt(PlayerPrefsKey, DefaultOnlineEnabled);
PlayerPrefs.Save();
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a38c03061babdb24b9208ec991bde237
@@ -42,7 +42,7 @@ namespace GameServer.Client
_cts = new CancellationTokenSource();
// 跳过系统代理
_ws.Options.Proxy = GlobalProxySelection.GetEmptyWebProxy();
_ws.Options.Proxy = null;
_ws.Options.KeepAliveInterval = TimeSpan.FromSeconds(20);
try
@@ -205,4 +205,4 @@ namespace GameServer.Client
_ws?.Dispose();
}
}
}
}
@@ -970,7 +970,7 @@ public class iBudeffPrefabController : MonoBehaviour
private static GameObject FindEnemyPutInScene()
{
var direct = GameObject.Find("budeffGrid");
var direct = SceneObjectLookupCache.Find("budeffGrid");
if (direct != null) return direct;
try
{
@@ -1065,7 +1065,7 @@ public class iBudeffPrefabController : MonoBehaviour
private void TryAutoBindPuts()
{
var ui = teamUIController.Instance != null ? teamUIController.Instance : FindAnyObjectByType<teamUIController>();
var ui = teamUIController.Instance != null ? teamUIController.Instance : SceneObjectLookupCache.FindAny<teamUIController>();
if (ui == null) return;
if (ally01_put == null) ally01_put = FindPutUnder(ui.objectFather_ally01);
@@ -65,14 +65,14 @@ public class iNumberPrefabController : MonoBehaviour
{
if (ally_number_prefab == null)
{
var foundAlly = GameObject.Find("player_instant_prefab_ally");
if (foundAlly == null) foundAlly = GameObject.Find("player_instant_prefab");
var foundAlly = SceneObjectLookupCache.Find("player_instant_prefab_ally");
if (foundAlly == null) foundAlly = SceneObjectLookupCache.Find("player_instant_prefab");
if (foundAlly != null && foundAlly.GetComponent<playerInstantNumbersPrefab>() != null) ally_number_prefab = foundAlly;
}
if (enemy_number_prefab == null)
{
var foundEnemy = GameObject.Find("player_instant_prefab_enemy");
var foundEnemy = SceneObjectLookupCache.Find("player_instant_prefab_enemy");
if (foundEnemy != null && foundEnemy.GetComponent<playerInstantNumbersPrefab>() != null) enemy_number_prefab = foundEnemy;
}
}
@@ -7,7 +7,7 @@ using UnityEngine.UI;
public class rankingListPrefab : MonoBehaviour
{
private const string RoomRankingServerText = "根据房间成绩动态排名,所在服务端:雅莉梦璃高新产业园";
private static string RoomRankingServerText => LocalizationService.Get("ranking.room_server", "根据房间成绩动态排名,所在服务端:雅莉梦璃高新产业园");
[Header("shutbutton")]
public Button shutbutton;
@@ -94,7 +94,7 @@ public class rankingListPrefab : MonoBehaviour
if (roomRankingData == null || roomRankingData.rankings == null || roomRankingData.rankings.Count == 0)
{
SetEmptyState(true, "房间排行榜暂无数据");
SetEmptyState(true, LocalizationService.Get("ranking.room_no_data", "房间排行榜暂无数据"));
return;
}
@@ -112,7 +112,7 @@ public class rankingListPrefab : MonoBehaviour
NetworkManager nm = NetworkManager.Instance;
if (nm == null)
{
SetEmptyState(true, "排行榜服务不可用");
SetEmptyState(true, LocalizationService.Get("ranking.service_unavailable", "排行榜服务不可用"));
return;
}
@@ -124,13 +124,13 @@ public class rankingListPrefab : MonoBehaviour
}
else
{
SetEmptyState(false, "正在拉取排行榜...");
SetEmptyState(false, LocalizationService.Get("ranking.loading", "正在拉取排行榜..."));
data = await nm.GetLeaderboard(currentSongId);
}
if (data == null || data.rankings == null || data.rankings.Count == 0)
{
SetEmptyState(true, "排行榜暂无数据");
SetEmptyState(true, LocalizationService.Get("ranking.no_data", "排行榜暂无数据"));
return;
}
@@ -139,7 +139,7 @@ public class rankingListPrefab : MonoBehaviour
catch (Exception ex)
{
Debug.LogWarning($"[rankingListPrefab] Failed to load leaderboard: {ex.Message}");
SetEmptyState(true, "拉取排行榜失败");
SetEmptyState(true, LocalizationService.Get("ranking.load_failed", "拉取排行榜失败"));
}
finally
{
@@ -239,7 +239,9 @@ public class rankingListPrefab : MonoBehaviour
{
if (rankingListType != null)
{
rankingListType.text = isRoomRanking ? "房间排行榜" : "世界排行榜";
rankingListType.text = isRoomRanking
? LocalizationService.Get("ranking.room_title", "房间排行榜")
: LocalizationService.Get("ranking.world_title", "世界排行榜");
}
if (versionandServer != null)
@@ -260,7 +262,7 @@ public class rankingListPrefab : MonoBehaviour
if (data == null || data.rankings == null || data.rankings.Count == 0 || string.IsNullOrWhiteSpace(roomRankingLocalSteamId))
{
yourPositionText.text = "您的得分为--,目前排名第<color=green>--</color>位";
yourPositionText.text = LocalizationService.Get("ranking.your_position_empty", "您的得分为--,目前排名第<color=green>--</color>位");
return;
}
@@ -277,12 +279,11 @@ public class rankingListPrefab : MonoBehaviour
if (localEntry == null)
{
yourPositionText.text = "您的得分为--,目前排名第<color=green>--</color>位";
yourPositionText.text = LocalizationService.Get("ranking.your_position_empty", "您的得分为--,目前排名第<color=green>--</color>位");
return;
}
yourPositionText.text =
$"您的得分为{localEntry.total_score},目前排名第<color=green>{localEntry.rank}</color>位";
yourPositionText.text = LocalizationService.GetFormat("ranking.your_position_value", localEntry.total_score, localEntry.rank);
}
private static string FormatSubmitTime(LeaderboardEntry entry)
@@ -89,7 +89,7 @@ public class settleChart : MonoBehaviour
BeatmapManager beatmapManager = BeatmapManager.Instance != null
? BeatmapManager.Instance
: FindAnyObjectByType<BeatmapManager>();
: SceneObjectLookupCache.FindAny<BeatmapManager>();
NoteData[] notes = beatmapManager != null && beatmapManager.beatmap != null
? beatmapManager.beatmap.notes
@@ -401,13 +401,13 @@ public class settleChart : MonoBehaviour
}
string lowerName = button.name.ToLowerInvariant();
if (previousPageButton == null && (lowerName.Contains("prev") || lowerName.Contains("previous") || lowerName.Contains("lastpage") || lowerName.Contains("uppage") || lowerName.Contains("ÉÏÒ»")))
if (previousPageButton == null && (lowerName.Contains("prev") || lowerName.Contains("previous") || lowerName.Contains("lastpage") || lowerName.Contains("uppage") || lowerName.Contains("һ")))
{
previousPageButton = button;
continue;
}
if (nextPageButton == null && (lowerName.Contains("next") || lowerName.Contains("downpage") || lowerName.Contains("nextpage") || lowerName.Contains("ÏÂÒ»")))
if (nextPageButton == null && (lowerName.Contains("next") || lowerName.Contains("downpage") || lowerName.Contains("nextpage") || lowerName.Contains("һ")))
{
nextPageButton = button;
}
@@ -1,4 +1,4 @@
%YAML 1.1
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &118873712428923354
GameObject:
@@ -208,8 +208,8 @@ MonoBehaviour:
reward_icon: {fileID: 6251169732777221245}
reward_name: {fileID: 267584174794072845}
reward_count: {fileID: 6866410370984929931}
coin_sprite: {fileID: 21300000, guid: 1836bfb842a045f4f9a4a38b08b395ef, type: 3}
jiyisuipian: {fileID: 21300000, guid: 27adfaee3f8bee14280e634021049f50, type: 3}
coin_sprite: {fileID: 21300000, guid: 16f0765cc15fce04f828ecd44d1dbf91, type: 3}
jiyisuipian: {fileID: 21300000, guid: 6fd4dd33d986d824d95f7b01579acc4d, type: 3}
player_exp: {fileID: 21300000, guid: af3ce18394be1b448a9e38602526eb80, type: 3}
--- !u!1 &7159171791523732843
GameObject:
@@ -437,7 +437,7 @@ public class settlementController : MonoBehaviour
{
for (int i = 0; i < 5; i++)
{
GameObject go = GameObject.Find($"ally_0{i + 1}");
GameObject go = SceneObjectLookupCache.Find($"ally_0{i + 1}");
if (go != null)
{
AllyCombatant ac = go.GetComponent<AllyCombatant>();
@@ -550,7 +550,7 @@ public class settlementController : MonoBehaviour
getThisSong_info();
// --- Statistics: Record total play time at settlement ---
GameManager runtimeGameManager = gm != null ? gm : FindAnyObjectByType<GameManager>();
GameManager runtimeGameManager = gm != null ? gm : SceneObjectLookupCache.FindAny<GameManager>();
float sessionDurationForTasks = runtimeGameManager != null ? runtimeGameManager.GetPendingSessionDurationSeconds() : 0f;
if (runtimeGameManager != null) runtimeGameManager.RecordTotalPlayTime();
@@ -733,7 +733,7 @@ public class settlementController : MonoBehaviour
if (settlementTeamLoader == null)
{
// try to auto-locate loader in scene if not assigned in inspector
settlementTeamLoader = FindAnyObjectByType<loadSettlementTeamPrefab>();
settlementTeamLoader = SceneObjectLookupCache.FindAny<loadSettlementTeamPrefab>();
}
if (settlementTeamLoader != null)
@@ -741,7 +741,7 @@ public class settlementController : MonoBehaviour
// ensure loader has key references
if (settlementTeamLoader.sm == null) settlementTeamLoader.sm = sm ?? ScoreManager.Instance;
if (settlementTeamLoader.tuic == null) settlementTeamLoader.tuic = teamUIController.Instance;
if (settlementTeamLoader.gm == null) settlementTeamLoader.gm = gm ?? FindAnyObjectByType<GameManager>();
if (settlementTeamLoader.gm == null) settlementTeamLoader.gm = gm ?? SceneObjectLookupCache.FindAny<GameManager>();
try { settlementTeamLoader.PopulateSettlementCards(); }
catch (System.Exception ex) { Debug.LogWarning("Failed to PopulateSettlementCards: " + ex); }
}
@@ -996,7 +996,7 @@ public class settlementController : MonoBehaviour
private bool IsEarlySettlement(int noteCountRaw)
{
NoteSpawner spawner = FindAnyObjectByType<NoteSpawner>();
NoteSpawner spawner = SceneObjectLookupCache.FindAny<NoteSpawner>();
if (spawner != null && spawner.IsImmediateSettlementTriggered)
{
return true;
@@ -2078,7 +2078,7 @@ public class settlementController : MonoBehaviour
// fallback: read AllyCombatant.currentScore from scene
for (int i = 0; i < 5; i++)
{
var go = GameObject.Find($"ally_0{ i+1 }");
var go = SceneObjectLookupCache.Find($"ally_0{ i+1 }");
if (go != null)
{
var ac = go.GetComponent<AllyCombatant>();