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;
}
}
}
}