using MalbersAnimations.Scriptables; using MalbersAnimations.Utilities; using UnityEngine; using UnityEngine.Serialization; namespace MalbersAnimations.Weapons { [AddComponentMenu("Malbers/Damage/Projectile Thrower")] public class MProjectileThrower : MonoBehaviour, IThrower , IAnimatorListener { /// Is Used to calculate the Trajectory and Display it as a LineRenderer public System.Action Predict { get; set; } [Header("Projectile")] [SerializeField, Tooltip("What projectile will be instantiated")] private GameObjectReference m_Projectile = new GameObjectReference(); [Tooltip("The projectile will be fired on start")] public BoolReference FireOnStart; [Header("Multipliers")] [Tooltip("Multiplier value to Apply to the Projectile Stat Modifier"),FormerlySerializedAs("Multiplier") ] public FloatReference DamageMultiplier = new FloatReference(1); [Tooltip("Multiplier value to apply to the Projectile Scale")] public FloatReference ScaleMultiplier = new FloatReference(1); [Tooltip("Multiplier value to apply to the Projectile Launch Force")] public FloatReference PowerMultiplier = new FloatReference(1); [Header("Layer Interaction")] [SerializeField] private LayerReference hitLayer = new LayerReference(-1); [SerializeField] private QueryTriggerInteraction triggerInteraction = QueryTriggerInteraction.Ignore; [Header("References")] [SerializeField, Tooltip("When this parameter is set it will Aim Directly to the Target")] private TransformReference m_Target; [SerializeField, Tooltip("Transform Reference for to calculate the Thrower Aim Origin Position")] private Transform m_AimOrigin; [SerializeField, Tooltip("Owner of the Thrower Component. By default it should be the Root GameObject")] private GameObjectReference m_Owner; [Tooltip("Reference for the Aimer Component")] public Aim Aimer; [Tooltip("if its set to False. it will use this GameObject Forward Direction")] public BoolReference useAimerDirection = new BoolReference( true); [Header("Physics Values")] [SerializeField, Tooltip("Launch force for the Projectile")] private float m_power = 50f; [Range(0, 90)] [SerializeField, Tooltip("Angle of the Projectile when a Target is assigned")] private float m_angle = 45f; [SerializeField, Tooltip("Gravity to apply to the Projectile. By default is set to Physics.gravity")] private Vector3Reference gravity = new Vector3Reference(Physics.gravity); public Vector3 Gravity { get => gravity.Value; set => gravity.Value = value; } public LayerMask Layer { get => hitLayer.Value; set => hitLayer.Value = value; } public QueryTriggerInteraction TriggerInteraction { get => triggerInteraction; set => triggerInteraction = value; } public Vector3 AimOriginPos => m_AimOrigin.position; public Transform Target { get => m_Target.Value; set => m_Target.Value = value; } /// Owner of the public GameObject Owner { get => m_Owner.Value; set => m_Owner.Value = value; } public GameObject Projectile { get => m_Projectile.Value; set => m_Projectile.Value = value; } /// Projectile Launch Velocity(v3) public Vector3 Velocity { get; set; } /// Force to Apply to the Projectile public float Power { get => m_power * PowerMultiplier; set => m_power = value; } /// Set if the Aimer Direction will be used or not public bool UseAimerDirection { get => useAimerDirection.Value; set => useAimerDirection.Value = value; } public Transform AimOrigin => m_AimOrigin; public bool CalculateTrajectory { get => m_CalculateTrajectory; set { m_CalculateTrajectory = value; Predict?.Invoke(value); } } private bool m_CalculateTrajectory; private void OnEnable() { if (Owner == null) Owner = transform.root.gameObject; if (m_AimOrigin == null) m_AimOrigin = transform; if (Aimer) m_AimOrigin = Aimer.AimOrigin; //Set the Aim origin from the Aimer if (FireOnStart) Fire(); } public virtual void SetProjectile(GameObject newProjectile) { if (Projectile != newProjectile) Projectile = newProjectile; } /// Fire Proyectile public virtual void Fire() { if (!enabled) return; if (Projectile == null) return; CalculateVelocity(); var Inst_Projectile = Instantiate(Projectile, AimOriginPos, Quaternion.identity); Inst_Projectile.transform.localScale *= ScaleMultiplier;// Prepare_Projectile(Inst_Projectile); Predict?.Invoke(false); } private void Update() { if (CalculateTrajectory) CalculateVelocity(); } public void SetTarget(Transform target) => Target = target; public void ClearTarget() => Target = null; public void SetTarget(GameObject target) => Target = target.transform; public virtual bool OnAnimatorBehaviourMessage(string message, object value) => this.InvokeWithParams(message, value); void Prepare_Projectile(GameObject p) { var projectile = p.GetComponent(); if (projectile != null) //Means its a Malbers Projectile ^^ { projectile.Prepare(Owner, Gravity, Velocity, Layer, TriggerInteraction); projectile.DamageMultiplier(DamageMultiplier); //Apply Multiplier projectile.Fire(); } else //Fire without the Projectile Component { var rb = p.GetComponent(); rb?.AddForce(Velocity, ForceMode.VelocityChange); } } public virtual void SetDamageMultiplier(float m) => DamageMultiplier = m; public virtual void ScaleProjectileMultiplier(float m) => ScaleMultiplier = m; public virtual void CalculateVelocity() { if (Target) { var target_Direction = (Target.position - AimOriginPos).normalized; float TargetAngle = 90 - Vector3.Angle(target_Direction, -Gravity) + 0.1f; //in case the angle is smaller than the target height if (TargetAngle < m_angle) TargetAngle = m_angle; Power = MTools.PowerFromAngle(AimOriginPos, Target.position, TargetAngle); Velocity = MTools.VelocityFromPower(AimOriginPos, Power, TargetAngle, Target.position); } else if (Aimer && useAimerDirection.Value) { Velocity = Aimer.AimDirection.normalized * Power; } else { Velocity = transform.forward.normalized * Power; } Predict?.Invoke(true); } void Reset() { m_Owner.Value = transform.root.gameObject; m_AimOrigin = transform; } #if UNITY_EDITOR void OnDrawGizmos() { if (AimOrigin) { UnityEditor.Handles.color = Color.blue; UnityEditor.Handles.ArrowHandleCap(0, m_AimOrigin.position, transform.rotation, Power * 0.01f, EventType.Repaint); } } #endif } }