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
{
///
/// Class containing data for a weapon.
///
[System.Serializable]
public class Weapon
{
#region Enumerations
public enum FiringButton
{
None = 0,
Primary = 1,
Secondary = 2,
AutoFire = 3
}
public enum WeaponType
{
FixedProjectile = 0,
FixedBeam = 1,
TurretProjectile = 10,
TurretBeam = 11
}
#endregion Enumerations
#region Public Static Variables
public static readonly int FixedProjectileInt = (int)WeaponType.FixedProjectile;
public static readonly int FixedBeamInt = (int)WeaponType.FixedBeam;
public static readonly int TurretProjectileInt = (int)WeaponType.TurretProjectile;
public static readonly int TurretBeamInt = (int)WeaponType.TurretBeam;
#endregion
#region Public variables
// IMPORTANT - when changing this section also update SetClassDefault()
// Also update ClassName(ClassName className) Clone Constructor (if there is one)
///
/// Name of the weapon
///
public string name;
///
/// The type of weapon. e.g. FixedProjectile, FixedBeam, TurretProjectile etc.
/// NOTE: We do not support changing this at runtime once the weapon has been initialised.
///
public WeaponType weaponType;
///
/// Position of the centre of the weapon in local space relative to the pivot point of the ship or surface turret.
/// This is the position where projectiles will be fired from.
///
public Vector3 relativePosition;
///
/// The local space direction in which the weapon fires. If you modify this, call Initialise().
///
public Vector3 fireDirection;
public bool isMultipleFirePositions;
public List firePositionList;
///
/// The prefab for the projectile fired by this weapon.
/// If you modify this, call shipControlModule.ReinitialiseShipProjectilesAndEffects() or
/// surfaceTurretModule.ReinitialiseTurretProjectilesAndEffects() depending on which component
/// this weapon is a member of.
///
public ProjectileModule projectilePrefab;
///
/// [INTERNAL USE ONLY]
/// The ID number for this weapon's projectile prefab (as assigned by the SSCManager in the scene).
///
public int projectilePrefabID;
///
/// The minimum time (in seconds) between consecutive firings of the weapon.
///
[Range(0.05f, 10f)] public float reloadTime;
///
/// The time (in seconds) until this weapon can fire again. Used for both
/// fixed and beam weapons.
///
public float reloadTimer;
///
/// The prefab for the beam to be fired by this weapon.
/// If you modify this, call shipControlModule.ReinitialiseShipBeams()
///
public BeamModule beamPrefab;
///
/// [INTERNAL USE ONLY]
/// The ID number for this weapon's beam prefab (as assigned by the SSCManager in the scene).
///
public int beamPrefabID;
///
/// The index of the damage region this weapon is associated with. When the damage model of the ship is set to simple, this
/// is irrelevant. A negative value means it is associated with no damage region (so the weapon's performance will not be
/// affected by damage). When the damage model of the ship is set to progressive, a value of zero means it is
/// associated with the main damage region. When the damage model of the ship is set to localised, a zero or positive value
/// indicates which damage region it is associated with (using a zero-based indexing system).
///
public int damageRegionIndex;
///
/// The starting health value of this weapon.
///
public float startingHealth;
///
/// Whether the weapon is shown as expanded in the inspector window of the editor.
///
public bool showInEditor;
///
/// Whether the weapon is shown as selected in the scene view of the editor.
///
public bool selectedInSceneView;
///
/// Whether the gizmos for this weapon are shown in the scene view of the editor.
///
public bool showGizmosInSceneView;
///
/// The trigger for the weapon to fire e.g. None, Primary, or Secondary.
/// Linked to the PlayerInputModule. Or AutoFire for AutoTargetingModule.
///
public FiringButton firingButton;
///
/// The quantity of projectiles available for use with this weapon.
/// Setting the value to -1, will give an unlimted amount of ammunition.
/// For beam weapons, instead, use chargeAmount.
///
public int ammunition;
///
/// The amount of charge or power the beam weapon has.
/// For projectile weapons, instead, use ammunition.
///
[Range(0f,1f)] public float chargeAmount;
///
/// The time (in seconds) it takes the fully discharged beam weapon to reach maximum charge
///
[Range(0f, 30f)] public float rechargeTime;
///
/// For turrets, the transform of the local y-axis pivot point.
/// The turret rotates around this point on the local x-z plane.
///
public Transform turretPivotY;
///
/// For turrets, the tranform of the local X-axis pivot point
/// This is the axis on which the barrel(s) move up and down
///
public Transform turretPivotX;
///
/// The minimum angle on the local x-axis the turret can rotate to
///
[Range(-359.99f, 359.99f)] public float turretMinX;
///
/// The maximum angle on the local x-axis the turret can rotate to
///
[Range(-359.99f, 359.99f)] public float turretMaxX;
///
/// The minimum angle on the local y-axis the turret can rotate to
///
[Range(-359.99f, 359.99f)] public float turretMinY;
///
/// The maximum angle on the local y-axis the turret can rotate to
///
[Range(-359.99f, 359.99f)] public float turretMaxY;
///
/// The speed at which the turret can rotate or raise the barrel(s) up and down
///
[Range(0f, 720f)] public float turretMoveSpeed;
///
/// When greater than 0, the number of seconds a turret will wait, after losing a target, to begin returning to the original orientation.
///
[Range(0f, 60f)] public float turretReturnToParkInterval;
///
/// Whether the weapon checks line of sight before firing (in order to prevent friendly fire) each frame. Only
/// relevant if the weaponType is TurretProjectile and the firingButton is AutoFire. Since this uses raycasts it
/// can lead to reduced performance.
///
public bool checkLineOfSight;
///
/// When the Auto Targeting Module is attached, use this to indicate targets should be assigned to the weapon.
///
public bool isAutoTargetingEnabled;
///
/// Currently only applies to turrets. Default to 0 which is the weapon
/// will attempt to hit the target. Range from 0.0 to 1.0. This will
/// have little or no effect on guided projectiles.
///
[Range(0f, 1f)] public float inaccuracy;
///
/// The maximum range (in metres) of the weapon. Currently only applies to beam weapons
///
public float maxRange;
///
/// The heat of the weapon - range 0.0 (starting temp) to 100.0 (max temp).
/// At runtime call either weapon.SetHeatLevel(..) or shipInstance.SetHeatLevel(..)
///
[Range(0f, 100f)] public float heatLevel;
///
/// The rate heat is added per second for beam weapons. For projectile weapons,
/// it is inversely proportional to the firing interval (reload time).
/// If rate is 0, heat level never changes.
///
[Range(0f, 20f)] public float heatUpRate;
///
/// The rate heat is removed per second. This is the rate the weapon cools when not in use.
///
[Range(0f, 20f)] public float heatDownRate = 2f;
///
/// The heat level that the weapon will begin to overheat and start being less efficient.
///
[Range(50f, 100f)] public float overHeatThreshold = 80f;
///
/// When the weapon reaches max heat level of 100, will the weapon be inoperable
/// until it is repaired?
///
public bool isBurnoutOnMaxHeat;
#endregion
#region Public Properties
///
/// The current performance level of this weapon (determined by the Health and Heat). The performance level affects how
/// efficient it is. At a performance level of one it operates normally. At a performance level of 0 it can do nothing.
///
public float CurrentPerformance { get { return currentPerformance; } }
///
/// The estimated range (in metres) of the weapon
///
public float estimatedRange { get; internal set; }
///
/// The normalised local space direction in which the weapon fires.
///
public Vector3 fireDirectionNormalised { get; private set; }
///
/// Returns whether the weapon has line-of-sight to the target. NOTE: This does not calculate line-of-sight, it
/// merely returns the last calculated value. To calculate line-of-sight for a weapon, call WeaponHasLineOfSight() on the
/// ship or surface turret module that it is attached to.
///
public bool HasLineOfSight { get; private set; }
///
/// The current health value of this weapon.
///
public float Health
{
get { return health; }
set
{
// Update the health value
health = value;
// Update the current performance value
UpdateWeaponPerformance();
}
}
///
/// [READONLY] When initialised, is the weapon in the parked or original orientation?
///
public bool IsParked { get { return isParked; } }
///
/// [READ ONLY] Is the weapon one that can fire projectiles?
///
public bool IsProjectileWeapon { get { return weaponType == WeaponType.FixedProjectile || weaponType == WeaponType.TurretProjectile; } }
///
/// [READ ONLY] Is the weapon one that can fire beams, rays or lasers?
///
public bool IsBeamWeapon { get { return weaponType == WeaponType.FixedBeam || weaponType == WeaponType.TurretBeam; } }
///
/// [READ ONLY] Is this weapon a turret?
///
public bool IsTurretWeapon { get { return weaponType == WeaponType.TurretBeam || weaponType == WeaponType.TurretProjectile; } }
///
/// Cached from the projectilePrefab for faster lookup when a projectile is instantiated
///
public bool isProjectileKGuideToTarget { get; internal set; }
#endregion
#region Public non-serialized variables
///
/// Is the weapon locked onto the target. If it is fired, is it
/// likely to hit the target? i.e. is it facing the correct direction?
///
[System.NonSerialized] public bool isLockedOnTarget;
///
/// After the weapon has been initialised, this can be used to avoid enumeration lookups
///
[System.NonSerialized] public int weaponTypeInt;
#endregion
#region Private variables
private Ray raycastRay;
private RaycastHit raycastHitInfo;
private Vector3 weaponRelativeFireDirectionLOS = Vector3.zero;
private float returnToParkTimer;
private bool isParked;
[Range(0f, 1f)] internal float currentPerformance;
#endregion
#region Internal Variables
///
/// [INTERNAL USE ONLY]
/// See Health property
///
internal float health;
///
/// [INTERNAL USE ONLY]
/// For turrets, stores the initial inverse rotation to be subtracted
/// from the current rotation on the Y-axis. Also subtracts parent
/// object rotation.
///
[System.NonSerialized] internal Quaternion intPivotYInvRot;
///
/// [INTERNAL USE ONLY]
/// For turrets, stores the initial local rotation for the optional return to parked rotation
///
[System.NonSerialized] internal Quaternion intPivotYLocalRot;
///
/// [INTERNAL USE ONLY]
/// For turrets, stores the initial local rotation for the optional return to parked rotation
///
[System.NonSerialized] internal Quaternion intPivotXLocalRot;
///
/// [INTERNAL USE ONLY] Call weapon.SetTarget(..), ship.SetWeaponTarget(..),
/// surfaceTurretModule.SetWeaponTarget(..), weapon.GetTarget()
/// Target of the weapon. Currently only applicable to Turret WeaponTypes and projectile
/// weapons with guided projectiles.
/// Determines where the turret or guided projectile should aim.
///
[System.NonSerialized] internal GameObject target;
///
/// [INTERNAL USE ONLY] Call weapon.SetTargetShip(..) or ship.SetWeaponTargetShip(...),
/// surfaceTurretModule.SetWeaponTargetShip(..),or weapon.GetTargetShip().
/// Target of the weapon. Currently only applicable to Turret WeaponTypes and projectile
/// weapons with guided projectiles.
/// Determines where the turret or guided projectile should aim.
///
[System.NonSerialized] internal ShipControlModule targetShip;
///
/// [INTERNAL USE ONLY]
/// Target's guidHash of the weapon. Currenly used with targetShip to define a DamageRegion
///
[System.NonSerialized] internal int targetguidHash;
///
/// [INTERNAL USE ONLY] Call weapon.SetTargetShipDamageRegion(..) or ship.SetWeaponTargetShipDamageRegion(..).
/// The damage region being targeted.
///
[System.NonSerialized] internal DamageRegion targetShipDamageRegion;
///
/// [INTERNAL USE ONLY]
/// Currently only updated for Turret weapons
///
[System.NonSerialized] internal Vector3 targetLastPosition;
///
/// [INTERNAL USE ONLY]
///
//[System.NonSerialized] internal Quaternion turretTargetRotation;
///
/// Timer recording how long the current target has been invalid (lost line-of-sight etc) for.
/// If the current target is valid, will be set to zero. Used by the Auto Targeting Module.
///
[System.NonSerialized] internal float invalidTargetTimer = 0f;
///
/// Timer recording how long it has been since we were assigned a new target. Used by the Auto Targeting Module.
///
[System.NonSerialized] internal float assignedNewTargetTimer = 0f;
///
/// [INTERNAL USE ONLY]
/// For beam weapons, this is used to identify the active beam for each firing point
///
[System.NonSerialized] internal List beamItemKeyList = null;
#endregion
#region Class constructors
public Weapon()
{
SetClassDefaults();
}
// Copy constructor
public Weapon(Weapon weapon)
{
if (weapon == null) { SetClassDefaults(); }
else
{
this.name = weapon.name;
this.weaponType = weapon.weaponType;
this.relativePosition = weapon.relativePosition;
this.fireDirection = weapon.fireDirection;
this.isMultipleFirePositions = weapon.isMultipleFirePositions;
if (weapon.firePositionList == null || weapon.firePositionList.Count == 0)
{
this.firePositionList = new List(2);
this.firePositionList.Add(Vector3.zero);
}
else
{
if (this.firePositionList == null) { this.firePositionList = new List(weapon.firePositionList.Count); }
this.firePositionList.AddRange(weapon.firePositionList);
}
this.projectilePrefab = weapon.projectilePrefab;
this.projectilePrefabID = weapon.projectilePrefabID;
this.beamPrefab = weapon.beamPrefab;
this.beamPrefabID = weapon.beamPrefabID;
this.reloadTime = weapon.reloadTime;
this.damageRegionIndex = weapon.damageRegionIndex;
this.startingHealth = weapon.startingHealth;
this.Health = weapon.Health;
this.showInEditor = weapon.showInEditor;
this.selectedInSceneView = weapon.selectedInSceneView;
this.showGizmosInSceneView = weapon.showGizmosInSceneView;
this.firingButton = weapon.firingButton;
this.ammunition = weapon.ammunition;
this.rechargeTime = weapon.rechargeTime;
this.chargeAmount = weapon.chargeAmount;
this.turretPivotY = weapon.turretPivotY;
this.turretPivotX = weapon.turretPivotX;
this.turretMinX = weapon.turretMinX;
this.turretMaxX = weapon.turretMaxX;
this.turretMinY = weapon.turretMinY;
this.turretMaxY = weapon.turretMaxY;
this.turretMoveSpeed = weapon.turretMoveSpeed;
this.turretReturnToParkInterval = weapon.turretReturnToParkInterval;
this.isLockedOnTarget = weapon.isLockedOnTarget;
this.checkLineOfSight = weapon.checkLineOfSight;
this.isAutoTargetingEnabled = weapon.isAutoTargetingEnabled;
this.inaccuracy = weapon.inaccuracy;
this.maxRange = weapon.maxRange;
this.heatLevel = weapon.heatLevel;
this.heatUpRate = weapon.heatUpRate;
this.heatDownRate = weapon.heatDownRate;
this.overHeatThreshold = weapon.overHeatThreshold;
this.isBurnoutOnMaxHeat = weapon.isBurnoutOnMaxHeat;
this.Initialise(Quaternion.Inverse(Quaternion.identity));
}
}
#endregion
#region Internal Member Methods
///
/// Check if we need to change the heat level on this weapon. If
/// heat up rate is 0, then do nothing.
/// When heatInput = 0, the weapon starts to cool down.
/// heatInput is the amount per second.
/// For projectile weapons it is inversely proportional to the
/// firing interval (reloadTime).
///
///
///
internal void ManageHeat (float dTime, float heatInput)
{
if (heatUpRate > 0f)
{
// Heat or cool weapon independently to the health level.
if (heatInput > 0f)
{
// Heating up
if (heatLevel < 100f)
{
SetHeatLevel(heatLevel + (heatInput * heatUpRate * dTime));
}
}
// Only cool down if not burnt out
else if (heatDownRate > 0f && (!isBurnoutOnMaxHeat || heatLevel < 100f))
{
// Cooling down
SetHeatLevel(heatLevel - (heatDownRate * dTime));
}
}
}
///
/// Rotate the weapon's turret to face the target.
/// Call SetWeaponTarget(..), SetWeaponTarget to set a weapon's target.
/// For performance reasons, it assumes the weaponType is a turret.
/// Updates isLockedOnTarget when turret is facing the target +/- 5 degrees.
/// Auto-park the weapon after losing the target if the park interval > 0 seconds.
///
///
internal void MoveTurret(Vector3 worldVelocity, bool isSurfaceTurret)
{
if (turretPivotY != null)
{
if (target != null)
{
// Adjust the target position for the relative speed of the ship or surfaceTurret aiming at the target
Vector3 targetRelativeVelocity = -worldVelocity; // Target velo - our velo
if (targetShip != null && targetShip.IsInitialised)
{
bool isTargetShipDamageRegion = targetguidHash != 0 && targetShipDamageRegion != null;
// If the target ship has been destroyed, stop targeting that ship.
// Somehow the shipInstance can become null even when targetShip is not null...
if (targetShip.shipInstance == null || targetShip.shipInstance.Destroyed())
{
ClearTarget();
return;
}
// If this is a damage region target, and it has been destroyed, then stop targeting it.
else if (isTargetShipDamageRegion && targetShipDamageRegion.isDestroyed)
{
ClearTarget();
return;
}
else
{
targetLastPosition = isTargetShipDamageRegion ? targetShip.shipInstance.GetDamageRegionWSPosition(targetShipDamageRegion) : targetShip.shipInstance.TransformPosition;
//targetLastPosition = targetShip.shipInstance.TransformPosition;
// If the target velocity is known, add it. Target velo - our velo
targetRelativeVelocity += targetShip.shipInstance.WorldVelocity;
}
}
else { targetLastPosition = target.transform.position; }
float targetRelativeSpeed = targetRelativeVelocity.magnitude;
Vector3 turretToTargetVector = targetLastPosition - turretPivotX.position;
float distToTarget = turretToTargetVector.magnitude;
Vector3 adjustedTargetPosition = targetLastPosition;
// Only adjust the target position if it has speed relative to the turret
// For guided projectiles, we don't want to aim in front of the target, else the projectiles
// suddenly veer off towards the target (rather than the predicted target position) due to the
// way Augmented Proportional Navigation works.
// Turret Beam weapons should always aim directly at the target (assuming inaccuracy = 0)
if (targetRelativeSpeed > 0.1f && !isProjectileKGuideToTarget && weaponTypeInt != TurretBeamInt)
{
// We need to calculate which position we should aim at so that the turret fire will intercept the target
float projectileSpeed = projectilePrefab.startSpeed;
float cosineTheta = Vector3.Dot(targetRelativeVelocity, -turretToTargetVector) / (targetRelativeSpeed * distToTarget);
float interceptionTime = 0f;
// Is this is a quadratic equation?
if (targetRelativeSpeed != projectileSpeed)
{
// This is a quadratic equation, so first check if there is a solution/s to the problem
float quadraticDiscriminant = ((targetRelativeSpeed * targetRelativeSpeed) * ((cosineTheta * cosineTheta) - 1)) +
(projectileSpeed * projectileSpeed);
if (quadraticDiscriminant >= 0f)
{
// Always choose the positive time solution
interceptionTime = (-targetRelativeSpeed * distToTarget * cosineTheta) +
(distToTarget * Mathf.Sqrt(quadraticDiscriminant));
interceptionTime /= (projectileSpeed * projectileSpeed) - (targetRelativeSpeed * targetRelativeSpeed);
}
}
else if (targetRelativeSpeed * cosineTheta != 0f)
{
// Linear solution - this is an edge case but is just here to avoid any divide by zero issues
interceptionTime = distToTarget / (2f * targetRelativeSpeed * cosineTheta);
}
// Calculate the interception position from the interception time
adjustedTargetPosition += targetRelativeVelocity * interceptionTime;
}
if (inaccuracy > 0f)
{
// Adjust the target position based on inaccuracy
// Inaccuracy is proportional to the distance to the target
adjustedTargetPosition += Random.insideUnitSphere * (distToTarget * 0.1f * inaccuracy);
}
// Find the position of the target in turret local space
Vector3 targetTurretSpacePos = Vector3.forward;
if (!isSurfaceTurret)
{
// Ship turret: Aim from the turret pivot Y parent object
targetTurretSpacePos = turretPivotY.parent.InverseTransformPoint(adjustedTargetPosition);
}
else
{
// Surface turret: Aim from the turret relative position
Quaternion turretParentInverseRotation = Quaternion.Inverse(turretPivotY.parent.rotation);
Vector3 turretRelativePosition = ((turretParentInverseRotation * turretPivotX.rotation) * relativePosition) + turretPivotY.parent.transform.position;
// Transform into turret space using rotation of pivot Y parent object and position of turret relative position
targetTurretSpacePos = turretParentInverseRotation * (adjustedTargetPosition - turretRelativePosition);
}
bool isFacingTarget = true;
// Turn the base of the turret
// Calculate the angle to rotate the turret base by
float azimuthAngle = Mathf.Atan2(targetTurretSpacePos.x, targetTurretSpacePos.z) * Mathf.Rad2Deg;
// Clamp the angle by rotation limits
if (azimuthAngle < turretMinY) { azimuthAngle = turretMinY; isFacingTarget = false; }
else if (azimuthAngle > turretMaxY) { azimuthAngle = turretMaxY; isFacingTarget = false; }
// Rotate the turret base towards the calculated angle at a given speed
turretPivotY.localRotation = Quaternion.RotateTowards(turretPivotY.localRotation,
Quaternion.Euler(0f, azimuthAngle, 0f), turretMoveSpeed * Time.deltaTime);
if (isFacingTarget)
{
// Is the turret facing roughly in the correct direction? +/- 5 degrees
float fixedEulerAngleY = turretPivotY.localRotation.eulerAngles.y > 180f ? turretPivotY.localRotation.eulerAngles.y - 360f : turretPivotY.localRotation.eulerAngles.y;
isFacingTarget = fixedEulerAngleY > azimuthAngle - 5f && fixedEulerAngleY < azimuthAngle + 5f;
}
// Move the barrel(s) up and down
if (turretPivotX != null)
{
// Calculate the angle of inclination for the turret guns
float altitudeAngle = Mathf.Atan(targetTurretSpacePos.y /
Mathf.Sqrt((targetTurretSpacePos.x * targetTurretSpacePos.x) + (targetTurretSpacePos.z * targetTurretSpacePos.z)))
* Mathf.Rad2Deg;
// Clamp the angle by rotation limits
if (altitudeAngle < turretMinX) { altitudeAngle = turretMinX; isFacingTarget = false; }
else if (altitudeAngle > turretMaxX) { altitudeAngle = turretMaxX; isFacingTarget = false; }
// Rotate the turret guns towards the calculated angle at a given speed
turretPivotX.localRotation = Quaternion.RotateTowards(turretPivotX.localRotation, Quaternion.Euler(-altitudeAngle, 0f, 0f), turretMoveSpeed * Time.deltaTime);
//Debug.DrawRay(turretPivotX.position, turretPivotX.forward * 1000f, Color.black);
if (isFacingTarget)
{
// Is the turret facing roughly in the correct direction? +/- 5 degrees
float fixedEulerAngleX = turretPivotX.localRotation.eulerAngles.x > 180f ? turretPivotX.localRotation.eulerAngles.x - 360f : turretPivotX.localRotation.eulerAngles.x;
isFacingTarget = fixedEulerAngleX > -altitudeAngle - 5f && fixedEulerAngleX < -altitudeAngle + 5f;
}
}
else { isFacingTarget = false; }
// The turret has most likely moved, so it is probably not parked.
// This is required here in case some code directly updated target variable
isParked = false;
// Check range to target
// If the weapon is at its full rotation angle or inclination, assume it is not facing the target
isLockedOnTarget = isFacingTarget;
}
else
{
// If there is no target, it can't be locked on to a target
isLockedOnTarget = false;
if (turretReturnToParkInterval > 0f && !isParked)
{
// If the turret should auto-park but hasn't started yet, increment the timer
if (returnToParkTimer < turretReturnToParkInterval) { returnToParkTimer += Time.deltaTime; }
else
{
// Move the barrel(s) up or down towards original rotation
turretPivotY.localRotation = Quaternion.RotateTowards(turretPivotY.localRotation, intPivotYLocalRot, turretMoveSpeed * Time.deltaTime);
// Rotate the base left or right towards the initial rotation
if (turretPivotX != null)
{
turretPivotX.localRotation = Quaternion.RotateTowards(turretPivotX.localRotation, intPivotYLocalRot, turretMoveSpeed * Time.deltaTime);
}
// Once parked we need to opt out of this
isParked = turretPivotY.localRotation == intPivotYLocalRot && (turretPivotX == null || turretPivotX.localRotation == intPivotXLocalRot);
}
}
}
}
// If there is no pivot point, it can't be locked on to a target
else { isLockedOnTarget = false; }
}
///
/// Returns whether a weapon has line of sight to a target.
/// If directToTarget is set to true, will raycast directly from the weapon to the target.
/// If directToTarget is set to false, will raycast in the direction the weapon is facing.
/// This method will return true if the raycast hits:
/// a) The target,
/// b) An enemy ship (distingushed by faction ID) - even if it is not the target when anyEnemy is true,
/// c) An object that isn't the target (if obstaclesBlockLineOfSight is set to false),
/// d) Nothing.
/// This method will return false if the raycast hits:
/// a) A friendly ship (distinguished by faction ID),
/// b) An object that isn't the target (if obstaclesBlockLineOfSight is set to true).
/// c) An enemy ship that is not the target when anyEnemy is false
/// The trfmPos, trfmRight, trfmUp, trfmFwd and trfmInvRot vectors/quaternions refer to the object the turret is
/// attached to (either a ship or a surface turret module). The same is true for the factionId integer.
///
///
///
///
///
///
///
///
///
///
///
///
///
internal void UpdateLineOfSight(GameObject target, Vector3 trfmPos, Vector3 trfmRight, Vector3 trfmUp,
Vector3 trfmFwd, Quaternion trfmInvRot, int factionId, bool isSurfaceTurret, bool directToTarget = false,
bool obstaclesBlockLineOfSight = true, bool anyEnemy = true)
{
// v1.2.4 fixed MissingReferenceException
if (target == null) { HasLineOfSight = false; return; }
#region Calculate Raycast Origin
// Get base weapon world position (not accounting for relative fire position)
if (isSurfaceTurret)
{
raycastRay.origin = trfmPos;
}
else
{
raycastRay.origin = (trfmRight * relativePosition.x) +
(trfmUp * relativePosition.y) +
(trfmFwd * relativePosition.z) +
trfmPos;
}
#endregion
#region Calculate Raycast Direction
Vector3 targetTrfmPos = target.transform.position;
if (directToTarget)
{
// Check line of sight from the weapon directly to the target
raycastRay.direction = targetTrfmPos - raycastRay.origin;
}
else
{
// Check line of sight in the direction the weapon is aiming
// Get relative fire direction
weaponRelativeFireDirectionLOS = fireDirectionNormalised;
// If this is a turret, adjust relative fire direction based on turret rotation
if (weaponTypeInt == TurretProjectileInt || weaponTypeInt == TurretBeamInt)
{
if (isSurfaceTurret)
{
weaponRelativeFireDirectionLOS = (trfmInvRot * turretPivotX.rotation) * weaponRelativeFireDirectionLOS;
}
else
{
weaponRelativeFireDirectionLOS = (trfmInvRot * turretPivotX.rotation) * intPivotYInvRot * weaponRelativeFireDirectionLOS;
}
}
// Get weapon world fire direction
raycastRay.direction = (trfmRight * weaponRelativeFireDirectionLOS.x) +
(trfmUp * weaponRelativeFireDirectionLOS.y) +
(trfmFwd * weaponRelativeFireDirectionLOS.z);
}
#endregion
#region Check Line Of Sight
bool hasLineOfSight = true;
// Raycast in the direction we have chosen
// NOTE: If there is a rigidbody on the object (other than the collider), raycastHitInfo.transform will be that
// of the rigidbody, not the collider.
//if (Physics.Raycast(raycastRay, out raycastHitInfo, projectilePrefab.startSpeed * projectilePrefab.despawnTime))
// Use estimatedRange to cater for projectiles and beams.
if (Physics.Raycast(raycastRay, out raycastHitInfo, estimatedRange))
{
float sqrDistToTarget = (targetTrfmPos - raycastRay.origin).sqrMagnitude;
// If required, check if we hit (only) the target indicated. This is particularly important for
// ship damage regions.
if (!anyEnemy)
{
// Determine if the collider is child of the target. Will also be true if the collider is directly on the target.
bool isColliderChildOfTarget = raycastHitInfo.collider.transform.IsChildOf(target.transform);
// This is almost always going to be true for ship damage regions because it has volume and the
// hitpoint will be closer than the middle of the damage region.
if (obstaclesBlockLineOfSight && sqrDistToTarget > raycastHitInfo.distance * raycastHitInfo.distance)
{
// Check whether it is the target or if it is some other object
hasLineOfSight = isColliderChildOfTarget;
}
// If we hit a friendly ship, set hasLineOfSight to false (maybe we incorrectly targeted a friendly ship)
if (hasLineOfSight && raycastHitInfo.rigidbody != null)
{
ShipControlModule hitShipControlModule = raycastHitInfo.rigidbody.GetComponent();
if (hitShipControlModule != null && hitShipControlModule.IsInitialised && hitShipControlModule.shipInstance != null)
{
// If we hit a ship, check if it has the same faction ID as we do
// If it has the same faction ID as we do, it is a friend, so we don't have line of sight
// Otherwise it is an enemy, so we have line of sight
hasLineOfSight = hitShipControlModule.shipInstance.factionId != factionId;
}
}
}
else if (raycastHitInfo.rigidbody != null)
{
// Performance has been verified - No GC and low overhead in U201746f1.
// What we hit had a rigidbody attached, so it could be a ship
ShipControlModule hitShipControlModule = raycastHitInfo.rigidbody.GetComponent();
if (hitShipControlModule != null && hitShipControlModule.IsInitialised && hitShipControlModule.shipInstance != null)
{
// If we hit a ship, check if it has the same faction ID as we do
// If it has the same faction ID as we do, it is a friend, so we don't have line of sight
// Otherwise it is an enemy, so we have line of sight
hasLineOfSight = hitShipControlModule.shipInstance.factionId != factionId;
}
// Only check for obstacles blocking line of sight if what we hit was between us and the target
else if (obstaclesBlockLineOfSight && sqrDistToTarget > raycastHitInfo.distance * raycastHitInfo.distance)
{
// If we did not hit a ship, check if it is the target
hasLineOfSight = raycastHitInfo.transform.IsChildOf(target.transform);
}
}
// Only check for obstacles blocking line of sight if what we hit was between us and the target
else if (obstaclesBlockLineOfSight && sqrDistToTarget > raycastHitInfo.distance * raycastHitInfo.distance)
{
// What we hit did not have a rigidbody attached, so it is a static object
// We now need to check whether it is the target or if it is some other object
hasLineOfSight = raycastHitInfo.transform.IsChildOf(target.transform);
}
}
#endregion
// Update the HasLineOfSight property accordingly
HasLineOfSight = hasLineOfSight;
}
#endregion
#region Public Member Methods
public void SetClassDefaults()
{
this.name = "Weapon";
this.weaponType = WeaponType.FixedProjectile;
this.relativePosition = Vector3.zero;
this.fireDirection = Vector3.forward;
this.isMultipleFirePositions = false;
// The most common scenarios would be 1 or 2 fire positions / cannons on a single weapon
this.firePositionList = new List(2);
this.firePositionList.Add(Vector3.zero);
this.projectilePrefab = null;
this.projectilePrefabID = -1;
this.beamPrefab = null;
this.beamPrefabID = -1;
this.reloadTime = 0.5f;
this.damageRegionIndex = -1;
this.startingHealth = 100f;
this.Health = 100f;
this.showInEditor = true;
this.selectedInSceneView = false;
this.showGizmosInSceneView = true;
// None by default so that weapon doesn't automatically fire when a weapon is added at runtime
this.firingButton = FiringButton.None;
this.ammunition = -1; // Unlimited (this should always be the default so it works with adding Beams at runtime)
this.rechargeTime = 0f; // Instantly recharge
this.chargeAmount = 1f; // Fully charged
this.turretPivotY = null;
this.turretPivotX = null;
this.turretMinX = -180f;
this.turretMaxX = 180f;
this.turretMinY = -180f;
this.turretMaxY = 180f;
this.turretMoveSpeed = 20f;
this.turretReturnToParkInterval = 0f;
this.isLockedOnTarget = false;
this.checkLineOfSight = false;
this.isAutoTargetingEnabled = false;
this.inaccuracy = 0f;
this.maxRange = 2000f;
this.HasLineOfSight = false;
this.invalidTargetTimer = 0f;
this.assignedNewTargetTimer = 0f;
this.heatLevel = 0f;
this.heatUpRate = 0f;
this.heatDownRate = 2f;
this.overHeatThreshold = 80f;
this.isBurnoutOnMaxHeat = false;
this.Initialise(Quaternion.Inverse(Quaternion.identity));
}
///
/// Initialises data for the weapon. This does some precalculation to allow for performance improvements.
/// Call after modifying fireDirection.
/// The rootObject is typically the gameobject of the shipControlModule or the surfaceTurretModule.
/// Initialised Beam weapons.
///
///
public void Initialise(Quaternion rootObjectInvRotation)
{
weaponTypeInt = (int)weaponType;
#if UNITY_EDITOR
if (weaponTypeInt == TurretProjectileInt || weaponTypeInt == TurretBeamInt)
{
if (turretPivotX == null || turretPivotY == null) { Debug.Log("[ERROR] The " + name + (weaponTypeInt == TurretBeamInt ? " Beam" : " Projectile") + " turret is missing Turret Pivot setup."); }
}
#endif
// Calculate normalised vectors
if ((weaponTypeInt == TurretProjectileInt || weaponTypeInt == TurretBeamInt) && turretPivotY != null)
{
// The turret pivot y parent gameobject may be rotated
intPivotYInvRot = Quaternion.Inverse(rootObjectInvRotation * turretPivotY.rotation);
fireDirectionNormalised = fireDirection.normalized;
// Remember intial rotations for self-parking option.
intPivotYLocalRot = turretPivotY.localRotation;
if (turretPivotX != null) { intPivotXLocalRot = turretPivotX.localRotation; }
else { intPivotXLocalRot = Quaternion.identity; }
}
else
{
intPivotYInvRot = Quaternion.Inverse(Quaternion.identity);
fireDirectionNormalised = fireDirection.normalized;
intPivotXLocalRot = Quaternion.identity;
intPivotYLocalRot = Quaternion.identity;
}
// Intialise beam weapon
if (weaponTypeInt == FixedBeamInt || weaponTypeInt == TurretBeamInt)
{
// If instant recharge, make fully charged
if (rechargeTime == 0f) { chargeAmount = 1f; }
int numFirePositions = firePositionList == null ? 0 : firePositionList.Count;
int numBeamItemKeys = beamItemKeyList == null ? 0 : beamItemKeyList.Count;
// Note: This may need changing for Ship.SetWeaponFireDirection(int, Vector3)
if (beamItemKeyList == null) { beamItemKeyList = new List(numFirePositions); }
else
{
if (numBeamItemKeys != numFirePositions) { beamItemKeyList.Clear(); }
}
for (int fIdx = 0; fIdx < numFirePositions; fIdx++)
{
if (fIdx >= numBeamItemKeys)
{
beamItemKeyList.Add(new SSCBeamItemKey(-1, -1, 0));
numBeamItemKeys++;
}
// Reset exiting beam keys. This might need changing for Ship.SetWeaponFireDirection(..)
else
{
SSCBeamItemKey beamItemKey = new SSCBeamItemKey(-1, -1, 0);
beamItemKeyList[fIdx] = beamItemKey;
}
}
}
else { beamItemKeyList = null; }
// Initialise raycast ray
raycastRay = new Ray(Vector3.zero, Vector3.up);
UpdateWeaponPerformance();
// Reset
isLockedOnTarget = false;
HasLineOfSight = false;
invalidTargetTimer = 0f;
assignedNewTargetTimer = 0f;
returnToParkTimer = 0f;
isParked = true;
}
///
/// Reset the heat level to 0 and reset health
///
public void Repair()
{
heatLevel = 0;
// Ensure threshold is a sensible value
if (overHeatThreshold < 50f) { overHeatThreshold = 80f; }
// This will also update the current performance
Health = startingHealth;
}
///
/// Set the new heat level on this weapon.
/// Range 0.0 (min) to 100.0 (max).
///
///
public void SetHeatLevel (float newHeatLevel)
{
if (newHeatLevel < 0f) { newHeatLevel = 0f; }
else if (newHeatLevel > 100f) { newHeatLevel = 100f; }
// Only update the weapon performance if we need to
// Update when:
// a) heatLevel will be equal or above the overheat threshold
// b) heatLevel will fallen below the overheat threshold
// c) AND it has changed
if (newHeatLevel != heatLevel && (newHeatLevel >= overHeatThreshold || (newHeatLevel < overHeatThreshold && heatLevel >= overHeatThreshold)))
{
heatLevel = newHeatLevel;
UpdateWeaponPerformance();
}
else
{
heatLevel = newHeatLevel;
}
}
///
/// Set the target GameObject for the weapon. Applies to Turret-type weapons and fixed projectile
/// weapons with guided projectiles. If targetShip was previously set, this will be reset to null.
///
///
public void SetTarget(GameObject targetGameObject)
{
isLockedOnTarget = false;
targetShip = null;
targetShipDamageRegion = null;
targetguidHash = 0;
if (targetGameObject == null)
{
// Reset last known position of the target
targetLastPosition = Vector3.zero;
// Used for turrets
returnToParkTimer = 0f;
isParked = false;
}
else
{
targetLastPosition = targetGameObject.transform.position;
}
this.target = targetGameObject;
}
///
/// Set the target ship for the weapon. Applies to Turret-type weapons and fixed projectile
/// weapons with guided projectiles.
/// Also sets the target (GameObject) for the weapon.
///
///
public void SetTargetShip(ShipControlModule targetShipControlModule)
{
isLockedOnTarget = false;
targetShip = targetShipControlModule;
targetShipDamageRegion = null;
targetguidHash = 0;
if (targetShip == null)
{
target = null;
// Reset last known position of the target
targetLastPosition = Vector3.zero;
// Used for turrets
returnToParkTimer = 0f;
isParked = false;
}
else
{
target = targetShip.gameObject;
if (targetShip.IsInitialised) { targetLastPosition = targetShip.shipInstance.TransformPosition; }
else { targetLastPosition = Vector3.zero; }
}
}
///
/// Set the target ship damage region for the weapon.
/// Also sets the target (GameObject) for the weapon.
///
///
///
public void SetTargetShipDamageRegion (ShipControlModule targetShipControlModule, DamageRegion damageRegion)
{
isLockedOnTarget = false;
targetShip = targetShipControlModule;
if (targetShip == null || damageRegion == null || damageRegion.guidHash == 0)
{
target = null;
targetShip = null;
targetguidHash = 0;
targetShipDamageRegion = null;
// Reset last known position of the target
targetLastPosition = Vector3.zero;
// Used for turrets
returnToParkTimer = 0f;
isParked = false;
}
else
{
// If the damage region child transform is set, consider using this for the gameObject to help with line of sight.
target = damageRegion.regionChildTransform == null ? targetShip.gameObject : damageRegion.regionChildTransform.gameObject;
targetguidHash = damageRegion.guidHash;
targetShipDamageRegion = damageRegion;
if (targetShip.IsInitialised)
{
targetLastPosition = targetShip.shipInstance.GetDamageRegionWSPosition(damageRegion);
}
else { targetLastPosition = Vector3.zero; }
}
}
///
/// Clear all targeting information for this weapon. This should be called if you do not know if the target
/// is a gameobject or a ship.
///
public void ClearTarget()
{
isLockedOnTarget = false;
target = null;
targetShip = null;
targetguidHash = 0;
// Reset last known position of the target
targetLastPosition = Vector3.zero;
// Used for turrets
returnToParkTimer = 0f;
isParked = false;
}
///
/// Get the current target for this weapon. Only applies to Turret-type weapons and FixedProjectile weapons with guided projectiles.
///
///
public GameObject GetTarget()
{
return target;
}
///
/// Get the current target ship for this weapon. Only applies to Turret-type weapons and FixedProjectile weapons with guided projectiles.
/// NOTE: A weapon may target a GameObject but not a Ship. For this case call GetTarget().
///
///
public ShipControlModule GetTargetShip()
{
return targetShip;
}
///
/// Update the CurrentPerformance value of this weapon. It gets automatically
/// called when the Health is changed and when heat levels change.
///
public void UpdateWeaponPerformance()
{
// Update the current performance value
if (heatLevel < 100f)
{
currentPerformance = health / startingHealth;
if (heatUpRate > 0f && heatLevel >= overHeatThreshold)
{
currentPerformance *= 1f - SSCMath.Normalise(heatLevel, overHeatThreshold, 100f);
}
//currentPerformance = currentPerformance > minPerformance ? currentPerformance : minPerformance;
currentPerformance = currentPerformance < 1f ? currentPerformance : 1f;
}
else { currentPerformance = 0f; }
}
#endregion
}
}