You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

209 lines
6.5 KiB
C#

using System;
using Unity.BossRoom.Gameplay.GameplayObjects.Character;
using Unity.BossRoom.Infrastructure;
using Unity.Netcode;
using UnityEngine;
namespace Unity.BossRoom.Gameplay.GameplayObjects
{
/// <summary>
/// This script handles the logic for a simple "single-shot" breakable object like a pot, or
/// other stationary items with arbitrary amounts of HP, like spawner-portal crystals.
/// Visualization for these objects works by swapping a "broken" prefab at the moment of breakage. The broken prefab
/// then handles the pesky details of actually falling apart.
/// </summary>
public class Breakable : NetworkBehaviour, IDamageable, ITargetable
{
[Header("Server Logic")]
[SerializeField]
[Tooltip("If left blank, this breakable effectively has 1 hit point")]
IntVariable m_MaxHealth;
[SerializeField]
[Tooltip("If this breakable will have hit points, add a NetworkHealthState component to this GameObject")]
NetworkHealthState m_NetworkHealthState;
[SerializeField]
Collider m_Collider;
[SerializeField]
[Tooltip("Indicate which special interaction behaviors are needed for this breakable")]
IDamageable.SpecialDamageFlags m_SpecialDamageFlags;
[Header("Visualization")]
[SerializeField]
private GameObject m_BrokenPrefab;
[SerializeField]
[Tooltip("If set, will be used instead of BrokenPrefab when new players join, skipping transition effects.")]
private GameObject m_PrebrokenPrefab;
[SerializeField]
[Tooltip("We use this transform's position and rotation when creating the prefab. (Defaults to self)")]
private Transform m_BrokenPrefabPos;
[SerializeField]
private GameObject[] m_UnbrokenGameObjects;
/// <summary>
/// Is the item broken or not?
/// </summary>
public NetworkVariable<bool> IsBroken;
public bool IsNpc { get { return true; } }
public bool IsValidTarget { get { return !IsBroken.Value; } }
private GameObject m_CurrentBrokenVisualization;
public override void OnNetworkSpawn()
{
if (IsServer)
{
if (m_MaxHealth && m_NetworkHealthState)
{
m_NetworkHealthState.HitPoints.Value = m_MaxHealth.Value;
}
}
if (IsClient)
{
IsBroken.OnValueChanged += OnBreakableStateChanged;
if (IsBroken.Value == true)
{
PerformBreakVisualization(true);
}
}
}
public override void OnNetworkDespawn()
{
if (IsClient)
{
IsBroken.OnValueChanged -= OnBreakableStateChanged;
}
}
public void ReceiveHP(ServerCharacter inflicter, int HP)
{
if (HP < 0)
{
if (inflicter && !inflicter.IsNpc)
{
bool isNotDamagedByPlayers = (GetSpecialDamageFlags() & IDamageable.SpecialDamageFlags.NotDamagedByPlayers) == IDamageable.SpecialDamageFlags.NotDamagedByPlayers;
if (isNotDamagedByPlayers)
{
// a player tried to damage us, but we are immune to player damage!
return;
}
}
if (m_NetworkHealthState)
{
m_NetworkHealthState.HitPoints.Value =
Mathf.Clamp(m_NetworkHealthState.HitPoints.Value + HP, 0, m_MaxHealth.Value);
if (m_NetworkHealthState.HitPoints.Value <= 0)
{
Break();
}
}
else
{
//any damage at all is enough to slay me.
Break();
}
}
}
private void Break()
{
IsBroken.Value = true;
if (m_Collider)
m_Collider.enabled = false;
}
public void Unbreak()
{
IsBroken.Value = false;
if (m_Collider)
m_Collider.enabled = true;
if (m_MaxHealth && m_NetworkHealthState)
m_NetworkHealthState.HitPoints.Value = m_MaxHealth.Value;
}
public IDamageable.SpecialDamageFlags GetSpecialDamageFlags()
{
return m_SpecialDamageFlags;
}
public bool IsDamageable()
{
// you can damage this breakable until it's broken!
return !IsBroken.Value;
}
private void OnBreakableStateChanged(bool wasBroken, bool isBroken)
{
if (!wasBroken && isBroken)
{
PerformBreakVisualization(false);
}
else if (wasBroken && !isBroken)
{
PerformUnbreakVisualization();
}
}
private void PerformBreakVisualization(bool onStart)
{
foreach (var unbrokenGameObject in m_UnbrokenGameObjects)
{
if (unbrokenGameObject)
{
unbrokenGameObject.SetActive(false);
}
}
if (m_CurrentBrokenVisualization)
Destroy(m_CurrentBrokenVisualization); // just a safety check, should be null when we get here
GameObject brokenPrefab = (onStart && m_PrebrokenPrefab != null) ? m_PrebrokenPrefab : m_BrokenPrefab;
if (brokenPrefab)
{
m_CurrentBrokenVisualization = Instantiate(brokenPrefab, m_BrokenPrefabPos.position, m_BrokenPrefabPos.rotation, transform);
}
}
private void PerformUnbreakVisualization()
{
if (m_CurrentBrokenVisualization)
{
Destroy(m_CurrentBrokenVisualization);
}
foreach (var unbrokenGameObject in m_UnbrokenGameObjects)
{
if (unbrokenGameObject)
{
unbrokenGameObject.SetActive(true);
}
}
}
#if UNITY_EDITOR
private void OnValidate()
{
if (!m_Collider)
m_Collider = GetComponent<Collider>();
if (!m_NetworkHealthState)
m_NetworkHealthState = GetComponent<NetworkHealthState>();
if (!m_BrokenPrefabPos)
m_BrokenPrefabPos = transform;
}
#endif
}
}