Chat Refactor + Spawn Points Y Axis Fix
This commit is contained in:
165
Assets/Scripts/Chat/ChatAuthenticator.cs
Normal file
165
Assets/Scripts/Chat/ChatAuthenticator.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using Mirror;
|
||||
using Mirror.Examples.Chat;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class ChatAuthenticator : NetworkAuthenticator
|
||||
{
|
||||
readonly HashSet<NetworkConnectionToClient> connectionsPendingDisconnect = new HashSet<NetworkConnectionToClient>();
|
||||
internal static readonly HashSet<string> playerNames = new HashSet<string>();
|
||||
|
||||
[Header("Client Username")]
|
||||
public string playerName;
|
||||
|
||||
#region Messages;
|
||||
|
||||
public struct AuthRequestMessage : NetworkMessage
|
||||
{
|
||||
public string authUsername;
|
||||
}
|
||||
|
||||
public struct AuthResponseMessage : NetworkMessage
|
||||
{
|
||||
public byte code;
|
||||
public string message;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Server
|
||||
|
||||
[UnityEngine.RuntimeInitializeOnLoadMethod]
|
||||
static void ResetStatics()
|
||||
{
|
||||
playerNames.Clear();
|
||||
}
|
||||
|
||||
public override void OnStartServer()
|
||||
{
|
||||
NetworkServer.RegisterHandler<AuthRequestMessage>(OnAuthRequestMessage, false);
|
||||
}
|
||||
|
||||
public override void OnStopServer()
|
||||
{
|
||||
NetworkServer.UnregisterHandler<AuthRequestMessage>();
|
||||
}
|
||||
|
||||
public override void OnServerAuthenticate(NetworkConnectionToClient conn)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnAuthRequestMessage(NetworkConnectionToClient conn, AuthRequestMessage msg)
|
||||
{
|
||||
Debug.Log($"Authentication Request Username: {msg.authUsername}");
|
||||
|
||||
if (connectionsPendingDisconnect.Contains(conn)) return;
|
||||
|
||||
if (!playerNames.Contains(msg.authUsername))
|
||||
{
|
||||
playerNames.Add(msg.authUsername);
|
||||
|
||||
conn.authenticationData = msg.authUsername;
|
||||
|
||||
AuthResponseMessage authResponseMessage = new AuthResponseMessage
|
||||
{
|
||||
code = 100,
|
||||
message = "Success"
|
||||
};
|
||||
|
||||
conn.Send(authResponseMessage);
|
||||
|
||||
ServerAccept(conn);
|
||||
}
|
||||
else
|
||||
{
|
||||
connectionsPendingDisconnect.Add(conn);
|
||||
|
||||
AuthResponseMessage authResponseMessage = new AuthResponseMessage
|
||||
{
|
||||
code = 200,
|
||||
message = "Username already in use... try again"
|
||||
};
|
||||
|
||||
conn.Send(authResponseMessage);
|
||||
|
||||
conn.isAuthenticated = false;
|
||||
|
||||
StartCoroutine(DelayedDisconnect(conn, 1f));
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator DelayedDisconnect(NetworkConnectionToClient conn, float waitTime)
|
||||
{
|
||||
yield return new WaitForSeconds(waitTime);
|
||||
|
||||
ServerReject(conn);
|
||||
|
||||
yield return null;
|
||||
|
||||
connectionsPendingDisconnect.Remove(conn);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Client
|
||||
|
||||
public void SetPlayerName(string username)
|
||||
{
|
||||
playerName = username;
|
||||
if (LoginUI.instance != null && LoginUI.instance.errorText != null)
|
||||
{
|
||||
LoginUI.instance.errorText.text = string.Empty;
|
||||
LoginUI.instance.errorText.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnStartClient()
|
||||
{
|
||||
NetworkClient.RegisterHandler<AuthResponseMessage>(OnAuthResponseMessage, false);
|
||||
}
|
||||
|
||||
public override void OnStopClient()
|
||||
{
|
||||
NetworkClient.UnregisterHandler<AuthResponseMessage>();
|
||||
}
|
||||
|
||||
public override void OnClientAuthenticate()
|
||||
{
|
||||
NetworkClient.Send(new AuthRequestMessage { authUsername = playerName });
|
||||
}
|
||||
|
||||
public void OnAuthResponseMessage(AuthResponseMessage msg)
|
||||
{
|
||||
if (msg.code == 100)
|
||||
{
|
||||
Debug.Log($"Authentication Response: {msg.code} {msg.message}");
|
||||
|
||||
ClientAccept();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"Authentication Response: {msg.code} {msg.message}");
|
||||
|
||||
NetworkManager.singleton.StopHost();
|
||||
|
||||
LoginUI.instance.errorText.text = msg.message;
|
||||
LoginUI.instance.errorText.gameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Chat/ChatAuthenticator.cs.meta
Normal file
2
Assets/Scripts/Chat/ChatAuthenticator.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23712bd1913985b419020af1e3c9479f
|
||||
97
Assets/Scripts/Chat/ChatNetworkManager.cs
Normal file
97
Assets/Scripts/Chat/ChatNetworkManager.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using UnityEngine;
|
||||
using Mirror;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[AddComponentMenu("")]
|
||||
public class ChatNetworkManager : NetworkManager
|
||||
{
|
||||
[SerializeField] private GameObject chatUIPrefab;
|
||||
|
||||
// Server-side authoritative store of chat messages
|
||||
private readonly List<string> chatMessages = new();
|
||||
|
||||
// Server-side map of connections to player names (moved from the old ChatUI)
|
||||
internal static readonly Dictionary<NetworkConnectionToClient, string> connNames = new();
|
||||
|
||||
public void SetHostname(string hostname)
|
||||
{
|
||||
networkAddress = hostname;
|
||||
}
|
||||
|
||||
public override void OnServerDisconnect(NetworkConnectionToClient conn)
|
||||
{
|
||||
if (conn.authenticationData != null)
|
||||
ChatAuthenticator.playerNames.Remove((string)conn.authenticationData);
|
||||
|
||||
connNames.Remove(conn);
|
||||
|
||||
base.OnServerDisconnect(conn);
|
||||
}
|
||||
|
||||
public override void OnClientDisconnect()
|
||||
{
|
||||
base.OnClientDisconnect();
|
||||
|
||||
// Destroy ChatUI instance when disconnecting
|
||||
ChatUI existingChatUI = FindFirstObjectByType<ChatUI>();
|
||||
if (existingChatUI != null)
|
||||
{
|
||||
Destroy(existingChatUI.gameObject);
|
||||
}
|
||||
|
||||
// Show LoginUI again
|
||||
if (LoginUI.instance != null)
|
||||
{
|
||||
LoginUI.instance.gameObject.SetActive(true);
|
||||
LoginUI.instance.usernameInput.text = "";
|
||||
LoginUI.instance.usernameInput.ActivateInputField();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnClientConnect()
|
||||
{
|
||||
base.OnClientConnect();
|
||||
|
||||
Debug.Log("ChatNetworkManager: OnClientConnect called");
|
||||
|
||||
// Spawn ChatUI prefab locally when client successfully connects
|
||||
if (chatUIPrefab == null)
|
||||
{
|
||||
Debug.LogError("ChatNetworkManager: ChatUI prefab is not assigned in the inspector!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if ChatUI already exists to avoid duplicates
|
||||
ChatUI existingChatUI = FindFirstObjectByType<ChatUI>();
|
||||
if (existingChatUI == null)
|
||||
{
|
||||
GameObject chatUIInstance = Instantiate(chatUIPrefab);
|
||||
if (chatUIInstance == null)
|
||||
{
|
||||
Debug.LogError("ChatNetworkManager: Failed to instantiate ChatUI prefab!");
|
||||
return;
|
||||
}
|
||||
|
||||
chatUIInstance.SetActive(true);
|
||||
}
|
||||
|
||||
// Hide LoginUI when successfully connected
|
||||
if (LoginUI.instance != null)
|
||||
{
|
||||
LoginUI.instance.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
[Server]
|
||||
public void AddServerMessage(string message)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message)) return;
|
||||
chatMessages.Add(message);
|
||||
}
|
||||
|
||||
[Server]
|
||||
public string[] GetHistory()
|
||||
{
|
||||
return chatMessages.ToArray();
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Chat/ChatNetworkManager.cs.meta
Normal file
2
Assets/Scripts/Chat/ChatNetworkManager.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe0d94c74152c1e45959c30f3bc4d94f
|
||||
107
Assets/Scripts/Chat/ChatUI.cs
Normal file
107
Assets/Scripts/Chat/ChatUI.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
// Client-only UI. DO NOT add a NetworkIdentity to this prefab.
|
||||
public class ChatUI : MonoBehaviour
|
||||
{
|
||||
[Header("UI Elements")]
|
||||
[SerializeField] private TextMeshProUGUI chatHistory;
|
||||
[SerializeField] private Scrollbar scrollbar;
|
||||
[SerializeField] private TMP_InputField chatMessage;
|
||||
[SerializeField] private Button sendButton;
|
||||
|
||||
internal static string localPlayerName;
|
||||
|
||||
private readonly List<string> lines = new();
|
||||
private Player localPlayer;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (chatMessage != null)
|
||||
{
|
||||
chatMessage.onValueChanged.AddListener(ToggleButton);
|
||||
chatMessage.onSubmit.AddListener(OnInputSubmit);
|
||||
}
|
||||
}
|
||||
|
||||
// Called by Player.OnStartLocalPlayer to connect the UI to the player's network methods.
|
||||
public void Initialize(Player player)
|
||||
{
|
||||
localPlayer = player;
|
||||
}
|
||||
|
||||
public void SetHistory(string[] history)
|
||||
{
|
||||
lines.Clear();
|
||||
if (history != null && history.Length > 0)
|
||||
lines.AddRange(history);
|
||||
|
||||
RefreshVisual();
|
||||
}
|
||||
|
||||
// Called by Player.RpcReceiveChat / Player.TargetReceiveHistory
|
||||
public void AddMessage(string message)
|
||||
{
|
||||
lines.Add(message);
|
||||
if (chatHistory != null)
|
||||
chatHistory.text += message + "\n";
|
||||
|
||||
StartCoroutine(ScrollNextFrame());
|
||||
}
|
||||
|
||||
IEnumerator ScrollNextFrame()
|
||||
{
|
||||
yield return null;
|
||||
yield return null;
|
||||
if (scrollbar != null)
|
||||
scrollbar.value = 0;
|
||||
}
|
||||
|
||||
void RefreshVisual()
|
||||
{
|
||||
if (chatHistory == null) return;
|
||||
|
||||
chatHistory.text = string.Empty;
|
||||
foreach (var l in lines)
|
||||
chatHistory.text += l + "\n";
|
||||
|
||||
StartCoroutine(ScrollNextFrame());
|
||||
}
|
||||
|
||||
public void ToggleButton(string input)
|
||||
{
|
||||
if (sendButton != null)
|
||||
sendButton.interactable = !string.IsNullOrWhiteSpace(input);
|
||||
}
|
||||
|
||||
private void OnInputSubmit(string input)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(input))
|
||||
SendMessage();
|
||||
}
|
||||
|
||||
public void SendMessage()
|
||||
{
|
||||
if (localPlayer == null)
|
||||
{
|
||||
Debug.LogWarning("ChatUI: local player not set; cannot send chat.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(chatMessage?.text)) return;
|
||||
|
||||
localPlayer.SendChatToServer(chatMessage.text.Trim());
|
||||
|
||||
chatMessage.text = string.Empty;
|
||||
chatMessage.ActivateInputField();
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (chatMessage != null)
|
||||
chatMessage.onValueChanged.RemoveListener(ToggleButton);
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Chat/ChatUI.cs.meta
Normal file
2
Assets/Scripts/Chat/ChatUI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 527a619416f29bd4c8493418ac26af76
|
||||
61
Assets/Scripts/Chat/LoginUI.cs
Normal file
61
Assets/Scripts/Chat/LoginUI.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Mirror;
|
||||
using TMPro;
|
||||
|
||||
public class LoginUI : MonoBehaviour
|
||||
{
|
||||
[Header("UI Elements")]
|
||||
[SerializeField] internal TMP_InputField usernameInput;
|
||||
[SerializeField] internal Button clientButton;
|
||||
[SerializeField] internal Text errorText;
|
||||
|
||||
public static LoginUI instance;
|
||||
|
||||
string originalNetworkAddress;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
instance = this;
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(NetworkManager.singleton.networkAddress))
|
||||
NetworkManager.singleton.networkAddress = "localhost";
|
||||
|
||||
originalNetworkAddress = NetworkManager.singleton.networkAddress;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(NetworkManager.singleton.networkAddress))
|
||||
NetworkManager.singleton.networkAddress = originalNetworkAddress;
|
||||
}
|
||||
|
||||
public void ToggleButton(string username)
|
||||
{
|
||||
clientButton.interactable = !string.IsNullOrWhiteSpace(username);
|
||||
}
|
||||
|
||||
public void OnClientButton()
|
||||
{
|
||||
var username = usernameInput.text.Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
if (errorText != null) { errorText.text = "Enter a username"; errorText.gameObject.SetActive(true); }
|
||||
return;
|
||||
}
|
||||
|
||||
// set the name on the ChatAuthenticator (if present)
|
||||
var auth = NetworkManager.singleton.GetComponent<ChatAuthenticator>();
|
||||
if (auth != null)
|
||||
auth.SetPlayerName(username);
|
||||
else
|
||||
Debug.LogWarning("LoginUI: ChatAuthenticator not found on NetworkManager.");
|
||||
|
||||
// start client
|
||||
NetworkManager.singleton.StartClient();
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Chat/LoginUI.cs.meta
Normal file
2
Assets/Scripts/Chat/LoginUI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2e50c9434c920c44ad6d25327815d5c
|
||||
88
Assets/Scripts/Chat/Player.cs
Normal file
88
Assets/Scripts/Chat/Player.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using UnityEngine;
|
||||
using Mirror;
|
||||
|
||||
// Networked player. This is the authority object used to run Commands.
|
||||
public class Player : NetworkBehaviour
|
||||
{
|
||||
[SyncVar(hook = nameof(OnNameChanged))]
|
||||
public string playerName;
|
||||
|
||||
public override void OnStartServer()
|
||||
{
|
||||
playerName = (string)connectionToClient.authenticationData;
|
||||
}
|
||||
|
||||
public override void OnStartLocalPlayer()
|
||||
{
|
||||
// initialize local UI
|
||||
var ui = FindFirstObjectByType<ChatUI>();
|
||||
if (ui != null)
|
||||
{
|
||||
ui.Initialize(this);
|
||||
ChatUI.localPlayerName = playerName;
|
||||
}
|
||||
|
||||
// request history from server
|
||||
if (isLocalPlayer)
|
||||
CmdRequestHistory();
|
||||
}
|
||||
|
||||
void OnNameChanged(string oldName, string newName)
|
||||
{
|
||||
if (isLocalPlayer)
|
||||
ChatUI.localPlayerName = newName;
|
||||
}
|
||||
|
||||
// Called by the local ChatUI to send a message
|
||||
public void SendChatToServer(string message)
|
||||
{
|
||||
if (!isLocalPlayer) return;
|
||||
if (string.IsNullOrWhiteSpace(message)) return;
|
||||
CmdSendChat(message);
|
||||
}
|
||||
|
||||
// Runs on server: store message and broadcast to all clients
|
||||
[Command]
|
||||
private void CmdSendChat(string message)
|
||||
{
|
||||
var manager = NetworkManager.singleton as ChatNetworkManager;
|
||||
manager?.AddServerMessage($"{playerName}: {message}");
|
||||
|
||||
RpcReceiveChat(playerName, message);
|
||||
}
|
||||
|
||||
// Runs on all clients: update local ChatUI
|
||||
[ClientRpc]
|
||||
private void RpcReceiveChat(string senderName, string message)
|
||||
{
|
||||
var ui = FindFirstObjectByType<ChatUI>();
|
||||
if (ui == null) return;
|
||||
|
||||
var prettyMessage = senderName == ChatUI.localPlayerName ?
|
||||
$"<color=red>{senderName}:</color> {message}" :
|
||||
$"<color=blue>{senderName}:</color> {message}";
|
||||
|
||||
ui.AddMessage(prettyMessage);
|
||||
}
|
||||
|
||||
// Request history from server; server will reply with TargetReceiveHistory
|
||||
[Command]
|
||||
private void CmdRequestHistory()
|
||||
{
|
||||
var manager = NetworkManager.singleton as ChatNetworkManager;
|
||||
if (manager == null) return;
|
||||
|
||||
var history = manager.GetHistory();
|
||||
TargetReceiveHistory(connectionToClient, history);
|
||||
}
|
||||
|
||||
// Sent only to the requesting client
|
||||
[TargetRpc]
|
||||
private void TargetReceiveHistory(NetworkConnection target, string[] history)
|
||||
{
|
||||
var ui = FindFirstObjectByType<ChatUI>();
|
||||
if (ui == null) return;
|
||||
|
||||
ui.SetHistory(history);
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Chat/Player.cs.meta
Normal file
2
Assets/Scripts/Chat/Player.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3732139ba57ef74daf2a04c11b62cfa
|
||||
Reference in New Issue
Block a user