diff --git a/Assets/Prefabs/DeadBody.prefab b/Assets/Prefabs/DeadBody.prefab new file mode 100644 index 000000000..b7aca257f --- /dev/null +++ b/Assets/Prefabs/DeadBody.prefab @@ -0,0 +1,95 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &2141754834917738923 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4130636948537856663} + - component: {fileID: 4038138864584441180} + - component: {fileID: 3458215582039164093} + - component: {fileID: 3928823046603185135} + - component: {fileID: 7017421174877670493} + m_Layer: 0 + m_Name: DeadBody + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4130636948537856663 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2141754834917738923} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &4038138864584441180 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2141754834917738923} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3} + m_Name: + m_EditorClassIdentifier: + serverOnly: 0 + localPlayerAuthority: 0 + m_AssetId: + m_SceneId: 3719402579 +--- !u!114 &3458215582039164093 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2141754834917738923} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 52246fc08ef54f047800aa1a2be91fd2, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &3928823046603185135 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2141754834917738923} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 62374ff6df2c74f9699075492ef7fdb9, type: 3} + m_Name: + m_EditorClassIdentifier: + m_health: 0 + m_onDamage: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0, + Culture=neutral, PublicKeyToken=null +--- !u!114 &7017421174877670493 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2141754834917738923} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4dafeb77b323d57489cc5b15183dc41b, type: 3} + m_Name: + m_EditorClassIdentifier: + forceMultiplier: 1 diff --git a/Assets/Prefabs/DeadBody.prefab.meta b/Assets/Prefabs/DeadBody.prefab.meta new file mode 100644 index 000000000..40b4f910e --- /dev/null +++ b/Assets/Prefabs/DeadBody.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 14d22e60f4926f64cbd36f393fe11b9a +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/GameManager.prefab b/Assets/Prefabs/GameManager.prefab index 53c3b02d4..8e5365e8d 100644 --- a/Assets/Prefabs/GameManager.prefab +++ b/Assets/Prefabs/GameManager.prefab @@ -171,6 +171,18 @@ MonoBehaviour: AIOutOfRangeTimeout: 5 AIOutOfRangeDistance: 250 pedSyncRate: 20 + ragdollPrefab: {fileID: 2141754834917738923, guid: 14d22e60f4926f64cbd36f393fe11b9a, + type: 3} + ragdollMass: 100 + ragdollLifetime: 90 + ragdollDrag: 0.05 + ragdollMaxDepenetrationVelocity: -1 + ragdollDamageForce: 50 + ragdollDamageForceWhenDetached: 20 + ragdollCollisionDetectionMode: 2 + ragdollSyncRate: 10 + ragdollPositionLerpFactor: 1 + ragdollRotationLerpFactor: 1 --- !u!114 &114560248511158306 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/Prefabs/NetworkManager.prefab b/Assets/Prefabs/NetworkManager.prefab index d8da275f0..3242b3d70 100644 --- a/Assets/Prefabs/NetworkManager.prefab +++ b/Assets/Prefabs/NetworkManager.prefab @@ -68,6 +68,7 @@ MonoBehaviour: - {fileID: 100010, guid: 2894e2899fe860745afbc810459e2cc2, type: 3} - {fileID: 100010, guid: 4f8c6e508a3f69243ab7b8813398b7ed, type: 3} - {fileID: 8038540346792760480, guid: 858b1226127ef9f4e8d16c9610e691b6, type: 3} + - {fileID: 2141754834917738923, guid: 14d22e60f4926f64cbd36f393fe11b9a, type: 3} --- !u!114 &4862460140142645991 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/RagdollBuilder.cs b/Assets/RagdollBuilder.cs index d79f33c51..81db8dd09 100644 --- a/Assets/RagdollBuilder.cs +++ b/Assets/RagdollBuilder.cs @@ -5,6 +5,7 @@ using UnityEngine; using System.Collections; using System; +using System.Collections.Generic; #pragma warning disable 649 @@ -45,7 +46,7 @@ public class RagdollBuilder Vector3 worldForward = Vector3.forward; public bool flipForward = false; - class BoneInfo + public class BoneInfo { public string name; @@ -68,9 +69,14 @@ class BoneInfo public float summedMass;// The mass of this and all children bodies } - ArrayList bones; + List bones; + public List Bones => bones; + BoneInfo rootBone; + public List AdditionalColliders = new List(); + + string CheckConsistency() { PrepareBones(); @@ -139,7 +145,7 @@ public void PrepareBones() worldForward = pelvis.TransformDirection(forward); } - bones = new ArrayList(); + bones = new List(); rootBone = new BoneInfo(); rootBone.name = "Pelvis"; @@ -280,7 +286,7 @@ void Cleanup() } } - void BuildBodies() + public void BuildBodies() { foreach (BoneInfo bone in bones) { @@ -289,7 +295,7 @@ void BuildBodies() } } - void BuildJoints() + public void BuildJoints() { foreach (BoneInfo bone in bones) { @@ -335,7 +341,7 @@ void CalculateMassRecurse(BoneInfo bone) bone.summedMass = mass; } - void CalculateMass() + public void CalculateMass() { // Calculate allChildMass by summing all bodies CalculateMassRecurse(rootBone); @@ -519,6 +525,9 @@ void AddHeadCollider() newBox.size = new Vector3(radius * 0.65f, 0.75f * breastBox.size.y, 0.5f * breastBox.size.z); newBox.center = breastBox.center + Vector3.right * (breastBox.size.x * 0.5f + newBox.size.x * 0.5f); + + AdditionalColliders.Add(jawSphere); + AdditionalColliders.Add(newBox); } } } diff --git a/Assets/Scripts/Behaviours/GameManager.cs b/Assets/Scripts/Behaviours/GameManager.cs index ff48159d8..6a6a250a3 100644 --- a/Assets/Scripts/Behaviours/GameManager.cs +++ b/Assets/Scripts/Behaviours/GameManager.cs @@ -25,6 +25,8 @@ public class GameManager : MonoBehaviour { [SerializeField] [Range(5, 100)] int m_defaultPhysicsUpdateRate = 30; [SerializeField] [Range(5, 100)] int m_defaultPhysicsUpdateRateOnMobile = 20; + public static int DefaultLayerIndex => 0; + public Vector2 cursorSensitivity = new Vector2(2f, 2f); diff --git a/Assets/Scripts/Behaviours/Ped/DeadBody.cs b/Assets/Scripts/Behaviours/Ped/DeadBody.cs new file mode 100644 index 000000000..bcd51cf7b --- /dev/null +++ b/Assets/Scripts/Behaviours/Ped/DeadBody.cs @@ -0,0 +1,270 @@ +using System.Collections.Generic; +using System.Linq; +using Mirror; +using SanAndreasUnity.Importing.Items; +using SanAndreasUnity.Importing.Items.Definitions; +using SanAndreasUnity.Net; +using SanAndreasUnity.Utilities; +using UnityEngine; +using Object = UnityEngine.Object; +using Random = UnityEngine.Random; + +namespace SanAndreasUnity.Behaviours.Peds +{ + public class DeadBody : NetworkBehaviour + { + private static List _deadBodies = new List(); + public static IEnumerable DeadBodies => _deadBodies; + public static int NumDeadBodies => _deadBodies.Count; + + public PushableByDamage PushableByDamage { get; private set; } + + private struct BoneInfo + { + public BoneInfo(Transform transform) + { + this.Transform = transform; + this.Rigidbody = transform.GetComponent(); + } + + public Transform Transform { get; set; } + public Rigidbody Rigidbody { get; set; } + } + + private Dictionary m_framesDict = new Dictionary(); + public int NumBones => m_framesDict.Count; + + private Dictionary m_rigidBodiesDict = new Dictionary(); + public int NumRigidBodies => m_rigidBodiesDict.Count; + + private int m_net_modelId; + + private struct BoneSyncData + { + public byte boneId; + public Vector3 position; + public Vector3 rotation; + public Vector3 velocity; + } + + private List m_bonesSyncData = new List(); + + + + private void Awake() + { + this.PushableByDamage = this.GetComponentOrThrow(); + this.PushableByDamage.forceMultiplier = PedManager.Instance.ragdollDamageForceWhenDetached; + + this.RefreshSyncRate(); + } + + private void OnEnable() + { + _deadBodies.Add(this); + } + + private void OnDisable() + { + _deadBodies.Remove(this); + } + + public override void OnStartClient() + { + if (NetStatus.IsServer) + return; + + F.RunExceptionSafe(this.InitialClientOnlySetup); + } + + private void InitialClientOnlySetup() + { + var def = Item.GetDefinition(m_net_modelId); + if (null == def) + { + Debug.LogError($"Failed to initialize dead body: ped definition not found by id {m_net_modelId}"); + return; + } + + this.gameObject.name = $"dead body {m_net_modelId} {def.ModelName}"; + + var model = this.gameObject.GetOrAddComponent(); + model.Load(m_net_modelId); + + // add rigid bodies - syncing looks smoother with them + model.RagdollBuilder.BuildBodies(); + foreach (var rb in this.transform.GetComponentsInChildren()) + { + rb.useGravity = false; + rb.detectCollisions = false; + rb.maxAngularVelocity = 0; + rb.interpolation = PedManager.Instance.ragdollInterpolationMode; + } + + m_framesDict = model.Frames.ToDictionary(f => f.BoneId, f => new BoneInfo(f.transform)); + + Object.Destroy(model.AnimComponent); + Object.Destroy(model); + + // apply initial sync data + // first sync should've been done before calling this function + this.UpdateBonesAfterDeserialization((byte)m_bonesSyncData.Count); + + } + + public override bool OnSerialize(NetworkWriter writer, bool initialState) + { + if (initialState) + writer.Write(m_net_modelId); + + writer.Write((byte)m_framesDict.Count); + + foreach (var pair in m_framesDict) + { + int boneId = pair.Key; + Transform tr = pair.Value.Transform; + + var boneSyncData = new BoneSyncData(); + boneSyncData.boneId = (byte)boneId; + boneSyncData.position = tr.localPosition; + boneSyncData.rotation = tr.localRotation.eulerAngles; + boneSyncData.velocity = m_rigidBodiesDict.TryGetValue(boneId, out Rigidbody rb) ? GetVelocityForSending(rb) : Vector3.zero; + + writer.Write(boneSyncData.boneId); + writer.Write(boneSyncData.position); + writer.Write(boneSyncData.rotation); + writer.Write(boneSyncData.velocity); + } + + return true; + } + + public override void OnDeserialize(NetworkReader reader, bool initialState) + { + if (initialState) + m_net_modelId = reader.ReadInt32(); + + byte count = reader.ReadByte(); + + m_bonesSyncData.EnsureCount(count); + + for (int i = 0; i < count; i++) + { + var boneSyncData = new BoneSyncData(); + boneSyncData.boneId = reader.ReadByte(); + boneSyncData.position = reader.ReadVector3(); + boneSyncData.rotation = reader.ReadVector3(); + boneSyncData.velocity = reader.ReadVector3(); + m_bonesSyncData[i] = boneSyncData; + } + + F.RunExceptionSafe(() => UpdateBonesAfterDeserialization(count)); + } + + private void UpdateBonesAfterDeserialization(byte count) + { + for (int i = 0; i < count; i++) + { + var boneSyncData = m_bonesSyncData[i]; + if (m_framesDict.TryGetValue(boneSyncData.boneId, out BoneInfo boneInfo)) + { + SetPosition(boneInfo, boneSyncData.position); + SetRotation(boneInfo, boneSyncData.rotation); + if (boneInfo.Rigidbody != null) + SetVelocity(boneInfo, boneSyncData.velocity); + } + } + } + + public static DeadBody Create(Transform ragdollTransform, Ped ped) + { + NetStatus.ThrowIfNotOnServer(); + + GameObject ragdollGameObject = Object.Instantiate(PedManager.Instance.ragdollPrefab); + DeadBody deadBody = ragdollGameObject.GetComponentOrThrow(); + + Object.Destroy(ragdollGameObject, PedManager.Instance.ragdollLifetime * Random.Range(0.85f, 1.15f)); + + ragdollGameObject.name = "dead body " + ped.name; + + ragdollTransform.SetParent(ragdollGameObject.transform); + + deadBody.m_framesDict = ragdollTransform.GetComponentsInChildren() + .ToDictionary(f => f.BoneId, f => new BoneInfo(f.transform)); + + foreach (var pair in deadBody.m_framesDict) + { + var rb = pair.Value.Rigidbody; + if (rb != null) + deadBody.m_rigidBodiesDict.Add(pair.Key, rb); + } + + deadBody.InitSyncVarsOnServer(ped); + + NetManager.Spawn(ragdollGameObject); + + return deadBody; + } + + private void InitSyncVarsOnServer(Ped ped) + { + m_net_modelId = ped.PedDef.Id; + } + + private void Update() + { + if (NetStatus.IsServer) + { + this.SetDirtyBit(1); + } + } + + public void RefreshSyncRate() + { + this.syncInterval = 1.0f / PedManager.Instance.ragdollSyncRate; + } + + private static Vector3 GetVelocityForSending(Rigidbody rb) + { + // it's better to send local velocity, because rotation of ragdoll can change very fast, and so + // will the world velocity + return rb.transform.InverseTransformVector(rb.velocity); + } + + private static Vector3 GetReceivedVelocityAsLocal(Transform tr, Vector3 receivedVelocity) + { + return receivedVelocity; + } + + private static Vector3 GetReceivedVelocityAsWorld(Transform tr, Vector3 receivedVelocity) + { + return tr.TransformVector(receivedVelocity); + } + + private static void SetPosition(BoneInfo boneInfo, Vector3 receivedPosition) + { + // if (boneInfo.Rigidbody != null) + // boneInfo.Rigidbody.MovePosition(boneInfo.Transform.TransformVector(receivedPosition)); + // else + // boneInfo.Transform.localPosition = receivedPosition; + + boneInfo.Transform.localPosition = receivedPosition; + } + + private static void SetRotation(BoneInfo boneInfo, Vector3 receivedRotation) + { + // Quaternion localRotation = Quaternion.Euler(receivedRotation); + // if (boneInfo.Rigidbody != null) + // boneInfo.Rigidbody.MoveRotation(boneInfo.Transform.TransformRotation(localRotation)); + // else + // boneInfo.Transform.localRotation = localRotation; + + boneInfo.Transform.localRotation = Quaternion.Euler(receivedRotation); + } + + private static void SetVelocity(BoneInfo boneInfo, Vector3 receivedVelocity) + { + boneInfo.Rigidbody.velocity = GetReceivedVelocityAsWorld(boneInfo.Transform, receivedVelocity); + } + } +} diff --git a/Assets/Scripts/Behaviours/Ped/DeadBody.cs.meta b/Assets/Scripts/Behaviours/Ped/DeadBody.cs.meta new file mode 100644 index 000000000..95cb8b293 --- /dev/null +++ b/Assets/Scripts/Behaviours/Ped/DeadBody.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 52246fc08ef54f047800aa1a2be91fd2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Behaviours/Ped/Ped_Damage.cs b/Assets/Scripts/Behaviours/Ped/Ped_Damage.cs index 5a74a8e21..cc2a139d1 100644 --- a/Assets/Scripts/Behaviours/Ped/Ped_Damage.cs +++ b/Assets/Scripts/Behaviours/Ped/Ped_Damage.cs @@ -16,6 +16,13 @@ public partial class Ped { public Bar HealthBar { get; private set; } + /// + /// Damage info that killed the ped. + /// + public DamageInfo KillingDamageInfo { get; set; } + + private bool m_alreadyKilled = false; + void AwakeForDamage () @@ -125,6 +132,26 @@ public void DisplayInflictedDamageMessage(float damageAmount) msg.Text = damageAmount.ToString(); } + public void Kill() + { + F.RunExceptionSafe(this.KillInternal); + } + + void KillInternal() + { + if (m_alreadyKilled) + return; + + m_alreadyKilled = true; + + if (this.PlayerModel != null) + { + this.PlayerModel.DetachRagdoll(this.KillingDamageInfo); + } + + Object.Destroy(this.gameObject); + } + } } diff --git a/Assets/Scripts/Behaviours/Ped/States/BaseScriptState.cs b/Assets/Scripts/Behaviours/Ped/States/BaseScriptState.cs index 0df96b7dd..e904a19e2 100644 --- a/Assets/Scripts/Behaviours/Ped/States/BaseScriptState.cs +++ b/Assets/Scripts/Behaviours/Ped/States/BaseScriptState.cs @@ -318,7 +318,8 @@ public virtual void OnDamaged(DamageInfo damageInfo) if (m_ped.Health <= 0) { - Object.Destroy(m_ped.gameObject); + m_ped.KillingDamageInfo = damageInfo; + m_ped.Kill(); } // notify clients diff --git a/Assets/Scripts/Behaviours/PedManager.cs b/Assets/Scripts/Behaviours/PedManager.cs index a6c5ca384..54b997df8 100644 --- a/Assets/Scripts/Behaviours/PedManager.cs +++ b/Assets/Scripts/Behaviours/PedManager.cs @@ -55,6 +55,19 @@ public class PedManager : MonoBehaviour public float pedSyncRate = 10; + [Header("Ragdoll")] + + public GameObject ragdollPrefab; + public float ragdollMass = 100f; + public float ragdollLifetime = 30f; + public float ragdollDrag = 0.05f; + public float ragdollMaxDepenetrationVelocity = 10f; + public float ragdollDamageForce = 4f; + public float ragdollDamageForceWhenDetached = 4f; + public CollisionDetectionMode ragdollCollisionDetectionMode = CollisionDetectionMode.Discrete; + [Range(1, 60)] public float ragdollSyncRate = 20f; + public RigidbodyInterpolation ragdollInterpolationMode = RigidbodyInterpolation.Extrapolate; + void Awake () { diff --git a/Assets/Scripts/Behaviours/PedModel.cs b/Assets/Scripts/Behaviours/PedModel.cs index 0ee55589b..54dd08152 100644 --- a/Assets/Scripts/Behaviours/PedModel.cs +++ b/Assets/Scripts/Behaviours/PedModel.cs @@ -5,6 +5,7 @@ using SanAndreasUnity.Importing.Animation; using UnityEngine; using System.Linq; +using SanAndreasUnity.Behaviours.Peds; using SanAndreasUnity.Utilities; namespace SanAndreasUnity.Behaviours @@ -100,6 +101,9 @@ private readonly Dictionary _loadedAnims readonly Dictionary m_damageLevelPerBones = new Dictionary(); + private RagdollBuilder m_ragdollBuilder; + public RagdollBuilder RagdollBuilder => m_ragdollBuilder; + public class FrameAnimData { public Vector3 pos; @@ -332,7 +336,7 @@ void SetupRagdoll() m_damageLevelPerBones.Clear(); - RagdollBuilder rb = new RagdollBuilder + RagdollBuilder rb = m_ragdollBuilder = new RagdollBuilder { pelvis = this.RootFrame.transform, leftHips = this.L_Thigh.transform, @@ -350,6 +354,8 @@ void SetupRagdoll() jaw = this.Jaw.transform, }; + rb.totalMass = PedManager.Instance.ragdollMass; + rb.PrepareBones(); rb.OnWizardCreate(); @@ -378,6 +384,61 @@ void SetupRagdoll() } + public DeadBody DetachRagdoll(DamageInfo damageInfo) + { + if (null == m_ragdollBuilder) + return null; + + if (null == this.RootFrame) + return null; + + Transform ragdollTransform = this.RootFrame.transform; + + // remove colliders which are not part of ragdoll + foreach (var c in m_ragdollBuilder.AdditionalColliders) + { + Object.Destroy(c); + } + + m_ragdollBuilder.BuildBodies(); + m_ragdollBuilder.BuildJoints(); + m_ragdollBuilder.CalculateMass(); + + m_ragdollBuilder = null; + + ragdollTransform.SetParent(null); + + // setup rigid bodies + foreach (var rb in ragdollTransform.GetComponentsInChildren()) + { + rb.drag = PedManager.Instance.ragdollDrag; + if (PedManager.Instance.ragdollMaxDepenetrationVelocity >= 0) + rb.maxDepenetrationVelocity = PedManager.Instance.ragdollMaxDepenetrationVelocity; + rb.collisionDetectionMode = PedManager.Instance.ragdollCollisionDetectionMode; + + // add velocity to ragdoll based on current ped's velocity + rb.velocity = m_ped.Velocity; + } + + // apply force to a collider that was hit by a weapon + if (damageInfo != null && damageInfo.raycastHitTransform != null && damageInfo.damageType == DamageType.Bullet) + { + var c = damageInfo.raycastHitTransform.GetComponent(); + if (c != null && c.attachedRigidbody != null) + { + c.attachedRigidbody.AddForceAtPosition( + damageInfo.hitDirection * damageInfo.amount.SqrtOrZero() * PedManager.Instance.ragdollDamageForce, + damageInfo.hitPoint, + ForceMode.Impulse); + } + } + + // change layer + ragdollTransform.gameObject.SetLayerRecursive(GameManager.DefaultLayerIndex); + + return DeadBody.Create(ragdollTransform, m_ped); + } + public float GetAmountOfDamageForBone(Transform boneTransform, float baseDamageValue) { if (m_damageLevelPerBones.TryGetValue(boneTransform, out int damageLevel)) diff --git a/Assets/Scripts/Behaviours/Vehicles/Vehicle_Damage.cs b/Assets/Scripts/Behaviours/Vehicles/Vehicle_Damage.cs index 7e3b6cc49..c49d5904c 100644 --- a/Assets/Scripts/Behaviours/Vehicles/Vehicle_Damage.cs +++ b/Assets/Scripts/Behaviours/Vehicles/Vehicle_Damage.cs @@ -207,6 +207,14 @@ private void ExplodeInternal() // assign explosion sound F.RunExceptionSafe(() => AssignExplosionSound(explosionGo)); + // kill all peds inside + foreach (Ped ped in this.Seats + .Select(s => s.OccupyingPed) + .Where(p => p != null) + .ToList()) + { + ped.Kill(); + } } public void DetachFrameDuringExplosion(string frameName, float mass, GameObject parentGo) diff --git a/Assets/Scripts/Behaviours/Weapon.cs b/Assets/Scripts/Behaviours/Weapon.cs index fd8ed0428..bfc56d322 100644 --- a/Assets/Scripts/Behaviours/Weapon.cs +++ b/Assets/Scripts/Behaviours/Weapon.cs @@ -713,9 +713,11 @@ public virtual void FireProjectile (Vector3 firePos, Vector3 fireDir, WeaponAtta { amount = this.Damage, raycastHitTransform = hit.collider.transform, + hitDirection = fireDir, hitPoint = hit.point, hitNormal = hit.normal, attacker = m_ped, + damageType = DamageType.Bullet, }); } diff --git a/Assets/Scripts/Networking/PlayerRequests.cs b/Assets/Scripts/Networking/PlayerRequests.cs index 3b94a6265..49fe162b6 100644 --- a/Assets/Scripts/Networking/PlayerRequests.cs +++ b/Assets/Scripts/Networking/PlayerRequests.cs @@ -115,8 +115,11 @@ public void RequestSuicide() [Command] void CmdRequestSuicide() { - if (m_player.OwnedPed != null) - Destroy(m_player.OwnedPed.gameObject); + F.RunExceptionSafe(() => + { + if (m_player.OwnedPed != null) + m_player.OwnedPed.Kill(); + }); } public void RequestToDestroyMyVehicles() => this.CmdRequestToDestroyMyVehicles(); diff --git a/Assets/Scripts/Settings/MiscSettings.cs b/Assets/Scripts/Settings/MiscSettings.cs index fb926b93b..c99265126 100644 --- a/Assets/Scripts/Settings/MiscSettings.cs +++ b/Assets/Scripts/Settings/MiscSettings.cs @@ -109,6 +109,15 @@ public class MiscSettings : MonoBehaviour { setValue = (value) => { PedManager.Instance.pedTurnSpeed = value; }, persistType = OptionsWindow.InputPersistType.OnStart, }; + OptionsWindow.FloatInput m_deadBodyLifetime = new OptionsWindow.FloatInput + { + description = "Dead body lifetime", + minValue = 0.5f, + maxValue = 300f, + getValue = () => PedManager.Instance.ragdollLifetime, + setValue = (value) => { PedManager.Instance.ragdollLifetime = value; }, + persistType = OptionsWindow.InputPersistType.OnStart, + }; @@ -118,6 +127,7 @@ void Awake () m_runInBackgroundInput, m_drawLineFromGunInput, m_enableCamera, m_displayFpsInput, m_pausePlayerSpawning, m_playerSpawnInterval, m_vehicleDetachedPartLifetime, + m_deadBodyLifetime, m_projectileReloadTime, m_turnSpeedInput, }; diff --git a/Assets/Scripts/Settings/NetSettings.cs b/Assets/Scripts/Settings/NetSettings.cs index 80334aa69..a18208066 100644 --- a/Assets/Scripts/Settings/NetSettings.cs +++ b/Assets/Scripts/Settings/NetSettings.cs @@ -1,4 +1,5 @@ using SanAndreasUnity.Behaviours; +using SanAndreasUnity.Behaviours.Peds; using SanAndreasUnity.Behaviours.Vehicles; using SanAndreasUnity.UI; using SanAndreasUnity.Utilities; @@ -14,6 +15,15 @@ public class NetSettings : MonoBehaviour setValue = (value) => { ApplyPedSyncRate(value); }, persistType = OptionsWindow.InputPersistType.OnStart }; + OptionsWindow.FloatInput m_deadBodySyncRate = new OptionsWindow.FloatInput + { + description = "Dead body sync rate", + minValue = 1, + maxValue = 60, + getValue = () => PedManager.Instance.ragdollSyncRate, + setValue = ApplyDeadBodySyncRate, + persistType = OptionsWindow.InputPersistType.OnStart, + }; OptionsWindow.FloatInput m_vehicleSyncRate = new OptionsWindow.FloatInput ("Vehicle sync rate", 1, 60) { isAvailable = () => VehicleManager.Instance != null, @@ -69,6 +79,7 @@ private void Awake() { OptionsWindow.RegisterInputs ("NET", m_pedSyncRate, + m_deadBodySyncRate, m_vehicleSyncRate, m_syncVehicleTransformUsingSyncVars, m_syncVehiclesLinearVelocity, @@ -86,6 +97,13 @@ static void ApplyPedSyncRate(float syncRate) ped.ApplySyncRate(syncRate); } + static void ApplyDeadBodySyncRate(float syncRate) + { + PedManager.Instance.ragdollSyncRate = syncRate; + foreach (var deadBody in DeadBody.DeadBodies) + deadBody.RefreshSyncRate(); + } + static void ApplyVehicleSyncRate(float syncRate) { VehicleManager.Instance.vehicleSyncRate = syncRate; diff --git a/Assets/Scripts/Stats/MiscStats.cs b/Assets/Scripts/Stats/MiscStats.cs index d9a21756c..8553191aa 100644 --- a/Assets/Scripts/Stats/MiscStats.cs +++ b/Assets/Scripts/Stats/MiscStats.cs @@ -2,6 +2,7 @@ using System.Linq; using UnityEngine; using SanAndreasUnity.Behaviours; +using SanAndreasUnity.Behaviours.Peds; using SanAndreasUnity.Behaviours.Vehicles; using SanAndreasUnity.Utilities; @@ -22,6 +23,9 @@ void OnStatGUI() sb.AppendFormat("num peds: {0}\n", Ped.NumPeds); sb.AppendFormat("num vehicles: {0}\n", Vehicle.NumVehicles); + sb.AppendFormat("num dead bodies: {0}\n", DeadBody.NumDeadBodies); + sb.AppendFormat("num bones in dead bodies: {0}\n", DeadBody.DeadBodies.Sum(db => db.NumBones)); + sb.AppendFormat("num rigid bodies in dead bodies: {0}\n", DeadBody.DeadBodies.Sum(db => db.NumRigidBodies)); sb.AppendFormat("num ped state changes received: {0}\n", Ped.NumStateChangesReceived); sb.AppendLine(); diff --git a/Assets/Scripts/UI/PedsWindow.cs b/Assets/Scripts/UI/PedsWindow.cs index 963d9db88..e2ebf6611 100644 --- a/Assets/Scripts/UI/PedsWindow.cs +++ b/Assets/Scripts/UI/PedsWindow.cs @@ -5,6 +5,7 @@ using SanAndreasUnity.Importing.Items; using SanAndreasUnity.Importing.Items.Definitions; using System.Linq; +using SanAndreasUnity.Behaviours.Peds; namespace SanAndreasUnity.UI { @@ -62,10 +63,12 @@ protected override void OnWindowGUI () if (NetUtils.IsServer) { - // button to kill all peds - if (GUILayout.Button ("Kill all peds", GUILayout.Width (100))) { + if (GUILayout.Button ("Kill all peds", GUILayout.Width (100))) KillAllPeds (); - } + + if (GUILayout.Button ("Remove all dead bodies", GUILayout.Width (180))) + RemoveAllDeadBodies (); + GUILayout.Space (5); } @@ -159,11 +162,18 @@ private Rect GetLayoutRect (float height) private static void KillAllPeds () { - - foreach (var p in Ped.AllPeds) { - Destroy (p.gameObject); + foreach (var p in Ped.AllPeds) + { + p.Kill(); } + } + private static void RemoveAllDeadBodies () + { + foreach (var db in DeadBody.DeadBodies.ToList()) + { + Destroy (db.gameObject); + } } } diff --git a/Assets/Scripts/Utilities/Damageable.cs b/Assets/Scripts/Utilities/Damageable.cs index 06c734bd2..9109caf8c 100644 --- a/Assets/Scripts/Utilities/Damageable.cs +++ b/Assets/Scripts/Utilities/Damageable.cs @@ -10,6 +10,7 @@ public class DamageInfo public float amount = 0f; public string damageType = null; public Transform raycastHitTransform = null; + public Vector3 hitDirection = Vector3.forward; public Vector3 hitPoint = Vector3.zero; public Vector3 hitNormal = Vector3.up; public object attacker = null; @@ -20,7 +21,10 @@ public static class DamageType { public static readonly string Bullet = "Bullet", - Explosion = "Explosion"; + Explosion = "Explosion", + Gas = "Gas", + Flame = "Flame", + Melee = "Melee"; } public class Damageable : MonoBehaviour diff --git a/Assets/Scripts/Utilities/ExplosionForce.cs b/Assets/Scripts/Utilities/ExplosionForce.cs index 7e629d6d7..b895b505a 100644 --- a/Assets/Scripts/Utilities/ExplosionForce.cs +++ b/Assets/Scripts/Utilities/ExplosionForce.cs @@ -16,8 +16,7 @@ public class ExplosionForce : MonoBehaviour private IEnumerator Start() { - // wait one frame because some explosions instantiate debris which should then - // be pushed by physics force + // wait one frame because some objects can be spawned right after the explosion yield return null; float multiplier = this.explosionMultiplier; diff --git a/Assets/Scripts/Utilities/F.cs b/Assets/Scripts/Utilities/F.cs index 809feb47d..9ffc74b75 100644 --- a/Assets/Scripts/Utilities/F.cs +++ b/Assets/Scripts/Utilities/F.cs @@ -92,6 +92,15 @@ public static int RoundToInt(this float f) return Mathf.RoundToInt (f); } + public static float SqrtOrZero(this float f) + { + if (float.IsNaN(f)) + return 0f; + if (f <= 0f) + return 0f; + return Mathf.Sqrt(f); + } + public static double DateTimeToUnixTimestamp(this DateTime dateTime) { return (TimeZoneInfo.ConvertTimeToUtc(dateTime) - @@ -523,6 +532,18 @@ public static bool AddIfNotPresent (this List list, T item) return false; } + public static void EnsureCount(this List list, int count) + { + if (list.Count < count) + { + int diff = count - list.Count; + for (int i = 0; i < diff; i++) + { + list.Add(default); + } + } + } + public static T RandomElement (this IList list) { if (list.Count < 1) @@ -530,6 +551,14 @@ public static T RandomElement (this IList list) return list [UnityEngine.Random.Range(0, list.Count)]; } + public static void ForEach(this IEnumerable enumerable, System.Action action) + { + foreach (var element in enumerable) + { + action(element); + } + } + public static int RemoveDeadObjects (this List list) where T : UnityEngine.Object { return list.RemoveAll(item => null == item); diff --git a/Assets/Scripts/Utilities/PushableByDamage.cs b/Assets/Scripts/Utilities/PushableByDamage.cs new file mode 100644 index 000000000..b7b0388d8 --- /dev/null +++ b/Assets/Scripts/Utilities/PushableByDamage.cs @@ -0,0 +1,44 @@ +using UnityEngine; + +namespace SanAndreasUnity.Utilities +{ + public class PushableByDamage : MonoBehaviour + { + public float forceMultiplier = 1; + private Damageable _damageable; + + + private void Awake() + { + _damageable = this.GetComponentOrThrow(); + _damageable.OnDamageEvent.AddListener(this.OnDamaged); + } + + void OnDamaged() + { + if (!NetUtils.IsServer) + return; + + DamageInfo damageInfo = _damageable.LastDamageInfo; + + if (damageInfo.damageType != DamageType.Bullet) + return; + + if (null == damageInfo.raycastHitTransform) + return; + + var c = damageInfo.raycastHitTransform.GetComponent(); + if (null == c) + return; + + var rb = c.attachedRigidbody; + if (null == rb) + return; + + rb.AddForceAtPosition( + damageInfo.hitDirection * damageInfo.amount.SqrtOrZero() * this.forceMultiplier, + damageInfo.hitPoint, + ForceMode.Impulse); + } + } +} diff --git a/Assets/Scripts/Utilities/PushableByDamage.cs.meta b/Assets/Scripts/Utilities/PushableByDamage.cs.meta new file mode 100644 index 000000000..84dc1c077 --- /dev/null +++ b/Assets/Scripts/Utilities/PushableByDamage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4dafeb77b323d57489cc5b15183dc41b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: