SanAndreasUnity/Assets/Scripts/Behaviours/Vehicles/Vehicle_Spawning.cs

584 lines
20 KiB
C#
Raw Normal View History

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");
}
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);
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);
}
public static Vehicle CreateInFrontOf(int carId, Transform inFrontOfTransform) {
2020-05-31 17:07:22 +00:00
Vector3 pos;
Quaternion rot;
GetPositionForSpawning (inFrontOfTransform, out pos, out rot);
2020-05-31 17:07:22 +00:00
return Create (carId, pos, rot);
}
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; }
private static GameObject s_highDetailMeshesContainer;
2020-06-02 23:10:38 +00:00
public Transform HighDetailMeshesParent { get; private set; }
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-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();
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
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;
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();
// 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 21:48:40 +00:00
// this object has a dedicated rigid body or is a wheel, so it will move
// 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
}
}
}