using System; using System.Collections.Generic; using Grid; using UnityEngine; namespace Actions { public class ShootAction : BaseAction { private const float ROTATION_SPEED = 10f; private const float SHOOTING_STATE_TIME = .2f; private const float COOL_OFF_STATE_TIME = 1f; private const float AIMING_STATE_TIME = 1.5f; private const float UNIT_SHOULDER_HEIGHT = 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; aimDirection.y = 0f; transform.forward = Vector3.Slerp(transform.forward, aimDirection, ROTATION_SPEED * 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 = SHOOTING_STATE_TIME; break; case State.Shooting: state = State.Cooloff; stateTimer = COOL_OFF_STATE_TIME; 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 = AIMING_STATE_TIME; ActionStart(onActionComplete); } public override List GetValidActionGridPositionList() => GetValidActionGridPositionList(Unit.GridPosition); private List GetValidActionGridPositionList(GridPosition unitGridPosition) { List validGridPositionList = new(); for (int xPosition = -MaxShootDistance; xPosition <= MaxShootDistance; xPosition++) { for (int zPosition = -MaxShootDistance; zPosition <= MaxShootDistance; zPosition++) { for (int floor = -MaxShootDistance; floor < MaxShootDistance; floor++) { GridPosition offsetGridPosition = new(xPosition, zPosition, floor); GridPosition testGridPosition = unitGridPosition + offsetGridPosition; if (!LevelGrid.Instance.IsValidGridPosition(testGridPosition)) continue; //Only return valid grid positions if (Mathf.Abs(xPosition) + Mathf.Abs(zPosition) > 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 * UNIT_SHOULDER_HEIGHT, 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; } } }