// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using System.Collections.Generic; using UltimateXR.Audio; using UltimateXR.Avatar; using UltimateXR.Core.Caching; using UltimateXR.Haptics; using UltimateXR.Manipulation; using UnityEngine; namespace UltimateXR.Mechanics.Weapons { /// /// Grenade weapon. A grenade inflicts explosive damage to components. /// public class UxrGrenadeWeapon : UxrWeapon, IUxrPrecacheable { #region Inspector Properties/Serialized Fields // General parameters [SerializeField] private UxrGrenadeActivationMode _activationTrigger; [SerializeField] private bool _explodeOnCollision; // Timer [SerializeField] private float _timerSeconds = 3.0f; // Pin [SerializeField] private UxrGrabbableObject _pin; [SerializeField] private UxrAudioSample _audioRemovePin; [SerializeField] private UxrHapticClip _hapticRemovePin = new UxrHapticClip(null, UxrHapticClipType.Click); // Impact [SerializeField] private LayerMask _impactExplosionCollisionMask = ~0; // Explosion [SerializeField] private GameObject[] _explosionPrefabPool; [SerializeField] private float _explosionPrefabLife = 4.0f; // Damage [SerializeField] private float _damageRadius = 10.0f; [SerializeField] private float _damageNear = 10.0f; [SerializeField] private float _damageFar; // Physics [SerializeField] private bool _createPhysicsExplosion; [SerializeField] private float _physicsExplosionForce = 10.0f; #endregion #region Public Types & Data /// /// Gets whether the grenade has been activated and the detonation timer is running. /// public bool IsActivated => _timer >= 0.0f; /// /// Gets the seconds left to explode. If the grenade hasn't been activated yet, it will return /// . /// public float Timer => _timer < 0.0f ? _timerSeconds : _timer; /// /// Gets the seconds it will take for the grenade to explode once it has been activated. /// public float TimerDuration => _timerSeconds; /// /// Gets distance from the explosion the grenade will start inflicting damage. /// public float DamageRadius => _damageRadius; /// /// Gets the maximum damage, applied at the very point of the explosion. /// public float DamageNear => _damageNear; /// /// Gets the minimum damage, applied at distance. /// public float DamageFar => _damageFar; #endregion #region Implicit IUxrPrecacheable /// public IEnumerable PrecachedInstances => _explosionPrefabPool; #endregion #region Public Methods /// /// Starts the detonation timer. /// public void ActivateTimer() { _timer = _timerSeconds; } /// /// Freezes or resumes the detonation timer. /// /// Whether to freeze or resume the detonation timer public void FreezeTimer(bool freeze = true) { _timerFrozen = freeze; } /// /// Restores the detonation timer to the initial time. /// public void RestoreTimer() { _timer = _timerSeconds; } #endregion #region Unity /// /// Subscribes to events. /// protected override void OnEnable() { base.OnEnable(); if (_pin) { _pin.Grabbed += Pin_Grabbed; } if (_activationTrigger == UxrGrenadeActivationMode.OnHandLaunch) { if (GrabbableObject != null) { GrabbableObject.Released += Grenade_Released; } } } /// /// Unsubscribes from events. /// protected override void OnDisable() { base.OnDisable(); if (_pin) { _pin.Grabbed -= Pin_Grabbed; } if (_activationTrigger == UxrGrenadeActivationMode.OnHandLaunch) { if (GrabbableObject != null) { GrabbableObject.Released -= Grenade_Released; } } } /// /// Initializes the component. /// protected override void Start() { base.Start(); if (_pin) { // Disable collider. We will enable it once the pin is grabbed. Collider pinCollider = _pin.GetCachedComponent(); if (pinCollider) { pinCollider.enabled = false; } } } /// /// Updates the component. /// private void Update() { if (_timer > 0.0f && !_timerFrozen) { _timer -= Time.deltaTime; if (_timer <= 0.0f) { Explode(); } } } /// /// Called by Unity when the physics-driven rigidbody collider hit something. /// /// Collision object private void OnCollisionEnter(Collision collision) { if (_explodeOnCollision && (_impactExplosionCollisionMask.value & 1 << collision.collider.gameObject.layer) != 0) { Explode(); } } #endregion #region Event Handling Methods /// /// Called when the grenade was released. /// /// Event sender /// Event parameters private void Grenade_Released(object sender, UxrManipulationEventArgs e) { if (e.IsGrabbedStateChanged && _activationTrigger == UxrGrenadeActivationMode.OnHandLaunch) { _timer = _timerSeconds; } } /// /// Called when the pin was grabbed. It will remove the pin from the grenade. /// /// Event sender /// Event parameters private void Pin_Grabbed(object sender, UxrManipulationEventArgs e) { if (e.IsGrabbedStateChanged && _activationTrigger == UxrGrenadeActivationMode.TriggerPin && !IsActivated) { _timer = _timerSeconds; Collider pinCollider = _pin.GetCachedComponent(); if (pinCollider) { pinCollider.enabled = true; } _audioRemovePin?.Play(transform.position); // Send haptic to the hand that grabbed the pin e.Grabber.Avatar.ControllerInput.SendGrabbableHapticFeedback(e.GrabbableObject, _hapticRemovePin); } } #endregion #region Private Methods /// /// Explodes the grenade, causing explosion and damage. /// private void Explode() { if (_exploded) { return; } _exploded = true; if (_explosionPrefabPool.Length > 0) { GameObject newExplosion = Instantiate(_explosionPrefabPool[Random.Range(0, _explosionPrefabPool.Length)], transform.position, Quaternion.LookRotation(-UxrAvatar.LocalAvatar.CameraForward)); if (_explosionPrefabLife > 0.0f) { Destroy(newExplosion, _explosionPrefabLife); } } if (_createPhysicsExplosion) { Collider[] colliders = Physics.OverlapSphere(transform.position, _damageRadius); foreach (Collider targetCollider in colliders) { if (targetCollider.TryGetComponent(out var targetRigidbody)) { targetRigidbody.AddExplosionForce(_physicsExplosionForce, transform.position, _damageRadius); } } } UxrWeaponManager.Instance.ApplyRadiusDamage(Owner, transform.position, _damageRadius, _damageNear, _damageFar); Destroy(gameObject); } #endregion #region Private Types & Data private float _timer = -1.0f; private bool _timerFrozen; private bool _exploded; #endregion } }