Join: replay part actions first, then rescan and place objects (detached parts no longer stuck in the car); rescan right after remote disassembly; lift out bodies that keep getting flung

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 08:52:53 +02:00
co-authored by Claude Opus 5.5
parent 8346cd9b09
commit 647acda46c
2 changed files with 58 additions and 7 deletions
+30 -3
View File
@@ -41,6 +41,7 @@ namespace MSCO
bool dedicated; // verbunden mit einem dedizierten Server bool dedicated; // verbunden mit einem dedizierten Server
bool connectingToServer; // Verbindungsaufbau zu einem dedizierten Server läuft bool connectingToServer; // Verbindungsaufbau zu einem dedizierten Server läuft
bool readySent; // Server: Weltzustand schon angefordert 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 byte[] pendingSnapshot; // Server: Weltzustand, wartet auf Objekt-Suchlauf
const int SnapshotEventsPerFrame = 20; const int SnapshotEventsPerFrame = 20;
readonly ServerBrowser browser = new ServerBrowser(); readonly ServerBrowser browser = new ServerBrowser();
@@ -1041,8 +1042,14 @@ namespace MSCO
objects.Handle(from, type, r); objects.Handle(from, type, r);
break; break;
case MsgType.FsmEvent: 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; break;
}
case MsgType.WorldTime: case MsgType.WorldTime:
worldSync.Apply(r); worldSync.Apply(r);
break; break;
@@ -1218,6 +1225,11 @@ namespace MSCO
} }
if (teleportToHost) TryTeleportToHost(); if (teleportToHost) TryTeleportToHost();
if (rescanSoon && objects.ScanComplete)
{
rescanSoon = false;
objects.ScanNow();
}
// Dedizierter Server: nach dem ersten Objekt-Suchlauf den Weltzustand anfordern und anwenden // Dedizierter Server: nach dem ersten Objekt-Suchlauf den Weltzustand anfordern und anwenden
if (dedicated && handshakeDone && !readySent && objects.ScanComplete) if (dedicated && handshakeDone && !readySent && objects.ScanComplete)
@@ -1365,14 +1377,16 @@ namespace MSCO
if (localPlayer != null) localPlayer.position = spawn + Vector3.up * 0.2f; 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 objectCount = r.ReadInt32();
int applied = 0; var poses = new List<KeyValuePair<int, Pose>>(objectCount);
for (int i = 0; i < objectCount; i++) for (int i = 0; i < objectCount; i++)
{ {
int id = r.ReadInt32(); int id = r.ReadInt32();
var pos = new Vector3(r.ReadSingle(), r.ReadSingle(), r.ReadSingle()); var pos = new Vector3(r.ReadSingle(), r.ReadSingle(), r.ReadSingle());
var rot = new Quaternion(r.ReadSingle(), 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<int, Pose>(id, new Pose { Position = pos, Rotation = rot }));
} }
int eventCount = r.ReadInt32(); int eventCount = r.ReadInt32();
@@ -1383,6 +1397,13 @@ namespace MSCO
if ((i + 1) % SnapshotEventsPerFrame == 0) yield return null; // nicht alles in einem Frame 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<int, Pose> p in poses)
if (objects.ApplySnapshotPose(p.Key, p.Value.Position, p.Value.Rotation)) applied++;
// Geld: Summe aller Änderungen seit dem Spielstand des Servers // Geld: Summe aller Änderungen seit dem Spielstand des Servers
if (r.BaseStream.Length - r.BaseStream.Position >= 4) moneySync.Apply(r.ReadSingle()); if (r.BaseStream.Length - r.BaseStream.Position >= 4) moneySync.Apply(r.ReadSingle());
@@ -1400,6 +1421,12 @@ namespace MSCO
SetStatus("Connected"); SetStatus("Connected");
} }
struct Pose
{
public Vector3 Position;
public Quaternion Rotation;
}
/// <summary>Sobald die erste Position des Hosts da ist: seitlich neben ihn stellen.</summary> /// <summary>Sobald die erste Position des Hosts da ist: seitlich neben ihn stellen.</summary>
void TryTeleportToHost() void TryTeleportToHost()
{ {
+28 -4
View File
@@ -31,6 +31,9 @@ namespace MSCO
// wird dann NICHT als loses Objekt synchronisiert - sonst reißt das Gelenk und das Teil fliegt weg // wird dann NICHT als loses Objekt synchronisiert - sonst reißt das Gelenk und das Teil fliegt weg
public bool Attached; public bool Attached;
public float NextAttachCheck; public float NextAttachCheck;
// Wie oft kurz hintereinander weggeschleudert (steckt es in etwas fest?)
public int RunawayCount;
public float RunawayWindowStart;
public Vector3 TargetPos; public Vector3 TargetPos;
public Quaternion TargetRot; public Quaternion TargetRot;
@@ -528,13 +531,27 @@ namespace MSCO
if (rb.isKinematic || rb.velocity.sqrMagnitude < RunawaySpeed * RunawaySpeed) return; if (rb.isKinematic || rb.velocity.sqrMagnitude < RunawaySpeed * RunawaySpeed) return;
rb.velocity = Vector3.zero; rb.velocity = Vector3.zero;
rb.angularVelocity = 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<Collider>()) 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; rb.transform.rotation = b.LastSentRot;
} }
else rb.transform.position = AboveGround(rb.position, rb.transform); b.NoMotionClaimUntil = Time.time + HandoverCooldown;
ModConsole.Warning("[MSCO] Stopped " + b.RootName + " - it was flung away by the physics"); if (b.RunawayCount <= 1 || stuck)
ModConsole.Warning("[MSCO] Stopped " + b.RootName + " - it was flung away by the physics" + (stuck ? " (stuck, lifted out)" : ""));
} }
/// <summary> /// <summary>
@@ -745,6 +762,13 @@ namespace MSCO
// ---------------------------------------------------------------- Registrierung // ---------------------------------------------------------------- Registrierung
/// <summary>Sofort nach neuen Objekten suchen (z.B. nach dem Nachspielen von Montage-Aktionen).</summary>
public void ScanNow()
{
Scan();
nextScan = Time.time + ScanInterval;
}
/// <summary>Neue Rigidbodies suchen (gekaufte Items, abmontierte Teile ...).</summary> /// <summary>Neue Rigidbodies suchen (gekaufte Items, abmontierte Teile ...).</summary>
void Scan() void Scan()
{ {