Organize custom scripts under Assets/Baba_yaga and merge Opsive folders to Assets root

This commit is contained in:
2026-07-01 20:32:28 +07:00
parent 83d4157ac6
commit befc19bf37
5901 changed files with 243 additions and 141 deletions

View File

@@ -0,0 +1,498 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Fusion;
using Fusion.Sockets;
using UnityEngine;
using Baba_yaga;
namespace Baba_yaga.UI
{
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.UI", sourceAssembly: "Opsive.UltimateCharacterController")]
public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks
{
private static BasicSpawner _instance;
public static BasicSpawner Instance
{
get
{
if (_instance == null)
{
_instance = UnityEngine.Object.FindFirstObjectByType<BasicSpawner>();
}
return _instance;
}
}
private NetworkRunner _runner;
public NetworkRunner Runner => _runner;
private bool _isStarting = false;
private bool _isInternalShutdown = false;
public event Action<List<SessionInfo>> OnSessionListUpdatedEvent;
public event Action<string> OnShutdownEvent;
public event Action OnJoinStartedEvent;
public event Action OnJoinFailedEvent;
[Header("Prefabs")]
[SerializeField] private NetworkPrefabRef _playerPrefab;
[SerializeField] private NetworkPrefabRef _playerDataManagerPrefab;
private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
// Ensure this is a root object so DontDestroyOnLoad works correctly
transform.SetParent(null);
DontDestroyOnLoad(gameObject);
}
private async void Start()
{
// Auto-connect if we bypass the UI and start directly in the Main Scene
if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == "Main Scene")
{
Debug.Log("[BasicSpawner] Auto-starting Fusion in AutoHostOrClient mode for testing...");
if (_isStarting) return;
_isStarting = true;
try
{
await EnsureRunnerExists();
var sceneManager = gameObject.GetComponent<NetworkSceneManagerDefault>();
if (sceneManager == null) sceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>();
var result = await _runner.StartGame(new StartGameArgs()
{
GameMode = GameMode.Shared,
SessionName = "QuickTestRoom", // Hardcoded session for instant testing
SceneManager = sceneManager,
Scene = SceneRef.FromIndex(UnityEngine.SceneManagement.SceneManager.GetActiveScene().buildIndex)
});
if (result.Ok)
{
Debug.Log("[BasicSpawner] Auto Connect SUCCESS!");
}
else
{
Debug.LogError($"[BasicSpawner] Auto Connect FAILED: {result.ShutdownReason}");
}
}
finally
{
_isStarting = false;
}
}
}
public PlayerProfile LocalPlayerProfile { get; private set; }
public void SetLocalPlayerProfile(PlayerProfile _profile)
{
LocalPlayerProfile = _profile;
}
private async Task EnsureRunnerExists()
{
if (_runner != null)
{
_isInternalShutdown = true;
try
{
if (_runner.IsRunning)
{
Debug.Log("[BasicSpawner] Shutting down existing runner before recreation.");
await _runner.Shutdown();
}
// Check if it still exists (Unity pseudo-null check)
if (_runner != null)
{
// Only log if it's actually a valid object to destroy
// If it's already marked for destruction, Unity == null will be true soon
Destroy(_runner);
}
_runner = null;
await Task.Yield();
}
finally
{
_isInternalShutdown = false;
}
}
if (this == null) return; // BasicSpawner itself might be destroyed
_runner = gameObject.GetComponent<NetworkRunner>();
if (_runner == null)
{
Debug.Log("[BasicSpawner] Creating new NetworkRunner component.");
_runner = gameObject.AddComponent<NetworkRunner>();
}
_runner.ProvideInput = true;
_runner.AddCallbacks(this);
}
public async Task StartLobby()
{
if (_isStarting) return;
// Nếu đã ở trong lobby rồi thì không cần làm gì
if (_runner != null && _runner.IsRunning && _runner.LobbyInfo.IsValid) return;
Debug.Log("[BasicSpawner] StartLobby called");
_isStarting = true;
try
{
await EnsureRunnerExists();
Debug.Log("[BasicSpawner] Joining Lobby...");
var result = await _runner.JoinSessionLobby(SessionLobby.ClientServer);
if (!result.Ok)
{
Debug.LogWarning($"Join lobby result: {result.ShutdownReason}");
}
}
finally
{
_isStarting = false;
}
}
public async Task<bool> StartHost(string sessionName, string displayName, string password = null)
{
// Wait for any existing startup process (like StartLobby) to finish
while (_isStarting)
{
await Task.Yield();
}
_isStarting = true;
try
{
Debug.Log($"[BasicSpawner] StartHost called: {sessionName} ({displayName})");
OnJoinStartedEvent?.Invoke();
bool sceneExists = false;
for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCountInBuildSettings; i++)
{
if (UnityEngine.SceneManagement.SceneUtility.GetScenePathByBuildIndex(i).Contains("Main Scene"))
{
sceneExists = true;
break;
}
}
if (!sceneExists)
{
Debug.LogError("CRITICAL: 'Main Scene' is NOT in Build Settings!");
return false;
}
await EnsureRunnerExists();
var customProps = new Dictionary<string, SessionProperty>();
if (!string.IsNullOrEmpty(password))
{
customProps.Add("pw", password);
}
customProps.Add("rn", displayName);
// Re-create or find SceneManager to ensure it matches the new runner
var sceneManager = gameObject.GetComponent<NetworkSceneManagerDefault>();
if (sceneManager == null) sceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>();
var result = await _runner.StartGame(new StartGameArgs()
{
GameMode = GameMode.Host,
SessionName = sessionName,
SessionProperties = customProps,
PlayerCount = 2,
SceneManager = sceneManager
});
if (result.Ok)
{
Debug.Log("[BasicSpawner] StartHost SUCCESS");
if (_runner.IsServer && _playerDataManagerPrefab.IsValid)
{
if (FindFirstObjectByType<PlayerDataManager>() == null)
{
Debug.Log("[BasicSpawner] Spawning PlayerDataManager");
_runner.Spawn(_playerDataManagerPrefab, Vector3.zero, Quaternion.identity, null);
}
}
return true;
}
else
{
Debug.LogError($"[BasicSpawner] Fusion StartHost Failed: {result.ShutdownReason}.");
OnJoinFailedEvent?.Invoke();
return false;
}
}
finally
{
_isStarting = false;
}
}
public async Task<bool> StartClient(string sessionName, string password = null)
{
if (_isStarting) return false;
_isStarting = true;
try
{
OnJoinStartedEvent?.Invoke();
await EnsureRunnerExists();
var sceneManager = gameObject.GetComponent<NetworkSceneManagerDefault>();
if (sceneManager == null) sceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>();
var result = await _runner.StartGame(new StartGameArgs()
{
GameMode = GameMode.Client,
SessionName = sessionName,
SceneManager = sceneManager
});
if (result.Ok)
{
return true;
}
else
{
Debug.LogError($"[BasicSpawner] Fusion StartClient Failed: {result.ShutdownReason}");
OnJoinFailedEvent?.Invoke();
return false;
}
}
finally
{
_isStarting = false;
}
}
private Dictionary<PlayerRef, NetworkObject> _spawnedCharacters = new Dictionary<PlayerRef, NetworkObject>();
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
Debug.Log($"[BasicSpawner] PlayerJoined: {player.PlayerId}");
// In Shared Mode, there is no Server. Each client is responsible for spawning their own player.
if (player == runner.LocalPlayer)
{
SendLocalMetaData(player);
SpawnPlayer(runner, player);
}
}
private async void SendLocalMetaData(PlayerRef player)
{
PlayerDataManager pdm = null;
int retries = 0;
while (pdm == null && retries < 20)
{
pdm = FindFirstObjectByType<PlayerDataManager>();
if (pdm != null) break;
await Task.Delay(500);
retries++;
}
if (pdm != null)
{
string playerName = LocalPlayerProfile != null ? LocalPlayerProfile.Name : "Player " + player.PlayerId;
// Thêm hậu tố (HOST) nếu là server để dễ phân biệt
if (_runner.IsServer) playerName += " (HOST)";
_Role playerRole = _Role.Seeker;
var metaData = new _PlayerMetaData()
{
Name = playerName,
Role = playerRole,
IsReady = false
};
pdm.RPC_UpdatePlayerMetaData(player, metaData);
}
else
{
Debug.LogError("[BasicSpawner] Could not find PlayerDataManager after retries. Data will not sync.");
}
}
public void StartGame()
{
if (_runner != null && _runner.IsServer)
{
_runner.LoadScene("Main Scene");
}
}
public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
{
if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject))
{
runner.Despawn(networkObject);
_spawnedCharacters.Remove(player);
}
// Logic Reassign Leader (Logical)
if (runner.IsServer && PlayerDataManager.Instance != null && PlayerDataManager.Instance.Leader == player)
{
var nextLeader = runner.ActivePlayers.FirstOrDefault();
if (nextLeader != PlayerRef.None)
{
PlayerDataManager.Instance.Leader = nextLeader;
Debug.Log($"[BasicSpawner] Leader left. New logical leader: {nextLeader}");
}
}
if (runner.IsServer && player == runner.LocalPlayer)
{
runner.Shutdown();
}
}
public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason)
{
Debug.LogWarning($"[Fusion] Shutdown occurred. Reason: {shutdownReason}");
OnShutdownEvent?.Invoke(shutdownReason.ToString());
// Nếu shutdown là do hệ thống chủ động hủy để tạo runner mới, KHÔNG quay về Menu
if (_isInternalShutdown)
{
Debug.Log("[BasicSpawner] Internal shutdown detected, skipping Menu routing.");
return;
}
// Nếu đang trong quá trình Host Migration, đừng quay về menu
if (shutdownReason == ShutdownReason.HostMigration)
{
Debug.Log("[BasicSpawner] Shutdown due to Host Migration. Waiting for recovery...");
return;
}
/*if (UIManager.Instance != null)
{
UIManager.Instance.OnBackToMenu();
}*/
}
public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList)
{
OnSessionListUpdatedEvent?.Invoke(sessionList);
}
public void OnInput(NetworkRunner runner, NetworkInput input)
{
var data = new PlayerInputData();
if (Baba_yaga.Network.FusionClientMovementBridge.Local != null)
{
data = Baba_yaga.Network.FusionClientMovementBridge.Local.GetLocalInputData();
}
input.Set(data);
}
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 OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ReliableKey key, ArraySegment<byte> data) { }
public void OnReliableDataProgress(NetworkRunner runner, PlayerRef player, ReliableKey key, float progress) { }
public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input) { }
public void OnObjectExitAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { }
public void OnObjectEnterAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { }
public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary<string, object> data) { }
public async void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken)
{
Debug.Log("[BasicSpawner] OnHostMigration triggered!");
// 1. Shutdown existing runner properly
await runner.Shutdown(false);
// 2. Create new runner
await EnsureRunnerExists();
// 3. Restart as new Host/Server using the migration token
var result = await _runner.StartGame(new StartGameArgs()
{
HostMigrationToken = hostMigrationToken,
SceneManager = gameObject.GetComponent<NetworkSceneManagerDefault>() ?? gameObject.AddComponent<NetworkSceneManagerDefault>()
});
/*if (result.Ok)
{
Debug.Log("[BasicSpawner] Host Migration SUCCESSFUL");
}
else
{
Debug.LogError($"[BasicSpawner] Host Migration FAILED: {result.ShutdownReason}");
UIManager.Instance?.OnBackToMenu();
}*/
}
public void OnSceneLoadDone(NetworkRunner runner)
{
string currentSceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
if (runner.IsServer && currentSceneName == "Main Scene")
{
foreach (var player in runner.ActivePlayers)
{
if (!_spawnedCharacters.ContainsKey(player))
{
SpawnPlayer(runner, player);
}
}
}
/*if (currentSceneName == "Main Scene")
{
UIManager.Instance?.OnGameStarted();
/
BNM098TYU78I98IU7Y6T57U8I9I8U7Y6T57U8I7Y6T5Y67U8IU7Y6T57U8IU7Y6E4XDER45ESZXSDCER45EDSXZSDCEFR45TTRFGHJUIYTRW
}*/
}
private void SpawnPlayer(NetworkRunner runner, PlayerRef player)
{
Debug.Log($"[BasicSpawner] Spawning Player {player.PlayerId} at {Time.time}");
Vector3 spawnPosition = (player == runner.LocalPlayer) ? new Vector3(-8, 2, 0) : new Vector3(8, 2, 0);
var networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player);
// In Shared Mode, runner.Spawn automatically grants State Authority to the caller.
// We just need to assign Input Authority.
networkPlayerObject.AssignInputAuthority(player);
_spawnedCharacters[player] = networkPlayerObject;
}
private void OnGUI()
{
if (_runner != null && _runner.IsRunning)
{
GUI.color = Color.green;
GUI.Label(new Rect(10, 10, 300, 30), $"[Network] Session: {_runner.SessionInfo?.Name}");
GUI.Label(new Rect(10, 30, 300, 30), $"[Network] Players in Room: {_runner.ActivePlayers.Count()}");
GUI.Label(new Rect(10, 50, 300, 30), $"[Network] Am I Server?: {_runner.IsServer}");
}
}
public void OnSceneLoadStart(NetworkRunner runner) { }
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ca752d01bdc2c5e42938776307031da3

