using System.Collections.Generic; using UnityEngine; using System.Collections; #if UNITY_EDITOR using UnityEditor; #endif // Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved. namespace SciFiShipController { [AddComponentMenu("Sci-Fi Ship Controller/Ship Components/Ship Control Module")] [HelpURL("https://scsmmedia.com/ssc-documentation")] [RequireComponent(typeof(Rigidbody))] [DisallowMultipleComponent] public class ShipControlModule : MonoBehaviour { #region Public Variables and Properties [HideInInspector] public bool allowRepaint = false; /// /// The class in which the majority of ship data is stored in. Typically use this to access/modify data relating to a ship. /// You should check IsInitialised before referencing the shipInstance. /// public Ship shipInstance; /// /// [INTERNAL USE ONLY] /// public GameObject[] thrusterEffectObjects = new GameObject[1]; // Remember which tabs etc were shown [HideInInspector] public int selectedTabInt = 0; /// /// [READONLY] Has the ship been initialised? /// public bool IsInitialised { get { return isInitialised; } } /// /// [READONLY] Session-only transform InstanceID. /// public int GetShipId { get { return isInitialised ? shipInstance.shipId : transform.GetInstanceID(); } } /// /// Attempt to get or set gravitational acceleration (must be initialised) /// public float GravitationalAcceleration { get { return isInitialised ? shipInstance.gravitationalAcceleration : 0f; } set { if (isInitialised) { shipInstance.gravitationalAcceleration = value; } } } /// /// Attempt to get or set the direction gravity is acting the ship (must be initialised) /// public Vector3 GravityDirection { get { return isInitialised ? shipInstance.gravityDirection : Vector3.zero; } set { if (isInitialised) { shipInstance.gravityDirection = value; } } } /// /// [READONLY] The rigidbody of the ship. /// public Rigidbody ShipRigidbody { get { return rBody; } } /// /// Are the thruster systems online or in the process of coming online? /// At runtime call StartupThrusterSystems() or ShutdownThrusterSystems(). /// public bool IsThrusterSystemsStarted { get { return isInitialised ? shipInstance.isThrusterSystemsStarted : false; } } /// /// [READONLY] Is the ship currently being respawned? /// The ship will be disabled during the respawn time. /// public bool IsRespawning { get { return isRespawning; } } /// /// [READONLY] Has respawning been paused? If so, this ship cannot respawn /// until ResumeRespawning() has been called. See also PauseRespawning(). /// public bool IsRespawningPaused { get { return isRespawingPaused; } } /// /// [READONLY] Is this ship visible to the radar? /// public bool IsVisbleToRadar { get { return isInitialised && shipInstance != null && shipInstance.isRadarEnabled && sscRadar != null && sscRadar.GetVisibility(shipInstance.radarItemIndex); } } /// /// [READONLY] The number of weapons on this ship. Will always return 0 if ship has not been initialised. /// See also ship.NumberOfWeapons. /// public int NumberOfWeapons { get { return isInitialised ? (shipInstance.weaponList == null ? 0 : shipInstance.weaponList.Count) : 0; } } /// /// [READONLY] The number of times the ship has been respawned. This is incremented /// when the ship is respawned - not when it is destroyed. /// public int NumberOfRespawns { get { return respawnCount; } } /// /// [INTERNAL USE ONLY] /// public string editorSearchThrusterFilter; #if UNITY_EDITOR public string ThrusterSystemsStatus { get { return isInitialised ? (shipInstance.isThrusterSystemsStarted ? (shipInstance.thrusterSystemShutdownTimer > 0f ? "shutting down" : (shipInstance.thrusterSystemStartupTimer > 0f ? "starting up" : "online") ) : "offline") : "--"; } } #endif #endregion #region Private Variables private bool isInitialised = false; private SSCManager sscManager; private SSCRadar sscRadar; private ThrusterEffects[] thrusterEffects; /// /// Used internally to reset PID Controllers on AI ships /// when ship velocity is set to 0. /// private ShipAIInputModule shipAIInputModule; private bool isShipAIInputModuleAttached = false; private ShipDocking shipDocking; private bool isShipDockingAttached = false; private Rigidbody rBody; private List activeRenderersList; private List audioSourcesList; /// /// This prevents the activeRenderersList being updated while the ship /// is disabled /// private bool isShipVisibilityDisabled = false; private int componentIndex; private int arrayLength; private Thruster thruster; private bool shipIsEnabled = true; /// /// This is a subset of shipIsEnabled. Current applies to: /// 1) Physics /// 2) Thruster Effects /// 3) User or AI Input /// 4) Sound FX (audio) /// [SerializeField] private bool isMovementEnabled = true; private int lastDamageEventIndex = 0; private float damageRumbleTimer = -1f; #region Input Variables private Vector3 pilotForceInput = Vector3.zero; // Make non-serialized internal so is visible to ShipCameraModule [System.NonSerialized] internal Vector3 pilotMomentInput = Vector3.zero; // TODO: There could be a problem with ship fire input // If framerate drops below 50 FPS, fixedupdate will be called more often than update // Therefore it would be possible for fixedupate to be called twice or more between update calls // So if fire can be held is false, fire could still be true for multiple frames for a single fire call // However, maybe this won't be a problem if reload time is kept high enough? private bool pilotPrimaryFireInput = false; private bool pilotSecondaryFireInput = false; private bool pilotDockInput = false; #endregion #region FixedUpdate Variables private Vector3 localResultantForce; private Vector3 localResultantMoment; private bool isRespawning = false; private bool isRespawingPaused = false; private int respawnCount = 0; private float respawnTimer = 0f; private float stuckTimer = 0f; #endregion #endregion #region Public Static Version Properties public static string SSCVersion { get { return "1.3.7"; } } public static string SSCBetaVersion { get { return ""; } } #endregion #region Public Delegates public delegate void CallbackOnCollision(ShipControlModule shipControlModule, Collision collision); public delegate void CallbackOnDestroy(Ship ship); public delegate void CallbackOnRespawn(ShipControlModule shipControlModule, ShipAIInputModule shipAIInputModule); public delegate void CallbackOnHit(CallbackOnShipHitParameters callbackOnShipHitParameters); public delegate void CallbackOnStuck(ShipControlModule shipControlModule); public delegate void CallbackOnRumble(float rumbleAmount); /// /// The name of the custom method that is called immediately after a collision. /// Your method must take 2 parameters (ShipControlModule and Collision). /// This should be a lightweight method to avoid performance issues. /// [System.NonSerialized] public CallbackOnCollision callbackOnCollision = null; /// /// The name of the custom method that is called immediately /// before the ship is destroyed. Your method must take 1 /// parameter of class Ship. This should be a lightweight /// method to avoid performance issues. It could be used to update /// a score or remove a ship from a squadron. /// [System.NonSerialized] public CallbackOnDestroy callbackOnDestroy = null; /// /// The name of the custom method that is called immediately /// after the ship is respawned. Your method must take 2 parameters: ShipControlModule /// and ShipAIInputModule. The first is never null but there may be no AI module /// attached to this ship. This should be a lightweight method to avoid /// performance issues. /// [System.NonSerialized] public CallbackOnRespawn callbackOnRespawn = null; /// /// The name of the custom method that is called immediately /// after the ship is hit by a projectile or beam. Your method must take 1 /// parameter of type CallbackOnShipHitParameters. This should be /// a lightweight method to avoid performance issues. It could be used to /// take evasive action while being pursued by an enemy ship. It could /// also be used to detect friendly fire. /// [System.NonSerialized] public CallbackOnHit callbackOnHit = null; /// /// The name of the custom method that is called immediately after /// a ship is detected as stuck. To avoid performance issues, action /// should be taken otherwise your method may be called each subsequent /// frame. See also ship.stuckTime and ship.stuckSpeedThreshold. /// [System.NonSerialized] public CallbackOnStuck callbackOnStuck = null; /// /// Generally reserved for internal use by the PlayerInputModule. If the human /// player ship does not have the PlayerInputModule component attached, you can /// create a lightweight custom method and assign it at runtime so that it is /// called whenever device rumble or force feedback is required. /// [System.NonSerialized] public CallbackOnRumble callbackOnRumble = null; #endregion #region Private Initialise Methods // Use this for initialization void Awake() { if (shipInstance.initialiseOnAwake) { InitialiseShip(); } } #endregion #region Update Methods void Update() { if (isInitialised) { if (isRespawning) { #region Respawning // If we are currently respawning, count down respawn timer respawnTimer -= isRespawingPaused ? 0f : Time.deltaTime; // Once respawn time reaches zero, respawn the ship if (respawnTimer <= 0f) { // We are now not in the process of respawning isRespawning = false; respawnCount++; // Reset the health of the ship shipInstance.ResetHealth(); shipInstance.ResetThrusterSystems(); if (shipInstance.respawningMode == Ship.RespawningMode.RespawnOnPath) { RespawnOnPath(shipInstance.respawningPathGUIDHash, true, false); } else { // Set ship position and rotation transform.SetPositionAndRotation(shipInstance.GetRespawnPosition(), shipInstance.GetRespawnRotation()); // Re-enable the ship and make it visible again EnableOrDisableShip(true, true, false); } // Re-initialise ground match variables since this will also reset PID controller shipInstance.ReinitialiseGroundMatchVariables(); // Set ship velocity rBody.velocity = transform.TransformDirection(shipInstance.respawnVelocity); rBody.angularVelocity = Vector3.zero; // If there is an AI component attached, reset the PID controllers // TODO: Test whether this is effective for when an AI respawns with a given velocity if (isShipAIInputModuleAttached && shipAIInputModule != null) { if (shipAIInputModule.IsInitialised) { shipAIInputModule.ResetPIDControllers(); } else { shipAIInputModule.Initialise(); } } if (callbackOnRespawn != null) { callbackOnRespawn(this, shipAIInputModule); } } #endregion } else { // Only apply input, calculate movement, update effects or process damage if the ship is enabled if (shipIsEnabled) { // Weapons can only fire, respawning after a collision, when movement is enabled. #region Pass input / apply combat output if (isMovementEnabled || shipInstance.useWeaponsWhenMovementDisabled) { // Pass position and movement data to the ship shipInstance.UpdatePositionAndMovementData(transform, rBody); // Pass pilot weapons input to the ship shipInstance.pilotPrimaryFireInput = pilotPrimaryFireInput; shipInstance.pilotSecondaryFireInput = pilotSecondaryFireInput; // Update weapons and damage data shipInstance.UpdateWeaponsAndDamage(sscManager); } #endregion // Thrusters only work when movement is enabled // OR when isThrusterFXStationary is true. #region Visual/Audial Effects // Behaviour change for 1.3.7 Beta 2a if (shipInstance.isThrusterSystemsStarted && (isMovementEnabled || shipInstance.isThrusterFXStationary)) { UpdateThrusterFX(); } #endregion #region Localised Damage Region Destruction, shield recharge, or Radar Update if (shipInstance.shipDamageModel == Ship.ShipDamageModel.Localised && shipInstance.numLocalisedDamageRegions > 0 && sscManager != null) { // Loop through localised damage regions DamageRegion damageRegion; for (int d = 0; d < shipInstance.numLocalisedDamageRegions; d++) { damageRegion = shipInstance.localisedDamageRegionList[d]; #region Effects Object for damage region // If a damage region has been "destroyed" but no effects object has been instantiated... if (damageRegion.Health <= 0f && !damageRegion.isDestructionEffectsObjectInstantiated) { // If there is an effects object for this damage region, instantiate it. if (damageRegion.effectsObjectPrefabID > 0) { InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters { effectsObjectPrefabID = damageRegion.effectsObjectPrefabID, position = transform.TransformPoint(damageRegion.relativePosition), rotation = shipInstance.TransformRotation }; // Keep track of the EffectsModule instance that was instantiated for this region if (sscManager.InstantiateEffectsObject(ref ieParms) != null) { damageRegion.destructionEffectItemKey = new SSCEffectItemKey(damageRegion.effectsObjectPrefabID, ieParms.effectsObjectPoolListIndex, ieParms.effectsObjectSequenceNumber); } } // Then remember that we have instantiated an effects object, so we don't do it again damageRegion.isDestructionEffectsObjectInstantiated = true; } // Check if the effects object should move with the ship else if (damageRegion.isMoveDestructionEffectsObject && damageRegion.destructionEffectItemKey.effectsObjectSequenceNumber > 0) { // Find the effects object, validate it is the correct one (it hasn't been despawned) and update position/rotation if (!sscManager.MoveEffectsObject(damageRegion.destructionEffectItemKey, transform.TransformPoint(damageRegion.relativePosition), shipInstance.TransformRotation, true)) { // The effect may have been despawned // Once the effect has finished, it will not be respawned for this damage region damageRegion.destructionEffectItemKey = new SSCEffectItemKey(-1, -1, 0); } } #endregion if (damageRegion.Health > 0f) { #region Damage Region Radar Update if (shipInstance.isRadarEnabled && damageRegion.isRadarEnabled && damageRegion.radarItemIndex >= 0) { // Update fields that may change at any time damageRegion.sscRadarPacket.isVisibleToRadar = true; damageRegion.sscRadarPacket.position = shipInstance.GetDamageRegionWSPosition(damageRegion); damageRegion.sscRadarPacket.velocity = shipInstance.WorldVelocity; damageRegion.sscRadarPacket.factionId = shipInstance.factionId; damageRegion.sscRadarPacket.squadronId = shipInstance.squadronId; // Use the internal radarItemIndex rather than RadarId to avoid the property lookup sscRadar.UpdateItem(damageRegion.radarItemIndex, damageRegion.sscRadarPacket); } #endregion #region Local Damage Region Shield Recharge damageRegion.CheckShieldRecharge(); #endregion } // Only destroy the damage region once else if (!damageRegion.isDestroyed) { #region Damage Region Destruct DestructDamageRegion(damageRegion, false); if (damageRegion.regionChildTransform != null) { damageRegion.regionChildTransform.gameObject.SetActive(false); // Return any weapon muzzle FX to the pool DestroyFX(damageRegion.regionChildTransform); } // The damage region destroy action has occurred damageRegion.isDestroyed = true; if (damageRegion.isRadarEnabled) { DisableRadar(damageRegion); } #endregion } } } #endregion #region Check if ship is stuck if (isMovementEnabled && IsShipStuck()) { if (shipInstance.stuckAction == Ship.StuckAction.InvokeCallback) { if (callbackOnStuck != null) { callbackOnStuck(this); } } else if (shipInstance.stuckAction == Ship.StuckAction.RespawnOnPath) { // Stop camera shake if it is currently happening if (shipInstance.callbackOnCameraShake != null) { shipInstance.callbackOnCameraShake(0f); } RespawnOnPath(shipInstance.stuckActionPathGUIDHash, false, true); } else if (shipInstance.stuckAction == Ship.StuckAction.SameAsRespawningMode) { if (shipInstance.respawningMode != Ship.RespawningMode.DontRespawn) { // We are now in the process of respawning isRespawning = true; // Set the respawn timer respawnTimer = shipInstance.respawnTime; // Stop camera shake if it is currently happening if (shipInstance.callbackOnCameraShake != null) { shipInstance.callbackOnCameraShake(0f); } // Now we want to "despawn" this ship // So we simply disable all visual and physics components // Disable the ship and make it invisible EnableOrDisableShip(false, true, false); } } } #endregion #region Damage - Rumble and Camera Shake if (isMovementEnabled && !shipInstance.Destroyed()) { // Check if the last damage event index has changed int thisDamageEventIndex = shipInstance.LastDamageEventIndex(); if (lastDamageEventIndex != thisDamageEventIndex) { // If the last damage event index has changed, there has been a damage event lastDamageEventIndex = thisDamageEventIndex; if (shipInstance.applyControllerRumble) { // Apply rumble if (callbackOnRumble != null) { callbackOnRumble(shipInstance.RequiredDamageRumbleAmount()); } // Set the rumble timer damageRumbleTimer = 0.5f; } if (shipInstance.callbackOnCameraShake != null) { shipInstance.callbackOnCameraShake(shipInstance.RequiredCameraShakeAmount()); } } // NOTE: CameraShake is turned off by a timer in ShipCameraModule or by calling StopCameraShake(..) else if (shipInstance.applyControllerRumble) { // Count down the rumble timer if (damageRumbleTimer > 0f) { damageRumbleTimer -= Time.deltaTime; } // If the timer reaches zero, disable rumble if (damageRumbleTimer < 0f) { if (callbackOnRumble != null) { callbackOnRumble(0f); } } } } #endregion #region Docking // If ship is still enabled, perform dock action if (pilotDockInput && shipIsEnabled && isShipDockingAttached && shipDocking.IsInitialised) { // Currently only supports Docked and Undocked for player ships if (shipDocking.GetStateInt() == ShipDocking.dockedInt) { shipDocking.SetState(ShipDocking.DockingState.NotDocked); } else { shipDocking.SetState(ShipDocking.DockingState.Docked); } } #endregion } #region Ship Destruction OR Ship radar update and Thruster Systems startup/shutdown // Check if the ship has been destroyed if (shipInstance.Destroyed()) { // Stop camera shake if it is currently happening if (shipInstance.callbackOnCameraShake != null) { shipInstance.callbackOnCameraShake(0f); } DestroyFX(transform); if (callbackOnDestroy != null) { callbackOnDestroy(this.shipInstance); } // Check if the ship should be respawned if (shipInstance.respawningMode != Ship.RespawningMode.DontRespawn) { // We are now in the process of respawning isRespawning = true; // Set the respawn timer respawnTimer = shipInstance.respawnTime; // Now we want to "despawn" this ship // So we simply disable all visual and physics components // Disable the ship and make it invisible EnableOrDisableShip(false, true, false); if (sscManager != null) { #region Instantiate the ship destruction effects prefab if (shipInstance.mainDamageRegion.destructionEffectsObject != null) { InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters { effectsObjectPrefabID = shipInstance.mainDamageRegion.effectsObjectPrefabID, position = transform.position, rotation = transform.rotation }; sscManager.InstantiateEffectsObject(ref ieParms); } #endregion DestructDamageRegion(shipInstance.mainDamageRegion, true); } // End the rumble timer damageRumbleTimer = -1f; } else { shipInstance.DeactivateBeams(sscManager); // If the ship is using radar, remove it from radar before the ship gameobject // is destroyed. Also remove any Localised Damage Regions from radar. if (shipInstance.isRadarEnabled && shipInstance.radarItemIndex >= 0) { DisableRadar(); //sscRadar.DisableRadar(shipInstance.radarItemIndex); } if (sscManager != null) { #region Instantiate the ship destruction effects prefab if (shipInstance.mainDamageRegion.destructionEffectsObject != null) { InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters { effectsObjectPrefabID = shipInstance.mainDamageRegion.effectsObjectPrefabID, position = transform.position, rotation = transform.rotation }; sscManager.InstantiateEffectsObject(ref ieParms); } #endregion DestructDamageRegion(shipInstance.mainDamageRegion, true); } // Destroy this ship // Prevent the shipInstance referencing the ShipControlModule transform. if (isInitialised) { shipInstance.shipTransform = null; } Destroy(gameObject); // Need to also destroy the ship class instance if (shipInstance != null) { isInitialised = false; shipInstance = null; } } } else if (shipIsEnabled) { if (shipInstance.isRadarEnabled) { // Update fields that may change at any time shipInstance.sscRadarPacket.isVisibleToRadar = true; shipInstance.sscRadarPacket.position = shipInstance.TransformPosition; shipInstance.sscRadarPacket.velocity = shipInstance.WorldVelocity; shipInstance.sscRadarPacket.factionId = shipInstance.factionId; shipInstance.sscRadarPacket.squadronId = shipInstance.squadronId; // Use the internal radarItemIndex rather than RadarId to avoid the property lookup sscRadar.UpdateItem(shipInstance.radarItemIndex, shipInstance.sscRadarPacket); } bool hasStarted, hasShutdown; if (shipInstance.CheckThrusterSystems(Time.deltaTime, out hasStarted, out hasShutdown)) { if (hasShutdown) { StopThrusterEffects(); } } shipInstance.mainDamageRegion.CheckShieldRecharge(); } #endregion } } } // FixedUpdate is called once per physics update (typically about 50 times per second) void FixedUpdate() { if (isInitialised) { if (!isRespawning) { //float calcStartTime = Time.realtimeSinceStartup; // When shipIsEnabled is false, isMovementEnabled is also false, // though the opposite may not be true. if (shipIsEnabled && isMovementEnabled) { #region Pass input / apply movement output // Pass position and movement data to the ship shipInstance.UpdatePositionAndMovementData(transform, rBody); // Pass pilot movement input to the ship shipInstance.pilotForceInput = pilotForceInput; shipInstance.pilotMomentInput = pilotMomentInput; // Calculate the local resultant force and moment shipInstance.CalculateForceAndMoment(ref localResultantForce, ref localResultantMoment); // Apply the local resultant force and moment to the rigidbody rBody.AddRelativeForce(localResultantForce); rBody.AddRelativeTorque(localResultantMoment); #endregion } //float calcEndTime = Time.realtimeSinceStartup; //Debug.Log("| Total: " + ((calcEndTime - calcStartTime) * 1000f).ToString("0.0000") + " ms |"); } } } #endregion #region Private Methods /// /// Enables or disables the ship (depending on the value of isEnabled). If updateVisibility is set to true, /// changes whether the ship is visible. If resetVelocity is set to true, resets the velocity of the ship to zero. /// Movement is not enabled if the ship is docked, although collision detection is updated. /// /// /// /// private void EnableOrDisableShip(bool isEnabled, bool updateVisibility, bool resetVelocity) { // This does not prove the ship is docked, but the ship thinks it is docked. For absolute proof, we would // need to check the ShipDockingStation's docking points (which is prob too expensive here). bool isDocked = isShipDockingAttached && shipDocking.IsInitialised && shipDocking.GetStateInt() == ShipDocking.dockedInt; if (isDocked && isEnabled) { ShipRigidbody.detectCollisions = shipDocking.detectCollisionsWhenDocked; } else { EnableOrDisableShipPhysics(isEnabled, resetVelocity); // Pause/resume thruster effects PauseOrResumeThrusters(isEnabled); } #region Update Visuals if (updateVisibility) { if (isEnabled) { // Re-enable previously active renderers arrayLength = activeRenderersList.Count; for (componentIndex = 0; componentIndex < arrayLength; componentIndex++) { activeRenderersList[componentIndex].enabled = true; } isShipVisibilityDisabled = false; } else { shipInstance.DeactivateBeams(sscManager); // We are disabling the ship - so we disable all visual and audial components #region Find and Disable Renderers if (!isShipVisibilityDisabled) { // Prevent the list being cleared when the ship is already disabled isShipVisibilityDisabled = true; // Get all the renderers in the ship and store them in a list // Use a pre-defined reusable list. If respawning happens multiple // times there will be a lot of GC which will hurt performance. GetComponentsInChildren(activeRenderersList); // Loop through the renderers arrayLength = activeRenderersList.Count; componentIndex = 0; while (componentIndex < arrayLength) { if (!activeRenderersList[componentIndex].enabled) { // If the renderer is currently disabled, remove it from the list // (We don't want to enable it when we respawn) activeRenderersList.RemoveAt(componentIndex); arrayLength--; } else { // If the renderer is currently enabled, disable it (and leave it in the list) activeRenderersList[componentIndex].enabled = false; componentIndex++; } } } #endregion StopAudioSources(); } } #endregion #region Update Rumble if (!isEnabled && callbackOnRumble != null) { callbackOnRumble(0f); } #endregion #region Update Radar if (shipInstance.isRadarEnabled) { sscRadar.SetVisibility(shipInstance.RadarId, isEnabled); // If any localised damage regions have radar enabled, set the visibility. if (shipInstance.shipDamageModel == Ship.ShipDamageModel.Localised) { for (int dmIdx = 0; dmIdx < shipInstance.numLocalisedDamageRegions; dmIdx++) { DamageRegion damageRegion = shipInstance.localisedDamageRegionList[dmIdx]; if (damageRegion != null && damageRegion.isRadarEnabled) { sscRadar.SetVisibility(damageRegion.radarItemIndex, isEnabled); } } } // Added for TechDemo3 (SSC v1.2.6 Beta 1b) if (isEnabled && isInitialised) { shipInstance.UpdatePositionAndMovementData(transform, rBody); // Update fields that may change at any time shipInstance.sscRadarPacket.isVisibleToRadar = true; shipInstance.sscRadarPacket.position = shipInstance.TransformPosition; shipInstance.sscRadarPacket.velocity = shipInstance.WorldVelocity; shipInstance.sscRadarPacket.factionId = shipInstance.factionId; shipInstance.sscRadarPacket.squadronId = shipInstance.squadronId; // Use the internal radarItemIndex rather than RadarId to avoid the property lookup sscRadar.UpdateItem(shipInstance.radarItemIndex, shipInstance.sscRadarPacket); } } #endregion // Update the shipIsEnabled variable shipIsEnabled = isEnabled; // Movement is a subset of the ship being enabled/disabled. isMovementEnabled = isEnabled && !isDocked; } /// /// This is a subset of EnableOrDisableShip(...). The ship can still incur damage, /// be destroyed and respawn (and become Enabled), and appear on Radar. /// It applies to: /// 1) Physics /// 2) Thruster Effects /// 3) User or AI Input to the Ship /// 4) Sound FX (audio) /// Movement is not enabled if the ship is docked, although collision detection is updated. /// /// /// private void EnableOrDisableShipMovement(bool isEnabled, bool resetVelocity) { // This does not prove the ship is docked, but the ship thinks it is docked. For absolute proof, we would // need to check the ShipDockingStation's docking points (which is prob too expensive here). bool isDocked = isShipDockingAttached && shipDocking.IsInitialised && shipDocking.GetStateInt() == ShipDocking.dockedInt; if (isDocked && isEnabled) { ShipRigidbody.detectCollisions = shipDocking.detectCollisionsWhenDocked; } else { EnableOrDisableShipPhysics(isEnabled, resetVelocity); PauseOrResumeThrusters(isEnabled); if (!isEnabled && callbackOnRumble != null) { callbackOnRumble(0f); } if (!isEnabled) { StopAudioSources(); } isMovementEnabled = isEnabled; } } /// /// Enable or disable the Physics on a ship. If this ia an AI ship, resets the PID controllers /// when isEnabled is true. /// If resetVelocity and isEnabled are true, resets the velocity of the ship to zero. /// NOTE: This should NOT be called when the ship is docked. /// /// /// private void EnableOrDisableShipPhysics(bool isEnabled, bool resetVelocity) { // Ensure the rigidbody's collision detection mode is configured to permit isKinematic. if (!isEnabled) { ShipRigidbody.collisionDetectionMode = CollisionDetectionMode.Discrete; } if (isEnabled) { // When enabling, turn on collisions and set the ship rigidbody to not kinematic ShipRigidbody.detectCollisions = true; ShipRigidbody.isKinematic = false; // Re-configure the rigidbody's collision detection mode ShipRigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic; } else { // When disabling, turn off collisions and set the ship rigidbody to kinematic ShipRigidbody.detectCollisions = false; ShipRigidbody.isKinematic = true; // Added 1.3.7 Beta 5c so that cached velo data correctly returns Vector.Zero // NOTE: This hasn't been fully regression tested. if (resetVelocity) { shipInstance.ResetVelocityData(); } } // Set our velocity when we enable the ship if (isEnabled) { if (resetVelocity) { // Reset velocity to zero ShipRigidbody.velocity = Vector3.zero; ShipRigidbody.angularVelocity = Vector3.zero; // If there is an AI component attached, reset the PID controllers if (isShipAIInputModuleAttached && shipAIInputModule != null) { if (shipAIInputModule.IsInitialised) { shipAIInputModule.ResetPIDControllers(); } else { shipAIInputModule.Initialise(); } } } else { // Set velocity to the velocity we had before the ship was disabled // NOTE: This assumes shipInstance.ResetVelocityData() isn't called when this method // is called with isEnabled = true. ShipRigidbody.velocity = shipInstance.WorldVelocity; ShipRigidbody.angularVelocity = shipInstance.WorldAngularVelocity; } } } /// /// Pause or Resume (play) all Thruster Effects on the ship /// /// private void PauseOrResumeThrusters(bool isEnabled) { arrayLength = thrusterEffects == null ? 0 : thrusterEffects.Length; for (componentIndex = 0; componentIndex < arrayLength; componentIndex++) { if (thrusterEffects[componentIndex] != null) { if (isEnabled) { thrusterEffects[componentIndex].Play(); } else { thrusterEffects[componentIndex].Pause(); } } } } /// /// Find and stop all audio sources /// private void StopAudioSources() { // Use the pre-defined reuseable list to minmize GC GetComponentsInChildren(audioSourcesList); arrayLength = audioSourcesList.Count; for (componentIndex = 0; componentIndex < arrayLength; componentIndex++) { if (audioSourcesList[componentIndex].enabled && audioSourcesList[componentIndex].isPlaying) { audioSourcesList[componentIndex].Stop(); } } } /// /// CURRENTLY NOT USED - FOR TESTING ONLY /// /// private IEnumerator RBodyReset() { //rBody.velocity = transform.TransformDirection(shipInstance.respawnVelocity); rBody.velocity = Vector3.zero; rBody.angularVelocity = Vector3.zero; yield return new WaitForFixedUpdate(); } /// /// A ship can be considered stuck if it is enabled, initialised, respawnStuckTime > 0 /// and it is trying to move for respawnStuckTime but cannot. /// /// private bool IsShipStuck() { if (!shipIsEnabled || !isInitialised || shipInstance.stuckTime <= 0f) { return false; } else { // velo in m/s if (Mathf.Abs((shipInstance.TransformInverseRotation * shipInstance.WorldVelocity).z) > shipInstance.stuckSpeedThreshold) { stuckTimer = 0f; return false; } else { stuckTimer += Time.deltaTime; return stuckTimer >= shipInstance.stuckTime; } } } /// /// Respawn (move) the ship to the closest point on the given Path. /// The ship must be initialised and the Path must be valid. /// /// /// /// private void RespawnOnPath(int pathguidHash, bool updateVisibility, bool resetVelocity) { if (isInitialised && pathguidHash != 0) { PathData pathData = sscManager.GetPath(pathguidHash); if (pathData != null) { Vector3 closestPointOnPath = Vector3.zero; float closestPointOnPathTValue = 0f; int prevPathLocationIdx = 0; if (SSCMath.FindClosestPointOnPath(pathData, shipInstance.TransformPosition, ref closestPointOnPath, ref closestPointOnPathTValue, ref prevPathLocationIdx)) { DisableShip(updateVisibility); // Check for closest object float lookDistance = 10f, minDistance = 10000f; RaycastHit raycastHit; Vector3 objectNormal = Vector3.zero; Ray ray = new Ray(closestPointOnPath, Vector3.up); SSCUtils.GetClosestCollider(ray, Vector3.up, lookDistance, ref minDistance, ref objectNormal, out raycastHit); SSCUtils.GetClosestCollider(ray, Vector3.down, lookDistance, ref minDistance, ref objectNormal, out raycastHit); SSCUtils.GetClosestCollider(ray, Vector3.left, lookDistance, ref minDistance, ref objectNormal, out raycastHit); SSCUtils.GetClosestCollider(ray, Vector3.right, lookDistance, ref minDistance, ref objectNormal, out raycastHit); SSCUtils.GetClosestCollider(ray, Vector3.forward, lookDistance, ref minDistance, ref objectNormal, out raycastHit); SSCUtils.GetClosestCollider(ray, Vector3.back, lookDistance, ref minDistance, ref objectNormal, out raycastHit); if (objectNormal.sqrMagnitude > 0.01f) { Vector3 pathTangent = Vector3.zero; // Get the direction of the path so ship faces in the correct direction AND is orientated upwards similar to closest object. if (SSCMath.GetPathTangent(pathData, prevPathLocationIdx, closestPointOnPathTValue, ref pathTangent)) { transform.SetPositionAndRotation(closestPointOnPath, Quaternion.LookRotation(pathTangent, objectNormal)); } else { transform.SetPositionAndRotation(closestPointOnPath, Quaternion.Euler(objectNormal)); } } else { transform.position = closestPointOnPath; } EnableShip(updateVisibility, resetVelocity); } } } } /// /// Check to see if there is a ShipAIInputModule attached to this ship. /// private void FetchShipAIInputModule() { isShipAIInputModuleAttached = TryGetComponent(out shipAIInputModule); //shipAIInputModule = GetComponent(); //isShipAIInputModuleAttached = shipAIInputModule != null; } /// /// Check to see if there is a ShipDocking component attached to this ship /// private void FetchShipDocking() { isShipDockingAttached = TryGetComponent(out shipDocking); //shipDocking = GetComponent(); //isShipDockingAttached = shipDocking != null; } /// /// [INTERNAL ONLY] Initialise radar for this ship /// Assumes shipInstance is not null and that /// sscRadar = SSCRadar.GetOrCreateRadar() has already /// been called. /// private void InitialiseRadar() { // Not assigned in the radar system shipInstance.radarItemIndex = -1; if (shipInstance.isRadarEnabled && sscRadar != null) { SSCRadarItem sscRadarItem = new SSCRadarItem(); // No data from the ship yet, so hide it from radar sscRadarItem.isVisibleToRadar = false; sscRadarItem.blipSize = shipInstance.radarBlipSize; sscRadarItem.shipControlModule = this; // Create a packet to be used to send data to the radar system shipInstance.sscRadarPacket = new SSCRadarPacket(); shipInstance.radarItemIndex = sscRadar.AddItem(sscRadarItem); } } /// /// [INTERNAL ONLY] Initialise radar for a localised damage region. /// Assumes shipInstance is not null, and that /// sscRadar = SSCRadar.GetOrCreateRadar() has already been called. /// private void InitialiseLocalDamageRegionRadar(DamageRegion damageRegion) { // Not assigned in the radar system damageRegion.radarItemIndex = -1; if (shipInstance.isRadarEnabled && damageRegion.isRadarEnabled && sscRadar != null) { SSCRadarItem sscRadarItem = new SSCRadarItem(); // No data from the ship damage region yet, so hide it from radar sscRadarItem.isVisibleToRadar = false; sscRadarItem.blipSize = 1; sscRadarItem.shipControlModule = this; // The damage region child transform can be used to help determine LoS. It may not be set. sscRadarItem.itemGameObject = damageRegion.regionChildTransform == null ? null : damageRegion.regionChildTransform.gameObject; sscRadarItem.radarItemType = SSCRadarItem.RadarItemType.ShipDamageRegion; sscRadarItem.guidHash = damageRegion.guidHash; // Create a packet to be used to send data to the radar system damageRegion.sscRadarPacket = new SSCRadarPacket(); damageRegion.radarItemIndex = sscRadar.AddItem(sscRadarItem); } } /// /// If muzzle FX on weapons are pooled and have been parented to the weapons, when a /// ship or damage region is destroyed (o rmade inactive), they need to be reparented to the pool. /// NOTE: This may impact GC /// private void DestroyFX (Transform tfm) { EffectsModule[] effectsModules = tfm.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(); } } } /// /// Automatically attempt to determine what kind of ship this is for use in the radar system. /// Typically called after InitialiseRadar(). /// It defaults ot PlayerShip. /// private void RadarAutoSetType() { if (sscRadar != null) { if (isShipAIInputModuleAttached) { sscRadar.SetItemType(shipInstance.RadarId, SSCRadarItem.RadarItemType.AIShip); } else { sscRadar.SetItemType(shipInstance.RadarId, SSCRadarItem.RadarItemType.PlayerShip); } } } /// /// Initiate a destruct prefab on a damage region of the ship /// NOTE: sscManager must not be null when calling this method. /// /// /// private void DestructDamageRegion(DamageRegion damageRegion, bool isMainRegion) { if (!damageRegion.isDestructObjectActivated && damageRegion.destructObjectPrefabID >= 0 && damageRegion.destructObject != null) { Vector3 destructPosition = shipInstance.TransformPosition; Quaternion destructRotation = shipInstance.TransformRotation; if (isMainRegion) { if (shipInstance.respawningMode == Ship.RespawningMode.DontRespawn) { // Turn off all colliders. As we are going to destroy the gameobject, // we can simply deactivate it. gameObject.SetActive(false); } else { // Cater for respawning - before DestructDamageRegion is called the ship is disabled // with EnableOrDisableShip(false, true, false). Here we should not turn off the gameObject. // This "seems" to work without having to disable the colliders but might need more testing. } } else { // Get the worldspace position of the region destructPosition = shipInstance.GetDamageRegionWSPosition(damageRegion); // Damage region needs to be repairable } // Instantiate the region destruct prefab InstantiateDestructParameters dstParms = new InstantiateDestructParameters { destructPrefabID = damageRegion.destructObjectPrefabID, position = destructPosition, rotation = destructRotation, explosionPowerFactor = 1f, explosionRadiusFactor = 1f }; // Keep track of the DestructModule instance that was instantiated for this region if (sscManager.InstantiateDestruct(ref dstParms) != null) { damageRegion.destructItemKey = new SSCDestructItemKey(damageRegion.destructObjectPrefabID, dstParms.destructPoolListIndex, dstParms.destructSequenceNumber); } damageRegion.isDestructObjectActivated = true; } } /// /// Update the Thruster Effects /// private void UpdateThrusterFX() { arrayLength = thrusterEffects == null ? 0 : thrusterEffects.Length; Vector3 localVelo = shipInstance.LocalVelocity; for (componentIndex = 0; componentIndex < arrayLength; componentIndex++) { if (thrusterEffects[componentIndex] != null) { thrusterEffects[componentIndex].UpdateThrusterInput(shipInstance.thrusterList[componentIndex], localVelo); } } } #endregion #region Events // Called when the ship rigidbody collides with another collider private void OnCollisionEnter(Collision collision) { if (callbackOnCollision != null) { callbackOnCollision.Invoke(this, collision); } else { shipInstance.ApplyCollisionDamage(collision); } } #endregion #region Public API Methods - Initialisation /// /// Runs all necessary initialisation processes for the ship. /// public void InitialiseShip() { if (!isInitialised) { // Find the rigidbody rBody = GetComponent(); // Cater for scenario where [RequireComponent(typeof(Rigidbody))] doesn't work // RequireComponent only seems to work when first adding the script to a gameobject. if (rBody == null) { isInitialised = false; return; } // Configure the rigidbody rBody.drag = 0f; rBody.angularDrag = 0f; rBody.useGravity = false; rBody.isKinematic = false; rBody.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic; // Set the mass and centre of mass of the ship ReinitialiseMass(); // Initialise all necessary ship data shipInstance.UpdatePositionAndMovementData(transform, rBody); shipInstance.Initialise(transform); // Initialise ship health shipInstance.ResetHealth(); ReinitialiseThrusterEffects(); if (!shipInstance.isThrusterSystemsStarted) { StopThrusterEffects(); } // Get a reference to the Ship Controller Manager instance sscManager = SSCManager.GetOrCreateManager(); // Initialise projectiles and effects objects ReinitialiseShipProjectilesAndEffects(); // Initialise beam weapons and the effects objects used by those weapons. ReinitialiseShipBeams(); // Initialise destruct objects used by damage regions ReinitialiseShipDestructObjects(); // Initialise required lists // Pre-size the list of renderers that is used when // ships are disable/enabled during respawing. If the list // is too small, it be expanded the first time it is used (and affect GC). activeRenderersList = new List(5); // Assume only 1 audio source per ship. Will auto expand if required. // Increase the capacity of the list if ships tend to have more audio sources. audioSourcesList = new List(1); FetchShipAIInputModule(); FetchShipDocking(); if (shipInstance.isRadarEnabled) { sscRadar = SSCRadar.GetOrCreateRadar(); InitialiseRadar(); RadarAutoSetType(); if (shipInstance.shipDamageModel == Ship.ShipDamageModel.Localised) { for (int dmIdx = 0; dmIdx < shipInstance.numLocalisedDamageRegions; dmIdx++) { InitialiseLocalDamageRegionRadar(shipInstance.localisedDamageRegionList[dmIdx]); } } } isInitialised = true; } } /// /// Reinitialises variables related to the mass of the ship. /// Call after modifying shipInstance.mass or shipInstance.centreOfMass. /// public void ReinitialiseMass() { // Set the mass and centre of mass of the ship rBody.mass = shipInstance.mass; rBody.centerOfMass = shipInstance.centreOfMass; } /// /// Reinitialises variables required for projectiles and effects of the ship. /// Call this after modifying any projectile or effect data for this ship. /// public void ReinitialiseShipProjectilesAndEffects() { if (sscManager != null) { // Initialise projectiles and effects objects sscManager.UpdateProjectilesAndEffects(shipInstance); } #if UNITY_EDITOR else { Debug.LogWarning("ShipControlModule.ReinitialiseShipProjectilesAndEffects Warning: could not find SSCManager to update projectiles and effects."); } #endif } /// /// Reinitialises variables required for destruct objects of the ship. /// Call after modifying any destruct data for this ship. /// public void ReinitialiseShipDestructObjects () { if (sscManager != null) { // Initialise destruct objects sscManager.UpdateDestructObjects(shipInstance); } #if UNITY_EDITOR else { Debug.LogWarning("ShipControlModule.ReinitialiseShipDestructObjects Warning: could not find SSCManager to update destruct objects."); } #endif } /// /// Reinitialises variables required for ship beam weapons and effects used by those beams. /// Call this after modifying any beams or beam effect data for this ship. /// public void ReinitialiseShipBeams() { if (sscManager != null) { // Initialise beams, and effects objects used by those beams sscManager.UpdateBeamsAndEffects(shipInstance); } #if UNITY_EDITOR else { Debug.LogWarning("ShipControlModule.ReinitialiseShipBeams Warning: could not find SSCManager to update (weapon) beams"); } #endif } /// /// Reinitialises the thruster effects for a ship. /// Call this after modifying thruster effects objects for this ship. /// WARNING: This will generate Garbage so use sparingly. /// public void ReinitialiseThrusterEffects() { // Initialise all thruster effects arrayLength = shipInstance.thrusterList == null ? 0 : shipInstance.thrusterList.Count; int numThrusterEffects = thrusterEffects == null ? 0 : thrusterEffects.Length; if (arrayLength > 0) { // There is a list of thrusterEffectObjects. Each thruster has a slot however it also may be null if the thruster has no effects parent gameobject. // Each thruster should have a single ThrusterEffects slot (which could be null if thruster has no effects parent gameobject in the scene) // One ThrusterEffects component is added the to thruster effects parent gameobject (if there is one) for each Thruster in the scene. // Each ThrusterEffects component is then initialised. if (numThrusterEffects == 0) { thrusterEffects = new ThrusterEffects[arrayLength]; } else if (arrayLength != numThrusterEffects) { System.Array.Resize(ref thrusterEffects, arrayLength); } numThrusterEffects = thrusterEffects == null ? 0 : thrusterEffects.Length; for (componentIndex = 0; componentIndex < arrayLength; componentIndex++) { if (thrusterEffectObjects[componentIndex] != null) { // Check if the ThrusterEffects component has previously been added. If not, add it now. ThrusterEffects thrusterEffect = thrusterEffectObjects[componentIndex].GetComponent(); if (thrusterEffect == null) { thrusterEffect = thrusterEffectObjects[componentIndex].AddComponent(); } thrusterEffects[componentIndex] = thrusterEffect; thrusterEffects[componentIndex].Initialise(); } else { //thrusterEffects[componentIndex].Clear(); } } } else { // Clean up old thruster effect class instances if (numThrusterEffects > 0) { for (componentIndex = 0; componentIndex < numThrusterEffects; componentIndex++) { thrusterEffects[componentIndex].Clear(); } System.Array.Clear(thrusterEffects, 0, numThrusterEffects); } thrusterEffects = null; } } #endregion #region Public API Methods - Reset, Enable, Disable Ship /// /// A fast way of re-initialising a ship without changing its position or core /// settings. Typically called when re-initialising a scene or bringing a ship /// out of hibernation. If you want to temporarily stop a ship from moving, like /// when a user brings up a menu, call DisableShip() and EnableShip() instead. /// public void ResetShip() { // Temporily prevent other things from occuring isInitialised = false; // Disable ship but don't make it invisible. Reset velocity EnableOrDisableShip(false, false, true); // Stop all Thrusters on the ship arrayLength = thrusterEffects == null ? 0 : thrusterEffects.Length; for (componentIndex = 0; componentIndex < arrayLength; componentIndex++) { if (thrusterEffects[componentIndex] != null) { thruster = shipInstance.thrusterList[componentIndex]; if (thruster != null) { thrusterEffects[componentIndex].Stop(); } } } isRespawning = false; // (Re)initialise all necessary ship data shipInstance.UpdatePositionAndMovementData(transform, rBody); // NOTE: This may cause issues with Radar as it sets radarItemIndex = -1 // If we see a problem, maybe we need to remove from radar first. shipInstance.Initialise(transform); // Reset the health of the ship shipInstance.ResetHealth(); // Get the last damage event index of the ship lastDamageEventIndex = shipInstance.LastDamageEventIndex(); // Reset rumble damageRumbleTimer = -1f; isInitialised = true; } /// /// Enables the ship and make visible. /// If resetVelocity is set to true, resets the velocity of the ship to zero. /// /// public void EnableShip (bool resetVelocity) { EnableOrDisableShip(true, true, resetVelocity); } /// /// Enables the ship. If updateVisibility is set to true, makes the ship visible. /// If resetVelocity is set to true, resets the velocity of the ship to zero. /// /// /// public void EnableShip(bool updateVisibility, bool resetVelocity) { EnableOrDisableShip(true, updateVisibility, resetVelocity); } public bool IsActive => isActive; private bool isActive = false; public void LittleEnableShip() { isActive = true; } public void LittleDisableShip() { isActive = false; } /// /// Disables the ship. If updateVisibility is set to true, makes the ship invisible. /// /// public void DisableShip(bool updateVisibility) { EnableOrDisableShip(false, updateVisibility, false); } /// /// Returns whether the ship is currently enabled. When disabled, it /// cannot move, be visible or radar, nor received damage and be destroyed. /// /// public bool ShipIsEnabled() { return shipIsEnabled; } #endregion #region Public API Methods - Enable, Disable Ship Movement /// /// Returns whether the ship movement is curently enabled. /// See also ShipIsEnabled(). A ship's movement may be disabled /// while it can still receive damaage, be destroyed or be visible /// to radar. /// /// public bool ShipMovementIsEnabled() { return isMovementEnabled; } /// /// This is a subset of EnableShip(...). /// It only applies to: /// 1) Physics /// 2) Thruster Effects /// 3) User or AI Input to the Ship /// 4) Sound FX (audio) /// If a ship is also disabled, this will re-enable the ship. /// /// public void EnableShipMovement(bool resetVelocity) { if (isInitialised) { if (!shipIsEnabled) { EnableOrDisableShip(true, true, resetVelocity); } else { EnableOrDisableShipMovement(true, resetVelocity); } } } /// /// This is a subset of DisableShip(...). /// It only applies to: /// 1) Physics /// 2) Thruster Effects /// 3) User or AI Input to the Ship /// 4) Sound FX (audio) /// public void DisableShipMovement() { if (isInitialised) { EnableOrDisableShipMovement(false, false); } } /// /// Teleport the ship to a new location by moving by an amount /// in the x, y and z directions. This could be useful if changing /// the origin or centre of your world to compensate for float-point /// error. /// NOTE: This does not alter the current Respawn position. /// /// /// public void TelePort (Vector3 delta, bool resetVelocity) { // Remember current situation of the ship bool isMovementEnabled = false; if (isInitialised) { DisableShipMovement(); isMovementEnabled = true; } transform.position += delta; // If movement was enabled, re-enable it if (isMovementEnabled) { EnableShipMovement(resetVelocity); } } /// /// Teleport the ship to a new location with a new rotation. /// NOTE: This does not alter the current Respawn position. /// /// /// /// public void TelePort (Vector3 newPosition, Quaternion newRotation, bool resetVelocity) { // Remember current situation of the ship bool isMovementEnabled = false; if (isInitialised) { DisableShipMovement(); isMovementEnabled = true; } transform.SetPositionAndRotation(newPosition, newRotation); // If movement was enabled, re-enable it if (isMovementEnabled) { EnableShipMovement(resetVelocity); } } #endregion #region Public API Methods - Respawning /// /// If the ship is currently respawning, this will stop /// the countdown timer, preventing the ship from respawning /// until ResumeRespawning() is called. /// NOTE: Has no effect if not already respawning. /// public void PauseRespawning() { if (isRespawning) { isRespawingPaused = true; } } /// /// If respawning is currently paused, the respawning /// timer will now continue until the ship is respawned. /// NOTE: Has no effect if respawningMode is DontRespawn /// public void ResumeRespawning() { isRespawingPaused = false; } #endregion #region Public API Methods - Radar /// /// The ship will no longer send tracking information to the radar system. /// If you want to change the visibility to other radar consumers, consider /// changing the radar item data rather than disabling the radar and (later) /// calling EnableRadar again. When using a Localised ShipDamageModel, it /// will also disable radar on all localised Damage Regions. /// public void DisableRadar() { if (isInitialised) { if (shipInstance.isRadarEnabled && sscRadar != null && sscRadar.IsInitialised) { // If enabled, remove the localised damage regions from radar if (shipInstance.shipDamageModel == Ship.ShipDamageModel.Localised) { for (int dmIdx = 0; dmIdx < shipInstance.numLocalisedDamageRegions; dmIdx++) { DisableRadar(shipInstance.localisedDamageRegionList[dmIdx]); } } // Remove this ship from radar sscRadar.RemoveItem(shipInstance.radarItemIndex); } shipInstance.sscRadarPacket = null; shipInstance.isRadarEnabled = false; shipInstance.radarItemIndex = -1; } sscRadar = null; } /// /// The ship will no longer send tracking information to the radar system for this /// damage region. If you want to change the visibility to other radar consumers, consider /// changing the radar item data rather than disabling the radar and (later) /// calling EnableRadar(damageRegion) again. /// NOTE: You do not need to call this if calling DisableRadar() for the ship. /// /// public void DisableRadar(DamageRegion damageRegion) { if (damageRegion != null) { if (damageRegion.isRadarEnabled && damageRegion.radarItemIndex >= 0) { sscRadar.RemoveItem(damageRegion.radarItemIndex); } damageRegion.sscRadarPacket = null; damageRegion.isRadarEnabled = false; damageRegion.radarItemIndex = -1; } } /// /// Enable the ship to send tracking information to the /// radar system. The ship must first be initialised. /// public void EnableRadar() { if (isInitialised) { sscRadar = SSCRadar.GetOrCreateRadar(); if (sscRadar != null) { shipInstance.isRadarEnabled = true; InitialiseRadar(); RadarAutoSetType(); } } } /// /// Enable the ship to send tracking information to the radar system /// for this damage region. The ship must be initialised and radar must /// already be enabled for the ship. See also EnableRadar(). /// /// public void EnableRadar(DamageRegion damageRegion) { if (isInitialised && damageRegion.radarItemIndex == -1) { InitialiseLocalDamageRegionRadar(damageRegion); damageRegion.isRadarEnabled = damageRegion.radarItemIndex >= 0; } } #endregion #region Public API Methods - Ship Input /// /// Provide an instance of shipInput, and have it populated with the current values from the ship. /// /// public void GetShipInput (ShipInput shipInput) { if (shipInput != null) { shipInput.horizontal = pilotForceInput.x; shipInput.vertical = pilotForceInput.y; shipInput.longitudinal = pilotForceInput.z; shipInput.pitch = pilotMomentInput.x; shipInput.yaw = pilotMomentInput.y; shipInput.roll = pilotMomentInput.z; shipInput.primaryFire = pilotPrimaryFireInput; shipInput.secondaryFire = pilotSecondaryFireInput; shipInput.dock = pilotDockInput; } } /// /// Sends the specified input to the ship control module in order to control the ship. /// By default all data inputs should be enabled even when sending 0 values. /// /// public void SendInput(ShipInput shipInput) { if (shipInput.isHorizontalDataEnabled) { pilotForceInput.x = shipInput.horizontal; } if (shipInput.isVerticalDataEnabled) { pilotForceInput.y = shipInput.vertical; } if (shipInput.isLongitudinalDataEnabled) { pilotForceInput.z = shipInput.longitudinal; } if (shipInput.isPitchDataEnabled) { pilotMomentInput.x = shipInput.pitch; } if (shipInput.isYawDataEnabled) { pilotMomentInput.y = shipInput.yaw; } if (shipInput.isRollDataEnabled) { pilotMomentInput.z = shipInput.roll; } if (shipInput.isPrimaryFireDataEnabled) { pilotPrimaryFireInput = shipInput.primaryFire; } if (shipInput.isSecondaryFireDataEnabled) { pilotSecondaryFireInput = shipInput.secondaryFire; } if (shipInput.isDockDataEnabled) { pilotDockInput = shipInput.dock; } } #endregion #region Public API Methods - Docking /// /// Is the ShipDocking component attached to this ship, and if so, is the ship's state 'Docked'? /// NOTE: This does not mean it must be docked with a ShipDockingStation. For that, you would /// need to check the ShipDockingStation's docking points. /// /// public bool ShipIsDocked() { return isShipDockingAttached && shipDocking.GetState() == ShipDocking.DockingState.Docked; } /// /// Is the ShipDocking component attached to this ship, and if so, is the ship's state 'Not Docked'? /// NOTE: This does not mean it must be not docked with a ShipDockingStation. For that, you would /// need to check the ShipDockingStation's docking points. /// /// public bool ShipIsNotDocked() { return isShipDockingAttached && shipDocking.GetStateInt() == ShipDocking.notDockedInt; } /// /// Retrieves a reference to the ShipDocking script if one was attached at the time this /// module was initiated. /// /// Ignore cached value and call GetComponent when true /// public ShipDocking GetShipDocking(bool forceCheck = false) { if (forceCheck) { FetchShipDocking(); } return isShipDockingAttached ? shipDocking : null; } #endregion #region Public API Methods - Ship AI /// /// Retrieves a reference to the ShipAIInputModule script if one was attached at the time /// this module was initialised. /// /// Ignore cached value and call GetComponent when true /// public ShipAIInputModule GetShipAIInputModule(bool forceCheck = false) { if (forceCheck) { FetchShipAIInputModule(); } return isShipAIInputModuleAttached ? shipAIInputModule : null; } #endregion #region Public API Methods - Thrusters /// /// Add 1 second of forward boost. /// See also StopBoost(). /// For more control, see shipInstance.AddBoost(..). /// /// public void AddBoost (float forceAmountKNeutons, float time = 1) { if (isInitialised) { shipInstance.AddBoost(Vector3.forward, forceAmountKNeutons * 1000f, time); } } public void MultiplyVelocity(float percent) { if (isInitialised) { rBody.velocity *= percent; } } /// /// Enable all the thruster effects where the gameobject contains the specified string. /// Typically used to turn on an effect to improve the quality or look of a game. /// Call ReinitialiseThrusterEffects() after calling 1 or more of these methods. /// If you wish to enable or start a thruster, it is more likely you want to use EnableShip() /// or EnableShipMovement(). /// WARNING: This will generate Garbage so use sparingly. /// /// public void EnableThrusterEffects(string effectNameContains) { arrayLength = thrusterEffectObjects == null ? 0 : thrusterEffectObjects.Length; // Look through all the thrusters for effects parent gameobjects for (componentIndex = 0; componentIndex < arrayLength; componentIndex++) { GameObject thrusterEffectsGO = thrusterEffectObjects[componentIndex]; if (thrusterEffectsGO != null) { // If the top level effects gameobject contains the string, then disable it. if (thrusterEffectsGO.name.Contains(effectNameContains)) { thrusterEffectsGO.SetActive(true); } else { // Search through the child objects including inactive transforms Transform[] childTrfms = thrusterEffectsGO.GetComponentsInChildren(true); int numChildren = childTrfms == null ? 0 : childTrfms.Length; for (int t = 0; t < numChildren; t++) { if (childTrfms[t] != null && childTrfms[t].name.Contains(effectNameContains)) { childTrfms[t].gameObject.SetActive(true); } } } } } } /// /// Disable all the thruster effects where the gameobject contains the specified string. /// Typically used to turn off an effect to reduce the performance overhead of running it. /// Call ReinitialiseThrusterEffects() after calling 1 or more of these methods. /// If you wish to pause or stop a thruster, it is more likely you want to use DisableShip() /// or DisableShipMovement(). /// WARNING: This will generate Garbage so use sparingly. /// /// public void DisableThrusterEffects (string effectNameContains) { arrayLength = thrusterEffectObjects == null ? 0 : thrusterEffectObjects.Length; // Look through all the thrusters for effects parent gameobjects for (componentIndex = 0; componentIndex < arrayLength; componentIndex++) { GameObject thrusterEffectsGO = thrusterEffectObjects[componentIndex]; if (thrusterEffectsGO != null) { // If the top level effects gameobject contains the string, then disable it. if (thrusterEffectsGO.name.Contains(effectNameContains)) { thrusterEffectsGO.SetActive(false); } else { // Search through the child objects Transform[] childTrfms = thrusterEffectsGO.GetComponentsInChildren(); int numChildren = childTrfms == null ? 0 : childTrfms.Length; for (int t = 0; t < numChildren; t++) { if (childTrfms[t] != null && childTrfms[t].name.Contains(effectNameContains)) { childTrfms[t].gameObject.SetActive(false); } } } } } } /// /// Set the maximum volume for a given thruster. Numbers begin at 1. Values should be between 0.0 and 1.0. /// /// /// public void SetThrusterMaxVolume (int thrusterNumber, float newMaxVolume) { if (isInitialised && thrusterNumber > 0 && thrusterNumber <= (thrusterEffects == null ? 0 : thrusterEffects.Length)) { if (thrusterEffects[thrusterNumber - 1] != null) { thrusterEffects[thrusterNumber - 1].SetMaxVolume(newMaxVolume); } } } /// /// Begin to shut down the thrusters. Optionally override the shutdown duration. /// public void ShutdownThrusterSystems (bool isInstantShutdown = false) { if (isInitialised) { shipInstance.ShutdownThrusterSystems(isInstantShutdown); //if (!shipInstance.isThrusterSystemsStarted) { UpdateThrusterFX(); //StopThrusterEffects(); } } } /// /// Begin to bring the thrusters online. Optionally, override the startup duration. /// As soon as the systems begin to start up, shipInstance.isThrusterSystemsStarted will be true. /// /// public void StartupThrusterSystems (bool isInstantStartup = false) { if (isInitialised) { shipInstance.StartupThrusterSystems(isInstantStartup); PauseOrResumeThrusters(true); UpdateThrusterFX(); } } /// /// Immediately stop any boost that has been applied with AddBoost(..). /// public void StopBoost() { if (isInitialised) { shipInstance.StopBoost(); } } /// /// Stop all Thruster Effects on the ship /// public void StopThrusterEffects() { arrayLength = thrusterEffects == null ? 0 : thrusterEffects.Length; for (componentIndex = 0; componentIndex < arrayLength; componentIndex++) { if (thrusterEffects[componentIndex] != null) { thrusterEffects[componentIndex].Stop(); } } } #endregion } #region Public Structures /// /// Paramaters structure for CallbackOnHit (callback for Ship Control Module). /// We do not recommend keeping references to any fields within this structure. /// Use them in one frame, then discard them. /// public struct CallbackOnShipHitParameters { /// /// Hit information for the raycast hit against the ship. /// public RaycastHit hitInfo; /// /// Prefab for the projectile that hit the ship. /// public ProjectileModule projectilePrefab; /// /// Prefab for the beam that hit the ship /// public BeamModule beamPrefab; /// /// Amount of damage done by the projectile or beam. /// public float damageAmount; /// /// The squadron ID of the ship that fired the projectile or beam. /// public int sourceSquadronId; /// /// Ship that fired the projectile or beam, else 0 /// public int sourceShipId; }; #endregion }