// Call()ed State #region Attacking Support Members private AutoPilotDestinationProxy MakePilotAttackTgtProxy(IShipAttackable attackTgt) { RangeDistance weapRange = Data.WeaponsRange; D.Assert(weapRange.Max > Constants.ZeroF); ShipCombatStance combatStance = Data.CombatStance; D.AssertNotEqual(ShipCombatStance.Disengage, combatStance); D.AssertNotEqual(ShipCombatStance.Defensive, combatStance); float maxRangeToTgtSurface = Constants.ZeroF; float minRangeToTgtSurface = Constants.ZeroF; bool hasOperatingLRWeapons = weapRange.Long > Constants.ZeroF; bool hasOperatingMRWeapons = weapRange.Medium > Constants.ZeroF; bool hasOperatingSRWeapons = weapRange.Short > Constants.ZeroF; float weapRangeMultiplier = Owner.WeaponRangeMultiplier; switch (combatStance) { case ShipCombatStance.Standoff: if (hasOperatingLRWeapons) { maxRangeToTgtSurface = weapRange.Long; minRangeToTgtSurface = RangeCategory.Medium.GetBaselineWeaponRange() * weapRangeMultiplier; } else if (hasOperatingMRWeapons) { maxRangeToTgtSurface = weapRange.Medium; minRangeToTgtSurface = RangeCategory.Short.GetBaselineWeaponRange() * weapRangeMultiplier; } else { D.Assert(hasOperatingSRWeapons); maxRangeToTgtSurface = weapRange.Short; minRangeToTgtSurface = Constants.ZeroF; } break; case ShipCombatStance.Balanced: if (hasOperatingMRWeapons) { maxRangeToTgtSurface = weapRange.Medium; minRangeToTgtSurface = RangeCategory.Short.GetBaselineWeaponRange() * weapRangeMultiplier; } else if (hasOperatingLRWeapons) { maxRangeToTgtSurface = weapRange.Long; minRangeToTgtSurface = RangeCategory.Medium.GetBaselineWeaponRange() * weapRangeMultiplier; } else { D.Assert(hasOperatingSRWeapons); maxRangeToTgtSurface = weapRange.Short; minRangeToTgtSurface = Constants.ZeroF; } break; case ShipCombatStance.PointBlank: if (hasOperatingSRWeapons) { maxRangeToTgtSurface = weapRange.Short; minRangeToTgtSurface = Constants.ZeroF; } else if (hasOperatingMRWeapons) { maxRangeToTgtSurface = weapRange.Medium; minRangeToTgtSurface = RangeCategory.Short.GetBaselineWeaponRange() * weapRangeMultiplier; } else { D.Assert(hasOperatingLRWeapons); maxRangeToTgtSurface = weapRange.Long; minRangeToTgtSurface = RangeCategory.Medium.GetBaselineWeaponRange() * weapRangeMultiplier; } break; case ShipCombatStance.Defensive: case ShipCombatStance.Disengage: case ShipCombatStance.None: default: throw new NotImplementedException(ErrorMessages.UnanticipatedSwitchValue.Inject(combatStance)); } minRangeToTgtSurface = Mathf.Max(minRangeToTgtSurface, CollisionDetectionZoneRadius); D.Assert(maxRangeToTgtSurface > minRangeToTgtSurface); return attackTgt.GetApAttackTgtProxy(minRangeToTgtSurface, maxRangeToTgtSurface); }
// 4.22.16: Currently Order is issued only by user or fleet. Once HQ has arrived at the IUnitAttackable target, // individual ships can still be a long way off trying to get there. In addition, the element a ship picks as its // primary target could also be a long way off so we need to rely on the AutoPilot to manage speed. #region ExecuteAttackOrder Support Members /// <summary> /// Tries to pick a primary target for the ship derived from the provided UnitTarget. Returns <c>true</c> if an acceptable /// target belonging to unitAttackTgt is found within SensorRange and the ship decides to attack, <c>false</c> otherwise. /// A ship can decide not to attack even if it finds an acceptable target - e.g. it has no currently operational weapons. /// </summary> /// <param name="unitAttackTgt">The unit target to Attack.</param> /// <param name="allowLogging">if set to <c>true</c> [allow logging].</param> /// <param name="shipPrimaryAttackTgt">The ship's primary attack target. Will be null when returning false.</param> /// <returns></returns> private bool TryPickPrimaryAttackTgt(IUnitAttackable unitAttackTgt, bool allowLogging, out IShipAttackable shipPrimaryAttackTgt) { D.AssertNotNull(unitAttackTgt); if (!unitAttackTgt.IsOperational) { D.Error("{0}'s unit attack target {1} is dead.", DebugName, unitAttackTgt.DebugName); } D.AssertNotEqual(ShipCombatStance.Defensive, Data.CombatStance); D.AssertNotEqual(ShipCombatStance.Disengage, Data.CombatStance); if (Data.WeaponsRange.Max == Constants.ZeroF) { if (ShowDebugLog && allowLogging) { D.Log("{0} is declining to engage with target {1} as it has no operational weapons.", DebugName, unitAttackTgt.DebugName); } shipPrimaryAttackTgt = null; return false; } var uniqueEnemyTargetsInSensorRange = Enumerable.Empty<IShipAttackable>(); Command.SensorRangeMonitors.ForAll(srm => { var attackableEnemyTgtsDetected = srm.EnemyTargetsDetected.Cast<IShipAttackable>(); uniqueEnemyTargetsInSensorRange = uniqueEnemyTargetsInSensorRange.Union(attackableEnemyTgtsDetected); }); IShipAttackable primaryTgt = null; var cmdTarget = unitAttackTgt as AUnitCmdItem; if (cmdTarget != null) { var primaryTargets = cmdTarget.Elements.Cast<IShipAttackable>(); var primaryTargetsInSensorRange = primaryTargets.Intersect(uniqueEnemyTargetsInSensorRange); if (primaryTargetsInSensorRange.Any()) { primaryTgt = __SelectHighestPriorityAttackTgt(primaryTargetsInSensorRange); } } else { // Planetoid var planetoidTarget = unitAttackTgt as APlanetoidItem; D.AssertNotNull(planetoidTarget); if (uniqueEnemyTargetsInSensorRange.Contains(planetoidTarget)) { primaryTgt = planetoidTarget; } } if (primaryTgt == null) { if (allowLogging) { D.Warn("{0} found no target within sensor range to attack!", DebugName); // UNCLEAR how this could happen. Sensors damaged? } shipPrimaryAttackTgt = null; return false; } shipPrimaryAttackTgt = primaryTgt; return true; }