Chat Refactor + Spawn Points Y Axis Fix

This commit is contained in:
pelpanagiotis
2026-01-16 08:19:14 +02:00
parent bc6989bf05
commit 71f1523dc7
63 changed files with 17493 additions and 1491 deletions

View 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()
{
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 23712bd1913985b419020af1e3c9479f

View 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();
}
}

View File

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

View 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);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 527a619416f29bd4c8493418ac26af76

View 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();
}
}

View File

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

View 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);
}
}

View File

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