using UnityEngine;
// Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved.
namespace SciFiShipController
{
///
/// Attach to stationary ground-based turrets.
///
[AddComponentMenu("Sci-Fi Ship Controller/Weapon Components/Surface Turret Module")]
[HelpURL("http://scsmmedia.com/ssc-documentation")]
public class SurfaceTurretModule : MonoBehaviour
{
#region Enumerations
#endregion
#region Public variables
///
/// 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.
///
public bool initialiseOnStart = false;
///
/// The faction or alliance the item belongs to. This can be used to identify if an item is friend or foe.
/// Default (neutral) is 0
///
public int factionId = 0;
///
/// Although normally representing a squadron of ships, this can be used on a turret to group it with other things in your scene
///
public int squadronId = -1;
///
/// Automatically create a Location in the SSCManager when turret is initialised
///
public bool autoCreateLocation = false;
///
/// Is this turret (Location) visible to radar queries? The turret needs a LocationData
/// entry in SSCManager to appear on radar.
///
public bool isVisibleToRadar;
///
/// The relative size of the blip on the radar mini-map
///
[Range(1, 5)] public byte radarBlipSize = 1;
///
/// The magnitude of the acceleration (in metres per second squared) due to gravity.
/// This affects how a projectile travels after it has been fired.
///
public float gravitationalAcceleration;
///
/// The direction in world space in which gravity acts upon the turret.
/// This affects how a projectile travels after it has been fired.
///
public Vector3 gravityDirection;
///
/// The sound or particle FX used when the turret is destroyed. It should automatically be despawned when the turret is destroyed.
/// If you modify this, call ReinitialiseTurretProjectilesAndEffects().
///
public EffectsModule destructionEffectsObject;
///
/// This is used when you want pre-build fragments of the turret to explode out from the turrets position when it is destroyed.
/// If you modify this, call ReinitialiseTurretDestructObjects().
///
public DestructModule destructObject;
///
/// Should the turret be removed from the scene when its health reaches 0?
///
public bool isDestroyOnNoHealth = false;
///
/// [READONLY] Has the turret been initialised?
///
public bool IsInitialised { get { return isInitialised; } }
///
/// [READONLY] The position of the turret as a vector. Derived from the position of the transform.
///
public Vector3 TransformPosition { get { return trfmPos; } }
///
/// [READONLY] The forward direction of the turret in world space as a vector. Derived from the forward direction of the transform.
///
public Vector3 TransformForward { get { return trfmFwd; } }
///
/// [READONLY] The right direction of the turret in world space as a vector. Derived from the right direction of the transform.
///
public Vector3 TransformRight { get { return trfmRight; } }
///
/// [READONLY] The up direction of the turret in world space as a vector. Derived from the up direction of the transform.
///
public Vector3 TransformUp { get { return trfmUp; } }
///
/// [READONLY] The rotation of the turret in world space as a quaternion. Derived from the rotation of the transform.
///
public Quaternion TransformRotation { get { return trfmRot; } }
///
/// [READONLY] The inverse rotation of the turret in world space as a quaternion. Derived from the rotation of the transform.
///
public Quaternion TransformInverseRotation { get { return trfmInvRot; } }
///
/// [READONLY] The number used by the SSCRadar system to identify this Surface Turret at a point in time.
/// This should not be stored across frames and is updated as required by the system.
///
public int RadarId { get { return radarItemIndex; } }
///
/// [READONLY] Has the surface turret been destroyed (typically when there is no weapon Health)
///
public bool IsDestroyed { get; private set; }
///
/// If a ship is being targeted, will return its name.
///
public string TargetShipName { get { return weapon.targetShip != null ? weapon.targetShip.name : "-"; } }
///
/// If a ship damage region is being targeted, will return its name.
///
public string TargetShipDamageRegionName { get { return weapon.targetShipDamageRegion != null ? weapon.targetShipDamageRegion.name : "-"; } }
///
/// If a gameobject is being targeted, will return its name
///
public string TargetGameObjectName { get { return weapon.target != null ? (string.IsNullOrEmpty(weapon.target.name) ? "no name" : weapon.target.name) : "-"; } }
///
/// [INTERNAL ONLY]
///
public Weapon weapon;
///
/// [INTERNAL ONLY]
///
public bool allowRepaint = false;
#endregion
#region Public Delegates
public delegate void CallbackOnDestroy(SurfaceTurretModule surfaceTurretModule);
///
/// The name of the custom method that is called immediately
/// before the turret is destroyed. Your method must take 1
/// parameter of class SurfaceTurretModule. This should be a lightweight
/// method to avoid performance issues. It could be used to update
/// a score or affect the status of a mission.
///
public CallbackOnDestroy callbackOnDestroy = null;
#endregion
#region Internal variables
///
/// The ID number for this turret's destruction effects object prefab (as assigned by the SSCManager in the scene).
/// This is the index in the SSCManager effectsObjectTemplatesList.
/// [INTERNAL USE ONLY]
///
internal int effectsObjectPrefabID = -1;
///
/// Flag for whether the destruction effects object has been instantiated.
/// [INTERNAL USE ONLY]
///
internal bool isDestructionEffectsObjectInstantiated = false;
///
/// The ID number for this turret's destruct prefab (as assigned by the SSCManager in the scene).
/// This is the index in the SSCManager destructTemplateList.
/// [INTERNAL USE ONLY]
///
internal int destructObjectPrefabID = -1;
///
/// Flag for whether the destruct object has been activated.
/// [INTERNAL USE ONLY]
///
internal bool isDestructObjectActivated = false;
// Radar variables
///
/// [INTERNAL USE ONLY]
///
[System.NonSerialized] internal int radarItemIndex = -1;
#endregion
#region Private varibles
private bool isInitialised = false;
private SSCManager sscManager = null;
private SSCRadar sscRadar = null;
private LocationData locationData = null;
///
/// [INTERNAL ONLY] - instead use FireIfReady()
/// Attempt to manually fire the weapon
///
private bool isManualFireNow = false;
///
/// [INTERNAL ONLY] - instead use SetAutoFire()
///
private bool isAutoFire = false;
// Temp firing variables
private Vector3 weaponWorldBasePosition = Vector3.zero;
private Vector3 weaponWorldFirePosition = Vector3.zero;
private Vector3 weaponWorldFireDirection = Vector3.zero;
private Vector3 weaponRelativeFirePosition = Vector3.zero;
private Vector3 weaponRelativeFireDirection = Vector3.zero;
// cached data
private Vector3 worldVelocity = Vector3.zero;
private Vector3 worldAngularVelocity = Vector3.zero;
private Vector3 trfmUp;
private Vector3 trfmFwd;
private Vector3 trfmRight;
private Vector3 trfmPos;
private Quaternion trfmRot;
private Quaternion trfmInvRot;
private int weaponTypeInt;
#endregion
#region Initialise Methods
void Start()
{
if (initialiseOnStart) { Initialise(); }
}
///
/// Initialise the Turret
///
public void Initialise()
{
// Only initialise once
if (!isInitialised)
{
#if UNITY_EDITOR
if (gameObject.GetComponent())
{
Debug.LogWarning("ERROR: This Turret Module is NOT designed to be used on a Ship. Please configure a Turret under Weapons on the Ship Control Module Combat tab.");
return;
}
#endif
UpdatePositionData();
if (weapon != null)
{
// Before v1.2.3, surface turrets didn't set the weaponType in the editor.
if (weapon.weaponType != Weapon.WeaponType.TurretProjectile && weapon.weaponType != Weapon.WeaponType.TurretBeam) { weapon.weaponType = Weapon.WeaponType.TurretProjectile; }
weapon.Health = weapon.startingHealth;
// Added in v1.2.3
weapon.Initialise(trfmInvRot);
// Lookup the enumeration once
weaponTypeInt = weapon.weaponTypeInt;
// Get a reference to the Ship Controller Manager instance
sscManager = SSCManager.GetOrCreateManager();
// Initialise projectiles and effects objects
ReinitialiseTurretProjectilesAndEffects();
// Initialise beams and (beam) effects
ReinitialiseTurretBeams();
// Initialise destruct modules
ReinitialiseTurretDestructObjects();
// When initialising, there should never be an existing Location
if (autoCreateLocation) { CreateLocation(false); }
else if (isVisibleToRadar)
{
// No location created but visible to radar, so create as a RadarItemType.GameObject
if (sscRadar == null) { sscRadar = SSCRadar.GetOrCreateRadar(); }
if (sscRadar != null) { radarItemIndex = sscRadar.EnableRadar(gameObject, transform.position, factionId, squadronId, 0, radarBlipSize); }
}
isManualFireNow = false;
isAutoFire = weapon.firingButton == Weapon.FiringButton.AutoFire;
if (weapon.turretPivotY == null)
{
isInitialised = false;
#if UNITY_EDITOR
Debug.LogWarning("ERROR: The surface turret (" + gameObject.name + ") needs to be assigned a Pivot Y transform before it can fire projectiles." );
#endif
}
else if (weapon.turretPivotX == null)
{
isInitialised = false;
#if UNITY_EDITOR
Debug.LogWarning("ERROR: The surface turret (" + gameObject.name + ") needs to be assigned a Pivot X transform before it can fire projectiles." );
#endif
}
else
{
// If there is a DamageReceiver component attached, setup the callback.
DamageReceiver damageReceiver = GetComponent();
if (damageReceiver != null) { damageReceiver.callbackOnHit = TakeDamage; }
isInitialised = true;
}
}
}
}
#endregion
#region Update Methods
// Update is called once per frame
void Update()
{
UpdateWeapon();
}
#endregion
#region Private and Internal Member Methods
///
/// This gets called from FixedUpdate in BeamModule. It performs the following:
/// 1. Checks if the beam should be despawned
/// 2. Moves the beam
/// 3. Changes the length
/// 4. Checks if it hits anything
/// 5. Updates damage on what it hits
/// 6. Instantiate effects object at hit point
/// 7. Consumes weapon power
/// It needs to be a member of the surfaceTurretModule instance as it requires both turret and beam data.
/// Assumes the beam linerenderer has useWorldspace enabled.
///
///
internal void MoveBeam(BeamModule beamModule)
{
if (beamModule.isInitialised && beamModule.isBeamEnabled && beamModule.weaponIndex >= 0 && beamModule.firePositionIndex >= 0)
{
SSCBeamItemKey beamItemKey = weapon.beamItemKeyList[beamModule.firePositionIndex];
if (weapon.weaponTypeInt == Weapon.TurretBeamInt && beamItemKey.beamSequenceNumber == beamModule.itemSequenceNumber)
{
// Is ready to fire - taken from UpdateWeapon()
bool isReadyToFire = (isManualFireNow || isAutoFire) && weapon.isLockedOnTarget && (!weapon.checkLineOfSight || WeaponHasLineOfSight(weapon.target));
// Should this beam be despawned or returned to the pool?
// a) No charge in weapon
// b) Weapon has no health
// c) Weapon has no performance
// d) user stopped firing and has fired for min time permitted
// e) has exceeded the maximum firing duration
if (weapon.chargeAmount <= 0f || weapon.health <= 0f || weapon.currentPerformance == 0f || (!isReadyToFire && beamModule.burstDuration > beamModule.minBurstDuration) || beamModule.burstDuration > beamModule.maxBurstDuration)
{
// Unassign the beam from this weapon's fire position
weapon.beamItemKeyList[beamModule.firePositionIndex] = new SSCBeamItemKey(-1, -1, 0);
beamModule.DestroyBeam();
}
else
{
// Move the beam start
// Calculate the World-space Fire Position (like when weapon is first fired)
// Note: That the surface turret could have moved and rotated, so we recalc here.
Vector3 _weaponWSBasePos = trfmPos;
Vector3 _weaponWSFireDir = GetWeaponWorldFireDirection();
Vector3 _weaponWSFirePos = GetWeaponWorldFirePosition(_weaponWSBasePos, beamModule.firePositionIndex);
// Move the beam transform but keep the first LineRenderer position at 0,0,0
beamModule.transform.position = _weaponWSFirePos;
beamModule.transform.SetPositionAndRotation(_weaponWSFirePos, Quaternion.LookRotation(_weaponWSFireDir, trfmUp));
// Calc length and end position
float _desiredLength = beamModule.burstDuration * beamModule.speed;
if (_desiredLength > weapon.maxRange) { _desiredLength = weapon.maxRange; }
Vector3 _endPosition = _weaponWSFirePos + (_weaponWSFireDir * _desiredLength);
RaycastHit raycastHitInfo;
// Check if it hit anything
if (Physics.Linecast(_weaponWSFirePos, _endPosition, out raycastHitInfo, ~0, QueryTriggerInteraction.Ignore))
{
// Adjust the end position to the hit point
_endPosition = raycastHitInfo.point;
// Update damage if it hit a ship or an object with a damage receiver
if (!BeamModule.CheckShipHit(raycastHitInfo, beamModule, Time.deltaTime))
{
// If it hit an object with a DamageReceiver script attached, take appropriate action like call a custom method
BeamModule.CheckObjectHit(raycastHitInfo, beamModule, Time.deltaTime);
}
// ISSUES - the effect may not be visible while firing if:
// 1) if the effect despawn time is less than the beam max burst duration (We have a warning in the BeamModule editor)
// 2) if the effect does not have looping enabled (this could be more expensive to check)
// Add or Move the effects object
if (beamModule.effectsObjectPrefabID >= 0)
{
if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }
if (sscManager != null)
{
// If the effect has not been spawned, do now
if (beamModule.effectsItemKey.effectsObjectSequenceNumber == 0)
{
InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters
{
effectsObjectPrefabID = beamModule.effectsObjectPrefabID,
position = _endPosition,
rotation = beamModule.transform.rotation
};
// Instantiate the hit effects
if (sscManager.InstantiateEffectsObject(ref ieParms) != null)
{
if (ieParms.effectsObjectSequenceNumber > 0)
{
// Record the muzzle effect item key
beamModule.effectsItemKey = new SSCEffectItemKey(ieParms.effectsObjectPrefabID, ieParms.effectsObjectPoolListIndex, ieParms.effectsObjectSequenceNumber);
}
}
}
// Move the existing effects object to the end of the beam
else
{
// Currently we are not checking sequence number matching (for pooled effects) as it is faster and can
// avoid doing an additional GetComponent().
sscManager.MoveEffectsObject(beamModule.effectsItemKey, _endPosition, beamModule.transform.rotation, false);
}
}
}
}
// The beam isn't hitting anything AND there is an active effects object
else if (beamModule.effectsObjectPrefabID >= 0 && beamModule.effectsItemKey.effectsObjectSequenceNumber > 0)
{
// Destroy the effects object or return it to the pool
if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }
if (sscManager != null)
{
sscManager.DestroyEffectsObject(beamModule.effectsItemKey);
}
}
// Move the beam end (assumes world space)
// useWorldspace is enabled in beamModule.InitialiseBeam(..)
beamModule.lineRenderer.SetPosition(0, _weaponWSFirePos);
beamModule.lineRenderer.SetPosition(1, _endPosition);
// Consume weapon power. Consumes upt to 2x power when overheating.
if (weapon.rechargeTime > 0f) { weapon.chargeAmount -= Time.deltaTime * (weapon.heatLevel > weapon.overHeatThreshold ? 2f - weapon.currentPerformance : 1f) / beamModule.dischargeDuration; }
weapon.ManageHeat(Time.deltaTime, 1f);
// If we run out of power, de-activate it before weapon starts recharging
if (weapon.chargeAmount <= 0f)
{
weapon.chargeAmount = 0f;
// Unassign the beam from this weapon's fire position
weapon.beamItemKeyList[beamModule.firePositionIndex] = new SSCBeamItemKey(-1, -1, 0);
beamModule.DestroyBeam();
}
}
}
}
#if UNITY_EDITOR
else { Debug.LogWarning("ERROR surfaceTurretModule.MoveBeam has been called on the wrong beam. isInitialised: " + beamModule.isInitialised +
" isBeamEnabled: " + beamModule.isBeamEnabled + " weaponIndex: " + beamModule.weaponIndex + " firePositionIndex: " + beamModule.firePositionIndex); }
#endif
}
///
/// If muzzle FX on this weapon are pooled and have been parented to the weapons,
/// when a surface turret is destroyed, they need to be reparented to the pool.
/// NOTE: This may impact GC (if this becomes a problem, we could precreate a list
/// with the appropriate capacity.
///
private void DestroyFX()
{
EffectsModule[] effectsModules = GetComponentsInChildren(true);
int numEffectsModules = effectsModules == null ? 0 : effectsModules.Length;
for (int emIdx = 0; emIdx < numEffectsModules; emIdx++)
{
EffectsModule effectsModule = effectsModules[emIdx];
if (effectsModule.usePooling && effectsModule.isReparented)
{
effectsModule.DestroyEffectsObject();
}
}
}
private Vector3 GetWeaponWorldFireDirection()
{
// Get relative fire direction
weaponRelativeFireDirection = weapon.fireDirectionNormalised;
// Adjust relative fire direction based on turret rotation
// (turret rotation less parent object rotation) - (original turret rotation less original rotation) + relative fire direction
weaponRelativeFireDirection = (trfmInvRot * weapon.turretPivotX.rotation) * weapon.intPivotYInvRot * weaponRelativeFireDirection;
// Get weapon world fire direction
return (trfmRight * weaponRelativeFireDirection.x) +
(trfmUp * weaponRelativeFireDirection.y) +
(trfmFwd * weaponRelativeFireDirection.z);
}
private Vector3 GetWeaponWorldFirePosition(Vector3 weaponWorldBasePosition, int firePositionIndex)
{
weaponRelativeFirePosition = weapon.relativePosition;
// Check if there are multiple fire positions
if (weapon.isMultipleFirePositions)
{
// Get relative fire position
weaponRelativeFirePosition += weapon.firePositionList[firePositionIndex];
}
// Adjust relative fire position based on turret rotation
weaponRelativeFirePosition = (trfmInvRot * weapon.turretPivotX.rotation) * weaponRelativeFirePosition;
// Get weapon world fire position
return weaponWorldBasePosition +
(trfmRight * weaponRelativeFirePosition.x) +
(trfmUp * weaponRelativeFirePosition.y) +
(trfmFwd * weaponRelativeFirePosition.z);
}
///
/// Update cached values.
/// NOTE: currently world velocity and angular velocity etc
/// are NOT updated.
///
private void UpdatePositionData()
{
// Update data obtained from transform
trfmPos = transform.position;
trfmFwd = transform.forward;
trfmRight = transform.right;
trfmUp = transform.up;
trfmRot = transform.rotation;
trfmInvRot = Quaternion.Inverse(trfmRot);
}
///
/// [INTERNAL ONLY]
/// Update weapon and fire if ready
/// Weapons that have no health, cannot aim, reload or fire.
///
private void UpdateWeapon()
{
// Check that the weapon has had a projectile ID or beam ID assigned
// Assumes that the IDs have been correctly assigned based on the weaponType
if (isInitialised && (weapon.projectilePrefabID >= 0 || weapon.beamPrefabID >= 0))
{
float _deltaTime = Time.deltaTime;
// Does the weapon need to cool down??
weapon.ManageHeat(_deltaTime, 0f);
// A weapon with no health or performance cannot rotate, reload, or fire.
if (weapon.health > 0f && weapon.currentPerformance > 0f)
{
weapon.MoveTurret(worldVelocity, true);
// Does the beam weapon need charging?
if (weaponTypeInt == Weapon.TurretBeamInt && weapon.rechargeTime > 0f && weapon.chargeAmount < 1f)
{
weapon.chargeAmount += _deltaTime * 1f / weapon.rechargeTime;
if (weapon.chargeAmount > 1f) { weapon.chargeAmount = 1f; }
}
// Check if this projectile weapon is reloading or not
// Check if this beam weapon is powering-up or not
if (weapon.reloadTimer > 0f)
{
// If reloading (or powering-up), update reload timer
weapon.reloadTimer -= _deltaTime;
}
else if (weapon.ammunition != 0)
{
// Is turret locked on target and in direct line of sight?
// NOTE: If check LoS is enabled, it will still fire if another enemy is between the turret and the weapon.target.
bool isReadyToFire = (isManualFireNow || isAutoFire) && weapon.isLockedOnTarget && (!weapon.checkLineOfSight || WeaponHasLineOfSight(weapon.target));
if (isReadyToFire)
{
// Get base weapon world position (not accounting for relative fire position)
weaponWorldBasePosition = trfmPos;
// Get relative fire direction
weaponRelativeFireDirection = weapon.fireDirectionNormalised;
// Adjust relative fire direction based on turret rotation
// (turret rotation less parent object rotation) - (original turret rotation less original rotation) + relative fire direction
weaponRelativeFireDirection = (trfmInvRot * weapon.turretPivotX.rotation) * weapon.intPivotYInvRot * weaponRelativeFireDirection;
// Get weapon world fire direction
weaponWorldFireDirection = (trfmRight * weaponRelativeFireDirection.x) +
(trfmUp * weaponRelativeFireDirection.y) +
(trfmFwd * weaponRelativeFireDirection.z);
// Start the firing cycle with no heat generated
float heatValue = 0f;
// Loop through all fire positions
int firePositionListCount = weapon.isMultipleFirePositions ? weapon.firePositionList.Count : 1;
for (int fp = 0; fp < firePositionListCount; fp++)
{
// Only fire if there is unlimited ammunition (-1) or greater than 0 projectiles available
if (weapon.ammunition != 0)
{
// If not unlimited ammo, decrement the quantity available
if (weapon.ammunition > 0) { weapon.ammunition--; }
// Get relative fire position
weaponRelativeFirePosition = weapon.relativePosition;
// If there are multiple fire positions, add the relative fire position
if (weapon.isMultipleFirePositions)
{
weaponRelativeFirePosition += weapon.firePositionList[fp];
}
// Adjust relative fire position based on turret rotation
//weaponRelativeFirePosition = (trfmInvRot * weapon.turretPivotX.rotation) * weapon.intPivotYInvRot * weaponRelativeFirePosition;
weaponRelativeFirePosition = (trfmInvRot * weapon.turretPivotX.rotation) * weaponRelativeFirePosition;
// Get weapon world fire position
weaponWorldFirePosition = weaponWorldBasePosition +
(trfmRight * weaponRelativeFirePosition.x) +
(trfmUp * weaponRelativeFirePosition.y) +
(trfmFwd * weaponRelativeFirePosition.z);
// Create a new beam or projectile using the SSCManager
// Velocity is world velocity of SurfaceTurretModule plus relative velocity of weapon due to angular velocity
if (weaponTypeInt == Weapon.TurretBeamInt)
{
// if the sequence number is 0, it means it is not active
if (weapon.beamItemKeyList[fp].beamSequenceNumber == 0)
{
InstantiateBeamParameters ibParms = new InstantiateBeamParameters()
{
beamPrefabID = weapon.beamPrefabID,
position = weaponWorldFirePosition,
fwdDirection = weaponWorldFireDirection,
upDirection = trfmUp,
shipId = 0,
squadronId = squadronId,
weaponIndex = 0,
firePositionIndex = fp,
beamSequenceNumber = 0,
beamPoolListIndex = -1
};
// Create a beam using the ship control Manager (SSCManager)
// Pass InstantiateBeamParameters by reference so we can get the beam index and sequence number back
BeamModule beamModule = sscManager.InstantiateBeam(ref ibParms);
if (beamModule != null)
{
beamModule.callbackOnMove = MoveBeam;
// Retrieve the unique identifiers for the beam instance
weapon.beamItemKeyList[fp] = new SSCBeamItemKey(weapon.beamPrefabID, ibParms.beamPoolListIndex, ibParms.beamSequenceNumber);
// Immediately update the beam position (required for pooled beams that have previously been used)
MoveBeam(beamModule);
// Was a poolable muzzle fx spawned?
if (beamModule.muzzleEffectsItemKey.effectsObjectSequenceNumber > 0)
{
// Only get the transform if the muzzle EffectsModule has Is Reparented enabled.
Transform muzzleFXTrfm = sscManager.GetEffectsObjectTransform(beamModule.muzzleEffectsItemKey.effectsObjectTemplateListIndex, beamModule.muzzleEffectsItemKey.effectsObjectPoolListIndex, true);
if (muzzleFXTrfm != null)
{
muzzleFXTrfm.SetParent(beamModule.transform);
}
}
}
}
}
else
{
InstantiateProjectileParameters ipParms = new InstantiateProjectileParameters()
{
projectilePrefabID = weapon.projectilePrefabID,
position = weaponWorldFirePosition,
fwdDirection = weaponWorldFireDirection,
upDirection = trfmUp,
weaponVelocity = worldVelocity + Vector3.Cross(worldAngularVelocity, weaponWorldFirePosition - trfmPos),
gravity = gravitationalAcceleration,
gravityDirection = gravityDirection,
shipId = 0,
squadronId = -1,
targetShip = weapon.isProjectileKGuideToTarget ? weapon.targetShip : null,
targetGameObject = weapon.isProjectileKGuideToTarget ? weapon.target : null,
targetguidHash = weapon.isProjectileKGuideToTarget ? weapon.targetguidHash : 0,
};
sscManager.InstantiateProjectile(ref ipParms);
// For now, assume projectile was instantiated
// Heat value is inversely proportional to the firing interval (reload time)
if (weapon.reloadTime > 0f) { heatValue += 1f / weapon.reloadTime; };
// Was a muzzle fx spawned?
if (ipParms.muzzleEffectsObjectPoolListIndex >= 0)
{
// Only get the transform if the muzzle EffectsModule has Is Reparented enabled.
Transform muzzleFXTrfm = sscManager.GetEffectsObjectTransform(ipParms.muzzleEffectsObjectPrefabID, ipParms.muzzleEffectsObjectPoolListIndex, true);
if (muzzleFXTrfm != null)
{
muzzleFXTrfm.SetParent(weapon.turretPivotX);
}
}
}
// Set reload timer to reload time
weapon.reloadTimer = weapon.reloadTime;
}
}
if (heatValue > 0f) { weapon.ManageHeat(_deltaTime, heatValue); }
}
}
}
isManualFireNow = false;
}
}
#endregion
#region Internal Public Methods
///
/// [INTERNAL ONLY]
/// When DamageReceiver component is attached, this routine is called by Sci-Fi Ship Controller when a projectile hits it.
/// If it is destroyed, it will no longer appear on radar.
///
///
public void TakeDamage(CallbackOnObjectHitParameters callbackOnObjectHitParameters)
{
if (isInitialised)
{
ProjectileModule projectile = callbackOnObjectHitParameters.projectilePrefab;
if (projectile != null)
{
weapon.Health -= projectile.damageAmount;
// Uncomment if you want to debug in the editor
//#if UNITY_EDITOR
//Debug.Log("Projectile: " + projectile.name + " hit " + gameObject.name + " with damage amount of " + projectile.damageAmount);
//#endif
}
// Must have been hit by a (laser) beam
else
{
weapon.Health -= callbackOnObjectHitParameters.damageAmount;
}
if (weapon.Health <= 0f)
{
if (!IsDestroyed && callbackOnDestroy != null) { callbackOnDestroy.Invoke(this); }
IsDestroyed = true;
DeactivateBeams(sscManager);
if (!isDestructionEffectsObjectInstantiated && effectsObjectPrefabID >= 0 && destructionEffectsObject != null && sscManager != null)
{
// Instantiate the turret destruction effects prefab
InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters
{
effectsObjectPrefabID = effectsObjectPrefabID,
position = transform.position,
rotation = transform.rotation
};
sscManager.InstantiateEffectsObject(ref ieParms);
isDestructionEffectsObjectInstantiated = true;
}
// If this turret has a set Location, remove it
if (locationData != null)
{
if (isVisibleToRadar) { sscManager.DisableRadar(locationData); }
sscManager.DeleteLocation(locationData);
radarItemIndex = -1;
isVisibleToRadar = false;
}
// Was the turret added to radar as RadarItemType.GameObject?
if (isVisibleToRadar && radarItemIndex >= 0 && sscRadar != null)
{
sscRadar.DisableRadar(radarItemIndex);
radarItemIndex = -1;
isVisibleToRadar = false;
}
if (isDestroyOnNoHealth)
{
// Is there a destruct prefab?
if (!isDestructObjectActivated && destructObjectPrefabID >= 0 && destructObject != null && sscManager != null)
{
// Turn off all colliders. As we are going to destroy the gameobject,
// we can simply deactivate it.
gameObject.SetActive(false);
// Instantiate the turret destruct prefab
InstantiateDestructParameters dstParms = new InstantiateDestructParameters
{
destructPrefabID = destructObjectPrefabID,
position = transform.position,
//position = transform.position + (Vector3.up * 20f),
rotation = transform.rotation,
explosionPowerFactor = 1f,
explosionRadiusFactor = 1f
};
sscManager.InstantiateDestruct(ref dstParms);
isDestructObjectActivated = true;
}
DestroyFX();
Destroy(gameObject);
}
}
}
}
#endregion
#region Public API Methods
///
/// Deactivate any beams that the weapon is currently firing
///
public void DeactivateBeams (SSCManager sscManager)
{
if (sscManager != null && weapon != null && weaponTypeInt == Weapon.TurretBeamInt)
{
int numBeams = weapon.beamItemKeyList == null ? 0 : weapon.beamItemKeyList.Count;
for (int bItemIdx = 0; bItemIdx < numBeams; bItemIdx++)
{
if (weapon.beamItemKeyList[bItemIdx].beamSequenceNumber > 0)
{
sscManager.DeactivateBeam(weapon.beamItemKeyList[bItemIdx]);
}
weapon.beamItemKeyList[bItemIdx] = new SSCBeamItemKey(-1, -1, 0);
}
}
}
///
/// Reinitialises variables required for surface turret beam weapon and effects used by those beams.
/// Call this after modifying any beams or beam effect data for this surface turret.
///
public void ReinitialiseTurretBeams()
{
if (sscManager != null)
{
// Initialise beams, and effects objects used by those beams
sscManager.UpdateBeamsAndEffects(this);
}
#if UNITY_EDITOR
else
{
Debug.LogWarning("SurfaceTurretModule.ReinitialiseTurretBeams Warning: could not find SSCManager to update (weapon) beams");
}
#endif
}
///
/// Reinitialises variables required for projectiles and effects of the surface turret.
/// Call after modifying any projectile or effect data for this surface turret.
///
public void ReinitialiseTurretProjectilesAndEffects ()
{
if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }
if (sscManager != null)
{
// Initialise projectiles and effects objects
sscManager.UpdateProjectilesAndEffects(this);
}
#if UNITY_EDITOR
else
{
Debug.LogWarning("SurfaceTurretModule.ReinitialiseTurretProjectilesAndEffects Warning: could not find SSCManager to update projectiles and effects.");
}
#endif
}
///
/// Reinitialises variables required for destruct objects of the surface turret.
/// Call after modifying any destruct data for this surface turret.
///
public void ReinitialiseTurretDestructObjects ()
{
if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }
if (sscManager != null)
{
// Initialise destruct objects
sscManager.UpdateDestructObjects(this);
}
#if UNITY_EDITOR
else
{
Debug.LogWarning("SurfaceTurretModule.ReinitialiseTurretDestructObjects Warning: could not find SSCManager to update destruct objects.");
}
#endif
}
///
/// Create a new Location using the SSCManager and add it to Radar
/// if required.
///
///
public void CreateLocation(bool removeExisting)
{
if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }
if (sscManager != null)
{
if (removeExisting && locationData != null)
{
sscManager.DeleteLocation(locationData);
}
locationData = sscManager.AppendLocation(this.gameObject);
if (locationData != null)
{
locationData.factionId = factionId;
locationData.radarBlipSize = radarBlipSize;
if (isVisibleToRadar) { sscManager.EnableRadar(locationData); }
// If isn't visible to radar (for whatever reason) it will be -1.
radarItemIndex = locationData.radarItemIndex;
}
}
}
///
/// Clears all targeting information for the weapon. This should be called if you do not know if the target
/// is a ship or a gameobject.
///
public void ClearWeaponTarget()
{
if (isInitialised) { weapon.ClearTarget(); }
}
///
/// The turret will track this gameobject.
///
///
public void SetWeaponTarget(GameObject target)
{
if (isInitialised) { weapon.SetTarget(target); }
}
///
/// The turret will track this ship
///
///
public void SetWeaponTargetShip(ShipControlModule targetShipControlModule)
{
if (isInitialised) { weapon.SetTargetShip(targetShipControlModule); }
}
///
/// The turret will track this ship's localised damage region
///
///
///
public void SetWeaponTargetShipDamageRegion (ShipControlModule targetShipControlModule, DamageRegion damageRegion)
{
if (isInitialised) { weapon.SetTargetShipDamageRegion(targetShipControlModule, damageRegion); }
}
///
/// Fire all cannons on the weapon if they are loaded
/// and ready. This is a single shot action. For continuous
/// firing, call SetAutoFire().
///
public void FireIfReady()
{
if (isInitialised && !isAutoFire)
{
isManualFireNow = true;
}
}
///
/// Sets the weapon to automatically fire if a target is acquired
/// and the weapon is ready.
///
public void SetAutoFire()
{
isManualFireNow = false;
if (weapon != null)
{
weapon.firingButton = Weapon.FiringButton.AutoFire;
isAutoFire = true;
}
}
///
/// For manually firing the weapon. After this is set,
/// call FireIfReady() to fire the weapon.
///
public void SetManualFire()
{
isManualFireNow = false;
if (weapon != null)
{
weapon.firingButton = Weapon.FiringButton.None;
isAutoFire = 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 (distinguished by faction ID) - even if it is not the target and 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
///
///
///
///
///
///
public bool WeaponHasLineOfSight(GameObject target, bool directToTarget = false, bool obstaclesBlockLineOfSight = true, bool anyEnemy = true)
{
if (target == null || !isInitialised) { return false; }
// Update the line-of-sight property
weapon.UpdateLineOfSight(target, trfmPos, trfmRight, trfmUp, trfmFwd, trfmInvRot, factionId,
true, directToTarget, obstaclesBlockLineOfSight, anyEnemy);
// Return the updated property
return weapon.HasLineOfSight;
}
#endregion
}
}