using System.Collections; using Unity.Netcode; using UnityEngine; /// /// A block that can be mined by players. /// Uses HealthComponent for health management and implements IDamageable. /// [RequireComponent(typeof(HealthComponent))] public class MineableBlock : NetworkBehaviour, IDamageable { [Header("Drop Settings")] [SerializeField] private ItemData dropItemData; [SerializeField] private GameObject genericDropPrefab; [Header("Visuals")] private Outline _outline; private Vector3 _originalPos; [Header("Shake Settings")] [SerializeField] private float shakeDuration = 0.15f; [SerializeField] private float shakeMagnitude = 0.1f; private Coroutine _shakeCoroutine; private Color _originalColor; private float _lastVisibleTime; private const float VisibilityThreshold = 0.25f; [Header("Fog Settings")] [Range(0f, 1f)] [SerializeField] private float darkIntensity = 0.2f; private MaterialPropertyBlock _propBlock; private NetworkVariable isDiscovered = new NetworkVariable(false); private MeshRenderer _renderer; private HealthComponent _health; #region IDamageable Implementation public float CurrentHealth => _health != null ? _health.CurrentHealth : 0f; public float MaxHealth => _health != null ? _health.MaxHealth : 0f; public bool IsAlive => _health != null && _health.IsAlive; public void TakeDamage(float amount) { TakeDamage(new DamageInfo(amount, DamageType.Mining)); } public void TakeDamage(DamageInfo damageInfo) { if (_health != null) { _health.TakeDamage(damageInfo); } } #endregion void Awake() { _health = GetComponent(); _renderer = GetComponentInChildren(); _propBlock = new MaterialPropertyBlock(); // Start hidden if (_renderer != null) _renderer.enabled = false; if (_renderer != null && _renderer.sharedMaterial != null) { _originalColor = _renderer.sharedMaterial.HasProperty("_BaseColor") ? _renderer.sharedMaterial.GetColor("_BaseColor") : _renderer.sharedMaterial.GetColor("_Color"); } // Find outline component _outline = GetComponentInChildren(); _originalPos = transform.localPosition; if (_outline != null) { _outline.enabled = false; } // Subscribe to health events if (_health != null) { _health.OnDamaged += HandleDamaged; _health.OnDeath += HandleDeath; } } void OnDestroy() { if (_health != null) { _health.OnDamaged -= HandleDamaged; _health.OnDeath -= HandleDeath; } } public override void OnNetworkSpawn() { // Update visuals when discovered state syncs UpdateVisuals(isDiscovered.Value); isDiscovered.OnValueChanged += (oldVal, newVal) => { if (newVal) UpdateState(); }; } void Update() { // Check if block should be discovered based on player distance if (!isDiscovered.Value && NetworkManager.Singleton != null && NetworkManager.Singleton.LocalClient != null && NetworkManager.Singleton.LocalClient.PlayerObject != null) { float dist = Vector3.Distance(transform.position, NetworkManager.Singleton.LocalClient.PlayerObject.transform.position); if (FogOfWarManager.Instance != null && dist < FogOfWarManager.Instance.revealRadius) { RequestRevealServerRpc(); } } // Update renderer visibility if (_renderer != null) { _renderer.enabled = isDiscovered.Value; } UpdateState(); } [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] private void RequestRevealServerRpc() { isDiscovered.Value = true; } private void UpdateState() { if (_renderer == null || !_renderer.enabled) return; bool isCurrentlyVisible = (Time.time - _lastVisibleTime) < VisibilityThreshold; _renderer.GetPropertyBlock(_propBlock); Color targetColor = isCurrentlyVisible ? _originalColor : _originalColor * darkIntensity; _propBlock.SetColor("_BaseColor", targetColor); _renderer.SetPropertyBlock(_propBlock); } /// /// Reveal this block (called by server). /// public void RevealBlock() { if (IsServer && !isDiscovered.Value) { isDiscovered.Value = true; } } /// /// Update local visibility for fog of war. /// public void UpdateLocalVisibility() { _lastVisibleTime = Time.time; } private void HandleDamaged(DamageInfo info) { // Play hit effect on all clients PlayHitEffectClientRpc(); } private void HandleDeath() { if (IsServer) { DropItem(); GetComponent().Despawn(); } } /// /// Set highlight state for targeting feedback. /// public void SetHighlight(bool isOn) { if (_outline != null) { _outline.enabled = isOn; } } /// /// Play hit visual effect on all clients. /// [ClientRpc] public void PlayHitEffectClientRpc() { if (_shakeCoroutine != null) StopCoroutine(_shakeCoroutine); _shakeCoroutine = StartCoroutine(ShakeRoutine()); } private IEnumerator ShakeRoutine() { float elapsed = 0.0f; while (elapsed < shakeDuration) { Vector3 randomOffset = Random.insideUnitSphere * shakeMagnitude; transform.localPosition = _originalPos + randomOffset; elapsed += Time.deltaTime; yield return null; } transform.localPosition = _originalPos; } private void DropItem() { if (!IsServer || dropItemData == null || genericDropPrefab == null) return; GameObject dropObj = Instantiate(genericDropPrefab, transform.position + Vector3.up * 0.5f, Quaternion.identity); NetworkObject netObj = dropObj.GetComponent(); netObj.Spawn(); if (dropObj.TryGetComponent(out var droppedItem)) { droppedItem.Initialize(dropItemData.itemID); } } private void UpdateVisuals(bool discovered) { if (_renderer != null) _renderer.enabled = discovered; if (!discovered && _outline != null) _outline.enabled = false; } /// /// Get the health component for direct access if needed. /// public HealthComponent Health => _health; }