Files
2026-07-03 17:44:41 +03:00

141 lines
4.5 KiB
C#

using UnityEngine;
using Unity.Netcode;
using Unity.Netcode.Components;
using System;
public class PlayerHealth : NetworkBehaviour
{
[SerializeField] private int maxHealth = 100;
public NetworkVariable<int> currentHealth = new NetworkVariable<int>(
100,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
public NetworkVariable<bool> isDead = new NetworkVariable<bool>(false);
public event Action<int, int> HealthChanged;
public event Action Died;
private bool isServer;
// Короткое окно после респавна, когда вход в яму не засчитывается — страховка на случай,
// если серверная позиция коллайдера не успела выйти из ямы (сетевая задержка).
[SerializeField] private float pitImmunityDuration = 1f;
private float pitImmuneUntil;
public bool IsPitImmune => Time.time < pitImmuneUntil;
public void Init(bool _isServer)
{
isServer = _isServer;
currentHealth.OnValueChanged += OnHealthChanged;
if(isServer)
{
currentHealth.Value = maxHealth;
}
HealthChanged?.Invoke(currentHealth.Value, maxHealth);
}
private void OnHealthChanged(int _previousValue, int _newValue)
{
HealthChanged?.Invoke(_newValue, maxHealth);
if(_newValue <= 0)
{
Die();
}
}
public void TakeDamage(int _damageAmount)
{
if (!isServer) return;
if(isDead.Value) return;
PlayerShield _shield = GetComponent<PlayerShield>();
if (_shield != null && _shield.IsShieldUp) return;
currentHealth.Value = Mathf.Max(0, currentHealth.Value - _damageAmount);
}
public override void OnNetworkDespawn()
{
currentHealth.OnValueChanged -= OnHealthChanged;
}
private void Die()
{
if(!isServer) return;
GetComponent<PlayerShield>()?.SetShieldState(false);
isDead.Value = true;
OnDiedClientRpc();
}
public void Respawn(Vector3 _spawnPosition)
{
if(!isServer) return;
GetComponent<PlayerShield>()?.SetShieldState(false);
isDead.Value = false;
currentHealth.Value = maxHealth;
pitImmuneUntil = Time.time + pitImmunityDuration;
RespawnClientRpc(_spawnPosition);
}
public void PushCurrentHealthValue()
{
HealthChanged?.Invoke(currentHealth.Value, maxHealth);
}
[ClientRpc]
private void OnDiedClientRpc()
{
Debug.Log($"Player {OwnerClientId} died");
SetPhysicsAndVisualsActive(false);
Died?.Invoke();
}
[ClientRpc]
private void RespawnClientRpc(Vector3 _spawnPosition)
{
SetPhysicsAndVisualsActive(true);
// Позицию задаёт ТОЛЬКО владелец — он авторитет над NetworkTransform (Owner authority).
// На сервере/не-владельце трогать transform бессмысленно: NetworkTransform всё равно
// перезапишет его синхронизацией от владельца. Серверную позицию догонит RoundManager
// через WaitForPlayersAtSpawn перед стартом записи (иначе запись стартует в яме).
if (!IsOwner) return;
Teleport(_spawnPosition);
}
// Телепорт без интерполяции. CharacterController кэширует свою позицию,
// поэтому его надо выключить на время установки трансформа.
private void Teleport(Vector3 _spawnPosition)
{
CharacterController _controller = GetComponent<CharacterController>();
if (_controller != null) _controller.enabled = false;
NetworkTransform _netTransform = GetComponent<NetworkTransform>();
if (_netTransform != null)
_netTransform.Teleport(_spawnPosition, transform.rotation, transform.localScale);
else
transform.position = _spawnPosition;
if (_controller != null) _controller.enabled = true;
}
private void SetPhysicsAndVisualsActive(bool _isActive)
{
foreach (Collider collider in GetComponentsInChildren<Collider>())
collider.enabled = _isActive;
foreach (MeshRenderer meshRenderer in GetComponentsInChildren<MeshRenderer>())
meshRenderer.enabled = _isActive;
}
}