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