This commit is contained in:
2026-07-09 21:33:33 +02:00
commit 84a2a365f3
2364 changed files with 950134 additions and 0 deletions
@@ -0,0 +1,298 @@
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);
}
}
}