2020-05-31 17:07:22 +00:00
using SanAndreasUnity.Importing.Conversion ;
using SanAndreasUnity.Importing.Items ;
using SanAndreasUnity.Importing.Items.Definitions ;
using SanAndreasUnity.Importing.Vehicles ;
using System ;
using System.Collections.Generic ;
using System.Linq ;
using UnityEngine ;
using VehicleDef = SanAndreasUnity . Importing . Items . Definitions . VehicleDef ;
namespace SanAndreasUnity.Behaviours.Vehicles
{
public partial class Vehicle
{
[Flags]
public enum WheelAlignment
{
None = 0 ,
Front = 1 ,
Mid = 2 ,
Rear = 4 ,
Left = 8 ,
Right = 16 ,
LeftRightMask = Left | Right ,
FrontMidRearMask = Front | Mid | Rear ,
RightFront = Right | Front ,
LeftFront = Left | Front ,
RightMid = Right | Mid ,
LeftMid = Left | Mid ,
RightRear = Right | Rear ,
LeftRear = Left | Rear ,
}
[Flags]
public enum SeatAlignment
{
None = 0 ,
Front = 1 ,
Back = 2 ,
Left = 4 ,
Right = 8 ,
FrontBackMask = Front | Back ,
LeftRightMask = Left | Right ,
FrontRight = Front | Right ,
FrontLeft = Front | Left ,
BackRight = Back | Right ,
BackLeft = Back | Left ,
}
public enum DoorAlignment
{
None ,
RightFront ,
LeftFront ,
RightRear ,
LeftRear ,
}
private static VehicleDef [ ] _sRandomSpawnable ;
private static int _sMaxSpawnableIndex ;
private static VehicleDef [ ] GetRandomSpawnableDefs ( out int maxIndex )
{
var all = Item . GetDefinitions < VehicleDef > ( ) . ToArray ( ) ;
var defs = all
. Where ( x = > x . Frequency > 0 & & x . VehicleType = = VehicleType . Car )
. ToArray ( ) ;
maxIndex = defs . Sum ( x = > x . Frequency ) ;
return defs ;
}
private static VehicleDef GetRandomDef ( )
{
if ( _sRandomSpawnable = = null )
{
_sRandomSpawnable = GetRandomSpawnableDefs ( out _sMaxSpawnableIndex ) ;
}
var index = UnityEngine . Random . Range ( 0 , _sMaxSpawnableIndex ) ;
foreach ( var def in _sRandomSpawnable )
{
index - = def . Frequency ;
if ( index < 0 ) return def ;
}
throw new Exception ( "Unable to find cars to spawn" ) ;
}
2019-07-08 21:43:13 +00:00
public static void GetPositionForSpawning ( Transform inFrontOfTransform , out Vector3 pos , out Quaternion rot ) {
2020-05-31 17:07:22 +00:00
pos = Vector3 . zero ;
rot = Quaternion . identity ;
Vector3 spawnOffset = new Vector3 ( 0 , 2 , 5 ) ;
2019-07-08 21:43:13 +00:00
pos = inFrontOfTransform . position + inFrontOfTransform . forward * spawnOffset . z + inFrontOfTransform . up * spawnOffset . y
+ inFrontOfTransform . right * spawnOffset . x ;
rot = Quaternion . LookRotation ( - inFrontOfTransform . right , Vector3 . up ) ;
2020-05-31 17:07:22 +00:00
}
public static Vehicle Create ( VehicleSpawner spawner )
{
return Create ( spawner . Info . CarId , spawner . Info . Colors , spawner . transform . position ,
spawner . transform . rotation ) ;
}
public static Vehicle Create ( int carId , Vector3 position , Quaternion rotation )
{
return Create ( carId , null , position , rotation ) ;
}
2019-07-08 21:43:13 +00:00
public static Vehicle CreateInFrontOf ( int carId , Transform inFrontOfTransform ) {
2020-05-31 17:07:22 +00:00
Vector3 pos ;
Quaternion rot ;
2019-07-08 21:43:13 +00:00
GetPositionForSpawning ( inFrontOfTransform , out pos , out rot ) ;
2020-05-31 17:07:22 +00:00
return Create ( carId , pos , rot ) ;
}
2019-07-08 21:43:13 +00:00
public static Vehicle CreateRandomInFrontOf ( Transform inFrontOfTransform )
{
return CreateInFrontOf ( - 1 , inFrontOfTransform ) ;
}
2020-05-31 17:07:22 +00:00
public static Vehicle Create ( int carId , int [ ] colors , Vector3 position , Quaternion rotation )
2019-04-29 22:30:06 +00:00
{
2019-04-29 23:41:18 +00:00
// this probably should not be done like this
var go = Instantiate ( VehicleManager . Instance . vehiclePrefab ) ;
var v = Create ( go , carId , colors , position , rotation ) ;
if ( Net . NetStatus . IsServer )
{
v . GetComponent < VehicleController > ( ) . OnAfterCreateVehicle ( ) ;
Net . NetManager . Spawn ( go ) ;
}
return v ;
2019-04-29 22:30:06 +00:00
}
public static Vehicle Create ( GameObject vehicleGameObject , int carId , int [ ] colors ,
Vector3 position , Quaternion rotation )
2020-05-31 17:07:22 +00:00
{
2019-04-29 22:23:42 +00:00
2019-04-29 22:30:06 +00:00
var inst = vehicleGameObject . AddComponent < Vehicle > ( ) ;
2020-05-31 17:07:22 +00:00
VehicleDef def ;
if ( carId = = - 1 )
{
def = GetRandomDef ( ) ;
}
else
{
def = Item . GetDefinition < VehicleDef > ( carId ) ;
}
inst . Initialize ( def , colors ) ;
inst . transform . position = position - Vector3 . up * inst . AverageWheelHeight ;
inst . transform . localRotation = rotation ;
2019-06-25 20:35:44 +00:00
OutOfRangeDestroyer destroyer = Utilities . F . GetOrAddComponent < OutOfRangeDestroyer > ( inst . gameObject ) ;
2020-05-31 17:07:22 +00:00
destroyer . timeUntilDestroyed = 5 ;
destroyer . range = 300 ;
return inst ;
}
private Geometry . GeometryParts _geometryParts ;
public class Wheel
{
public WheelAlignment Alignment { get ; set ; }
public bool IsLeftHand
{
get { return ( Alignment & WheelAlignment . Left ) = = WheelAlignment . Left ; }
}
public bool IsRightHand
{
get { return ( Alignment & WheelAlignment . Right ) = = WheelAlignment . Right ; }
}
public bool IsFront
{
get { return ( Alignment & WheelAlignment . Front ) = = WheelAlignment . Front ; }
}
public bool IsMid
{
get { return ( Alignment & WheelAlignment . Mid ) = = WheelAlignment . Mid ; }
}
public bool IsRear
{
get { return ( Alignment & WheelAlignment . Rear ) = = WheelAlignment . Rear ; }
}
public Transform Parent { get ; set ; }
public Transform Child { get ; set ; }
public WheelCollider Collider { get ; set ; }
public Wheel Complement { get ; set ; }
public float Travel { get ; private set ; }
public void UpdateTravel ( )
{
Travel = 1f ;
WheelHit hit ;
if ( Collider . GetGroundHit ( out hit ) )
{
Travel = ( - Parent . transform . InverseTransformPoint ( hit . point ) . y - Collider . radius ) / Collider . suspensionDistance ;
}
}
public Quaternion Roll { get ; set ; }
}
public class Seat
{
2020-05-07 16:36:56 +00:00
public SeatAlignment Alignment { get ; internal set ; }
2020-05-31 17:07:22 +00:00
2020-05-07 16:36:56 +00:00
public Transform Parent { get ; internal set ; }
2020-05-31 17:07:22 +00:00
/// <summary> Ped that is occupying this seat. </summary>
public Ped OccupyingPed { get ; internal set ; }
2020-05-07 15:58:47 +00:00
public float TimeWhenPedChanged { get ; internal set ; } = float . NegativeInfinity ;
public float TimeSincePedChanged = > Time . time - this . TimeWhenPedChanged ;
2020-05-31 17:07:22 +00:00
public bool IsTaken { get { return this . OccupyingPed ! = null ; } }
public bool IsLeftHand
{
get { return ( Alignment & SeatAlignment . Left ) = = SeatAlignment . Left ; }
}
public bool IsRightHand
{
get { return ( Alignment & SeatAlignment . Right ) = = SeatAlignment . Right ; }
}
public bool IsFront
{
get { return ( Alignment & SeatAlignment . Front ) = = SeatAlignment . Front ; }
}
public bool IsBack
{
get { return ( Alignment & SeatAlignment . Back ) = = SeatAlignment . Back ; }
}
public bool IsDriver
{
get { return Alignment = = SeatAlignment . FrontLeft ; }
}
}
private FrameContainer _frames ;
2020-06-20 14:23:53 +00:00
public Transform EngineTransform { get ; private set ; }
2020-06-29 19:57:44 +00:00
public Transform PetrolcapTransform { get ; private set ; }
2020-06-20 14:23:53 +00:00
2020-06-02 20:40:46 +00:00
private static GameObject s_highDetailMeshesContainer ;
2020-06-02 23:10:38 +00:00
public Transform HighDetailMeshesParent { get ; private set ; }
2020-06-02 20:40:46 +00:00
private List < KeyValuePair < Transform , Transform > > m_highDetailMeshObjectsToUpdate = new List < KeyValuePair < Transform , Transform > > ( ) ;
2020-05-31 17:07:22 +00:00
private readonly List < Wheel > _wheels = new List < Wheel > ( ) ;
private readonly List < Seat > _seats = new List < Seat > ( ) ;
public List < Wheel > Wheels { get { return _wheels ; } }
public List < Seat > Seats { get { return _seats ; } }
private WheelAlignment GetWheelAlignment ( string frameName )
{
switch ( frameName )
{
case "wheel_rf_dummy" :
return WheelAlignment . RightFront ;
case "wheel_lf_dummy" :
return WheelAlignment . LeftFront ;
case "wheel_rm_dummy" :
return WheelAlignment . RightMid ;
case "wheel_lm_dummy" :
return WheelAlignment . LeftMid ;
case "wheel_rb_dummy" :
return WheelAlignment . RightRear ;
case "wheel_lb_dummy" :
return WheelAlignment . LeftRear ;
default :
return WheelAlignment . None ;
}
}
private DoorAlignment GetDoorAlignment ( string frameName )
{
switch ( frameName )
{
case "door_rf_dummy" :
return DoorAlignment . RightFront ;
case "door_lf_dummy" :
return DoorAlignment . LeftFront ;
case "door_rr_dummy" :
return DoorAlignment . RightRear ;
case "door_lr_dummy" :
return DoorAlignment . LeftRear ;
default :
return DoorAlignment . None ;
}
}
public Transform GetPart ( string name )
{
var frame = _frames . GetByName ( name ) ;
return frame ! = null ? frame . transform : null ;
}
private void AttachSeat ( Transform parent , SeatAlignment alignment )
{
_seats . Add ( new Seat { Parent = parent , Alignment = alignment } ) ;
}
private void Initialize ( VehicleDef def , int [ ] colors = null )
{
Definition = def ;
if ( colors ! = null & & colors [ 0 ] ! = - 1 )
{
SetColors ( colors ) ;
}
else
{
var defaultClrs = CarColors . GetCarDefaults ( Definition . ModelName ) ;
if ( defaultClrs ! = null )
{
SetColors ( defaultClrs [ UnityEngine . Random . Range ( 0 , defaultClrs . Count ) ] ) ;
}
else
{
Debug . LogWarningFormat ( "No colours defined for {0}!" , def . GameName ) ;
}
}
name = Definition . GameName ;
_geometryParts = Geometry . Load ( Definition . ModelName ,
TextureDictionary . Load ( Definition . TextureDictionaryName ) ,
TextureDictionary . Load ( "vehicle" ) ,
TextureDictionary . Load ( "misc" ) ) ;
_frames = _geometryParts . AttachFrames ( transform , MaterialFlags . Vehicle ) ;
var wheelFrame = _frames . FirstOrDefault ( x = > x . Name = = "wheel" ) ;
if ( wheelFrame = = null )
{
Debug . LogWarningFormat ( "No wheels defined for {0}!" , def . GameName ) ;
Destroy ( gameObject ) ;
return ;
}
2020-06-20 14:23:53 +00:00
var engineFrame = _frames . FirstOrDefault ( x = > x . Name = = "engine" ) ;
if ( engineFrame ! = null )
this . EngineTransform = engineFrame . transform ;
2020-06-29 19:57:44 +00:00
var petrolcapFrame = _frames . FirstOrDefault ( x = > x . Name = = "petrolcap" ) ;
if ( petrolcapFrame ! = null )
this . PetrolcapTransform = petrolcapFrame . transform ;
2020-05-31 17:07:22 +00:00
foreach ( var frame in _frames )
{
if ( ! frame . Name . StartsWith ( "wheel_" ) ) continue ;
if ( ! frame . Name . EndsWith ( "_dummy" ) ) continue ;
var childFrames = _frames . Where ( x = > x . ParentIndex = = frame . Index ) ;
// disable all children of wheel dummies
foreach ( var childFrame in childFrames )
{
childFrame . gameObject . SetActive ( false ) ;
}
var wheelAlignment = GetWheelAlignment ( frame . Name ) ;
Wheel inst ;
// see if this wheel dummy has a wheel child
var wheel = childFrames . FirstOrDefault ( x = > x . Name = = "wheel" ) ;
if ( wheel = = null )
{
var copy = Instantiate ( wheelFrame . transform ) ;
copy . SetParent ( frame . transform , false ) ;
_wheels . Add ( inst = new Wheel
{
Alignment = wheelAlignment ,
Parent = frame . transform ,
Child = copy ,
} ) ;
}
else
{
// all children of wheel dummies get set to inactive so activate this one
wheel . gameObject . SetActive ( true ) ;
_wheels . Add ( inst = new Wheel
{
Alignment = wheelAlignment ,
Parent = frame . transform ,
Child = wheel . transform ,
} ) ;
}
if ( inst . IsLeftHand )
{
frame . transform . Rotate ( Vector3 . up , 180.0f ) ;
}
inst . Complement = _wheels . FirstOrDefault ( x = >
( x . Alignment & WheelAlignment . LeftRightMask ) ! = ( inst . Alignment & WheelAlignment . LeftRightMask ) & &
( x . Alignment & WheelAlignment . FrontMidRearMask ) = = ( inst . Alignment & WheelAlignment . FrontMidRearMask ) ) ;
if ( inst . Complement ! = null )
{
inst . Complement . Complement = inst ;
}
}
InitializePhysics ( ) ;
2020-06-28 14:44:32 +00:00
this . Health = this . MaxHealth = Mathf . Pow ( this . HandlingData . Mass , VehicleManager . Instance . massToHealthExponent ) ;
2020-06-09 17:45:25 +00:00
2020-05-31 17:07:22 +00:00
foreach ( var pair in _frames . Where ( x = > x . Name . StartsWith ( "door_" ) ) )
{
var doorAlignment = GetDoorAlignment ( pair . Name ) ;
if ( doorAlignment = = DoorAlignment . None ) continue ;
var hinge = pair . gameObject . AddComponent < HingeJoint > ( ) ;
hinge . axis = Vector3 . up ;
hinge . useLimits = true ;
var limit = 90.0f * ( ( doorAlignment = = DoorAlignment . LeftFront | | doorAlignment = = DoorAlignment . LeftRear ) ? 1.0f : - 1.0f ) ;
hinge . limits = new JointLimits { min = Mathf . Min ( 0 , limit ) , max = Mathf . Max ( 0 , limit ) , } ;
hinge . connectedBody = gameObject . GetComponent < Rigidbody > ( ) ;
}
var frontSeat = GetPart ( "ped_frontseat" ) ;
var backSeat = GetPart ( "ped_backseat" ) ;
if ( frontSeat ! = null )
{
var frontSeatMirror = new GameObject ( "ped_frontseat" ) . transform ;
frontSeatMirror . SetParent ( frontSeat . parent , false ) ;
frontSeatMirror . localPosition = Vector3 . Scale ( frontSeat . localPosition , new Vector3 ( - 1f , 1f , 1f ) ) ;
if ( frontSeat . localPosition . x > 0f )
{
AttachSeat ( frontSeat , SeatAlignment . FrontRight ) ;
AttachSeat ( frontSeatMirror , SeatAlignment . FrontLeft ) ;
}
else
{
AttachSeat ( frontSeatMirror , SeatAlignment . FrontRight ) ;
AttachSeat ( frontSeat , SeatAlignment . FrontLeft ) ;
}
DriverTransform = GetSeat ( SeatAlignment . FrontLeft ) . Parent ;
}
if ( backSeat ! = null )
{
var backSeatMirror = new GameObject ( "ped_backseat" ) . transform ;
backSeatMirror . SetParent ( backSeat . parent , false ) ;
backSeatMirror . localPosition = Vector3 . Scale ( backSeat . localPosition , new Vector3 ( - 1f , 1f , 1f ) ) ;
if ( backSeat . localPosition . x > 0f )
{
AttachSeat ( backSeat , SeatAlignment . BackRight ) ;
AttachSeat ( backSeatMirror , SeatAlignment . BackLeft ) ;
}
else
{
AttachSeat ( backSeatMirror , SeatAlignment . BackRight ) ;
AttachSeat ( backSeat , SeatAlignment . BackLeft ) ;
}
}
// Add vehicle damage
2019-05-27 18:37:16 +00:00
/ *
2020-05-31 17:07:22 +00:00
var dam = gameObject . AddComponent < VehicleDamage > ( ) ;
dam . damageParts = new Transform [ ] { transform . GetChild ( 0 ) . Find ( "engine" ) } ;
dam . deformMeshes = gameObject . GetComponentsInChildren < MeshFilter > ( ) ;
dam . displaceParts = gameObject . GetComponentsInChildren < Transform > ( ) . Where ( x = > x . GetComponent < Frame > ( ) ! = null | | x . GetComponent < FrameContainer > ( ) ! = null ) . ToArray ( ) ;
dam . damageFactor = VehicleAPI . constDamageFactor ;
dam . collisionIgnoreHeight = - . 4f ;
dam . collisionTimeGap = . 1f ;
//OptimizeVehicle();
dam . deformColliders = gameObject . GetComponentsInChildren < MeshCollider > ( ) ;
2019-05-27 18:37:16 +00:00
* /
2020-05-31 17:07:22 +00:00
gameObject . SetLayerRecursive ( Layer ) ;
2019-05-27 18:37:16 +00:00
2020-06-02 20:40:46 +00:00
SetupHighDetailMesh ( ) ;
}
void SetupHighDetailMesh ( )
{
// We need to add mesh colliders with high detail vehicle's mesh.
// These colliders will be used, among other things, when raycasting with weapons.
// This is a problem because Unity does not support concave (non-convex) mesh colliders attached to rigid body.
// Tried adding a separate kinematic rigid body (kinematic ones work with concave mesh colliders) to each object with a mesh filter, but without success.
// So, we are left with the following options:
// - somehow generate multiple convex meshes from a concave mesh
// - create a separate game object with mesh colliders, and update his position/rotation every frame to be the same as vehicle's
// Option with a separate game object is chosen.
if ( null = = s_highDetailMeshesContainer )
{
s_highDetailMeshesContainer = new GameObject ( "Vehicle high detail meshes container" ) ;
}
GameObject parent = new GameObject ( this . gameObject . name ) ;
2020-06-02 23:10:38 +00:00
this . HighDetailMeshesParent = parent . transform ;
2020-06-02 20:40:46 +00:00
parent . transform . parent = s_highDetailMeshesContainer . transform ;
parent . transform . SetPositionAndRotation ( this . transform . position , this . transform . rotation ) ;
2020-06-09 17:45:25 +00:00
this . SetupDamagable ( ) ;
2020-06-02 20:40:46 +00:00
// for each mesh filter, create child game object with mesh collider
foreach ( var meshFilter in this . gameObject . GetComponentsInChildren < MeshFilter > ( ) )
{
GameObject child = new GameObject ( meshFilter . gameObject . name , typeof ( MeshCollider ) ) ;
child . layer = Vehicle . MeshLayer ;
child . transform . parent = parent . transform ;
child . transform . SetPositionAndRotation ( meshFilter . transform . position , meshFilter . transform . rotation ) ;
var meshCollider = child . GetComponent < MeshCollider > ( ) ;
meshCollider . convex = false ;
meshCollider . sharedMesh = meshFilter . sharedMesh ;
2020-06-02 21:48:40 +00:00
if ( null ! = meshFilter . gameObject . GetComponent < Rigidbody > ( )
| | null ! = meshFilter . transform . parent . GetComponent < Rigidbody > ( )
| | null ! = meshFilter . transform . parent . GetComponent < WheelCollider > ( ) )
2020-06-02 20:40:46 +00:00
{
2020-06-02 21:48:40 +00:00
// this object has a dedicated rigid body or is a wheel, so it will move
2020-06-02 20:40:46 +00:00
// make sure that we update transform of this object
m_highDetailMeshObjectsToUpdate . Add ( new KeyValuePair < Transform , Transform > ( meshFilter . transform , child . transform ) ) ;
}
}
2020-05-31 17:07:22 +00:00
}
}
}