/// ---------------------------------------------
/// Ultimate Character Controller
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.UltimateCharacterController.Character.Abilities.Items
{
using Opsive.Shared.Events;
using Opsive.UltimateCharacterController.Input;
using Opsive.UltimateCharacterController.Utility;
using UnityEngine;
///
/// ItemAbility which will aim the item.
///
[DefaultStartType(AbilityStartType.ButtonDownContinuous)]
[DefaultStopType(AbilityStopType.ButtonUp)]
[DefaultInputName("Fire2")]
[DefaultItemStateIndex(1)]
[DefaultState("Aim")]
public class Aim : ItemAbility
{
[Tooltip("When the Aim ability is activated should it stop the speed change ability?")]
[SerializeField] protected bool m_StopSpeedChange = true;
[Tooltip("Should the ability activate when the first person perspective is enabled?")]
[SerializeField] protected bool m_ActivateInFirstPerson = true;
[Tooltip("Should the ability rotate the character to face the look source target?")]
[SerializeField] protected bool m_RotateTowardsLookSourceTarget = true;
public bool StopSpeedChange { get { return m_StopSpeedChange; } set { m_StopSpeedChange = value; } }
public bool ActivateInFirstPerson { get { return m_ActivateInFirstPerson; } set { m_ActivateInFirstPerson = value; } }
public bool RotateTowardsLookSourceTarget { get { return m_RotateTowardsLookSourceTarget; } set { m_RotateTowardsLookSourceTarget = value; } }
private ILookSource m_LookSource;
private bool m_FirstPersonPerspective;
private bool m_InputStart;
private bool m_PerspectiveSwitch;
#if THIRD_PERSON_CONTROLLER
private ThirdPersonController.Character.Abilities.Items.ItemPullback m_ItemPullback;
#endif
public override bool CanReceiveMultipleStarts { get { return true; } }
public bool InputStart { get { return m_InputStart; } }
///
/// Initialize the default values.
///
public override void Awake()
{
base.Awake();
#if THIRD_PERSON_CONTROLLER
m_ItemPullback = m_CharacterLocomotion.GetAbility();
#endif
// The look source may have already been assigned if the ability was added to the character after the look source was assigned.
m_LookSource = m_CharacterLocomotion.LookSource;
EventHandler.RegisterEvent(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource);
EventHandler.RegisterEvent(m_GameObject, "OnCameraWillChangePerspectives", OnChangePerspectives);
EventHandler.RegisterEvent(m_GameObject, "OnCharacterAbilityActive", OnAbilityActive);
EventHandler.RegisterEvent(m_GameObject, "OnCharacterItemAbilityActive", OnItemAbilityActive);
EventHandler.RegisterEvent(m_GameObject, "OnRespawn", OnRespawn);
}
///
/// A new ILookSource object has been attached to the character.
///
/// The ILookSource object attached to the character.
private void OnAttachLookSource(ILookSource lookSource)
{
m_LookSource = lookSource;
}
///
/// The character perspective between first and third person has changed.
///
/// Is the character in a first person perspective?
private void OnChangePerspectives(bool firstPersonPerspective)
{
// While in first person mode the character is always aiming so the ability should be started so the character shadow is correct.
m_FirstPersonPerspective = firstPersonPerspective;
if (m_ActivateInFirstPerson) {
if (!IsActive && firstPersonPerspective) {
StartAbility();
} else if ( IsActive && !firstPersonPerspective && !m_InputStart) {
m_PerspectiveSwitch = true;
StopAbility(true);
m_PerspectiveSwitch = false;
}
}
}
///
/// Returns the Item State Index which corresponds to the slot ID.
///
/// The ID of the slot that corresponds to the Item State Index.
/// The Item State Index which corresponds to the slot ID.
public override int GetItemStateIndex(int slotID)
{
#if THIRD_PERSON_CONTROLLER
// If the ItemPullback ability is active then an object would be obstructing the item location. Wait to change the state index
// until no objects are obstructing the item location.
if (m_ItemPullback != null && m_ItemPullback.IsActive) {
return -1;
}
#endif
return base.GetItemStateIndex(slotID);
}
///
/// Returns the Item Substate Index which corresponds to the slot ID.
///
/// The ID of the slot that corresponds to the Item Substate Index.
/// The Item Substate Index which corresponds to the slot ID.
public override int GetItemSubstateIndex(int slotID)
{
#if THIRD_PERSON_CONTROLLER
// If the ItemPullback ability is active then an object would be obstructing the item location. Wait to change the state index
// until no objects are obstructing the item location.
if (m_ItemPullback != null && m_ItemPullback.IsActive) {
return -1;
}
#endif
// Return the UsableItem's substate index if it isn't 0. This will allow the animator to know if the item is out of ammo.
var item = m_Inventory.GetActiveItem(slotID);
if (item != null && item.DominantItem) {
var itemActions = item.ItemActions;
for (int i = 0; i < itemActions.Length; ++i) {
var usableItem = itemActions[i] as UltimateCharacterController.Items.Actions.IUsableItem;
if (usableItem != null) {
var substateIndex = usableItem.GetItemSubstateIndex();
if (substateIndex != -1) {
return substateIndex;
}
}
}
}
return m_InputStart ? 1 : 0;
}
///
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
///
/// True if the ability can be started.
public override bool CanStartAbility()
{
if (m_InputStart) {
return false;
}
#if THIRD_PERSON_CONTROLLER
if (m_ItemPullback != null && m_ItemPullback.IsActive) {
return false;
}
#endif
return base.CanStartAbility();
}
///
/// Called when another ability is attempting to start and the current ability is active.
/// Returns true or false depending on if the new ability should be blocked from starting.
///
/// The ability that is starting.
/// True if the ability should be blocked.
public override bool ShouldBlockAbilityStart(Ability startingAbility)
{
if (base.ShouldBlockAbilityStart(startingAbility)) {
return true;
}
return PreventAbility(startingAbility);
}
///
/// Called when the current ability is attempting to start and another ability is active.
/// Returns true or false depending on if the active ability should be stopped.
///
/// The ability that is currently active.
/// True if the ability should be stopped.
public override bool ShouldStopActiveAbility(Ability activeAbility)
{
if (base.ShouldStopActiveAbility(activeAbility)) {
return true;
}
return PreventAbility(activeAbility);
}
///
/// Can the specified ability be active while the Aim ability is active?
///
/// The ability to check if it can be active.
/// True if the specified ability can be active while the Aim ability is active.
private bool PreventAbility(Ability activeAbility)
{
// InputStart isn't set until AbilityStarted so the InputIndex should be used as well.
if (m_StopSpeedChange && (InputIndex != -1 || m_InputStart) && activeAbility is SpeedChange) {
return true;
}
return false;
}
///
/// The ability has started.
///
protected override void AbilityStarted()
{
// If the ability started because of the first person perspective change then the state shouldn't be changed. If the state was changed
// to first person and the state was changed then the camera would zoom in which should only occur with a button press.
if (InputIndex != -1 || m_StartType == AbilityStartType.Automatic) {
base.AbilityStarted();
m_InputStart = true;
}
EventHandler.ExecuteEvent(m_GameObject, "OnAimAbilityStart", true, m_InputStart);
EventHandler.ExecuteEvent(m_GameObject, "OnAimAbilityAim", true);
}
///
/// Update the controller's rotation values.
///
public override void UpdateRotation()
{
// If the character can look independently then the character does not need to rotate to face the look direction.
if (m_CharacterLocomotion.ActiveMovementType.UseIndependentLook(true)) {
return;
}
// The look source may be null if a remote player is still being initialized.
if (m_LookSource == null || !m_RotateTowardsLookSourceTarget) {
return;
}
// Determine the direction that the character should be facing.
var lookDirection = m_LookSource.LookDirection(m_LookSource.LookPosition(), true, m_CharacterLayerManager.IgnoreInvisibleCharacterLayers, false);
var rotation = m_Transform.rotation * Quaternion.Euler(m_CharacterLocomotion.DeltaRotation);
var localLookDirection = MathUtility.InverseTransformDirection(lookDirection, rotation);
localLookDirection.y = 0;
lookDirection = MathUtility.TransformDirection(localLookDirection, rotation);
var targetRotation = Quaternion.LookRotation(lookDirection, rotation * Vector3.up);
m_CharacterLocomotion.DeltaRotation = (Quaternion.Inverse(m_Transform.rotation) * targetRotation).eulerAngles;
}
///
/// Can the input stop the ability?
///
/// A reference to the input component.
/// True if the input can stop the ability.
public override bool CanInputStopAbility(PlayerInput playerInput)
{
if (m_ActivateInFirstPerson && !m_InputStart) {
return false;
}
return base.CanInputStopAbility(playerInput);
}
///
/// Can the ability be stopped?
///
/// True if the ability can be stopped.
public override bool CanStopAbility()
{
if (m_ActivateInFirstPerson && m_FirstPersonPerspective) {
// The ability can't stop for as long as the character is in first person mode. If the ability was started in with a button press then
// the ability shouldn't stop but it should change the state so the camera will no longer zoom.
if (m_InputStart) {
base.AbilityStopped(false);
EventHandler.ExecuteEvent(m_GameObject, "OnAimAbilityStart", false, m_InputStart);
m_InputStart = false;
m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters();
}
return false;
}
return true;
}
///
/// The ability has stopped running.
///
/// Was the ability force stopped?
protected override void AbilityStopped(bool force)
{
// The base AbilityStopped may have already been called within CanStopAbility - don't call it again to prevent duplicate calls.
EventHandler.ExecuteEvent(m_GameObject, "OnAimAbilityAim", false);
if (m_PerspectiveSwitch && !m_InputStart) {
return;
}
EventHandler.ExecuteEvent(m_GameObject, "OnAimAbilityStart", false, m_InputStart);
m_InputStart = false;
base.AbilityStopped(force);
}
///
/// Should the input be checked to ensure button up is using the correct value?
///
/// True if the input should be checked.
protected override bool ShouldCheckInput() { return false; }
///
/// The ability has been started or stopped.
///
/// The ability which was started or stopped.
/// True if the ability was started, false if it was stopped.
private void OnAbilityActive(Ability ability, bool active)
{
// Another ability may have stopped aiming while in first person. Activate aiming again if necessary.
if (!IsActive && !active && m_ActivateInFirstPerson && m_CharacterLocomotion.FirstPersonPerspective) {
StartAbility();
}
}
///
/// The item ability has been started or stopped.
///
/// The item ability which was started or stopped.
/// True if the ability was started, false if it was stopped.
private void OnItemAbilityActive(ItemAbility itemAbility, bool active)
{
#if UNITY_EDITOR
if (IsActive && itemAbility.Index > Index && (itemAbility is Use
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
|| itemAbility is Reload
#endif
)) {
Debug.Log($"Warning: The ability {itemAbility.GetType().Name} started but it has a lower priority than the aim ability." +
"This will prevent that ability from updating the Animator.");
}
#endif
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
// The Aim ability should start again if the Reload ability stopped and is in a first person view. The Reload ability would have
// stopped the Aim ability.
if (itemAbility is Reload && !active && m_CharacterLocomotion.FirstPersonPerspective) {
OnChangePerspectives(true);
}
#endif
#if THIRD_PERSON_CONTROLLER
// If the ItemPullback ability is activated then the aim state should no longer be set. This will prevent the aim animator parameters from updating.
if (IsActive && itemAbility == m_ItemPullback) {
EventHandler.ExecuteEvent(m_GameObject, "OnAimAbilityAim", !active);
}
#endif
}
///
/// The character has respawned. Determine if the ability should start.
///
private void OnRespawn()
{
if (m_ActivateInFirstPerson && m_CharacterLocomotion.FirstPersonPerspective) {
StartAbility();
}
}
///
/// Called when the character is destroyed.
///
public override void OnDestroy()
{
base.OnDestroy();
EventHandler.UnregisterEvent(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource);
EventHandler.UnregisterEvent(m_GameObject, "OnCameraWillChangePerspectives", OnChangePerspectives);
EventHandler.UnregisterEvent(m_GameObject, "OnCharacterAbilityActive", OnAbilityActive);
EventHandler.UnregisterEvent(m_GameObject, "OnCharacterItemAbilityActive", OnItemAbilityActive);
EventHandler.UnregisterEvent(m_GameObject, "OnRespawn", OnRespawn);
}
}
}