rabidus-test/Assets/SCSM/SciFiShipController/Scripts/Physics/Behaviours/ShipControlModule.cs

2078 lines
91 KiB
C#
Raw Permalink Normal View History

2023-07-24 16:38:13 +03:00
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;
/// <summary>
/// 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.
/// </summary>
public Ship shipInstance;
/// <summary>
/// [INTERNAL USE ONLY]
/// </summary>
public GameObject[] thrusterEffectObjects = new GameObject[1];
// Remember which tabs etc were shown
[HideInInspector] public int selectedTabInt = 0;
/// <summary>
/// [READONLY] Has the ship been initialised?
/// </summary>
public bool IsInitialised { get { return isInitialised; } }
/// <summary>
/// [READONLY] Session-only transform InstanceID.
/// </summary>
public int GetShipId { get { return isInitialised ? shipInstance.shipId : transform.GetInstanceID(); } }
/// <summary>
/// Attempt to get or set gravitational acceleration (must be initialised)
/// </summary>
public float GravitationalAcceleration { get { return isInitialised ? shipInstance.gravitationalAcceleration : 0f; } set { if (isInitialised) { shipInstance.gravitationalAcceleration = value; } } }
/// <summary>
/// Attempt to get or set the direction gravity is acting the ship (must be initialised)
/// </summary>
public Vector3 GravityDirection { get { return isInitialised ? shipInstance.gravityDirection : Vector3.zero; } set { if (isInitialised) { shipInstance.gravityDirection = value; } } }
/// <summary>
/// [READONLY] The rigidbody of the ship.
/// </summary>
public Rigidbody ShipRigidbody { get { return rBody; } }
/// <summary>
/// Are the thruster systems online or in the process of coming online?
/// At runtime call StartupThrusterSystems() or ShutdownThrusterSystems().
/// </summary>
public bool IsThrusterSystemsStarted { get { return isInitialised ? shipInstance.isThrusterSystemsStarted : false; } }
/// <summary>
/// [READONLY] Is the ship currently being respawned?
/// The ship will be disabled during the respawn time.
/// </summary>
public bool IsRespawning { get { return isRespawning; } }
/// <summary>
/// [READONLY] Has respawning been paused? If so, this ship cannot respawn
/// until ResumeRespawning() has been called. See also PauseRespawning().
/// </summary>
public bool IsRespawningPaused { get { return isRespawingPaused; } }
/// <summary>
/// [READONLY] Is this ship visible to the radar?
/// </summary>
public bool IsVisbleToRadar { get { return isInitialised && shipInstance != null && shipInstance.isRadarEnabled && sscRadar != null && sscRadar.GetVisibility(shipInstance.radarItemIndex); } }
/// <summary>
/// [READONLY] The number of weapons on this ship. Will always return 0 if ship has not been initialised.
/// See also ship.NumberOfWeapons.
/// </summary>
public int NumberOfWeapons { get { return isInitialised ? (shipInstance.weaponList == null ? 0 : shipInstance.weaponList.Count) : 0; } }
/// <summary>
/// [READONLY] The number of times the ship has been respawned. This is incremented
/// when the ship is respawned - not when it is destroyed.
/// </summary>
public int NumberOfRespawns { get { return respawnCount; } }
/// <summary>
/// [INTERNAL USE ONLY]
/// </summary>
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;
/// <summary>
/// Used internally to reset PID Controllers on AI ships
/// when ship velocity is set to 0.
/// </summary>
private ShipAIInputModule shipAIInputModule;
private bool isShipAIInputModuleAttached = false;
private ShipDocking shipDocking;
private bool isShipDockingAttached = false;
private Rigidbody rBody;
private List<Renderer> activeRenderersList;
private List<AudioSource> audioSourcesList;
/// <summary>
/// This prevents the activeRenderersList being updated while the ship
/// is disabled
/// </summary>
private bool isShipVisibilityDisabled = false;
private int componentIndex;
private int arrayLength;
private Thruster thruster;
private bool shipIsEnabled = true;
/// <summary>
/// This is a subset of shipIsEnabled. Current applies to:
/// 1) Physics
/// 2) Thruster Effects
/// 3) User or AI Input
/// 4) Sound FX (audio)
/// </summary>
[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);
/// <summary>
/// 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.
/// </summary>
[System.NonSerialized] public CallbackOnCollision callbackOnCollision = null;
/// <summary>
/// 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.
/// </summary>
[System.NonSerialized] public CallbackOnDestroy callbackOnDestroy = null;
/// <summary>
/// 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.
/// </summary>
[System.NonSerialized] public CallbackOnRespawn callbackOnRespawn = null;
/// <summary>
/// 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.
/// </summary>
[System.NonSerialized] public CallbackOnHit callbackOnHit = null;
/// <summary>
/// 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.
/// </summary>
[System.NonSerialized] public CallbackOnStuck callbackOnStuck = null;
/// <summary>
/// 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.
/// </summary>
[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
/// <summary>
/// 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.
/// </summary>
/// <param name="isEnabled"></param>
/// <param name="updateVisibility"></param>
/// <param name="resetVelocity"></param>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="isEnabled"></param>
/// <param name="resetVelocity"></param>
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;
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="isEnabled"></param>
/// <param name="resetVelocity"></param>
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;
}
}
}
/// <summary>
/// Pause or Resume (play) all Thruster Effects on the ship
/// </summary>
/// <param name="isEnabled"></param>
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(); }
}
}
}
/// <summary>
/// Find and stop all audio sources
/// </summary>
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();
}
}
}
/// <summary>
/// CURRENTLY NOT USED - FOR TESTING ONLY
/// </summary>
/// <returns></returns>
private IEnumerator RBodyReset()
{
//rBody.velocity = transform.TransformDirection(shipInstance.respawnVelocity);
rBody.velocity = Vector3.zero;
rBody.angularVelocity = Vector3.zero;
yield return new WaitForFixedUpdate();
}
/// <summary>
/// A ship can be considered stuck if it is enabled, initialised, respawnStuckTime > 0
/// and it is trying to move for respawnStuckTime but cannot.
/// </summary>
/// <returns></returns>
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;
}
}
}
/// <summary>
/// Respawn (move) the ship to the closest point on the given Path.
/// The ship must be initialised and the Path must be valid.
/// </summary>
/// <param name="pathguidHash"></param>
/// <param name="updateVisibility"></param>
/// <param name="resetVelocity"></param>
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);
}
}
}
}
/// <summary>
/// Check to see if there is a ShipAIInputModule attached to this ship.
/// </summary>
private void FetchShipAIInputModule()
{
isShipAIInputModuleAttached = TryGetComponent(out shipAIInputModule);
//shipAIInputModule = GetComponent<ShipAIInputModule>();
//isShipAIInputModuleAttached = shipAIInputModule != null;
}
/// <summary>
/// Check to see if there is a ShipDocking component attached to this ship
/// </summary>
private void FetchShipDocking()
{
isShipDockingAttached = TryGetComponent(out shipDocking);
//shipDocking = GetComponent<ShipDocking>();
//isShipDockingAttached = shipDocking != null;
}
/// <summary>
/// [INTERNAL ONLY] Initialise radar for this ship
/// Assumes shipInstance is not null and that
/// sscRadar = SSCRadar.GetOrCreateRadar() has already
/// been called.
/// </summary>
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);
}
}
/// <summary>
/// [INTERNAL ONLY] Initialise radar for a localised damage region.
/// Assumes shipInstance is not null, and that
/// sscRadar = SSCRadar.GetOrCreateRadar() has already been called.
/// </summary>
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);
}
}
/// <summary>
/// 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
/// </summary>
private void DestroyFX (Transform tfm)
{
EffectsModule[] effectsModules = tfm.GetComponentsInChildren<EffectsModule>(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();
}
}
}
/// <summary>
/// Automatically attempt to determine what kind of ship this is for use in the radar system.
/// Typically called after InitialiseRadar().
/// It defaults ot PlayerShip.
/// </summary>
private void RadarAutoSetType()
{
if (sscRadar != null)
{
if (isShipAIInputModuleAttached) { sscRadar.SetItemType(shipInstance.RadarId, SSCRadarItem.RadarItemType.AIShip); }
else { sscRadar.SetItemType(shipInstance.RadarId, SSCRadarItem.RadarItemType.PlayerShip); }
}
}
/// <summary>
/// Initiate a destruct prefab on a damage region of the ship
/// NOTE: sscManager must not be null when calling this method.
/// </summary>
/// <param name="damageRegion"></param>
/// <param name="isMainRegion"></param>
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;
}
}
/// <summary>
/// Update the Thruster Effects
/// </summary>
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
/// <summary>
/// Runs all necessary initialisation processes for the ship.
/// </summary>
public void InitialiseShip()
{
if (!isInitialised)
{
// Find the rigidbody
rBody = GetComponent<Rigidbody>();
// 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<Renderer>(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<AudioSource>(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;
}
}
/// <summary>
/// Reinitialises variables related to the mass of the ship.
/// Call after modifying shipInstance.mass or shipInstance.centreOfMass.
/// </summary>
public void ReinitialiseMass()
{
// Set the mass and centre of mass of the ship
rBody.mass = shipInstance.mass;
rBody.centerOfMass = shipInstance.centreOfMass;
}
/// <summary>
/// Reinitialises variables required for projectiles and effects of the ship.
/// Call this after modifying any projectile or effect data for this ship.
/// </summary>
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
}
/// <summary>
/// Reinitialises variables required for destruct objects of the ship.
/// Call after modifying any destruct data for this ship.
/// </summary>
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
}
/// <summary>
/// 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.
/// </summary>
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
}
/// <summary>
/// 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.
/// </summary>
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<ThrusterEffects>();
if (thrusterEffect == null) { thrusterEffect = thrusterEffectObjects[componentIndex].AddComponent<ThrusterEffects>(); }
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
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>
/// Enables the ship and make visible.
/// If resetVelocity is set to true, resets the velocity of the ship to zero.
/// </summary>
/// <param name="resetVelocity"></param>
public void EnableShip (bool resetVelocity)
{
EnableOrDisableShip(true, true, resetVelocity);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="updateVisibility"></param>
/// <param name="resetVelocity"></param>
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;
}
/// <summary>
/// Disables the ship. If updateVisibility is set to true, makes the ship invisible.
/// </summary>
/// <param name="updateVisibility"></param>
public void DisableShip(bool updateVisibility)
{
EnableOrDisableShip(false, updateVisibility, false);
}
/// <summary>
/// Returns whether the ship is currently enabled. When disabled, it
/// cannot move, be visible or radar, nor received damage and be destroyed.
/// </summary>
/// <returns></returns>
public bool ShipIsEnabled()
{
return shipIsEnabled;
}
#endregion
#region Public API Methods - Enable, Disable Ship Movement
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
public bool ShipMovementIsEnabled()
{
return isMovementEnabled;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="resetVelocity"></param>
public void EnableShipMovement(bool resetVelocity)
{
if (isInitialised)
{
if (!shipIsEnabled) { EnableOrDisableShip(true, true, resetVelocity); }
else { EnableOrDisableShipMovement(true, resetVelocity); }
}
}
/// <summary>
/// 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)
/// </summary>
public void DisableShipMovement()
{
if (isInitialised)
{
EnableOrDisableShipMovement(false, false);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="delta"></param>
/// <param name="resetVelocity"></param>
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); }
}
/// <summary>
/// Teleport the ship to a new location with a new rotation.
/// NOTE: This does not alter the current Respawn position.
/// </summary>
/// <param name="newPosition"></param>
/// <param name="newRotation"></param>
/// <param name="resetVelocity"></param>
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
/// <summary>
/// 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.
/// </summary>
public void PauseRespawning()
{
if (isRespawning) { isRespawingPaused = true; }
}
/// <summary>
/// If respawning is currently paused, the respawning
/// timer will now continue until the ship is respawned.
/// NOTE: Has no effect if respawningMode is DontRespawn
/// </summary>
public void ResumeRespawning()
{
isRespawingPaused = false;
}
#endregion
#region Public API Methods - Radar
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="damageRegion"></param>
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;
}
}
/// <summary>
/// Enable the ship to send tracking information to the
/// radar system. The ship must first be initialised.
/// </summary>
public void EnableRadar()
{
if (isInitialised)
{
sscRadar = SSCRadar.GetOrCreateRadar();
if (sscRadar != null)
{
shipInstance.isRadarEnabled = true;
InitialiseRadar();
RadarAutoSetType();
}
}
}
/// <summary>
/// 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().
/// </summary>
/// <param name="damageRegion"></param>
public void EnableRadar(DamageRegion damageRegion)
{
if (isInitialised && damageRegion.radarItemIndex == -1)
{
InitialiseLocalDamageRegionRadar(damageRegion);
damageRegion.isRadarEnabled = damageRegion.radarItemIndex >= 0;
}
}
#endregion
#region Public API Methods - Ship Input
/// <summary>
/// Provide an instance of shipInput, and have it populated with the current values from the ship.
/// </summary>
/// <param name="shipInput"></param>
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;
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="shipInput"></param>
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
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
public bool ShipIsDocked()
{
return isShipDockingAttached && shipDocking.GetState() == ShipDocking.DockingState.Docked;
}
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
public bool ShipIsNotDocked()
{
return isShipDockingAttached && shipDocking.GetStateInt() == ShipDocking.notDockedInt;
}
/// <summary>
/// Retrieves a reference to the ShipDocking script if one was attached at the time this
/// module was initiated.
/// </summary>
/// <param name="forceCheck">Ignore cached value and call GetComponent when true</param>
/// <returns></returns>
public ShipDocking GetShipDocking(bool forceCheck = false)
{
if (forceCheck) { FetchShipDocking(); }
return isShipDockingAttached ? shipDocking : null;
}
#endregion
#region Public API Methods - Ship AI
/// <summary>
/// Retrieves a reference to the ShipAIInputModule script if one was attached at the time
/// this module was initialised.
/// </summary>
/// <param name="forceCheck">Ignore cached value and call GetComponent when true</param>
/// <returns></returns>
public ShipAIInputModule GetShipAIInputModule(bool forceCheck = false)
{
if (forceCheck) { FetchShipAIInputModule(); }
return isShipAIInputModuleAttached ? shipAIInputModule : null;
}
#endregion
#region Public API Methods - Thrusters
/// <summary>
/// Add 1 second of forward boost.
/// See also StopBoost().
/// For more control, see shipInstance.AddBoost(..).
/// </summary>
/// <param name="forceAmountKNeutons"></param>
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;
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="effectNameContains"></param>
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<Transform>(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);
}
}
}
}
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="effectNameContains"></param>
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<Transform>();
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);
}
}
}
}
}
}
/// <summary>
/// Set the maximum volume for a given thruster. Numbers begin at 1. Values should be between 0.0 and 1.0.
/// </summary>
/// <param name="thrusterNumber"></param>
/// <param name="newMaxVolume"></param>
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);
}
}
}
/// <summary>
/// Begin to shut down the thrusters. Optionally override the shutdown duration.
/// </summary>
public void ShutdownThrusterSystems (bool isInstantShutdown = false)
{
if (isInitialised)
{
shipInstance.ShutdownThrusterSystems(isInstantShutdown);
//if (!shipInstance.isThrusterSystemsStarted)
{
UpdateThrusterFX();
//StopThrusterEffects();
}
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="isInstantStartup"></param>
public void StartupThrusterSystems (bool isInstantStartup = false)
{
if (isInitialised)
{
shipInstance.StartupThrusterSystems(isInstantStartup);
PauseOrResumeThrusters(true);
UpdateThrusterFX();
}
}
/// <summary>
/// Immediately stop any boost that has been applied with AddBoost(..).
/// </summary>
public void StopBoost()
{
if (isInitialised) { shipInstance.StopBoost(); }
}
/// <summary>
/// Stop all Thruster Effects on the ship
/// </summary>
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
/// <summary>
/// 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.
/// </summary>
public struct CallbackOnShipHitParameters
{
/// <summary>
/// Hit information for the raycast hit against the ship.
/// </summary>
public RaycastHit hitInfo;
/// <summary>
/// Prefab for the projectile that hit the ship.
/// </summary>
public ProjectileModule projectilePrefab;
/// <summary>
/// Prefab for the beam that hit the ship
/// </summary>
public BeamModule beamPrefab;
/// <summary>
/// Amount of damage done by the projectile or beam.
/// </summary>
public float damageAmount;
/// <summary>
/// The squadron ID of the ship that fired the projectile or beam.
/// </summary>
public int sourceSquadronId;
/// <summary>
/// Ship that fired the projectile or beam, else 0
/// </summary>
public int sourceShipId;
};
#endregion
}