using UnityEngine; using ArcadeVP; using UnityEngine.TextCore.Text; public class CarHealth : MonoBehaviour { [Header("Health Settings")] public float maxHealth = 100f; public float currentHealth; public bool isDestroyed = false; public GameObject charactersHips; [Header("Damage Physics")] [Tooltip("Layers that can cause crash damage (e.g., Default, Obstacles). Roads and ground should NOT be in this mask. Cars will always damage each other regardless of layer.")] public LayerMask damagingLayers = Physics.DefaultRaycastLayers; // By default hits most things, user can tweak in Inspector [Tooltip("Minimum impact speed (relative velocity) required to take damage. Scraping is usually 1-3 m/s, ramming is 10+ m/s.")] public float minImpactSpeed = 5f; [Tooltip("Damage taken per 1 unit of speed above the minimum impact speed.")] public float damageMultiplier = 2.5f; [Tooltip("Cooldown between damage events to prevent multi-hit frame glitches.")] public float damageCooldown = 0.2f; private float lastDamageTime = 0f; private ArcadeVehicleController vehicleController; void Start() { currentHealth = maxHealth; vehicleController = GetComponent(); // Ensure we receive collisions even if the colliders/rigidbodies are on child objects (like SphereRB or CarBody) if (vehicleController != null) { AttachCollisionForwarder(vehicleController.rb?.gameObject); AttachCollisionForwarder(vehicleController.carBody?.gameObject); } else { AttachCollisionForwarder(gameObject); } // --- FREEZE RAGDOLL ON SPAWN --- // Unity's ragdoll wizard turns physics on by default. We must freeze them // until the car is destroyed so the driver stays seated! if (charactersHips != null) { Rigidbody[] ragdollBodies = charactersHips.GetComponentsInChildren(); foreach (Rigidbody rb in ragdollBodies) { rb.isKinematic = true; } // Disable their colliders so they don't glitch the car's physics or cause self-damage! Collider[] ragdollColliders = charactersHips.GetComponentsInChildren(); foreach (Collider col in ragdollColliders) { col.enabled = false; } } } private void AttachCollisionForwarder(GameObject targetObj) { if (targetObj == null) return; CollisionForwarder forwarder = targetObj.GetComponent(); if (forwarder == null) { forwarder = targetObj.AddComponent(); } forwarder.healthScript = this; } public void HandleCollision(Collision collision) { if (Time.time < lastDamageTime + damageCooldown) return; // 1. Check if we hit another car CarHealth otherCar = collision.gameObject.GetComponentInParent(); if (otherCar == null) { CollisionForwarder forwarder = collision.gameObject.GetComponent(); if (forwarder != null) otherCar = forwarder.healthScript; } // 2. Filter out road/ground damage // If we didn't hit a car, check if the object is in our allowed damaging layers mask if (otherCar == null) { if ((damagingLayers.value & (1 << collision.gameObject.layer)) == 0) { return; // Ignored! (e.g. hit the Road or Drivable layer) } } // 3. relativeVelocity is the exact difference in physical speed between the two colliding objects! float impactSpeed = collision.relativeVelocity.magnitude; if (impactSpeed >= minImpactSpeed) { float speedAdvantageMultiplier = 1f; // If it's a car vs car crash, calculate who is faster! if (otherCar != null && vehicleController.carBody != null && otherCar.vehicleController.carBody != null) { float mySpeed = vehicleController.carBody.linearVelocity.magnitude; float theirSpeed = otherCar.vehicleController.carBody.linearVelocity.magnitude; if (mySpeed > theirSpeed) { // We are faster: Take LESS damage (reduces by 5% per 1m/s advantage, max 80% reduction) float speedDiff = mySpeed - theirSpeed; speedAdvantageMultiplier = Mathf.Clamp(1f - (speedDiff * 0.05f), 0.2f, 1f); } else if (theirSpeed > mySpeed) { // They are faster: We take MORE damage (increases by 5% per 1m/s disadvantage, max 200% penalty) float speedDiff = theirSpeed - mySpeed; speedAdvantageMultiplier = Mathf.Clamp(1f + (speedDiff * 0.05f), 1f, 2.0f); } } float damageToTake = (impactSpeed - minImpactSpeed) * damageMultiplier * speedAdvantageMultiplier; TakeDamage(damageToTake); } } public void TakeDamage(float amount) { if (currentHealth <= 0) return; // Already destroyed currentHealth -= amount; currentHealth = Mathf.Clamp(currentHealth, 0f, maxHealth); lastDamageTime = Time.time; Debug.Log($"{gameObject.name} took {amount:F1} damage! Health: {currentHealth:F1}"); if (currentHealth <= 0f) { DestroyCar(); } } public void RestoreHealth(float amount) { if (currentHealth <= 0) return; // Can't heal a completely destroyed car (optional) currentHealth += amount; currentHealth = Mathf.Clamp(currentHealth, 0f, maxHealth); Debug.Log($"{gameObject.name} restored {amount:F1} health! Health: {currentHealth:F1}"); } private void DestroyCar() { isDestroyed = true; Debug.Log($"{gameObject.name} has been destroyed!"); // Disable driving controls so the wreck rolls to a stop if (vehicleController != null) { vehicleController.enabled = false; } // --- EJECT DRIVER (RAGDOLL) --- if (charactersHips != null) { // Unparent from the car so the driver flies free charactersHips.transform.SetParent(null); // Teleport them up slightly so they don't snag on the steering wheel charactersHips.transform.position += Vector3.up * 1.5f; // Kill the Animator (search parents AND children to guarantee we find it) Animator anim = charactersHips.GetComponentInParent(); if (anim == null) anim = charactersHips.GetComponentInChildren(); if (anim != null) anim.enabled = false; // Enable ragdoll physics and apply force Rigidbody[] ragdollBodies = charactersHips.GetComponentsInChildren(); Collider[] ragdollColliders = charactersHips.GetComponentsInChildren(); // Turn all their colliders back on so they bounce on the road foreach (Collider col in ragdollColliders) { col.enabled = true; } // FALLBACK: If you haven't set up a Unity Ragdoll yet (0 rigidbodies found), // we will instantly create a dummy physics body so they still fly out! if (ragdollBodies.Length == 0) { Rigidbody dummyRb = charactersHips.gameObject.AddComponent(); BoxCollider box = charactersHips.gameObject.AddComponent(); box.size = new Vector3(0.5f, 1.5f, 0.5f); // Rough human size ragdollBodies = new Rigidbody[] { dummyRb }; } Vector3 carVelocity = vehicleController != null && vehicleController.carBody != null ? vehicleController.carBody.linearVelocity : Vector3.zero; Vector3 ejectForce = transform.forward * 15f + transform.up * 8f; foreach (Rigidbody rb in ragdollBodies) { rb.isKinematic = false; rb.useGravity = true; rb.linearVelocity = carVelocity; rb.AddForce(ejectForce, ForceMode.Impulse); // Add some random spin to make the crash look absolutely brutal! rb.AddTorque(Random.insideUnitSphere * 10f, ForceMode.Impulse); } } // Disable AI script if it's an AI car AIController ai = GetComponent(); if (ai != null) { ai.currentState = AIController.AIState.Destroyed; ai.enabled = false; } // Optional: Trigger explosion effect here // Instantiate(explosionPrefab, transform.position, Quaternion.identity); // We leave the GameObject alive so the wrecked chassis stays on the road as an obstacle! } } // Small helper class to route collisions from child Rigidbodies back to the main CarHealth script public class CollisionForwarder : MonoBehaviour { public CarHealth healthScript; private void OnCollisionEnter(Collision collision) { if (healthScript != null) { healthScript.HandleCollision(collision); } } }