Files
BABA_YAGA/Assets/Opsive/UltimateCharacterController/Scripts/Items/Actions/MagicItem.cs
2026-06-09 02:05:00 +07:00

1010 lines
44 KiB
C#

/// ---------------------------------------------
/// Ultimate Character Controller
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.UltimateCharacterController.Items.Actions
{
using Opsive.Shared.Events;
using Opsive.Shared.Game;
using Opsive.Shared.Utility;
using Opsive.UltimateCharacterController.Character;
using Opsive.UltimateCharacterController.Character.Abilities;
using Opsive.UltimateCharacterController.Character.Abilities.Items;
using Opsive.UltimateCharacterController.Game;
using Opsive.UltimateCharacterController.Items.Actions.PerspectiveProperties;
using Opsive.UltimateCharacterController.Items.Actions.Magic.CastActions;
using Opsive.UltimateCharacterController.Items.Actions.Magic.ImpactActions;
using Opsive.UltimateCharacterController.Items.Actions.Magic.BeginEndActions;
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
using Opsive.UltimateCharacterController.Networking;
using Opsive.UltimateCharacterController.Networking.Character;
#endif
using Opsive.UltimateCharacterController.Objects.ItemAssist;
using Opsive.UltimateCharacterController.SurfaceSystem;
using Opsive.UltimateCharacterController.Utility;
using UnityEngine;
/// <summary>
/// The Magic Item uses Cast Actions to perform different magical actions when used. Impact Actions will then perform an action from the resulting cast impact.
/// </summary>
public class MagicItem : UsableItem
{
/// <summary>
/// Specifies the direction of the cast.
/// </summary>
public enum CastDirection
{
None, // The cast has no movement.
Forward, // The cast should move in the forward direction.
Target, // The cast should move towards a target.
Indicate // The cast should move towards an indicated position.
}
/// <summary>
/// Specifies how often the magic item does its cast.
/// </summary>
public enum CastUseType
{
Single, // The cast should occur once per use.
Continuous // The cast should occur every use update.
}
/// <summary>
/// Specifies if the cast should interrupted.
/// </summary>
public enum CastInterruptSource
{
Movement = 1, // The cast should be interrupted when the character moves.
Damage = 2 // The cast should be interrupted when the character takes damage.
}
[Tooltip("Is the character required to be on the ground?")]
[SerializeField] protected bool m_RequireGrounded = true;
[Tooltip("The direction of the cast.")]
[SerializeField] protected CastDirection m_Direction = CastDirection.Forward;
[Tooltip("Should the look source be used when determining the cast direction?")]
[SerializeField] protected bool m_UseLookSource = true;
[Tooltip("The maximum distance of the movement cast direction.")]
[SerializeField] protected float m_MaxDistance = 100;
[Tooltip("The radius of the movement cast direction.")]
[SerializeField] protected float m_Radius = 0.1f;
[Tooltip("The layers that the movement directions can collide with.")]
[SerializeField] protected LayerMask m_DetectLayers = ~(1 << LayerManager.IgnoreRaycast | 1 << LayerManager.UI | 1 << LayerManager.SubCharacter | 1 << LayerManager.Overlay | 1 << LayerManager.VisualEffect);
[Tooltip("The maximum angle that the target object can be compared to the character's forward direction.")]
[SerializeField] protected float m_MaxAngle = 30;
[Tooltip("The maximum number of colliders that can be detected by the target cast.")]
[SerializeField] protected int m_MaxCollisionCount = 100;
[Tooltip("The number of objects that a single cast should cast.")]
[SerializeField] protected int m_TargetCount = 1;
[Tooltip("The transform used to indicate the surface. Can be null.")]
[SerializeField] protected Transform m_SurfaceIndicator;
[Tooltip("The offset when positioning the surface indicator.")]
[SerializeField] protected Vector3 m_SurfaceIndicatorOffset = new Vector3(0, 0.1f, 0);
[Tooltip("Specifies how often the cast is used.")]
[SerializeField] protected CastUseType m_UseType;
[Tooltip("The minimum duration of the continuous use type. If a value of -1 is set then the item will be stopped when the stop is requested.")]
[SerializeField] protected float m_MinContinuousUseDuration = 1;
[Tooltip("Should the continuous use type cast every update?")]
[SerializeField] protected bool m_ContinuousCast;
[Tooltip("The amount of the ItemIdentifier that should be used each cast.")]
[SerializeField] protected int m_UseAmount;
[Tooltip("Specifies when the cast should be interrupted.")]
[SerializeField] protected CastInterruptSource m_InterruptSource;
[Tooltip("The SurfaceImpact of the cast.")]
[SerializeField] protected SurfaceImpact m_SurfaceImpact;
[Tooltip("The value to add to the Item Substate Index when the item can stop.")]
[SerializeField] protected int m_CanStopSubstateIndexAddition = 10;
[Tooltip("The serialization data for the BeginActions.")]
[SerializeField] [ForceSerialized] protected Serialization[] m_BeginActionData;
[Tooltip("The serialization data for the CastActions.")]
[SerializeField] [ForceSerialized] protected Serialization[] m_CastActionData;
[Tooltip("The serialization data for the MagicImpactActions.")]
[SerializeField] [ForceSerialized] protected Serialization[] m_ImpactActionData;
[Tooltip("The serialization data for the EndActions.")]
[SerializeField] [ForceSerialized] protected Serialization[] m_EndActionData;
public bool RequireGrounded { get { return m_RequireGrounded; } set { m_RequireGrounded = value; } }
public CastDirection Direction { get { return m_Direction; } set { m_Direction = value; } }
public bool UseLookSource { get { return m_UseLookSource; } set { m_UseLookSource = value; } }
public float MaxDistance { get { return m_MaxDistance; } set { m_MaxDistance = value; } }
public float Radius { get { return m_Radius; } set { m_Radius = value; } }
public LayerMask DetectLayers { get { return m_DetectLayers; } set { m_DetectLayers = value; } }
public float MaxAngle { get { return m_MaxAngle; } set { m_MaxAngle = value; } }
public int TargetCount { get { return m_TargetCount; } set { m_TargetCount = value; } }
public Transform SurfaceIndicator { get { return m_SurfaceIndicator; } }
public Vector3 SurfaceIndicatorOffset { get { return m_SurfaceIndicatorOffset; }
set {
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
// The local surface indicator should not show for remote players.
if (m_NetworkInfo != null && !m_NetworkInfo.IsLocalPlayer()) {
return;
}
#endif
m_SurfaceIndicatorOffset = value;
}
}
public CastUseType UseType { get { return m_UseType; } set { m_UseType = value; } }
public float MinContinuousUseDuration { get { return m_MinContinuousUseDuration; } set { m_MinContinuousUseDuration = value; } }
public bool ContinuousCast { get { return m_ContinuousCast; } set { m_ContinuousCast = value; } }
public int UseAmount { get { return m_UseAmount; } set { m_UseAmount = value; } }
public CastInterruptSource InterruptSource { get { return m_InterruptSource; } set { m_InterruptSource = value; } }
public SurfaceImpact SurfaceImpact { get { return m_SurfaceImpact; } set { m_SurfaceImpact = value; } }
public int CanStopSubstateIndexAddition { get { return m_CanStopSubstateIndexAddition; } set { m_CanStopSubstateIndexAddition = value; } }
public Serialization[] BeginActionData {
get { return m_BeginActionData; }
set {
m_BeginActionData = value;
if (!Application.isPlaying) {
DeserializeBeginActions(false);
}
}
}
public Serialization[] CastActionData {
get { return m_CastActionData; }
set {
m_CastActionData = value;
if (!Application.isPlaying) {
DeserializeCastActions(false);
}
}
}
public Serialization[] ImpactActionData {
get { return m_ImpactActionData; }
set {
m_ImpactActionData = value;
if (!Application.isPlaying) {
DeserializeImpactActions(false);
}
}
}
public Serialization[] EndActionData {
get { return m_EndActionData; }
set {
m_EndActionData = value;
if (!Application.isPlaying) {
DeserializeEndActions(false);
}
}
}
private Transform m_CharacterTransform;
private UltimateCharacterLocomotion m_CharacterLocomotion;
private CharacterLayerManager m_CharacterLayerManager;
private IMagicItemPerspectiveProperties m_MagicItemPerspectiveProperties;
private BeginEndAction[] m_BeginActions;
private CastAction[] m_CastActions;
private ImpactAction[] m_ImpactActions;
private BeginEndAction[] m_EndActions;
private ItemAbility m_StartAbility;
private Collider[] m_TargetColliders;
private float[] m_TargetAngles;
private float m_StartUseTime;
private float m_StartCastTime;
private Vector3 m_CastDirection;
private Vector3 m_CastPosition;
private Vector3 m_CastNormal;
private bool m_Used;
private uint m_CastID;
private bool[] m_CastActionUsed;
private bool[] m_CastActionCasted;
private bool m_InterruptImpact;
private bool m_StopRequested;
private bool m_Stopping;
private bool m_ForceStop;
public GameObject Character { get { return m_Character; } }
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
public INetworkInfo NetworkInfo { get { return m_NetworkInfo; } }
public INetworkCharacter NetworkCharacter { get { return m_NetworkCharacter; } }
#endif
public IMagicItemPerspectiveProperties MagicItemPerspectiveProperties { get { return m_MagicItemPerspectiveProperties; } }
public BeginEndAction[] BeginActions {
get {
if (!Application.isPlaying && m_BeginActions == null) { DeserializeBeginActions(false); }
return m_BeginActions;
}
set { m_BeginActions = value; }
}
public CastAction[] CastActions {
get {
if (!Application.isPlaying && m_CastActions == null) { DeserializeCastActions(false); }
return m_CastActions;
}
set { m_CastActions = value; }
}
public ImpactAction[] ImpactActions {
get {
if (!Application.isPlaying && m_ImpactActions == null) { DeserializeImpactActions(false); }
return m_ImpactActions;
}
set { m_ImpactActions = value; }
}
public BeginEndAction[] EndActions {
get {
if (!Application.isPlaying && m_EndActions == null) { DeserializeEndActions(false); }
return m_EndActions;
}
set { m_EndActions = value; }
}
/// <summary>
/// Initialize the default values.
/// </summary>
protected override void Awake()
{
base.Awake();
m_CharacterTransform = m_Character.transform;
m_CharacterLocomotion = m_Character.GetCachedComponent<UltimateCharacterLocomotion>();
m_CharacterLayerManager = m_Character.GetCachedComponent<CharacterLayerManager>();
m_MagicItemPerspectiveProperties = m_ActivePerspectiveProperties as IMagicItemPerspectiveProperties;
DeserializeBeginActions(false);
DeserializeCastActions(false);
DeserializeImpactActions(false);
DeserializeEndActions(false);
if (m_CastActions != null) {
m_CastActionUsed = new bool[m_CastActions.Length];
m_CastActionCasted = new bool[m_CastActions.Length];
}
if (m_SurfaceIndicator != null) {
m_SurfaceIndicator.gameObject.SetActive(false);
}
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
// The local surface indicator should not show for remote players.
if (m_NetworkInfo != null && !m_NetworkInfo.IsLocalPlayer()) {
m_SurfaceIndicator = null;
}
#endif
}
/// <summary>
/// Deserialize the start actions.
/// </summary>
/// <param name="forceDeserialization">Should the actions be force deserialized?</param>
public void DeserializeBeginActions(bool forceDeserialization)
{
// The begin actions only need to be deserialized once.
if (m_BeginActions != null && !forceDeserialization) {
return;
}
if (m_BeginActionData != null) {
m_BeginActions = new BeginEndAction[m_BeginActionData.Length];
for (int i = 0; i < m_BeginActions.Length; ++i) {
m_BeginActions[i] = m_BeginActionData[i].DeserializeFields(MemberVisibility.Public) as BeginEndAction;
if (Application.isPlaying) {
m_BeginActions[i].Initialize(m_Character, this, true, i);
}
}
}
}
/// <summary>
/// Deserialize the cast actions.
/// </summary>
/// <param name="forceDeserialization">Should the actions be force deserialized?</param>
public void DeserializeCastActions(bool forceDeserialization)
{
// The cast actions only need to be deserialized once.
if (m_CastActions != null && !forceDeserialization) {
return;
}
if (m_CastActionData != null) {
m_CastActions = new CastAction[m_CastActionData.Length];
for (int i = 0; i < m_CastActions.Length; ++i) {
m_CastActions[i] = m_CastActionData[i].DeserializeFields(MemberVisibility.Public) as CastAction;
if (Application.isPlaying) {
m_CastActions[i].Initialize(m_Character, this, i);
}
}
}
}
/// <summary>
/// Deserialize the impact actions.
/// </summary>
/// <param name="forceDeserialization">Should the actions be force deserialized?</param>
public void DeserializeImpactActions(bool forceDeserialization)
{
// The impact actions only need to be deserialized once.
if (m_ImpactActions != null && !forceDeserialization) {
return;
}
if (m_ImpactActionData != null) {
m_ImpactActions = new ImpactAction[m_ImpactActionData.Length];
for (int i = 0; i < m_ImpactActions.Length; ++i) {
m_ImpactActions[i] = m_ImpactActionData[i].DeserializeFields(MemberVisibility.Public) as ImpactAction;
if (Application.isPlaying) {
m_ImpactActions[i].Initialize(m_Character, this, i);
}
}
}
}
/// <summary>
/// Deserialize the end actions.
/// </summary>
/// <param name="forceDeserialization">Should the actions be force deserialized?</param>
public void DeserializeEndActions(bool forceDeserialization)
{
// The end actions only need to be deserialized once.
if (m_EndActions != null && !forceDeserialization) {
return;
}
if (m_EndActionData != null) {
m_EndActions = new BeginEndAction[m_EndActionData.Length];
for (int i = 0; i < m_EndActions.Length; ++i) {
m_EndActions[i] = m_EndActionData[i].DeserializeFields(MemberVisibility.Public) as BeginEndAction;
if (Application.isPlaying) {
m_EndActions[i].Initialize(m_Character, this, false, i);
}
}
}
}
/// <summary>
/// Initializes any values that require on other components to first initialize.
/// </summary>
protected override void Start()
{
base.Start();
if (m_CastActions != null) {
// Awake is called when the item is ready to go (within start).
for (int i = 0; i < m_CastActions.Length; ++i) {
m_CastActions[i].Awake();
}
}
if (m_MagicItemPerspectiveProperties == null) {
m_MagicItemPerspectiveProperties = m_ActivePerspectiveProperties as IMagicItemPerspectiveProperties;
if (m_MagicItemPerspectiveProperties == null) {
Debug.LogError($"Error: The First/Third Person Magic Item Properties component cannot be found for the Item {name}." +
$"Ensure the component exists and the component's Action ID matches the Action ID of the Item ({m_ID}).");
}
}
}
/// <summary>
/// The item has been equipped by the character.
/// </summary>
public override void Equip()
{
base.Equip();
EventHandler.RegisterEvent<bool>(m_Character, "OnCharacterMoving", OnMoving);
EventHandler.RegisterEvent<Ability, bool>(m_Character, "OnCharacterAbilityActive", OnAbilityActive);
EventHandler.RegisterEvent<float, Vector3, Vector3, GameObject, Collider>(m_Character, "OnHealthDamage", OnDamage);
}
/// <summary>
/// Can the item be used?
/// </summary>
/// <param name="itemAbility">The itemAbility that is trying to use the item.</param>
/// <param name="abilityState">The state of the Use ability when calling CanUseItem.</param>
/// <returns>True if the item can be used.</returns>
public override bool CanUseItem(ItemAbility itemAbility, UseAbilityState abilityState)
{
if (!base.CanUseItem(itemAbility, abilityState)) {
return false;
}
if (abilityState == UseAbilityState.Update) {
// Don't allow the item to continue to be reused if it can no longer be used.
if (m_Used && CanStopItemUse()) {
return false;
}
} else if (abilityState == UseAbilityState.Start) {
// Certain items require the character to be grounded.
if (m_RequireGrounded && !m_CharacterLocomotion.Grounded) {
return false;
}
// The item can't start if it is in the process of being stopped.
if (m_Stopping) {
return false;
}
// If the cast isn't valid then the item shouldn't start.
if (m_Direction == CastDirection.Target) {
DetermineTargetColliders();
}
if (!DetermineCastValues(0, ref m_CastDirection, ref m_CastPosition, ref m_CastNormal)) {
return false;
}
}
return true;
}
/// <summary>
/// Can the ability be started?
/// </summary>
/// <param name="ability">The ability that is trying to start.</param>
/// <returns>True if the ability can be started.</returns>
public override bool CanStartAbility(Ability ability)
{
if (!m_StartAbility.AllowPositionalInput && ability is StoredInputAbilityBase) {
return false;
}
if (!(ability is Jump)) {
return true;
}
return !m_RequireGrounded;
}
/// <summary>
/// Starts the item use.
/// </summary>
/// <param name="itemAbility">The item ability that is using the item.</param>
public override void StartItemUse(ItemAbility itemAbility)
{
base.StartItemUse(itemAbility);
m_StartUseTime = Time.time;
m_StartCastTime = 0;
m_StartAbility = itemAbility;
// The Begin Actions allows the effect to play any starting effect.
StartStopBeginEndActions(true, true, false);
// Reset the cast action used time.
if (m_CastActionUsed != null) {
for (int i = 0; i < m_CastActionUsed.Length; ++i) {
m_CastActionUsed[i] = m_CastActionCasted[i] = false;
}
}
}
/// <summary>
/// Starts or stops the begin or end actions.
/// </summary>
/// <param name="beginActions">Should the begin actions be started?</param>
/// <param name="start">Should the actions be started?</param>
/// <param name="networkEvent">Should the event be sent over the network?</param>
public void StartStopBeginEndActions(bool beginActions, bool start, bool networkEvent)
{
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
if (networkEvent && m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) {
m_NetworkCharacter.StartStopBeginEndMagicActions(this, beginActions, start);
}
#endif
var actions = beginActions ? m_BeginActions : m_EndActions;
if (actions != null) {
for (int i = 0; i < actions.Length; ++i) {
if (start) {
actions[i].Start(m_MagicItemPerspectiveProperties.OriginLocation);
} else {
actions[i].Stop();
}
}
}
}
/// <summary>
/// Uses the item.
/// </summary>
public override void UseItem()
{
base.UseItem();
// If the item hasn't been used yet the begin actions stop.
if (m_StartCastTime == 0) {
m_StartCastTime = Time.time;
StartStopBeginEndActions(true, false, true);
}
if (m_CastActions != null) {
// Only cast the actions that haven't been casted yet.
var actionsCasted = !m_ContinuousCast;
if (!m_ContinuousCast) {
for (int i = 0; i < m_CastActionUsed.Length; ++i) {
if (!m_CastActionUsed[i]) {
actionsCasted = false;
break;
}
}
}
if (!actionsCasted) {
var useCount = m_Direction == CastDirection.Target ? m_TargetCount : 1;
for (int i = 0; i < useCount; ++i) {
// The values should be updated for the current cast.
if (m_Used || i > 0) {
if (!DetermineCastValues(i, ref m_CastDirection, ref m_CastPosition, ref m_CastNormal)) {
continue;
}
}
for (int j = 0; j < m_CastActions.Length; ++j) {
if (!m_ContinuousCast && m_CastActionUsed[j]) {
continue;
}
// The action may not need to cast if it's not time yet.
if (m_StartCastTime + m_CastActions[j].Delay > Time.time) {
continue;
}
if (m_CastActions[j].CastID == 0) {
m_CastActions[j].CastID = ++m_CastID;
}
m_CastActions[j].Cast(m_MagicItemPerspectiveProperties.OriginLocation, m_CastDirection, m_CastPosition);
m_CastActionCasted[j] = !m_ContinuousCast;
}
}
// Synchronize the used array with the casted array. This will allow the same action to be performed for multiple targets.
if (!m_ContinuousCast) {
for (int i = 0; i < m_CastActionUsed.Length; ++i) {
m_CastActionUsed[i] = m_CastActionCasted[i];
}
}
}
}
if (!m_Used) {
// The item isn't done being used until all actions have been used.
if (!m_ContinuousCast) {
for (int i = 0; i < m_CastActionUsed.Length; ++i) {
if (!m_CastActionUsed[i]) {
return;
}
}
}
// If the item was just used the end actions should start.
if (m_UseType == CastUseType.Single) {
StartStopBeginEndActions(false, true, true);
}
m_Inventory.AdjustItemIdentifierAmount(m_Item.ItemIdentifier, m_UseAmount);
m_Used = true;
}
}
/// <summary>
/// Determines the colliders that are hit by the target direction.
/// </summary>
private void DetermineTargetColliders()
{
if (m_TargetColliders == null) {
m_TargetColliders = new Collider[m_MaxCollisionCount];
m_TargetAngles = new float[m_MaxCollisionCount];
// Initialize to the max value.
for (int i = 0; i < m_TargetAngles.Length; ++i) {
m_TargetAngles[i] = int.MaxValue;
}
}
var hitCount = Physics.OverlapSphereNonAlloc(m_CharacterTransform.position, m_MaxDistance, m_TargetColliders, m_DetectLayers, QueryTriggerInteraction.Ignore);
if (hitCount == 0) {
return;
}
#if UNITY_EDITOR
if (hitCount == m_TargetColliders.Length) {
Debug.LogWarning("Warning: The hit count is equal to the max collider array size. This will cause objects to be missed. Consider increasing the max collision count size.");
}
#endif
for (int i = 0; i < hitCount; ++i) {
if (m_TargetColliders[i].transform.IsChildOf(m_CharacterTransform)) {
m_TargetAngles[i] = int.MaxValue;
continue;
}
// The target object needs to be within the field of view of the current object
var direction = m_TargetColliders[i].transform.position - m_CharacterTransform.position;
var angle = Vector3.Angle(direction, transform.forward);
if (angle < m_MaxAngle * 0.5f) {
// The target must be within sight.
var hitTransform = false;
if (Physics.Linecast(m_CharacterTransform.position, m_TargetColliders[i].transform.position, out var raycastHit, m_CharacterLayerManager.IgnoreInvisibleCharacterWaterLayers, QueryTriggerInteraction.Ignore)) {
if (raycastHit.transform.IsChildOf(m_TargetColliders[i].transform) || raycastHit.transform.IsChildOf(m_CharacterTransform)) {
hitTransform = true;
}
}
// Find the target that is most in front of the character.
m_TargetAngles[i] = hitTransform ? angle : int.MaxValue;
} else {
m_TargetAngles[i] = int.MaxValue;
}
}
// Sort by the angle. Return the min angle.
System.Array.Sort(m_TargetAngles, m_TargetColliders);
}
/// <summary>
/// Determines the values of the cast.
/// </summary>
/// <param name="index">The index of the target position to retrieve.</param>
/// <param name="direction">A reference to the target direction.</param>
/// <param name="position">A reference to the target position.</param>
/// <param name="normal">A reference to the target normal.</param>
/// <returns>True if the cast is valid.</returns>
private bool DetermineCastValues(int index, ref Vector3 direction, ref Vector3 position, ref Vector3 normal)
{
if (m_Direction == CastDirection.Forward || m_Direction == CastDirection.Indicate) {
Vector3 castPosition;
if (m_Direction == CastDirection.Forward) {
direction = m_UseLookSource ? m_LookSource.LookDirection(false) : m_CharacterTransform.forward;
castPosition = m_UseLookSource ? m_LookSource.LookPosition() : m_CharacterTransform.position;
} else { // Indicate.
direction = m_LookSource.LookDirection(false);
castPosition = m_LookSource.LookPosition();
}
m_CharacterLocomotion.EnableColliderCollisionLayer(false);
var validCast = Physics.SphereCast(castPosition - direction * m_Radius, m_Radius, direction, out var raycastHit, m_MaxDistance, m_DetectLayers, QueryTriggerInteraction.Ignore);
if (validCast) {
// The Cast Actions may indicate that the position is invalid.
if (m_CastActions != null) {
for (int i = 0; i < m_CastActions.Length; ++i) {
if (!m_CastActions[i].IsValidTargetPosition(raycastHit.point, raycastHit.normal)) {
return false;
}
}
}
position = raycastHit.point;
normal = raycastHit.normal;
}
m_CharacterLocomotion.EnableColliderCollisionLayer(true);
return validCast;
} else if (m_Direction == CastDirection.Target) {
if (index >= m_TargetAngles.Length || m_TargetAngles[index] == int.MaxValue) {
return false;
}
var targetTransform = m_TargetColliders[index].transform;
PivotOffset pivotOffset;
if ((pivotOffset = targetTransform.gameObject.GetCachedComponent<PivotOffset>()) != null) {
position = targetTransform.TransformPoint(pivotOffset.Offset);
} else {
position = targetTransform.position;
}
direction = (position - m_MagicItemPerspectiveProperties.OriginLocation.position).normalized;
normal = targetTransform.up;
return true;
}
// None direction.
direction = m_CharacterTransform.forward;
position = m_CharacterTransform.position;
normal = m_CharacterTransform.up;
return true;
}
/// <summary>
/// Allows the item to update while it is being used.
/// </summary>
public override void UseItemUpdate()
{
base.UseItemUpdate();
var beginEndActions = (m_Used ? m_EndActions : m_BeginActions);
if (beginEndActions != null) {
for (int i = 0; i < beginEndActions.Length; ++i) {
beginEndActions[i].Update();
}
}
}
/// <summary>
/// Is the item waiting to be used? This will return true if the item is waiting to be charged or pulled back.
/// </summary>
/// <returns>Returns true if the item is waiting to be used.</returns>
public override bool IsItemUsePending()
{
return !CanStopItemUse();
}
/// <summary>
/// Returns the substate index that the item should be in.
/// </summary>
/// <returns>the substate index that the item should be in.</returns>
public override int GetItemSubstateIndex()
{
return base.GetItemSubstateIndex() + (CanStopItemUse() ? m_CanStopSubstateIndexAddition : 0);
}
/// <summary>
/// The item has been used.
/// </summary>
public override void ItemUseComplete()
{
base.ItemUseComplete();
for (int i = 0; i < m_CastActions.Length; ++i) {
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) {
m_NetworkCharacter.StopMagicCast(this, i, m_CastActions[i].CastID);
}
#endif
m_CastActions[i].Stop();
}
}
/// <summary>
/// Draws an indicator when the direction is indicate.
/// </summary>
public void LateUpdate()
{
var direction = Vector3.zero;
var position = Vector3.zero;
var normal = Vector3.zero;
if (m_Direction != CastDirection.Indicate || !DetermineCastValues(-1, ref direction, ref position, ref normal)) {
if (m_SurfaceIndicator != null && m_SurfaceIndicator.gameObject.activeSelf) {
m_SurfaceIndicator.gameObject.SetActive(false);
}
return;
}
// The position is valid. Show an optional indicator.
if (m_SurfaceIndicator != null) {
m_SurfaceIndicator.rotation = Quaternion.LookRotation(Vector3.ProjectOnPlane(direction, normal));
m_SurfaceIndicator.position = position + m_SurfaceIndicator.TransformDirection(m_SurfaceIndicatorOffset);
if (!m_SurfaceIndicator.gameObject.activeSelf) {
m_SurfaceIndicator.gameObject.SetActive(true);
}
}
}
/// <summary>
/// A cast has caused a collision. Perform the impact actions.
/// </summary>
/// <param name="castID">The ID of the cast.</param>
/// <param name="source">The object that caused the cast.</param>
/// <param name="target">The object that was hit by the cast.</param>
/// <param name="hit">The raycast that caused the impact.</param>
public void PerformImpact(uint castID, GameObject source, GameObject target, RaycastHit hit)
{
if (!IsValidCollisionObject(target)) {
return;
}
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) {
m_NetworkCharacter.MagicImpact(this, castID, source, target, hit.point, hit.normal);
}
#endif
m_InterruptImpact = false;
if (m_ImpactActions != null) {
for (int i = 0; i < m_ImpactActions.Length; ++i) {
// Stop calling interrupt if InteruptImpact was triggered.
if (m_InterruptImpact) {
break;
}
m_ImpactActions[i].Impact(castID, source, target, hit);
}
}
// Execute an event to allow interested objects know about the collision.
EventHandler.ExecuteEvent(target, "OnMagicCastCollision", hit, m_SurfaceImpact);
}
/// <summary>
/// Returns true if the object can be collided with.
/// </summary>
/// <param name="other">The object that may be able to be collided with.</param>
/// <returns>True if the object can be collided with.</returns>
private bool IsValidCollisionObject(GameObject other)
{
if (m_Direction == CastDirection.Target) {
return MathUtility.InLayerMask(other.layer, m_DetectLayers);
}
return true;
}
/// <summary>
/// The impact actions should be interrupted. Consider the case where the first action deducts health and the second action plays a particle effect.
/// The particle effect should not be played if the object does not have the Health component.
/// </summary>
public void InterruptImpact()
{
m_InterruptImpact = true;
}
/// <summary>
/// Tries to stop the item use.
/// </summary>
public override void TryStopItemUse()
{
base.TryStopItemUse();
// The end actions aren't called until the continuous use item stops.
m_StopRequested = true;
if (!m_Stopping && CanStopItemUse()) {
m_Stopping = true;
m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters();
if (m_CastActions != null) {
for (int i = 0; i < m_CastActions.Length; ++i) {
m_CastActions[i].WillStop();
}
}
if (m_UseType == CastUseType.Continuous) {
StartStopBeginEndActions(false, true, true);
}
}
}
/// <summary>
/// Can the item use be stopped?
/// </summary>
/// <returns>True if the item use can be stopped.</returns>
public override bool CanStopItemUse()
{
// The item can always stop when the attributes are no longer valid.
if ((m_UseAttribute != null && !m_UseAttribute.IsValid(-m_UseAttributeAmount)) ||
(m_CharacterUseAttribute != null && !m_CharacterUseAttribute.IsValid(-m_CharacterUseAttributeAmount))) {
return true;
}
return m_UseType == CastUseType.Single ||
(m_StopRequested && (m_MinContinuousUseDuration == -1 || (m_MinContinuousUseDuration > 0 && m_StartUseTime + m_MinContinuousUseDuration < Time.time)));
}
/// <summary>
/// Stops the item use.
/// </summary>
public override void StopItemUse()
{
base.StopItemUse();
// If Force Stop is true then the cast was interrupted. Reset the objects.
if (m_ForceStop) {
ItemUseComplete();
StartStopBeginEndActions(true, false, false);
}
m_Used = false;
m_Stopping = m_StopRequested = false;
m_ForceStop = false;
StartStopBeginEndActions(false, false, false);
}
/// <summary>
/// The item has started to be unequipped by the character.
/// </summary>
public override void StartUnequip()
{
base.StartUnequip();
if (m_SurfaceIndicator != null && m_SurfaceIndicator.gameObject.activeSelf) {
m_SurfaceIndicator.gameObject.SetActive(false);
}
}
/// <summary>
/// The item has been unequipped by the character.
/// </summary>
public override void Unequip()
{
base.Unequip();
EventHandler.UnregisterEvent<bool>(m_Character, "OnCharacterMoving", OnMoving);
EventHandler.UnregisterEvent<Ability, bool>(m_Character, "OnCharacterAbilityActive", OnAbilityActive);
EventHandler.UnregisterEvent<float, Vector3, Vector3, GameObject, Collider>(m_Character, "OnHealthDamage", OnDamage);
}
/// <summary>
/// The character has started to or stopped moving.
/// </summary>
/// <param name="moving">Is the character moving?</param>
private void OnMoving(bool moving)
{
// Stop the item if the character starts to move and the cast should be interrupted on movement.
if (moving && (m_InterruptSource & CastInterruptSource.Movement) != 0 && IsItemInUse()) {
m_ForceStop = true;
m_StartAbility.StopAbility(true);
}
}
/// <summary>
/// The character's ability has been started or stopped.
/// </summary>
/// <param name="ability">The ability which was started or stopped.</param>
/// <param name="active">True if the ability was started, false if it was stopped.</param>
private void OnAbilityActive(Ability ability, bool active)
{
if (!active || (!(ability is Jump) && !(ability is Fall))) {
return;
}
// Stop the item if the character starts to jump or fall and the cast should be interrupted on movement.
if ((m_InterruptSource & CastInterruptSource.Movement) != 0 && IsItemInUse()) {
m_ForceStop = true;
m_StartAbility.StopAbility(true);
}
}
/// <summary>
/// The character has taken damage.
/// </summary>
/// <param name="amount">The amount of damage taken.</param>
/// <param name="position">The position of the damage.</param>
/// <param name="force">The amount of force applied to the object while taking the damage.</param>
/// <param name="attacker">The GameObject that did the damage.</param>
/// <param name="hitCollider">The Collider that was hit.</param>
private void OnDamage(float amount, Vector3 position, Vector3 force, GameObject attacker, Collider hitCollider)
{
// The item can stop the use ability when the character takes damage.
if ((m_InterruptSource & CastInterruptSource.Damage) != 0 && IsItemInUse()) {
m_ForceStop = true;
m_StartAbility.StopAbility(true);
}
}
/// <summary>
/// The camera perspective between first and third person has changed.
/// </summary>
/// <param name="firstPersonPerspective">Is the character in a first person perspective?</param>
protected override void OnChangePerspectives(bool firstPersonPerspective)
{
base.OnChangePerspectives(firstPersonPerspective);
var targetMagicItemPerspectiveProperties = m_ActivePerspectiveProperties as IMagicItemPerspectiveProperties;
// The OriginLocation cannot be null.
if (targetMagicItemPerspectiveProperties.OriginLocation == null) {
Debug.LogError($"Error: The OriginLocation is null on the {name} MagicItemPerspectiveProperties.");
return;
}
m_MagicItemPerspectiveProperties = targetMagicItemPerspectiveProperties;
if (m_BeginActions != null) {
for (int i = 0; i < m_BeginActions.Length; ++i) {
m_BeginActions[i].OnChangePerspectives(m_MagicItemPerspectiveProperties.OriginLocation);
}
}
if (m_CastActions != null) {
for (int i = 0; i < m_CastActions.Length; ++i) {
m_CastActions[i].OnChangePerspectives(m_MagicItemPerspectiveProperties.OriginLocation);
}
}
if (m_EndActions != null) {
for (int i = 0; i < m_EndActions.Length; ++i) {
m_EndActions[i].OnChangePerspectives(m_MagicItemPerspectiveProperties.OriginLocation);
}
}
}
/// <summary>
/// The item has been destroyed.
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
if (m_BeginActions != null) {
for (int i = 0; i < m_BeginActions.Length; ++i) {
m_BeginActions[i].OnDestroy();
}
}
if (m_CastActions != null) {
for (int i = 0; i < m_CastActions.Length; ++i) {
m_CastActions[i].OnDestroy();
}
}
if (m_ImpactActions != null) {
for (int i = 0; i < m_ImpactActions.Length; ++i) {
m_ImpactActions[i].OnDestroy();
}
}
if (m_EndActions != null) {
for (int i = 0; i < m_EndActions.Length; ++i) {
m_EndActions[i].OnDestroy();
}
}
}
}
}