Files
dungeons/Assets/ThirdParty/UltimateXR/Runtime/Scripts/Mechanics/Weapons/UxrWeaponManager.cs

357 lines
16 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrWeaponManager.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Core;
using UltimateXR.Core.Components.Singleton;
using UltimateXR.Core.Settings;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Singleton manager in charge of updating projectiles, computing hits against entities and damage done on
/// <see cref="UxrActor" /> components.
/// </summary>
public partial class UxrWeaponManager : UxrSingleton<UxrWeaponManager>
{
#region Public Types & Data
/// <summary>
/// Event triggered right before an <see cref="UxrActor" /> is about to receive damage.
/// Setting <see cref="UxrDamageEventArgs.Cancel" /> will allow not to take the damage.
/// </summary>
public event EventHandler<UxrDamageEventArgs> DamageReceiving;
/// <summary>
/// Event triggered right after the actor received damage.
/// Setting <see cref="UxrDamageEventArgs.Cancel" /> is not supported, since the damage was already taken.
/// </summary>
public event EventHandler<UxrDamageEventArgs> DamageReceived;
/// <summary>
/// Event called whenever there was a projectile impact but no <see cref="UxrActor" /> was involved. Mostly hits
/// against the scenario that still generate decals, FX, etc.
/// </summary>
public event EventHandler<UxrNonDamagingImpactEventArgs> NonActorImpacted;
#endregion
#region Public Methods
/// <summary>
/// Updates the manager.
/// </summary>
public void UpdateManager()
{
UpdateProjectiles();
}
/// <summary>
/// Registers a new projectile shot so that it gets automatically update by the manager from that moment until it hits
/// something or gets destroyed.
/// </summary>
/// <param name="projectileSource">Projectile source</param>
/// <param name="shotDescriptor">Shot descriptor</param>
/// <param name="position">World position</param>
/// <param name="orientation">World orientation. The projectile will travel in the forward (z) direction</param>
public void RegisterNewProjectileShot(UxrProjectileSource projectileSource, UxrShotDescriptor shotDescriptor, Vector3 position, Quaternion orientation)
{
_projectiles.Add(new ProjectileInfo(projectileSource.TryGetWeaponOwner(), projectileSource, shotDescriptor, position, orientation));
}
/// <summary>
/// Applies radius damage to all elements around a source position.
/// </summary>
/// <param name="actorSource">The actor that was responsible for the damage or null if there wasn't any</param>
/// <param name="position">Explosion world position</param>
/// <param name="radius">Radius</param>
/// <param name="nearDamage">Damage at the very same point of the explosion</param>
/// <param name="farDamage">Damage at the distance set by <paramref name="radius" /></param>
public void ApplyRadiusDamage(UxrActor actorSource, Vector3 position, float radius, float nearDamage, float farDamage)
{
foreach (KeyValuePair<UxrActor, ActorInfo> damageActorPair in _damageActors)
{
float distance = Vector3.Distance(damageActorPair.Value.Transform.position, position);
if (distance < radius)
{
float damage = Mathf.Lerp(nearDamage, farDamage, distance / radius);
damageActorPair.Key.ReceiveExplosion(actorSource, position, damage);
}
}
}
#endregion
#region Unity
/// <summary>
/// Initializes the manager. Subscribes to actor enable/disable events.
/// </summary>
protected override void Awake()
{
base.Awake();
UxrActor.GlobalEnabled += Actor_Enabled;
UxrActor.GlobalDisabled += Actor_Disabled;
}
/// <summary>
/// Initializes the manager. Unsubscribes from actor enable/disable events.
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
UxrActor.GlobalEnabled -= Actor_Enabled;
UxrActor.GlobalDisabled -= Actor_Disabled;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called whenever an <see cref="UxrActor" /> was enabled. Create a new entry in the internal dictionary.
/// </summary>
/// <param name="actor">Actor that was enabled</param>
private void Actor_Enabled(UxrActor actor)
{
if (_damageActors.ContainsKey(actor) == false)
{
_damageActors.Add(actor, new ActorInfo(actor));
actor.DamageReceiving += Actor_DamageReceiving;
actor.DamageReceived += Actor_DamageReceived;
}
}
/// <summary>
/// Called whenever an <see cref="UxrActor" /> was disabled. Remove the entry from the internal dictionary.
/// </summary>
/// <param name="actor">Actor that was disabled</param>
private void Actor_Disabled(UxrActor actor)
{
if (_damageActors.ContainsKey(actor))
{
_damageActors.Remove(actor);
actor.DamageReceiving -= Actor_DamageReceiving;
actor.DamageReceived -= Actor_DamageReceived;
}
}
/// <summary>
/// Called whenever an actor is about to receive any damage. The damage can be canceled using
/// <see cref="UxrDamageEventArgs.Cancel" />.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void Actor_DamageReceiving(object sender, UxrDamageEventArgs e)
{
OnDamageReceiving(e);
}
/// <summary>
/// Called right after an actor received any damage. Damage can not be canceled using
/// <see cref="UxrDamageEventArgs.Cancel" /> because it was already taken.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void Actor_DamageReceived(object sender, UxrDamageEventArgs e)
{
OnDamageReceived(e);
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Event trigger for <see cref="DamageReceiving" />.
/// </summary>
/// <param name="e">Event parameters</param>
private void OnDamageReceiving(UxrDamageEventArgs e)
{
DamageReceiving?.Invoke(this, e);
}
/// <summary>
/// Event trigger for <see cref="DamageReceived" />.
/// </summary>
/// <param name="e">Event parameters</param>
private void OnDamageReceived(UxrDamageEventArgs e)
{
if (UxrGlobalSettings.Instance.LogLevelWeapons >= UxrLogLevel.Relevant)
{
string sourceInfo = e.ActorSource != null ? $" from actor {e.ActorSource.name}." : string.Empty;
if (UxrGlobalSettings.Instance.LogLevelWeapons >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.WeaponsModule}: Actor {e.ActorTarget.name} received {e.Damage} damage of type {e.DamageType}{sourceInfo}. Is dead? {e.Dies}.");
}
}
DamageReceived?.Invoke(this, e);
}
#endregion
#region Private Methods
/// <summary>
/// Updates all current projectiles.
/// </summary>
private void UpdateProjectiles()
{
for (int i = 0; i < _projectiles.Count; ++i)
{
if (_projectiles[i].Projectile == null)
{
// Dynamic instance removed somewhere
_projectiles.RemoveAt(i);
i--;
continue;
}
if (_projectiles[i].FirstFrame)
{
// Render first frame where the weapon is
_projectiles[i].FirstFrame = false;
continue;
}
Vector3 oldPos = _projectiles[i].ProjectileLastPosition;
Vector3 newPos = _projectiles[i].Projectile.transform.position;
Vector3 projectileForward = _projectiles[i].Projectile.transform.forward;
if (_projectiles[i].ShotDescriptor.UseAutomaticProjectileTrajectory)
{
newPos = oldPos + (_projectiles[i].ProjectileSpeed * Time.deltaTime * projectileForward);
_projectiles[i].Projectile.transform.position = newPos;
}
_projectiles[i].ProjectileLastPosition = newPos;
_projectiles[i].ProjectileDistanceTravelled += Vector3.Distance(oldPos, newPos);
if (_projectiles[i].ProjectileDistanceTravelled >= _projectiles[i].ShotDescriptor.ProjectileMaxDistance)
{
// Max distance travelled
Destroy(_projectiles[i].Projectile);
_projectiles.RemoveAt(i);
i--;
continue;
}
if (_projectiles[i].ShotDescriptor.UseAutomaticProjectileTrajectory)
{
float rayLength = Vector3.Distance(oldPos, newPos + projectileForward * _projectiles[i].ShotDescriptor.ProjectileLength);
if (Physics.Raycast(oldPos, projectileForward, out RaycastHit raycastHit, rayLength, _projectiles[i].ShotLayerMask, QueryTriggerInteraction.Ignore))
{
UxrProjectileDeflect projectileDeflect = raycastHit.collider.GetComponentInParent<UxrProjectileDeflect>();
// Has UxrProjectileDeflect component?
if (projectileDeflect != null)
{
Vector3 newForward = Vector3.Reflect(projectileForward, raycastHit.normal).normalized;
_projectiles[i].Projectile.transform.SetPositionAndRotation(raycastHit.point + newForward * 0.05f, Quaternion.LookRotation(newForward)); // + rayLength - raycastHit.distance));
_projectiles[i].ProjectileDeflectSource = projectileDeflect;
projectileDeflect.AudioDeflect?.Play(raycastHit.point);
if (projectileDeflect.DecalOnReflect != null)
{
// Generate decal?
UxrImpactDecal.CheckCreateDecal(raycastHit,
-1,
projectileDeflect.DecalOnReflect,
projectileDeflect.DecalLife,
projectileDeflect.DecalFadeoutDuration,
projectileDeflect.TwoSidedDecal,
projectileDeflect.TwoSidedDecalThickness);
}
_projectiles[i].AddShotLayerMask(projectileDeflect.CollideLayersAddOnReflect.value);
_projectiles[i].ProjectileLastPosition = _projectiles[i].Projectile.transform.position;
projectileDeflect.RaiseProjectileDeflected(new UxrDeflectEventArgs(_projectiles[i].ProjectileSource, raycastHit, newForward));
}
else
{
UxrActor targetActor = raycastHit.collider.GetComponentInParent<UxrActor>();
if (targetActor != null)
{
// Impact with an actor.
float normalizedDistance = _projectiles[i].ProjectileDistanceTravelled / _projectiles[i].ShotDescriptor.ProjectileMaxDistance;
float damage = Mathf.Lerp(_projectiles[i].ShotDescriptor.ProjectileDamageNear, _projectiles[i].ShotDescriptor.ProjectileDamageFar, normalizedDistance);
if (_projectiles[i].ProjectileDeflectSource != null)
{
// Came from a shot deflected by a UxrProjectileDeflect
targetActor.ReceiveImpact(_projectiles[i].ProjectileDeflectSource.Owner, raycastHit, damage);
}
else
{
// Direct hit from a projectile
targetActor.ReceiveImpact(_projectiles[i].ProjectileSource.TryGetWeaponOwner(), raycastHit, damage);
}
}
else
{
// Impact with something that isn't an actor, probably the scenario.
if (_projectiles[i].ShotDescriptor.PrefabInstantiateOnImpact)
{
Vector3 instantiateForward = Vector3.Reflect(projectileForward, raycastHit.normal).normalized;
GameObject newInstance = Instantiate(_projectiles[i].ShotDescriptor.PrefabInstantiateOnImpact, raycastHit.point, Quaternion.LookRotation(instantiateForward));
if (_projectiles[i].ShotDescriptor.PrefabInstantiateOnImpactLife >= 0.0f)
{
Destroy(newInstance, _projectiles[i].ShotDescriptor.PrefabInstantiateOnImpactLife);
}
}
Rigidbody rigidbody = raycastHit.collider.GetComponentInParent<Rigidbody>();
if (rigidbody != null)
{
rigidbody.AddForceAtPosition(_projectiles[i].ProjectileSpeed * _projectiles[i].ShotDescriptor.ProjectileImpactForceMultiplier * projectileForward, raycastHit.transform.position);
}
UxrOverrideImpactDecal overrideDecal = raycastHit.collider.GetComponentInParent<UxrOverrideImpactDecal>();
// Generate decal?
UxrImpactDecal.CheckCreateDecal(raycastHit,
_projectiles[i].ShotDescriptor.CreateDecalLayerMask,
overrideDecal != null ? overrideDecal.DecalToUse : _projectiles[i].ShotDescriptor.PrefabScenarioImpactDecal,
_projectiles[i].ShotDescriptor.PrefabScenarioImpactDecalLife,
_projectiles[i].ShotDescriptor.DecalFadeoutDuration);
NonActorImpacted?.Invoke(_projectiles[i].ProjectileSource, new UxrNonDamagingImpactEventArgs(_projectiles[i].ProjectileSource.TryGetWeaponOwner(), _projectiles[i].ProjectileSource, raycastHit));
}
Destroy(_projectiles[i].Projectile);
_projectiles.RemoveAt(i);
i--;
}
}
}
}
}
#endregion
#region Private Types & Data
private readonly Dictionary<UxrActor, ActorInfo> _damageActors = new Dictionary<UxrActor, ActorInfo>();
private readonly List<ProjectileInfo> _projectiles = new List<ProjectileInfo>();
#endregion
}
}