using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
namespace MSCO
{
///
/// Türen, Motorhaube und Kofferraum an Fahrzeugen: hängen mit einem Scharnier (HingeJoint) an der Karosserie
/// und werden deshalb nicht vom Objekt-Sync bewegt. Übertragen wird die Stellung relativ zum Auto, damit eine
/// offene Tür beim anderen genauso weit offen steht.
/// Wer sendet: Fährt jemand das Auto, der Fahrer. Ist es frei, wer gerade daneben steht (Tür auf/zu gemacht).
///
public class HingeSync
{
const float ScanInterval = 10f;
const float SendInterval = 0.1f;
const float MinAngle = 2f; // Grad - kleiner ist Zittern
const float NearRange = 4f; // so nah muss man an einer Tür stehen, um sie zu bewegen
const float ApplyHold = 1.5f; // so lange nach einer Nachricht die Stellung halten und selbst nicht senden
class Hinge
{
public int Key;
public string Root;
public Rigidbody Rb;
public Rigidbody Body; // woran das Scharnier hängt
public Quaternion SentRot;
public bool HasSent;
public Quaternion TargetRot;
public Vector3 TargetPos;
public float TargetTime = -100f;
}
readonly Dictionary hinges = new Dictionary();
readonly HashSet seen = new HashSet();
readonly List outgoing = new List();
float nextScan, sendTimer;
public int Count { get { return hinges.Count; } }
public void Reset()
{
hinges.Clear();
seen.Clear();
nextScan = 0f;
}
public void Update(ObjectSync objects, int localId, Vector3 playerPos, float dt, Action send)
{
if (Time.time >= nextScan)
{
nextScan = Time.time + ScanInterval;
Scan();
}
sendTimer += dt;
if (sendTimer < SendInterval) return;
sendTimer = 0f;
outgoing.Clear();
foreach (Hinge h in hinges.Values)
{
if (h.Rb == null || h.Body == null || !h.Rb.gameObject.activeInHierarchy) continue;
if (Time.time - h.TargetTime < ApplyHold) continue; // gerade von anderen gesetzt
int owner = objects.VehicleOwner(h.Root);
if (owner != SyncedBody.NoOwner && owner != localId) continue; // der Fahrer schickt
if (owner == SyncedBody.NoOwner && (h.Rb.position - playerPos).sqrMagnitude > NearRange * NearRange) continue;
Quaternion rel = Quaternion.Inverse(h.Body.rotation) * h.Rb.rotation;
if (h.HasSent && Quaternion.Angle(rel, h.SentRot) < MinAngle) continue;
h.SentRot = rel;
h.HasSent = true;
outgoing.Add(h);
}
if (outgoing.Count == 0) return;
send(MsgType.HingeStates, NetSession.Build(w =>
{
w.Write(outgoing.Count);
foreach (Hinge h in outgoing)
{
Vector3 p = h.Body.transform.InverseTransformPoint(h.Rb.position);
Quaternion q = h.SentRot;
w.Write(h.Key);
w.Write(p.x); w.Write(p.y); w.Write(p.z);
w.Write(q.x); w.Write(q.y); w.Write(q.z); w.Write(q.w);
}
}));
}
public void Handle(BinaryReader r)
{
int count = r.ReadInt32();
for (int i = 0; i < count; i++)
{
int key = r.ReadInt32();
var p = new Vector3(r.ReadSingle(), r.ReadSingle(), r.ReadSingle());
var q = new Quaternion(r.ReadSingle(), r.ReadSingle(), r.ReadSingle(), r.ReadSingle());
Hinge h;
if (!hinges.TryGetValue(key, out h)) { nextScan = 0f; continue; }
if (float.IsNaN(p.x) || float.IsNaN(q.w) || p.sqrMagnitude > 25f) continue; // ein Scharnier ist nie weit weg
h.TargetPos = p;
h.TargetRot = q;
h.TargetTime = Time.time;
h.SentRot = q; // nicht als eigene Bewegung zurückschicken
h.HasSent = true;
}
}
/// Empfangene Stellungen halten (die Physik würde die Tür sonst beim Fahren weiterschwingen lassen).
public void FixedUpdate()
{
foreach (Hinge h in hinges.Values)
{
if (Time.time - h.TargetTime > ApplyHold || h.Rb == null || h.Body == null) continue;
Quaternion rot = h.Body.rotation * h.TargetRot;
Vector3 pos = h.Body.transform.TransformPoint(h.TargetPos);
float t = Mathf.Clamp01(Time.fixedDeltaTime * 15f);
h.Rb.MoveRotation(Quaternion.Slerp(h.Rb.rotation, rot, t));
h.Rb.MovePosition(Vector3.Lerp(h.Rb.position, pos, t));
h.Rb.angularVelocity = Vector3.zero;
h.Rb.velocity = h.Body.velocity;
}
}
void Scan()
{
int before = hinges.Count;
foreach (HingeJoint j in UnityEngine.Object.FindObjectsOfType())
{
if (j == null || !seen.Add(j)) continue;
Rigidbody rb = j.GetComponent();
Rigidbody body = j.connectedBody;
if (rb == null || body == null || !IsVehicle(body.transform.root.name)) continue;
int key = Hash(FsmSync.PathOf(j.transform));
if (hinges.ContainsKey(key)) continue;
hinges[key] = new Hinge { Key = key, Root = body.transform.root.name, Rb = rb, Body = body };
}
// Abgebaute Teile (Scharnier weg) vergessen
var gone = new List();
foreach (KeyValuePair kv in hinges)
{
Hinge h = kv.Value;
if (h.Rb == null || h.Body == null || h.Rb.GetComponent() == null) gone.Add(kv.Key);
}
foreach (int k in gone) hinges.Remove(k);
if (gone.Count > 0) seen.RemoveWhere(x => x == null);
if (hinges.Count != before) MscoLog.Info("[MSCO] Vehicle doors/hood/trunk: " + hinges.Count + " synced");
}
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;
}
}
}
}