View File

@@ -0,0 +1,263 @@
using UnityEngine;
using Fusion;
using Opsive.Shared.Events;
using Opsive.UltimateCharacterController.Networking;
using Opsive.UltimateCharacterController.Networking.Character;
using Opsive.UltimateCharacterController.Character;
using Opsive.UltimateCharacterController.Character.Abilities;
using Opsive.UltimateCharacterController.Character.Abilities.Items;
using Opsive.UltimateCharacterController.Camera;
using Opsive.UltimateCharacterController.Input;
using Baba_yaga;
using Opsive.UltimateCharacterController.Game;
namespace Baba_yaga.Network
{
// Ensure Opsive components load before this
[DefaultExecutionOrder(100)]
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.Network", sourceAssembly: "Opsive.UltimateCharacterController")]
public class FusionClientMovementBridge : NetworkBehaviour, INetworkInfo, INetworkCharacter, ILookSource
{
public static FusionClientMovementBridge Local { get; private set; }
[Networked]
public PlayerInputData SyncInput { get; set; }
private UltimateCharacterLocomotion m_CharacterLocomotion;
private UltimateCharacterLocomotionHandler m_LocoHandler;
private Opsive.UltimateCharacterController.Input.PlayerInput m_PlayerInput;
private void Awake()
{
m_CharacterLocomotion = GetComponent<UltimateCharacterLocomotion>();
m_LocoHandler = GetComponent<UltimateCharacterLocomotionHandler>();
m_PlayerInput = GetComponent<Opsive.UltimateCharacterController.Input.PlayerInput>();
}
public override void Spawned()
{
// Because we passed State Authority to the client in BasicSpawner,
// HasStateAuthority is true ONLY for the local player.
// Check HasInputAuthority as well so the client's character correctly activates input!
bool isLocal = Object.HasStateAuthority || Object.HasInputAuthority;
if (isLocal)
{
Local = this;
}
// 1. Isolate Input: Only the local player should read keyboard/mouse inputs.
if (m_LocoHandler != null) m_LocoHandler.enabled = isLocal;
var activeInput = GetComponent<UnityInput>(); // Corrected class reference
if (activeInput != null) activeInput.enabled = isLocal;
// 2. Isolate Camera: Only attach the camera if this is the local player.
if (isLocal)
{
var cameraController = UnityEngine.Object.FindFirstObjectByType<CameraController>();
if (cameraController != null)
{
cameraController.Character = gameObject;
}
}
else
{
// For remote players, register this bridge as the LookSource
EventHandler.ExecuteEvent<ILookSource>(gameObject, "OnCharacterAttachLookSource", this);
}
}
public override void Despawned(NetworkRunner runner, bool hasState)
{
if (Local == this)
{
Local = null;
}
}
public override void FixedUpdateNetwork()
{
// ONLY attempt to get input if we are the Host (StateAuthority) or the owning Client (InputAuthority).
// Calling GetInput on a Proxy (someone else's character) is what causes the GetTypeKey exception!
if (Object.HasStateAuthority || Object.HasInputAuthority)
{
if (GetInput<PlayerInputData>(out var input))
{
// Apply input locally so the Client's character actually moves predicted!
if (m_CharacterLocomotion != null)
{
m_CharacterLocomotion.InputVector = input.InputVector;
m_CharacterLocomotion.RawInputVector = input.RawInputVector;
m_CharacterLocomotion.DeltaRotation = input.DeltaRotation;
}
if (Object.HasStateAuthority)
{
SyncInput = input;
}
}
}
// If we do NOT have StateAuthority AND we do NOT have InputAuthority, it's a Proxy (remote player).
if (Object.IsProxy)
{
// Ensure look source is attached for remote players
if (m_CharacterLocomotion != null && m_CharacterLocomotion.LookSource != (ILookSource)this)
{
EventHandler.ExecuteEvent<ILookSource>(gameObject, "OnCharacterAttachLookSource", this);
}
// Sync the movement inputs to KinematicObjectManager so it moves the remote player character
if (m_CharacterLocomotion != null && m_CharacterLocomotion.KinematicObjectIndex != -1)
{
KinematicObjectManager.SetCharacterMovementInput(
m_CharacterLocomotion.KinematicObjectIndex,
SyncInput.Direction.x,
SyncInput.Direction.y
);
}
if (m_CharacterLocomotion != null)
{
m_CharacterLocomotion.InputVector = SyncInput.InputVector;
m_CharacterLocomotion.RawInputVector = SyncInput.RawInputVector;
m_CharacterLocomotion.DeltaRotation = SyncInput.DeltaRotation;
// Sync remote abilities based on state
UpdateRemoteAbility<SpeedChange>(SyncInput.sprint);
UpdateRemoteAbility<Jump>(SyncInput.jump);
UpdateRemoteAbility<HeightChange>(SyncInput.crouch);
UpdateRemoteAbility<Aim>(SyncInput.aim);
UpdateRemoteAbility<Use>(SyncInput.use);
UpdateRemoteAbility<Reload>(SyncInput.reload);
}
}
}
private void UpdateRemoteAbility<T>(bool shouldBeActive) where T : Ability
{
if (m_CharacterLocomotion == null) return;
var ability = m_CharacterLocomotion.GetAbility<T>();
if (ability != null)
{
if (shouldBeActive && !ability.IsActive)
{
m_CharacterLocomotion.TryStartAbility(ability, true);
}
else if (!shouldBeActive && ability.IsActive)
{
m_CharacterLocomotion.TryStopAbility(ability, true);
}
}
}
public PlayerInputData GetLocalInputData()
{
var data = new PlayerInputData();
if (m_CharacterLocomotion == null) return data;
if (m_PlayerInput != null)
{
data.Direction = new Vector2(m_PlayerInput.GetAxisRaw("Horizontal"), m_PlayerInput.GetAxisRaw("Vertical"));
// Sync abilities active states or buttons
data.sprint = IsAbilityActive<SpeedChange>() || m_PlayerInput.GetButton("Change Speeds");
data.jump = IsAbilityActive<Jump>() || m_PlayerInput.GetButton("Jump");
data.crouch = IsAbilityActive<HeightChange>() || m_PlayerInput.GetButton("Crouch");
data.aim = IsAbilityActive<Aim>() || m_PlayerInput.GetButton("Aim");
data.use = IsAbilityActive<Use>() || m_PlayerInput.GetButton("Fire1");
data.reload = IsAbilityActive<Reload>() || m_PlayerInput.GetButton("Reload");
}
// Sync locomotion internal state parameters
data.InputVector = m_CharacterLocomotion.InputVector;
data.RawInputVector = m_CharacterLocomotion.RawInputVector;
data.DeltaRotation = m_CharacterLocomotion.DeltaRotation;
data.rot = transform.rotation;
// Sync Look direction and look pitch
var lookSource = m_CharacterLocomotion.LookSource;
if (lookSource != null)
{
data.LookPitch = lookSource.Pitch;
data.LookDirection = lookSource.LookDirection(true);
}
return data;
}
private bool IsAbilityActive<T>() where T : Ability
{
if (m_CharacterLocomotion == null) return false;
var ability = m_CharacterLocomotion.GetAbility<T>();
return ability != null && ability.IsActive;
}
// --- ILookSource Implementation ---
public GameObject GameObject => gameObject;
public Transform Transform => transform;
public float LookDirectionDistance => 1f;
public float Pitch => SyncInput.LookPitch;
public Vector3 LookPosition()
{
var animator = GetComponent<Animator>();
if (animator != null)
{
var head = animator.GetBoneTransform(HumanBodyBones.Head);
if (head != null) return head.position;
}
return transform.position + Vector3.up * 1.5f;
}
public Vector3 LookDirection(bool characterLookDirection)
{
return SyncInput.LookDirection == Vector3.zero ? transform.forward : SyncInput.LookDirection;
}
public Vector3 LookDirection(Vector3 lookPosition, bool characterLookDirection, int layerMask, bool useRecoil)
{
return LookDirection(characterLookDirection);
}
// --- INetworkInfo Implementation ---
public bool IsLocalPlayer() => Object.HasStateAuthority || Object.HasInputAuthority;
public bool IsServer() => Runner.IsServer;
// Return FALSE because we are doing Client-Authoritative movement!
public bool IsServerAuthoritative() => false;
// --- INetworkCharacter Implementation ---
// Since we are using Fusion's NetworkTransform to sync position,
// we can leave these methods empty. Opsive will handle the local
// movement, and Fusion's NetworkTransform will drag the remote players.
public void SetPosition(Vector3 position, bool snapAnimator) { }
public void SetRotation(Quaternion rotation, bool snapAnimator) { }
public void SetPositionAndRotation(Vector3 position, Quaternion rotation, bool snapAnimator) { }
public void ResetRotationPosition() { }
public void SetActive(bool active, bool uiEvent) { }
public void LoadDefaultLoadout() { }
public void EquipUnequipItem(uint itemID, int slotID, bool equip) { }
public void ItemIdentifierPickup(uint id, int amount, int slot, bool immediate, bool force) { }
public void RemoveAllItems() { }
public void Fire(Opsive.UltimateCharacterController.Items.Actions.ItemAction itemAction, float strength) { }
public void StartItemReload(Opsive.UltimateCharacterController.Items.Actions.ItemAction itemAction) { }
public void ReloadItem(Opsive.UltimateCharacterController.Items.Actions.ItemAction itemAction, bool fullClip) { }
public void ItemReloadComplete(Opsive.UltimateCharacterController.Items.Actions.ItemAction itemAction, bool success, bool immediateReload) { }
public void MeleeHitCollider(Opsive.UltimateCharacterController.Items.Actions.ItemAction itemAction, int hitboxIndex, RaycastHit raycastHit, GameObject hitGameObject, UltimateCharacterLocomotion hitCharacterLocomotion) { }
public void ThrowItem(Opsive.UltimateCharacterController.Items.Actions.ItemAction action) { }
public void EnableThrowableObjectMeshRenderers(Opsive.UltimateCharacterController.Items.Actions.ItemAction action) { }
public void StartStopBeginEndMagicActions(Opsive.UltimateCharacterController.Items.Actions.ItemAction action, bool begin, bool start) { }
public void MagicCast(Opsive.UltimateCharacterController.Items.Actions.ItemAction action, int index, uint castID, Vector3 dir, Vector3 target) { }
public void MagicImpact(Opsive.UltimateCharacterController.Items.Actions.ItemAction action, uint castID, GameObject source, GameObject target, Vector3 pos, Vector3 norm) { }
public void StopMagicCast(Opsive.UltimateCharacterController.Items.Actions.ItemAction action, int index, uint castID) { }
public void ToggleFlashlight(Opsive.UltimateCharacterController.Items.Actions.ItemAction action, bool active) { }
public void PushRigidbody(Rigidbody rb, Vector3 force, Vector3 point) { }
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 39343fbd597ed5947b34fa2777fa1b7c

View File

@@ -0,0 +1,74 @@
using Fusion;
using UnityEngine;
using Baba_yaga.Game;
using Baba_yaga.UI;
using System.Linq;
namespace Baba_yaga.Network
{
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.Network", sourceAssembly: "Opsive.UltimateCharacterController")]
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)
{
// 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();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9eac0255d30a2bb40a43ff12cdcdf960

View File

@@ -0,0 +1,30 @@
using Fusion;
using UnityEngine;
public class PlayerData : NetworkBehaviour
{
[Networked]
public _Role PlayerRole { get; set; }
public override void Spawned()
{
if (Object.HasInputAuthority)
{
SetupByRole(PlayerRole);
}
}
void SetupByRole(_Role role)
{
if (role == _Role.Seeker)
{
Debug.Log("I am Seeker");
// bật flashlight
}
else
{
Debug.Log("I am Trapper");
// bật trap UI
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 96ce77b74a34e7440a0b54af32c6d402

View File

@@ -0,0 +1,80 @@
using System;
using Fusion;
using UnityEngine;
// struct quản lý thông tin
public struct _PlayerMetaData : INetworkStruct
{
public NetworkString<_16> Name;
public _Role Role;
public NetworkBool IsReady;
}
public class PlayerDataManager : NetworkBehaviour
{
public static PlayerDataManager Instance { get; private set; }
[Networked]
public NetworkDictionary<PlayerRef, _PlayerMetaData> Players => default;
[Networked]
public PlayerRef Leader { get; set; }
public event Action<PlayerRef, string> OnChatMessageReceived;
public override void Spawned()
{
Instance = this;
if (Object.HasStateAuthority)
{
Leader = Runner.LocalPlayer;
}
}
public override void Despawned(NetworkRunner runner, bool hasState)
{
if (Instance == this) Instance = null;
}
[Rpc(RpcSources.All, RpcTargets.StateAuthority)]
public void RPC_TransferLeader(PlayerRef newLeader)
{
if (Players.ContainsKey(newLeader))
{
Leader = newLeader;
}
}
[Rpc(RpcSources.All, RpcTargets.StateAuthority)]
public void RPC_UpdatePlayerMetaData(PlayerRef playerRef, _PlayerMetaData metaData)
{
if (Object == null || !Object.IsValid) return;
Players.Set(playerRef, metaData);
}
[Rpc(RpcSources.All, RpcTargets.StateAuthority)]
public void RPC_SetReady(PlayerRef playerRef, bool ready)
{
if (Object == null || !Object.IsValid) return;
if (Players.TryGet(playerRef, out var data))
{
data.IsReady = ready;
Players.Set(playerRef, data);
}
}
[Rpc(RpcSources.All, RpcTargets.All)]
public void RPC_SendChatMessage(PlayerRef sender, string message)
{
OnChatMessageReceived?.Invoke(sender, message);
}
public bool TryGetPlayerMetaData(PlayerRef playerRef, out _PlayerMetaData metaData)
{
metaData = default;
// Kiểm tra xem object đã được Spawned chưa trước khi truy cập networked property
if (Object == null || !Object.IsValid) return false;
return Players.TryGet(playerRef, out metaData);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b3d9934ebd60c9c4ea3e464b77fd7ae0

View File

@@ -0,0 +1,56 @@
using Fusion;
using TMPro;
using UnityEngine;
public enum _Role
{
Seeker,
Trapper
}
[System.Serializable]
public class PlayerProfile
{
public string Name = "Player";
public _Role Role = _Role.Seeker;
}
public class PlayerInfo : NetworkBehaviour
{
[Networked] public string playerName { get; set; }
public PlayerDataManager playerDataManager;
public TextMeshProUGUI nameText;
public GameObject[] characterIcons; // mảng chứa icon tương ứng với từng class, có thể gán trong inspector
// sau khi game object được tạo ra trên mạng,
// sẽ gọi phương thức này để khởi tạo thông tin player
public override void Spawned()
{
playerDataManager = FindFirstObjectByType<PlayerDataManager>(); // tìm PlayerDataManager trong scene
}
// phương thức này sẽ được gọi mỗi frame để cập nhật thông tin hiển thị của player
public override void Render()
{
if (playerDataManager == null) return;
if (playerDataManager.TryGetPlayerMetaData(Object.InputAuthority, out var metadata))
{
var name = metadata.Name;
var charClass = metadata.Role;
if (nameText != null)
nameText.text = $"{name} ({charClass})";
if (characterIcons != null)
{
for (var i = 0; i < characterIcons.Length; i++)
{
if (characterIcons[i] != null)
characterIcons[i].SetActive(i == (int)charClass); // hiển thị icon tương ứng với class của player
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 70abb536cf50f2948882e913634daedf

View File

@@ -0,0 +1,49 @@
using Fusion;
using UnityEngine;
namespace Baba_yaga
{
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "OnlyScove.Scripts", sourceAssembly: "Opsive.UltimateCharacterController")]
public struct PlayerInputData : INetworkInput, INetworkStruct
{
// Di chuyển (thường là Vector2 cho X/Y hoặc WASD)
public Vector2 Direction;
// Trạng thái chạy nhanh
public NetworkBool sprint;
// Trạng thái nhảy
public NetworkBool jump;
// Góc quay của Camera/Nhân vật (Dùng Quaternion hoặc Vector3 tùy thuộc vào PlanarRotation của bạn)
public Quaternion rot;
// Vector di chuyển đã qua xử lý (UCC CharacterLocomotion.InputVector)
public Vector2 InputVector;
// Vector di chuyển thô (UCC UltimateCharacterLocomotion.RawInputVector)
public Vector2 RawInputVector;
// Độ lệch xoay của nhân vật (UCC CharacterLocomotion.DeltaRotation)
public Vector3 DeltaRotation;
// Trạng thái ngồi (UCC HeightChange ability)
public NetworkBool crouch;
// Trạng thái ngắm bắn (UCC Aim ability)
public NetworkBool aim;
// Trạng thái sử dụng item/tấn công (UCC Use ability)
public NetworkBool use;
// Trạng thái thay đạn (UCC Reload ability)
public NetworkBool reload;
// Góc Pitch của nguồn nhìn (UCC ILookSource.Pitch)
public float LookPitch;
// Hướng nhìn của nhân vật (UCC ILookSource.LookDirection)
public Vector3 LookDirection;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 731ed4a4b6e0ae64c8194463a76646c7