/// <summary> /// Calculate the max range this turret can shoot at with at least one weapon, /// IN UNITY UNITS. /// </summary> /// This is a method to avoid storing duplicate information, and /// because we may want to ignore disabled turrets, or turrets /// that can't shoot at a specific target etc. /// /// TODO: Code duplication can be reduced if we only implement this in /// the turret class and have a fake toplevel turret we call this method on, /// but a fake turret like that also adds complexity, hard to decide. private float MaxRange( TargetTuple target, bool includeDirectFire, bool includeIndirectFire) { float maxRange = 0; foreach (Turret turret in Children) { float turretMax; if (includeDirectFire && includeIndirectFire) { turretMax = turret.MaxRange(target); } else if (includeDirectFire) { turretMax = turret.MaxRangeDirectFire(target); } else if (includeIndirectFire) { turretMax = turret.MaxRangeIndirectFire(target); } else { turretMax = 0; } if (maxRange < turretMax) { maxRange = turretMax; } } return(maxRange); }
private void Update() { foreach (Turret turret in Children) { turret.HandleUpdate(); } float distanceToTarget = 99999; if (_target != null && _target.Exists) { // TODO move most of the Update logic to the respective turrets // (likely to a new member class of them called 'TargetingStrategy') distanceToTarget = Vector3.Distance( Unit.transform.position, _target.Position); if (_fireRange > distanceToTarget && _movingTowardsTarget) { StopMoving(); } else if (distanceToTarget >= _fireRange && !_movingTowardsTarget) { // todo start chasing target again.. } MaybeDropOutOfRangeTarget(); if (_target.IsUnit && !_target.Enemy.VisionComponent.IsSpotted) { Logger.LogTargeting( "Dropping a target because it is no longer spotted.", gameObject); _target = null; } } if (_target != null && _target.Exists) { bool targetInRange = !_movingTowardsTarget; bool shotFired = false; foreach (Turret turret in Children) { shotFired |= turret.MaybeShoot(distanceToTarget, isServer); } // If shooting at the ground, stop after the first shot: if (shotFired && _target.IsGround) { _target = null; _explicitTarget = null; foreach (Turret turret in Children) { turret.SetExplicitTarget(_explicitTarget); } } } else { FindAndTargetClosestEnemy(); } }
private void FireWeapon(TargetTuple target, Vector3 displacement, bool isServer) { // sound _source.PlayOneShot(_shotSound, _shotVolume); // particle _shotEffect.Play(); if (_muzzleFlashEffect != null) { _muzzleFlashEffect.Play(); } if (isServer) { if (target.IsUnit) { float roll = _random.NextFloat(0.0, 100.0); // HIT if (roll <= _data.Accuracy) { Debug.LogWarning("Cannon shell dispersion is not implemented yet"); target.Enemy.HandleHit(_data.Damage, displacement, null); } } else { // TODO: fire pos damage not implemented } } }
public void ClearExplicitTarget() { _explicitTarget = null; _target = null; foreach (Turret turret in Children) { turret.ClearExplicitTarget(); } }
/// <summary> /// Sets a max-priority target for this turret. /// </summary> public void SetExplicitTarget(TargetTuple target) { _explicitTarget = target; _target = target; // TODO check if we can shoot it before setting this foreach (Turret turret in Children) { turret.SetExplicitTarget(target); } }
/// <summary> /// Get the range at which this turret can shoot at a specific target. /// </summary> /// /// TODO In the future we will need to also return -1 /// for turrets that can't shoot the target at all. /// /// <param name="target"></param> /// <returns></returns> public float MaxRange(TargetTuple target) { float maxRange = _fireRange; foreach (Turret turret in Children) { float turretMax = turret.MaxRange(target); maxRange = maxRange > turretMax ? maxRange : turretMax; } return maxRange; }
/// <summary> /// Calculate the max range this turret can shoot at with at least one weapon, /// IN UNITY UNITS. /// </summary> /// This is a method to avoid storing duplicate information, and /// because we may want to ignore disabled turrets, or turrets /// that can't shoot at a specific target etc. /// /// TODO: Code duplication can be reduced if we only implement this in /// the turret class and have a fake toplevel turret we call this method on, /// but a fake turret like that also adds complexity, hard to decide. private float MaxRange(TargetTuple target) { float maxRange = 0; foreach (Turret turret in Children) { float turretMax = turret.MaxRange(target); maxRange = maxRange > turretMax ? maxRange : turretMax; } return(maxRange); }
/// <summary> /// To pick which ammo type to use we need an estimation /// of the expected damage. This can differ from the actual /// damage dealt if we hit a different armor section, /// if the explosion lands to the side etc.. /// </summary> public float EstimateDamageAgainstTarget( TargetTuple target, Vector3 displacement, float distance) { float result = 0; float range = GetRangeAgainstTargetType(target.Type); if (range > distance) { switch (DamageType) { case DamageType.HE: if (target.Type == TargetType.GROUND) { result = DamageValue; } else { // Distance = 0 because we assume the explosion is on the target result = target.Enemy.EstimateDamage( DamageType, DamageValue, displacement, 0); } break; case DamageType.HEAT: result = target.Enemy.EstimateDamage( DamageType, DamageValue, displacement, distance); break; case DamageType.KE: result = target.Enemy.EstimateDamage( DamageType, DamageValue, displacement, distance); break; case DamageType.SMALL_ARMS: result = target.Enemy.EstimateDamage( DamageType, DamageValue, displacement, distance); break; case DamageType.HEAVY_ARMS: result = target.Enemy.EstimateDamage( DamageType, DamageValue, displacement, distance); break; } } Logger.LogDamage( LogLevel.DUMP, $"Estimating {result} damage with dmg type {DamageType}," + $" firepower {DamageValue} at range {distance / Constants.MAP_SCALE}"); return(result); }
/// <summary> /// Get the range at which this turret can shoot at a specific target. /// </summary> /// /// TODO In the future we will need to also return -1 /// for turrets that can't shoot the target at all. public float MaxRange(TargetTuple target) { float maxRange = _maxRanges[(int)target.Type]; foreach (Turret turret in Children) { float turretMax = turret.MaxRange(target); maxRange = maxRange > turretMax ? maxRange : turretMax; } return(maxRange); }
public void CancelOrders() { _movingTowardsTarget = false; _explicitTarget = null; _target = null; foreach (Turret turret in Children) { turret.ClearExplicitTarget(); } }
public bool TryShoot( TargetTuple target, float deltaTime, Vector3 displacement, bool isServer) { _reloadTimeLeft -= deltaTime; if (_reloadTimeLeft > 0) { return(false); } _reloadTimeLeft = _data.SalvoReload; return(Shoot(target, isServer)); }
/// <summary> /// Fire on the provided target if the weapon is not reloading etc. /// </summary> /// <param name="target"></param> /// <param name="displacement">Vector from the firing unit to the target unit</param> /// <param name="distance">Distance from the firing unit to the target unit</param> /// <param name="isServer">Non-server code should only affect art.</param> /// <returns>True if a shot was fired, false otherwise.</returns> public bool TryShoot( TargetTuple target, Vector3 displacement, float distance, bool isServer) { if (_reloadTimeLeft > 0) { return(false); } // TODO implement salvo + shot reload _reloadTimeLeft = _salvoReload; FireWeapon(target, displacement, distance, isServer); return(true); }
public bool TryShoot( TargetTuple target, float deltaTime, Vector3 displacement, bool isServer) { _reloadTimeLeft -= deltaTime; if (_reloadTimeLeft > 0) { return(false); } // TODO implement salvo + shot reload _reloadTimeLeft = _data.SalvoReload; FireWeapon(target, displacement, isServer); return(true); }
/// <summary> /// Shoot at the current target if in range. /// </summary> /// <returns>True if a shot was produced.</returns> public bool MaybeShoot(TargetTuple target, float distanceToTarget, bool isServer) { bool shotFired = false; if (IsFacingTarget && TargetInRange(target, distanceToTarget)) { Vector3 vectorToTarget = target.Position - _turret.transform.position; shotFired = _weapon.TryShoot( target, vectorToTarget, distanceToTarget, isServer); } foreach (Turret turret in Children) { shotFired |= turret.MaybeShoot(target, distanceToTarget, isServer); } return(shotFired); }
/// <summary> /// Set a max-priority target for all child turrets. /// </summary> private void SetTarget(TargetTuple target, bool autoApproach) { Logger.LogTargeting( LogLevel.DEBUG, gameObject, "Received target from the outside."); float distance = Vector3.Distance(Unit.transform.position, target.Position); _explicitTarget = target; _target = target; if (distance > _fireRange && autoApproach) { _movingTowardsTarget = true; // TODO if the UnitDispatcher can detect that we're in range, we // would be able to drop the handle to it Unit.SetDestination(target.Position); } }
private bool Shoot(TargetTuple target, bool isServer) { // Vector3 start = new Vector3(ShotStarterPosition.position.x, ShotStarterPosition.position.y+0., ShotStarterPosition.position.z); GameObject shell = Resources.Load <GameObject>("shell"); GameObject shell_new = GameObject.Instantiate( shell, _shotStarterPosition.position, _shotStarterPosition.transform.rotation); shell_new.GetComponent <BulletBehavior>().SetUp(_shotStarterPosition, target.Position, 60); if (isServer) { // TODO apply damage; } return(true); }
/// <summary> /// Get the range at which this turret can shoot at a specific target. /// </summary> public float MaxRange( TargetTuple target, bool includeDirectFireAmmo, bool includeIndirectFireAmmo) { float maxRange; if (includeDirectFireAmmo) { if (includeIndirectFireAmmo) { maxRange = _maxRanges[(int)target.Type]; } else { maxRange = _maxRangesDirectFire[(int)target.Type]; } } else { if (includeIndirectFireAmmo) { maxRange = _maxRangesIndirectFire[(int)target.Type]; } else { maxRange = 0; } } foreach (Turret turret in Children) { float turretMax = turret.MaxRange( target, includeDirectFireAmmo, includeIndirectFireAmmo); if (maxRange < turretMax) { maxRange = turretMax; } } return(maxRange); }
private Ammo PickBestAmmo( TargetTuple target, Vector3 displacement, float distance) { Ammo result = _ammo[0]; float bestDamage = result.EstimateDamageAgainstTarget( target, displacement, distance); for (int i = 1; i < _ammo.Length; i++) { float damage = _ammo[i].EstimateDamageAgainstTarget( target, displacement, distance); if (damage > bestDamage) { result = _ammo[i]; bestDamage = damage; } } return(result); }
/// <summary> /// Fire on the provided target if the weapon is not reloading etc. /// </summary> /// <param name="target"></param> /// <param name="displacement">Vector from the firing unit to the target unit</param> /// <param name="distance">Distance from the firing unit to the target unit</param> /// <param name="isServer">Non-server code should only affect art.</param> /// <returns>True if a shot was fired, false otherwise.</returns> public bool TryShoot( TargetTuple target, Vector3 displacement, float distance, bool isServer) { if (_lastTarget != target) { _lastTarget = target; _aimStartedTimestamp = Time.time; return(false); } if (PercentageAimed < 1) { return(false); } if (PercentageReloaded < 1) { return(false); } if (CanReloadSalvo) { _salvoRemaining = _salvoLength; } if (FireWeapon(target, displacement, distance, isServer)) { _lastShotTimestamp = Time.time; _salvoRemaining--; return(true); } return(false); }
private Ammo PickBestAmmo( TargetTuple target, Vector3 displacement, float distance) { Ammo result = null; float bestDamage = 0; bool mustUseAp = _apRange > distance && target.Type == TargetType.VEHICLE; for (int i = 0; i < Ammo.Length; i++) { if (Ammo[i].ShellCountRemaining == 0) { continue; } float damage = Ammo[i].EstimateDamageAgainstTarget( target, displacement, distance); // Tanks and autocannons dont shoot other vehicles with HE: if (mustUseAp) { if (Ammo[i].DamageType != DamageType.KE && Ammo[i].DamageType != DamageType.HEAT) { damage = 0; } } if (damage > bestDamage) { result = Ammo[i]; bestDamage = damage; } } return(result); }
public void CancelOrders() { _movingTowardsTarget = false; _explicitTarget = null; _target = null; }
private bool FireWeapon( TargetTuple target, Vector3 displacement, float distance, bool isServer) { Ammo ammo = PickBestAmmo(target, displacement, distance); if (ammo == null) { return(false); } ammo.ShellCountRemaining--; // sound _audioSource.PlayOneShot(ammo.ShotSound, _shotVolume); if (ammo.MuzzleFlashEffect != null) { ammo.MuzzleFlashEffect.transform.LookAt(target.Position); ammo.MuzzleFlashEffect.Play(); } GameObject shell = GameObject.Instantiate( _shellPrefab, _barrelTip.position, _barrelTip.transform.rotation); GameObject.Instantiate(ammo.ShellArtPrefab, shell.transform); float roll = _random.NextFloat(0.0, 100.0); bool isHit = roll <= ammo.Accuracy; Vector3 shellDestination = target.Position; if (!isHit) { int deviationMode = (int)roll % 4; float missFactor = _random.NextFloat( Constants.MISS_FACTOR_MIN, Constants.MISS_FACTOR_MAX); float weightX = _random.NextFloat(0, 1); switch (deviationMode) { case 0: shellDestination.x += distance * missFactor * weightX; shellDestination.y += distance * missFactor * (1 - weightX); break; case 1: shellDestination.x -= distance * missFactor * weightX; shellDestination.y += distance * missFactor * (1 - weightX); break; case 2: shellDestination.x += distance * missFactor * weightX; shellDestination.y -= distance * missFactor * (1 - weightX); break; case 3: shellDestination.x -= distance * missFactor * weightX; shellDestination.y -= distance * missFactor * (1 - weightX); break; } } ShellBehaviour shellBehaviour = shell.GetComponent <ShellBehaviour>(); shellBehaviour.Initialize(shellDestination, ammo); if (isServer) { if (target.IsUnit) { if (isHit && !ammo.IsAoe) { target.Enemy.HandleHit( ammo.DamageType, ammo.DamageValue, displacement, distance); } } else { // HE damage is applied by the shellBehavior when it explodes } } return(true); }
public float MaxRange(TargetTuple target) => MaxRange(target, true, true);
private void Update() { foreach (Turret turret in Children) { turret.Rotate(_target); } float distanceToTarget = 99999; if (_target != null && _target.Exists) { // TODO move most of the Update logic to the respective turrets // (likely to a new member class of them called 'TargetingStrategy') distanceToTarget = Vector3.Distance( Unit.transform.position, _target.Position); if (_fireRange > distanceToTarget && _movingTowardsTarget) { StopChasingTarget(); } else if (distanceToTarget >= _fireRange && !_movingTowardsTarget) { StartChasingTarget(); } if (_target.IsUnit && !_target.Enemy.VisionComponent.IsSpotted) { Logger.LogTargeting( LogLevel.DEBUG, gameObject, "Dropping a target because it is no longer spotted."); if (_target == _explicitTarget) { _explicitTarget = null; } _target = null; } if (!HasTargetingOrder && _target != null) { if (!_vision.IsInHardLineOfSightFast(_target.Position) || !_vision.IsInSoftLineOfSight(_target.Position, 0) || distanceToTarget > _fireRange) { _target = null; Logger.LogTargeting( LogLevel.DEBUG, gameObject, "Dropping a target because it is out of range or LoS."); } } } if (_target != null && _target.Exists) { bool targetInRange = !_movingTowardsTarget; bool shotFired = false; foreach (Turret turret in Children) { shotFired |= turret.MaybeShoot(_target, distanceToTarget, isServer); } // If shooting at the ground, stop after the first shot: if (shotFired && _target.IsGround) { _target = null; _explicitTarget = null; } } else { FindAndTargetClosestEnemy(); } }
private bool TargetInRange(TargetTuple target, float distanceToTarget) { return(_maxRanges[(int)target.Type] > distanceToTarget); }
public void Rotate(TargetTuple target) { // Do not return from this method before we've called into each child's // update handler! foreach (Turret turret in Children) { turret.Rotate(target); } if (target == null || !target.Exists) { TurnTurretBackToDefaultPosition(); return; } bool aimed = false; float targetHorizontalAngle = 0f; float targetVerticalAngle = 0f; if (target.Position != Vector3.zero) { aimed = true; Vector3 directionToTarget = target.Position - _turret.position; directionToTarget = new Vector3(directionToTarget.x, 0, directionToTarget.z); Quaternion rotationToTarget = Quaternion.LookRotation( _turret.parent.transform.InverseTransformDirection(directionToTarget)); // Add any necessary cannon elevation to the rotation rotationToTarget *= ShellBehaviour.CalculateBarrelAngle( _shellVelocity, _turret.transform.position, target.Position, out _); targetHorizontalAngle = rotationToTarget.eulerAngles.y.unwrapDegree(); // If this turret has no flexibility (ArcHorizontal = 0) and is fully // rotated by a parent turret, it can get stuck 0.0000000001 degrees // away from the target due to float rounding errors (parent rounds // one way and decides he's done, child rounds the other way). // So round away the last degree to avoid this case: targetHorizontalAngle = Util.RoundTowardZero(targetHorizontalAngle); if (Mathf.Abs(targetHorizontalAngle) > _arcHorizontal) { targetHorizontalAngle = 0f; aimed = false; } targetVerticalAngle = rotationToTarget.eulerAngles.x.unwrapDegree(); targetVerticalAngle = (float)Math.Floor(targetVerticalAngle); if (targetVerticalAngle < -_arcUp || targetVerticalAngle > _arcDown) { targetVerticalAngle = 0f; aimed = false; } } float turn = Time.deltaTime * _rotationRate; float horizontalAngle = _turret.localEulerAngles.y; float verticalAngle = _turret.localEulerAngles.x; float deltaAngle; deltaAngle = (targetHorizontalAngle - horizontalAngle).unwrapDegree(); if (Mathf.Abs(deltaAngle) > turn) { horizontalAngle += (deltaAngle > 0 ? 1 : -1) * turn; aimed = false; } else { horizontalAngle = targetHorizontalAngle; } deltaAngle = (targetVerticalAngle - verticalAngle).unwrapDegree(); if (Mathf.Abs(deltaAngle) > turn) { verticalAngle += (deltaAngle > 0 ? 1 : -1) * turn; aimed = false; } else { verticalAngle = targetVerticalAngle; } _turret.localEulerAngles = new Vector3(verticalAngle, horizontalAngle, 0); IsFacingTarget = aimed; }
public float MaxRangeDirectFire(TargetTuple target) => MaxRange(target, true, false);
public float MaxRangeIndirectFire(TargetTuple target) => MaxRange(target, false, true);
private float MaxRange(TargetTuple target) => MaxRange(target, true, true);