using System; using System.Collections.Generic; using Grid; using UnityEngine; using UnityEngine.Serialization; namespace Actions { public class ShootAction : BaseAction { private enum State { Aiming, Shooting, Cooloff } public Unit TargetUnit { get; set; } public int maxShootDistance = 7; [SerializeField] private LayerMask obstaclesLayerMask; private State state; private float stateTimer; private bool canShootBullet; 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; public event EventHandler OnShoot; public static event EventHandler OnAnyShoot; public class ShootEventArgs : EventArgs { public Unit TargetUnit; public Unit ShootingUnit; } protected override void Awake() { base.Awake(); 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(); } 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); 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; } }