rabidus-test/Assets/SCSM/SciFiShipController/Scripts/Physics/Classes/Ship.cs

5522 lines
283 KiB
C#
Raw Permalink Normal View History

2023-07-24 16:38:13 +03:00
#define _SSC_SHIP_DEBUG
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// For Math functions instead of Mathf
using System;
// Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved.
namespace SciFiShipController
{
/// <summary>
/// Class containing data for a ship.
/// </summary>
[System.Serializable]
public class Ship
{
#region Enumerations
public enum ShipPhysicsModel
{
PhysicsBased = 10,
Arcade = 20
}
public enum GroundNormalCalculationMode
{
SingleNormal = 10,
SmoothedNormal = 20
//AverageHeight = 30
}
public enum RollControlMode
{
YawInput = 10,
StrafeInput = 20
}
public enum ShipDamageModel
{
Simple = 10,
Progressive = 20,
Localised = 30
}
public enum RespawningMode
{
DontRespawn = 10,
RespawnAtOriginalPosition = 20,
RespawnAtLastPosition = 30,
RespawnAtSpecifiedPosition = 40,
RespawnOnPath = 50
}
public enum StuckAction
{
DoNothing = 10,
InvokeCallback = 20,
RespawnOnPath = 30,
SameAsRespawningMode = 50
}
public enum InputControlAxis
{
None = 0,
X = 1,
Y = 2
}
#endregion
#region Public Variables and Properties
// IMPORTANT - when changing this section also update SetClassDefault()
// Also update ClassName(ClassName className) Clone Constructor (if there is one)
/// <summary>
/// If enabled, the InitialiseShip() will be called as soon as Awake() runs for the ship. This should be disabled if you are
/// instantiating the ship through code.
/// </summary>
public bool initialiseOnAwake;
/// <summary>
/// What physics model the ship uses. If you modify this, call ReinitialiseShipPhysicsModel().
/// Physics Based: Physics-based model. Physically realistic thrusters and aerodynamic devices are used to control the ship's motion.
/// Arcade: Simplified physics model. Some physical effects are simplified and unrealistic effects can be used to enhance gameplay.
/// </summary>
public ShipPhysicsModel shipPhysicsModel;
// Physical characteristics
/// <summary>
/// Mass of the ship in kilograms. If you modify this, call ReinitialiseMass() on the ShipControlModule.
/// </summary>
public float mass;
/// <summary>
/// Centre of mass of the ship in local space. If you modify this, call ReinitialiseMass() on the ShipControlModule.
/// </summary>
public Vector3 centreOfMass;
/// <summary>
/// If set to true, the centre of mass will be set to the value of centreOfMass.
/// Otherwise the centre of mass will be set to the unity default specified by the rigdbody.
/// NOTE: This value will not affect anything if changed at runtime.
/// </summary>
public bool setCentreOfMassManually;
/// <summary>
/// Show the centre of mass gizmos in the scene view.
/// </summary>
public bool showCOMGizmosInSceneView;
/// <summary>
/// Show the centre of lift and lift direction gizmos in the scene view.
/// </summary>
public bool showCOLGizmosInSceneView;
/// <summary>
/// Show the centre of forwards thrust gizmos in the scene view.
/// </summary>
public bool showCOTGizmosInSceneView;
/// <summary>
/// List of all thruster components for this ship. If you modify this, call ReinitialiseThrusterVariables() and ReinitialiseInputVariables().
/// </summary>
public List<Thruster> thrusterList;
/// <summary>
/// When the ship is enabled, but ship movement is disabled, should thrusters fire
/// if they have isMinEffectsAlwaysOn enabled?
/// </summary>
public bool isThrusterFXStationary;
/// <summary>
/// Are the thruster systems online or in the process of coming online?
/// At runtime call shipInstance.StartupThrusterSystems() or ShutdownThrusterSystems()
/// </summary>
public bool isThrusterSystemsStarted;
/// <summary>
/// The time, in seconds, it takes for the thrusters to fully come online
/// </summary>
[Range(0f, 60f)] public float thrusterSystemStartupDuration;
/// <summary>
/// The time, in seconds, it takes for the thrusters to fully shutdown
/// </summary>
[Range(0f, 60f)] public float thrusterSystemShutdownDuration;
/// <summary>
/// For thrusters, use a central fuel level, rather than fuel level per thruster.
/// </summary>
public bool useCentralFuel;
/// <summary>
/// The amount of fuel available when useCentralFuel is true - range 0.0 (empty) to 100.0 (full).
/// At runtime call shipInstance.SetFuelLevel(..)
/// </summary>
[Range(0f, 100f)] public float centralFuelLevel;
/// <summary>
/// List of all wing components for this ship. If you modify this, call ReinitialiseWingVariables().
/// </summary>
public List<Wing> wingList;
/// <summary>
/// List of all control surface components for this ship. If you modify this, call ReinitialiseInputVariables().
/// </summary>
public List<ControlSurface> controlSurfaceList;
/// <summary>
/// List of all weapon components for this ship. If you modify this, call ReinitialiseWeaponVariables().
/// </summary>
public List<Weapon> weaponList;
/// <summary>
/// When the ship is enabled, but movement is disabled, weapons and damage are updated
/// </summary>
public bool useWeaponsWhenMovementDisabled = false;
/// <summary>
/// [READ ONLY] The number of weapons on this ship
/// </summary>
public int NumberOfWeapons { get { return weaponList == null ? 0 : weaponList.Count; } }
/// <summary>
/// [Internal Use] Is the thruster systems section expanded in the SCM Editor?
/// </summary>
public bool isThrusterSystemsExpanded;
/// <summary>
/// [Internal Use] Is the thruster list expanded in the SCM Editor?
/// </summary>
public bool isThrusterListExpanded;
/// <summary>
/// [Internal Use] Is the wing list expanded in the SCM Editor?
/// </summary>
public bool isWingListExpanded;
/// <summary>
/// [Internal Use] Is the control surface list expanded in the SCM Editor?
/// </summary>
public bool isControlSurfaceListExpanded;
/// <summary>
/// [Internal Use] Is the weapon list expanded in the SCM Editor?
/// </summary>
public bool isWeaponListExpanded;
/// <summary>
/// [Internal Use] Is the main damage expanded in the SCM Editor?
/// </summary>
public bool isMainDamageExpanded;
/// <summary>
/// [Internal Use] Is the local damage list expanded in the SCM Editor?
/// </summary>
public bool isDamageListExpanded;
/// <summary>
/// Whether the Damge is shown as expanded in the inspector window of the editor.
/// </summary>
public bool showDamageInEditor;
// Aerodynamic properties
/// <summary>
/// The drag coefficients in the local x, y and z directions. Increasing the drag coefficients will increase drag.
/// </summary>
public Vector3 dragCoefficients;
/// <summary>
/// The projected areas (in square metres) of the ship in the local x, y and z planes.
/// </summary>
public Vector3 dragReferenceAreas;
/// <summary>
/// The centre of drag for moments causing rotation along the local x-axis.
/// </summary>
public Vector3 centreOfDragXMoment;
/// <summary>
/// The centre of drag for moments causing rotation along the local y-axis.
/// </summary>
public Vector3 centreOfDragYMoment;
/// <summary>
/// The centre of drag for moments causing rotation along the local z-axis.
/// </summary>
public Vector3 centreOfDragZMoment;
/// <summary>
/// Multiplier of the effect of angular drag. Only relevant when not in physics-based mode (when in this mode it is set to the physically realistic value of 1).
/// </summary>
[Range(0f, 4f)] public float angularDragFactor;
/// <summary>
/// When this is enabled, drag moments have no effect (i.e. drag cannot cause the ship to rotate). Only relevant when not in physics-based mode (when in this mode drag moments are always enabled).
/// </summary>
public bool disableDragMoments;
/// <summary>
/// A multiplier for drag moments causing rotation along the local (pitch) x-axis. Decreasing this will make these moments weaker.
/// </summary>
[Range(0f, 1f)] public float dragXMomentMultiplier;
/// <summary>
/// A multiplier for drag moments causing rotation along the local (yaw) y-axis. Decreasing this will make these moments weaker.
/// </summary>
[Range(0f, 1f)] public float dragYMomentMultiplier;
/// <summary>
/// A multiplier for drag moments causing rotation along the local (roll) z-axis. Decreasing this will make these moments weaker.
/// </summary>
[Range(0f, 1f)] public float dragZMomentMultiplier;
/// <summary>
/// How strong the effect of stalling is on wings (between 0 and 1). Higher values make the effect of stalling more prominent.
/// </summary>
[Range(0f, 1f)] public float wingStallEffect;
/// <summary>
/// Whether a braking component is enabled in arcade mode.
/// </summary>
public bool arcadeUseBrakeComponent;
/// <summary>
/// The strength of the brake force in arcade mode. Only relevant when not in physics-based mode and when
/// arcadeUseBrakeComponent is enabled.
/// </summary>
public float arcadeBrakeStrength;
/// <summary>
/// Whether the strength of the brake force ignores the density of the medium the ship is in (assuming it to be a constant
/// value of one kilogram per cubic metre). Only relevant when not in physics-based mode and when arcadeUseBrakeComponent
/// is enabled.
/// </summary>
public bool arcadeBrakeIgnoreMediumDensity;
/// <summary>
/// The minimum braking acceleration (in metres per second) caused by the brake when the brake is fully engaged. Increase this
/// value to make the ship come to a stop more quickly at low speeds. Only relevant when not in physics-based mode and when
/// arcadeUseBrakeComponent is enabled.
/// </summary>
public float arcadeBrakeMinAcceleration;
// Input modulation
/// <summary>
/// Strength of the rotational flight assist. Set to zero to disable.
/// </summary>
[Range(0f, 10f)] public float rotationalFlightAssistStrength;
/// <summary>
/// Strength of the translational flight assist. Set to zero to disable.
/// </summary>
[Range(0f, 10f)] public float translationalFlightAssistStrength;
/// <summary>
/// Strength of the stability flight assist. Set to zero to disable.
/// </summary>
[Range(0f, 10f)] public float stabilityFlightAssistStrength;
/// <summary>
/// Strength of the brake flight assist. Set to zero to disable [Default = 0]
/// Operates on Forward and Backward movements when there is no ship input.
/// Is overridden in forwards direction when AutoCruise is enabled on PlayerInputModule.
/// </summary>
[Range(0f, 10f)] public float brakeFlightAssistStrength;
/// <summary>
/// The effective minimum speed in m/s that the brake flight assist will operate
/// </summary>
[Range(-1000f, 1000f)] public float brakeFlightAssistMinSpeed;
/// <summary>
/// The effective maximum speed in m/s that the brake flight assist will operate
/// </summary>
[Range(-1000f, 1000f)] public float brakeFlightAssistMaxSpeed;
/// <summary>
/// How much power of the ship's roll thrusters is used to execute roll maneuvers.
/// Increasing this value will increase the roll speed and responsiveness of the ship.
/// </summary>
[Range(0f, 1f)] public float rollPower;
/// <summary>
/// How much power of the ship's roll thrusters is used to execute pitching maneuvers.
/// Increasing this value will increase the pitch speed and responsiveness of the ship.
/// </summary>
[Range(0f, 1f)] public float pitchPower;
/// <summary>
/// How much power of the ship's roll thrusters is used to execute yaw maneuvers.
/// Increasing this value will increase the yaw speed and responsiveness of the ship.
/// </summary>
[Range(0f, 1f)] public float yawPower;
/// <summary>
/// How much steering inputs are prioritised over lateral inputs for thrusters.
/// A value of 0 means no prioritisation takes place, while a value of 1 will almost completely deactivate relevant lateral
/// thrusters whenever any opposing steering input at all is applied.
/// </summary>
[Range(0f, 1f)] public float steeringThrusterPriorityLevel;
/// <summary>
/// Whether pitch and roll is limited within a certain range.
/// </summary>
public bool limitPitchAndRoll;
/// <summary>
/// The maximum allowed pitch (in degrees). Only relevant when limitPitchAndRoll is set to true.
/// </summary>
[Range(0f, 75f)] public float maxPitch;
/// <summary>
/// The maximum allowed roll (in degrees) when turning. Only relevant when limitPitchAndRoll is set to true.
/// </summary>
[Range(0f, 75f)] public float maxTurnRoll;
/// <summary>
/// How quickly the ship pitches (in degrees per second) when pitch and roll is limited to a certain range. Only relevant when limitPitchAndRoll is set to true.
/// </summary>
[Range(10f, 90f)] public float pitchSpeed;
/// <summary>
/// How quickly the ship rolls (in degrees per second) when pitch and roll is limited to a certain range. Only relevant when limitPitchAndRoll is set to true.
/// </summary>
[Range(10f, 90f)] public float turnRollSpeed;
/// <summary>
/// The mode currently being used for controlling roll. Only relevant when limitPitchAndRoll is set to true.
/// Yaw Input: The ship's roll is dependent on the yaw input.
/// Strafe Input: The ship's roll is dependent on the left-right strafe input.
/// </summary>
public RollControlMode rollControlMode;
/// <summary>
/// How quickly the ship pitches and rolls to match the target pitch and roll. Only relevant when limitPitchAndRoll is set to true.
/// </summary>
[Range(1f, 10f)] public float pitchRollMatchResponsiveness;
/// <summary>
/// Whether the ship will attempt to maintain a constant distance from the detectable ground surface.
/// </summary>
public bool stickToGroundSurface;
/// <summary>
/// Helps the ship to avoid crashing into the ground below it
/// </summary>
public bool avoidGroundSurface;
/// <summary>
/// Whether the ship will orient itself upwards when a ground surface is not detectable.
/// </summary>
public bool orientUpInAir;
/// <summary>
/// The distance from the ground the ship will attempt to maintain. Only relevant when stickToGroundSurface is set to true.
/// </summary>
public float targetGroundDistance;
/// <summary>
/// Whether the movement used for matching the Target Distance from the ground is smoothed. Enable this to prevent the ship
/// from accelerating too quickly when near the Target Distance. Only relevant when stickToGroundSurface is set to true.
/// If you modify this, call ReinitialiseGroundMatchVariables().
/// </summary>
public bool useGroundMatchSmoothing;
/// <summary>
/// Whether the ground match algorithm "looks ahead" to detect obstacles ahead of the ship.
/// Only relevant when stickToGroundSurface is set to true.
/// </summary>
public bool useGroundMatchLookAhead;
/// <summary>
/// The minimum distance from the ground the ship will attempt to maintain. Only relevant when stickToGroundSurface and
/// useGroundMatchSmoothing is set to true.
/// </summary>
public float minGroundDistance;
/// <summary>
/// The minimum distance from the ground the ship will attempt to maintain. Only relevant when stickToGroundSurface and
/// useGroundMatchSmoothing is set to true.
/// </summary>
public float maxGroundDistance;
/// <summary>
/// The maximum distance to check for the ground from the bottom of the ship via raycast.
/// Only relevant when stickToGroundSurface is set to true.
/// </summary>
[Range(0f, 100f)] public float maxGroundCheckDistance;
/// <summary>
/// How responsive the ship is to sudden changes in the distance to the ground.
/// Increasing this value will allow the ship to match the Target Distance more closely but may lead to a juddering effect
/// if increased too much. Only relevant when stickToGroundSurface is set to true.
/// If you modify this, call ReinitialiseGroundMatchVariables().
/// </summary>
[Range(1f, 100f)] public float groundMatchResponsiveness;
/// <summary>
/// How much the up/down motion of the ship is damped when attempting to match the Target Distance.
/// Increasing this value will reduce overshoot but may make the movement too rigid if increased too much.
/// Only relevant when stickToGroundSurface is set to true.
/// If you modify this, call ReinitialiseGroundMatchVariables().
/// </summary>
[Range(1f, 100f)] public float groundMatchDamping;
/// <summary>
/// The limit to how quickly the ship can accelerate to maintain the Target Distance to the ground.
/// Increasing this value will allow the ship to match the Target Distance more closely but may look less natural.
/// If you modify this, call ReinitialiseGroundMatchVariables().
/// </summary>
[Range(1f, 4f)] public float maxGroundMatchAccelerationFactor;
/// <summary>
/// The limit (when at the Target Distance from the ground) to how quickly the ship can accelerate to maintain the Target
/// Distance to the ground. Increasing this value will allow the ship to match the Target Distance more closely but may
/// look less natural. Only relevant when stickToGroundSurface and useGroundMatchSmoothing is set to true.
/// If you modify this, call ReinitialiseGroundMatchVariables().
/// </summary>
public float centralMaxGroundMatchAccelerationFactor;
/// <summary>
/// How the normal direction (orientation) is determined.
/// When Single Normal is selected, the single normal of each face of the ground geometry is used.
/// When Smoothed Normal is selected, the normals on each vertex of the face of the ground geometry are blended together to give a smoothed normal, which is used instead.
/// Smoothed Normal is more computationally expensive.
/// </summary>
public GroundNormalCalculationMode groundNormalCalculationMode;
/// <summary>
/// The number of past frames (including this frame) used to average ground normals over. Increase this value to make pitch and roll
/// movement smoother. Decrease this value to make pitch and roll movement more responsive to changes in the ground
/// surface. Only relevant when stickToGroundSurface is set to true.
/// If you modify this, call ReinitialiseGroundMatchVariables().
/// </summary>
[Range(1, 50)] public int groundNormalHistoryLength;
/// <summary>
/// Layermask determining which layers will be detected as part of the ground surface when raycasted against.
/// </summary>
public LayerMask groundLayerMask;
/// <summary>
/// The input axis to be controlled or limited. This is used to simulate 2.5D flight.
/// </summary>
public InputControlAxis inputControlAxis;
/// <summary>
/// The target value of the inputControlAxis.
/// </summary>
public float inputControlLimit;
/// <summary>
/// The rate at which force is applied to limit control
/// </summary>
[Range(0.1f, 100f)] public float inputControlMovingRigidness;
/// <summary>
/// The rate at which the ship turns to limit or correct the rotation
/// </summary>
[Range(0.1f, 100f)] public float inputControlTurningRigidness;
/// <summary>
/// Forward X angle for the plane the ship will fly in
/// </summary>
[Range(0f, 180f)] public float inputControlForwardAngle;
// Arcade physics properties
/// <summary>
/// How fast the ship accelerates when pitching up and down in degrees per second squared.
/// Increasing this value will increase how fast the ship can be pitched up and down by pilot and rotational flight assist inputs.
/// </summary>
[Range(0f, 500f)] public float arcadePitchAcceleration;
/// <summary>
/// How fast the ship accelerates when turning left and right in degrees per second squared.
/// Increasing this value will increase how fast the ship can be turned left and right by pilot and rotational flight assist inputs.
/// </summary>
[Range(0f, 500f)] public float arcadeYawAcceleration;
/// <summary>
/// How fast the ship accelerates when rolling left and right in degrees per second squared.
/// Increasing this value will increase how fast the ship can be rolled left and right by pilot and rotational flight assist inputs.
/// </summary>
[Range(0f, 500f)] public float arcadeRollAcceleration;
/// <summary>
/// How quickly the ship accelerates in metres per second squared while in the air to move in the direction the ship is facing.
/// </summary>
[Range(0f, 1000f)] public float arcadeMaxFlightTurningAcceleration;
/// <summary>
/// How quickly the ship accelerates in metres per second squared while near the ground to move in the direction the ship is facing.
/// </summary>
[Range(0f, 1000f)] public float arcadeMaxGroundTurningAcceleration;
/// <summary>
/// [INTERNAL USE ONLY] shouldn't be called directly, call SendInput in the ship control module instead
/// </summary>
public Vector3 pilotForceInput { private get; set; }
/// <summary>
/// [INTERNAL USE ONLY] shouldn't be called directly, call SendInput in the ship control module instead
/// </summary>
public Vector3 pilotMomentInput { private get; set; }
/// <summary>
/// [INTERNAL USE ONLY] shouldn't be called directly, call SendInput in the ship control module instead
/// </summary>
public bool pilotPrimaryFireInput { private get; set; }
/// <summary>
/// [INTERNAL USE ONLY] shouldn't be called directly, call SendInput in the ship control module instead
/// </summary>
public bool pilotSecondaryFireInput { private get; set; }
/// <summary>
/// The density (in kilograms per cubic metre) of the fluid the ship is travelling through (usually air).
/// </summary>
public float mediumDensity;
/// <summary>
/// The magnitude of the acceleration (in metres per second squared) due to gravity.
/// </summary>
public float gravitationalAcceleration;
/// <summary>
/// The direction in world space in which gravity acts upon the ship.
/// </summary>
public Vector3 gravityDirection;
// TODO: Update comment when adding more damage models
/// <summary>
/// What damage model the ship uses.
/// Simple: Simplistic damage model with a single health value. Effects of damage are only visual until the ship is destroyed.
/// Progressive: More complex damage model. As the ship takes damage the performance of parts is affected.
/// Localised: More complex damage model. Similar to progressive, but different parts can be damaged independently of each other.
/// If you modify this, call ReinitialiseDamageRegionVariables().
/// </summary>
public ShipDamageModel shipDamageModel;
/// <summary>
/// Damage Region used in the simple and progressive damage models.
/// If you modify this, call ReinitialiseDamageRegionVariables().
/// NOTE: Not required if just changing the Health or ShieldHealth
/// </summary>
public DamageRegion mainDamageRegion;
/// <summary>
/// List of Damage Regions used in the localised damage model.
/// If you modify this, call ReinitialiseDamageRegionVariables().
/// NOTE: Not required if just changing the Health or ShieldHealth
/// </summary>
public List<DamageRegion> localisedDamageRegionList;
/// <summary>
/// Whether damage multipliers are used when calculating damage from projectiles.
/// </summary>
public bool useDamageMultipliers;
/// <summary>
/// Whether damage multipliers are localised (i.e. there are different sets of damage multipliers for each damage region
/// of the ship). Only relevant when useDamageMultipliers is set to true and shipDamageModel is set to Localised.
/// </summary>
public bool useLocalisedDamageMultipliers;
/// <summary>
/// [READONLY]
/// Normalised (0.0 1.0) value of the overall health of the ship. To get the actual health values, see the damageRegions of the ship.
/// </summary>
public float HealthNormalised
{
get
{
float _healthN = mainDamageRegion.startingHealth == 0f ? 0f : mainDamageRegion.Health / mainDamageRegion.startingHealth;
if (_healthN > 1f) { return 1f; }
else if (_healthN < 0f) { return 0f; }
else { return _healthN; }
}
}
/// <summary>
/// How respawning happens. If you change this to RespawningMode.RespawnAtLastPosition, call ReinitialiseRespawnVariables().
/// </summary>
public RespawningMode respawningMode;
/// <summary>
/// How long the respawning process takes (in seconds). Only relevant when respawningMode is not set to DontRespawn.
/// </summary>
public float respawnTime;
/// <summary>
/// The time (in seconds) between updates of the collision respawn position. Hence when the ship is destroyed by colliding with
/// something, the ship respawn position will be where the ship was between this time ago and twice this time ago. Only
/// relevant when respawningMode is set to RespawnAtLastPosition.
/// </summary>
public float collisionRespawnPositionDelay;
/// <summary>
/// Where the ship respawns from in world space when respawningMode is set to RespawnFromSpecifiedPosition.
/// </summary>
public Vector3 customRespawnPosition;
/// <summary>
/// The rotation the ship respawns from in world space when respawningMode is set to RespawnFromSpecifiedPosition.
/// </summary>
public Vector3 customRespawnRotation;
/// <summary>
/// The velocity the ship respawns with in local space. Only relevant when respawningMode is not set to DontRespawn.
/// </summary>
public Vector3 respawnVelocity;
/// <summary>
/// guidHash code of the Path which the ship will be respawned on when respawningMode is RespawnOnPath.
/// </summary>
public int respawningPathGUIDHash;
/// <summary>
/// [READONLY] The current respawn position
/// </summary>
public Vector3 RespawnPosition { get { return currentRespawnPosition; } }
/// <summary>
/// Whether controller rumble is applied to the ship by the ship control module.
/// </summary>
public bool applyControllerRumble = false;
/// <summary>
/// The minimum amount of damage that will cause controller rumble.
/// </summary>
public float minRumbleDamage = 0f;
/// <summary>
/// The amount of damage corresponding to maximum controller rumble.
/// </summary>
public float maxRumbleDamage = 10f;
/// <summary>
/// The time (in seconds) that a controller rumble event lasts for.
/// </summary>
[Range(0.1f, 5f)] public float damageRumbleTime = 0.5f;
/// <summary>
/// The amount of time that needs to elapse before a stationary ship is considered stuck.
/// When the value is 0, a stationary ship is never considered stuck.
/// </summary>
[Range(0f, 300)] public float stuckTime;
/// <summary>
/// The maximum speed in m/sec the ship can be moving before it can be considered stuck
/// </summary>
[Range(0f, 1f)] public float stuckSpeedThreshold;
/// <summary>
/// The action to take when the ship is deemed stationary or stuck.
/// </summary>
public StuckAction stuckAction;
/// <summary>
/// guidHash code of the Path which the ship will be respawned on when it is stuck
/// and the StuckAction is RespawnOnPath.
/// </summary>
public int stuckActionPathGUIDHash;
/// <summary>
/// [READONLY] The world velocity of the ship as a vector. Derived from the velocity of the rigidbody.
/// </summary>
public Vector3 WorldVelocity { get { return worldVelocity; } }
/// <summary>
/// [READONLY] The local velocity of the ship as a vector. Derived from the velocity of the rigidbody.
/// </summary>
public Vector3 LocalVelocity { get { return localVelocity; } }
/// <summary>
/// [READONLY] The world angular velocity of the ship as a vector. Derived from the angular velocity of the rigidbody.
/// </summary>
public Vector3 WorldAngularVelocity { get { return worldAngularVelocity; } }
/// <summary>
/// [READONLY] The local angular velocity of the ship as a vector. Derived from the angular velocity of the rigidbody.
/// </summary>
public Vector3 LocalAngularVelocity { get { return localAngularVelocity; } }
/// <summary>
/// [READONLY] The pitch angle, in degrees, above or below the artificial horizon
/// </summary>
public float PitchAngle { get { return Vector3.SignedAngle(new Vector3(trfmFwd.x, 0f, trfmFwd.z), trfmFwd, Vector3.right) * Mathf.Sign(-Vector3.Dot(Vector3.forward, trfmFwd)); } }
/// <summary>
/// [READONLY] The roll angle, in degrees, left (-ve) or right (+ve)
/// </summary>
public float RollAngle { get { Vector3 _localUpNormal = trfmInvRot * Vector3.up; return -Mathf.Atan2(_localUpNormal.x, _localUpNormal.y) * Mathf.Rad2Deg; } }
/// <summary>
/// [READONLY] The rigidbody position of the ship as a vector. Derived from the position of the rigidbody. This is
/// where the physics engine says the ship is.
/// </summary>
public Vector3 RigidbodyPosition { get { return rbodyPos; } }
/// <summary>
/// [READONLY] The rigidbody rotation of the ship as a vector. Derived from the rotation of the rigidbody. This is
/// where the physics engine says the ship is rotated.
/// </summary>
public Quaternion RigidbodyRotation { get { return rbodyRot; } }
/// <summary>
/// [READONLY] The rigidbody inverse rotation of the ship as a vector. Derived from the rotation of the rigidbody. This is
/// the inverse of where the physics engine says the ship is rotated.
/// </summary>
public Quaternion RigidbodyInverseRotation { get { return rbodyInvRot; } }
/// <summary>
/// [READONLY] The rigidbody forward direction of the ship as a vector. Derived from the rotation of the rigidbody. This is
/// the direction the physics engine says the ship is facing.
/// </summary>
public Vector3 RigidbodyForward { get { return rbodyFwd; } }
/// <summary>
/// [READONLY] The rigidbody right direction of the ship as a vector. Derived from the rotation of the rigidbody. This is
/// the direction the physics engine says the ship's right direction is facing.
/// </summary>
public Vector3 RigidbodyRight { get { return rbodyRight; } }
/// <summary>
/// [READONLY] The rigidbody up direction of the ship as a vector. Derived from the rotation of the rigidbody. This is
/// the direction the physics engine says the ship's up direction is facing.
/// </summary>
public Vector3 RigidbodyUp { get { return rbodyUp; } }
/// <summary>
/// [READONLY] The position of the ship as a vector. Derived from the position of the transform. You should use
/// RigidbodyPosition instead if you are using the data for physics calculations.
/// </summary>
public Vector3 TransformPosition { get { return trfmPos; } }
/// <summary>
/// [READONLY] The forward direction of the ship in world space as a vector. Derived from the forward direction of the transform.
/// You should use RigidbodyForward instead if you are using the data for physics calculations.
/// </summary>
public Vector3 TransformForward { get { return trfmFwd; } }
/// <summary>
/// [READONLY] The right direction of the ship in world space as a vector. Derived from the right direction of the transform.
/// You should use RigidbodyRight instead if you are using the data for physics calculations.
/// </summary>
public Vector3 TransformRight { get { return trfmRight; } }
/// <summary>
/// [READONLY] The up direction of the ship in world space as a vector. Derived from the up direction of the transform.
/// You should use RigidbodyUp instead if you are using the data for physics calculations.
/// </summary>
public Vector3 TransformUp { get { return trfmUp; } }
/// <summary>
/// [READONLY] The rotation of the ship in world space as a quaternion. Derived from the rotation of the transform.
/// You should use RigidbodyRotation instead if you are using the data for physics calculations.
/// </summary>
public Quaternion TransformRotation { get { return trfmRot; } }
/// <summary>
/// [READONLY] The inverse rotation of the ship in world space as a quaternion. Derived from the rotation of the transform.
/// You should use RigidbodyInverseRotation instead if you are using the data for physics calculations.
/// </summary>
public Quaternion TransformInverseRotation { get { return trfmInvRot; } }
/// <summary>
/// [READONLY] Whether the ship is currently sticking to a ground surface.
/// </summary>
public bool IsGrounded { get { return stickToGroundSurfaceEnabled; } }
/// <summary>
/// [READONLY] The current normal of the target plane in world space. This is the upwards direction the ship will attempt to
/// orient itself to if limit pitch and roll is enabled.
/// </summary>
public Vector3 WorldTargetPlaneNormal { get { return worldTargetPlaneNormal; } }
/// <summary>
/// The faction or alliance the ship belongs to. This can be used to identify if a ship is friend or foe.
/// Default (neutral) is 0.
/// </summary>
public int factionId;
/// <summary>
/// The squadron this ship is a member of.
/// </summary>
public int squadronId;
/// <summary>
/// The relative size of the blip on the radar mini-map
/// </summary>
[Range(1, 5)] public byte radarBlipSize;
/// <summary>
/// [INTERNAL USE ONLY] Instead, call shipControlModule.EnableRadar() or DisableRadar().
/// </summary>
public bool isRadarEnabled;
/// <summary>
/// [READONLY] The number used by the SSCRadar system to identify this ship at a point in time.
/// This should not be stored across frames and is updated as required by the system.
/// </summary>
public int RadarId { get { return radarItemIndex; } }
/// <summary>
/// [INTERNAL USE ONLY]
/// </summary>
[System.NonSerialized] public SSCRadarPacket sscRadarPacket;
/// <summary>
/// Session-only transform InstanceID
/// </summary>
[System.NonSerialized] public int shipId;
/// <summary>
/// Session-only estimated maximum range of turret weapons
/// </summary>
[System.NonSerialized] public float estimatedMaxTurretRange;
/// <summary>
/// Session-only estimated maximum range of all weapons with isAutoTargetingEnabled = true
/// </summary>
[System.NonSerialized] public float estimatedMaxAutoTargetRange;
#endregion
#region Public Delegates
public delegate void CallbackOnDamage(float mainDamageRegionHealth);
public delegate void CallbackOnCameraShake(float shakeAmountNormalised);
public delegate void CallbackOnWeaponFired(Ship ship, Weapon weapon);
public delegate void CallbackOnWeaponNoAmmo(Ship ship, Weapon weapon);
/// <summary>
/// The name of the custom method that is called immediately
/// after damage has changed. Your method must take 1 float
/// parameter. This should be a lightweight method to avoid
/// performance issues. It could be used to update a HUD or
/// take some other action.
/// </summary>
[NonSerialized] public CallbackOnDamage callbackOnDamage = null;
/// <summary>
/// Generally reserved for internal use by ShipCameraModule. If you use your own
/// camera scripts, you can create a lightweight custom method and assign it at
/// runtime so that this is called whenever camera shake data is available.
/// </summary>
[NonSerialized] public CallbackOnCameraShake callbackOnCameraShake = null;
/// <summary>
/// The name of the custom method that is called immediately after
/// a weapon has fired.
/// This should be a lightweight method to avoid performance issues that
/// doesn't hold references the ship or weapon past the end of the current frame.
/// </summary>
[NonSerialized] public CallbackOnWeaponFired callbackOnWeaponFired = null;
/// <summary>
/// The name of the custom method that is called immediately after the weapon
/// runs out of ammunition. Your method must take a ship and weapon parameter.
/// This should be a lightweight method to avoid performance issues that
/// doesn't hold references the ship or weapon past the end of the current frame.
/// </summary>
[NonSerialized] public CallbackOnWeaponNoAmmo callbackOnWeaponNoAmmo = null;
#endregion
#region Private and Internal Variables
#region Private Variables - Cached
private Vector3 worldVelocity;
private Vector3 localVelocity;
private Vector3 absLocalVelocity;
private Vector3 worldAngularVelocity;
private Vector3 localAngularVelocity;
private Vector3 trfmUp;
private Vector3 trfmFwd;
private Vector3 trfmRight;
private Vector3 trfmPos;
private Vector3 rbodyPos;
private Vector3 rbodyUp;
private Vector3 rbodyFwd;
private Vector3 rbodyRight;
private Quaternion trfmRot;
private Quaternion trfmInvRot;
private Quaternion rbodyRot;
private Quaternion rbodyInvRot;
private Vector3 rBodyInertiaTensor;
private bool shipPhysicsModelPhysicsBased;
private bool shipDamageModelIsSimple;
#endregion
#region Private Variables - Timing related
private float lastFixedUpdateTime = 0f;
private Vector3 previousFrameWorldVelocity = Vector3.zero;
internal float thrusterSystemStartupTimer = 0f;
internal float thrusterSystemShutdownTimer = 0f;
#endregion
#region Private Variables - Cached input
// Is each input being used on the ship?
private bool longitudinalThrustInputUsed;
private bool horizontalThrustInputUsed;
private bool verticalThrustInputUsed;
private bool pitchMomentInputUsed;
private bool yawMomentInputUsed;
private bool rollMomentInputUsed;
#endregion
#region Private Variables - Input
private bool limitPitchAndRollEnabled = false;
private bool stickToGroundSurfaceEnabled = false;
private Vector3 forceInput = Vector3.zero;
private Vector3 momentInput = Vector3.zero;
private float pitchAmountInput = 0f, rollAmountInput = 0f;
private float pitchAmount = 0f, rollAmount = 0f;
private float maxFramePitchDelta, maxFrameRollDelta;
private float targetLocalPitch, targetLocalRoll;
private float yawVelocity;
private float flightAssistValue;
#endregion
#region Private Variables - Thruster
private Thruster thruster;
private Vector3 thrustForce = Vector3.zero;
#endregion
#region Private Variables - Aerodynamics
private float dragLength, dragWidth, dragHeight;
private float quarticLengthXProj, quarticLengthYProj;
private float quarticWidthYProj, quarticWidthZProj;
private float quarticHeightXProj, quarticHeightZProj;
private Vector3 localDragForce;
private Vector3 localLiftForce;
private Vector3 localInducedDragForce;
private float liftForceMagnitude;
private Wing wing;
private Vector3 wingPlaneNormal = Vector3.right;
private Vector3 localVeloInWingPlane = Vector3.zero;
private float wingAngleOfAttack = 0f;
private float angularDragMultiplier;
private ControlSurface controlSurface;
private float aerodynamicTrueAirspeed;
private float liftCoefficient = 0f, phi0, phi1, phi2, phi3;
private Vector3 controlSurfaceMovementAxis = Vector3.up;
private float controlSurfaceInput = 0f, controlSurfaceAngle;
private float controlSurfaceLiftDelta, controlSurfaceDragDelta;
private Vector3 controlSurfaceRelativePosition;
#endregion
#region Private Variables - Gravity
private Vector3 localGravityAcceleration;
#endregion
#region Private Variables - Arcade physics
/// <summary>
/// Note that this is actually an acceleration.
/// </summary>
private Vector3 arcadeMomentVector = Vector3.zero;
/// <summary>
/// Note that this is actually an acceleration.
/// </summary>
private Vector3 arcadeForceVector = Vector3.zero;
private float arcadeBrakeForceMagnitude = 0f;
private float arcadeBrakeForceMinMagnitude = 0f;
private float maxGroundMatchAcceleration = 0f;
private float centralMaxGroundMatchAcceleration = 0f;
// Temp ground distance input required
private float groundDistInput = 0f;
#endregion
#region Private Variables - Combat variables
private Weapon weapon;
private Vector3 weaponRelativeFirePosition = Vector3.zero;
private Vector3 weaponRelativeFireDirection = Vector3.zero;
private Vector3 weaponWorldBasePosition = Vector3.zero;
private Vector3 weaponWorldFirePosition = Vector3.zero;
private Vector3 weaponWorldFireDirection = Vector3.zero;
private Vector3 currentRespawnPosition = Vector3.zero;
private Quaternion currentRespawnRotation = Quaternion.identity;
private Vector3 currentCollisionRespawnPosition = Vector3.zero;
private Vector3 nextCollisionRespawnPosition = Vector3.zero;
private Quaternion currentCollisionRespawnRotation = Quaternion.identity;
private Quaternion nextCollisionRespawnRotation = Quaternion.identity;
private float collisionRespawnPositionTimer = 0f;
private bool lastDamageWasCollisionDamage = false;
private int weaponFiringButtonInt;
private int weaponPrimaryFiringInt, weaponSecondaryFiringInt, weaponAutoFiringInt;
private int weaponTypeInt;
private int lastDamageEventIndex = -1;
private float damageRumbleAmountRequired = 0f;
private float damageCameraShakeAmountRequired = 0f;
// Damage Regions
[System.NonSerialized] internal int numLocalisedDamageRegions;
// Radar variables
[System.NonSerialized] internal int radarItemIndex = -1;
#endregion
#region Private Variables - Collision
/// <summary>
/// Unordered unique set of colliders on objects that have been attached to this ship.
/// These are ignored during collision events
/// </summary>
[NonSerialized] private HashSet<int> attachedCollisionColliders = new HashSet<int>();
#endregion
#region Private Variables - PID Controllers
private PIDController pitchPIDController;
private PIDController rollPIDController;
private PIDController groundDistPIDController;
private PIDController rFlightAssistPitchPIDController;
private PIDController rFlightAssistYawPIDController;
private PIDController rFlightAssistRollPIDController;
private PIDController inputAxisForcePIDController;
private PIDController inputAxisMomentPIDController;
private PIDController sFlightAssistPitchPIDController;
private PIDController sFlightAssistYawPIDController;
private PIDController sFlightAssistRollPIDController;
private float sFlightAssistTargetPitch = 0f;
private float sFlightAssistTargetYaw = 0f;
private float sFlightAssistTargetRoll = 0f;
private bool sFlightAssistRotatingPitch = false;
private bool sFlightAssistRotatingYaw = false;
private bool sFlightAssistRotatingRoll = false;
#endregion
#region Private Variables - Normals
private Vector3 worldTargetPlaneNormal = Vector3.up;
private Vector3 localTargetPlaneNormal = Vector3.up;
private float currentPerpendicularGroundDist = -1f;
private MeshCollider groundMeshCollider;
private MeshCollider cachedGroundMeshCollider;
private Mesh cachedGroundMesh;
private Vector3[] cachedGroundMeshNormals;
private int[] cachedGroundMeshTriangles;
private Vector3 groundNormal0;
private Vector3 groundNormal1;
private Vector3 groundNormal2;
private Vector3 barycentricCoord;
private Vector3[] groundNormalHistory;
private int groundNormalHistoryIdx = 0;
#endregion
#region Private and Internal Variables - Misc
private int componentIndex;
private int componentListSize;
private Ray raycastRay;
private RaycastHit raycastHitInfo;
/// <summary>
/// [INTERNAL ONLY]
/// Added in 1.3.3 to support muzzle FX parenting.
/// MAY NEED TO BE REMOVED IN THE FUTURE.
/// WARNING: Not fully tested with destroying ships
/// in the scene. Need to be careful it doesn't prevent
/// garabage collection of ShipControlModule.
/// Where possible - use the cached trfmXXX itema above.
/// </summary>
[System.NonSerialized] internal Transform shipTransform;
private SSCManager sscManager = null;
// Reusable lists - typically used with GetComponents or GetComponentsInChildren to avoid GC.
//private List<ShipControlModule> tempShipControlModuleList;
// Add additional force or boost
private Vector3 boostDirection;
private float boostTimer;
private float boostForce; // in newtons
#endregion
#endregion
#region Constructors
// Class constructor
public Ship()
{
SetClassDefaults();
}
// Copy constructor
public Ship (Ship ship)
{
if (ship == null) { SetClassDefaults(); }
else
{
this.initialiseOnAwake = ship.initialiseOnAwake;
this.shipPhysicsModel = ship.shipPhysicsModel;
this.mass = ship.mass;
this.centreOfMass = ship.centreOfMass;
this.setCentreOfMassManually = ship.setCentreOfMassManually;
this.showCOMGizmosInSceneView = ship.showCOMGizmosInSceneView;
this.showCOLGizmosInSceneView = ship.showCOLGizmosInSceneView;
this.showCOTGizmosInSceneView = ship.showCOTGizmosInSceneView;
if (ship.thrusterList == null)
{
this.thrusterList = new List<Thruster>(25);
// Create a default forwards-facing thruster
Thruster thruster = new Thruster();
if (thruster != null)
{
thruster.name = "Forward Thruster";
thruster.forceUse = 1;
thruster.primaryMomentUse = 0;
thruster.secondaryMomentUse = 0;
thruster.maxThrust = 100000;
thruster.thrustDirection = Vector3.forward;
thruster.relativePosition = Vector3.zero;
this.thrusterList.Add(thruster);
}
}
else { this.thrusterList = ship.thrusterList.ConvertAll(th => new Thruster(th)); }
if (ship.wingList == null) { this.wingList = new List<Wing>(5); }
else { this.wingList = ship.wingList.ConvertAll(wg => new Wing(wg)); }
if (ship.controlSurfaceList == null) { this.controlSurfaceList = new List<ControlSurface>(10); }
else { this.controlSurfaceList = ship.controlSurfaceList.ConvertAll(cs => new ControlSurface(cs)); }
if (ship.weaponList == null) { this.weaponList = new List<Weapon>(10); }
else { this.weaponList = ship.weaponList.ConvertAll(wp => new Weapon(wp)); }
this.isThrusterSystemsExpanded = ship.isThrusterSystemsExpanded;
this.isThrusterListExpanded = ship.isThrusterListExpanded;
this.isWingListExpanded = ship.isWingListExpanded;
this.isControlSurfaceListExpanded = ship.isControlSurfaceListExpanded;
this.isWeaponListExpanded = ship.isWeaponListExpanded;
this.isMainDamageExpanded = ship.isMainDamageExpanded;
this.isDamageListExpanded = ship.isDamageListExpanded;
this.showDamageInEditor = ship.showDamageInEditor;
this.isThrusterFXStationary = ship.isThrusterFXStationary;
this.isThrusterSystemsStarted = ship.isThrusterSystemsStarted;
this.thrusterSystemStartupDuration = ship.thrusterSystemStartupDuration;
this.thrusterSystemShutdownDuration = ship.thrusterSystemShutdownDuration;
this.useCentralFuel = ship.useCentralFuel;
this.centralFuelLevel = ship.centralFuelLevel;
this.dragCoefficients = ship.dragCoefficients;
this.dragReferenceAreas = ship.dragReferenceAreas;
this.centreOfDragXMoment = ship.centreOfDragXMoment;
this.centreOfDragYMoment = ship.centreOfDragYMoment;
this.centreOfDragZMoment = ship.centreOfDragZMoment;
this.angularDragFactor = ship.angularDragFactor;
this.disableDragMoments = ship.disableDragMoments;
this.dragXMomentMultiplier = ship.dragXMomentMultiplier;
this.dragYMomentMultiplier = ship.dragYMomentMultiplier;
this.dragZMomentMultiplier = ship.dragZMomentMultiplier;
this.wingStallEffect = ship.wingStallEffect;
this.arcadeUseBrakeComponent = ship.arcadeUseBrakeComponent;
this.arcadeBrakeStrength = ship.arcadeBrakeStrength;
this.arcadeBrakeIgnoreMediumDensity = ship.arcadeBrakeIgnoreMediumDensity;
this.arcadeBrakeMinAcceleration = ship.arcadeBrakeMinAcceleration;
this.rotationalFlightAssistStrength = ship.rotationalFlightAssistStrength;
this.translationalFlightAssistStrength = ship.translationalFlightAssistStrength;
this.brakeFlightAssistStrength = ship.brakeFlightAssistStrength;
this.brakeFlightAssistMaxSpeed = ship.brakeFlightAssistMinSpeed;
this.brakeFlightAssistMaxSpeed = ship.brakeFlightAssistMaxSpeed;
this.limitPitchAndRoll = ship.limitPitchAndRoll;
this.maxPitch = ship.maxPitch;
this.maxTurnRoll = ship.maxTurnRoll;
this.pitchSpeed = ship.pitchSpeed;
this.turnRollSpeed = ship.turnRollSpeed;
this.rollControlMode = ship.rollControlMode;
this.pitchRollMatchResponsiveness = ship.pitchRollMatchResponsiveness;
this.stickToGroundSurface = ship.stickToGroundSurface;
this.avoidGroundSurface = ship.avoidGroundSurface;
this.useGroundMatchSmoothing = ship.useGroundMatchSmoothing;
this.useGroundMatchLookAhead = ship.useGroundMatchLookAhead;
this.orientUpInAir = ship.orientUpInAir;
this.targetGroundDistance = ship.targetGroundDistance;
this.minGroundDistance = ship.minGroundDistance;
this.maxGroundDistance = ship.maxGroundDistance;
this.maxGroundCheckDistance = ship.maxGroundCheckDistance;
this.groundMatchResponsiveness = ship.groundMatchResponsiveness;
this.groundMatchDamping = ship.groundMatchDamping;
this.maxGroundMatchAccelerationFactor = ship.maxGroundMatchAccelerationFactor;
this.centralMaxGroundMatchAccelerationFactor = ship.centralMaxGroundMatchAccelerationFactor;
this.groundNormalCalculationMode = ship.groundNormalCalculationMode;
this.groundNormalHistoryLength = ship.groundNormalHistoryLength;
this.groundLayerMask = ship.groundLayerMask;
this.inputControlAxis = ship.inputControlAxis;
this.inputControlLimit = ship.inputControlLimit;
this.inputControlForwardAngle = ship.inputControlForwardAngle;
this.inputControlMovingRigidness = ship.inputControlMovingRigidness;
this.inputControlTurningRigidness = ship.inputControlTurningRigidness;
this.rollPower = ship.rollPower;
this.pitchPower = ship.pitchPower;
this.yawPower = ship.yawPower;
this.steeringThrusterPriorityLevel = ship.steeringThrusterPriorityLevel;
this.pilotMomentInput = ship.pilotMomentInput;
this.pilotForceInput = ship.pilotForceInput;
this.arcadePitchAcceleration = ship.arcadePitchAcceleration;
this.arcadeYawAcceleration = ship.arcadeYawAcceleration;
this.arcadeRollAcceleration = ship.arcadeRollAcceleration;
this.arcadeMaxFlightTurningAcceleration = ship.arcadeMaxFlightTurningAcceleration;
this.arcadeMaxGroundTurningAcceleration = ship.arcadeMaxGroundTurningAcceleration;
this.mediumDensity = ship.mediumDensity;
this.gravitationalAcceleration = ship.gravitationalAcceleration;
this.gravityDirection = ship.gravityDirection;
this.shipDamageModel = ship.shipDamageModel;
if (ship.mainDamageRegion == null)
{
this.mainDamageRegion = new DamageRegion();
this.mainDamageRegion.name = "Main Damage Region";
}
else { this.mainDamageRegion = new DamageRegion(ship.mainDamageRegion); }
if (ship.localisedDamageRegionList == null) { this.localisedDamageRegionList = new List<DamageRegion>(10); }
else { this.localisedDamageRegionList = ship.localisedDamageRegionList.ConvertAll(dr => new DamageRegion(dr)); }
this.useDamageMultipliers = ship.useDamageMultipliers;
this.useLocalisedDamageMultipliers = ship.useLocalisedDamageMultipliers;
this.respawnTime = ship.respawnTime;
this.collisionRespawnPositionDelay = ship.collisionRespawnPositionDelay;
this.respawningMode = ship.respawningMode;
this.customRespawnPosition = ship.customRespawnPosition;
this.customRespawnRotation = ship.customRespawnRotation;
this.respawnVelocity = ship.respawnVelocity;
this.respawningPathGUIDHash = ship.respawningPathGUIDHash;
this.minRumbleDamage = ship.minRumbleDamage;
this.maxRumbleDamage = ship.maxRumbleDamage;
this.stuckTime = ship.stuckTime;
this.stuckSpeedThreshold = ship.stuckSpeedThreshold;
this.stuckAction = ship.stuckAction;
this.stuckActionPathGUIDHash = ship.stuckActionPathGUIDHash;
this.factionId = ship.factionId;
this.squadronId = ship.squadronId;
this.isRadarEnabled = ship.isRadarEnabled;
this.radarBlipSize = ship.radarBlipSize;
}
}
#endregion
#region Private and Internal Non-Static Methods
/// <summary>
/// [INTERNAL ONLY]
/// Check if the thruster systems are starting up or shutting down.
/// If the state has changed in this frame, like has started or shutdown, the method will return true.
/// </summary>
/// <param name="deltaTime"></param>
/// <param name="hasShutdown"></param>
/// <param name="hasStarted"></param>
/// <returns></returns>
internal bool CheckThrusterSystems(float deltaTime, out bool hasStarted, out bool hasShutdown)
{
bool hasStateChanged = false;
hasShutdown = false;
hasStarted = false;
// Is the system shutting down?
if (thrusterSystemShutdownTimer > 0f)
{
thrusterSystemShutdownTimer += deltaTime;
//Debug.Log("[DEBUG] Thruster System shutting down: " + thrusterSystemShutdownTimer);
if (thrusterSystemShutdownTimer > thrusterSystemShutdownDuration)
{
ShutdownThrusterSystems(true);
hasStateChanged = true;
hasShutdown = true;
}
else
{
SetThrottleAllThrusters(1f - thrusterSystemShutdownTimer / thrusterSystemShutdownDuration);
}
}
else if (thrusterSystemStartupTimer > 0f)
{
thrusterSystemStartupTimer += deltaTime;
//Debug.Log("[DEBUG] Thruster System starting up: " + thrusterSystemStartupTimer);
if (thrusterSystemStartupTimer > thrusterSystemStartupDuration)
{
StartupThrusterSystems(true);
hasStateChanged = true;
hasStarted = true;
}
else
{
SetThrottleAllThrusters(thrusterSystemStartupTimer / thrusterSystemStartupDuration);
}
}
return hasStateChanged;
}
/// <summary>
/// Reset the thruster systems variables and thruster throttle values.
/// The systems might have been starting or shutting down when the ship
/// was destroyed.
/// Typically used when a ship is respawned.
/// </summary>
internal void ResetThrusterSystems()
{
// If the systems are starting or shutting down, reset all the
// thruster throttle values.
if (thrusterSystemStartupTimer > 0f || thrusterSystemShutdownTimer > 0f)
{
SetThrottleAllThrusters(isThrusterSystemsStarted ? 1f : 0f);
}
thrusterSystemShutdownTimer = 0f;
thrusterSystemStartupTimer = 0f;
}
/// <summary>
/// Returns the angle between two vectors projected into a specified plane, with a sign indicating the direction.
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <param name="planeNormal"></param>
/// <returns></returns>
private float SignedAngleInPlane(Vector3 from, Vector3 to, Vector3 planeNormal)
{
return Vector3.SignedAngle(Vector3.ProjectOnPlane(from, planeNormal), Vector3.ProjectOnPlane(to, planeNormal), planeNormal);
}
/// <summary>
/// Returns a float number raised to an integer power.
/// </summary>
/// <param name="baseNum"></param>
/// <param name="indexNum"></param>
/// <returns></returns>
private float IntegerPower(float baseNum, int powerNum)
{
// Start power calculation at power of zero instead of power of one, so that anything to the power of zero is one
float resultNum = 1f;
// Iteratively mutiply by the base number
for (int i = 0; i < powerNum; i++) { resultNum *= baseNum; }
return resultNum;
}
/// <summary>
/// Returns the damped (averaged) normal.
/// </summary>
/// <param name="currentNormal"></param>
/// <returns></returns>
private Vector3 GetDampedGroundNormal()
{
Vector3 dampedNormal = Vector3.zero;
// Loop through ground normal
for (int hIdx = 0; hIdx < groundNormalHistoryLength; hIdx++)
{
// Optimise by setting each axis rather than creating new vectors
dampedNormal.x += groundNormalHistory[hIdx].x;
dampedNormal.y += groundNormalHistory[hIdx].y;
dampedNormal.z += groundNormalHistory[hIdx].z;
}
dampedNormal.x *= 1f / groundNormalHistoryLength;
dampedNormal.y *= 1f / groundNormalHistoryLength;
dampedNormal.z *= 1f / groundNormalHistoryLength;
return dampedNormal.normalized;
}
/// <summary>
/// Update the normal history.
/// </summary>
/// <param name="currentNormal"></param>
private void UpdateGroundNormalHistory(Vector3 currentNormal)
{
// Update the current element of the history with the current normal
if (groundNormalHistoryLength > groundNormalHistoryIdx) { groundNormalHistory[groundNormalHistoryIdx] = currentNormal; }
// Keeps track of which array position to replace. Avoids having to shuffle elements
groundNormalHistoryIdx++;
if (groundNormalHistoryIdx >= groundNormalHistoryLength) { groundNormalHistoryIdx = 0; }
}
/// <summary>
/// Returns a damped max acceleration input.
/// </summary>
/// <param name="currentDisplacement"></param>
/// <param name="targetDisplacement"></param>
/// <param name="minDisplacement"></param>
/// <param name="maxDisplacement"></param>
/// <param name="minAcceleration"></param>
/// <param name="maxAcceleration"></param>
/// <param name="inputScalingFactor"></param>
/// <param name="inputPowerFactor"></param>
/// <returns></returns>
private float DampedMaxAccelerationInput(float currentDisplacement, float targetDisplacement, float minDisplacement,
float maxDisplacement, float minAcceleration, float maxAcceleration, float inputScalingFactor, float inputPowerFactor)
{
// Highest possible max acceleration input is max acceleration
float maxAccelerationInput = maxAcceleration;
// The max acceleration is determined by how close we are to the ground
// At the min/max allowed distance from the ground the limit is infinity
// At the target distance from the ground the limit is some given value
if (currentDisplacement < targetDisplacement && currentDisplacement > minDisplacement)
{
// Reciprocal
float xVal = (currentDisplacement - minDisplacement) / (targetDisplacement - minDisplacement);
maxAccelerationInput = minAcceleration + (inputScalingFactor * ((1f / Mathf.Pow(xVal, inputPowerFactor)) - 1f));
}
else if (currentDisplacement > targetDisplacement && currentDisplacement < maxDisplacement)
{
// Reciprocal
float xVal = (currentDisplacement - maxDisplacement) / (targetDisplacement - maxDisplacement);
maxAccelerationInput = minAcceleration + (inputScalingFactor * ((1f / Mathf.Pow(xVal, inputPowerFactor)) - 1f));
}
// Highest possible max acceleration input is max acceleration
return maxAccelerationInput < maxAcceleration ? maxAccelerationInput : maxAcceleration;
}
/// <summary>
/// Applies a specified amount of damage to the ship at a specified position.
/// </summary>
/// <param name="damageAmount"></param>
/// <param name="damagePosition"></param>
/// <param name="isCollisionDamage"></param>
private void ApplyDamage(float damageAmount, ProjectileModule.DamageType damageType, Vector3 damagePosition, bool adjustForCollisionResistance)
{
// Determine which damage region was hit
bool[] regionDamagedArray = new bool[1];
float[] actualDamageArray = new float[1];
int damageArrayLengths = 1;
if (shipDamageModel == ShipDamageModel.Localised)
{
damageArrayLengths = localisedDamageRegionList.Count + 1;
regionDamagedArray = new bool[damageArrayLengths];
actualDamageArray = new float[damageArrayLengths];
}
// Loop through all damage regions
DamageRegion thisDamageRegion;
float actualDamage = 0f;
bool regionDamaged = false;
bool isShipInvincible = mainDamageRegion.isInvincible;
// Get damage position in local space (calc once outside the loop)
Vector3 localDamagePosition = trfmInvRot * (damagePosition - trfmPos);
for (int i = 0; i < damageArrayLengths; i++)
{
// Get the current damage region we are iterating over, and work out whether it has been hit
if (i == 0)
{
// Main damage region
thisDamageRegion = mainDamageRegion;
// Main damage region is always hit unless it is invincible (which would make the whole ship invincible)
regionDamaged = !isShipInvincible;
}
else
{
// Localised damage region
thisDamageRegion = localisedDamageRegionList[i - 1];
// If the main damage region is invincible, so are all the damage regions
if (isShipInvincible || thisDamageRegion.isInvincible) { regionDamaged = false; }
else
{
// Calculate whether the hit point is within the volume, and hence populate regionDamaged variable
// First, get damage position in local space
//localDamagePosition = trfmInvRot * (damagePosition - trfmPos);
// Then check whether the position is within the bounds of this damage region
regionDamaged = thisDamageRegion.IsHit(localDamagePosition);
// For some reason we need to slightly expand the damage region when the collider is exactly the same size.
//regionDamaged = localDamagePosition.x >= thisDamageRegion.relativePosition.x - (thisDamageRegion.size.x / 2) - 0.0001f &&
// localDamagePosition.x <= thisDamageRegion.relativePosition.x + (thisDamageRegion.size.x / 2) + 0.0001f &&
// localDamagePosition.y >= thisDamageRegion.relativePosition.y - (thisDamageRegion.size.y / 2) - 0.0001f &&
// localDamagePosition.y <= thisDamageRegion.relativePosition.y + (thisDamageRegion.size.y / 2) + 0.0001f &&
// localDamagePosition.z >= thisDamageRegion.relativePosition.z - (thisDamageRegion.size.z / 2) - 0.0001f &&
// localDamagePosition.z <= thisDamageRegion.relativePosition.z + (thisDamageRegion.size.z / 2) + 0.0001f;
}
}
// Don't do any further calculations if the region wasn't hit
if (regionDamaged)
{
// Adjust for collision resistance - value passed in is just an impulse magnitude
if (adjustForCollisionResistance)
{
if (thisDamageRegion.collisionDamageResistance < 0.01f) { actualDamage = Mathf.Infinity; }
else { actualDamage = damageAmount / thisDamageRegion.collisionDamageResistance * 4f / mass; }
}
else { actualDamage = damageAmount; }
// Modify damage dealt based on relevant damage multipliers
if (useDamageMultipliers)
{
if (useLocalisedDamageMultipliers)
{
actualDamage *= thisDamageRegion.GetDamageMultiplier(damageType);
}
else
{
actualDamage *= mainDamageRegion.GetDamageMultiplier(damageType);
}
}
// Determine whether shielding is active for this damage region
if (thisDamageRegion.useShielding && thisDamageRegion.ShieldHealth > 0f)
{
// Set the shielding to active
regionDamaged = false;
// Only do damage to the shield if the damage amount is above the shielding threshold
if (actualDamage >= thisDamageRegion.shieldingDamageThreshold)
{
thisDamageRegion.ShieldHealth -= actualDamage;
thisDamageRegion.shieldRechargeDelayTimer = 0f;
// If this damage destroys the shielding entirely...
if (thisDamageRegion.ShieldHealth <= 0f)
{
// Get the residual damage value
actualDamage = -thisDamageRegion.ShieldHealth;
// Set the shielding to inactive
regionDamaged = true;
thisDamageRegion.ShieldHealth = -0.01f;
}
}
}
if (regionDamaged)
{
// Reduce health of damage region itself
thisDamageRegion.Health -= actualDamage;
}
}
// Added v1.2.7 - regions not damaged should have 0 damage
else { actualDamage = 0f; }
// Set calculated array variables
actualDamageArray[i] = actualDamage;
regionDamagedArray[i] = regionDamaged;
}
// Need to re-check this variable, as damage can destroy shielding and then use residual damage to affect
// the damage region as well
if (!shipDamageModelIsSimple && !isShipInvincible)
{
// Loop through each part list and reduce health of all parts in the regions that have been damaged
// Compute an index shift for localised damage regions
// Main damage region is index 0, so localised damage regions are a one-based indexing system
// as opposed to the standard zero-based indexing system
int drIndexShift = 0, drShiftedIndex, drIndex;
if (shipDamageModel == ShipDamageModel.Localised) { drIndexShift = 1; }
componentListSize = thrusterList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
drIndex = thrusterList[componentIndex].damageRegionIndex;
drShiftedIndex = drIndex + drIndexShift;
if (drIndex > -1 && drShiftedIndex < damageArrayLengths && regionDamagedArray[drShiftedIndex])
{
thrusterList[componentIndex].Health -= actualDamageArray[drShiftedIndex];
}
}
componentListSize = wingList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
drIndex = wingList[componentIndex].damageRegionIndex;
drShiftedIndex = drIndex + drIndexShift;
if (drIndex > -1 && drShiftedIndex < damageArrayLengths && regionDamagedArray[drShiftedIndex])
{
wingList[componentIndex].Health -= actualDamageArray[drShiftedIndex];
}
}
componentListSize = controlSurfaceList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
drIndex = controlSurfaceList[componentIndex].damageRegionIndex;
drShiftedIndex = drIndex + drIndexShift;
if (drIndex > -1 && drShiftedIndex < damageArrayLengths && regionDamagedArray[drShiftedIndex])
{
controlSurfaceList[componentIndex].Health -= actualDamageArray[drShiftedIndex];
}
}
componentListSize = weaponList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
drIndex = weaponList[componentIndex].damageRegionIndex;
drShiftedIndex = drIndex + drIndexShift;
if (drIndex > -1 && drShiftedIndex < damageArrayLengths && regionDamagedArray[drShiftedIndex])
{
weaponList[componentIndex].Health -= actualDamageArray[drShiftedIndex];
}
}
}
// Damage callback
if (callbackOnDamage != null) { callbackOnDamage(mainDamageRegion.Health); }
// Add a damage event
lastDamageEventIndex++;
// Calculate rumble and camera shake required
float rumbleDamageAmount = damageAmount;
if (adjustForCollisionResistance)
{
rumbleDamageAmount /= mainDamageRegion.collisionDamageResistance * 2500f;
}
// Start camera shake as damageAmount with adjusted collision amount if applicable
damageCameraShakeAmountRequired = rumbleDamageAmount;
if (rumbleDamageAmount <= minRumbleDamage) { damageRumbleAmountRequired = 0f; }
else if (rumbleDamageAmount >= maxRumbleDamage) { damageRumbleAmountRequired = 1f; }
else { damageRumbleAmountRequired = (rumbleDamageAmount - minRumbleDamage) / (maxRumbleDamage - minRumbleDamage); }
// Calculate camera shake required
if (damageCameraShakeAmountRequired < 0f) { damageCameraShakeAmountRequired = 0f; }
else if (damageCameraShakeAmountRequired > 1f) { damageCameraShakeAmountRequired = 1f; }
}
/// <summary>
/// Get the index or 0-based position in the list of damage regions.
/// Although typically used to return the index in the localisedDamageRegionList,
/// if passed the mainDamageRegion, it will always return 0.
/// If no matching regions are found, it will return -1.
/// </summary>
/// <param name="damageRegion"></param>
/// <returns></returns>
private int GetDamageRegionIndex(DamageRegion damageRegion)
{
int damageRegionIndex = -1;
if (damageRegion.guidHash == mainDamageRegion.guidHash) { return 0; }
else
{
int localisedDamageRegionListCount = localisedDamageRegionList == null ? 0 : localisedDamageRegionList.Count;
for (int drIdx = 0; drIdx < localisedDamageRegionListCount; drIdx++)
{
if (localisedDamageRegionList[drIdx].guidHash == damageRegion.guidHash) { damageRegionIndex = drIdx; break; }
}
}
return damageRegionIndex;
}
/// <summary>
/// Update the maximum estimated range for all turrets with auto targeting enabled
/// </summary>
internal void UpdateMaxTurretRange()
{
int numWeapons = weaponList == null ? 0 : weaponList.Count;
float maxRange = 0f;
for (int wpIdx = 0; wpIdx < numWeapons; wpIdx++)
{
Weapon weapon = weaponList[wpIdx];
// Only process turrets with Auto Targeting enabled
if (weapon != null && weapon.isAutoTargetingEnabled && (weapon.weaponTypeInt == Weapon.TurretProjectileInt || weapon.weaponTypeInt == Weapon.TurretBeamInt))
{
if (weapon.estimatedRange > maxRange) { maxRange = weapon.estimatedRange; }
}
}
estimatedMaxTurretRange = maxRange;
}
/// <summary>
/// Update the maximum estimated range for all weapons with auto targeting enabled
/// </summary>
internal void UpdateMaxAutoTargetRange()
{
int numWeapons = weaponList == null ? 0 : weaponList.Count;
float maxRange = 0f;
for (int wpIdx = 0; wpIdx < numWeapons; wpIdx++)
{
Weapon weapon = weaponList[wpIdx];
// Only process weapons with Auto Targeting enabled
if (weapon != null && weapon.isAutoTargetingEnabled)
{
if (weapon.estimatedRange > maxRange) { maxRange = weapon.estimatedRange; }
}
}
estimatedMaxAutoTargetRange = maxRange;
}
/// <summary>
/// This gets called from FixedUpdate in BeamModule. It performs the following:
/// 1. Checks if the beam should be despawned
/// 2. Moves the beam
/// 3. Changes the length
/// 4. Checks if it hits anything
/// 5. Updates damage on what it hits
/// 6. Instantiate effects object at hit point
/// 7. Consumes weapon power
/// It needs to be a member of the ship instance as it requires both ship and beam data.
/// Assumes the beam linerenderer has useWorldspace enabled.
/// </summary>
/// <param name="beamModule"></param>
internal void MoveBeam(BeamModule beamModule)
{
if (beamModule.isInitialised && beamModule.isBeamEnabled && beamModule.weaponIndex >= 0 && beamModule.firePositionIndex >= 0)
{
Weapon weapon = weaponList[beamModule.weaponIndex];
SSCBeamItemKey beamItemKey = weapon.beamItemKeyList[beamModule.firePositionIndex];
// Read once from the deltaTime property getter
float _deltaTime = Time.deltaTime;
if ((weapon.weaponTypeInt == Weapon.FixedBeamInt || weapon.weaponTypeInt == Weapon.TurretBeamInt) && beamItemKey.beamSequenceNumber == beamModule.itemSequenceNumber)
{
weaponFiringButtonInt = (int)weapon.firingButton;
// For autofiring weapons, this will return false.
bool isReadyToFire = (pilotPrimaryFireInput && weaponFiringButtonInt == weaponPrimaryFiringInt) || (pilotSecondaryFireInput && weaponFiringButtonInt == weaponSecondaryFiringInt);
// If this is auto-firing turret check if it is ready to fire
if (weaponFiringButtonInt == weaponAutoFiringInt && weapon.weaponTypeInt == Weapon.TurretBeamInt)
{
// Is turret locked on target and in direct line of sight?
// NOTE: If check LoS is enabled, it will still fire if another enemy is between the turret and the weapon.target.
isReadyToFire = weapon.isLockedOnTarget && (!weapon.checkLineOfSight || WeaponHasLineOfSight(weapon));
// BUILD TEST
//isReadyToFire = !weapon.checkLineOfSight || WeaponHasLineOfSight(weapon);
// TODO - Is target in range?
}
// Should this beam be despawned or returned to the pool?
// a) No charge in weapon
// b) Weapon has no health
// c) Weapon has no performance
// d) user stopped firing and has fired for min time permitted
// e) has exceeded the maximum firing duration
if (weapon.chargeAmount <= 0f || weapon.health <= 0f || weapon.currentPerformance == 0f || (!isReadyToFire && beamModule.burstDuration > beamModule.minBurstDuration) || beamModule.burstDuration > beamModule.maxBurstDuration)
{
// Unassign the beam from this weapon's fire position
weapon.beamItemKeyList[beamModule.firePositionIndex] = new SSCBeamItemKey(-1, -1, 0);
beamModule.DestroyBeam();
}
else
{
// Move the beam start
// Calculate the World-space Fire Position (like when weapon is first fired)
// Note: That the ship could have moved and rotated, so we recalc here.
Vector3 _weaponWSBasePos = GetWeaponWorldBasePosition(weapon);
Vector3 _weaponWSFireDir = GetWeaponWorldFireDirection(weapon);
Vector3 _weaponWSFirePos = GetWeaponWorldFirePosition(weapon, _weaponWSBasePos, beamModule.firePositionIndex);
// Move the beam transform but keep the first LineRenderer position at 0,0,0
beamModule.transform.position = _weaponWSFirePos;
beamModule.transform.SetPositionAndRotation(_weaponWSFirePos, Quaternion.LookRotation(_weaponWSFireDir, rbodyUp));
// Calc length and end position
float _desiredLength = beamModule.burstDuration * beamModule.speed;
if (_desiredLength > weapon.maxRange) { _desiredLength = weapon.maxRange; }
Vector3 _endPosition = _weaponWSFirePos + (_weaponWSFireDir * _desiredLength);
// Check if it hit anything
if (Physics.Linecast(_weaponWSFirePos, _endPosition, out raycastHitInfo, ~0, QueryTriggerInteraction.Ignore))
{
// Adjust the end position to the hit point
_endPosition = raycastHitInfo.point;
// Update damage if it hit a ship or an object with a damage receiver
if (!BeamModule.CheckShipHit(raycastHitInfo, beamModule, _deltaTime))
{
// If it hit an object with a DamageReceiver script attached, take appropriate action like call a custom method
BeamModule.CheckObjectHit(raycastHitInfo, beamModule, _deltaTime);
}
// ISSUES - the effect may not be visible while firing if:
// 1) if the effect despawn time is less than the beam max burst duration (We have a warning in the BeamModule editor)
// 2) if the effect does not have looping enabled (this could be more expensive to check)
// Add or Move the effects object
if (beamModule.effectsObjectPrefabID >= 0)
{
if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }
if (sscManager != null)
{
// If the effect has not been spawned, do now
if (beamModule.effectsItemKey.effectsObjectSequenceNumber == 0)
{
InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters
{
effectsObjectPrefabID = beamModule.effectsObjectPrefabID,
position = _endPosition,
rotation = beamModule.transform.rotation
};
// Instantiate the hit effects
if (sscManager.InstantiateEffectsObject(ref ieParms) != null)
{
if (ieParms.effectsObjectSequenceNumber > 0)
{
// Record the hit effect item key
beamModule.effectsItemKey = new SSCEffectItemKey(ieParms.effectsObjectPrefabID, ieParms.effectsObjectPoolListIndex, ieParms.effectsObjectSequenceNumber);
}
}
}
// Move the existing effects object to the end of the beam
else
{
// Currently we are not checking sequence number matching (for pooled effects) as it is faster and can
// avoid doing an additional GetComponent().
sscManager.MoveEffectsObject(beamModule.effectsItemKey, _endPosition, beamModule.transform.rotation, false);
}
}
}
}
// The beam isn't hitting anything AND there is an active effects object
else if (beamModule.effectsObjectPrefabID >= 0 && beamModule.effectsItemKey.effectsObjectSequenceNumber > 0)
{
// Destroy the effects object or return it to the pool
if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }
if (sscManager != null)
{
sscManager.DestroyEffectsObject(beamModule.effectsItemKey);
}
}
// Move the beam end (assumes world space)
// useWorldspace is enabled in beamModule.InitialiseBeam(..)
beamModule.lineRenderer.SetPosition(0, _weaponWSFirePos);
beamModule.lineRenderer.SetPosition(1, _endPosition);
// Consume weapon power. Consumes upt to 2x power when overheating.
if (weapon.rechargeTime > 0f) { weapon.chargeAmount -= _deltaTime * (weapon.heatLevel > weapon.overHeatThreshold ? 2f - weapon.currentPerformance : 1f) / beamModule.dischargeDuration; }
weapon.ManageHeat(_deltaTime, 1f);
// If we run out of power, de-activate it before weapon starts recharging
if (weapon.chargeAmount <= 0f)
{
weapon.chargeAmount = 0f;
// Unassign the beam from this weapon's fire position
weapon.beamItemKeyList[beamModule.firePositionIndex] = new SSCBeamItemKey(-1, -1, 0);
beamModule.DestroyBeam();
}
}
}
}
#if UNITY_EDITOR
else { Debug.LogWarning("ERROR ship.MoveBeam has been called on the wrong beam. isInitialised: " + beamModule.isInitialised +
" isBeamEnabled: " + beamModule.isBeamEnabled + " weaponIndex: " + beamModule.weaponIndex + " firePositionIndex: " + beamModule.firePositionIndex); }
#endif
}
private Vector3 GetWeaponWorldBasePosition(Weapon weapon)
{
return (trfmRight * weapon.relativePosition.x) +
(trfmUp * weapon.relativePosition.y) +
(trfmFwd * weapon.relativePosition.z) +
trfmPos;
}
private Vector3 GetWeaponWorldFireDirection(Weapon weapon)
{
// Get relative fire direction
weaponRelativeFireDirection = weapon.fireDirectionNormalised;
// If this is a turret, adjust relative fire direction based on turret rotation
if (weapon.weaponTypeInt == Weapon.TurretProjectileInt || weapon.weaponTypeInt == Weapon.TurretBeamInt)
{
// (turret rotation less ship rotate) - (original turret rotation less ship rotation) + relative fire direction
weaponRelativeFireDirection = (trfmInvRot * weapon.turretPivotX.rotation) * weapon.intPivotYInvRot * weaponRelativeFireDirection;
}
// Get weapon world fire direction
return (trfmRight * weaponRelativeFireDirection.x) +
(trfmUp * weaponRelativeFireDirection.y) +
(trfmFwd * weaponRelativeFireDirection.z);
}
private Vector3 GetWeaponWorldFirePosition(Weapon weapon, Vector3 weaponWorldBasePosition, int firePositionIndex)
{
// Check if there are multiple fire positions
if (weapon.isMultipleFirePositions)
{
// Get relative fire position
weaponRelativeFirePosition = weapon.firePositionList[firePositionIndex];
}
else
{
// If there is only one fire position, relative position must be the zero vector
weaponRelativeFirePosition.x = 0f;
weaponRelativeFirePosition.y = 0f;
weaponRelativeFirePosition.z = 0f;
}
// If this is a turret, adjust relative fire position based on turret rotation
if (weapon.weaponTypeInt == Weapon.TurretProjectileInt || weapon.weaponTypeInt == Weapon.TurretBeamInt)
{
weaponRelativeFirePosition = (trfmInvRot * weapon.turretPivotX.rotation) * weaponRelativeFirePosition;
}
return weaponWorldBasePosition +
(trfmRight * weaponRelativeFirePosition.x) +
(trfmUp * weaponRelativeFirePosition.y) +
(trfmFwd * weaponRelativeFirePosition.z);
}
/// <summary>
/// Begin to shut down the thrusters. Optionally override the shutdown duration.
/// See also shipControlModule.ShutdownThrusterSystems(..)
/// </summary>
internal void ShutdownThrusterSystems (bool isInstantShutdown = false)
{
// Instant shutdown can be called even while in the process of shutting down.
if (isThrusterSystemsStarted || isInstantShutdown)
{
// Perform an instant shutdown?
if (isInstantShutdown || thrusterSystemShutdownDuration <= 0f)
{
isThrusterSystemsStarted = false;
thrusterSystemShutdownTimer = 0f;
thrusterSystemStartupTimer = 0f;
SetThrottleAllThrusters(0f);
}
else
{
// Activate the shutdown sequence
// if startup was in progress, begin at that point
if (thrusterSystemStartupTimer > 0f && thrusterSystemStartupDuration > 0f)
{
thrusterSystemShutdownTimer = (1f - (thrusterSystemStartupTimer / thrusterSystemStartupDuration)) * thrusterSystemShutdownDuration;
}
else
{
thrusterSystemShutdownTimer = 0.001f;
}
// Reset the startup timer so that shutdown can continue
thrusterSystemStartupTimer = 0f;
}
}
}
/// <summary>
/// Begin to bring the thrusters online. Optionally, override the startup duration.
/// As soon as the systems begin to start up, isThrusterSystemsStarted will be true.
/// See also shipControlModule.StartupThrusterSystems(..)
/// </summary>
internal void StartupThrusterSystems (bool isInstantStartup = false)
{
// Instant startup can be called even while in the process of
// starting up. This is actually how we trigger the end of the
// startup process when the timer expires in CheckThrusterSystems(..).
// Perform an instant startup?
if (isInstantStartup || thrusterSystemStartupDuration <= 0f)
{
thrusterSystemShutdownTimer = 0f;
thrusterSystemStartupTimer = 0f;
SetThrottleAllThrusters(1f);
}
else
{
// Activate the startup sequence
// if shutdown was in progress, begin at that point
if (thrusterSystemShutdownTimer > 0f && thrusterSystemShutdownDuration > 0f)
{
thrusterSystemStartupTimer = (1f - (thrusterSystemShutdownTimer / thrusterSystemShutdownDuration)) * thrusterSystemStartupDuration;
}
else
{
thrusterSystemStartupTimer = 0.001f;
}
// Reset the shutdown timer so that startup can continue
thrusterSystemShutdownTimer = 0f;
}
isThrusterSystemsStarted = true;
}
#endregion
#region Public Non-Static Methods
#region Set Class Defaults
public void SetClassDefaults()
{
this.initialiseOnAwake = true;
this.shipPhysicsModel = ShipPhysicsModel.Arcade;
this.mass = 5000f;
this.centreOfMass = Vector3.zero;
this.setCentreOfMassManually = false;
this.showCOMGizmosInSceneView = true;
this.showCOLGizmosInSceneView = false;
this.showCOTGizmosInSceneView = false;
this.thrusterList = new List<Thruster>(25);
// Create a default forwards-facing thruster
Thruster thruster = new Thruster();
if (thruster != null)
{
thruster.name = "Forward Thruster";
thruster.forceUse = 1;
thruster.primaryMomentUse = 0;
thruster.secondaryMomentUse = 0;
thruster.maxThrust = 100000;
thruster.thrustDirection = Vector3.forward;
thruster.relativePosition = Vector3.zero;
this.thrusterList.Add(thruster);
}
isThrusterFXStationary = false;
isThrusterSystemsStarted = true;
thrusterSystemStartupDuration = 5f;
thrusterSystemShutdownDuration = 5f;
useCentralFuel = true;
centralFuelLevel = 100f;
this.wingList = new List<Wing>(5);
this.controlSurfaceList = new List<ControlSurface>(10);
this.weaponList = new List<Weapon>(10);
// Expand editor lists by default
isThrusterSystemsExpanded = true;
isThrusterListExpanded = true;
isWingListExpanded = true;
isControlSurfaceListExpanded = true;
isWeaponListExpanded = true;
isDamageListExpanded = true;
isMainDamageExpanded = true;
showDamageInEditor = true;
this.dragCoefficients = new Vector3(0.5f, 1f, 0.1f);
this.dragReferenceAreas = new Vector3(10f, 20f, 5f);
this.centreOfDragXMoment = Vector3.zero;
this.centreOfDragYMoment = Vector3.zero;
this.centreOfDragZMoment = Vector3.zero;
this.angularDragFactor = 1f;
this.disableDragMoments = false;
this.dragXMomentMultiplier = 1f;
this.dragYMomentMultiplier = 1f;
this.dragZMomentMultiplier = 1f;
this.wingStallEffect = 1f;
this.arcadeUseBrakeComponent = true;
this.arcadeBrakeStrength = 100f;
this.arcadeBrakeIgnoreMediumDensity = false;
this.arcadeBrakeMinAcceleration = 25f;
this.rotationalFlightAssistStrength = 3f;
this.translationalFlightAssistStrength = 3f;
this.stabilityFlightAssistStrength = 0f;
this.brakeFlightAssistStrength = 0f;
this.brakeFlightAssistMinSpeed = -10f;
this.brakeFlightAssistMaxSpeed = 10f;
this.limitPitchAndRoll = false;
this.maxPitch = 10f;
this.maxTurnRoll = 15f;
this.pitchSpeed = 30f;
this.turnRollSpeed = 30f;
this.rollControlMode = RollControlMode.YawInput;
this.pitchRollMatchResponsiveness = 4f;
this.stickToGroundSurface = false;
this.avoidGroundSurface = false;
this.useGroundMatchSmoothing = false;
this.useGroundMatchLookAhead = false;
this.orientUpInAir = false;
this.targetGroundDistance = 5f;
this.minGroundDistance = 2f;
this.maxGroundDistance = 8f;
this.maxGroundCheckDistance = 25f;
this.groundMatchResponsiveness = 50f;
this.groundMatchDamping = 50f;
this.maxGroundMatchAccelerationFactor = 3f;
this.centralMaxGroundMatchAccelerationFactor = 1f;
this.groundNormalCalculationMode = GroundNormalCalculationMode.SmoothedNormal;
this.groundNormalHistoryLength = 5;
this.groundLayerMask = ~0;
this.inputControlAxis = InputControlAxis.None;
this.inputControlLimit = 0f;
this.inputControlForwardAngle = 0f;
this.inputControlMovingRigidness = 25f;
this.inputControlTurningRigidness = 25f;
this.rollPower = 1f;
this.pitchPower = 1f;
this.yawPower = 1f;
this.steeringThrusterPriorityLevel = 0f;
this.pilotMomentInput = Vector3.zero;
this.pilotForceInput = Vector3.zero;
this.arcadePitchAcceleration = 100f;
this.arcadeYawAcceleration = 100f;
this.arcadeRollAcceleration = 250f;
this.arcadeMaxFlightTurningAcceleration = 500f;
this.arcadeMaxGroundTurningAcceleration = 500f;
this.mediumDensity = 1.293f;
this.gravitationalAcceleration = 9.81f;
this.gravityDirection = -Vector3.up;
this.shipDamageModel = ShipDamageModel.Simple;
this.mainDamageRegion = new DamageRegion();
this.mainDamageRegion.name = "Main Damage Region";
this.localisedDamageRegionList = new List<DamageRegion>(10);
this.useDamageMultipliers = false;
this.useLocalisedDamageMultipliers = false;
this.respawnTime = 5f;
this.collisionRespawnPositionDelay = 5f;
this.respawningMode = RespawningMode.DontRespawn;
this.customRespawnPosition = Vector3.zero;
this.customRespawnRotation = Vector3.zero;
this.respawnVelocity = Vector3.zero;
this.respawningPathGUIDHash = 0;
this.minRumbleDamage = 0f;
this.maxRumbleDamage = 10f;
this.stuckTime = 0f;
this.stuckSpeedThreshold = 0.1f;
this.stuckAction = StuckAction.DoNothing;
this.stuckActionPathGUIDHash = 0;
// By default, all ships on the same faction/side/alliance.
this.factionId = 0;
squadronId = -1; // NOT SET
this.isRadarEnabled = false;
this.radarBlipSize = 1;
}
#endregion
#region Initialisation
/// <summary>
/// Initialise all necessary ship data.
/// </summary>
public void Initialise(Transform trfm)
{
// Store physics model as a boolean value to save looking up enumeration at runtime
shipPhysicsModelPhysicsBased = shipPhysicsModel == ShipPhysicsModel.PhysicsBased;
// New values, better for pipes
pitchPIDController = new PIDController(0.5f, 0f, 0.1f);
rollPIDController = new PIDController(0.5f, 0f, 0.1f);
// For pitch and roll derivative on measurement is disabled so that
// a) in limit pitch/roll mode we can use local-space pitch and roll measurements
// b) if the target pitch/roll is quickly moving away from the measured pitch/roll the
// PID controller can use a large input to prevent it
pitchPIDController.derivativeOnMeasurement = false;
rollPIDController.derivativeOnMeasurement = false;
ReinitialisePitchRollMatchVariables();
// Create a new PID controller for ground match
groundDistPIDController = new PIDController(0f, 0f, 0f);
// For ground dist derivative on measurement is disabled so that if the target distance is quickly moving away from
// the measured pitch/roll the PID controller can use a large input to prevent it
groundDistPIDController.derivativeOnMeasurement = false;
// Initialise ground dist PID controller variables
ReinitialiseGroundMatchVariables();
// Create new PID controllers for input axis control
inputAxisForcePIDController = new PIDController(0f, 0f, 0f);
inputAxisMomentPIDController = new PIDController(0f, 0f, 0f);
inputAxisForcePIDController.derivativeOnMeasurement = false;
inputAxisMomentPIDController.derivativeOnMeasurement = false;
ReinitialiseInputControlVariables();
// Create new PID controllers for stability flight assist
sFlightAssistPitchPIDController = new PIDController(0f, 0f, 0f);
sFlightAssistYawPIDController = new PIDController(0f, 0f, 0f);
sFlightAssistRollPIDController = new PIDController(0f, 0f, 0f);
ReinitialiseStabilityFlightAssistVariables();
// Initialise ray for raycasting against the ground
raycastRay = new Ray(Vector3.zero, Vector3.up);
// Initialise thruster, wing, weapon and damage region variables
ReinitialiseThrusterVariables();
ReinitialiseWingVariables();
ReinitialiseWeaponVariables();
ReinitialiseDamageRegionVariables(true);
// Cache the Weapon FiringButton enumeration values to avoid looking up the enumeration
// Used in CalculateForceAndMoment(..)
weaponPrimaryFiringInt = (int)Weapon.FiringButton.Primary;
weaponSecondaryFiringInt = (int)Weapon.FiringButton.Secondary;
weaponAutoFiringInt = (int)Weapon.FiringButton.AutoFire;
// Initialise respawn data
ReinitialiseRespawnVariables();
// Initialise input data
ReinitialiseInputVariables();
// Get a ship ID integer from the transform ID
shipId = trfm.GetInstanceID();
// Initially there is no radar item assigned in the radar system
radarItemIndex = -1;
// Added in 1.3.3 for muzzle FX reparenting ONLY.
// Use with caution as may need to be removed in the future if
// causes issues like when destroying the ShipControlModule.
shipTransform = trfm;
StopBoost();
}
/// <summary>
/// Re-initialises all variables needed when changing the ship physics model.
/// Call after modifying shipPhysicsModel.
/// </summary>
public void ReinitialiseShipPhysicsModel()
{
// Store physics model as a boolean value to save looking up enumeration at runtime
shipPhysicsModelPhysicsBased = shipPhysicsModel == ShipPhysicsModel.PhysicsBased;
ReinitialisePitchRollMatchVariables();
ReinitialiseGroundMatchVariables();
ReinitialiseInputVariables();
ReinitialiseInputControlVariables();
}
/// <summary>
/// Re-initialises variables related to the stability flight assist.
/// Call after modifying stabilityFlightAssistStrength.
/// </summary>
private void ReinitialiseStabilityFlightAssistVariables()
{
// Configure pitch PID controller
sFlightAssistPitchPIDController.pGain = 0.025f * stabilityFlightAssistStrength;
sFlightAssistPitchPIDController.iGain = 0.005f * stabilityFlightAssistStrength;
sFlightAssistPitchPIDController.dGain = 0.01f * stabilityFlightAssistStrength;
sFlightAssistPitchPIDController.SetInputLimits(-1f, 1f);
// Configure yaw PID controller
sFlightAssistYawPIDController.pGain = 0.025f * stabilityFlightAssistStrength;
sFlightAssistYawPIDController.iGain = 0.005f * stabilityFlightAssistStrength;
sFlightAssistYawPIDController.dGain = 0.01f * stabilityFlightAssistStrength;
sFlightAssistYawPIDController.SetInputLimits(-1f, 1f);
// Configure roll PID controller
sFlightAssistRollPIDController.pGain = 0.025f * stabilityFlightAssistStrength;
sFlightAssistRollPIDController.iGain = 0.005f * stabilityFlightAssistStrength;
sFlightAssistRollPIDController.dGain = 0.01f * stabilityFlightAssistStrength;
sFlightAssistRollPIDController.SetInputLimits(-1f, 1f);
}
/// <summary>
/// Re-initialises variables related to pitch and roll match.
/// </summary>
private void ReinitialisePitchRollMatchVariables()
{
if (shipPhysicsModelPhysicsBased)
{
// Pitch and roll PID controllers are always constrained to ship physics
pitchPIDController.SetInputLimits(-1f, 1f);
rollPIDController.SetInputLimits(-1f, 1f);
}
else
{
// Pitch and roll PID controllers are not constrained to ship physics
pitchPIDController.SetInputLimits(Mathf.NegativeInfinity, Mathf.Infinity);
rollPIDController.SetInputLimits(Mathf.NegativeInfinity, Mathf.Infinity);
}
// Reset PID controllers
pitchPIDController.ResetController();
rollPIDController.ResetController();
}
/// <summary>
/// Re-initialises variables related to ground distance match calculations.
/// Call after modifying useGroundMatchSmoothing, groundMatchResponsiveness, groundMatchDamping,
/// maxGroundMatchAccelerationFactor, centralMaxGroundMatchAccelerationFactor or groundNormalHistoryLength.
/// </summary>
public void ReinitialiseGroundMatchVariables()
{
// Calculate height range constant (if we are using a min-max range for target distance)
float groundMatchHeightRangeConstant = 10f;
if (useGroundMatchSmoothing)
{
// Set the constant to the minimum of: The difference between the min and target distances and the difference
// between the max and target distances
groundMatchHeightRangeConstant = targetGroundDistance - minGroundDistance;
if (groundMatchHeightRangeConstant > maxGroundDistance - targetGroundDistance)
{
groundMatchHeightRangeConstant = maxGroundDistance - targetGroundDistance;
}
}
// P-gain is proportional to responsiveness and inversely proportional to height range
groundDistPIDController.pGain = 100f * groundMatchResponsiveness / groundMatchHeightRangeConstant;
// I-gain is half of p-gain
groundDistPIDController.iGain = 50f * groundMatchResponsiveness / groundMatchHeightRangeConstant;
// D-gain is proportional to damping and inversely proportional to height range
groundDistPIDController.dGain = 10f * groundMatchDamping / groundMatchHeightRangeConstant;
if (shipPhysicsModelPhysicsBased)
{
// In physics-based mode, the forces must stay within the limits of the ship thrusters
groundDistPIDController.SetInputLimits(-1f, 1f);
}
else
{
// In arcade mode, the forces must stay within a maximum acceleration
maxGroundMatchAcceleration = Mathf.Pow(10f, maxGroundMatchAccelerationFactor);
groundDistPIDController.SetInputLimits(-maxGroundMatchAcceleration, maxGroundMatchAcceleration);
centralMaxGroundMatchAcceleration = Mathf.Pow(10f, centralMaxGroundMatchAccelerationFactor);
}
// Reset the PID Controller
groundDistPIDController.ResetController();
// Initialise ground normal history
groundNormalHistory = new Vector3[groundNormalHistoryLength];
for (int gnIdx = 0; gnIdx < groundNormalHistoryLength; gnIdx++) { groundNormalHistory[gnIdx] = trfmUp; }
}
/// <summary>
/// Re-initialises variables related to Input Control for 2.5D flight.
/// Call this after modifying inputControlAxis, inputControlMovingRigidness or inputControlTurningRigidness.
/// </summary>
public void ReinitialiseInputControlVariables()
{
if (shipPhysicsModelPhysicsBased)
{
// Set up the physics-based movement PID controller
// 0.5, 0.1, 0.1 TODO tweak for physics-based
inputAxisForcePIDController.pGain = inputControlMovingRigidness * 0.1f;
inputAxisForcePIDController.iGain = inputControlMovingRigidness * 0.02f;
inputAxisForcePIDController.dGain = inputControlMovingRigidness * 0.02f;
// Set up the physics-base rotation PID controller
// 10, 2, 2 TODO tweak for physics-based
inputAxisMomentPIDController.pGain = inputControlTurningRigidness * 1f;
inputAxisMomentPIDController.iGain = inputControlTurningRigidness * 0.2f;
inputAxisMomentPIDController.dGain = inputControlTurningRigidness * 0.2f;
// Set physical input limits
inputAxisForcePIDController.SetInputLimits(-1f, 1f);
inputAxisMomentPIDController.SetInputLimits(-1f, 1f);
}
else
{
// Set up the arcade movement PID controller
inputAxisForcePIDController.pGain = inputControlMovingRigidness * 1f;
inputAxisForcePIDController.iGain = inputControlMovingRigidness * 0.2f;
inputAxisForcePIDController.dGain = inputControlMovingRigidness * 0.2f;
// Set up the arcade rotation PID controller
inputAxisMomentPIDController.pGain = inputControlTurningRigidness * 1f;
inputAxisMomentPIDController.iGain = inputControlTurningRigidness * 0.2f;
inputAxisMomentPIDController.dGain = inputControlTurningRigidness * 0.2f;
// Set non-physical input limits
inputAxisForcePIDController.SetInputLimits(Mathf.NegativeInfinity, Mathf.Infinity);
inputAxisMomentPIDController.SetInputLimits(Mathf.NegativeInfinity, Mathf.Infinity);
}
inputAxisForcePIDController.ResetController();
inputAxisMomentPIDController.ResetController();
}
/// <summary>
/// Re-initialises variables related to thrusters.
/// Call after modifying thrusterList.
/// </summary>
public void ReinitialiseThrusterVariables()
{
// Initialise all thrusters
componentListSize = thrusterList == null ? 0 : thrusterList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
thrusterList[componentIndex].Initialise();
}
// If thruster systems are not online, immediately turn off all thrusters.
if (!isThrusterSystemsStarted)
{
ShutdownThrusterSystems(true);
}
}
/// <summary>
/// Re-initialises variables related to wings.
/// Call after modifying wingList.
/// </summary>
public void ReinitialiseWingVariables()
{
// Initialise all wings
componentListSize = wingList == null ? 0 : wingList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
wingList[componentIndex].Initialise();
}
}
/// <summary>
/// Re-initialises variables related to weapons.
/// Call after modifying weaponList.
/// </summary>
public void ReinitialiseWeaponVariables()
{
// Initialise all weapons
componentListSize = weaponList == null ? 0 : weaponList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
weaponList[componentIndex].Initialise(trfmInvRot);
}
}
/// <summary>
/// Re-initialises variables related to damage regions.
/// Call after modifying mainDamageRegion or localDamageRegionList (or modifying shipDamageModel).
/// NOTE: Not required if just changing the Health or ShieldHealth
/// </summary>
public void ReinitialiseDamageRegionVariables(bool refreshAll = false)
{
// Initialise the main damage region
mainDamageRegion.Initialise();
numLocalisedDamageRegions = 0;
// Only initialise localised damage regions if the ship damage model is localised
if (shipDamageModel == ShipDamageModel.Localised || refreshAll)
{
// Initialise all localised damage regions
numLocalisedDamageRegions = localisedDamageRegionList == null ? 0 : localisedDamageRegionList.Count;
for (componentIndex = 0; componentIndex < numLocalisedDamageRegions; componentIndex++)
{
localisedDamageRegionList[componentIndex].Initialise();
}
}
}
/// <summary>
/// Re-initialises respawn variables using the current position and rotation of the ship.
/// Also needs to be called after changing respawningMode to RespawningMode.RespawnAtLastPosition.
/// </summary>
public void ReinitialiseRespawnVariables()
{
// Initialise current respawn position/rotation
currentRespawnPosition = trfmPos;
currentRespawnRotation = trfmRot;
if (respawningMode == RespawningMode.RespawnAtLastPosition)
{
currentCollisionRespawnPosition = currentRespawnPosition;
currentCollisionRespawnRotation = currentRespawnRotation;
nextCollisionRespawnPosition = currentRespawnPosition;
nextCollisionRespawnRotation = currentRespawnRotation;
}
}
/// <summary>
/// Re-initialises variables related to ship inputs.
/// Call after modifying thrusterList or controlSurfaceList.
/// Call after modifying the forceUse/primaryMomentUse/secondaryMomentUse of a thruster or the type of a control surface.
/// </summary>
public void ReinitialiseInputVariables()
{
// Initially assume that no inputs are used
longitudinalThrustInputUsed = false;
horizontalThrustInputUsed = false;
verticalThrustInputUsed = false;
pitchMomentInputUsed = false;
yawMomentInputUsed = false;
rollMomentInputUsed = false;
if (shipPhysicsModelPhysicsBased)
{
// Loop through all thrusters to check force and moment inputs
componentListSize = thrusterList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
// Get the thruster we are concerned with
thruster = thrusterList[componentIndex];
// Check whether force inputs are used for this thruster
if (thruster.forceUse == 1 || thruster.forceUse == 2) { longitudinalThrustInputUsed = true; }
else if (thruster.forceUse == 3 || thruster.forceUse == 4) { verticalThrustInputUsed = true; }
else if (thruster.forceUse == 5 || thruster.forceUse == 6) { horizontalThrustInputUsed = true; }
// Check whether moment inputs are used for this thruster
if (thruster.primaryMomentUse == 1 || thruster.primaryMomentUse == 2) { rollMomentInputUsed = true; }
else if (thruster.primaryMomentUse == 3 || thruster.primaryMomentUse == 4) { pitchMomentInputUsed = true; }
else if (thruster.primaryMomentUse == 5 || thruster.primaryMomentUse == 6) { yawMomentInputUsed = true; }
if (thruster.secondaryMomentUse == 1 || thruster.secondaryMomentUse == 2) { rollMomentInputUsed = true; }
else if (thruster.secondaryMomentUse == 3 || thruster.secondaryMomentUse == 4) { pitchMomentInputUsed = true; }
else if (thruster.secondaryMomentUse == 5 || thruster.secondaryMomentUse == 6) { yawMomentInputUsed = true; }
}
// Loop through all control surfaces to check moment inputs and brake input
componentListSize = controlSurfaceList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
// Get the control surface we are concerned with
controlSurface = controlSurfaceList[componentIndex];
// Check whether moment inputs are used for this control surface
if (controlSurface.type == ControlSurface.ControlSurfaceType.Aileron) { rollMomentInputUsed = true; }
else if (controlSurface.type == ControlSurface.ControlSurfaceType.Elevator) { pitchMomentInputUsed = true; }
else if (controlSurface.type == ControlSurface.ControlSurfaceType.Rudder) { yawMomentInputUsed = true; }
else if (controlSurface.type == ControlSurface.ControlSurfaceType.AirBrake) { longitudinalThrustInputUsed = true; }
}
}
else
{
// Loop through all thrusters to check force inputs
componentListSize = thrusterList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
// Get the thruster we are concerned with
thruster = thrusterList[componentIndex];
// Check whether force inputs are used for this thruster
if (thruster.forceUse == 1 || thruster.forceUse == 2) { longitudinalThrustInputUsed = true; }
else if (thruster.forceUse == 3 || thruster.forceUse == 4) { verticalThrustInputUsed = true; }
else if (thruster.forceUse == 5 || thruster.forceUse == 6) { horizontalThrustInputUsed = true; }
}
// In arcade mode, moment inputs only depend on the pitch/roll/yaw accelerations being non-zero
pitchMomentInputUsed = arcadePitchAcceleration > 0.01f;
yawMomentInputUsed = arcadeYawAcceleration > 0.01f;
rollMomentInputUsed = arcadeRollAcceleration > 0.01f;
// In arcade mode, brakes can count for longitudinal inputs
if (arcadeUseBrakeComponent) { longitudinalThrustInputUsed = true; }
}
}
#endregion
#region Update Position And Movement Data
/// <summary>
/// Reset the cached velocity data. Typically called
/// when the ship rigidbody is set to kinematic.
/// </summary>
public void ResetVelocityData()
{
worldVelocity = Vector3.zero;
localVelocity = Vector3.zero;
absLocalVelocity = Vector3.zero;
worldAngularVelocity = Vector3.zero;
localAngularVelocity = Vector3.zero;
}
/// <summary>
/// Update position and movement data using data obtained from a transform and a rigidbody.
/// </summary>
/// <param name="transform"></param>
/// <param name="rigidbody"></param>
public void UpdatePositionAndMovementData(Transform transform, Rigidbody rigidbody)
{
// Update data obtained from transform
trfmPos = transform.position;
trfmFwd = transform.forward;
trfmRight = transform.right;
trfmUp = transform.up;
trfmRot = transform.rotation;
trfmInvRot = Quaternion.Inverse(trfmRot);
// Update data obtained from rigidbody
rbodyPos = rigidbody.position;
rbodyRot = rigidbody.rotation;
rbodyInvRot = Quaternion.Inverse(rbodyRot);
rbodyFwd = rbodyRot * Vector3.forward;
rbodyRight = rbodyRot * Vector3.right;
rbodyUp = rbodyRot * Vector3.up;
worldVelocity = rigidbody.velocity;
worldAngularVelocity = rigidbody.angularVelocity;
localVelocity = rbodyInvRot * worldVelocity;
localAngularVelocity = rbodyInvRot * worldAngularVelocity;
rBodyInertiaTensor = rigidbody.inertiaTensor;
}
#endregion
#region Calculate Force and Moment
/// <summary>
/// Calculate the equivalent local resultant force and moment for the ship and store them in the supplied reference vectors.
/// Should be called during FixedUpdate(), and have UpdatePositionAndMovementData() called prior to it.
/// </summary>
/// <param name="localResultantForce"></param>
/// <param name="localResultantMoment"></param>
public void CalculateForceAndMoment(ref Vector3 localResultantForce, ref Vector3 localResultantMoment)
{
#if SSC_SHIP_DEBUG
float initStartTime = Time.realtimeSinceStartup;
#endif
// TODO: OPTIMISATIONS
// Reduce number of function calls
// Use less math functions
// Remove variable declarations
// Try not to use transform functions and vector3 functions
// Do as much precalculation as possible i.e. store a bool for isPhysicsBased etc.
#region Initialisation
// Read from Time property getter once
float _deltaTime = Time.deltaTime;
// Remember the previous frame's world velocity
previousFrameWorldVelocity = worldVelocity;
// Reset local resultant force and moment
localResultantForce = Vector3.zero;
localResultantMoment = Vector3.zero;
// Reset any inputs that aren't used (forceInput and momentInput variables used temporarily)
forceInput.x = horizontalThrustInputUsed ? pilotForceInput.x : 0f;
forceInput.y = verticalThrustInputUsed ? pilotForceInput.y : 0f;
forceInput.z = longitudinalThrustInputUsed ? pilotForceInput.z : 0f;
momentInput.x = pitchMomentInputUsed ? pilotMomentInput.x : 0f;
momentInput.y = yawMomentInputUsed ? pilotMomentInput.y : 0f;
momentInput.z = rollMomentInputUsed ? pilotMomentInput.z : 0f;
pilotForceInput = forceInput;
pilotMomentInput = momentInput;
// Reset force and moment input
forceInput = Vector3.zero;
momentInput = Vector3.zero;
// Reset arcade force and moment vectors
arcadeForceVector = Vector3.zero;
arcadeMomentVector = Vector3.zero;
// Get the ship physics model
shipPhysicsModelPhysicsBased = shipPhysicsModel == ShipPhysicsModel.PhysicsBased;
// Get the ship damage model
shipDamageModelIsSimple = shipDamageModel == ShipDamageModel.Simple;
#endregion
#if SSC_SHIP_DEBUG
float initEndTime = Time.realtimeSinceStartup;
float groundStartTime = Time.realtimeSinceStartup;
#endif
#region Get Ground Information
if (limitPitchAndRoll)
{
if (stickToGroundSurface || avoidGroundSurface)
{
// TODO: This shouldn't always be centre of the object
//raycastRay.origin = trfmPos + (trfmFwd * 3.5f);
raycastRay.origin = trfmPos;
// If we were on the ground last frame, raycast in the "down" direction indicated by the ground normal
if (stickToGroundSurfaceEnabled) { raycastRay.direction = -worldTargetPlaneNormal; }
// Otherwise raycast in the ship's down direction
else { raycastRay.direction = -trfmUp; }
// Perform the raycast
if (Physics.Raycast(raycastRay, out raycastHitInfo, maxGroundCheckDistance, groundLayerMask, QueryTriggerInteraction.Ignore))
{
// Single normal, derived from raycast hit normal
// This is the backup for smoothed normals and average height methods
worldTargetPlaneNormal = raycastHitInfo.normal;
if (groundNormalCalculationMode == GroundNormalCalculationMode.SmoothedNormal)
{
// Only use smoothed normals if the ground is using a mesh collider
// Check for a valid number of triangles, as sometimes can return -1. This potentially happens when
// Unity simplifies the collider (called degenerate) to improve performance.
if (raycastHitInfo.collider != null && raycastHitInfo.collider.GetType() == typeof(MeshCollider) && raycastHitInfo.triangleIndex >= 0)
{
// Get the collider as a mesh collider
groundMeshCollider = (MeshCollider)raycastHitInfo.collider;
if (groundMeshCollider != null)
{
// Check if this mesh collider is the cached one - if it is, we can use
// the previously stored data without having to look it up from the mesh
if (groundMeshCollider != cachedGroundMeshCollider)
{
// If this mesh collider is not the cached one, cache the mesh data for next time
cachedGroundMeshCollider = groundMeshCollider;
// Get the shared mesh being used by the ground collider and check that it isn't null
cachedGroundMesh = groundMeshCollider.sharedMesh;
if (cachedGroundMesh != null)
{
// Get the normals and triangle indices arrays
cachedGroundMeshNormals = cachedGroundMesh.normals;
cachedGroundMeshTriangles = cachedGroundMesh.triangles;
}
}
// Get the normals from the triangle we hit using the cached data
groundNormal0 = cachedGroundMeshNormals[cachedGroundMeshTriangles[raycastHitInfo.triangleIndex * 3 + 0]];
groundNormal1 = cachedGroundMeshNormals[cachedGroundMeshTriangles[raycastHitInfo.triangleIndex * 3 + 1]];
groundNormal2 = cachedGroundMeshNormals[cachedGroundMeshTriangles[raycastHitInfo.triangleIndex * 3 + 2]];
// Get the barycentric coordinate of the point we hit - this gives us information as to
// where in the triangle this point is
barycentricCoord = raycastHitInfo.barycentricCoordinate;
// Interpolate between the three normals using the barycentric coordinate to
// get the smoothed normal - this is how smooth shading works
worldTargetPlaneNormal = groundMeshCollider.transform.TransformDirection(
barycentricCoord[0] * groundNormal0 +
barycentricCoord[1] * groundNormal1 +
barycentricCoord[2] * groundNormal2).normalized;
}
}
}
//else if (groundNormalCalculationMode == GroundNormalCalculationMode.AverageHeight)
//{
// float halfRectangleWidth = 3.5f;
// float halfRectangleLength = 5.5f;
// bool raycastFRHit, raycastFLHit, raycastRRHit, raycastRLHit;
// float nUsableNormals = 0f;
// Vector3 raycastFRHitPoint = Vector3.zero;
// Vector3 raycastFLHitPoint = Vector3.zero;
// Vector3 raycastRRHitPoint = Vector3.zero;
// Vector3 raycastRLHitPoint = Vector3.zero;
// RaycastHit testRaycastHitInfo;
// Ray testRaycastRay = new Ray();
// Vector3 averageNormal = Vector3.zero;
// // Front-right raycast
// testRaycastRay.origin = trfmPos + (trfmRight * halfRectangleWidth) + (trfmFwd * halfRectangleLength);
// testRaycastRay.direction = -trfmUp;
// if (Physics.Raycast(testRaycastRay, out testRaycastHitInfo, maxGroundCheckDistance, groundLayerMask, QueryTriggerInteraction.Ignore))
// {
// raycastFRHit = true;
// raycastFRHitPoint = testRaycastHitInfo.point;
// }
// else { raycastFRHit = false; }
// // Front-left raycast
// testRaycastRay.origin = trfmPos - (trfmRight * halfRectangleWidth) + (trfmFwd * halfRectangleLength);
// testRaycastRay.direction = -trfmUp;
// if (Physics.Raycast(testRaycastRay, out testRaycastHitInfo, maxGroundCheckDistance, groundLayerMask, QueryTriggerInteraction.Ignore))
// {
// raycastFLHit = true;
// raycastFLHitPoint = testRaycastHitInfo.point;
// }
// else { raycastFLHit = false; }
// // Rear-right raycast
// testRaycastRay.origin = trfmPos + (trfmRight * halfRectangleWidth) - (trfmFwd * halfRectangleLength);
// testRaycastRay.direction = -trfmUp;
// if (Physics.Raycast(testRaycastRay, out testRaycastHitInfo, maxGroundCheckDistance, groundLayerMask, QueryTriggerInteraction.Ignore))
// {
// raycastRRHit = true;
// raycastRRHitPoint = testRaycastHitInfo.point;
// }
// else { raycastRRHit = false; }
// // Rear-left raycast
// testRaycastRay.origin = trfmPos - (trfmRight * halfRectangleWidth) - (trfmFwd * halfRectangleLength);
// testRaycastRay.direction = -trfmUp;
// if (Physics.Raycast(testRaycastRay, out testRaycastHitInfo, maxGroundCheckDistance, groundLayerMask, QueryTriggerInteraction.Ignore))
// {
// raycastRLHit = true;
// raycastRLHitPoint = testRaycastHitInfo.point;
// }
// else { raycastRLHit = false; }
// if (raycastFLHit && raycastFRHit && raycastRRHit)
// {
// nUsableNormals += 1f;
// averageNormal += Vector3.Cross(raycastRRHitPoint - raycastFRHitPoint, raycastFLHitPoint - raycastFRHitPoint).normalized;
// }
// if (raycastFRHit && raycastRRHit && raycastRLHit)
// {
// nUsableNormals += 1f;
// averageNormal += Vector3.Cross(raycastRLHitPoint - raycastRRHitPoint, raycastFRHitPoint - raycastRRHitPoint).normalized;
// }
// if (raycastRRHit && raycastRLHit && raycastFLHit)
// {
// nUsableNormals += 1f;
// averageNormal += Vector3.Cross(raycastFLHitPoint - raycastRLHitPoint, raycastRRHitPoint - raycastRLHitPoint).normalized;
// }
// if (raycastRLHit && raycastFLHit && raycastFRHit)
// {
// nUsableNormals += 1f;
// averageNormal += Vector3.Cross(raycastFRHitPoint - raycastFLHitPoint, raycastRLHitPoint - raycastFLHitPoint).normalized;
// }
// if (nUsableNormals > 0f) { worldTargetPlaneNormal = (averageNormal / nUsableNormals).normalized; }
//}
// Calculate a normal averaged from the last few frames
UpdateGroundNormalHistory(worldTargetPlaneNormal);
worldTargetPlaneNormal = GetDampedGroundNormal();
// Get target plane normal in local space
localTargetPlaneNormal = trfmInvRot * worldTargetPlaneNormal;
// Calculate distance to the ground in a direction parallel to the ground normal
// (from the ground directly beneath the ship)
currentPerpendicularGroundDist = Vector3.Dot(trfmPos - raycastHitInfo.point, worldTargetPlaneNormal);
//Debug.DrawRay(raycastRay.origin, raycastRay.direction.normalized * maxGroundCheckDistance, Color.blue);
// If required, use look ahead to check if there is an obstacle ahead we will need to go over
if (useGroundMatchLookAhead)
{
float maxRaycastDistance;
RaycastHit secondaryRaycastHitInfo;
// Loop through a series of different look ahead times, starting at half a second away
for (float groundMatchLookAheadTime = 0.5f; groundMatchLookAheadTime > 0.01f; groundMatchLookAheadTime -= 0.1f)
{
// Calculate the corresponding look ahead distance
float groundMatchLookAheadDistance = groundMatchLookAheadTime * localVelocity.z;
// Calculate the raycast direction
raycastRay.direction = (-currentPerpendicularGroundDist * worldTargetPlaneNormal) + (groundMatchLookAheadDistance * trfmFwd);
// Calculate the required raycast length to go down the same distance as the standard ground check
maxRaycastDistance = Mathf.Sqrt((currentPerpendicularGroundDist * currentPerpendicularGroundDist) +
(groundMatchLookAheadDistance * groundMatchLookAheadDistance)) * (maxGroundCheckDistance / currentPerpendicularGroundDist);
// Perform the raycast
if (Physics.Raycast(raycastRay, out secondaryRaycastHitInfo, maxRaycastDistance, groundLayerMask,
QueryTriggerInteraction.Ignore))
{
// Calculate the perpendicular ground distance (projected onto the target normal)
float futurePerpendicularGroundDistance = Vector3.Dot(trfmPos - secondaryRaycastHitInfo.point, worldTargetPlaneNormal);
// If the perpendicular ground distance we have calculated is LESS than the current
// perpendicular ground distance (i.e. for the ground beneath the ship), use this
// new ground distance instead
if (futurePerpendicularGroundDistance <= currentPerpendicularGroundDist)
{
currentPerpendicularGroundDist = futurePerpendicularGroundDistance;
//Debug.DrawRay(raycastRay.origin, raycastRay.direction.normalized * maxRaycastDistance, Color.green);
// Exit the loop
break;
}
//else
//{
// Debug.DrawRay(raycastRay.origin, raycastRay.direction.normalized * maxRaycastDistance, Color.red);
//}
}
}
}
// Set relevant variables, to indicate to later code that we have found a ground surface
limitPitchAndRollEnabled = true;
stickToGroundSurfaceEnabled = true;
}
else
{
// If we were on the ground on the previous frame, and now we are not,
// reset the PID controller to prevent spikes when we go back onto the ground again
if (stickToGroundSurfaceEnabled) { groundDistPIDController.ResetController(); }
limitPitchAndRollEnabled = orientUpInAir;
stickToGroundSurfaceEnabled = false;
if (limitPitchAndRollEnabled)
{
worldTargetPlaneNormal = Vector3.up;
// Update ground normal history
UpdateGroundNormalHistory(worldTargetPlaneNormal);
// Get target plane normal in local space
localTargetPlaneNormal = trfmInvRot * worldTargetPlaneNormal;
}
else
{
// Update ground normal history
UpdateGroundNormalHistory(trfmUp);
}
}
}
else
{
limitPitchAndRollEnabled = true;
stickToGroundSurfaceEnabled = false;
worldTargetPlaneNormal = Vector3.up;
// Get target plane normal in local space
localTargetPlaneNormal = trfmInvRot * worldTargetPlaneNormal;
}
}
else
{
limitPitchAndRollEnabled = false;
stickToGroundSurfaceEnabled = false;
}
#endregion
#if SSC_SHIP_DEBUG
float groundEndTime = Time.realtimeSinceStartup;
#endif
#region Gravity
// Calculate acceleration due to gravity in local space (storing the value for use later)
localGravityAcceleration = trfmInvRot * gravityDirection * gravitationalAcceleration;
// Then add it to local resultant force vector (multiplying by mass to turn it into a force)
localResultantForce += localGravityAcceleration * mass;
#endregion
#if SSC_SHIP_DEBUG
float inputStartTime = Time.realtimeSinceStartup;
#endif
#region Calculate Final Inputs
// Calculate final input
// | Assists/limiters info |
// Rotational flight assist:
// - Always on
// - If the pilot has released an axial input but we are still spinning on that axis,
// modify the input to slow down the spin
// Translational flight assist:
// - Always on
// - If the pilot has released a translational input but we are still moving on that axis,
// modify the input to slow down the movement
// - Is overriden on y-axis by stick to ground surface
// - Currently does not apply on z-axis
// - Isn't available in arcade mode (it is instead replaced by velocity match code)
// Brake flight assist:
// - Off by default
// - Translational flight assist on z-axis only
// - Only applies when no z-axis input
// - Only applies when fwd/back movement is non-zero and not close to zero
// - Can be overridden in forwards direction by AutoCruise on PlayerInputModule
// Limit pitch and roll:
// - Overrides x and z axes of rotational flight assist
// - Causes y-axis rotational flight assist to apply on an axis normal to the target plane
// Stick to ground surface
// - Overrides y axis of pilot force input
// Moment inputs
// - Rotational flight assist
if (limitPitchAndRollEnabled)
{
// Ground angle match assist: Calculate a target pitch and roll from the ground angle
// Pitch is still affected by pilot input but is clamped between min and max pitch
// Roll is controlled by yaw input or strafe input
// Calculate target plane normal in local space
// Calculate target pitch (relative to the ground normal) in degrees
// This is always dependent on the pitch input
pitchAmountInput = pilotMomentInput.x * maxPitch;
// Calculate target roll (relative to the ground normal) in degrees
// a) From the yaw input
if (rollControlMode == RollControlMode.YawInput) { rollAmountInput = pilotMomentInput.y * maxTurnRoll; }
// b) From the strafe input
else { rollAmountInput = pilotForceInput.x * maxTurnRoll; }
// Calculate the maximum possible change in pitch/roll for this frame
maxFramePitchDelta = _deltaTime * pitchSpeed;
maxFrameRollDelta = _deltaTime * turnRollSpeed;
// Move the pitch amount towards the target pitch input
if (pitchAmountInput >= pitchAmount - maxFramePitchDelta && pitchAmountInput <= pitchAmount + maxFramePitchDelta) { pitchAmount = pitchAmountInput; }
else if (pitchAmountInput > pitchAmount) { pitchAmount += maxFramePitchDelta; }
else { pitchAmount -= maxFramePitchDelta; }
// Move the roll amount towards the target roll input
if (rollAmountInput >= rollAmount - maxFrameRollDelta && rollAmountInput <= rollAmount + maxFrameRollDelta) { rollAmount = rollAmountInput; }
else if (rollAmountInput > rollAmount) { rollAmount += maxFrameRollDelta; }
else { rollAmount -= maxFrameRollDelta; }
// Calculate target local-space roll and pitch in degrees
targetLocalPitch = ((float)Math.Atan2(localTargetPlaneNormal.z, localTargetPlaneNormal.y) * Mathf.Rad2Deg) + pitchAmount;
targetLocalRoll = ((float)Math.Atan2(localTargetPlaneNormal.x, localTargetPlaneNormal.y) * Mathf.Rad2Deg) + rollAmount;
//Debug.Log("Target pitch: " + targetLocalPitch.ToString("00.00") + ", target roll: " + targetLocalRoll.ToString("00.00"));
if (shipPhysicsModelPhysicsBased)
{
// Use the PID controllers to calculate the required moment inputs on the x and z axes
momentInput.z = rollPIDController.RequiredInput(targetLocalRoll, 0f, _deltaTime);
momentInput.x = pitchPIDController.RequiredInput(targetLocalPitch, 0f, _deltaTime);
momentInput.y = 0f;
}
else
{
// Use the PID controllers to calculate the required moments on the x and z axes
arcadeMomentVector.z = rollPIDController.RequiredInput(targetLocalRoll, 0f, _deltaTime) * 50f * pitchRollMatchResponsiveness;
arcadeMomentVector.x = pitchPIDController.RequiredInput(targetLocalPitch, 0f, _deltaTime) * 50f * pitchRollMatchResponsiveness;
arcadeMomentVector.y = 0f;
// Completely reset moment input here
momentInput = Vector3.zero;
}
// Apply typical yaw rotation with respect to ground normal instead of local y coordinate
// TODO: Surely this line can be optimised...?
yawVelocity = Vector3.Dot(Vector3.Project(localAngularVelocity * Mathf.Rad2Deg, localTargetPlaneNormal), localTargetPlaneNormal);
if (shipPhysicsModelPhysicsBased)
{
// Activate rotational flight assist if pilot releases an input
// or if input is in direction opposing rotational velocity
if ((pilotMomentInput.y > 0f) == (yawVelocity < 0f) || pilotMomentInput.y == 0f)
{
// Calculate the flight assist value on the ground normal axis
flightAssistValue = -yawVelocity / 100f * rotationalFlightAssistStrength;
// Choose whichever value has the highest absolute value: The flight assist value or the pilot input value
if ((flightAssistValue > 0f ? flightAssistValue : -flightAssistValue) >
(pilotMomentInput.y > 0f ? pilotMomentInput.y : -pilotMomentInput.y))
{
momentInput += localTargetPlaneNormal * flightAssistValue;
}
else { momentInput += localTargetPlaneNormal * pilotMomentInput.y; }
}
else { momentInput += localTargetPlaneNormal * pilotMomentInput.y; }
}
else
{
momentInput += localTargetPlaneNormal * pilotMomentInput.y;
// Activate arcade rotational flight assist if pilot releases an input
// or if input is in direction opposing rotational velocity
if ((pilotMomentInput.y > 0f) == (yawVelocity < 0f) || pilotMomentInput.y == 0f)
{
// Calculate the flight assist value on the ground normal axis
arcadeMomentVector += localTargetPlaneNormal * -yawVelocity * rotationalFlightAssistStrength;
}
}
}
else
{
// Rotational flight assist: Input to counteract rotational velocity
if (shipPhysicsModelPhysicsBased)
{
// Activate rotational flight assist if pilot releases an input
// or if input is in direction opposing rotational velocity
if ((pilotMomentInput.x > 0f) == (localAngularVelocity.x < 0f) || pilotMomentInput.x == 0f)
{
// Calculate the flight assist value on this axis
flightAssistValue = -localAngularVelocity.x * Mathf.Rad2Deg / 100f * rotationalFlightAssistStrength;
// Choose whichever value has the highest absolute value: The flight assist value or the pilot input value
if ((flightAssistValue > 0f ? flightAssistValue : -flightAssistValue) >
(pilotMomentInput.x > 0f ? pilotMomentInput.x : -pilotMomentInput.x)) { momentInput.x = flightAssistValue; }
else { momentInput.x = pilotMomentInput.x; }
}
else { momentInput.x = pilotMomentInput.x; }
if ((pilotMomentInput.y > 0f) == (localAngularVelocity.y < 0f) || pilotMomentInput.y == 0f)
{
// Calculate the flight assist value on this axis
flightAssistValue = -localAngularVelocity.y * Mathf.Rad2Deg / 100f * rotationalFlightAssistStrength;
// Choose whichever value has the highest absolute value: The flight assist value or the pilot input value
if ((flightAssistValue > 0f ? flightAssistValue : -flightAssistValue) >
(pilotMomentInput.y > 0f ? pilotMomentInput.y : -pilotMomentInput.y)) { momentInput.y = flightAssistValue; }
else { momentInput.y = pilotMomentInput.y; }
}
else { momentInput.y = pilotMomentInput.y; }
if ((pilotMomentInput.z > 0f) == (localAngularVelocity.z > 0f) || pilotMomentInput.z == 0f)
{
// Calculate the flight assist value on this axis
flightAssistValue = localAngularVelocity.z * Mathf.Rad2Deg / 100f * rotationalFlightAssistStrength;
// Choose whichever value has the highest absolute value: The flight assist value or the pilot input value
if ((flightAssistValue > 0f ? flightAssistValue : -flightAssistValue) >
(pilotMomentInput.z > 0f ? pilotMomentInput.z : -pilotMomentInput.z)) { momentInput.z = flightAssistValue; }
else { momentInput.z = pilotMomentInput.z; }
}
else { momentInput.z = pilotMomentInput.z; }
}
else
{
// Activate arcade rotational flight assist if pilot releases an input
// or if input is in direction opposing rotational velocity
momentInput = pilotMomentInput;
if ((pilotMomentInput.x > 0f) == (localAngularVelocity.x < 0f) || pilotMomentInput.x == 0f)
{
arcadeMomentVector.x = -localAngularVelocity.x * Mathf.Rad2Deg * rotationalFlightAssistStrength;
}
if ((pilotMomentInput.y > 0f) == (localAngularVelocity.y < 0f) || pilotMomentInput.y == 0f)
{
arcadeMomentVector.y = -localAngularVelocity.y * Mathf.Rad2Deg * rotationalFlightAssistStrength;
}
if ((pilotMomentInput.z > 0f) == (localAngularVelocity.z > 0f) || pilotMomentInput.z == 0f)
{
arcadeMomentVector.z = localAngularVelocity.z * Mathf.Rad2Deg * rotationalFlightAssistStrength;
}
}
}
if (stabilityFlightAssistStrength > 0f)
{
// TODO I need to optimise the calculation section
#region Calculate Current Pitch, Yaw and Roll
// TODO I really need to optimise this properly
// Calculate current pitch of the ship
Vector3 pitchProjectionPlaneNormal = Vector3.Cross(Vector3.up, TransformForward);
Vector3 pitchProjectedUpDirection = Vector3.ProjectOnPlane(TransformUp, pitchProjectionPlaneNormal);
float stabilityCurrentPitch = Vector3.SignedAngle(Vector3.up, pitchProjectedUpDirection, pitchProjectionPlaneNormal);
// Measure pitch with respect to target pitch direction
stabilityCurrentPitch -= sFlightAssistTargetPitch;
if (stabilityCurrentPitch > 180f) { stabilityCurrentPitch -= 360f; }
else if (stabilityCurrentPitch < -180f) { stabilityCurrentPitch += 360f; }
// Calculate current yaw of the ship
// TODO this can be massively optimised by just setting the y-value to zero
Vector3 yawProjectionPlaneNormal = Vector3.up;
Vector3 yawProjectedForwardDirection = Vector3.ProjectOnPlane(TransformForward, yawProjectionPlaneNormal);
float stabilityCurrentYaw = Vector3.SignedAngle(Vector3.forward, yawProjectedForwardDirection, yawProjectionPlaneNormal);
// Measure yaw with respect to target yaw direction
stabilityCurrentYaw -= sFlightAssistTargetYaw;
if (stabilityCurrentYaw > 180f) { stabilityCurrentYaw -= 360f; }
else if (stabilityCurrentYaw < -180f) { stabilityCurrentYaw += 360f; }
// Calculate current roll of the ship
Vector3 rollProjectionPlaneNormal = TransformForward;
rollProjectionPlaneNormal.y = 0f;
Vector3 rollProjectedUpDirection = Vector3.ProjectOnPlane(TransformUp, rollProjectionPlaneNormal);
float stabilityCurrentRoll = -Vector3.SignedAngle(Vector3.up, rollProjectedUpDirection, rollProjectionPlaneNormal);
// Measure roll with respect to target roll direction
stabilityCurrentRoll -= sFlightAssistTargetRoll;
if (stabilityCurrentRoll > 180f) { stabilityCurrentRoll -= 360f; }
else if (stabilityCurrentRoll < -180f) { stabilityCurrentRoll += 360f; }
#endregion
#region Update Target Pitch, Yaw and Roll Values
// How this works: There are two (mutually exclusive) modes of operation:
// 1. Setting target values for pitch/yaw/roll
// 2. Applying the stability assist for pitch/yaw/roll
// For example, if the target pitch is being set, this means that the stability assist will not be used
// for pitch in that frame
bool allowPitchStabilityAssist = true;
bool allowYawStabilityAssist = true;
bool allowRollStabilityAssist = true;
// Check if the pilot is initiating a manoeuvre on any of the three axes via pilot input
bool initiatingPitchManoeuvre = pilotMomentInput.x > 0.01f || pilotMomentInput.x < -0.01f;
bool initiatingYawManoeuvre = pilotMomentInput.y > 0.01f || pilotMomentInput.y < -0.01f;
bool initiatingRollManoeuvre = pilotMomentInput.z > 0.01f || pilotMomentInput.z < -0.01f;
// If there is currently some pilot pitch/yaw/roll input...
if (initiatingPitchManoeuvre || initiatingRollManoeuvre || initiatingYawManoeuvre)
{
// ... set the target pitch/yaw/roll
sFlightAssistTargetPitch += stabilityCurrentPitch;
sFlightAssistPitchPIDController.ResetController();
sFlightAssistTargetYaw += stabilityCurrentYaw;
sFlightAssistYawPIDController.ResetController();
sFlightAssistTargetRoll += stabilityCurrentRoll;
sFlightAssistRollPIDController.ResetController();
allowPitchStabilityAssist = false;
allowYawStabilityAssist = false;
allowRollStabilityAssist = false;
// If a manouevre has been initiated on a given axis via pilot input, remember it
// We can hence know when we are rotating on a particular axis due to pilot input
// or due to drag moments etc and act accordingly
// Note that pitch can affect yaw and vice versa
if (initiatingPitchManoeuvre || initiatingYawManoeuvre)
{
sFlightAssistRotatingPitch = true;
sFlightAssistRotatingYaw = true;
}
if (initiatingRollManoeuvre) { sFlightAssistRotatingRoll = true; }
}
// Otherwise...
else
{
// If there is a significant pitch assist input to correct a pitch manoeuvre...
if (sFlightAssistRotatingPitch && (momentInput.x > 0.3f || momentInput.x < -0.3f))
{
// ... set the target pitch
sFlightAssistTargetPitch += stabilityCurrentPitch;
sFlightAssistPitchPIDController.ResetController();
allowPitchStabilityAssist = false;
}
// When the pitch assist moment input has reduced enough, we can assume we have finished
// any pitch manoeuvres initiated by the pilot
else { sFlightAssistRotatingPitch = false; }
// If there is a significant yaw assist input to correct a yaw manoeuvre...
if (sFlightAssistRotatingYaw && (momentInput.y > 0.3f || momentInput.y < -0.3f))
{
// ... set the target yaw
sFlightAssistTargetYaw += stabilityCurrentYaw;
sFlightAssistYawPIDController.ResetController();
allowYawStabilityAssist = false;
}
// When the yaw assist moment input has reduced enough, we can assume we have finished
// any yaw manoeuvres initiated by the pilot
else { sFlightAssistRotatingYaw = false; }
// If there is a significant roll assist input to correct a roll manoeuvre...
if (sFlightAssistRotatingRoll && (momentInput.z > 0.3f || momentInput.z < -0.3f))
{
// ... set the target roll
sFlightAssistTargetRoll += stabilityCurrentRoll;
sFlightAssistRollPIDController.ResetController();
allowRollStabilityAssist = false;
}
// When the roll assist moment input has reduced enough, we can assume we have finished
// any roll manoeuvres initiated by the pilot
else { sFlightAssistRotatingRoll = false; }
}
#endregion
#region Apply Stability Assist
if (allowPitchStabilityAssist)
{
// Get input from pitch PID controller and apply it
momentInput.x += sFlightAssistPitchPIDController.RequiredInput(0f, stabilityCurrentPitch, _deltaTime);
}
if (allowYawStabilityAssist)
{
// Get input from yaw PID controller and apply it
momentInput.y += sFlightAssistYawPIDController.RequiredInput(0f, stabilityCurrentYaw, _deltaTime);
}
if (allowRollStabilityAssist)
{
// Get input from roll PID controller and apply it
momentInput.z += sFlightAssistRollPIDController.RequiredInput(0f, stabilityCurrentRoll, _deltaTime);
}
#endregion
}
if (shipPhysicsModelPhysicsBased)
{
// Scale moment inputs in physics-based mode - they generally need to be a lot less than force inputs
momentInput.x *= pitchPower;
momentInput.y *= yawPower;
momentInput.z *= rollPower;
// Clamp moment inputs to be between -1 and 1. We don't do this in arcade mode so that moment assists/limits
// can use more than the specified maximum moments if necessary
if (momentInput.x > 1f) { momentInput.x = 1f; }
else if (momentInput.x < -1f) { momentInput.x = -1f; }
if (momentInput.y > 1f) { momentInput.y = 1f; }
else if (momentInput.y < -1f) { momentInput.y = -1f; }
if (momentInput.z > 1f) { momentInput.z = 1f; }
else if (momentInput.z < -1f) { momentInput.z = -1f; }
}
// Force inputs
// - Translational flight assist (physics-based only)
// - Stick to ground surface (physics-based only: arcade equivalent is applied separately)
if (shipPhysicsModelPhysicsBased)
{
// Translational flight assist: Input to counteract translational velocity
// Activate translational flight assist if pilot releases an input
// or if input is in direction opposing velocity
if ((pilotForceInput.x > 0f) == (localVelocity.x < 0f) || pilotForceInput.x == 0f)
{
// Calculate the flight assist value on this axis
flightAssistValue = -localVelocity.x / 100f * translationalFlightAssistStrength;
if (flightAssistValue > 1f) { flightAssistValue = 1f; }
else if (flightAssistValue < -1f) { flightAssistValue = -1f; }
// Choose whichever value has the highest absolute value: The flight assist value or the pilot input value
if ((flightAssistValue > 0f ? flightAssistValue : -flightAssistValue) >
(pilotForceInput.x > 0f ? pilotForceInput.x : -pilotForceInput.x)) { forceInput.x = flightAssistValue; }
else { forceInput.x = pilotForceInput.x; }
}
else { forceInput.x = pilotForceInput.x; }
if (stickToGroundSurfaceEnabled)
{
// Get input from ground distance PID controller
forceInput.y = groundDistPIDController.RequiredInput(targetGroundDistance, currentPerpendicularGroundDist, _deltaTime);
// NOTE: This hasn't been tested yet in physics-model mode
if (avoidGroundSurface)
{
// Only override user input if the ship is going below the targetGroundDistance
forceInput.y = forceInput.y > 0f ? forceInput.y : pilotForceInput.y;
}
}
else
{
// Activate translational flight assist if pilot releases an input
// or if input is in direction opposing velocity
if ((pilotForceInput.y > 0f) == (localVelocity.y < 0f) || pilotForceInput.y == 0f)
{
// Calculate the flight assist value on this axis
flightAssistValue = -localVelocity.y / 100f * translationalFlightAssistStrength;
if (flightAssistValue > 1f) { flightAssistValue = 1f; }
else if (flightAssistValue < -1f) { flightAssistValue = -1f; }
// Choose whichever value has the highest absolute value: The flight assist value or the pilot input value
if ((flightAssistValue > 0f ? flightAssistValue : -flightAssistValue) >
(pilotForceInput.y > 0f ? pilotForceInput.y : -pilotForceInput.y)) { forceInput.y = flightAssistValue; }
else { forceInput.y = pilotForceInput.y; }
}
else { forceInput.y = pilotForceInput.y; }
}
// Force input on z-axis is always just pilot input
forceInput.z = pilotForceInput.z;
}
else
{
forceInput.x = pilotForceInput.x;
// In arcade mode, stick-to-ground force isn't achieved through thrusters
// Currently this implementation won't allow for thrust pushing up/down from ground
// to activate particle trails in non-physics-based mode...
forceInput.y = stickToGroundSurfaceEnabled ? 0f : pilotForceInput.y;
forceInput.z = pilotForceInput.z;
}
#region Brake flight assist
// Pilot or AI releases input and ship is moving forwards or backwards
// Works within the speed range configured.
// Does not make adjustments if velocity is very near zero.
if (brakeFlightAssistStrength > 0f && forceInput.z == 0f && localVelocity.z > brakeFlightAssistMinSpeed && localVelocity.z < brakeFlightAssistMaxSpeed && (localVelocity.z < -0.01f || localVelocity.z > 0.01f))
{
// Max brakeFlightAssistStrength is 10, so div by 10 (x 0.1)
// Apply thrust in the opposite direction to current z-axis velocity
flightAssistValue = (localVelocity.z < 0f ? brakeFlightAssistStrength : -brakeFlightAssistStrength) * 0.1f;
// Dampen based on how close velo is to 0 using a x^2 curve when velo between -10 and +10 m/s.
if (localVelocity.z > 0f && localVelocity.z < 10f) { flightAssistValue *= localVelocity.z / 10f; }
else if (localVelocity.z < 0f && localVelocity.z > -10f) { flightAssistValue *= localVelocity.z / -10f; }
// Clamp -1.0 to 1.0
if (flightAssistValue > 1f) { flightAssistValue = 1f; }
else if (flightAssistValue < -1f) { flightAssistValue = -1f; }
forceInput.z = flightAssistValue;
}
#endregion
#region Input Control - 2.5D
// TODO probably can significantly optimise the input control axis code
if ((int)inputControlAxis > 0)
{
// Y axis for side-view OR top-down flight
if ((int)inputControlAxis == 2)
{
// Project the transform right direction onto the XZ plane
Vector3 flatTrfmRight = TransformRight;
flatTrfmRight.y = 0f;
// Project the transform forward direction onto the XZ plane...
Vector3 targetForward = TransformForward;
targetForward.y = 0f;
// ... then onto the plane of the ship's up and forwards directions while keeping it on the XZ plane
// To do this step we subtract the projection of this vector onto flatTrfmRight
targetForward -= (Vector3.Dot(targetForward, flatTrfmRight) / Vector3.Dot(flatTrfmRight, flatTrfmRight)) * flatTrfmRight;
// Measure the angle from this projected forwards direction (which will be the target) to the current forwards direction
float inputControlLimitAngleDelta = Vector3.SignedAngle(targetForward, TransformForward, TransformRight);
if (shipPhysicsModelPhysicsBased)
{
// Physics-based: Set the inputs to counteract any displacement from the target position/rotation
forceInput.y = inputAxisForcePIDController.RequiredInput(inputControlLimit, TransformPosition.y, _deltaTime);
momentInput.x = inputAxisMomentPIDController.RequiredInput(0f, inputControlLimitAngleDelta, _deltaTime);
}
else
{
// Arcade: Use arcade force/moment to counteract any displacement from the target position/rotation
arcadeForceVector.y += inputAxisForcePIDController.RequiredInput(inputControlLimit, TransformPosition.y, _deltaTime);
arcadeMomentVector.x += inputAxisMomentPIDController.RequiredInput(0f, inputControlLimitAngleDelta, _deltaTime);
// Set inputs to zero
forceInput.y = 0f;
momentInput.x = 0f;
}
}
// X axis for side-scroller-like flight
else if ((int)inputControlAxis == 1)
{
// Calculate the normal to the plane we want to remain in
Vector3 targetInputControlPlaneNormal = Quaternion.Euler(0f, inputControlForwardAngle, 0f) * Vector3.right;
// Project the transform up direction onto the YZ plane
Vector3 flatTrfmUp = TransformUp;
flatTrfmUp -= Vector3.Project(flatTrfmUp, targetInputControlPlaneNormal);
// Project the transform forward direction onto the YZ plane...
Vector3 targetForward = TransformForward;
targetForward -= Vector3.Project(targetForward, targetInputControlPlaneNormal);
// ... then onto the plane of the ship's right and forwards directions while keeping it on the YZ plane
// To do this step we subtract the projection of this vector onto flatTrfmUp
targetForward -= (Vector3.Dot(targetForward, flatTrfmUp) / Vector3.Dot(flatTrfmUp, flatTrfmUp)) * flatTrfmUp;
// Measure the angle from this projected forwards direction (which will be the target) to the current forwards direction
float inputControlLimitAngleDelta = Vector3.SignedAngle(targetForward, TransformForward, TransformUp);
if (shipPhysicsModelPhysicsBased)
{
// Physics-based: Set the inputs to counteract any displacement from the target position/rotation
forceInput.x = inputAxisForcePIDController.RequiredInput(inputControlLimit, TransformPosition.x, _deltaTime);
momentInput.y = inputAxisMomentPIDController.RequiredInput(0f, inputControlLimitAngleDelta, _deltaTime);
}
else
{
// Arcade: Use arcade force/moment to counteract any displacement from the target position/rotation
arcadeForceVector.x += inputAxisForcePIDController.RequiredInput(inputControlLimit, TransformPosition.x, _deltaTime);
arcadeMomentVector.y += inputAxisMomentPIDController.RequiredInput(0f, inputControlLimitAngleDelta, _deltaTime);
// Set inputs to zero
forceInput.x = 0f;
momentInput.y = 0f;
}
}
}
#endregion
#endregion
#if SSC_SHIP_DEBUG
float inputEndTime = Time.realtimeSinceStartup;
float thrusterStartTime = Time.realtimeSinceStartup;
#endif
#region Thrusters
if (thrusterList != null)
{
componentListSize = thrusterList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
// Get the thruster we are concerned with
thruster = thrusterList[componentIndex];
// Calculate thruster input - for this we need to know what type of thruster
// this is so that we can read the right input/s
// First set thruster input to zero
thruster.currentInput = 0f;
// Thrusters with max heat (temperature) or with no fuel produce no thrust etc.
// Check heat before fuels, so that if we run out of fuel, thruster
// can still cool down.
if (thruster.heatLevel >= 100f || (useCentralFuel ? centralFuelLevel <= 0f : thruster.fuelLevel <= 0f))
{
// Check if we need to cool the thruster down
thruster.ManageHeat(_deltaTime);
continue;
}
// Then add inputs for force/moments to give a final input...
switch (thruster.forceUse)
{
// Not a force thruster
case 0: break;
// Forward thruster
case 1: thruster.currentInput += (forceInput.z > 0f ? forceInput.z : 0f); break;
// Backward thruster
case 2: thruster.currentInput += (forceInput.z < 0f ? -forceInput.z : 0f); break;
// Upward thruster
case 3: thruster.currentInput += (forceInput.y > 0f ? forceInput.y : 0f); break;
// Downward thruster
case 4: thruster.currentInput += (forceInput.y < 0f ? -forceInput.y : 0f); break;
// Rightward thruster
case 5: thruster.currentInput += (forceInput.x > 0f ? forceInput.x : 0f); break;
// Leftward thruster
case 6: thruster.currentInput += (forceInput.x < 0f ? -forceInput.x : 0f); break;
// Not a valid thruster type
#if UNITY_EDITOR
default: Debug.Log("Ship - Invalid thruster force use: " + thruster.forceUse.ToString()); break;
#endif
}
// Only allow thrusters to affect rotation when using the physics-based model
if (shipPhysicsModelPhysicsBased)
{
// Addition of moment inputs can be negative i.e. if we need to rotate in the opposite direction, add weighting
// to tell this thruster not to activate
switch (thruster.primaryMomentUse)
{
// Not a moment thruster
case 0: break;
// Positive roll thruster
case 1: if (momentInput.z / rollPower >= -(1f - (steeringThrusterPriorityLevel * 0.9f))) { thruster.currentInput += momentInput.z; } else { thruster.currentInput = 0f; } break;
// Negative roll thruster
case 2: if (momentInput.z / rollPower <= (1f - (steeringThrusterPriorityLevel * 0.9f))) { thruster.currentInput -= momentInput.z; } else { thruster.currentInput = 0f; } break;
// Positive pitch thruster
case 3: if (momentInput.x / pitchPower >= -(1f - (steeringThrusterPriorityLevel * 0.9f))) { thruster.currentInput += momentInput.x; } else { thruster.currentInput = 0f; } break;
// Negative pitch thruster
case 4: if (momentInput.x / pitchPower <= (1f - (steeringThrusterPriorityLevel * 0.9f))) { thruster.currentInput -= momentInput.x; } else { thruster.currentInput = 0f; } break;
// Positive yaw thruster
case 5: if (momentInput.y / yawPower >= -(1f - (steeringThrusterPriorityLevel * 0.9f))) { thruster.currentInput += momentInput.y; } else { thruster.currentInput = 0f; } break;
// Negative yaw thruster
case 6: if (momentInput.y / yawPower <= (1f - (steeringThrusterPriorityLevel * 0.9f))) { thruster.currentInput -= momentInput.y; } else { thruster.currentInput = 0f; } break;
// Not a valid thruster type
#if UNITY_EDITOR
default: Debug.Log("Ship - Invalid thruster moment use: " + thruster.primaryMomentUse.ToString()); break;
#endif
}
switch (thruster.secondaryMomentUse)
{
// Not a moment thruster
case 0: break;
// Positive roll thruster
case 1: thruster.currentInput += momentInput.z; break;
// Negative roll thruster
case 2: thruster.currentInput -= momentInput.z; break;
// Positive pitch thruster
case 3: thruster.currentInput += momentInput.x; break;
// Negative pitch thruster
case 4: thruster.currentInput -= momentInput.x; break;
// Positive yaw thruster
case 5: thruster.currentInput += momentInput.y; break;
// Negative yaw thruster
case 6: thruster.currentInput -= momentInput.y; break;
// Not a valid thruster type
#if UNITY_EDITOR
default: Debug.Log("Ship - Invalid thruster moment use: " + thruster.secondaryMomentUse.ToString()); break;
#endif
}
}
// Calculate thrust force
// Allow for the thruster to be throttled up and down over time
// Input is clamped between 0 and 1 (can only act within physical constraints of thruster)
thruster.SmoothThrusterInput(_deltaTime);
thrustForce = thruster.thrustDirectionNormalised * thruster.maxThrust * thruster.currentInput;
if (useCentralFuel)
{
if (thruster.fuelBurnRate > 0f && thruster.currentInput > 0f)
{
// Burn fuel independently to the health level. A damaged thruster will burn the same
// amount of fuel but produce less thrust
SetFuelLevel(centralFuelLevel - (thruster.currentInput * thruster.fuelBurnRate * _deltaTime));
}
}
else
{
thruster.BurnFuel(_deltaTime);
}
thruster.ManageHeat(_deltaTime);
// Adjust for thruster damage
if (!shipDamageModelIsSimple && thruster.damageRegionIndex > -1)
{
thrustForce *= thruster.CurrentPerformance;
}
// Add calculated thrust force to local resultant force and moment
localResultantForce += thrustForce;
// Only allow thrusters to affect rotation when using the physics-based model
if (shipPhysicsModelPhysicsBased)
{
localResultantMoment += Vector3.Cross(thruster.relativePosition - centreOfMass, thrustForce);
}
}
}
#endregion
#if SSC_SHIP_DEBUG
float thrusterEndTime = Time.realtimeSinceStartup;
float aeroStartTime = Time.realtimeSinceStartup;
#endif
#region Boost
// Is boost currently in operation?
if (boostTimer > 0f)
{
boostTimer -= _deltaTime;
localResultantForce += boostDirection * boostForce;
}
#endregion
#region Aerodynamics
// Dynamic viscosity of air at 25 deg C and 1 atm: 18.37 x 10^-6 Pa s
// Density of air on earth at low altitudes: 1.293 kg/m^3
// Aircraft head-on drag coefficient: ~0.05
if (mediumDensity > 0f)
{
//float pDragStartTime = Time.realtimeSinceStartup;
#region Profile Drag
// Profile drag calculation
// Calculate local drag force vector
// Drag force magnitude = 0.5 * fluid density * velocity squared * drag coefficient * cross-sectional area
localDragForce = 0.5f * mediumDensity * -localVelocity;
localDragForce.x *= dragCoefficients.x * dragReferenceAreas.x * (localVelocity.x > 0 ? localVelocity.x : -localVelocity.x);
localDragForce.y *= dragCoefficients.y * dragReferenceAreas.y * (localVelocity.y > 0 ? localVelocity.y : -localVelocity.y);
localDragForce.z *= dragCoefficients.z * dragReferenceAreas.z * (localVelocity.z > 0 ? localVelocity.z : -localVelocity.z);
// Add calculated drag force to local resultant force
localResultantForce += localDragForce;
if (!disableDragMoments)
{
// Add calculated drag force to local resultant moment
// Centre of drag is precalculated separately for each axis
localResultantMoment.x += ((centreOfDragXMoment.y - centreOfMass.y) * localDragForce.z) * dragXMomentMultiplier;
localResultantMoment.x += ((centreOfDragXMoment.z - centreOfMass.z) * -localDragForce.y) * dragXMomentMultiplier;
localResultantMoment.y += ((centreOfDragYMoment.x - centreOfMass.x) * -localDragForce.z) * dragYMomentMultiplier;
localResultantMoment.y += ((centreOfDragYMoment.z - centreOfMass.z) * localDragForce.x) * dragYMomentMultiplier;
localResultantMoment.z += ((centreOfDragZMoment.x - centreOfMass.x) * localDragForce.y) * dragZMomentMultiplier;
localResultantMoment.z += ((centreOfDragZMoment.y - centreOfMass.y) * -localDragForce.x) * dragZMomentMultiplier;
}
#endregion
//float pDragEndTime = Time.realtimeSinceStartup;
//float aDragStartTime = Time.realtimeSinceStartup;
#region Angular Profile Drag
// Angular drag calculation
if (angularDragFactor > 0f || shipPhysicsModelPhysicsBased)
{
// TODO: Could the length, width and height be precalculated?
// Step 1: We are modelling the drag as if it is occuring on a rectangular prism,
// so calculate the hypothetical length, width and height of that prism from the given areas of each side
dragLength = (float)Math.Sqrt(dragReferenceAreas.x * dragReferenceAreas.y / dragReferenceAreas.z);
dragWidth = dragReferenceAreas.y / dragLength;
dragHeight = dragReferenceAreas.z / dragWidth;
// Step 2: Calculate the angular drag moment on each axis
// TODO: Re-comment this section
// TODO: Could these values be precalculated?
quarticLengthXProj = IntegerPower((dragLength / 2) + centreOfMass.z - centreOfDragYMoment.z, 4) + IntegerPower((dragLength / 2) - centreOfMass.z + centreOfDragYMoment.z, 4);
quarticLengthYProj = IntegerPower((dragLength / 2) + centreOfMass.z - centreOfDragXMoment.z, 4) + IntegerPower((dragLength / 2) - centreOfMass.z + centreOfDragXMoment.z, 4);
quarticWidthYProj = IntegerPower((dragWidth / 2) + centreOfMass.x - centreOfDragZMoment.x, 4) + IntegerPower((dragWidth / 2) - centreOfMass.x + centreOfDragZMoment.x, 4);
quarticWidthZProj = IntegerPower((dragWidth / 2) + centreOfMass.x - centreOfDragYMoment.x, 4) + IntegerPower((dragWidth / 2) - centreOfMass.x + centreOfDragYMoment.x, 4);
quarticHeightXProj = IntegerPower((dragHeight / 2) + centreOfMass.y - centreOfDragZMoment.y, 4) + IntegerPower((dragHeight / 2) - centreOfMass.y + centreOfDragZMoment.y, 4);
quarticHeightZProj = IntegerPower((dragHeight / 2) + centreOfMass.y - centreOfDragXMoment.y, 4) + IntegerPower((dragHeight / 2) - centreOfMass.y + centreOfDragXMoment.y, 4);
if (shipPhysicsModelPhysicsBased) { angularDragMultiplier = 1f; }
else { angularDragMultiplier = (float)Math.Pow(10f, angularDragFactor - 1f); }
// Formula: For a rotating rectangle with the pivot at one end:
// Drag moment = 1/8 * fluid density * angular velocity squared * height * drag coefficient * length to the power of four
// X-axis angular drag
localResultantMoment.x += mediumDensity / 8f * localAngularVelocity.x * localAngularVelocity.x * dragWidth *
dragCoefficients.y * quarticLengthYProj * (localAngularVelocity.x > 0f ? -1f : 1f) * angularDragMultiplier;
localResultantMoment.x += mediumDensity / 8f * localAngularVelocity.x * localAngularVelocity.x * dragWidth *
dragCoefficients.z * quarticHeightZProj * (localAngularVelocity.x > 0f ? -1f : 1f) * angularDragMultiplier;
// Y-axis angular drag
localResultantMoment.y += mediumDensity / 8f * localAngularVelocity.y * localAngularVelocity.y * dragHeight *
dragCoefficients.x * quarticLengthXProj * (localAngularVelocity.y > 0f ? -1f : 1f) * angularDragMultiplier;
localResultantMoment.y += mediumDensity / 8f * localAngularVelocity.y * localAngularVelocity.y * dragHeight *
dragCoefficients.z * quarticWidthZProj * (localAngularVelocity.y > 0f ? -1f : 1f) * angularDragMultiplier;
// Z-axis angular drag
localResultantMoment.z += mediumDensity / 8f * localAngularVelocity.z * localAngularVelocity.z * dragLength *
dragCoefficients.y * quarticWidthYProj * (localAngularVelocity.z > 0f ? -1f : 1f) * angularDragMultiplier;
localResultantMoment.z += mediumDensity / 8f * localAngularVelocity.z * localAngularVelocity.z * dragLength *
dragCoefficients.x * quarticHeightXProj * (localAngularVelocity.z > 0f ? -1f : 1f) * angularDragMultiplier;
}
#endregion
//float aDragEndTime = Time.realtimeSinceStartup;
//float wingsStartTime = Time.realtimeSinceStartup;
// Calculate the "true airspeed" - this is just velocity in forwards direction
aerodynamicTrueAirspeed = localVelocity.z;
#region Wings
// Wing simulation
componentListSize = wingList == null ? 0 : wingList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
// Get the wing we are concerned with
wing = wingList[componentIndex];
// Only calculate lift if some component of the velocity is in the negative airflow direction
if (aerodynamicTrueAirspeed > 0f)
{
//float aoaStartTime = Time.realtimeSinceStartup;
#region Angle of Attack
// OLD UNOPTIMISED CODE. TODO: Delete
////Vector3 wingPlaneNormal = Vector3.Cross(wing.liftDirection, wing.airflowDirection);
//wingPlaneNormal = Vector3.Cross(wing.liftDirection, Vector3.back);
//localVeloInWingPlane = Vector3.ProjectOnPlane(localVelocity, wingPlaneNormal);
//// Is this the right direction?
////float angleOfAttack = Vector3.SignedAngle(-wing.airflowDirection, localVeloInWingPlane, wingPlaneNormal) + wing.angleOfAttack;
//wingAngleOfAttack = Vector3.SignedAngle(Vector3.forward, localVeloInWingPlane, wingPlaneNormal) + wing.angleOfAttack;
// Project the local velocity into the plane containing the lift direction and the forwards (negative airflow) direction
// Take the cross product of lift direction and forwards direction to get the normal of the plane
// Normalise it for the dot product in the next step
//wingPlaneNormal = Vector3.Cross(wing.liftDirectionNormalised, Vector3.forward).normalized;
wingPlaneNormal.x = wing.liftDirectionNormalised.y;
wingPlaneNormal.y = -wing.liftDirectionNormalised.x;
wingPlaneNormal.z = 0f;
wingPlaneNormal.Normalize();
//localVeloInWingPlane = (localVelocity - (Vector3.Dot(localVelocity, wingPlaneNormal) * wingPlaneNormal)).normalized;
// Multiplty the dot product of the local velocity and the wing plane normal with the wing plane normal to
// get the component of the local velocity in the direction of the wing plane normal, then subtract
// this vector from the local velocity to project it into the desired plane
// Then normalise the vector for use in the dot product of the next step
// NOTE: The dot product does not require a z component as the z component of wing plane normal will always be zero,
// as it is perpendicular to the forwards direction
localVeloInWingPlane = (localVelocity - (((localVelocity.x * wingPlaneNormal.x) + (localVelocity.y * wingPlaneNormal.y)) * wingPlaneNormal)).normalized;
// The angle of attack of the wing is equal to:
// The angle between the lift direction and the velocity in the wing plane (computed via arccos of dot product) MINUS
// The angle between the lift direction and the forwards direction PLUS
// The angle of attack of the wing due to camber
// Resultant of the first thing minus the second gives the angle between forwards direction and velocity, with
// forwards above velocity resulting in positive numbers
wingAngleOfAttack = ((float)(Math.Acos(Mathf.Clamp((wing.liftDirectionNormalised.x * localVeloInWingPlane.x) +
(wing.liftDirectionNormalised.y * localVeloInWingPlane.y) +
(wing.liftDirectionNormalised.z * localVeloInWingPlane.z), -1f, 1f)) -
Math.Acos(Mathf.Clamp(wing.liftDirectionNormalised.z, -1f, 1f))) * Mathf.Rad2Deg) +
wing.angleOfAttack;
#endregion
//float aoaEndTime = Time.realtimeSinceStartup;
//float lcStartTime = Time.realtimeSinceStartup;
#region Lift Coefficient
phi0 = (20f * wingStallEffect) - 25f;
phi1 = 15f;
phi2 = 45f - (25f * wingStallEffect);
phi3 = 75f - (50f * wingStallEffect);
//// If angle of attack is not between the min and max wing angles, the wing will stall and not produce any lift
//if (angleOfAttack > -5f && angleOfAttack < 15f)
//{
// // Approximation of lift coefficient, adapted from thin airfoil theory:
// // Lift coefficient = (m * angle of attack) + d
// // Where m = max lift coefficient / (max wing angle - min wing angle) and d = -m * min wing angle
// // Max lift coefficient = 1.5, min wing angle = -5 degrees, max wing angle = 15 degrees
// float liftCoefficient = (0.075f * angleOfAttack) + 0.375f;
//}
// TODO: NEED A COMMENT FOR BELOW THEORY
// Also should look at whether I should just enable/disable stalling
// and just use predetermined values
// Also, possibly stall angle actually comes from chord (wing width)?
// Stall region
if (wingAngleOfAttack < phi0) { liftCoefficient = 0f; }
// Increasing lift region
else if (wingAngleOfAttack < phi1) { liftCoefficient = 1.6f * (wingAngleOfAttack - phi0) / (phi1 - phi0); }
// Maximum lift region
else if (wingAngleOfAttack < phi2) { liftCoefficient = 1.6f; }
// Falloff region
else if (wingAngleOfAttack < phi3) { liftCoefficient = 1.6f * (wingAngleOfAttack - phi3) / (phi2 - phi3); }
// Stall region
else { liftCoefficient = 0f; }
#endregion
//float lcEndTime = Time.realtimeSinceStartup;
//float lidStartTime = Time.realtimeSinceStartup;
#region Lift and Induced Drag
// Lift force magnitude = 0.5 * fluid density * velocity squared * planform wing area * coefficient of lift
liftForceMagnitude = 0.5f * mediumDensity * aerodynamicTrueAirspeed * aerodynamicTrueAirspeed * (wing.span * wing.chord * (float)Math.Cos(wingAngleOfAttack * Mathf.Deg2Rad)) * liftCoefficient;
// Adjust for wing damage
if (!shipDamageModelIsSimple && wing.damageRegionIndex > -1)
{
liftForceMagnitude *= wing.CurrentPerformance;
}
localLiftForce = liftForceMagnitude * wing.liftDirectionNormalised;
// Induced drag force magnitude = 2 * lift squared / (fluid density * velocity squared * pi * wingspan squared)
localInducedDragForce = 2f * liftForceMagnitude * liftForceMagnitude /
(mediumDensity * aerodynamicTrueAirspeed * aerodynamicTrueAirspeed * Mathf.PI * wing.span * wing.span) * Vector3.back;
#endregion
//float lidEndTime = Time.realtimeSinceStartup;
//float forceStartTime = Time.realtimeSinceStartup;
// Add calculated lift and induced drag forces to local resultant force and moment
localResultantForce += localLiftForce + localInducedDragForce;
if (shipPhysicsModelPhysicsBased)
{
localResultantMoment += Vector3.Cross(wing.relativePosition - centreOfMass, localLiftForce + localInducedDragForce);
}
//float forceEndTime = Time.realtimeSinceStartup;
//if (componentIndex == 0)
//{
// Debug.Log("| Angle of attack: " + ((aoaEndTime - aoaStartTime) * 1000f).ToString("0.0000") + " ms | " +
// "Lift coefficient: " + ((lcEndTime - lcStartTime) * 1000f).ToString("0.0000") + " ms | " +
// "Lift and induced drag: " + ((lidEndTime - lidStartTime) * 1000f).ToString("0.0000") + " ms | " +
// "Force and moment: " + ((forceEndTime - forceStartTime) * 1000f).ToString("0.0000") + " ms | ");
//}
}
}
#endregion
//float wingsEndTime = Time.realtimeSinceStartup;
//float controlStartTime = Time.realtimeSinceStartup;
// Control surfaces only available in physics-based mode
if (shipPhysicsModelPhysicsBased)
{
#region Control Surfaces
// Control surface simulation
componentListSize = controlSurfaceList == null ? 0 : controlSurfaceList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
// Get the control surface we are concerned with
controlSurface = controlSurfaceList[componentIndex];
#region Control Surface Type
switch (controlSurface.type)
{
case ControlSurface.ControlSurfaceType.Aileron:
// Aileron
controlSurfaceMovementAxis = Vector3.up;
controlSurfaceInput = momentInput.z * (controlSurface.relativePosition.x > 0f ? 1f : -1f);
break;
case ControlSurface.ControlSurfaceType.Elevator:
// Elevator
controlSurfaceMovementAxis = Vector3.up;
controlSurfaceInput = -momentInput.x;
break;
case ControlSurface.ControlSurfaceType.Rudder:
// Rudder
controlSurfaceMovementAxis = Vector3.right;
controlSurfaceInput = momentInput.y;
break;
case ControlSurface.ControlSurfaceType.AirBrake:
// Air brake
controlSurfaceMovementAxis = Vector3.zero;
controlSurfaceInput = forceInput.z < 0f ? -forceInput.z : 0f;
break;
#if UNITY_EDITOR
default:
// Custom (TODO)
Debug.Log("Ship - Custom control surfaces not supported yet.");
break;
#endif
}
#endregion
// When there is no input the control surface does not apply any force
if (controlSurfaceInput != 0f)
{
#region Calculate Lift and Drag Forces
// Calculate the angle the control surface is inclined at
// Absolute value of controlSurfaceInput is used as we only want magnitude - direction is added in a later step
controlSurfaceAngle = Mathf.PI / 2f * (controlSurfaceInput > 0f ? controlSurfaceInput : -controlSurfaceInput);
// Calculate the magnitude of the change (delta) in lift and drag caused by the inclination of the control surface
// This is essentially the change in planform area (for lift) and the change in profile area (for drag)
// multiplied by the usual aerodynamic formulae for each
// TODO: Should probably work out something mildly more accurate for lift coefficient (currently is just 1.6)
// TODO: Should probably have something for a drag coefficient as well (currently is just 2)
// Then convert the forces into vectors by multiplying by their directions
// TODO: Include relative position based on inclination
// - I think this means the position of the centre of the CS adjusted for where it has been rotated to?
if (controlSurfaceMovementAxis != Vector3.zero)
{
controlSurfaceLiftDelta = (1f - (float)Math.Cos(controlSurfaceAngle)) * 0.5f * mediumDensity *
aerodynamicTrueAirspeed * aerodynamicTrueAirspeed * controlSurface.chord * controlSurface.span * 1.6f;
}
else { controlSurfaceLiftDelta = 0f; }
controlSurfaceDragDelta = (float)Math.Sin(controlSurfaceAngle) * 0.5f * mediumDensity *
aerodynamicTrueAirspeed * aerodynamicTrueAirspeed * controlSurface.chord * controlSurface.span * 2f;
// Adjust for control surface damage
if (!shipDamageModelIsSimple && controlSurface.damageRegionIndex > -1)
{
controlSurfaceLiftDelta *= controlSurface.CurrentPerformance;
controlSurfaceDragDelta *= controlSurface.CurrentPerformance;
}
localLiftForce = controlSurfaceLiftDelta * (controlSurfaceInput > 0f ? -1f : 1f) * controlSurfaceMovementAxis;
localDragForce = controlSurfaceDragDelta * (aerodynamicTrueAirspeed > 0f ? 1f : -1f) * Vector3.back;
#endregion
// Adjust relative position to take into account current inclination of control surface
controlSurfaceRelativePosition = controlSurface.relativePosition + (controlSurfaceMovementAxis *
controlSurface.chord * 0.5f * (float)Math.Sin(controlSurfaceAngle));
// Add calculated lift and drag forces to resultant force and moment
localResultantForce += localLiftForce + localDragForce;
localResultantMoment += Vector3.Cross(controlSurfaceRelativePosition - centreOfMass, localLiftForce + localDragForce);
}
}
#endregion
}
//float controlEndTime = Time.realtimeSinceStartup;
//Debug.Log("| Profile Drag: " + ((pDragEndTime - pDragStartTime) * 1000f).ToString("0.0000") + " ms | " +
// "Angular Drag: " + ((aDragEndTime - aDragStartTime) * 1000f).ToString("0.0000") + " ms | " +
// "Wings: " + ((wingsEndTime - wingsStartTime) * 1000f).ToString("0.0000") + " ms | " +
// "Control Surfaces: " + ((controlEndTime - controlStartTime) * 1000f).ToString("0.0000") + " ms | ");
}
#endregion
#if SSC_SHIP_DEBUG
float aeroEndTime = Time.realtimeSinceStartup;
float arcadeStartTime = Time.realtimeSinceStartup;
#endif
#region Arcade Physics Adjustments
if (!shipPhysicsModelPhysicsBased)
{
// Apply moments to match pitch, yaw and roll inputs, as well as for various arcade adjustments
// These include rotational flight assist and target pitch/roll moment
// Multiplication by inertia tensor is used so that we can use acceleration parameters instead of force parameters
localResultantMoment.x += ((momentInput.x * arcadePitchAcceleration) + arcadeMomentVector.x) * Mathf.Deg2Rad * rBodyInertiaTensor.x;
localResultantMoment.y += ((momentInput.y * arcadeYawAcceleration) + arcadeMomentVector.y) * Mathf.Deg2Rad * rBodyInertiaTensor.y;
localResultantMoment.z += ((momentInput.z * arcadeRollAcceleration) + arcadeMomentVector.z) * Mathf.Deg2Rad * rBodyInertiaTensor.z * -1f;
// TODO: Currently this doesn't take into account the force already being applied by the ship,
// calculated prior to this point
// Should it do that?
if (stickToGroundSurfaceEnabled)
{
// Calculate the limits for input for ground matching
float maxGroundDistInput = maxGroundMatchAcceleration;
if (useGroundMatchSmoothing)
{
// Previous values (for last two arguments): 50f, 2f
maxGroundDistInput = DampedMaxAccelerationInput(currentPerpendicularGroundDist, targetGroundDistance,
minGroundDistance, maxGroundDistance, centralMaxGroundMatchAcceleration, maxGroundMatchAcceleration, 200f, 0.5f);
}
// Set input limits
groundDistPIDController.SetInputLimits(-maxGroundDistInput, maxGroundDistInput);
// Get input from ground distance PID controller
groundDistInput = groundDistPIDController.RequiredInput(targetGroundDistance, currentPerpendicularGroundDist, _deltaTime);
//Debug.Log("Ground dist input: " + groundDistInput.ToString("000000.0"));
//Debug.Log("Current ground dist: " + currentPerpendicularGroundDist.ToString("00.00"));
// Are we preventing an arcade model ship from going below the target (minimum) distnace?
if (avoidGroundSurface)
{
// Only override user input if the ship is going below the targetGroundDistance
stickToGroundSurfaceEnabled = groundDistInput > 0f;
}
}
if (stickToGroundSurfaceEnabled)
{
// Apply turning force counteracting velocity on x axis if pilot releases the input
// or if input is in direction opposing velocity
if ((forceInput.x > 0f) == (localVelocity.x < 0f) || forceInput.x == 0f)
{
// The force also includes a component to counteract gravity on this axis
arcadeForceVector.x = -((localVelocity.x * 0.5f / _deltaTime) + localGravityAcceleration.x);
}
else { arcadeForceVector.x = 0f; }
// When we are sticking to the ground surface don't apply turning force on y axis,
// as this force should be primarily handled by the force keeping the ship at the
// target distance from the ground
arcadeForceVector.y = 0f;
// TODO: Maybe want to have an option to allow force to act on z as well (resulting in very arcade-like handling)
arcadeForceVector.z = 0f;
// Clamp the magnitude of the turning force vector so that the force applied does not exceed
// the maximum on-ground turning acceleration. Multiply by mass to convert the acceleration into a force
arcadeForceVector = Vector3.ClampMagnitude(arcadeForceVector, arcadeMaxGroundTurningAcceleration) * mass;
// Moved above in 1.2.6
//// Calculate the limits for input for ground matching
//float maxGroundDistInput = maxGroundMatchAcceleration;
//if (useGroundMatchSmoothing)
//{
// // Previous values (for last two arguments): 50f, 2f
// maxGroundDistInput = DampedMaxAccelerationInput(currentPerpendicularGroundDist, targetGroundDistance,
// minGroundDistance, maxGroundDistance, centralMaxGroundMatchAcceleration, maxGroundMatchAcceleration, 200f, 0.5f);
//}
//// Set input limits
//groundDistPIDController.SetInputLimits(-maxGroundDistInput, maxGroundDistInput);
// Add a force acting in the direction of the ground normal to keep the ship at a target distance
// from the ground (the magnitude of the force is calculated by the associated PID controller)
arcadeForceVector += trfmInvRot * worldTargetPlaneNormal * groundDistInput * mass;
// pre-v1.2.6 (calculated above in 1.2.6+ as groundDistInput variable)
//arcadeForceVector += trfmInvRot * worldTargetPlaneNormal *
// groundDistPIDController.RequiredInput(targetGroundDistance, currentPerpendicularGroundDist, _deltaTime) * mass;
//localResultantForce.y += stickToGroundAcc * mass;
//Debug.Log("Dist: " + currentPerpendicularGroundDist.ToString("00.00") + " m, acc: " + stickToGroundAcc.ToString("000.00") + "m/s^2");
//Debug.Log("Speed: " + speed.ToString("0000") + " m/s");
}
else
{
// Apply turning force counteracting velocity on x and y axes if pilot releases the input
// or if input is in direction opposing velocity
if ((forceInput.x > 0f) == (localVelocity.x < 0f) || forceInput.x == 0f)
{
// The force also includes a component to counteract gravity on this axis
arcadeForceVector.x = -((localVelocity.x * 0.5f / _deltaTime) + localGravityAcceleration.x);
}
else { arcadeForceVector.x = 0f; }
if ((forceInput.y > 0f) == (localVelocity.y < 0f) || forceInput.y == 0f)
{
// The force also includes a component to counteract gravity on this axis
arcadeForceVector.y = -((localVelocity.y * 0.5f / _deltaTime) + localGravityAcceleration.y);
}
else { arcadeForceVector.y = 0f; }
// Maybe want to have an option to allow force to act on z as well (resulting in very arcade-like handling)
arcadeForceVector.z = 0f;
// Clamp the magnitude of the turning force vector so that the force applied does not exceed
// the maximum in-flight turning acceleration. Multiply by mass to convert the acceleration into a force (f = ma)
arcadeForceVector = Vector3.ClampMagnitude(arcadeForceVector, arcadeMaxFlightTurningAcceleration) * mass;
}
// Braking component for arcade mode - active when moving forward and braking
if (arcadeUseBrakeComponent && localVelocity.z > 0f && forceInput.z < 0f)
{
// Calculate magnitude of braking force using drag formula
arcadeBrakeForceMagnitude = 0.5f * (arcadeBrakeIgnoreMediumDensity ? 1f : mediumDensity) * localVelocity.z * localVelocity.z * arcadeBrakeStrength * -forceInput.z;
// Calculate min braking force (f = ma)
arcadeBrakeForceMinMagnitude = arcadeBrakeMinAcceleration * mass;
// Make sure min braking force doesn't overcompensate and cause the ship to start moving backwards
if (arcadeBrakeForceMinMagnitude > localVelocity.z / _deltaTime * mass)
{
arcadeBrakeForceMinMagnitude = localVelocity.z / _deltaTime * mass;
}
// Add braking force or min braking force - whichever is larger
arcadeForceVector.z -= arcadeBrakeForceMagnitude > arcadeBrakeForceMinMagnitude ? arcadeBrakeForceMagnitude : arcadeBrakeForceMinMagnitude;
}
// Add the calculated arcade force to the local resultant force
localResultantForce += arcadeForceVector;
}
#endregion
#if SSC_SHIP_DEBUG
float arcadeEndTime = Time.realtimeSinceStartup;
#endif
// Remember time of this fixed update
lastFixedUpdateTime = Time.time;
#if SSC_SHIP_DEBUG
//Debug.Log("| Init: " + ((initEndTime - initStartTime) * 1000f).ToString("0.0000") + " ms | " +
// "Ground: " + ((groundEndTime - groundStartTime) * 1000f).ToString("0.0000") + " ms | " +
// "Input: " + ((inputEndTime - inputStartTime) * 1000f).ToString("0.0000") + " ms | " +
// "Thrusters: " + ((thrusterEndTime - thrusterStartTime) * 1000f).ToString("0.0000") + " ms | " +
// "Aero: " + ((aeroEndTime - aeroStartTime) * 1000f).ToString("0.0000") + " ms | " +
// "Arcade: " + ((arcadeEndTime - arcadeStartTime) * 1000f).ToString("0.0000") + " ms | " +
// "Total: " + ((arcadeEndTime - initStartTime) * 1000f).ToString("0.0000") + " ms | ");
#endif
}
#endregion
#region Update Weapons And Damage
/// <summary>
/// Update weapons and damage information for this ship. Should be called during Update(), and have
/// UpdatePositionAndMovementData() called prior to it.
/// NOTE: We often hardcode the weaponType int rather than looking up the enum for the sake of performance.
/// </summary>
/// <param name="shipControllerManager"></param>
public void UpdateWeaponsAndDamage(SSCManager shipControllerManager)
{
#region Initialisation
float _deltaTime = Time.deltaTime;
// Get the ship damage model
shipDamageModelIsSimple = shipDamageModel == ShipDamageModel.Simple;
// Update the local reference, which is used also in MoveBeam()
if (sscManager == null) { sscManager = shipControllerManager; }
#endregion
#region Weapons
componentListSize = weaponList == null ? 0 : weaponList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
// Get the weapon we are concerned with
weapon = weaponList[componentIndex];
if (weapon == null) { continue; }
// Check that the weapon has had a projectile ID or beam ID assigned
// Assumes that the IDs have been correctly assigned based on the weaponType.
if (weapon.projectilePrefabID >= 0 || weapon.beamPrefabID >= 0)
{
// Lookup the enumeration once
weaponTypeInt = weapon.weaponTypeInt;
weaponFiringButtonInt = (int)weapon.firingButton;
// Does the weapon need to cool down??
weapon.ManageHeat(_deltaTime, 0f);
// If the weapon is not connected to a firing mechanism we can probably ignore the reload timer
// Weapons with no health or performance cannot aim, reload, move turrets, or fire.
if (weaponFiringButtonInt > 0 && weapon.health > 0f && weapon.currentPerformance > 0f)
{
if (weaponTypeInt == Weapon.TurretProjectileInt || weaponTypeInt == Weapon.TurretBeamInt) { weapon.MoveTurret(worldVelocity, false); }
// Does the beam weapon need charging?
if ((weaponTypeInt == Weapon.FixedBeamInt || weaponTypeInt == Weapon.TurretBeamInt) && weapon.rechargeTime > 0f && weapon.chargeAmount < 1f)
{
weapon.chargeAmount += _deltaTime * 1f / weapon.rechargeTime;
if (weapon.chargeAmount > 1f) { weapon.chargeAmount = 1f; }
}
// Check if this projectile weapon is reloading or not
// Check if this beam weapon is powering-up or not
if (weapon.reloadTimer > 0f)
{
// If reloading (or powering-up), update reload timer
weapon.reloadTimer -= _deltaTime;
}
// Check if this weapon is damaged or not
else if (shipDamageModelIsSimple || weapon.damageRegionIndex < 0 || weapon.Health > 0f)
{
// If not reloading and not damaged, check if we should fire the weapon or not
// For autofiring weapons, this will return false.
bool isReadyToFire = (pilotPrimaryFireInput && weaponFiringButtonInt == weaponPrimaryFiringInt) || (pilotSecondaryFireInput && weaponFiringButtonInt == weaponSecondaryFiringInt);
// If this is auto-firing turret check if it is ready to fire
if (weaponFiringButtonInt == weaponAutoFiringInt && (weaponTypeInt == Weapon.TurretProjectileInt || weaponTypeInt == Weapon.TurretBeamInt))
{
// Is turret locked on target and in direct line of sight?
// NOTE: If check LoS is enabled, it will still fire if another enemy is between the turret and the weapon.target.
isReadyToFire = weapon.isLockedOnTarget && (!weapon.checkLineOfSight || WeaponHasLineOfSight(weapon));
// TODO - Is target in range?
}
// If not reloading and not damaged, check if we should fire the weapon or not
if (isReadyToFire && weapon.ammunition != 0)
{
// ==============================================================================
// NOTE: We need to do most of these steps also in MoveBeam(..). So the commented
// out methods below called in MoveBeam(..)
// ==============================================================================
// Get base weapon world position (not accounting for relative fire position)
//weaponWorldBasePosition = GetWeaponWorldBasePosition(weapon);
weaponWorldBasePosition = (trfmRight * weapon.relativePosition.x) +
(trfmUp * weapon.relativePosition.y) +
(trfmFwd * weapon.relativePosition.z) +
trfmPos;
// Get relative fire direction
weaponRelativeFireDirection = weapon.fireDirectionNormalised;
// If this is a turret, adjust relative fire direction based on turret rotation
if (weaponTypeInt == Weapon.TurretProjectileInt || weaponTypeInt == Weapon.TurretBeamInt)
{
// (turret rotation less ship rotate) - (original turret rotation less ship rotation) + relative fire direction
weaponRelativeFireDirection = (trfmInvRot * weapon.turretPivotX.rotation) * weapon.intPivotYInvRot * weaponRelativeFireDirection;
}
// Get weapon world fire direction
weaponWorldFireDirection = (trfmRight * weaponRelativeFireDirection.x) +
(trfmUp * weaponRelativeFireDirection.y) +
(trfmFwd * weaponRelativeFireDirection.z);
//weaponWorldFireDirection = GetWeaponWorldFireDirection(weapon);
// Start the firing cycle with no heat generated
float heatValue = 0f;
bool hasFired = false;
// Loop through all fire positions
int firePositionListCount = weapon.isMultipleFirePositions ? weapon.firePositionList.Count : 1;
for (int fp = 0; fp < firePositionListCount; fp++)
{
// Only fire if there is unlimited ammunition (-1) or greater than 0 projectiles available
if (weapon.ammunition != 0)
{
// If not unlimited ammo, decrement the quantity available
if (weapon.ammunition > 0) { weapon.ammunition--; }
// Check if there are multiple fire positions
if (weapon.isMultipleFirePositions)
{
// Get relative fire position
weaponRelativeFirePosition = weapon.firePositionList[fp];
}
else
{
// If there is only one fire position, relative position must be the zero vector
weaponRelativeFirePosition.x = 0f;
weaponRelativeFirePosition.y = 0f;
weaponRelativeFirePosition.z = 0f;
}
// If this is a turret, adjust relative fire position based on turret rotation
if (weaponTypeInt == Weapon.TurretProjectileInt || weaponTypeInt == Weapon.TurretBeamInt)
{
weaponRelativeFirePosition = (trfmInvRot * weapon.turretPivotX.rotation) * weaponRelativeFirePosition;
}
// Get weapon world fire position
weaponWorldFirePosition = weaponWorldBasePosition +
(trfmRight * weaponRelativeFirePosition.x) +
(trfmUp * weaponRelativeFirePosition.y) +
(trfmFwd * weaponRelativeFirePosition.z);
//weaponWorldFirePosition = GetWeaponWorldFirePosition(weapon, weaponWorldBasePosition, weaponRelativeFirePosition);
if (weaponTypeInt == Weapon.FixedBeamInt || weaponTypeInt == Weapon.TurretBeamInt)
{
// if the sequence number is 0, it means it is not active
if (weapon.beamItemKeyList[fp].beamSequenceNumber == 0)
{
InstantiateBeamParameters ibParms = new InstantiateBeamParameters()
{
beamPrefabID = weapon.beamPrefabID,
position = weaponWorldFirePosition,
fwdDirection = weaponWorldFireDirection,
upDirection = trfmUp,
shipId = shipId,
squadronId = squadronId,
weaponIndex = componentIndex,
firePositionIndex = fp,
beamSequenceNumber = 0,
beamPoolListIndex = -1
};
// Create a beam using the ship control Manager (SSCManager)
// Pass InstantiateBeamParameters by reference so we can get the beam index and sequence number back
BeamModule beamModule = shipControllerManager.InstantiateBeam(ref ibParms);
if (beamModule != null)
{
beamModule.callbackOnMove = MoveBeam;
// Retrieve the unique identifiers for the beam instance
weapon.beamItemKeyList[fp] = new SSCBeamItemKey(weapon.beamPrefabID, ibParms.beamPoolListIndex, ibParms.beamSequenceNumber);
// Immediately update the beam position (required for pooled beams that have previously been used)
MoveBeam(beamModule);
hasFired = true;
// Was a poolable muzzle fx spawned?
if (beamModule.muzzleEffectsItemKey.effectsObjectSequenceNumber > 0)
{
// Only get the transform if the muzzle EffectsModule has Is Reparented enabled.
Transform muzzleFXTrfm = shipControllerManager.GetEffectsObjectTransform(beamModule.muzzleEffectsItemKey.effectsObjectTemplateListIndex, beamModule.muzzleEffectsItemKey.effectsObjectPoolListIndex, true);
if (muzzleFXTrfm != null)
{
muzzleFXTrfm.SetParent(beamModule.transform);
}
}
}
}
}
else
{
// Create a new projectile using the ship controller Manager (SSCManager)
// Velocity is world velocity of ship plus relative velocity of weapon due to angular velocity
InstantiateProjectileParameters ipParms = new InstantiateProjectileParameters()
{
projectilePrefabID = weapon.projectilePrefabID,
position = weaponWorldFirePosition,
fwdDirection = weaponWorldFireDirection,
upDirection = trfmUp,
weaponVelocity = worldVelocity + Vector3.Cross(worldAngularVelocity, weaponWorldFirePosition - trfmPos),
gravity = gravitationalAcceleration,
gravityDirection = gravityDirection,
shipId = shipId,
squadronId = squadronId,
targetShip = weapon.isProjectileKGuideToTarget ? weapon.targetShip : null,
targetGameObject = weapon.isProjectileKGuideToTarget ? weapon.target : null,
targetguidHash = weapon.isProjectileKGuideToTarget ? weapon.targetguidHash : 0,
};
shipControllerManager.InstantiateProjectile(ref ipParms);
// For now, assume projectile was instantiated
// Heat value is inversely proportional to the firing interval (reload time)
if (weapon.reloadTime > 0f) { heatValue += 1f / weapon.reloadTime; };
hasFired = true;
// Was a poolable muzzle fx spawned?
if (ipParms.muzzleEffectsObjectPoolListIndex >= 0)
{
// Only get the transform if the muzzle EffectsModule has Is Reparented enabled.
Transform muzzleFXTrfm = shipControllerManager.GetEffectsObjectTransform(ipParms.muzzleEffectsObjectPrefabID, ipParms.muzzleEffectsObjectPoolListIndex, true);
if (muzzleFXTrfm != null)
{
if (weaponTypeInt == Weapon.TurretProjectileInt)
{
muzzleFXTrfm.SetParent(weapon.turretPivotX);
}
else
{
// This may be risky as it could create a dependency that
// causes GC and/or circular reference issues when the ShipControlModule is destroyed.
// Might need a better method of doing this in the future.
muzzleFXTrfm.SetParent(shipTransform);
}
}
}
}
// Set reload timer to reload time
weapon.reloadTimer = weapon.reloadTime;
// Did we just run out of ammo?
if (weapon.ammunition == 0 && callbackOnWeaponNoAmmo != null)
{
callbackOnWeaponNoAmmo.Invoke(this, weapon);
}
}
}
if (heatValue > 0f) { weapon.ManageHeat(_deltaTime, heatValue); }
// If the ship has a callback configured for the weapon, and it
// has fired at least one fire point, invoke the callback.
if (hasFired && callbackOnWeaponFired != null)
{
callbackOnWeaponFired.Invoke(this, weapon);
}
}
}
}
}
}
#endregion
#region Damage
if (!Destroyed())
{
if (respawningMode == RespawningMode.RespawnAtLastPosition)
{
// Update current respawn position and rotation
currentRespawnPosition = trfmPos;
currentRespawnRotation = trfmRot;
// Increment collision respawn position timer
collisionRespawnPositionTimer += _deltaTime;
// When timer reaches the interval amount...
if (collisionRespawnPositionTimer > collisionRespawnPositionDelay)
{
// Update current respawn position/rotation (from what was next)
currentCollisionRespawnPosition = nextCollisionRespawnPosition;
currentCollisionRespawnRotation = nextCollisionRespawnRotation;
// Set next respawn position/rotation from current position/rotation
nextCollisionRespawnPosition = trfmPos;
nextCollisionRespawnRotation = trfmRot;
// Reset the timer
collisionRespawnPositionTimer = 0f;
}
}
}
#endregion
}
#endregion
#endregion
#region Public API Methods
#region Damage API Methods
/// <summary>
/// When attaching an object to the ship, call this method for each non-trigger collider.
/// It is automatically called when using VR hands to avoid them colliding with the ship
/// and causing damage.
/// </summary>
/// <param name="colliderToAttach"></param>
public void AttachCollider (Collider colliderToAttach)
{
if (colliderToAttach != null && !colliderToAttach.isTrigger)
{
attachedCollisionColliders.Add(colliderToAttach.GetInstanceID());
}
}
/// <summary>
/// When attaching an object to the ship, call this method with an array of non-trigger collider.
/// It is automatically called when using VR hands to avoid them colliding with the ship
/// and causing damage. See also AttachCollider(..) and DetachCollider(..).
/// </summary>
/// <param name="collidersToAttach"></param>
public void AttachColliders (Collider[] collidersToAttach)
{
int numAttachedColliders = collidersToAttach == null ? 0 : collidersToAttach.Length;
for (int colIdx = 0; colIdx < numAttachedColliders; colIdx++)
{
Collider attachCollider = collidersToAttach[colIdx];
if (attachCollider.enabled && !attachCollider.isTrigger) { AttachCollider(attachCollider); }
}
}
/// <summary>
/// When detaching or removing an object from the ship, call this method for each non-trigger collider.
/// This is only required if it was first registered with AttachCollider(..).
/// USAGE: DetachCollider (collider.GetInstanceID())
/// </summary>
/// <param name="colliderID"></param>
public void DetachCollider (int colliderID)
{
attachedCollisionColliders.Remove(colliderID);
}
/// <summary>
/// When detaching or removing an object from the ship, call this method with an array
/// of non-trigger colliders. This is only required if they were first attached.
/// </summary>
/// <param name="collidersToDetach"></param>
public void DetachColliders (Collider[] collidersToDetach)
{
int numAttachedColliders = collidersToDetach == null ? 0 : collidersToDetach.Length;
for (int colIdx = 0; colIdx < numAttachedColliders; colIdx++)
{
Collider attachCollider = collidersToDetach[colIdx];
if (attachCollider != null && attachCollider.enabled) { DetachCollider(attachCollider.GetInstanceID()); }
}
}
/// <summary>
/// Get the localised damage region with guidHash in the list of regions.
/// The ship must be initialised.
/// </summary>
/// <param name="guidHash"></param>
/// <returns></returns>
public DamageRegion GetDamageRegion(int guidHash)
{
DamageRegion damageRegion = null;
if (guidHash != 0 && shipDamageModel == ShipDamageModel.Localised)
{
// Attempt to find a matching guidHash
for (int dmIdx = 0; dmIdx < numLocalisedDamageRegions; dmIdx++)
{
if (localisedDamageRegionList[dmIdx].guidHash == guidHash)
{
damageRegion = localisedDamageRegionList[dmIdx]; break;
}
}
}
return damageRegion;
}
/// <summary>
/// Get the localised damage region with the zero-based index in the list of regions.
/// The ship must be initialised.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public DamageRegion GetDamageRegionByIndex(int index)
{
if (shipDamageModel == ShipDamageModel.Localised && index >= 0 && index < numLocalisedDamageRegions)
{
return localisedDamageRegionList[index];
}
else { return null; }
}
/// <summary>
/// Get the zero-based index of a local damage region in the ship given the damage region name.
/// Returns -1 if not found or the Damage Model is not Localised.
/// Use this sparingly as it will incur garabage. Always declare the parameter as a static readonly variable.
/// Usage:
/// private static readonly string EngineDamageRegionName = "Engines";
/// ..
/// int drIdx = GetDamageRegionIndexByName(EngineDamageRegionName);
/// DamageRegion damageRegion = GetDamageRegionByIndex(drIdx);
/// </summary>
/// <param name="damageRegionName"></param>
/// <returns></returns>
public int GetDamageRegionIndexByName(string damageRegionName)
{
if (shipDamageModel == ShipDamageModel.Localised && localisedDamageRegionList != null)
{
return localisedDamageRegionList.FindIndex(dr => dr.name == damageRegionName);
}
else { return -1; }
}
/// <summary>
/// Get the world space central position of the localised damage region
/// with the given guidHash. If the ship is not using the Localised damage model
/// or a damage region with guidHash cannot be found, this returns the world space
/// position of the ship.
/// The ship must be initialised.
/// </summary>
/// <param name="guidHash"></param>
/// <returns></returns>
public Vector3 GetDamageRegionWSPosition(int guidHash)
{
DamageRegion damageRegion = GetDamageRegion(guidHash);
if (damageRegion != null)
{
return (trfmRight * damageRegion.relativePosition.x) +
(trfmUp * damageRegion.relativePosition.y) +
(trfmFwd * damageRegion.relativePosition.z) +
trfmPos;
}
else { return TransformPosition; }
}
/// <summary>
/// Get the world space central position of the damage region.
/// The ship must be initialised.
/// </summary>
/// <param name="damageRegion"></param>
/// <returns></returns>
public Vector3 GetDamageRegionWSPosition(DamageRegion damageRegion)
{
return (trfmRight * damageRegion.relativePosition.x) +
(trfmUp * damageRegion.relativePosition.y) +
(trfmFwd * damageRegion.relativePosition.z) +
trfmPos;
}
/// <summary>
/// Is this world space point on the ship, currently shielded?
/// If the main damage region is invincible, it will always return true.
/// If the ShipDamageModel is NOT Localised, the worldSpacePoint is ignored.
/// </summary>
/// <param name="worldSpacePoint"></param>
/// <returns></returns>
public bool HasActiveShield (Vector3 worldSpacePoint)
{
bool isShielded = false;
if (mainDamageRegion.isInvincible) { isShielded = true; }
else if (shipDamageModel != ShipDamageModel.Localised)
{
isShielded = mainDamageRegion.useShielding && mainDamageRegion.ShieldHealth > 0f;
}
else
{
// Determine which damage region was hit
int numDamageRegions = localisedDamageRegionList.Count + 1;
// Loop through all damage regions
DamageRegion thisDamageRegion;
// Get damage position in local space (calc once outside the loop)
Vector3 localDamagePosition = trfmInvRot * (worldSpacePoint - trfmPos);
for (int i = 0; i < numDamageRegions; i++)
{
thisDamageRegion = i == 0 ? mainDamageRegion : localisedDamageRegionList[i - 1];
if (thisDamageRegion.useShielding && thisDamageRegion.ShieldHealth > 0f && thisDamageRegion.IsHit(localDamagePosition))
{
isShielded = true;
break;
}
}
}
return isShielded;
}
/// <summary>
/// Check if a world-space point is within the volume area of a damage region.
/// </summary>
/// <param name="damageRegion"></param>
/// <param name="worldSpacePoint"></param>
/// <returns></returns>
public bool IsPointInDamageRegion (DamageRegion damageRegion, Vector3 worldSpacePoint)
{
return damageRegion == null ? false : damageRegion.IsHit(trfmInvRot * (worldSpacePoint - trfmPos));
}
/// <summary>
/// Make the whole ship invincible to damage.
/// For individual damageRegions change the isInvisible value on the localised region.
/// </summary>
public void MakeShipInvincible()
{
mainDamageRegion.isInvincible = true;
}
/// <summary>
/// Make the whole ship vincible to damage. When hit, the ship or shields will take damage.
/// For individual damageRegions change the isInvisible value on the localised region.
/// </summary>
public void MakeShipVincible()
{
mainDamageRegion.isInvincible = false;
}
/// <summary>
/// Resets health data for the ship. Used when initialising and respawning the ship.
/// </summary>
/// <returns></returns>
public void ResetHealth()
{
// Reset health value for the damage region
mainDamageRegion.Health = mainDamageRegion.startingHealth;
// Reset shield health value for the damage region
mainDamageRegion.ShieldHealth = mainDamageRegion.shieldingAmount;
mainDamageRegion.shieldRechargeDelayTimer = 0f;
mainDamageRegion.isDestructObjectActivated = false;
mainDamageRegion.isDestroyed = false;
damageCameraShakeAmountRequired = 0f;
if (!shipDamageModelIsSimple)
{
// Reset health value for each component
componentListSize = thrusterList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
thrusterList[componentIndex].Repair();
}
componentListSize = wingList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
wingList[componentIndex].Health = wingList[componentIndex].startingHealth;
}
componentListSize = controlSurfaceList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
controlSurfaceList[componentIndex].Health = controlSurfaceList[componentIndex].startingHealth;
}
componentListSize = weaponList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
weaponList[componentIndex].Repair();
}
if (shipDamageModel == ShipDamageModel.Localised)
{
// For each damage region:
// reset health value, shield health value, effects object or destruct instantiated flags, and child tranform
for (componentIndex = 0; componentIndex < numLocalisedDamageRegions; componentIndex++)
{
DamageRegion _damageRegion = localisedDamageRegionList[componentIndex];
_damageRegion.Health = localisedDamageRegionList[componentIndex].startingHealth;
_damageRegion.ShieldHealth = localisedDamageRegionList[componentIndex].shieldingAmount;
_damageRegion.shieldRechargeDelayTimer = 0f;
_damageRegion.isDestructionEffectsObjectInstantiated = false;
_damageRegion.isDestructObjectActivated = false;
_damageRegion.isDestroyed = false;
// if there is one, reactivate the transform for this region.
if (_damageRegion.regionChildTransform != null && !_damageRegion.regionChildTransform.gameObject.activeSelf)
{
_damageRegion.regionChildTransform.gameObject.SetActive(true);
}
}
}
}
if (callbackOnDamage != null) { callbackOnDamage(mainDamageRegion.Health); }
}
/// <summary>
/// Typically used when ShipDamageModel is Simple or Progressive, add health to the ship.
/// If isAffectShield is true, and the health reaches the maximum configured, excess health will
/// be applied to the shield for the specified DamageRegion. For Progressive Damage, health is
/// also added to components that have Use Progressive Damage enabled.
/// </summary>
/// <param name="healthAmount"></param>
/// <param name="isAffectShield"></param>
public void AddHealth(float healthAmount, bool isAffectShield)
{
AddHealth(mainDamageRegion, healthAmount, isAffectShield);
}
/// <summary>
/// Add health to a specific DamageRegion.
/// If isAffectShield is true, and the health reaches the maximum configured, excess health will
/// be applied to the shield for the specified DamageRegion. For Progressive Damage, health is
/// also added to components that have Use Progressive Damage enabled.
/// NOTE: -ve values are ignored. To incur damage use the ApplyNormalDamage or ApplyCollisionDamage
/// API methods.
/// </summary>
/// <param name="damageRegion"></param>
/// <param name="healthAmount"></param>
/// <param name="isAffectShield"></param>
public void AddHealth(DamageRegion damageRegion, float healthAmount, bool isAffectShield)
{
if (damageRegion != null && healthAmount > 0f)
{
float health = damageRegion.Health;
if (health < 0f) { health = healthAmount; }
else { health += healthAmount; }
if (health > damageRegion.startingHealth)
{
if (isAffectShield && damageRegion.useShielding)
{
float newShieldHealth = 0f;
// When shielding is -ve (e.g. -0.01 when it has been used up) set the shielding amount rather than adding it
if (damageRegion.ShieldHealth < 0f) { newShieldHealth = health - damageRegion.startingHealth; }
else { newShieldHealth = damageRegion.ShieldHealth + health - damageRegion.startingHealth; }
// Cap shielding to maximum permitted.
if (newShieldHealth > damageRegion.shieldingAmount) { newShieldHealth = damageRegion.shieldingAmount; }
damageRegion.ShieldHealth = newShieldHealth;
}
// Cap health to maximum permitted
health = damageRegion.startingHealth;
}
damageRegion.Health = health;
// Not sure if this is correct... why do we do this??
damageRegion.isDestructionEffectsObjectInstantiated = false;
// Use !shipDamageModelIsSimple first to avoid always having to lookup the enumeration
if (!shipDamageModelIsSimple)
{
int damageRegionIndex = GetDamageRegionIndex(damageRegion);
// Apply health to each of the components
if (damageRegionIndex >= 0)
{
// For Progressive, a damageRegionIndex of 0 on the component means Use Progressive Damage.
// If -1, the component doesn't use Progressive damage.
// For localised damage, the damageRegionIndex needs to match the damage region that health is being added to.
// Add health to Thrusters
componentListSize = thrusterList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
// For Progressive, a region index of 0 means Use Progressive Damage.
if (thrusterList[componentIndex].damageRegionIndex == damageRegionIndex)
{
health = thrusterList[componentIndex].Health;
if (health < 0f) { health = healthAmount; }
else { health += healthAmount; }
if (health > thrusterList[componentIndex].startingHealth) { health = thrusterList[componentIndex].startingHealth; }
// Only set the health value once for a given thruster
thrusterList[componentIndex].Health = health;
}
}
//Add health to Wings
componentListSize = wingList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
// For Progressive, a region index of 0 means Use Progressive Damage
if (wingList[componentIndex].damageRegionIndex == damageRegionIndex)
{
health = wingList[componentIndex].Health;
if (health < 0f) { health = healthAmount; }
else { health += healthAmount; }
if (health > wingList[componentIndex].startingHealth) { health = wingList[componentIndex].startingHealth; }
// Only set the health value once for a given wing
wingList[componentIndex].Health = health;
}
}
// Add health to Control Surfaces
componentListSize = controlSurfaceList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
// For Progressive, a region index of 0 means Use Progressive Damage
if (controlSurfaceList[componentIndex].damageRegionIndex == damageRegionIndex)
{
health = controlSurfaceList[componentIndex].Health;
if (health < 0f) { health = healthAmount; }
else { health += healthAmount; }
if (health > controlSurfaceList[componentIndex].startingHealth) { health = controlSurfaceList[componentIndex].startingHealth; }
// Only set the health value once for a given Control Surface
controlSurfaceList[componentIndex].Health = health;
}
}
// Add health to Weapons
componentListSize = weaponList.Count;
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
{
// For Progressive, a region index of 0 means Use Progressive Damage
if (weaponList[componentIndex].damageRegionIndex == damageRegionIndex)
{
health = weaponList[componentIndex].Health;
if (health < 0f) { health = healthAmount; }
else { health += healthAmount; }
if (health > weaponList[componentIndex].startingHealth) { health = weaponList[componentIndex].startingHealth; }
// Only set the health value once for a given Weapon
weaponList[componentIndex].Health = health;
}
}
}
}
}
}
/// <summary>
/// Applies damage to the ship due to a collision. The damage is registered as "collision" damage,
/// meaning that if it destroys the ship the ship will be respawned at a previously recorded respawn position.
/// </summary>
/// <param name="collisionInfo"></param>
public void ApplyCollisionDamage (Collision collisionInfo)
{
int hitColliderID = collisionInfo.collider.GetInstanceID();
// Avoid collision with colliders that have been registered with (attached to) this ship
if (!attachedCollisionColliders.Contains(hitColliderID))
{
// Loop through contact points
int numberOfContactPoints = collisionInfo.contactCount;
float damageAtEachPoint = collisionInfo.impulse.magnitude / numberOfContactPoints;
for (int c = 0; c < numberOfContactPoints; c++)
{
//Debug.Log("[DEBUG] " + collisionInfo.collider.name + " hit " + collisionInfo.GetContact(c).thisCollider.name + " T:" + Time.time);
// Apply damage at each contact point
ApplyDamage(damageAtEachPoint, ProjectileModule.DamageType.Default, collisionInfo.GetContact(c).point, true);
}
// Remember that this was collision damage
lastDamageWasCollisionDamage = true;
}
}
/// <summary>
/// Applies a specified amount of damage to the ship at a specified position. The damage is registered as "collision" damage,
/// meaning that if it destroys the ship the ship will be respawned at a previously recorded respawn position.
/// </summary>
/// <param name="damageAmount"></param>
/// <param name="damagePosition"></param>
public void ApplyCollisionDamage(float damageAmount, Vector3 damagePosition)
{
ApplyDamage(damageAmount, ProjectileModule.DamageType.Default, damagePosition, false);
// Remember that this was collision damage
lastDamageWasCollisionDamage = true;
}
/// <summary>
/// Applies a specified amount of damage to the ship at a specified position.
/// </summary>
/// <param name="damageAmount"></param>
/// <param name="damagePosition"></param>
public void ApplyNormalDamage(float damageAmount, ProjectileModule.DamageType damageType, Vector3 damagePosition)
{
ApplyDamage(damageAmount, damageType, damagePosition, false);
// Remember that this was not collision damage
lastDamageWasCollisionDamage = false;
}
/// <summary>
/// Returns the index of the last damage event. When this value changes, a damage event has occurred.
/// </summary>
/// <returns></returns>
public int LastDamageEventIndex() { return lastDamageEventIndex; }
/// <summary>
/// Returns the amount of rumble required due to the last damage event.
/// </summary>
/// <returns></returns>
public float RequiredDamageRumbleAmount() { return damageRumbleAmountRequired; }
/// <summary>
/// Returns the amount of camera shake required due to the last damage event
/// </summary>
/// <returns></returns>
public float RequiredCameraShakeAmount() { return damageCameraShakeAmountRequired; }
#endregion
#region General API Methods
/// <summary>
/// Returns an interpolated velocity in world space (akin to to the interpolated position/rotation on rigidbodies).
/// </summary>
public Vector3 GetInterpolatedWorldVelocity()
{
// Get the time in seconds since the last fixed update
float timeSinceLastFixedUpdate = Time.time - lastFixedUpdateTime;
// Interpolate between the world velocities of the last frame and the previous frame to get an interpolated world velocity
return previousFrameWorldVelocity + (worldVelocity - previousFrameWorldVelocity) * (timeSinceLastFixedUpdate / Time.fixedDeltaTime);
}
/// <summary>
/// Add temporary boost to the ship in a normalised local-space forceDirection
/// which a force of forceAmount Newtons for a period of duration seconds.
/// IMPORTANT: forceDirection must be normalised, otherwise you will get odd results.
/// </summary>
/// <param name="forceDirection"></param>
/// <param name="forceAmount"></param>
/// <param name="duration"></param>
public void AddBoost(Vector3 forceDirection, float forceAmount, float duration)
{
boostDirection = forceDirection;
boostForce = forceAmount;
boostTimer = duration;
}
/// <summary>
/// Immediately stop any boost that has been applied with AddBoost(..).
/// </summary>
public void StopBoost()
{
boostTimer = 0f;
}
#endregion
#region Respawning API Methods
/// <summary>
/// Returns true if the ship has been destroyed.
/// </summary>
public bool Destroyed()
{
if (shipDamageModel == ShipDamageModel.Simple || shipDamageModel == ShipDamageModel.Progressive)
{
return mainDamageRegion.Health <= 0f;
}
else
{
return mainDamageRegion.Health <= 0f;
}
}
/// <summary>
/// Returns the position for the ship to respawn at.
/// </summary>
/// <returns></returns>
public Vector3 GetRespawnPosition()
{
if (respawningMode == RespawningMode.RespawnAtLastPosition)
{
if (lastDamageWasCollisionDamage) { return currentCollisionRespawnPosition; }
else { return currentRespawnPosition; }
}
else if (respawningMode == RespawningMode.RespawnAtOriginalPosition)
{
return currentRespawnPosition;
}
else if (respawningMode == RespawningMode.RespawnAtSpecifiedPosition)
{
return customRespawnPosition;
}
else { return Vector3.zero; }
}
/// <summary>
/// Returns the rotation for the ship to respawn with.
/// </summary>
/// <returns></returns>
public Quaternion GetRespawnRotation()
{
if (respawningMode == RespawningMode.RespawnAtLastPosition)
{
if (lastDamageWasCollisionDamage) { return currentCollisionRespawnRotation; }
else { return currentRespawnRotation; }
}
else if (respawningMode == RespawningMode.RespawnAtOriginalPosition)
{
return currentRespawnRotation;
}
else if (respawningMode == RespawningMode.RespawnAtSpecifiedPosition)
{
return Quaternion.Euler(customRespawnRotation);
}
else { return Quaternion.identity; }
}
#endregion
#region Thruster API Methods
/// <summary>
/// Get the (central) fuel level of the ship. Fuel Level Range 0.0 (empty) to 100.0 (full).
/// </summary>
/// <returns></returns>
public float GetFuelLevel()
{
return centralFuelLevel;
}
/// <summary>
/// Get the fuel level of the Thruster based on the order it appears in the Thrusters tab
/// in the ShipControlModule. Numbers begin at 1.
/// Fuel Level Range 0.0 (empty) to 100.0 (full).
/// </summary>
/// <param name="thrusterNumber"></param>
/// <returns></returns>
public float GetFuelLevel (int thrusterNumber)
{
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
{
return thrusterList[thrusterNumber - 1].fuelLevel;
}
else { return 0f; }
}
/// <summary>
/// Get the heat level of the Thruster based on the order it appears in the Thrusters tab
/// in the ShipControlModule. Numbers begin at 1.
/// Heat Level Range 0.0 (min) to 100.0 (max).
/// </summary>
/// <param name="thrusterNumber"></param>
/// <returns></returns>
public float GetHeatLevel (int thrusterNumber)
{
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
{
return thrusterList[thrusterNumber - 1].heatLevel;
}
else { return 0f; }
}
/// <summary>
/// Get the overheating threshold of the Thruster based on the order it appears in the Thrusters tab
/// in the ShipControlModule. Numbers begin at 1.
/// </summary>
/// <param name="thrusterNumber"></param>
/// <returns></returns>
public float GetOverheatingThreshold (int thrusterNumber)
{
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
{
return thrusterList[thrusterNumber - 1].overHeatThreshold;
}
else { return 0f; }
}
/// <summary>
/// Get the Maximum Thrust of the Thruster based on the order it appears in the Thrusters tab
/// in the ShipControlModule. Numbers begin at 1. Values are returned in kilo Newtons.
/// </summary>
/// <param name="thrusterNumber"></param>
/// <returns></returns>
public float GetMaxThrust (int thrusterNumber)
{
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
{
return thrusterList[thrusterNumber-1].maxThrust;
}
else { return 0f; }
}
/// <summary>
/// Is the thruster heat level at or above the overheating threshold?
/// Thruster Number is based on the order it appears in the Thrusters tab
/// in the ShipControlModule. Numbers begin at 1.
/// </summary>
/// <param name="thrusterNumber"></param>
/// <returns></returns>
public bool IsThrusterOverheating (int thrusterNumber)
{
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
{
return thrusterList[thrusterNumber - 1].IsThrusterOverheating();
}
else { return false; }
}
/// <summary>
/// Repair the health of a thruster. Will also set the heat level to 0.
/// Can be useful if a thruster has burnt out after being over heated.
/// Thruster Number is based on the order it appears in the Thrusters tab
/// in the ShipControlModule. Numbers begin at 1.
/// </summary>
/// <param name="thrusterNumber"></param>
public void RepairThruster (int thrusterNumber)
{
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
{
thrusterList[thrusterNumber - 1].Repair();
}
}
/// <summary>
/// Set the central fuel level for the whole ship. If useCentralFuel is false,
/// use SetFuelLevel (thrusterNumber, new FuelLevel).
/// </summary>
/// <param name="newFuelLevel"></param>
public void SetFuelLevel (float newFuelLevel)
{
// Clamp the fuel level
if (newFuelLevel < 0f) { newFuelLevel = 0f; }
else if (newFuelLevel > 100f) { newFuelLevel = 100f; }
centralFuelLevel = newFuelLevel;
}
/// <summary>
/// Set the fuel level of the Thruster based on the order it appears in the Thrusters tab
/// in the ShipControlModule. Numbers begin at 1.
/// Fuel Level Range 0.0 (empty) to 100.0 (full).
/// </summary>
/// <param name="thrusterNumber"></param>
/// <returns></returns>
public void SetFuelLevel (int thrusterNumber, float newFuelLevel)
{
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
{
thrusterList[thrusterNumber - 1].SetFuelLevel(newFuelLevel);
}
}
/// <summary>
/// Set the heat level of the Thruster based on the order it appears in the Thrusters tab
/// in the ShipControlModule. Numbers begin at 1.
/// Heat Level Range 0.0 (min) to 100.0 (max).
/// </summary>
/// <param name="thrusterNumber"></param>
/// <returns></returns>
public void SetHeatLevel (int thrusterNumber, float newHeatLevel)
{
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
{
thrusterList[thrusterNumber - 1].SetHeatLevel(newHeatLevel);
}
}
/// <summary>
/// Set the Maximum Thrust of the Thruster based on the order it appears in the Thruster tab
/// in the ShipControlModule. Numbers begin at 1. Values should be in kilo Newtons
/// </summary>
/// <param name="thrusterNumber"></param>
/// <param name="newMaxThrustkN"></param>
public void SetMaxThrust (int thrusterNumber, int newMaxThrustkN)
{
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
{
thrusterList[thrusterNumber - 1].maxThrust = newMaxThrustkN * 1000f;
}
}
/// <summary>
/// Use this if you want fine-grained control over max thruster force, otherwise use SetMaxThrust.
/// Set the Maximum Thrust of the Thruster based on the order it appears in the Thruster tab
/// in the ShipControlModule. Numbers begin at 1. Values are in Newtons.
/// </summary>
/// <param name="thrusterNumber"></param>
/// <param name="newMaxThrustNewtons"></param>
public void SetMaxThrustNewtons (int thrusterNumber, float newMaxThrustNewtons)
{
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
{
thrusterList[thrusterNumber - 1].maxThrust = newMaxThrustNewtons;
}
}
/// <summary>
/// Set all thrusters to the same throttle value.
/// Values are clamped to between 0.0 and 1.0.
/// </summary>
/// <param name="newThrottleValue"></param>
public void SetThrottleAllThrusters (float newThrottleValue)
{
// Clamp values
if (newThrottleValue < 0f) { newThrottleValue = 0f; }
else if (newThrottleValue > 1f) { newThrottleValue = 1f; }
int numThrusters = thrusterList == null ? 0 : thrusterList.Count;
for (int thIdx = 0; thIdx < numThrusters; thIdx++)
{
thrusterList[thIdx].throttle = newThrottleValue;
}
}
/// <summary>
/// Set the throttle for a given thruster. Numbers begin at 1. Values should be between 0.0 and 1.0.
/// </summary>
/// <param name="thrusterNumber"></param>
/// <param name="newThrottleValue"></param>
public void SetThrusterThrottle (int thrusterNumber, float newThrottleValue)
{
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
{
thrusterList[thrusterNumber - 1].throttle = newThrottleValue < 0f ? 0f : newThrottleValue > 1f ? 1f : newThrottleValue;
}
}
#endregion
#region Weapon API Methods
/// <summary>
/// Deactivate all beam weapons that are currently firing
/// </summary>
public void DeactivateBeams(SSCManager sscManager)
{
int numWeapons = NumberOfWeapons;
for (int weaponIdx = 0; weaponIdx < numWeapons; weaponIdx++)
{
Weapon weapon = weaponList[weaponIdx];
if (weapon != null && (weapon.weaponTypeInt == Weapon.FixedBeamInt || weaponTypeInt == Weapon.TurretBeamInt))
{
int numBeams = weapon.beamItemKeyList == null ? 0 : weapon.beamItemKeyList.Count;
for (int bItemIdx = 0; bItemIdx < numBeams; bItemIdx++)
{
if (weapon.beamItemKeyList[bItemIdx].beamSequenceNumber > 0)
{
sscManager.DeactivateBeam(weapon.beamItemKeyList[bItemIdx]);
}
weapon.beamItemKeyList[bItemIdx] = new SSCBeamItemKey(-1, -1, 0);
}
}
}
}
/// <summary>
/// Get the zero-based index of a weapon on the ship given the weapon name. Returns -1 if not found.
/// Use this sparingly as it will incur garabage. Always declare the parameter as a static readonly variable.
/// Usage:
/// private static readonly string WPNturret1Name = "Turret 1";
/// ..
/// int wpIdx = GetWeaponIndexByName(WPNturret1Name);
/// </summary>
/// <param name="weaponName"></param>
/// <returns></returns>
public int GetWeaponIndexByName(string weaponName)
{
if (weaponList != null) { return weaponList.FindIndex(wp => wp.name == weaponName); }
else { return -1; }
}
/// <summary>
/// Get the weapon on the ship from a zero-based index in the list of weapons.
/// This will be 1 less than the number shown next to the weapon on the Combat tab.
/// It validates the weaponIdx so if possible, don't call this every frame.
/// </summary>
/// <param name="weaponIdx"></param>
/// <returns></returns>
public Weapon GetWeaponByIndex(int weaponIdx)
{
if (weaponIdx >= 0 && weaponIdx < (weaponList == null ? 0 : weaponList.Count))
{
return weaponList[weaponIdx];
}
else { return null; }
}
/// <summary>
/// Get the heat level of the Weapon with the zero-based index in list of weapons.
/// Heat Level Range 0.0 (min) to 100.0 (max).
/// </summary>
/// <param name="weaponIdx"></param>
/// <returns></returns>
public float GetWeaponHeatLevel (int weaponIdx, float newHeatLevel)
{
if (weaponIdx >= 0 && weaponIdx < (weaponList == null ? 0 : weaponList.Count))
{
return weaponList[weaponIdx].heatLevel;
}
else { return 0f; }
}
/// <summary>
/// Get the index in the weaponList of next weapon that has AutoTargetingEnabled.
/// If no match is found, return -1. startIdx is the zero-based index in the
/// weaponList to begin the search.
/// </summary>
/// <param name="startIdx"></param>
/// <returns></returns>
public int GetNextAutoTargetingWeaponIndex(int startIdx)
{
int foundWeaponIdx = -1;
Weapon _tempWeapon = null;
int numWeapons = weaponList == null ? 0 : weaponList.Count;
if (startIdx >= 0 && startIdx < numWeapons)
{
// Use a for-loop rather than list.Find to avoid GC
for (int weaponIdx = startIdx; weaponIdx < numWeapons; weaponIdx++)
{
_tempWeapon = weaponList[weaponIdx];
if (_tempWeapon != null && _tempWeapon.isAutoTargetingEnabled)
{
foundWeaponIdx = weaponIdx;
break;
}
}
}
return foundWeaponIdx;
}
/// <summary>
/// Clears all targeting information for the weapon. This should be called if you do not
/// know if the target is a ship or a gameobject.
/// WARNING: For the sake of performance, does not validate weaponIdx.
/// </summary>
/// <param name="weaponIdx">zero-based index in list of weapons</param>
public void ClearWeaponTarget(int weaponIdx)
{
if (weaponIdx >= 0) { weaponList[weaponIdx].ClearTarget(); }
}
/// <summary>
/// Has the weapon with the zero-based index in list of weapons got a target
/// assigned to it? The target could be a GameObject or a Ship.
/// WARNING: For the sake of performance, does not validate weaponIdx.
/// </summary>
/// <param name="weaponIdx"></param>
/// <returns></returns>
public bool HasWeaponTarget(int weaponIdx)
{
if (weaponIdx >= 0) { return weaponList[weaponIdx].target != null; }
else { return false; }
}
/// <summary>
/// Set the heat level of the Weapon with the zero-based index in list of weapons.
/// Heat Level Range 0.0 (min) to 100.0 (max).
/// </summary>
/// <param name="weaponIdx"></param>
/// <returns></returns>
public void SetWeaponHeatLevel (int weaponIdx, float newHeatLevel)
{
if (weaponIdx >= 0 && weaponIdx < (weaponList == null ? 0 : weaponList.Count))
{
weaponList[weaponIdx].SetHeatLevel(newHeatLevel);
}
}
/// <summary>
/// Sets the gameobject that the weapon will attempt to aim at. Currently only applies
/// to Weapon.WeaponType.TurretProjectile and FixedProjectile weapons with guided projectiles.
/// However, for the sake of performance, this method does not do any WeaponType validation.
/// WARNING: This method will generate garbage. Where possible call
/// SetWeaponTarget(int weaponIdx, GameObject target). If only the name is known,
/// first call GetWeaponIndexByName(string weaponName) once in your Awake() routine.
/// </summary>
/// <param name="weaponName"></param>
/// <param name="target"></param>
public void SetWeaponTarget(string weaponName, GameObject target)
{
int wpIdx = GetWeaponIndexByName(weaponName);
if (wpIdx >= 0)
{
// It is quicker not to check the weapon type and just set the target
// Call SetTarget so it is configured correctly rather than setting the public variable
weaponList[wpIdx].SetTarget(target);
}
}
/// <summary>
/// Sets the gameobject that the weapon will attempt to aim at. Currently only applies
/// to Weapon.WeaponType.TurretProjectile and FixedProjectile weapons with guided projectiles.
/// However, for the sake of performance, this method does not do any WeaponType validation.
/// WARNING: For the sake of performance, does not validate weaponIdx.
/// </summary>
/// <param name="weaponIdx">zero-based index in list of weapons</param>
/// <param name="target"></param>
public void SetWeaponTarget(int weaponIdx, GameObject target)
{
// Currently don't check weapon type or number of weapons as this would incur overhead.
if (weaponIdx >= 0)
{
// Call SetTarget so it is configured correctly rather than setting the public variable
weaponList[weaponIdx].SetTarget(target);
}
}
/// <summary>
/// Sets the ship that the weapon will attempt to aim at. Currently only applies
/// to Weapon.WeaponType.TurretProjectile and FixedProjectile weapons with guided projectiles.
/// However, for the sake of performance, this method does not do any WeaponType validation.
/// WARNING: For the sake of performance, does not validate weaponIdx.
/// </summary>
/// <param name="weaponIdx">zero-based index in list of weapons</param>
/// <param name="targetShipControlModule"></param>
public void SetWeaponTargetShip(int weaponIdx, ShipControlModule targetShipControlModule)
{
// Currently don't check weapon type or number of weapons as this would incur overhead.
if (weaponIdx >= 0)
{
// Call SetTargetShip so it is configured correctly rather than setting the public variable
weaponList[weaponIdx].SetTargetShip(targetShipControlModule);
}
}
/// <summary>
/// Sets the ship's damage region that the weapon will attempt to aim at. Currently only applies
/// to Weapon.WeaponType.TurretProjectile and FixedProjectile weapons with guided projectiles.
/// However, for the sake of performance, this method does not do any WeaponType validation.
/// WARNING: For the sake of performance, does not validate weaponIdx.
/// </summary>
/// <param name="weaponIdx"></param>
/// <param name="targetShipControlModule"></param>
/// <param name="damageRegion"></param>
public void SetWeaponTargetShipDamageRegion (int weaponIdx, ShipControlModule targetShipControlModule, DamageRegion damageRegion)
{
// Currently don't check weapon type or number of weapons as this would incur overhead.
if (weaponIdx >= 0)
{
// Call SetTargetShipDamageRegion so it is configured correctly rather than setting the public variable
weaponList[weaponIdx].SetTargetShipDamageRegion(targetShipControlModule, damageRegion);
}
}
/// <summary>
/// Performs a once-off change to the direction the weapon will fire based on a
/// "target" position in world-space. The weapon will NOT stay locked onto this
/// position.
/// NOTE: Should NOT be used for Turrets. Use SetWeaponTarget(..) instead.
/// WARNING: For the sake of performance, does not validate weaponIdx.
/// </summary>
/// <param name="weaponIdx">zero-based position in the list of weapons</param>
/// <param name="aimAtWorldSpacePosition"></param>
public void SetWeaponFireDirection(int weaponIdx, Vector3 aimAtWorldSpacePosition)
{
// Currently don't check weapon type or number of weapons as this would incur overhead.
if (weaponIdx >= 0)
{
Weapon weapon = weaponList[weaponIdx];
if (weapon != null)
{
// Get base weapon world position (not accounting for relative fire position)
weaponWorldBasePosition = (trfmRight * weapon.relativePosition.x) +
(trfmUp * weapon.relativePosition.y) +
(trfmFwd * weapon.relativePosition.z) +
trfmPos;
// Convert ws fire direction into local ship space
weapon.fireDirection = trfmInvRot * (aimAtWorldSpacePosition - weaponWorldBasePosition);
weapon.Initialise(trfmInvRot);
}
}
}
/// <summary>
/// Returns whether a weapon has line of sight to the weapon's specified target (i.e. weapon.target).
/// If directToTarget is set to true, will raycast directly from the weapon to the target.
/// If directToTarget is set to false, will raycast in the direction the weapon is facing.
/// This method will return true if the raycast hits:
/// a) The weapon's specified target,
/// b) An enemy ship (distinguished by faction ID) - even if it is not the target and anyEnemy is true,
/// c) An object that isn't the target (if obstaclesBlockLineOfSight is set to false),
/// d) Nothing.
/// This method will return false if the raycast hits:
/// a) A friendly ship (distinguished by faction ID),
/// b) An object that isn't the target (if obstaclesBlockLineOfSight is set to true).
/// c) An enemy ship that is not the target when anyEnemy is false
/// </summary>
/// <param name="weapon"></param>
/// <param name="directToTarget"></param>
/// <returns></returns>
public bool WeaponHasLineOfSight (Weapon weapon, bool directToTarget = false, bool obstaclesBlockLineOfSight = true, bool anyEnemy = true)
{
return WeaponHasLineOfSight(weapon, weapon.target, directToTarget, obstaclesBlockLineOfSight, anyEnemy);
}
/// <summary>
/// Returns whether a weapon has line of sight to a target.
/// If directToTarget is set to true, will raycast directly from the weapon to the target.
/// If directToTarget is set to false, will raycast in the direction the weapon is facing.
/// This method will return true if the raycast hits:
/// a) The target,
/// b) An enemy ship (distingushed by faction ID) - even if it is not the target and anyEnemy is true,
/// c) An object that isn't the target (if obstaclesBlockLineOfSight is set to false),
/// d) Nothing.
/// This method will return false if the raycast hits:
/// a) A friendly ship (distinguished by faction ID),
/// b) An object that isn't the target (if obstaclesBlockLineOfSight is set to true).
/// c) An enemy ship that is not the target when anyEnemy is false
/// </summary>
/// <param name="weapon"></param>
/// <param name="target"></param>
/// <param name="directToTarget"></param>
/// <returns></returns>
public bool WeaponHasLineOfSight(Weapon weapon, GameObject target, bool directToTarget = false, bool obstaclesBlockLineOfSight = true, bool anyEnemy = true)
{
// Update the line-of-sight property
weapon.UpdateLineOfSight(target, trfmPos, trfmRight, trfmUp, trfmFwd, trfmInvRot, factionId,
false, directToTarget, obstaclesBlockLineOfSight, anyEnemy);
// Return the updated property
return weapon.HasLineOfSight;
}
#endregion
#endregion
}
}