using System; using System.Collections.Generic; using System.Linq; using Sirenix.Utilities; using UnityEngine; using VOID.Generic.Dict; using VOID.Generic.Dict.Ability; using VOID.Generic.Objects.Abstract; using VOID.Generic.Objects.Unit.Actions.Exceptions; using VOID.Generic.Objects.Unit.Events; using VOID.Generic.Projectile; using VOID.Generic.Util; using VOID.ScriptableObjects; using VOID.ScriptableObjects.Abilities; using Object = UnityEngine.Object; namespace VOID.Generic.Objects.Unit.Actions { /// /// Makes cast given . ///

/// :
/// ///
public class AbilityAction : AbstractAction { private class Target { public readonly AbstractObject targetObject; public readonly Vector3 targetPosition; public Target(AbstractObject targetObject, Vector3 targetPosition) { this.targetObject = targetObject; this.targetPosition = targetPosition; } } private readonly UnitObject _unit; private readonly BasicAbility _ability; private readonly Target[] _targets; private int _projectilesEnded; private AbilityCastEvent _abilityEvent; public AbilityAction(UnitObject unit, BasicAbility ability) : this(unit, ability, Array.Empty()) { } public AbilityAction(UnitObject unit, BasicAbility ability, Vector3 target) : this(unit, ability, new[] { new Target(null, target) }) { } public AbilityAction(UnitObject unit, BasicAbility ability, AbstractObject target) : this(unit, ability, new[] { new Target(target, target.transform.position + Vector3.up * target.basicData.height/2) }) { } private AbilityAction(UnitObject unit, BasicAbility ability, Target target) : this(unit, ability, new[] { target }) { } public AbilityAction(UnitObject unit, BasicAbility ability, IEnumerable targets) : this(unit, ability, targets.Select(p => new Target(null, p)).ToArray()) { } public AbilityAction(UnitObject unit, BasicAbility ability, IEnumerable targets) : this(unit, ability, targets.Select(o => new Target(o, o.transform.position+ Vector3.up * o.basicData.height/2)).ToArray()) { } private AbilityAction(UnitObject unit, BasicAbility ability, Target[] targets) { _unit = unit; _ability = ability; _targets = targets; } public override bool IsInRange() { return _targets.IsNullOrEmpty() || _targets.All(target => target.targetObject ? RangeUtil.IsInRange(_unit, target.targetObject, _ability.range) : RangeUtil.IsInRange(_unit, target.targetPosition, _ability.range)); } public override bool CanBeStopped() => _abilityEvent is null || _abilityEvent.prevented; public override void CanDoIt() { Check( state == ActionState.Ready, UnitActionErrorType.ActionStateIsNotReady); if (_unit.basicState.turnManager) { Check( _unit.unitState.isSelfTurn, UnitActionErrorType.UnitSelfTurnNotActive); Check( _unit.unitData.currentResources.actionPoints >= _ability.actionPointCost, UnitActionErrorType.UnitNotEnoughActionPoints); Check( _unit.unitData.currentResources.movePoints >= _ability.movePointCost, UnitActionErrorType.UnitNotEnoughMovePoints); } Check( _ability is not BasicAttackAbility || _unit.unitData.canAttack, UnitActionErrorType.ThisUnitCantAttack); Check( _unit.unitState.isControllable, UnitActionErrorType.UnitStateIsNotControllable); Check( IsInRange(), UnitActionErrorType.NotInRange); Check( _ability.requirements.All(requirement => requirement.Check(_unit)), UnitActionErrorType.RequirementsNotMeetToUseThatAbility); Check( _ability.projectileSteps.First().validTargets.All(validTarget => _targets.All(target => validTarget.IsValid(_unit, target.targetPosition, target.targetObject))), UnitActionErrorType.WrongTargetsSelectedToUseThatAbility); Check( _ability.aimingType is AbilityAimingType.Blank || _ability.castUsages == _targets.Length, UnitActionErrorType.WrongNumberOfTargetsToUseThatAbility); } protected override void OnDoIt() { if (_unit.basicState.turnManager) { _unit.unitData.UseResource(UnitResourceType.ActionPoints, _ability.actionPointCost); _unit.unitData.UseResource(UnitResourceType.MovePoints, _ability.movePointCost); } _abilityEvent = new AbilityCastEvent { caster = _unit, ability = _ability }; _unit.unitEvents.onStartAbilityBefore.Invoke(_abilityEvent); Check(!_abilityEvent.prevented, UnitActionErrorType.AbilityIsPrevented); _unit.unitEvents.onStartAbilityAfter.Invoke(_abilityEvent); if (_ability.castingType == AbilityCastingType.Instant) { DoAbility(null); EndIt(); return; } var animationGenericEvent = new AnimationGenericEvent { animationType = AnimationType.Ability, unit = _unit, ability = _ability }; if (_targets.Length > 0) new RotateAction(_unit, _targets[0].targetPosition).DoIt(); if (_ability.castingType is AbilityCastingType.Channel && _ability.castUsages > 0) { // When channeling - ability ends when all projectiles end _unit.unitEvents.onAnimation[AnimationType.Ability].onPerform.AddListener(DoAbility); _unit.unitAnimator.SetLoopAnimation(AnimationType.Ability, _ability.abilityLoopAnimationList); _unit.unitAnimator.StartLoopAnimation(animationGenericEvent); } else { // Otherwise - ability ends with animation _unit.unitEvents.onAnimation[AnimationType.Ability].onPerform.AddListener(DoAbility); _unit.unitEvents.onAnimation[AnimationType.Ability].onEnd.AddListener(OnAbilityEnd); _unit.unitAnimator.SetAnimation(AnimationType.Ability, _ability.abilityAnimationList); _unit.unitAnimator.StartAnimation(animationGenericEvent); } } private void DoAbility(AnimationGenericEvent tEvent) { BeforePerform(); if (_ability.applyOnCasterMoment is AbilityExecutionMoment.AfterCast) ApplyEffectsToCaster(); _targets.ForEach((target, index) => { // Predefined object is only for visual things // Else - new empty projectile object var projectile = _ability.projectile ? Object.Instantiate(_ability.projectile.gameObject).GetComponent() : new GameObject().AddComponent(); projectile.StartProjectile(index, _unit, _unit.unitData.castPosition.position, target.targetPosition, _ability.projectileSteps); projectile.onHit.AddListener(OnProjectileHit); projectile.onEnd.AddListener(OnProjectileEnd); _unit.unitEvents.onAbilityProjectileStart.Invoke(new AbilityProjectileEvent { caster = _unit, ability = _ability, projectileIndex = index, projectile = projectile }); }); AfterPerform(); } private void OnProjectileHit(ProjectileObject projectile, AbstractObject hitObject) { var abilityHitEvent = new AbilityHitEvent { caster = _unit, target = hitObject, ability = _ability }; hitObject.basicEvents.onAbilityHitBefore.Invoke(abilityHitEvent); _unit.unitEvents.onAbilityHitBefore.Invoke(abilityHitEvent); // We cant run Check here, because this OnHit can be one of many - just do nothing more for this projectile if (abilityHitEvent.prevented) return; hitObject.basicEvents.onAbilityHitAfter.Invoke(abilityHitEvent); _unit.unitEvents.onAbilityHitAfter.Invoke(abilityHitEvent); ApplyEffectsToTarget(hitObject); } private void OnProjectileEnd(ProjectileObject projectile) { _projectilesEnded++; _unit.unitEvents.onAbilityProjectileEnd.Invoke(new AbilityProjectileEvent { caster = _unit, ability = _ability, projectileIndex = projectile.index, projectile = projectile }); if (_ability.castingType is AbilityCastingType.Channel && _projectilesEnded == _ability.castUsages) { _unit.unitAnimator.EndLoopAnimation(); EndIt(); } } private void ApplyEffectsToTarget(AbstractObject target) { if (_ability.applyOnTarget.Count <= 0) return; // Because ability have defined only effects we need create dummy status // IMPORTANT - It is hidden and instantly starts and then ends! var targetStatus = ScriptableObject.CreateInstance(); targetStatus.effects = _ability.applyOnTarget; targetStatus.hidden = true; target.statusManager.AddStatus(targetStatus, _unit, false); } private void ApplyEffectsToCaster() { if (_ability.applyOnCaster.Count <= 0) return; // Because ability have defined only effects we need create dummy status // IMPORTANT - It is hidden and instantly starts and then ends! var casterStatus = ScriptableObject.CreateInstance(); casterStatus.effects = _ability.applyOnCaster; casterStatus.hidden = true; _unit.statusManager.AddStatus(casterStatus, _unit, false); } private void OnAbilityEnd(AnimationGenericEvent tEvent) { EndIt(); } protected override void OnCancelIt() { _unit.unitAnimator.EndLoopAnimation(); _unit.unitEvents.onAnimation[AnimationType.Ability].onPerform.RemoveListener(DoAbility); _unit.unitEvents.onAnimation[AnimationType.Ability].onEnd.RemoveListener(OnAbilityEnd); _unit.unitEvents.onEndAbility.Invoke(_abilityEvent); } protected override void OnEndIt() { _unit.unitAnimator.EndLoopAnimation(); if (_ability.applyOnCasterMoment is AbilityExecutionMoment.AfterEnd) ApplyEffectsToCaster(); _unit.unitEvents.onAnimation[AnimationType.Ability].onPerform.RemoveListener(DoAbility); _unit.unitEvents.onAnimation[AnimationType.Ability].onEnd.RemoveListener(OnAbilityEnd); _unit.unitEvents.onEndAbility.Invoke(_abilityEvent); } } }