From 1222d39fdf68cf0531b67175349a0b322a04cf7d Mon Sep 17 00:00:00 2001 From: scove Date: Wed, 29 Apr 2026 13:10:00 +0700 Subject: [PATCH] Elo system update --- .idea/.idea.HALLUCINATE/.idea/workspace.xml | 21 +- Assets/Scripts/Duy/PlayerInputData.cs | 12 +- Assets/Scripts/Duy/_BasicSpawner.cs | 67 +++++-- Assets/Scripts/Duy/_PlayerDataManager.cs | 16 +- Assets/Scripts/Fusion/BasicSpawner.cs | 187 ------------------ Assets/Scripts/Fusion/BasicSpawner.cs.meta | 2 - Assets/Scripts/Fusion/LobbyHelper.cs | 1 - Assets/Scripts/Fusion/LobbyHelper.cs.meta | 2 - Assets/Scripts/Fusion/LobbyManager.cs | 28 --- Assets/Scripts/Fusion/LobbyManager.cs.meta | 2 - Assets/Scripts/Fusion/PlayerProfile.cs | 17 -- Assets/Scripts/Fusion/PlayerProfile.cs.meta | 2 - Assets/Scripts/{Fusion.meta => Game.meta} | 2 +- Assets/Scripts/Game/EloSystem.cs | 64 ++++++ Assets/Scripts/Game/EloSystem.cs.meta | 2 + Assets/Scripts/Network.meta | 8 + Assets/Scripts/Network/MatchResultManager.cs | 77 ++++++++ .../Network/MatchResultManager.cs.meta | 2 + .../Player Controller/PlayerStateMachine.cs | 24 ++- .../Scripts/Player Controller/PlayerStats.cs | 17 +- Assets/Scripts/UI/BaseUIController.cs | 2 + Assets/Scripts/UI/HUDController.cs | 2 +- Assets/Scripts/UI/LobbyController.cs | 177 +++++++++++++++-- Assets/Scripts/UI/MainMenuController.cs | 2 +- Assets/Scripts/UI/UIManager.cs | 19 +- 25 files changed, 462 insertions(+), 293 deletions(-) delete mode 100644 Assets/Scripts/Fusion/BasicSpawner.cs delete mode 100644 Assets/Scripts/Fusion/BasicSpawner.cs.meta delete mode 100644 Assets/Scripts/Fusion/LobbyHelper.cs delete mode 100644 Assets/Scripts/Fusion/LobbyHelper.cs.meta delete mode 100644 Assets/Scripts/Fusion/LobbyManager.cs delete mode 100644 Assets/Scripts/Fusion/LobbyManager.cs.meta delete mode 100644 Assets/Scripts/Fusion/PlayerProfile.cs delete mode 100644 Assets/Scripts/Fusion/PlayerProfile.cs.meta rename Assets/Scripts/{Fusion.meta => Game.meta} (77%) create mode 100644 Assets/Scripts/Game/EloSystem.cs create mode 100644 Assets/Scripts/Game/EloSystem.cs.meta create mode 100644 Assets/Scripts/Network.meta create mode 100644 Assets/Scripts/Network/MatchResultManager.cs create mode 100644 Assets/Scripts/Network/MatchResultManager.cs.meta diff --git a/.idea/.idea.HALLUCINATE/.idea/workspace.xml b/.idea/.idea.HALLUCINATE/.idea/workspace.xml index 4698e1a3..61e14203 100644 --- a/.idea/.idea.HALLUCINATE/.idea/workspace.xml +++ b/.idea/.idea.HALLUCINATE/.idea/workspace.xml @@ -6,12 +6,25 @@ + + + + + + + + + + + + + + + + - - - diff --git a/Assets/Scripts/Duy/PlayerInputData.cs b/Assets/Scripts/Duy/PlayerInputData.cs index c95009d6..b1396da0 100644 --- a/Assets/Scripts/Duy/PlayerInputData.cs +++ b/Assets/Scripts/Duy/PlayerInputData.cs @@ -1,6 +1,12 @@ using Fusion; +using UnityEngine; -public partial struct _PlayerInputData : INetworkInput +namespace OnlyScove.Scripts { - public float direction; -} \ No newline at end of file + public struct PlayerInputData : INetworkInput + { + public Vector2 Direction; + public NetworkBool sprint; + public Quaternion rot; + } +} diff --git a/Assets/Scripts/Duy/_BasicSpawner.cs b/Assets/Scripts/Duy/_BasicSpawner.cs index 2f9534e2..00af20bf 100644 --- a/Assets/Scripts/Duy/_BasicSpawner.cs +++ b/Assets/Scripts/Duy/_BasicSpawner.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Fusion; using Fusion.Sockets; using UnityEngine; +using OnlyScove.Scripts; namespace Hallucinate.UI { @@ -42,7 +43,6 @@ namespace Hallucinate.UI { OnJoinStartedEvent?.Invoke(); - // Sử dụng SessionProperties để lưu mật khẩu var customProps = new Dictionary(); if (!string.IsNullOrEmpty(password)) { @@ -88,13 +88,6 @@ namespace Hallucinate.UI public void OnPlayerJoined(NetworkRunner runner, PlayerRef player) { - if (runner.IsServer) - { - Vector2 spawnPosition = (player == runner.LocalPlayer) ? new Vector2(-8, 0) : new Vector2(8, 0); - var networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player); - _spawnedCharacters.Add(player, networkPlayerObject); - } - if (player == runner.LocalPlayer) { var pdm = FindFirstObjectByType<_PlayerDataManager>(); @@ -104,12 +97,21 @@ namespace Hallucinate.UI { Name = LocalPlayerProfile.Name, Role = LocalPlayerProfile.Role, + IsReady = false }; pdm.RPC_UpdatePlayerMetaData(player, metaData); } } } + public void StartGame() + { + if (_runner.IsServer) + { + _runner.LoadScene("Main Scene"); + } + } + public void OnPlayerLeft(NetworkRunner runner, PlayerRef player) { if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject)) @@ -132,8 +134,27 @@ namespace Hallucinate.UI public void OnInput(NetworkRunner runner, NetworkInput input) { - var data = new _PlayerInputData(); - data.direction = Input.GetAxis("Vertical"); + var data = new PlayerInputData(); + + // Try to get input from the local player's InputReader + if (PlayerStateMachine.Local != null && PlayerStateMachine.Local.Input != null) + { + var inputReader = PlayerStateMachine.Local.Input; + data.Direction = inputReader.MoveInput; + data.sprint = inputReader.IsSprintHeld; + + if (PlayerStateMachine.Local.Cam != null) + { + data.rot = PlayerStateMachine.Local.Cam.PlanarRotation; + } + } + else + { + // Fallback to basic input if player not spawned or InputReader missing + data.Direction = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")); + data.sprint = Input.GetKey(KeyCode.LeftShift); + } + input.Set(data); } @@ -149,7 +170,31 @@ namespace Hallucinate.UI public void OnObjectEnterAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { } public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary data) { } public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken) { } - public void OnSceneLoadDone(NetworkRunner runner) { } + + public void OnSceneLoadDone(NetworkRunner runner) + { + string currentSceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name; + + if (runner.IsServer && currentSceneName == "Main Scene") + { + foreach (var player in runner.ActivePlayers) + { + Vector2 spawnPosition = (player == runner.LocalPlayer) ? new Vector2(-8, 0) : new Vector2(8, 0); + var networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player); + _spawnedCharacters.Add(player, networkPlayerObject); + } + } + + if (currentSceneName == "Main Scene") + { + if (UIManager.Instance != null) UIManager.Instance.OnGameStarted(); + } + else if (currentSceneName == "Lobby" || currentSceneName == "Menu") + { + if (UIManager.Instance != null) UIManager.Instance.OnBackToMenu(); + } + } + public void OnSceneLoadStart(NetworkRunner runner) { } } } diff --git a/Assets/Scripts/Duy/_PlayerDataManager.cs b/Assets/Scripts/Duy/_PlayerDataManager.cs index 5762967d..10efcd46 100644 --- a/Assets/Scripts/Duy/_PlayerDataManager.cs +++ b/Assets/Scripts/Duy/_PlayerDataManager.cs @@ -6,23 +6,29 @@ public struct _PlayerMetaData : INetworkStruct { public NetworkString<_16> Name; public _Role Role; + public NetworkBool IsReady; } public class _PlayerDataManager : NetworkBehaviour { - // biến này của Fusion sẽ tự động đồng bộ giữa các client và host, - // khi có thay đổi sẽ tự động cập nhật ở tất cả các bên [Networked] public NetworkDictionary Players => default; - // RPC: phương thức này sẽ được gọi từ client hoặc - // host để cập nhật thông tin player, sau đó sẽ được gửi - // đến state authority (host) để xử lý và đồng bộ lại cho tất cả các client [Rpc(RpcSources.All, RpcTargets.StateAuthority)] public void RPC_UpdatePlayerMetaData(PlayerRef playerRef, _PlayerMetaData metaData) { Players.Set(playerRef, metaData); } + + [Rpc(RpcSources.All, RpcTargets.StateAuthority)] + public void RPC_SetReady(PlayerRef playerRef, bool ready) + { + if (Players.TryGet(playerRef, out var data)) + { + data.IsReady = ready; + Players.Set(playerRef, data); + } + } public bool TryGetPlayerMetaData(PlayerRef playerRef, out _PlayerMetaData metaData) { diff --git a/Assets/Scripts/Fusion/BasicSpawner.cs b/Assets/Scripts/Fusion/BasicSpawner.cs deleted file mode 100644 index 7f9488a9..00000000 --- a/Assets/Scripts/Fusion/BasicSpawner.cs +++ /dev/null @@ -1,187 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Linq; -using Fusion; -using Fusion.Sockets; -using UnityEngine; -using UnityEngine.SceneManagement; -using Random = UnityEngine.Random; - -// Struct input đồng bộ giữa Spawner, Movement và StateMachine -public struct PlayerInputData : INetworkInput -{ - public Vector2 Direction; - public Quaternion rot; - public bool sprint; -} - -public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks -{ - private NetworkRunner _runner; - private string _roomName = "Room1"; - - public LobbyManager LobbyManager; - [SerializeField] private NetworkPrefabRef _playerPrefab; - private Dictionary _spawnedCharacters = new Dictionary(); - - // Thông tin profile local - public PlayerProfile LocalPlayerProfile { get; private set; } - public void SetLocalPlayerProfile(PlayerProfile profile) - { - LocalPlayerProfile = profile; - } - - private void Awake() - { - if (_runner == null) _runner = gameObject.AddComponent(); - DontDestroyOnLoad(gameObject); - } - - // Khởi tạo Game (Host/Client) - public async Task StartGame(GameMode mode, string sessionName = "TestRoom") - { - Debug.Log($"Fusion: Đang khởi tạo kết nối với Mode: {mode} | Phòng: {sessionName}"); - - if (_runner == null) _runner = gameObject.AddComponent(); - _runner.ProvideInput = true; - - var sceneManager = gameObject.GetComponent(); - if (sceneManager == null) sceneManager = gameObject.AddComponent(); - - var result = await _runner.StartGame(new StartGameArgs() - { - GameMode = mode, - SessionName = sessionName, - Scene = SceneRef.FromIndex(1), - SceneManager = sceneManager - }); - - if (result.Ok) - { - Debug.Log($"Fusion thành công: Đã vào phòng {sessionName}"); - } - else - { - Debug.LogError($"Fusion thất bại: Lý do: {result.ShutdownReason}"); - if (_runner != null) - { - Destroy(_runner); - _runner = null; - } - } - } - - private void OnGUI() - { - if (_runner == null || !_runner.IsRunning) - { - float width = 400; - float height = 300; - float x = (Screen.width - width) / 2f; - float y = (Screen.height - height) / 2f; - - GUI.Box(new Rect(x, y, width, height), "FUSION MULTIPLAYER"); - - float innerX = x + 20; - float innerY = y + 40; - float contentWidth = width - 40; - - GUI.Label(new Rect(innerX, innerY, 100, 30), "Tên phòng:"); - _roomName = GUI.TextField(new Rect(innerX + 100, innerY, contentWidth - 100, 30), _roomName); - - if (GUI.Button(new Rect(innerX, innerY + 50, contentWidth, 60), "VÀO PHÒNG\n(Tự động Host/Client)")) - { - _ = StartGame(GameMode.AutoHostOrClient, _roomName); - } - - if (GUI.Button(new Rect(innerX, innerY + 120, (contentWidth / 2) - 5, 50), "Tạo phòng\n(Host)")) - { - _ = StartGame(GameMode.Host, _roomName); - } - - if (GUI.Button(new Rect(innerX + (contentWidth / 2) + 5, innerY + 120, (contentWidth / 2) - 5, 50), "Tham gia\n(Client)")) - { - _ = StartGame(GameMode.Client, _roomName); - } - - GUI.Label(new Rect(innerX, innerY + 180, contentWidth, 50), "Gợi ý: Nhập cùng tên phòng để chơi chung.\nNếu phòng chưa có, máy sẽ tự tạo mới."); - } - else - { - string region = (_runner.SessionInfo != null && _runner.SessionInfo.IsValid) ? _runner.SessionInfo.Region : "Connecting..."; - int playerCount = 0; - foreach (var p in _runner.ActivePlayers) playerCount++; - - string info = $"Mode: {_runner.GameMode} | Region: {region} | Players: {playerCount}"; - GUI.Box(new Rect(10, 10, 400, 30), info); - - if (GUI.Button(new Rect(10, 50, 100, 30), "Thoát")) - { - _runner.Shutdown(); - } - } - } - - public void OnPlayerJoined(NetworkRunner runner, PlayerRef player) - { - if (runner.IsServer) - { - Vector3 spawnPosition = new Vector3(Random.Range(-10f, 10f), 2f, Random.Range(-10f, 10f)); - spawnPosition += new Vector3(player.RawEncoded % 3, 0, player.RawEncoded % 3); - - var networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player); - runner.SetPlayerObject(player, networkPlayerObject); - _spawnedCharacters.Add(player, networkPlayerObject); - } - } - - public void OnPlayerLeft(NetworkRunner runner, PlayerRef player) - { - if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject)) - { - if (networkObject != null) runner.Despawn(networkObject); - _spawnedCharacters.Remove(player); - } - } - - public void OnInput(NetworkRunner runner, NetworkInput input) - { - var data = new PlayerInputData(); - - // ĐỌC TRỰC TIẾP: Không dùng Buffer để tránh bị trôi phím - data.Direction = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")); - data.sprint = Input.GetKey(KeyCode.LeftShift); - - if (OnlyScove.Scripts.PlayerStateMachine.Local != null) - { - var sm = OnlyScove.Scripts.PlayerStateMachine.Local; - if (sm.Cam != null) data.rot = sm.Cam.PlanarRotation; - else data.rot = sm.NetworkedCameraRotation; - } - - input.Set(data); - } - - public void OnSessionListUpdated(NetworkRunner runner, List sessionList) - { - if (LobbyManager != null) LobbyManager.DisplayRoomList(sessionList); - // UI.UIEventBus.TriggerRoomListUpdate(); - } - - public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input) { } - public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason) { } - public void OnConnectedToServer(NetworkRunner runner) { } - public void OnDisconnectedFromServer(NetworkRunner runner, NetDisconnectReason reason) { } - public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token) { } - public void OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason) { } - public void OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message) { } - public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary data) { } - public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken) { } - public void OnSceneLoadDone(NetworkRunner runner) { } - public void OnSceneLoadStart(NetworkRunner runner) { } - public void OnObjectExitAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { } - public void OnObjectEnterAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { } - public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ReliableKey key, ArraySegment data) { } - public void OnReliableDataProgress(NetworkRunner runner, PlayerRef player, ReliableKey key, float progress) { } -} \ No newline at end of file diff --git a/Assets/Scripts/Fusion/BasicSpawner.cs.meta b/Assets/Scripts/Fusion/BasicSpawner.cs.meta deleted file mode 100644 index cf96aa18..00000000 --- a/Assets/Scripts/Fusion/BasicSpawner.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 44bfaa339c82069418e72a14479a0212 \ No newline at end of file diff --git a/Assets/Scripts/Fusion/LobbyHelper.cs b/Assets/Scripts/Fusion/LobbyHelper.cs deleted file mode 100644 index e129507a..00000000 --- a/Assets/Scripts/Fusion/LobbyHelper.cs +++ /dev/null @@ -1 +0,0 @@ -// File này hiện tại không còn nội dung, có thể xóa đi hoặc để trống. diff --git a/Assets/Scripts/Fusion/LobbyHelper.cs.meta b/Assets/Scripts/Fusion/LobbyHelper.cs.meta deleted file mode 100644 index ce238440..00000000 --- a/Assets/Scripts/Fusion/LobbyHelper.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 2bf2c7f159565794b87d6f3ca2eb2976 \ No newline at end of file diff --git a/Assets/Scripts/Fusion/LobbyManager.cs b/Assets/Scripts/Fusion/LobbyManager.cs deleted file mode 100644 index 996e94ec..00000000 --- a/Assets/Scripts/Fusion/LobbyManager.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; -using Fusion; -using UnityEngine; - -public class LobbyManager : MonoBehaviour -{ - [Header("UI References")] - public Transform roomListContent; // Ô chứa danh sách phòng (nếu có) - - public void DisplayRoomList(List sessionList) - { - Debug.Log($"Lobby Update: Đang tìm thấy {sessionList.Count} phòng."); - - // Xóa danh sách cũ (nếu bạn làm UI) - /* - foreach (Transform child in roomListContent) { - Destroy(child.gameObject); - } - */ - - // Hiển thị danh sách mới - foreach (var session in sessionList) - { - Debug.Log($"- Phòng: {session.Name} | Người chơi: {session.PlayerCount}/{session.MaxPlayers}"); - // Ở đây bạn sẽ Instantiate các Button đại diện cho mỗi phòng - } - } -} diff --git a/Assets/Scripts/Fusion/LobbyManager.cs.meta b/Assets/Scripts/Fusion/LobbyManager.cs.meta deleted file mode 100644 index a269a8c8..00000000 --- a/Assets/Scripts/Fusion/LobbyManager.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: b5aeb4670d7bf41499d3aaf409820260 \ No newline at end of file diff --git a/Assets/Scripts/Fusion/PlayerProfile.cs b/Assets/Scripts/Fusion/PlayerProfile.cs deleted file mode 100644 index 14fb3da9..00000000 --- a/Assets/Scripts/Fusion/PlayerProfile.cs +++ /dev/null @@ -1,17 +0,0 @@ -using UnityEngine; - -// Enum các loại nhân vật -public enum CharacterClass -{ - Warrior, - Mage, - Archer -} - -// Lớp quản lý thông tin nhân vật local -[System.Serializable] -public class PlayerProfile -{ - public string Name = "Player"; - public CharacterClass Class = CharacterClass.Warrior; -} diff --git a/Assets/Scripts/Fusion/PlayerProfile.cs.meta b/Assets/Scripts/Fusion/PlayerProfile.cs.meta deleted file mode 100644 index 2b9acb62..00000000 --- a/Assets/Scripts/Fusion/PlayerProfile.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 8a1452ae101af8e43b94c2c778a70fe0 \ No newline at end of file diff --git a/Assets/Scripts/Fusion.meta b/Assets/Scripts/Game.meta similarity index 77% rename from Assets/Scripts/Fusion.meta rename to Assets/Scripts/Game.meta index 13389f28..1aacaf90 100644 --- a/Assets/Scripts/Fusion.meta +++ b/Assets/Scripts/Game.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 6aa851b2c7ee553439ee0065f77665cb +guid: 1a38893b1d8574b45bce269c39824bd6 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Scripts/Game/EloSystem.cs b/Assets/Scripts/Game/EloSystem.cs new file mode 100644 index 00000000..276d16ca --- /dev/null +++ b/Assets/Scripts/Game/EloSystem.cs @@ -0,0 +1,64 @@ +using UnityEngine; +using Fusion; + +namespace Hallucinate.Game +{ + public static class EloSystem + { + public static EloResult Calculate( + int ratingA, int ratingB, + int gamesPlayedA, int gamesPlayedB, + float resultA) // 1=win, 0=lose, 0.5=draw + { + float eA = 1f / (1f + Mathf.Pow(10f, (ratingB - ratingA) / 400f)); + int kA = GetK(ratingA, gamesPlayedA); + int kB = GetK(ratingB, gamesPlayedB); + + int nA = Mathf.Max(100, Mathf.RoundToInt(ratingA + kA * (resultA - eA))); + int nB = Mathf.Max(100, Mathf.RoundToInt(ratingB + kB * ((1 - resultA) - (1 - eA)))); + + return new EloResult(nA, nB, nA - ratingA, nB - ratingB); + } + + private static int GetK(int r, int g) => + g < 30 ? 40 : r < 1200 ? 32 : r < 2000 ? 24 : 16; + + public static string GetRank(int rating) + { + if (rating < 800) return "Iron"; + if (rating < 1000) return "Bronze"; + if (rating < 1200) return "Silver"; + if (rating < 1500) return "Gold"; + if (rating < 1800) return "Platinum"; + if (rating < 2100) return "Diamond"; + return "Master"; + } + + public static string GetRankColor(int rating) + { + if (rating < 800) return "#8A8A8A"; + if (rating < 1000) return "#CD7F32"; + if (rating < 1200) return "#C0C0C0"; + if (rating < 1500) return "#FFD700"; + if (rating < 1800) return "#4DC8A0"; + if (rating < 2100) return "#7B6EE8"; + return "#E84D8A"; + } + } + + public struct EloResult : INetworkStruct + { + public int NewRatingA; + public int NewRatingB; + public int DeltaA; + public int DeltaB; + + public EloResult(int nA, int nB, int dA, int dB) + { + NewRatingA = nA; + NewRatingB = nB; + DeltaA = dA; + DeltaB = dB; + } + } +} diff --git a/Assets/Scripts/Game/EloSystem.cs.meta b/Assets/Scripts/Game/EloSystem.cs.meta new file mode 100644 index 00000000..9c8dcfc1 --- /dev/null +++ b/Assets/Scripts/Game/EloSystem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 366843378ad652a41aeb5972186ebe18 \ No newline at end of file diff --git a/Assets/Scripts/Network.meta b/Assets/Scripts/Network.meta new file mode 100644 index 00000000..2654083c --- /dev/null +++ b/Assets/Scripts/Network.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f96029db35b52ba4182888a7f14d2ea7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Network/MatchResultManager.cs b/Assets/Scripts/Network/MatchResultManager.cs new file mode 100644 index 00000000..d36207d2 --- /dev/null +++ b/Assets/Scripts/Network/MatchResultManager.cs @@ -0,0 +1,77 @@ +using Fusion; +using UnityEngine; +using Hallucinate.Game; +using Hallucinate.UI; +using System.Linq; + +namespace Hallucinate.Network +{ + public class MatchResultManager : NetworkBehaviour + { + public static MatchResultManager Instance { get; private set; } + + public override void Spawned() + { + if (Object.HasStateAuthority) Instance = this; + } + + [Rpc(RpcSources.StateAuthority, RpcTargets.All)] + public void RPC_BroadcastResult(PlayerRef winner, EloResult eloResult) + { + Debug.Log($"Game Over! Winner: {winner}. Elo updated."); + + // Update local Elo display and show Result UI + if (Runner.LocalPlayer == winner) + { + ShowResultUI(true, eloResult.DeltaA, eloResult.NewRatingA); + } + else + { + ShowResultUI(false, eloResult.DeltaB, eloResult.NewRatingB); + } + } + + private void ShowResultUI(bool isWin, int delta, int newRating) + { + var hud = FindFirstObjectByType(); + if (hud != null) + { + // In a real scenario, we might push a new Result screen + // For now, let's assume HUD has a result panel + Debug.Log($"RESULT: {(isWin ? "WIN" : "LOSS")} | Delta: {delta} | New Rating: {newRating}"); + + // Save to PlayerPrefs as a dummy "Server" persistence + PlayerPrefs.SetInt("EloRating", newRating); + int gamesPlayed = PlayerPrefs.GetInt("GamesPlayed", 0); + PlayerPrefs.SetInt("GamesPlayed", gamesPlayed + 1); + PlayerPrefs.Save(); + } + } + + public void ProcessMatchEnd(PlayerRef winner) + { + if (!Object.HasStateAuthority) return; + + // Get ratings for both players + // In a real game, these would come from the server/metadata + int ratingA = PlayerPrefs.GetInt("EloRating", 1000); + int ratingB = 1000; // Placeholder for opponent + int gamesA = PlayerPrefs.GetInt("GamesPlayed", 0); + int gamesB = 0; // Placeholder + + float resultA = (Runner.LocalPlayer == winner) ? 1.0f : 0.0f; + + EloResult elo = EloSystem.Calculate(ratingA, ratingB, gamesA, gamesB, resultA); + + RPC_BroadcastResult(winner, elo); + + // Shut down runner after some delay + Invoke(nameof(ShutdownRunner), 5.0f); + } + + private void ShutdownRunner() + { + Runner.Shutdown(); + } + } +} diff --git a/Assets/Scripts/Network/MatchResultManager.cs.meta b/Assets/Scripts/Network/MatchResultManager.cs.meta new file mode 100644 index 00000000..848e604e --- /dev/null +++ b/Assets/Scripts/Network/MatchResultManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9eac0255d30a2bb40a43ff12cdcdf960 \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerStateMachine.cs b/Assets/Scripts/Player Controller/PlayerStateMachine.cs index 128e4b6d..8bbed301 100644 --- a/Assets/Scripts/Player Controller/PlayerStateMachine.cs +++ b/Assets/Scripts/Player Controller/PlayerStateMachine.cs @@ -146,14 +146,6 @@ namespace OnlyScove.Scripts bool isRunning = Runner != null && Runner.IsRunning; if (Object == null && isRunning) return; - // Proxy Sync - if (isRunning && Movement.NetworkedPosition != Vector3.zero && !Object.HasInputAuthority) - { - Controller.enabled = false; - transform.position = Movement.NetworkedPosition; - Controller.enabled = true; - } - if (GetInput(out PlayerInputData data)) { MoveInput = data.Direction; @@ -175,10 +167,24 @@ namespace OnlyScove.Scripts currentState?.Tick(isRunning ? Runner.DeltaTime : Time.fixedDeltaTime); } } - else + } + + public override void Render() + { + bool isRunning = Runner != null && Runner.IsRunning; + if (isRunning && !Object.HasInputAuthority) { + // Smooth interpolation for proxies + if (Movement.NetworkedPosition != Vector3.zero) + { + transform.position = Vector3.Lerp(transform.position, Movement.NetworkedPosition, Runner.DeltaTime * 15f); + } UpdateAnimator(Runner.DeltaTime); } + else if (!isRunning) + { + UpdateAnimator(Time.deltaTime); + } } private void Update() diff --git a/Assets/Scripts/Player Controller/PlayerStats.cs b/Assets/Scripts/Player Controller/PlayerStats.cs index ae81db9b..627c8999 100644 --- a/Assets/Scripts/Player Controller/PlayerStats.cs +++ b/Assets/Scripts/Player Controller/PlayerStats.cs @@ -1,6 +1,7 @@ using UnityEngine; using Fusion; using System; +using Hallucinate.Network; namespace OnlyScove.Scripts { @@ -24,10 +25,23 @@ namespace OnlyScove.Scripts void OnHealthChangedRender() { OnHealthChanged?.Invoke(Health); + + if (Health <= 0 && Object.HasStateAuthority) + { + // Find the other player as winner + foreach (var player in Runner.ActivePlayers) + { + if (player != Object.InputAuthority) + { + MatchResultManager.Instance?.ProcessMatchEnd(player); + break; + } + } + } + if (Object.HasInputAuthority) { // UI Placeholder: Trigger Health UI Change - // Example: UI.UIEventBus.TriggerHealthChange(Health / 100f); } } @@ -37,7 +51,6 @@ namespace OnlyScove.Scripts if (Object.HasInputAuthority) { // UI Placeholder: Trigger Stamina UI Change - // Example: UI.UIEventBus.TriggerStaminaChange(Stamina / 100f); } } diff --git a/Assets/Scripts/UI/BaseUIController.cs b/Assets/Scripts/UI/BaseUIController.cs index ea9e766f..11b1cdd4 100644 --- a/Assets/Scripts/UI/BaseUIController.cs +++ b/Assets/Scripts/UI/BaseUIController.cs @@ -45,6 +45,8 @@ namespace Hallucinate.UI return key; } + public virtual void Update() { } + public virtual async Task PlayTransitionIn() { if (root == null) return; diff --git a/Assets/Scripts/UI/HUDController.cs b/Assets/Scripts/UI/HUDController.cs index 1dffb2e4..7ab9e928 100644 --- a/Assets/Scripts/UI/HUDController.cs +++ b/Assets/Scripts/UI/HUDController.cs @@ -53,7 +53,7 @@ namespace Hallucinate.UI } } - public void Update() + public override void Update() { if (!_isFaded && Time.time - _lastActionTime > FADE_TIMEOUT) { diff --git a/Assets/Scripts/UI/LobbyController.cs b/Assets/Scripts/UI/LobbyController.cs index 979222b6..b6a033a5 100644 --- a/Assets/Scripts/UI/LobbyController.cs +++ b/Assets/Scripts/UI/LobbyController.cs @@ -3,6 +3,7 @@ using UnityEngine.UIElements; using System.Collections.Generic; using System.Threading.Tasks; using Fusion; +using System.Linq; namespace Hallucinate.UI { @@ -10,6 +11,7 @@ namespace Hallucinate.UI { private VisualTreeAsset _roomItemTemplate; private _BasicSpawner _spawner; + private _PlayerDataManager _playerDataManager; // Containers private VisualElement _joinContainer, _createContainer, _loungeContainer, _passOverlay; @@ -24,6 +26,11 @@ namespace Hallucinate.UI private Label _joinPassError; private SessionInfo _selectedSession; + // Lounge Elements + private VisualElement _playerListContainer; + private Button _readyBtn, _startBtn; + private Label _loungeRoomName; + public override void Initialize(VisualElement uxmlRoot, UIManager manager) { base.Initialize(uxmlRoot, manager); @@ -44,27 +51,63 @@ namespace Hallucinate.UI _joinPassInput = root.Q("JoinPassInput"); _joinPassError = root.Q