using System; using System.Collections.Generic; using System.IO; using UnityEngine; namespace MSCO { /// /// Verkehr (NPC-Autos): Der Spieler mit der kleinsten ID (Welt-Autorität, gibt auch Uhrzeit/Wetter vor) /// simuliert den Verkehr und schickt die Positionen. Bei allen anderen fahren die Autos dann nur noch nach /// (kinematisch) - so stehen sie überall an derselben Stelle und man kann sich gemeinsam darüber ärgern. /// public class TrafficSync { static readonly string[] Roots = { "TRAFFIC", "NPC_CARS" }; const float ScanInterval = 10f; const float SendInterval = 0.1f; const float Stale = 1.5f; // so lange ohne Daten: wieder selbst simulieren const float Smoothing = 12f; const float MaxExtrapolation = 0.3f; class Car { public int Key; public Rigidbody Rb; public bool WasKinematic; public bool Following; public Vector3 TargetPos, TargetVel; public Quaternion TargetRot; public float TargetTime = -100f; } readonly Dictionary cars = new Dictionary(); readonly HashSet seen = new HashSet(); float nextScan, sendTimer; public int Count { get { return cars.Count; } } public void Reset() { foreach (Car c in cars.Values) StopFollowing(c); cars.Clear(); seen.Clear(); nextScan = 0f; } public void Update(bool authority, float dt, Action send) { if (Time.time >= nextScan) { nextScan = Time.time + ScanInterval; Scan(); } if (!authority) { foreach (Car c in cars.Values) if (c.Following && Time.time - c.TargetTime > Stale) StopFollowing(c); return; } // Wir simulieren: eventuell noch nachfahrende Autos freigeben, Positionen senden foreach (Car c in cars.Values) if (c.Following) StopFollowing(c); sendTimer += dt; if (sendTimer < SendInterval) return; sendTimer = 0f; var list = new List(); foreach (Car c in cars.Values) if (c.Rb != null && c.Rb.gameObject.activeInHierarchy) list.Add(c); if (list.Count == 0) return; send(MsgType.TrafficStates, NetSession.Build(w => { w.Write(list.Count); foreach (Car c in list) { Vector3 p = c.Rb.position, v = c.Rb.velocity; Quaternion q = c.Rb.rotation; w.Write(c.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); w.Write(v.x); w.Write(v.y); w.Write(v.z); } })); } public void Handle(BinaryReader r, bool authority) { 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()); var v = new Vector3(r.ReadSingle(), r.ReadSingle(), r.ReadSingle()); Car c; if (authority || !cars.TryGetValue(key, out c) || c.Rb == null) continue; if (float.IsNaN(p.x) || float.IsNaN(q.w) || p.sqrMagnitude > 1e8f || v.sqrMagnitude > 1e4f) continue; c.TargetPos = p; c.TargetRot = q; c.TargetVel = v; c.TargetTime = Time.time; if (!c.Following) { c.WasKinematic = c.Rb.isKinematic; c.Rb.isKinematic = true; c.Rb.position = p; c.Rb.rotation = q; c.Following = true; } } } public void FixedUpdate() { foreach (Car c in cars.Values) { if (!c.Following || c.Rb == null) continue; float ahead = Mathf.Min(Time.time - c.TargetTime, MaxExtrapolation); Vector3 goal = c.TargetPos + c.TargetVel * ahead; float t = Mathf.Clamp01(Time.fixedDeltaTime * Smoothing); if ((c.Rb.position - goal).sqrMagnitude > 25f) { c.Rb.position = goal; c.Rb.rotation = c.TargetRot; continue; } c.Rb.MovePosition(Vector3.Lerp(c.Rb.position, goal, t)); c.Rb.MoveRotation(Quaternion.Slerp(c.Rb.rotation, c.TargetRot, t)); } } static void StopFollowing(Car c) { if (!c.Following) return; c.Following = false; if (c.Rb != null) c.Rb.isKinematic = c.WasKinematic; } void Scan() { int before = cars.Count; foreach (Rigidbody rb in UnityEngine.Object.FindObjectsOfType()) { if (rb == null || !seen.Add(rb)) continue; if (Array.IndexOf(Roots, rb.transform.root.name) < 0) continue; // nur das oberste Rigidbody je Auto (keine Räder/Türen) Transform parent = rb.transform.parent; if (parent != null && parent.GetComponentInParent() != null) continue; int key = Hash(FsmSync.PathOf(rb.transform)); if (cars.ContainsKey(key)) continue; cars[key] = new Car { Key = key, Rb = rb }; } if (cars.Count != before) MscoLog.Info("[MSCO] Traffic: " + cars.Count + " cars synced"); } static int Hash(string s) { unchecked { uint h = 2166136261; foreach (char c in s) { h ^= c; h *= 16777619; } return (int)h; } } } }