2.8: sync bought/consumed items, wheel spin, crash damage, jobs/shop, traffic
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+163
@@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MSCO
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<int, Car> cars = new Dictionary<int, Car>();
|
||||
readonly HashSet<Rigidbody> seen = new HashSet<Rigidbody>();
|
||||
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<MsgType, byte[]> 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<Car>();
|
||||
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<Rigidbody>())
|
||||
{
|
||||
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<Rigidbody>() != 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user