Freeze attached parts of remote-driven vehicles (joints broke, car fell apart); don't sync bolt tightness

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-26 23:39:43 +02:00
co-authored by Claude Opus 5.5
parent e050712c0d
commit f4d154d4a5
2 changed files with 55 additions and 9 deletions
+52 -7
View File
@@ -22,6 +22,9 @@ namespace MSCO
public int OwnerId = NoOwner; public int OwnerId = NoOwner;
public bool OwnerActive; // Besitzer hält/fährt es gerade aktiv public bool OwnerActive; // Besitzer hält/fährt es gerade aktiv
public bool WasKinematic; // Zustand bevor ein anderer Spieler es übernommen hat public bool WasKinematic; // Zustand bevor ein anderer Spieler es übernommen hat
// Angebaute Teile eines Fahrzeugs (eigene Rigidbodies mit Gelenken), solange ein anderer fährt: kinematisch
public Dictionary<Rigidbody, bool> FrozenParts;
public float NextPartScan;
public float StillTime; public float StillTime;
public float NoMotionClaimUntil; // kurz nach einer Übergabe nicht wegen Nachfedern/Nachrollen übernehmen public float NoMotionClaimUntil; // kurz nach einer Übergabe nicht wegen Nachfedern/Nachrollen übernehmen
@@ -177,7 +180,7 @@ namespace MSCO
public void Reset() public void Reset()
{ {
foreach (SyncedBody b in byId.Values) foreach (SyncedBody b in byId.Values)
if (IsRemoteOwned(b) && b.Rb != null) b.Rb.isKinematic = b.WasKinematic; if (IsRemoteOwned(b) && b.Rb != null) RestoreKinematic(b);
byId.Clear(); byId.Clear();
byRb.Clear(); byRb.Clear();
rejected.Clear(); rejected.Clear();
@@ -194,7 +197,7 @@ namespace MSCO
foreach (SyncedBody b in byId.Values) foreach (SyncedBody b in byId.Values)
{ {
if (b.OwnerId != playerId || b.Rb == null) continue; if (b.OwnerId != playerId || b.Rb == null) continue;
b.Rb.isKinematic = b.WasKinematic; RestoreKinematic(b);
b.OwnerId = SyncedBody.NoOwner; b.OwnerId = SyncedBody.NoOwner;
b.NoMotionClaimUntil = Time.time + HandoverCooldown; b.NoMotionClaimUntil = Time.time + HandoverCooldown;
} }
@@ -290,6 +293,7 @@ namespace MSCO
foreach (SyncedBody b in byId.Values) foreach (SyncedBody b in byId.Values)
{ {
if (!IsRemoteOwned(b) || b.Rb == null) continue; if (!IsRemoteOwned(b) || b.Rb == null) continue;
if (b.IsVehicle && Time.time >= b.NextPartScan) FreezeParts(b);
if (Time.time - b.AvatarHoldTime < 0.2f) continue; // sitzt gerade in der Hand eines Mitspielers if (Time.time - b.AvatarHoldTime < 0.2f) continue; // sitzt gerade in der Hand eines Mitspielers
if ((b.Rb.position - b.TargetPos).sqrMagnitude > SnapDistance * SnapDistance) if ((b.Rb.position - b.TargetPos).sqrMagnitude > SnapDistance * SnapDistance)
{ {
@@ -333,7 +337,7 @@ namespace MSCO
void Claim(SyncedBody b, bool active) void Claim(SyncedBody b, bool active)
{ {
if (IsRemoteOwned(b)) b.Rb.isKinematic = b.WasKinematic; if (IsRemoteOwned(b)) RestoreKinematic(b);
b.OwnerId = LocalId; b.OwnerId = LocalId;
b.OwnerActive = active; b.OwnerActive = active;
b.StillTime = 0f; b.StillTime = 0f;
@@ -362,8 +366,7 @@ namespace MSCO
if (b.OwnerId == ownerId) return; if (b.OwnerId == ownerId) return;
if (!IsRemoteOwned(b)) if (!IsRemoteOwned(b))
{ {
b.WasKinematic = b.Rb.isKinematic; FreezeForRemote(b);
b.Rb.isKinematic = true;
b.TargetPos = b.Rb.position; b.TargetPos = b.Rb.position;
b.TargetRot = b.Rb.rotation; b.TargetRot = b.Rb.rotation;
} }
@@ -372,11 +375,53 @@ namespace MSCO
void SetFree(SyncedBody b) void SetFree(SyncedBody b)
{ {
if (IsRemoteOwned(b)) b.Rb.isKinematic = b.WasKinematic; if (IsRemoteOwned(b)) RestoreKinematic(b);
b.OwnerId = SyncedBody.NoOwner; b.OwnerId = SyncedBody.NoOwner;
b.NoMotionClaimUntil = Time.time + HandoverCooldown; b.NoMotionClaimUntil = Time.time + HandoverCooldown;
} }
/// <summary>
/// Ein anderer Spieler bewegt das Objekt: bei uns nur noch folgen (kinematisch). Bei Fahrzeugen auch alle
/// angebauten Teile - die hängen in MSC mit Gelenken (mit Bruchkraft) am Auto. Würde nur das Auto ruckartig
/// versetzt, rissen die Gelenke und das Auto fiele auseinander.
/// </summary>
void FreezeForRemote(SyncedBody b)
{
b.WasKinematic = b.Rb.isKinematic;
b.Rb.isKinematic = true;
if (b.IsVehicle) FreezeParts(b);
}
void FreezeParts(SyncedBody b)
{
b.NextPartScan = Time.time + 1f;
if (b.FrozenParts == null) b.FrozenParts = new Dictionary<Rigidbody, bool>();
foreach (Rigidbody part in b.Rb.GetComponentsInChildren<Rigidbody>())
{
if (part == b.Rb || b.FrozenParts.ContainsKey(part)) continue;
b.FrozenParts[part] = part.isKinematic;
part.isKinematic = true;
}
}
/// <summary>Wieder selbst simulieren: Auto und angebaute Teile zurück auf ihren vorherigen Zustand.</summary>
void RestoreKinematic(SyncedBody b)
{
if (b.Rb != null) b.Rb.isKinematic = b.WasKinematic;
if (b.FrozenParts == null) return;
foreach (KeyValuePair<Rigidbody, bool> kv in b.FrozenParts)
{
if (kv.Key == null) continue;
kv.Key.isKinematic = kv.Value;
if (!kv.Value)
{
kv.Key.velocity = Vector3.zero; // keinen Schwung aus dem Folgen mitnehmen
kv.Key.angularVelocity = Vector3.zero;
}
}
b.FrozenParts = null;
}
void SendOwnedStates() void SendOwnedStates()
{ {
sendList.Clear(); sendList.Clear();
@@ -450,7 +495,7 @@ namespace MSCO
if (!IsValidPose(pos, rot)) { SetFree(b); return; } if (!IsValidPose(pos, rot)) { SetFree(b); return; }
b.Rb.transform.position = pos; b.Rb.transform.position = pos;
b.Rb.transform.rotation = rot; b.Rb.transform.rotation = rot;
b.Rb.isKinematic = b.WasKinematic; RestoreKinematic(b);
if (!b.Rb.isKinematic) if (!b.Rb.isKinematic)
{ {
b.Rb.velocity = vel; b.Rb.velocity = vel;
+3 -2
View File
@@ -21,13 +21,14 @@ namespace MSCO
static readonly string[] Keywords = static readonly string[] Keywords =
{ {
"fuel", "oil", "coolant", "water", "fluid", "brake", "clutchfluid", "battery", "charge", "fuel", "oil", "coolant", "water", "fluid", "brake", "clutchfluid", "battery", "charge",
"bolt", "tight", "damage", "wear", "condition", "odo", "km", "dirt", "paint", "color", "colour", "damage", "wear", "condition", "odo", "km", "dirt", "paint", "color", "colour",
"tune", "tuning", "ratio", "broken", "fixed", "flat", "pressure", "level", "mixture", "timing", "tune", "tuning", "ratio", "broken", "fixed", "flat", "pressure", "level", "mixture", "timing",
"idle", "air", "jet", "spark", "gap", "rust", "health", "burn", "cool", "temp" "idle", "air", "jet", "spark", "gap", "rust", "health", "burn", "cool", "temp"
}; };
// Montiert-Flags nicht direkt setzen: das echte Anbauen läuft über FsmSync (Events) // Montiert-Flags nicht direkt setzen: das echte Anbauen läuft über FsmSync (Events)
static readonly string[] Excluded = { "install", "rpm", "speed", "steer", "throttle", "gear", "raycast", "distance" }; // Schrauben/Festigkeit laufen über FsmSync (Events) - ein falscher Wert hier würde Teile lockern und abfallen lassen
static readonly string[] Excluded = { "install", "bolt", "tight", "detach", "attach", "rpm", "speed", "steer", "throttle", "gear", "raycast", "distance" };
const float ScanInterval = 10f; const float ScanInterval = 10f;
const float CheckInterval = 0.5f; const float CheckInterval = 0.5f;