189 lines
7.6 KiB
C#
189 lines
7.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using UnityEngine;
|
|
|
|
namespace MSCO
|
|
{
|
|
/// <summary>
|
|
/// 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 der Öffnungswinkel des Scharniers.
|
|
/// Beim Empfänger wird die Tür NICHT hingesetzt (Teleportieren gegen das Gelenk erzeugt riesige Kräfte - Teile
|
|
/// reißen ab, im schlimmsten Fall stürzt die Physik ab), sondern die Feder des Scharniers zieht sie sanft auf
|
|
/// den Winkel. Danach bekommt das Scharnier seine ursprüngliche Einstellung zurück.
|
|
/// Wer sendet: Fährt jemand das Auto, der Fahrer. Ist es frei, wer gerade daneben steht (Tür auf/zu gemacht).
|
|
/// </summary>
|
|
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 Feder halten und selbst nicht senden
|
|
const float SpringForce = 120f;
|
|
const float SpringDamper = 12f;
|
|
|
|
class Hinge
|
|
{
|
|
public int Key;
|
|
public string Root;
|
|
public HingeJoint Joint;
|
|
public Rigidbody Rb;
|
|
public float SentAngle;
|
|
public bool HasSent;
|
|
public float TargetAngle;
|
|
public float TargetTime = -100f;
|
|
public bool Driving; // Feder gerade von uns gesetzt
|
|
public bool OrigUseSpring;
|
|
public JointSpring OrigSpring;
|
|
}
|
|
|
|
readonly Dictionary<int, Hinge> hinges = new Dictionary<int, Hinge>();
|
|
readonly HashSet<HingeJoint> seen = new HashSet<HingeJoint>();
|
|
readonly List<Hinge> outgoing = new List<Hinge>();
|
|
float nextScan, sendTimer;
|
|
|
|
public int Count { get { return hinges.Count; } }
|
|
|
|
public void Reset()
|
|
{
|
|
foreach (Hinge h in hinges.Values) StopDriving(h);
|
|
hinges.Clear();
|
|
seen.Clear();
|
|
nextScan = -1f; // erster Suchlauf verteilt, siehe Update
|
|
}
|
|
|
|
public void Update(ObjectSync objects, int localId, Vector3 playerPos, float dt, Action<MsgType, byte[]> send)
|
|
{
|
|
if (nextScan < 0f) nextScan = Time.time + 1.7f; // nicht alle Sync-Teile im selben Frame suchen lassen
|
|
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.Joint == null || h.Rb == 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;
|
|
|
|
float angle = h.Joint.angle;
|
|
if (float.IsNaN(angle) || float.IsInfinity(angle)) continue;
|
|
if (h.HasSent && Mathf.Abs(angle - h.SentAngle) < MinAngle) continue;
|
|
h.SentAngle = angle;
|
|
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) { w.Write(h.Key); w.Write(h.SentAngle); }
|
|
}));
|
|
}
|
|
|
|
public void Handle(BinaryReader r)
|
|
{
|
|
int count = r.ReadInt32();
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
int key = r.ReadInt32();
|
|
float angle = r.ReadSingle();
|
|
Hinge h;
|
|
if (!hinges.TryGetValue(key, out h)) { nextScan = 0f; continue; }
|
|
if (float.IsNaN(angle) || float.IsInfinity(angle) || Mathf.Abs(angle) > 180f) continue;
|
|
h.TargetAngle = angle;
|
|
h.TargetTime = Time.time;
|
|
h.SentAngle = angle; // nicht als eigene Bewegung zurückschicken
|
|
h.HasSent = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>Feder des Scharniers auf den empfangenen Winkel stellen bzw. wieder zurückgeben.</summary>
|
|
public void FixedUpdate()
|
|
{
|
|
foreach (Hinge h in hinges.Values)
|
|
{
|
|
if (h.Joint == null || h.Rb == null) continue;
|
|
bool fresh = Time.time - h.TargetTime < ApplyHold && !h.Rb.isKinematic;
|
|
if (!fresh) { StopDriving(h); continue; }
|
|
|
|
if (!h.Driving)
|
|
{
|
|
h.OrigUseSpring = h.Joint.useSpring;
|
|
h.OrigSpring = h.Joint.spring;
|
|
h.Driving = true;
|
|
}
|
|
float target = h.TargetAngle;
|
|
if (h.Joint.useLimits)
|
|
{
|
|
JointLimits l = h.Joint.limits;
|
|
target = Mathf.Clamp(target, Mathf.Min(l.min, l.max), Mathf.Max(l.min, l.max));
|
|
}
|
|
JointSpring s = h.Joint.spring;
|
|
s.spring = SpringForce;
|
|
s.damper = SpringDamper;
|
|
s.targetPosition = target;
|
|
h.Joint.spring = s;
|
|
h.Joint.useSpring = true;
|
|
h.Rb.WakeUp();
|
|
}
|
|
}
|
|
|
|
static void StopDriving(Hinge h)
|
|
{
|
|
if (!h.Driving) return;
|
|
h.Driving = false;
|
|
if (h.Joint == null) return;
|
|
h.Joint.spring = h.OrigSpring;
|
|
h.Joint.useSpring = h.OrigUseSpring;
|
|
}
|
|
|
|
void Scan()
|
|
{
|
|
int before = hinges.Count;
|
|
foreach (HingeJoint j in UnityEngine.Object.FindObjectsOfType<HingeJoint>())
|
|
{
|
|
if (j == null || !seen.Add(j)) continue;
|
|
Rigidbody rb = j.GetComponent<Rigidbody>();
|
|
Rigidbody body = j.connectedBody;
|
|
if (rb == null || body == null || !IsVehicle(body.transform.root.name)) continue;
|
|
int key = Hash(FsmSync.PathOf(j.transform) + "|" + FsmSync.PathOf(body.transform));
|
|
if (hinges.ContainsKey(key)) continue;
|
|
hinges[key] = new Hinge { Key = key, Root = body.transform.root.name, Joint = j, Rb = rb };
|
|
}
|
|
// Abgebaute Teile (Scharnier weg) vergessen
|
|
var gone = new List<int>();
|
|
foreach (KeyValuePair<int, Hinge> kv in hinges)
|
|
if (kv.Value.Joint == null || kv.Value.Rb == 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;
|
|
}
|
|
}
|
|
}
|
|
}
|