260 lines
10 KiB
C#
260 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
using VOID.Generic.Dict.Ability;
|
|
using VOID.Generic.Objects.Unit;
|
|
using VOID.Generic.Objects.Unit.Actions;
|
|
using VOID.Generic.Objects.Unit.Events;
|
|
using VOID.Generic.Player.Input;
|
|
using VOID.Generic.Player.Util;
|
|
using VOID.UserInterface;
|
|
using VOID.UserInterface.Game.Party;
|
|
using VOID.Generic.Util;
|
|
using VOID.ScriptableObjects.Abilities;
|
|
using PlayerInputManager = VOID.Generic.Player.Input.PlayerInputManager;
|
|
|
|
namespace VOID.Generic.Player
|
|
{
|
|
/// <summary>
|
|
/// When <see cref="PlayerController.activeUnit"/> starts <see cref="CastingAction"/> then player can select
|
|
/// targets by <see cref="AbilityAimingType"/> selected in <see cref="BasicAbility"/>. After successfull selection
|
|
/// and usage accepting then active unit starts <see cref="AbilityAction"/>.
|
|
/// <br/>
|
|
/// For each target <see cref="ProjectileVisualizer"/> is created.
|
|
/// </summary>
|
|
[DefaultExecutionOrder(-1)]
|
|
public class PlayerAbilityCasting : MonoBehaviour
|
|
{
|
|
[SerializeField] private Material _trajectoryMaterial;
|
|
[SerializeField] private Material _areaOfEffectMaterial;
|
|
private UnitObject _caster;
|
|
private BasicAbility _ability;
|
|
private readonly List<GameObject> _targets = new();
|
|
private bool _isTargetInRange = true;
|
|
private bool _isValidTarget = true;
|
|
|
|
private void Start()
|
|
{
|
|
PlayerController.current.OnActiveUnitChange += OnActiveUnitChange;
|
|
CastingInputManager.current.confirm.performed += OnConfirm;
|
|
CastingInputManager.current.cancel.performed += OnCancel;
|
|
|
|
enabled = false;
|
|
}
|
|
|
|
private void FixedUpdate()
|
|
{
|
|
switch (_ability.aimingType)
|
|
{
|
|
case AbilityAimingType.Blank:
|
|
// Blank have zero targets - just do nothing
|
|
break;
|
|
case AbilityAimingType.Self:
|
|
// Always one target - self
|
|
_targets.First().transform.position = _caster.transform.position;
|
|
break;
|
|
case AbilityAimingType.Target:
|
|
// Default target aiming - player needs to select targets
|
|
MoveCurrentTargetToCursor();
|
|
RotateUnitTowardsFirstTarget();
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stick current target to position which cursor pointing.
|
|
/// </summary>
|
|
private void MoveCurrentTargetToCursor()
|
|
{
|
|
// Zero targets should not be possible here thanks to validations in BasicAbility
|
|
if (_targets.Count == 0)
|
|
{
|
|
Debug.LogError($"{AbilityAimingType.Target} with exactly ZERO targets defined in {_ability} is invalid!");
|
|
return;
|
|
}
|
|
|
|
// Get position which mouse pointing, if nothing found then player poiting UI or literaly nothing?
|
|
var select = SelectUtil.Select();
|
|
var targetPosition = select.position;
|
|
var targetObject = select.selectObject;
|
|
if (!select.hit) return;
|
|
if (select.onUI && select.unitPortraitUI == null) return;
|
|
|
|
// Directional target correction - forces aim at maximum distance in given direction ignoring height
|
|
// By default this correction can't aim at objects - it is purely positional
|
|
if (_ability.aimingCorrectionType is AbilityAimingCorrectionType.Directional)
|
|
{
|
|
targetPosition = TargetPositionCorrection_Directional(targetPosition);
|
|
targetObject = null;
|
|
}
|
|
|
|
// Target must be allowed by ability
|
|
_isValidTarget = _ability.projectileSteps.First().validTargets.All(validTarget =>
|
|
validTarget.IsValid(_caster, targetPosition, targetObject));
|
|
|
|
// Target must be in range!
|
|
_isTargetInRange = RangeUtil.IsInRange(_caster, targetPosition, _ability.range);
|
|
|
|
// Player can have multiple target, but now we manage only latest one's position
|
|
var currentTarget = _targets.Last();
|
|
currentTarget.SetActive(_isValidTarget && _isTargetInRange);
|
|
|
|
// Current target to cursor's pointing position
|
|
currentTarget.transform.position = targetPosition;
|
|
}
|
|
|
|
private Vector3 TargetPositionCorrection_Directional(Vector3 toPosition)
|
|
{
|
|
var casterPosition = _caster.transform.position;
|
|
toPosition = Vector3.Scale(toPosition, new Vector3(1, 0, 1));
|
|
var fromPosition = Vector3.Scale(casterPosition, new Vector3(1, 0, 1));
|
|
var direction = Vector3.Normalize(toPosition - fromPosition);
|
|
return casterPosition + direction * (_ability.range + _caster.basicData.radius);
|
|
}
|
|
|
|
/// <summary>
|
|
/// While selecting first target make <see cref="UnitObject"/> look it that direction.
|
|
/// </summary>
|
|
private void RotateUnitTowardsFirstTarget()
|
|
{
|
|
if (_targets.Count == 1) _caster.OrderInstantly(new RotateAction(_caster, _targets.Last().transform.position));
|
|
}
|
|
|
|
/// <summary>
|
|
/// When player accepting this target's position. Also check if this position is valid!
|
|
/// </summary>
|
|
private void OnConfirm(InputAction.CallbackContext callbackContext)
|
|
{
|
|
// On UI allow only selecting unit via its portrait
|
|
if (GameUI.current.isCursorOverUI && GameUI.current.PickTopComponentUI<UnitPortraitUI>() == null) return;
|
|
|
|
// Not in player's unit range OR ability dont allow that kind of target
|
|
if (!_isTargetInRange || !_isValidTarget) return;
|
|
|
|
if (_targets.Count == _ability.castUsages) ConfirmAbility();
|
|
else if (_targets.Count > 0) InitNextTarget();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whole casting completed, proceed to start ability.
|
|
/// </summary>
|
|
private void ConfirmAbility()
|
|
{
|
|
// OrderStop() also will stop currently running CastingAction which also runs this::EndCasting()
|
|
// Thats why we need to prepare AbilityAction before that
|
|
var abilityAction = new AbilityAction(_caster, _ability, _targets.Select(target => target.transform.position));
|
|
_caster.OrderStop();
|
|
_caster.Order(abilityAction);
|
|
}
|
|
|
|
/// <summary>
|
|
/// When player want to cancel current target or cancel whole casting.
|
|
/// </summary>
|
|
private void OnCancel(InputAction.CallbackContext callbackContext)
|
|
{
|
|
// Cancel current target if there is any (ability can be targetless and only need confirmation)
|
|
if (_targets.Count > 0) UndoTarget();
|
|
|
|
// Stops whole casting if there is no targeting active
|
|
if (_targets.Count == 0) CancelAbility();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancel current target so it can go back to previous target selecting.
|
|
/// </summary>
|
|
private void UndoTarget()
|
|
{
|
|
var lastIndex = _targets.Count-1;
|
|
Destroy(_targets.Last());
|
|
_targets.RemoveAt(lastIndex);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancel whole casting process.
|
|
/// </summary>
|
|
private void CancelAbility()
|
|
{
|
|
EndCasting();
|
|
_caster.OrderStop();
|
|
}
|
|
|
|
/// <summary>
|
|
/// When player changes <see cref="PlayerController.activeUnit"/>:<br/>
|
|
/// 1. Stops casting for previous unit if active<br/>
|
|
/// 2. Waiting for new unit casting action
|
|
/// </summary>
|
|
private void OnActiveUnitChange(UnitObject unit)
|
|
{
|
|
if (_caster)
|
|
{
|
|
_caster.unitEvents.onStartCasting.RemoveListener(StartCasting);
|
|
EndCasting();
|
|
}
|
|
_caster = unit;
|
|
_caster.unitEvents.onStartCasting.AddListener(StartCasting);
|
|
}
|
|
|
|
/// <summary>
|
|
/// When player order <see cref="PlayerController.activeUnit"/> to <see cref="CastingAction"/>.
|
|
/// </summary>
|
|
private void StartCasting(CastingEvent castingEvent)
|
|
{
|
|
PlayerInputManager.current.enabled = false;
|
|
CastingInputManager.current.enabled = true;
|
|
_ability = castingEvent.ability;
|
|
_targets.Clear();
|
|
enabled = true;
|
|
InitNextTarget();
|
|
|
|
_caster.unitEvents.onEndCasting.AddListener(EndCasting);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prepares next target to be visualized on scene.
|
|
/// </summary>
|
|
private void InitNextTarget()
|
|
{
|
|
// No more targets to be visualized - usually because ability don't have any targets to select
|
|
if (_ability.castUsages <= _targets.Count) return;
|
|
|
|
var visualizerGameObject = new GameObject("AbilityTarget-" + _targets.Count);
|
|
var projectileVisualizer = visualizerGameObject.AddComponent<ProjectileVisualizer>();
|
|
|
|
projectileVisualizer.Init(
|
|
_caster,
|
|
_ability.projectileSteps,
|
|
_trajectoryMaterial,
|
|
_areaOfEffectMaterial,
|
|
visualizerGameObject.transform
|
|
);
|
|
|
|
_targets.Add(visualizerGameObject);
|
|
}
|
|
|
|
/// <summary>
|
|
/// When player order <see cref="PlayerController.activeUnit"/> to stop casting.
|
|
/// </summary>
|
|
private void EndCasting(CastingEvent castingEvent)
|
|
{
|
|
EndCasting();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stop whole target selecting and projectile visualizing.
|
|
/// </summary>
|
|
private void EndCasting()
|
|
{
|
|
_caster.unitEvents.onEndCasting.RemoveListener(EndCasting);
|
|
_targets.ForEach(Destroy);
|
|
_targets.Clear();
|
|
enabled = false;
|
|
|
|
CastingInputManager.current.enabled = false;
|
|
PlayerInputManager.current.enabled = true;
|
|
}
|
|
}
|
|
} |