2021-07-23 20:59:44 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2019-04-24 17:17:40 +00:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
using Mirror;
|
2019-04-24 21:02:39 +00:00
|
|
|
|
using SanAndreasUnity.Behaviours;
|
2019-04-24 23:00:31 +00:00
|
|
|
|
using SanAndreasUnity.Utilities;
|
2019-05-05 21:12:42 +00:00
|
|
|
|
using System.Linq;
|
2021-07-23 20:59:44 +00:00
|
|
|
|
using System.Text;
|
2019-04-24 17:17:40 +00:00
|
|
|
|
|
|
|
|
|
namespace SanAndreasUnity.Net
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
public class Player : NetworkBehaviour
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
static List<Player> s_allPlayers = new List<Player>();
|
2021-07-18 19:12:40 +00:00
|
|
|
|
public static Player[] AllPlayersCopy => s_allPlayers.ToArray();
|
|
|
|
|
public static IEnumerable<Player> AllPlayersEnumerable => s_allPlayers;
|
|
|
|
|
public static IReadOnlyList<Player> AllPlayersList => s_allPlayers;
|
2019-04-24 17:17:40 +00:00
|
|
|
|
|
2019-04-24 21:02:39 +00:00
|
|
|
|
/// <summary>Local player.</summary>
|
|
|
|
|
public static Player Local { get; private set; }
|
|
|
|
|
|
2019-04-24 23:00:31 +00:00
|
|
|
|
public static event System.Action<Player> onStart = delegate {};
|
2021-02-22 14:52:46 +00:00
|
|
|
|
public static event System.Action<Player> onDisable = delegate {};
|
2019-04-24 23:00:31 +00:00
|
|
|
|
|
2019-04-24 22:04:14 +00:00
|
|
|
|
[SyncVar(hook=nameof(OnOwnedGameObjectChanged))] GameObject m_ownedGameObject;
|
|
|
|
|
Ped m_ownedPed;
|
2019-04-24 21:02:39 +00:00
|
|
|
|
//public GameObject OwnedGameObject { get { return m_ownedGameObject; } internal set { m_ownedGameObject = value; } }
|
2019-04-24 22:04:14 +00:00
|
|
|
|
public Ped OwnedPed { get { return m_ownedPed; } internal set { m_ownedPed = value; m_ownedGameObject = value != null ? value.gameObject : null; } }
|
2019-04-24 17:39:38 +00:00
|
|
|
|
|
2021-07-23 20:59:44 +00:00
|
|
|
|
[SyncVar]
|
|
|
|
|
private string m_net_playerName = string.Empty;
|
|
|
|
|
public string PlayerName
|
|
|
|
|
{
|
|
|
|
|
get => m_net_playerName;
|
|
|
|
|
private set => m_net_playerName = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static readonly int MaxPlayerNameLength = 20;
|
|
|
|
|
public static readonly int MinPlayerNameLength = 2;
|
|
|
|
|
public static readonly string DefaultPlayerName = "Player";
|
|
|
|
|
|
2022-04-03 17:01:00 +00:00
|
|
|
|
public string DescriptionForLogging => $"(netId={this.netId}, addr={this.CachedIpAddress})";
|
2019-07-09 00:37:49 +00:00
|
|
|
|
|
2022-04-02 18:15:08 +00:00
|
|
|
|
private readonly SyncDictionary<string, string> m_syncDictionary = new SyncDictionary<string, string>();
|
2021-02-19 20:45:54 +00:00
|
|
|
|
public SyncedBag ExtraData { get; }
|
2019-04-24 17:17:40 +00:00
|
|
|
|
|
2022-04-03 17:01:00 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// We cache IP address of the client, because it's sometimes not available (eg. in destroy callbacks).
|
|
|
|
|
/// </summary>
|
|
|
|
|
public string CachedIpAddress { get; private set; } = string.Empty;
|
|
|
|
|
|
2022-04-21 18:54:33 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Is this player a server admin ? Admins have full control of server.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool IsServerAdmin { get; set; } = false;
|
|
|
|
|
|
2019-05-05 21:12:42 +00:00
|
|
|
|
|
2021-02-19 20:45:54 +00:00
|
|
|
|
Player()
|
|
|
|
|
{
|
|
|
|
|
ExtraData = new SyncedBag(m_syncDictionary);
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-05 21:12:42 +00:00
|
|
|
|
public static Player GetOwningPlayer(Ped ped)
|
|
|
|
|
{
|
|
|
|
|
if (null == ped)
|
|
|
|
|
return null;
|
|
|
|
|
return AllPlayersEnumerable.FirstOrDefault(p => p.OwnedPed == ped);
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-23 20:59:44 +00:00
|
|
|
|
private void Awake()
|
|
|
|
|
{
|
|
|
|
|
if (NetStatus.IsServer)
|
|
|
|
|
{
|
|
|
|
|
// assign default player name
|
|
|
|
|
this.PlayerName = GeneratePlayerName(DefaultPlayerName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-24 17:17:40 +00:00
|
|
|
|
void OnEnable()
|
|
|
|
|
{
|
|
|
|
|
s_allPlayers.Add(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OnDisable()
|
|
|
|
|
{
|
|
|
|
|
s_allPlayers.Remove(this);
|
2019-04-28 14:03:54 +00:00
|
|
|
|
|
|
|
|
|
// kill player's ped
|
|
|
|
|
if (NetStatus.IsServer)
|
|
|
|
|
{
|
|
|
|
|
if (this.OwnedPed)
|
|
|
|
|
Destroy(this.OwnedPed.gameObject);
|
|
|
|
|
}
|
2019-04-28 14:09:58 +00:00
|
|
|
|
|
2021-02-22 14:52:46 +00:00
|
|
|
|
F.InvokeEventExceptionSafe(onDisable, this);
|
|
|
|
|
|
2022-04-03 17:01:00 +00:00
|
|
|
|
// log some info about this
|
|
|
|
|
if (!this.isLocalPlayer)
|
|
|
|
|
Debug.LogFormat("Player {0} disconnected, time: {1}", this.DescriptionForLogging, F.CurrentDateForLogging);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnStartServer()
|
|
|
|
|
{
|
|
|
|
|
base.OnStartServer();
|
|
|
|
|
|
|
|
|
|
this.CachedIpAddress = this.connectionToClient.address;
|
2019-04-24 17:17:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-24 22:04:14 +00:00
|
|
|
|
public override void OnStartClient()
|
|
|
|
|
{
|
|
|
|
|
base.OnStartClient();
|
|
|
|
|
|
|
|
|
|
if (this.isServer)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_ownedPed = m_ownedGameObject != null ? m_ownedGameObject.GetComponent<Ped>() : null;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-24 21:02:39 +00:00
|
|
|
|
public override void OnStartLocalPlayer()
|
|
|
|
|
{
|
|
|
|
|
base.OnStartLocalPlayer();
|
|
|
|
|
Local = this;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-24 17:17:40 +00:00
|
|
|
|
void Start()
|
|
|
|
|
{
|
2019-04-28 14:24:33 +00:00
|
|
|
|
// log some info
|
2019-07-09 00:46:45 +00:00
|
|
|
|
if (!this.isLocalPlayer)
|
2019-07-09 00:37:49 +00:00
|
|
|
|
Debug.LogFormat("Player {0} connected, time: {1}", this.DescriptionForLogging, F.CurrentDateForLogging);
|
2019-04-28 14:24:33 +00:00
|
|
|
|
|
2019-04-24 23:00:31 +00:00
|
|
|
|
F.InvokeEventExceptionSafe(onStart, this);
|
2019-04-24 17:17:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-04-02 18:15:08 +00:00
|
|
|
|
void OnOwnedGameObjectChanged(GameObject oldGo, GameObject newGo)
|
2019-04-24 22:04:14 +00:00
|
|
|
|
{
|
2019-04-25 23:57:20 +00:00
|
|
|
|
Debug.LogFormat("Owned game object changed for player (net id {0})", this.netId);
|
|
|
|
|
|
2019-04-24 22:04:14 +00:00
|
|
|
|
if (this.isServer)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_ownedGameObject = newGo;
|
|
|
|
|
|
|
|
|
|
m_ownedPed = m_ownedGameObject != null ? m_ownedGameObject.GetComponent<Ped>() : null;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-09 00:28:09 +00:00
|
|
|
|
void Update()
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
// Telepathy does not detect dead connections, so we'll have to detect them ourselves
|
|
|
|
|
if (NetStatus.IsServer && !this.isLocalPlayer)
|
|
|
|
|
{
|
|
|
|
|
if (Time.time - this.connectionToClient.lastMessageTime > 6f)
|
|
|
|
|
{
|
|
|
|
|
// disconnect client
|
2019-07-09 00:41:08 +00:00
|
|
|
|
Debug.LogFormat("Detected dead connection for player {0}", this.DescriptionForLogging);
|
2019-07-09 00:28:09 +00:00
|
|
|
|
this.connectionToClient.Disconnect();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-20 20:53:36 +00:00
|
|
|
|
public void Disconnect()
|
|
|
|
|
{
|
|
|
|
|
this.connectionToClient.Disconnect();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Player GetByNetId(uint netId)
|
|
|
|
|
{
|
|
|
|
|
var go = NetManager.GetNetworkObjectById(netId);
|
|
|
|
|
return go != null ? go.GetComponent<Player>() : null;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-23 20:59:44 +00:00
|
|
|
|
public static Player GetByName(string name)
|
|
|
|
|
{
|
|
|
|
|
return s_allPlayers.Find(p => p.PlayerName == name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string ValidatePlayerName(string playerName)
|
|
|
|
|
{
|
|
|
|
|
if (null == playerName)
|
|
|
|
|
return DefaultPlayerName;
|
|
|
|
|
|
|
|
|
|
if (playerName.Length < MinPlayerNameLength)
|
|
|
|
|
return DefaultPlayerName;
|
|
|
|
|
|
|
|
|
|
if (playerName.Length > MaxPlayerNameLength)
|
|
|
|
|
playerName = playerName.Substring(0, MaxPlayerNameLength);
|
|
|
|
|
|
|
|
|
|
var sb = new StringBuilder(playerName);
|
|
|
|
|
|
|
|
|
|
// remove tags
|
|
|
|
|
sb.Replace("<", "< "); // the easiest way
|
|
|
|
|
|
|
|
|
|
sb.Replace('\r', ' ');
|
|
|
|
|
sb.Replace('\n', ' ');
|
|
|
|
|
sb.Replace('\t', ' ');
|
|
|
|
|
|
|
|
|
|
// trim has to be done after other operations, because they can produce whitespaces
|
|
|
|
|
playerName = sb.ToString().Trim();
|
|
|
|
|
|
|
|
|
|
if (playerName.Length < MinPlayerNameLength)
|
|
|
|
|
return DefaultPlayerName;
|
|
|
|
|
|
|
|
|
|
return playerName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string GeneratePlayerName(string playerName)
|
|
|
|
|
{
|
|
|
|
|
playerName = ValidatePlayerName(playerName);
|
|
|
|
|
|
|
|
|
|
// check if player with this name already exists
|
|
|
|
|
|
|
|
|
|
if (GetByName(playerName) == null)
|
|
|
|
|
return playerName;
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i < 10000; i++)
|
|
|
|
|
{
|
|
|
|
|
string newName = playerName + " (" + i + ")";
|
|
|
|
|
if (null == GetByName(newName))
|
|
|
|
|
return newName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw new Exception("Failed to generate player name");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void RequestNameChange(string newName) => this.CmdRequestNameChange(newName);
|
|
|
|
|
|
|
|
|
|
[Command]
|
|
|
|
|
private void CmdRequestNameChange(string newName)
|
|
|
|
|
{
|
|
|
|
|
this.PlayerName = GeneratePlayerName(newName);
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-24 17:17:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|