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#
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
|
|
}
|
|
|
|
|
|
}
|
|
|