952 lines
49 KiB
C#
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
|
|
}
|
|
} |