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 = 1f; // Fahrzeuge, die wir fahren
const float FreeCheckInterval = 5f; // geparkte Fahrzeuge (Unfall nur durch Anstoßen) seltener
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 Vector3[] Known; // zuletzt gesendet/empfangen
}
readonly Dictionary meshes = new Dictionary();
readonly HashSet seen = new HashSet();
readonly List changed = new List();
float nextScan, nextCheck, nextFreeCheck;
static List types;
public int Count { get { return meshes.Count; } }
public void Reset()
{
meshes.Clear();
seen.Clear();
nextScan = 0f;
}
public void Update(ObjectSync objects, int localId, Action send)
{
if (Time.time >= nextScan)
{
nextScan = Time.time + ScanInterval;
Scan();
}
if (Time.time < nextCheck) return;
nextCheck = Time.time + CheckInterval;
bool checkFree = Time.time >= nextFreeCheck;
if (checkFree) nextFreeCheck = Time.time + FreeCheckInterval;
foreach (DamageMesh m in meshes.Values)
{
if (m.Filter == null) continue;
int owner = objects.VehicleOwner(m.Root);
if (owner != SyncedBody.NoOwner && owner != localId) continue; // der Fahrer schickt die Schäden
if (owner == SyncedBody.NoOwner && !checkFree) 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
meshes[key] = new DamageMesh { Key = key, Root = mf.transform.root.name, Filter = mf, Known = mesh.vertices };
}
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;
}
}
}
}