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 } }