298 lines
12 KiB
C#
298 lines
12 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Makes <see cref="UnitObject"/> cast given <see cref="BasicAbility"/>.
|
|
/// <br/><br/>
|
|
/// <see cref="AbstractAction"/>:<br/>
|
|
/// <inheritdoc cref="AbstractAction"/>
|
|
/// </summary>
|
|
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<Target>())
|
|
{
|
|
}
|
|
|
|
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<Vector3> targets)
|
|
: this(unit, ability, targets.Select(p => new Target(null, p)).ToArray())
|
|
{
|
|
}
|
|
|
|
public AbilityAction(UnitObject unit, BasicAbility ability, IEnumerable<AbstractObject> 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<ProjectileObject>()
|
|
: new GameObject().AddComponent<ProjectileObject>();
|
|
|
|
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<BasicStatus>();
|
|
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<BasicStatus>();
|
|
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);
|
|
}
|
|
}
|
|
} |