diff --git a/MultiplayerManager.cs b/MultiplayerManager.cs index cc9fbd8..88cfaf7 100644 --- a/MultiplayerManager.cs +++ b/MultiplayerManager.cs @@ -41,6 +41,7 @@ namespace MSCO bool dedicated; // verbunden mit einem dedizierten Server bool connectingToServer; // Verbindungsaufbau zu einem dedizierten Server läuft bool readySent; // Server: Weltzustand schon angefordert + bool rescanSoon; // nach einer Abbau-Aktion neue lose Teile sofort erfassen byte[] pendingSnapshot; // Server: Weltzustand, wartet auf Objekt-Suchlauf const int SnapshotEventsPerFrame = 20; readonly ServerBrowser browser = new ServerBrowser(); @@ -1041,8 +1042,14 @@ namespace MSCO objects.Handle(from, type, r); break; case MsgType.FsmEvent: - FsmSync.Apply(FsmEventMsg.Deserialize(r)); + { + FsmEventMsg msg = FsmEventMsg.Deserialize(r); + FsmSync.Apply(msg); + // Ein Teil wurde abgebaut: gleich als loses Objekt erfassen, damit es der Hand des Mitspielers + // folgt, statt Sekunden lang im Auto zu stecken (die Physik würde es samt Auto wegschleudern) + if (msg.EventName == "DISASSEMBLE" || msg.EventName == "REMOVE" || msg.EventName == "UNINSTALL") rescanSoon = true; break; + } case MsgType.WorldTime: worldSync.Apply(r); break; @@ -1218,6 +1225,11 @@ namespace MSCO } if (teleportToHost) TryTeleportToHost(); + if (rescanSoon && objects.ScanComplete) + { + rescanSoon = false; + objects.ScanNow(); + } // Dedizierter Server: nach dem ersten Objekt-Suchlauf den Weltzustand anfordern und anwenden if (dedicated && handshakeDone && !readySent && objects.ScanComplete) @@ -1365,14 +1377,16 @@ namespace MSCO if (localPlayer != null) localPlayer.position = spawn + Vector3.up * 0.2f; } + // Positionen erst merken: sie werden NACH den Montage-Aktionen gesetzt. Sonst steckt ein Teil, das eine + // nachgespielte Aktion vom Auto löst, noch im Auto - die Physik schleudert es dann samt Auto weg. int objectCount = r.ReadInt32(); - int applied = 0; + var poses = new List>(objectCount); for (int i = 0; i < objectCount; i++) { int id = r.ReadInt32(); var pos = new Vector3(r.ReadSingle(), r.ReadSingle(), r.ReadSingle()); var rot = new Quaternion(r.ReadSingle(), r.ReadSingle(), r.ReadSingle(), r.ReadSingle()); - if (objects.ApplySnapshotPose(id, pos, rot)) applied++; + poses.Add(new KeyValuePair(id, new Pose { Position = pos, Rotation = rot })); } int eventCount = r.ReadInt32(); @@ -1383,6 +1397,13 @@ namespace MSCO if ((i + 1) % SnapshotEventsPerFrame == 0) yield return null; // nicht alles in einem Frame } + // Abgebaute Teile sind jetzt eigene Objekte: erfassen, dann alle Positionen setzen + yield return null; + objects.ScanNow(); + int applied = 0; + foreach (KeyValuePair p in poses) + if (objects.ApplySnapshotPose(p.Key, p.Value.Position, p.Value.Rotation)) applied++; + // Geld: Summe aller Änderungen seit dem Spielstand des Servers if (r.BaseStream.Length - r.BaseStream.Position >= 4) moneySync.Apply(r.ReadSingle()); @@ -1400,6 +1421,12 @@ namespace MSCO SetStatus("Connected"); } + struct Pose + { + public Vector3 Position; + public Quaternion Rotation; + } + /// Sobald die erste Position des Hosts da ist: seitlich neben ihn stellen. void TryTeleportToHost() { diff --git a/ObjectSync.cs b/ObjectSync.cs index 5652fca..65f4d85 100644 --- a/ObjectSync.cs +++ b/ObjectSync.cs @@ -31,6 +31,9 @@ namespace MSCO // wird dann NICHT als loses Objekt synchronisiert - sonst reißt das Gelenk und das Teil fliegt weg public bool Attached; public float NextAttachCheck; + // Wie oft kurz hintereinander weggeschleudert (steckt es in etwas fest?) + public int RunawayCount; + public float RunawayWindowStart; public Vector3 TargetPos; public Quaternion TargetRot; @@ -528,13 +531,27 @@ namespace MSCO if (rb.isKinematic || rb.velocity.sqrMagnitude < RunawaySpeed * RunawaySpeed) return; rb.velocity = Vector3.zero; rb.angularVelocity = Vector3.zero; - if (IsLocal(b) && b.LastSentTime > 0f && IsValidPose(b.LastSentPos, b.LastSentRot)) + + // Mehrmals kurz hintereinander: das Objekt steckt in etwas fest (z.B. abgebautes Teil im Auto). + // Dann nicht wieder an dieselbe Stelle setzen, sondern nach oben heraus heben. + if (Time.time - b.RunawayWindowStart > 5f) { b.RunawayWindowStart = Time.time; b.RunawayCount = 0; } + b.RunawayCount++; + bool stuck = b.RunawayCount >= 3; + if (stuck) { - rb.transform.position = AboveGround(b.LastSentPos, rb.transform); + Bounds bounds = new Bounds(rb.worldCenterOfMass, Vector3.zero); + foreach (Collider c in rb.GetComponentsInChildren()) bounds.Encapsulate(c.bounds); + rb.transform.position += Vector3.up * (bounds.size.y + 1.5f); + b.RunawayCount = 0; + } + else if (IsLocal(b) && b.LastSentTime > 0f && IsValidPose(b.LastSentPos, b.LastSentRot)) + { + rb.transform.position = b.LastSentPos; rb.transform.rotation = b.LastSentRot; } - else rb.transform.position = AboveGround(rb.position, rb.transform); - ModConsole.Warning("[MSCO] Stopped " + b.RootName + " - it was flung away by the physics"); + b.NoMotionClaimUntil = Time.time + HandoverCooldown; + if (b.RunawayCount <= 1 || stuck) + ModConsole.Warning("[MSCO] Stopped " + b.RootName + " - it was flung away by the physics" + (stuck ? " (stuck, lifted out)" : "")); } /// @@ -745,6 +762,13 @@ namespace MSCO // ---------------------------------------------------------------- Registrierung + /// Sofort nach neuen Objekten suchen (z.B. nach dem Nachspielen von Montage-Aktionen). + public void ScanNow() + { + Scan(); + nextScan = Time.time + ScanInterval; + } + /// Neue Rigidbodies suchen (gekaufte Items, abmontierte Teile ...). void Scan() {