using System; using System.Collections.Generic; using Grid; using UnityEngine; namespace Actions { public class ShootAction : BaseAction { private const float rotationSpeed = 10f; private const float shootingStateTime = .2f; private const float coolOffStateTime = 1f; private const float aimingStateTime = 1.5f; private const float unitShoulderHeight = 1.7f; [SerializeField] private LayerMask obstaclesLayerMask; private bool canShootBullet; private State state; private float stateTimer; public Unit TargetUnit { get; private set; } public Unit ShootingUnit { get; private set; } public int MaxShootDistance { get; private set; } protected override void Awake() { base.Awake(); MaxShootDistance = 7; ActionName = "Shoot"; } private void Update() { if (!IsActive) return; stateTimer -= Time.deltaTime; switch (state) { case State.Aiming: Vector3 aimDirection = (TargetUnit.GetWorldPosition() - Unit.GetWorldPosition()).normalized; transform.forward = Vector3.Lerp(transform.forward, aimDirection, rotationSpeed * Time.deltaTime); break; case State.Shooting: if (canShootBullet) { Shoot(); canShootBullet = false; } break; case State.Cooloff: break; } if (stateTimer <= 0f) NextState(); } public event EventHandler OnShoot; public static event EventHandler OnAnyShoot; private void Shoot() { OnAnyShoot?.Invoke(this, new() { TargetUnit = TargetUnit, ShootingUnit = Unit }); OnShoot?.Invoke(this, new() { TargetUnit = TargetUnit, ShootingUnit = Unit }); TargetUnit.Damage(40); } private void NextState() { switch (state) { case State.Aiming: state = State.Shooting; stateTimer = shootingStateTime; break; case State.Shooting: state = State.Cooloff; stateTimer = coolOffStateTime; break; case State.Cooloff: ActionComplete(); break; } } public override void TakeAction(GridPosition gridPosition, Action onActionComplete) { IsActive = true; TargetUnit = LevelGrid.Instance.GetUnitAtGridPosition(gridPosition); canShootBullet = true; state = State.Aiming; stateTimer = aimingStateTime; ActionStart(onActionComplete); } public override List GetValidActionGridPositionList() => GetValidActionGridPositionList(Unit.GridPosition); private List GetValidActionGridPositionList(GridPosition unitGridPosition) { List validGridPositionList = new(); for (int x = -MaxShootDistance; x <= MaxShootDistance; x++) { for (int z = -MaxShootDistance; z <= MaxShootDistance; z++) { GridPosition offsetGridPosition = new(x, z, 0); GridPosition testGridPosition = unitGridPosition + offsetGridPosition; if (!LevelGrid.Instance.IsValidGridPosition(testGridPosition)) continue; //Only return valid grid positions if (Mathf.Abs(x) + Mathf.Abs(z) > MaxShootDistance) continue; //Out of Range if (!LevelGrid.Instance.HasAnyUnitOnGridPosition(testGridPosition)) continue; //Grid position is empty, no unit Unit unitAtGridPosition = LevelGrid.Instance.GetUnitAtGridPosition(testGridPosition); if (unitAtGridPosition.IsEnemy == Unit.IsEnemy) continue; //Both units are on the same 'team' TargetUnit = unitAtGridPosition; Vector3 unitWorldPosition = LevelGrid.Instance.GetWorldPosition(unitGridPosition); Vector3 shootDir = (TargetUnit.GetWorldPosition() - unitWorldPosition).normalized; if (Physics.Raycast(unitWorldPosition + Vector3.up * unitShoulderHeight, shootDir, Vector3.Distance(unitWorldPosition, TargetUnit.GetWorldPosition()), obstaclesLayerMask)) continue; // Blocked by an obstacle validGridPositionList.Add(testGridPosition); } } return validGridPositionList; } protected override EnemyAIAction GetEnemyAIAction(GridPosition gridPosition) { Unit targetUnit = LevelGrid.Instance.GetUnitAtGridPosition(gridPosition); return new(gridPosition, 100 + Mathf.RoundToInt((1 - targetUnit.HealthSystem.HealthNormalized) * 100f)); } public int GetTargetCountAtPosition(GridPosition gridPosition) => GetValidActionGridPositionList(gridPosition).Count; private enum State { Aiming, Shooting, Cooloff } public class ShootEventArgs : EventArgs { public Unit ShootingUnit; public Unit TargetUnit; } } }