using System; using System.Collections.Generic; using System.IO; using System.Reflection; using UnityEngine; namespace MSCO { /// /// Unfallschäden (verbeulte Karosserie): Das Spiel verformt die Meshes der Fahrzeuge bei Kollisionen /// (CarDamage, CarDamage2, Deformable). Das lässt sich nicht nachspielen, also übertragen wir das Ergebnis: /// Wer das Fahrzeug fährt (oder bei freien Fahrzeugen: bei wem es passiert), schickt die geänderten Eckpunkte. /// public class DamageSync { const float ScanInterval = 10f; const float CheckInterval = 0.5f; // so oft nach einem Aufprall prüfen const float ImpactWindow = 4f; // so lange nach einem Aufprall prüfen (Verformung läuft ein paar Frames) const float ImpactDeltaV = 2.5f; // plötzliche Geschwindigkeitsänderung (m/s pro Frame) = Aufprall const float SafetyCheckInterval = 2f; // sonst nur ab und zu EIN Mesh (z.B. Reparatur in der Werkstatt) - verteilt statt alle auf einmal const float MinMove = 0.002f; const int MaxPerMessage = 4000; static readonly string[] DamageTypes = { "CarDamage", "CarDamage2", "Deformable" }; class DamageMesh { public int Key; public string Root; public MeshFilter Filter; public Rigidbody Body; // Fahrzeug, zu dem das Mesh gehört public Vector3[] Known; // zuletzt gesendet/empfangen } readonly Dictionary meshes = new Dictionary(); readonly HashSet seen = new HashSet(); readonly List changed = new List(); float nextScan, nextCheck, nextSafetyCheck; int safetyIndex; // Mesh-Daten lesen ist teuer (große Arrays, Speicher-Müll) - nur nach einem Aufprall readonly Dictionary lastVelocity = new Dictionary(); readonly Dictionary lastImpact = new Dictionary(); readonly List bodies = new List(); static List types; public int Count { get { return meshes.Count; } } public void Reset() { meshes.Clear(); seen.Clear(); lastVelocity.Clear(); lastImpact.Clear(); bodies.Clear(); nextScan = -1f; // erster Suchlauf verteilt, siehe Update } public void Update(ObjectSync objects, int localId, Action send) { if (nextScan < 0f) nextScan = Time.time + 1.0f; // nicht alle Sync-Teile im selben Frame suchen lassen if (Time.time >= nextScan) { nextScan = Time.time + ScanInterval; Scan(); } // Aufprall erkennen: Geschwindigkeit ändert sich schlagartig (billig, jedes Frame) for (int i = 0; i < bodies.Count; i++) { Rigidbody rb = bodies[i]; if (rb == null || rb.isKinematic) continue; Vector3 v = rb.velocity, before; if (lastVelocity.TryGetValue(rb, out before) && (v - before).sqrMagnitude > ImpactDeltaV * ImpactDeltaV) lastImpact[rb] = Time.time; lastVelocity[rb] = v; } if (Time.time < nextCheck) return; nextCheck = Time.time + CheckInterval; bool safety = Time.time >= nextSafetyCheck; if (safety) { nextSafetyCheck = Time.time + SafetyCheckInterval; safetyIndex++; } int index = -1; foreach (DamageMesh m in meshes.Values) { index++; if (m.Filter == null) continue; bool safetyThis = safety && meshes.Count > 0 && index == safetyIndex % meshes.Count; int owner = objects.VehicleOwner(m.Root); if (owner != SyncedBody.NoOwner && owner != localId) continue; // der Fahrer schickt die Schäden float impact; bool recentImpact = m.Body != null && lastImpact.TryGetValue(m.Body, out impact) && Time.time - impact < ImpactWindow; if (!recentImpact && !safetyThis) continue; Mesh mesh = m.Filter.sharedMesh; if (mesh == null) continue; Vector3[] now = mesh.vertices; if (now.Length != m.Known.Length) { m.Known = now; continue; } changed.Clear(); for (int i = 0; i < now.Length; i++) if ((now[i] - m.Known[i]).sqrMagnitude > MinMove * MinMove) changed.Add(i); if (changed.Count == 0) continue; for (int start = 0; start < changed.Count; start += MaxPerMessage) { int from = start, count = Math.Min(MaxPerMessage, changed.Count - start); int key = m.Key, total = now.Length; send(MsgType.VehicleDamage, NetSession.Build(w => { w.Write(key); w.Write(total); w.Write(count); for (int i = from; i < from + count; i++) { int idx = changed[i]; Vector3 v = now[idx]; w.Write(idx); w.Write(v.x); w.Write(v.y); w.Write(v.z); } })); } m.Known = now; } } public void Handle(BinaryReader r) { int key = r.ReadInt32(); int total = r.ReadInt32(); int count = r.ReadInt32(); DamageMesh m; if (!meshes.TryGetValue(key, out m) || m.Filter == null) { nextScan = 0f; return; } Mesh mesh = m.Filter.mesh; // eigene Kopie, falls das Mesh (noch) mit anderen geteilt ist if (mesh == null) return; Vector3[] verts = mesh.vertices; if (verts.Length != total) return; // anderes Mesh (anderes Teil verbaut) for (int i = 0; i < count; i++) { int idx = r.ReadInt32(); var v = new Vector3(r.ReadSingle(), r.ReadSingle(), r.ReadSingle()); if (idx < 0 || idx >= verts.Length || float.IsNaN(v.x) || float.IsNaN(v.y) || float.IsNaN(v.z)) continue; if (v.sqrMagnitude > 1e6f) continue; // Unsinn nie übernehmen verts[idx] = v; } mesh.vertices = verts; mesh.RecalculateNormals(); mesh.RecalculateBounds(); m.Known = verts; } void Scan() { if (types == null) { types = new List(); foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies()) { if (a.GetName().Name != "Assembly-CSharp") continue; foreach (string n in DamageTypes) { Type t = a.GetType(n); if (t != null) types.Add(t); } } } int before = meshes.Count; foreach (Type t in types) { foreach (UnityEngine.Object o in UnityEngine.Object.FindObjectsOfType(t)) { var c = o as Component; if (c == null || !IsVehicle(c.transform.root.name)) continue; foreach (FieldInfo f in t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { if (f.FieldType == typeof(MeshFilter)) Register(f.GetValue(c) as MeshFilter); else if (f.FieldType == typeof(MeshFilter[])) { var arr = f.GetValue(c) as MeshFilter[]; if (arr != null) foreach (MeshFilter mf in arr) Register(mf); } } } } if (meshes.Count != before) MscoLog.Info("[MSCO] Crash damage: " + meshes.Count + " body meshes synced"); } void Register(MeshFilter mf) { if (mf == null || !seen.Add(mf)) return; Mesh mesh = mf.sharedMesh; if (mesh == null || mesh.vertexCount == 0 || mesh.vertexCount > 65000) return; int key = Hash(FsmSync.PathOf(mf.transform)); if (meshes.ContainsKey(key)) return; // nicht eindeutig Rigidbody body = mf.transform.root.GetComponent(); meshes[key] = new DamageMesh { Key = key, Root = mf.transform.root.name, Filter = mf, Body = body, Known = mesh.vertices }; if (body != null && !bodies.Contains(body)) bodies.Add(body); } static bool IsVehicle(string root) { foreach (string v in ObjectSync.VehicleRoots) if (root.StartsWith(v)) return true; return false; } static int Hash(string s) { unchecked { uint h = 2166136261; foreach (char c in s) { h ^= c; h *= 16777619; } return (int)h; } } } }