rabidus-test/Assets/SCSM/SciFiShipController/Scripts/Radar/AutoTargetingModule.cs

952 lines
49 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved.
namespace SciFiShipController
{
/// <summary>
/// Attached to a weapon-based system to automatically assign
/// targets to weapons.
/// </summary>
[AddComponentMenu("Sci-Fi Ship Controller/Weapon Components/Auto Targeting Module")]
[HelpURL("http://scsmmedia.com/ssc-documentation")]
public class AutoTargetingModule : MonoBehaviour
{
#region Enumerations
// If this is modified, also update VerifyModule()
public enum ModuleMode
{
ShipControlModule = 0,
SurfaceTurretModule = 5
}
#endregion
#region Public variables
/// <summary>
/// If enabled, the Initialise() will be called as soon as Start() runs. This should be disabled if you are
/// initialising the turret through code and using the SurfaceTurretModule API methods.
/// </summary>
public bool initialiseOnStart = false;
/// <summary>
/// The mode that the AutoTargetingModule will operate in. This should match the
/// module it is attached to.
/// </summary>
public ModuleMode moduleMode = ModuleMode.SurfaceTurretModule;
/// <summary>
/// When acquiring a new target, when enabled, this will verify there is a direct line of sight between
/// the weapon and the target.
/// </summary>
public bool isCheckLineOfSightNewTarget = false;
/// <summary>
/// Whether the target should be reassigned periodically (after a fixed period of time).
/// </summary>
public bool updateTargetPeriodically = false;
/// <summary>
/// The time to wait (in seconds) before assigning a new target. Only valid if updateTargetPeriodically is enabled.
/// </summary>
public float updateTargetTime = 10f;
/// <summary>
/// Whether the current target can be 'lost' through either loss of line of sight or an inability to lock on to the target.
/// </summary>
public bool canLoseTarget = false;
/// <summary>
/// How long (in seconds) a target must be invalid for it to be lost (prompting a new target to be assigned).
/// Only valid if canLoseTarget is enabled.
/// </summary>
public float targetLostTime = 2f;
/// <summary>
/// Whether a target can be 'lost' if line-of-sight is lost. Only valid if canLoseTarget is enabled.
/// </summary>
public bool isValidTargetRequireLOS = true;
/// <summary>
/// Whether a target can be 'lost' if the turret is unable to lock on to it. Only valid if canLoseTarget is enabled.
/// </summary>
public bool isValidTargetRequireTargetLock = true;
public bool IsModuleModeValid { get; internal set; }
/// <summary>
/// Is the AutoTargetingModule initialised and ready for use?
/// If not, call Initialise() or set initialiseOnStart in the Inspector
/// </summary>
public bool IsInitialised { get { return isInitialised; } }
/// <summary>
/// The number of targets that are currently in range of the weapons with auto-targeting enabled.
/// </summary>
public int NumberOfTargetsInRange { get; private set; }
/// <summary>
/// The HUD to be used with a player ship. Targeting data is sent
/// to the HUD. At runtime call SetHUD(...).
/// </summary>
public ShipDisplayModule shipDisplayModule;
/// <summary>
/// Should the targets be shown on the ShipDisplayModule (HUD)?
/// At runtime call ShowTargetsOnHUD() or HideTargetsOnHUD().
/// This will use existing Display Targets on the HUD.
/// </summary>
public bool isTargetsShownOnHUD = false;
/// <summary>
/// Are the radar targets being sent to the HUD?
/// </summary>
public bool IsTargetsShownOnHUD { get { return isTargetsShownOnHUD; } }
/// <summary>
/// Used for debugging in the editor
/// </summary>
public SSCRadarQuery GetCurrentQuery { get { return sscRadarQuery; } }
/// <summary>
/// Used for debugging purposes only. Do NOT hold a reference to this
/// list over multiple frames or any of the blips within the list.
/// </summary>
public List<SSCRadarBlip> GetBlipList { get { return blipsList; } }
/// <summary>
/// [INTERNAL ONLY]
/// </summary>
public bool allowRepaint = false;
#endregion
#region Private varibles
private bool isInitialised = false;
private SSCRadar sscRadar = null;
private List<SSCRadarBlip> blipsList = null;
private SSCRadarQuery sscRadarQuery = null;
private int[] excludeNeutralAndSelf = null;
private ShipControlModule shipControlModule = null;
private SurfaceTurretModule surfaceTurretModule = null;
// To avoid enumeration lookups
private int moduleModeInt = -1;
private int mmShipControlModuleInt = (int)ModuleMode.ShipControlModule;
private int mmSurfaceTurretModuleInt = (int)ModuleMode.SurfaceTurretModule;
#endregion
#region Initialise Methods
void Start()
{
if (initialiseOnStart) { Initialise(); }
}
/// <summary>
/// Call this method if adding this component at runtime or after updating some fields.
/// </summary>
public void Initialise()
{
if (!isInitialised)
{
VerifyModule();
if (IsModuleModeValid)
{
sscRadar = SSCRadar.GetOrCreateRadar();
blipsList = new List<SSCRadarBlip>(20);
moduleModeInt = (int)moduleMode;
int selfFactionId = 0;
if (moduleModeInt == mmShipControlModuleInt)
{
if (shipControlModule.shipInstance != null) { selfFactionId = shipControlModule.shipInstance.factionId; }
}
else if (moduleModeInt == mmSurfaceTurretModuleInt)
{
selfFactionId = surfaceTurretModule.factionId;
}
excludeNeutralAndSelf = new int[] { 0, selfFactionId };
// Set up a default query for auto targeting
sscRadarQuery = new SSCRadarQuery()
{
// The centre position should be updated each time the query is run
centrePosition = transform.position,
factionId = -1,
// exclude neutral items (0), and anything in the same alliance as the turret
factionsToExclude = excludeNeutralAndSelf,
is3DQueryEnabled = true,
// return the closest enemy first
querySortOrder = SSCRadarQuery.QuerySortOrder.DistanceAsc3D,
// Start with no range
range = 0f
};
isInitialised = sscRadar != null;
}
}
}
#endregion
#region Update Methods
private void Update()
{
if (isInitialised)
{
if (moduleModeInt == mmSurfaceTurretModuleInt)
{
UpdateSurfaceTurretTarget();
}
else if (moduleModeInt == mmShipControlModuleInt)
{
UpdateShipWeaponTargets();
}
}
}
#endregion
#region Internal Methods
/// <summary>
/// [INTERNAL ONLY]
/// Used to verify if the module is attached to a matching component
/// </summary>
public void VerifyModule()
{
int modeInt = (int)moduleMode;
// ShipControlModule
if (modeInt == 0)
{
surfaceTurretModule = null;
shipControlModule = GetComponent<ShipControlModule>();
IsModuleModeValid = shipControlModule != null;
}
// SurfaceTurretModule
else if (modeInt == 5)
{
shipControlModule = null;
surfaceTurretModule = GetComponent<SurfaceTurretModule>();
IsModuleModeValid = surfaceTurretModule != null;
}
// Anything unsupported
else
{
surfaceTurretModule = null;
shipControlModule = null;
IsModuleModeValid = false;
}
}
#endregion
#region Private Member Methods
/// <summary>
/// Is the current weapon target valid, or is a new target required?
/// If there are no enemy, the existing target must be invalid, so return true.
/// </summary>
/// <param name="weapon"></param>
/// <param name="numEnemy"></param>
/// <returns></returns>
private bool IsNewTargetRequired(Weapon weapon, int numEnemy)
{
bool isNewTargetRequired = true;
int targetGameObjectInstanceId = weapon.target.GetInstanceID();
float estimatedRangeSqr = weapon.estimatedRange * weapon.estimatedRange;
// Update the invalid target timer for this weapon (should happen even if no enemy)
// FUTURE - is the fixed projectile weapon with guided projectile locked on to the target?
if ((!isValidTargetRequireLOS || weapon.HasLineOfSight) &&
(!isValidTargetRequireTargetLock || weapon.isLockedOnTarget || weapon.weaponTypeInt == Weapon.FixedProjectileInt || weapon.weaponTypeInt == Weapon.FixedBeamInt))
{
weapon.invalidTargetTimer = 0f;
}
else { weapon.invalidTargetTimer += Time.deltaTime; }
// Is the target still in range?
// FUTURE consideration
// 1. Is the target within the firing cone of the weapon?
// 2. How long has the weapon been trying to obtain a lock?
for (int bIdx = 0; bIdx < numEnemy; bIdx++)
{
SSCRadarBlip blip = blipsList[bIdx];
// Check ship damage region (with a childTransform) first
if (blip.guidHash != 0 && blip.shipControlModule != null && blip.itemGameObject != null && blip.itemGameObject.GetInstanceID() == targetGameObjectInstanceId)
{
// Found the matching blip for this target. If out of range of this weapon, then we need a new target
isNewTargetRequired = blip.distanceSqr3D > estimatedRangeSqr || weapon.invalidTargetTimer >= targetLostTime;
break;
}
// Check a ship or ship damage region (without a childTransform)
else if (blip.shipControlModule != null && blip.shipControlModule.gameObject.GetInstanceID() == targetGameObjectInstanceId)
{
// Found the matching blip for this target. If out of range of this weapon, then we need a new target
isNewTargetRequired = blip.distanceSqr3D > estimatedRangeSqr || weapon.invalidTargetTimer >= targetLostTime;
break;
}
else if (blip.itemGameObject != null && blip.itemGameObject.GetInstanceID() == targetGameObjectInstanceId)
{
// Found the matching blip for this target. If out of range of this weapon, then we need a new target
isNewTargetRequired = blip.distanceSqr3D > estimatedRangeSqr || weapon.invalidTargetTimer >= targetLostTime;
break;
}
}
return isNewTargetRequired;
}
/// <summary>
/// Turn on/off all Display Targets on the ShipDisplayModule
/// </summary>
/// <param name="isShown"></param>
private void ShowOrHideTargetsOnHUD(bool isShown)
{
if (isInitialised && shipDisplayModule != null)
{
isTargetsShownOnHUD = isShown;
}
else { isTargetsShownOnHUD = false; }
if (shipDisplayModule != null && shipDisplayModule.IsInitialised)
{
if (isTargetsShownOnHUD) { shipDisplayModule.ShowDisplayTargets(); }
else { shipDisplayModule.HideDisplayTargets(); }
}
}
/// <summary>
/// Update all weapons on a ship with AutoTargeting enabled.
/// NOTE: Currently, it will always run a radar query, even if there are no weapons that have AutoTargeting.
/// Ships will never fire at radar items in the same faction.
/// </summary>
private void UpdateShipWeaponTargets()
{
if (shipControlModule != null && shipControlModule.IsInitialised && shipControlModule.ShipIsEnabled())
{
int numWeapons = shipControlModule.shipInstance.weaponList == null ? 0 : shipControlModule.shipInstance.weaponList.Count;
int numBlips = 0;
bool isNewTargetRequired = true;
int selfFactionId = shipControlModule.shipInstance.factionId;
// Display Target notes
// 1. The number of display targets will depend on if this feature is enabled
// 2. When assigned to a weapon, the weaponIndex wil be stored in the DisplayTarget class instance
int numDisplayTargets = !isTargetsShownOnHUD || shipDisplayModule == null || !shipDisplayModule.IsHUDShown ? 0 : shipDisplayModule.GetNumberDisplayTargets;
// If we have a HUD, build a minimal radar query with the required targets
if (numDisplayTargets > 0) { shipDisplayModule.PopulateTargetingRadarQuery(sscRadarQuery); }
// Otherwise just create a radar query that excludes our faction and the neutral faction
else
{
sscRadarQuery.factionsToInclude = null;
sscRadarQuery.factionsToExclude = excludeNeutralAndSelf;
sscRadarQuery.squadronsToInclude = null;
sscRadarQuery.squadronsToExclude = null;
}
if (numWeapons > 0)
{
// NOTE: Assumes blip list only contains enemy
// If we want to also contain friendly ships it will need more work...
/// TODO - optimise by only updating when required
shipControlModule.shipInstance.UpdateMaxAutoTargetRange();
// Prepare and run a radar query
sscRadarQuery.range = shipControlModule.shipInstance.estimatedMaxAutoTargetRange;
}
if (numDisplayTargets > 0 || numWeapons > 0)
{
// Prepare and run a radar query
sscRadarQuery.centrePosition = shipControlModule.shipInstance.TransformPosition;
sscRadar.GetRadarResults(sscRadarQuery, blipsList);
numBlips = blipsList == null ? 0 : blipsList.Count;
}
// ASSUMES all blips are enemy
NumberOfTargetsInRange = numBlips;
#region AutoTargeting Turret weapons
for (int wpIdx = 0; wpIdx < numWeapons; wpIdx++)
{
Weapon weapon = shipControlModule.shipInstance.weaponList[wpIdx];
// Only process turret weapons with Auto Targeting enabled
if (weapon != null && weapon.isAutoTargetingEnabled && (weapon.weaponTypeInt == Weapon.TurretProjectileInt || weapon.weaponTypeInt == Weapon.TurretBeamInt))
{
isNewTargetRequired = true;
float estimatedRangeSqr = weapon.estimatedRange * weapon.estimatedRange;
#region Check if a new target is required
if (weapon.target != null)
{
// Update assigned new target timer
if (updateTargetPeriodically) { weapon.assignedNewTargetTimer += Time.deltaTime; }
// Check if we need to get a new target
isNewTargetRequired = (updateTargetPeriodically && weapon.assignedNewTargetTimer > updateTargetTime) ||
IsNewTargetRequired(weapon, numBlips);
// If we had a target which has gone out of range and there are no other candidates
// remove the target from the weapon.
if (isNewTargetRequired && numBlips < 1)
{
shipControlModule.shipInstance.ClearWeaponTarget(wpIdx);
// Reset invalid target timer
weapon.invalidTargetTimer = 0f;
// Reset assigned new target timer
weapon.assignedNewTargetTimer = 0f;
// Do not try to find another target if there are no enemy
isNewTargetRequired = false;
}
}
#endregion
#region Find and assign a new target
// Target the first suitable candidate
// In the FUTURE we might consider:
// 1. Least time to rotate a turret weapon towards target
// 2. permit the targeting of Locations (which don't have a gameobject)
// 3. try not to assign the same enemy to multiple turret weapons
if (isNewTargetRequired && numBlips > 0)
{
// Loop through all the enemy radar blips
for (int bIdx = 0; bIdx < numBlips; bIdx++)
{
SSCRadarBlip blip = blipsList[bIdx];
#region Check how suitable this enemy blip is for targeting
// Weapon range could be less than estimatedMaxTurretRange of all weapons on the ship
if (blip.factionId == selfFactionId || blip.factionId == SSCRadar.NEUTRAL_FACTION || blip.distanceSqr3D > estimatedRangeSqr) { continue; }
// Skip this blip if it is not within the firing cone of the weapon
// Find the position of the target in turret space
Vector3 targetTurretSpacePos = weapon.turretPivotY.parent.InverseTransformPoint(blip.wsPosition);
// Get azimuth and altitude angles of the target
float azimuthAngle = Mathf.Atan2(targetTurretSpacePos.x, targetTurretSpacePos.z) * Mathf.Rad2Deg;
float altitudeAngle = Mathf.Atan(targetTurretSpacePos.y /
Mathf.Sqrt((targetTurretSpacePos.x * targetTurretSpacePos.x) + (targetTurretSpacePos.z * targetTurretSpacePos.z)))
* Mathf.Rad2Deg;
if (azimuthAngle < weapon.turretMinY || azimuthAngle > weapon.turretMaxY ||
altitudeAngle < weapon.turretMinX || altitudeAngle > weapon.turretMaxX) { continue; }
#endregion
#region Assign the target and consider LoS and HUD if enabled
if (blip.shipControlModule != null)
{
// Check if this target is a ship or the damage region of a ship
if (blip.guidHash == 0)
{
// Skip this blip if LoS is enabled but no direct LoS is available to the damage region
if (isCheckLineOfSightNewTarget && !shipControlModule.shipInstance.WeaponHasLineOfSight(weapon, blip.shipControlModule.gameObject, true, true, false)) { continue; }
weapon.SetTargetShip(blip.shipControlModule);
}
else
{
// Skip this blip if LoS is enabled but no direct LoS is available
// If available, use the damage region child transform which is stored in blip.itemGameObject
if (isCheckLineOfSightNewTarget && !shipControlModule.shipInstance.WeaponHasLineOfSight(weapon, blip.itemGameObject == null ? blip.shipControlModule.gameObject : blip.itemGameObject, true, true, false)) { continue; }
// Set the target as the ship's damage region
weapon.SetTargetShipDamageRegion(blip.shipControlModule, blip.shipControlModule.shipInstance.GetDamageRegion(blip.guidHash));
}
// To "fix" issue of turret beams not working in builds
// weapon.isLockedOnTarget is false in ship.MoveBeam() in build (fine in editor)
// Reset invalid target timer
weapon.invalidTargetTimer = 0f;
// Reset assigned new target timer
weapon.assignedNewTargetTimer = 0f;
isNewTargetRequired = false;
break;
}
else if (blip.itemGameObject != null)
{
// Skip this blip if LoS is enabled but no direct LoS is available
if (isCheckLineOfSightNewTarget && !shipControlModule.shipInstance.WeaponHasLineOfSight(weapon, blip.itemGameObject, true, true, false)) { continue; }
weapon.SetTarget(blip.itemGameObject);
// To "fix" issue of turret beams not working in builds
// weapon.isLockedOnTarget is false in ship.MoveBeam() in build (fine in editor)
// Reset invalid target timer
weapon.invalidTargetTimer = 0f;
// Reset assigned new target timer
weapon.assignedNewTargetTimer = 0f;
isNewTargetRequired = false;
break;
}
#endregion
}
}
#endregion
// If we tried to find a target but could not, the previous target may still be set
if (isNewTargetRequired && weapon.target != null)
{
shipControlModule.shipInstance.ClearWeaponTarget(wpIdx);
// Reset invalid target timer
weapon.invalidTargetTimer = 0f;
// Reset assigned new target timer
weapon.assignedNewTargetTimer = 0f;
}
}
} // End weapon loop
#endregion End Turret Weapons
if (numDisplayTargets > 0)
{
#region Allocate Display Targets
// Set the score for all the targets to be negative one
for (int dIdx = 0; dIdx < numDisplayTargets; dIdx++)
{
// Get the current display target
DisplayTarget displayTarget = shipDisplayModule.displayTargetList[dIdx];
// Loop through the slots
for (int sIdx = 0; sIdx < displayTarget.maxNumberOfTargets; sIdx++)
{
// Set the score to -1
displayTarget.displayTargetSlotList[sIdx].fixedWeaponTargetScore = -1f;
// Set the target slot to be invalid initially
shipDisplayModule.AssignDisplayTargetSlot(dIdx, sIdx, -1, 0, true);
}
}
// Loop through all the blips and find blips that match the criteria for each display target
for (int bIdx = 0; bIdx < numBlips; bIdx++)
{
// Get the current blip
SSCRadarBlip blip = blipsList[bIdx];
// TODO: Is this something that we always want to do? Maybe it should be optional...
// Skip this target if it would not be visible in the HUD viewport
// This will only make sense with forward facing weapons
if (!sscRadar.IsBlipInScreenViewPort(blip, shipDisplayModule.mainCamera, shipDisplayModule.ScreenResolution,
shipDisplayModule.GetTargetsViewportSize, shipDisplayModule.GetTargetsViewportOffset)) { continue; }
// Loop through the list of display targets and check this blip to see if it matches the criteria
for (int dIdx = 0; dIdx < numDisplayTargets; dIdx++)
{
// Get the current display target
DisplayTarget displayTarget = shipDisplayModule.displayTargetList[dIdx];
// Skip any display target without any targets
// NOTE: This should never the case but could be done in user code
if (displayTarget.maxNumberOfTargets < 1) { continue; }
#region Check If Blip Matches Display Target Criteria
// Check if the blip matches the faction criteria (it automatically matches if there are no specified factions)
int numDTFactionsToInclude = displayTarget.factionsToInclude == null ? 0 : displayTarget.factionsToInclude.Length;
bool blipMatchesFaction = numDTFactionsToInclude == 0;
for (int fIdx = 0; fIdx < numDTFactionsToInclude; fIdx++)
{
if (blip.factionId == displayTarget.factionsToInclude[fIdx]) { blipMatchesFaction = true; break; }
}
// We only need to check squadron criteria if the blip matched the faction criteria
bool blipMatchesSquadron = false;
if (blipMatchesFaction)
{
// Check if the blip matches the squadron criteria (it automatically matches if there are no specified squadrons)
int numDTSquadronsToInclude = displayTarget.squadronsToInclude == null ? 0 : displayTarget.squadronsToInclude.Length;
blipMatchesSquadron = numDTSquadronsToInclude == 0;
for (int sqIdx = 0; sqIdx < numDTSquadronsToInclude; sqIdx++)
{
if (blip.squadronId == displayTarget.squadronsToInclude[sqIdx]) { blipMatchesSquadron = true; break; }
}
}
#endregion Check If Blip Matches Display Target Criteria
if (blipMatchesFaction && blipMatchesSquadron)
{
// Score this target based on its position
float thisTargetScore = CalculateFixedWeaponTargetScore(shipControlModule, blip);
#region Check If This is a Valid Target to Display
// Loop through the list of the current best scores for this display target, and
// find the lowest score (and its corresponding index)
float displayTargetLowestScore = displayTarget.displayTargetSlotList[0].fixedWeaponTargetScore;
int displayTargetLowestScoreIndex = 0;
for (int sIdx = 1; sIdx < displayTarget.maxNumberOfTargets; sIdx++)
{
// Compare this score to the current lowest score
DisplayTargetSlot displayTargetSlot = displayTarget.displayTargetSlotList[sIdx];
if (displayTargetSlot.fixedWeaponTargetScore < displayTargetLowestScore)
{
displayTargetLowestScore = displayTargetSlot.fixedWeaponTargetScore;
displayTargetLowestScoreIndex = sIdx;
}
}
// Check if this target's score is higher than the current lowest display target score
if (thisTargetScore > displayTargetLowestScore)
{
// If so, replace the lowest display target score with this one
displayTarget.displayTargetSlotList[displayTargetLowestScoreIndex].fixedWeaponTargetScore = thisTargetScore;
// Set this target to be displayed as a display target (replacing the target with the lowest score)
shipDisplayModule.AssignDisplayTargetSlot(dIdx, displayTargetLowestScoreIndex, blip.radarItemIndex, blip.radarItemSequenceNumber, true);
}
#endregion Check If This is a Valid Target to Display
// Stop looping once we find a display target match for this blip
dIdx = numDisplayTargets;
}
}
}
#endregion Allocate Display Targets
#region Assign Fixed Weapons Target
float fixedWeaponBestTargetScore = 0f;
SSCRadarItem fixedWeaponBestTargetRadarItem = null;
// Loop through the list of display targets
for (int dIdx = 0; dIdx < numDisplayTargets; dIdx++)
{
// Get the current display target
DisplayTarget displayTarget = shipDisplayModule.displayTargetList[dIdx];
// Only consider display targets that we have marked as targetable
if (displayTarget.isTargetable)
{
// Loop through the list of display target slots
for (int sIdx = 0; sIdx < displayTarget.maxNumberOfTargets; sIdx++)
{
// First try and get the radar item for this target - check that it is valid
SSCRadarItemKey targetRadarItemKey = shipDisplayModule.GetAssignedDisplayTarget(dIdx, sIdx);
SSCRadarItem targetRadarItem = sscRadar.GetRadarItem(targetRadarItemKey.radarItemIndex, targetRadarItemKey.radarItemSequenceNumber);
if (targetRadarItem != null)
{
// If check line of sight for new targets is enabled, target validity depends on whether we
// have line-of-sight to the new weapon
bool isTargetValid = !isCheckLineOfSightNewTarget;
if (isCheckLineOfSightNewTarget)
{
// Try and find the gameobject for this target
GameObject targetGameObject = null;
if (targetRadarItem.shipControlModule != null)
{
// Check for ship damage region target
if (targetRadarItem.guidHash != 0 && targetRadarItem.itemGameObject != null)
{
targetGameObject = targetRadarItem.itemGameObject;
}
else
{
targetGameObject = targetRadarItem.shipControlModule.gameObject;
}
}
else if (targetRadarItem.itemGameObject != null)
{
targetGameObject = targetRadarItem.itemGameObject;
}
if (targetGameObject != null)
{
// Loop through the fixed weapons and see if any of the weapons has line of sight to this target
for (int wpIdx = 0; wpIdx < numWeapons; wpIdx++)
{
// Get the weapon reference
Weapon weapon = shipControlModule.shipInstance.weaponList[wpIdx];
// Only process fixed weapons with Auto Targeting enabled
if (weapon != null && weapon.isAutoTargetingEnabled && (weapon.weaponTypeInt == Weapon.FixedProjectileInt || weapon.weaponTypeInt == Weapon.FixedBeamInt))
{
// Check if this weapon has line of sight to the target
if (shipControlModule.shipInstance.WeaponHasLineOfSight(weapon, targetGameObject, true, true, false))
{
// If it does, skip all the other weapons and mark the target as valid
// We only need one weapon to have line-of-sight to the target
isTargetValid = true;
wpIdx = numWeapons;
}
}
}
}
}
// Compare this score to the current best target score
DisplayTargetSlot displayTargetSlot = displayTarget.displayTargetSlotList[sIdx];
if (isTargetValid && displayTargetSlot.fixedWeaponTargetScore > fixedWeaponBestTargetScore)
{
// If it is better, remember this as the best target score
fixedWeaponBestTargetScore = displayTargetSlot.fixedWeaponTargetScore;
// Remember the corresponding radar item for this target
fixedWeaponBestTargetRadarItem = targetRadarItem;
}
}
}
}
}
// Loop through the fixed weapons and set their targets to the best target we found
for (int wpIdx = 0; wpIdx < numWeapons; wpIdx++)
{
// Get the weapon reference
Weapon weapon = shipControlModule.shipInstance.weaponList[wpIdx];
// Only process fixed weapons with Auto Targeting enabled
if (weapon != null && weapon.isAutoTargetingEnabled && (weapon.weaponTypeInt == Weapon.FixedProjectileInt || weapon.weaponTypeInt == Weapon.FixedBeamInt))
{
// If the radar item retrieved is not null, we have a valid radar item
if (fixedWeaponBestTargetRadarItem != null)
{
// Set the target of the weapon
// TODO: Maybe at some point this should work for things that aren't ships/gameobjects?
if (fixedWeaponBestTargetRadarItem.shipControlModule != null)
{
// Check if this is a damage region target
if (fixedWeaponBestTargetRadarItem.guidHash == 0)
{
weapon.SetTargetShip(fixedWeaponBestTargetRadarItem.shipControlModule);
}
else
{
// Set the target as the ship's damage region
weapon.SetTargetShipDamageRegion(fixedWeaponBestTargetRadarItem.shipControlModule,
fixedWeaponBestTargetRadarItem.shipControlModule.shipInstance.GetDamageRegion(fixedWeaponBestTargetRadarItem.guidHash));
}
}
else if (fixedWeaponBestTargetRadarItem.itemGameObject != null)
{
weapon.SetTarget(fixedWeaponBestTargetRadarItem.itemGameObject);
}
}
// If we do not have a valid radar item, we need to unassign each weapon
else
{
weapon.ClearTarget();
}
}
}
#endregion Assign Fixed Weapons Target
}
}
else { NumberOfTargetsInRange = 0; }
}
/// <summary>
/// Check if the weapon has a target assigned. If so, check if it
/// is still a valid target (i.e. within range).
/// If required, look for a new target that is within range.
/// </summary>
private void UpdateSurfaceTurretTarget()
{
if (surfaceTurretModule != null && surfaceTurretModule.IsInitialised)
{
Weapon weapon = surfaceTurretModule.weapon;
sscRadarQuery.centrePosition = surfaceTurretModule.TransformPosition;
sscRadarQuery.range = weapon.estimatedRange;
// The default Surface Turret query should only return enemy
sscRadar.GetRadarResults(sscRadarQuery, blipsList);
int numBlips = blipsList == null ? 0 : blipsList.Count;
NumberOfTargetsInRange = numBlips;
bool isNewTargetRequired = true;
#region Check if a new target is required
if (weapon.target != null)
{
// Update assigned new target timer
if (updateTargetPeriodically) { weapon.assignedNewTargetTimer += Time.deltaTime; }
// Check if we need to get a new target
isNewTargetRequired = (updateTargetPeriodically && weapon.assignedNewTargetTimer > updateTargetTime) ||
IsNewTargetRequired(weapon, numBlips);
// If we had a target which has gone out of range and there are no other candidates
// remove the target from the weapon.
if (isNewTargetRequired && numBlips < 1)
{
//surfaceTurretModule.SetWeaponTarget(null);
surfaceTurretModule.ClearWeaponTarget();
// Reset invalid target timer
weapon.invalidTargetTimer = 0f;
// Reset assigned new target timer
weapon.assignedNewTargetTimer = 0f;
}
}
#endregion
// Target the first suitable candidate
// In the FUTURE we might consider:
// 1. Least time to rotate weapon towards target
// 2. blips within the firing cone of the weapon
// 3. permit the targetting of Locations (which don't have a gameobject)
if (isNewTargetRequired && numBlips > 0)
{
for (int bIdx = 0; bIdx < numBlips; bIdx++)
{
SSCRadarBlip blip = blipsList[bIdx];
#region Check how suitable this enemy blip is for targeting
// Skip this blip if it is not within the firing cone of the weapon
// Find the position of the target in turret space
Quaternion turretParentInverseRotation = Quaternion.Inverse(weapon.turretPivotY.parent.rotation);
Vector3 turretRelativePosition = ((turretParentInverseRotation * weapon.turretPivotX.rotation) * weapon.relativePosition) + weapon.turretPivotY.parent.transform.position;
// Transform into turret space using rotation of pivot Y parent object and position of turret relative position
Vector3 targetTurretSpacePos = turretParentInverseRotation * (blip.wsPosition - turretRelativePosition);
// Get azimuth and altitude angles of the target
float azimuthAngle = Mathf.Atan2(targetTurretSpacePos.x, targetTurretSpacePos.z) * Mathf.Rad2Deg;
float altitudeAngle = Mathf.Atan(targetTurretSpacePos.y /
Mathf.Sqrt((targetTurretSpacePos.x * targetTurretSpacePos.x) + (targetTurretSpacePos.z * targetTurretSpacePos.z)))
* Mathf.Rad2Deg;
if (azimuthAngle < weapon.turretMinY || azimuthAngle > weapon.turretMaxY ||
altitudeAngle < weapon.turretMinX || altitudeAngle > weapon.turretMaxX) { continue; }
#endregion
#region Assign the target and consider LoS if enabled
if (blip.shipControlModule != null)
{
// Check if this target is a ship or the damage region of a ship
if (blip.guidHash == 0)
{
// Skip this blip if LoS is enabled but no direct LoS is available
if (isCheckLineOfSightNewTarget && !surfaceTurretModule.WeaponHasLineOfSight(blip.shipControlModule.gameObject, true, true, false)) { continue; }
weapon.SetTargetShip(blip.shipControlModule);
}
else
{
// Skip this blip if LoS is enabled but no direct LoS is available to the damage region
// If available, use the damage region child transform which is stored in blip.itemGameObject
if (isCheckLineOfSightNewTarget && !surfaceTurretModule.WeaponHasLineOfSight(blip.itemGameObject == null ? blip.shipControlModule.gameObject : blip.itemGameObject, true, true, false)) { continue; }
// Set the target as the ship's damage region
weapon.SetTargetShipDamageRegion(blip.shipControlModule, blip.shipControlModule.shipInstance.GetDamageRegion(blip.guidHash));
}
break;
}
else if (blip.itemGameObject != null)
{
// Skip this blip if LoS is enabled but no direct LoS is available
if (isCheckLineOfSightNewTarget && !surfaceTurretModule.WeaponHasLineOfSight(blip.itemGameObject, true, true, false)) { continue; }
surfaceTurretModule.SetWeaponTarget(blip.itemGameObject);
break;
}
#endregion
}
}
}
else { NumberOfTargetsInRange = 0; }
}
#endregion
#region Public API Methods
/// <summary>
/// If the Module Mode is ShipControlModule, assign the HUD
/// to the AutoTargetingModule - else set it to null.
/// </summary>
/// <param name="shipDisplayModule"></param>
public void SetHUD(ShipDisplayModule shipDisplayModule)
{
if (moduleModeInt == mmShipControlModuleInt)
{
this.shipDisplayModule = shipDisplayModule;
}
else { this.shipDisplayModule = null; }
}
/// <summary>
/// If the HUD is initialised and shown, start sending Target data to the HUD.
/// Only Display Targets already on the HUD will be updated.
/// See also shipDisplayModule.AddTarget(..).
/// </summary>
public void ShowTargetsOnHUD()
{
ShowOrHideTargetsOnHUD(true);
}
/// <summary>
/// Stop sending targeting data to the HUD. Also, turn off any Display Targets
/// on the HUD.
/// </summary>
public void HideTargetsOnHUD()
{
ShowOrHideTargetsOnHUD(false);
}
/// <summary>
/// Calculates a fixed weapon targeting score for a target given a source ship. Higher scores indicate better targets.
/// The maximum score is 1000. Targets behind the ship get assigned a score of -1 to indicate that they are invalid.
/// </summary>
/// <param name="sourceShip"></param>
/// <param name="targetBlip"></param>
/// <returns></returns>
public float CalculateFixedWeaponTargetScore(ShipControlModule sourceShip, SSCRadarBlip targetBlip)
{
// Score starts as 1000 (which is the maximum)
float targetScore = 1000f;
// Calculate the dot product of the ship's forward direction and the vector from the ship to the target
float forwardsToShipDotProduct = Vector3.Dot(sourceShip.shipInstance.TransformForward, targetBlip.wsPosition - sourceShip.shipInstance.TransformPosition);
// If this is positive, then the target is in front the ship
if (forwardsToShipDotProduct > 0f)
{
// Normalise the dot product
float targetBlipDistance3D = Mathf.Sqrt(targetBlip.distanceSqr3D);
float normalisedDotProduct = forwardsToShipDotProduct / targetBlipDistance3D;
// This normalised dot product is one when the target is straight ahead, and decreases as the angle increases
// until it is zero when the target is at a position perpendicular to the forwards direction of the ship
// We multiply the score by this value, so that the score increases as angle decreases
targetScore *= normalisedDotProduct;
// The score should also increase with decreasing distance, in the ratio of:
// As distance decreases by a factor of 10, score increases by a factor of 1.1
float logarithmicDistance = Mathf.Log10(targetBlipDistance3D / 100f);
if (logarithmicDistance < 0f) { logarithmicDistance = 0f; }
//float modifiedDistance = Mathf.Pow(2f, logarithmicDistance);
float modifiedDistance = Mathf.Pow(1.1f, logarithmicDistance);
// Divide the score by this modified distance
targetScore /= modifiedDistance;
}
// Otherwise, the target is behind the ship
else
{
// Targets behind the ship just get assigned a score of -1 (invalid target)
targetScore = -1f;
}
return targetScore;
}
#endregion
}
}