init
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user