Don't sync parts that are attached to a vehicle (they were flung away / car fell apart); softer plaid, calmer colors

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 08:40:46 +02:00
co-authored by Claude Opus 5.5
parent 3970ef130b
commit 477949e559
3 changed files with 59 additions and 17 deletions
+1 -1
View File
@@ -1475,7 +1475,7 @@ namespace MSCO
Color[] colors = Color[] colors =
{ {
new Color(0.6f, 0.13f, 0.12f), new Color(0.16f, 0.25f, 0.5f), new Color(0.2f, 0.38f, 0.2f), new Color(0.7f, 0.52f, 0.14f), new Color(0.6f, 0.13f, 0.12f), new Color(0.16f, 0.25f, 0.5f), new Color(0.2f, 0.38f, 0.2f), new Color(0.7f, 0.52f, 0.14f),
new Color(0.42f, 0.28f, 0.17f), new Color(0.33f, 0.36f, 0.4f), new Color(0.45f, 0.14f, 0.3f), new Color(0.38f, 0.4f, 0.16f) new Color(0.42f, 0.28f, 0.17f), new Color(0.33f, 0.36f, 0.4f), new Color(0.16f, 0.36f, 0.38f), new Color(0.38f, 0.4f, 0.16f)
}; };
return colors[id % colors.Length]; return colors[id % colors.Length];
} }
+46 -6
View File
@@ -27,6 +27,10 @@ namespace MSCO
public float NextPartScan; 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
// Am Auto angebautes Teil (Gelenk zum Fahrzeug oder unter einem anderen Rigidbody): bewegt sich mit dem Auto,
// wird dann NICHT als loses Objekt synchronisiert - sonst reißt das Gelenk und das Teil fliegt weg
public bool Attached;
public float NextAttachCheck;
public Vector3 TargetPos; public Vector3 TargetPos;
public Quaternion TargetRot; public Quaternion TargetRot;
@@ -107,6 +111,7 @@ namespace MSCO
SyncedBody b; SyncedBody b;
if (!byId.TryGetValue(id, out b) || b.Rb == null || b.OwnerId != SyncedBody.NoOwner) return false; if (!byId.TryGetValue(id, out b) || b.Rb == null || b.OwnerId != SyncedBody.NoOwner) return false;
if (!IsValidPose(pos, rot)) return false; if (!IsValidPose(pos, rot)) return false;
if (IsAttached(b)) return false; // inzwischen angebaut: Position kommt vom Auto
if (b.IsVehicle) pos = AboveGround(pos, b.Rb.transform); // gespeicherte Position evtl. knapp im Boden if (b.IsVehicle) pos = AboveGround(pos, b.Rb.transform); // gespeicherte Position evtl. knapp im Boden
b.Rb.transform.position = pos; b.Rb.transform.position = pos;
b.Rb.transform.rotation = rot; b.Rb.transform.rotation = rot;
@@ -247,6 +252,7 @@ namespace MSCO
{ {
if (b.Rb == null) { removeList.Add(b); continue; } // z.B. Teil wurde montiert if (b.Rb == null) { removeList.Add(b); continue; } // z.B. Teil wurde montiert
if (!b.Rb.gameObject.activeInHierarchy) continue; if (!b.Rb.gameObject.activeInHierarchy) continue;
if (UpdateAttached(b)) continue; // angebaut: bewegt sich mit dem Auto
bool active = b == held || b == driven; bool active = b == held || b == driven;
if (!IsRemoteOwned(b)) StopRunaway(b); if (!IsRemoteOwned(b)) StopRunaway(b);
@@ -292,7 +298,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 || b.Attached) continue;
if (b.IsVehicle && Time.time >= b.NextPartScan) FreezeParts(b); 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)
@@ -315,7 +321,7 @@ namespace MSCO
public void ApplyAvatarHold(int playerId, int bodyId, Vector3 pos, Quaternion rot) public void ApplyAvatarHold(int playerId, int bodyId, Vector3 pos, Quaternion rot)
{ {
SyncedBody b; SyncedBody b;
if (!byId.TryGetValue(bodyId, out b) || b.Rb == null || b.IsVehicle) return; // ältere Mods melden evtl. ein Auto als "gehalten" if (!byId.TryGetValue(bodyId, out b) || b.Rb == null || b.IsVehicle || b.Attached) return; // ältere Mods melden evtl. ein Auto als "gehalten"
if (b.OwnerId != playerId) if (b.OwnerId != playerId)
{ {
if (!Wins(playerId, true, b.OwnerId, b.OwnerActive)) return; if (!Wins(playerId, true, b.OwnerId, b.OwnerActive)) return;
@@ -428,7 +434,7 @@ namespace MSCO
float now = Time.time; float now = Time.time;
foreach (SyncedBody b in byId.Values) foreach (SyncedBody b in byId.Values)
{ {
if (!IsLocal(b) || b.Rb == null) continue; if (!IsLocal(b) || b.Rb == null || b.Attached) continue;
bool changed = (b.Rb.position - b.LastSentPos).sqrMagnitude > 0.0001f bool changed = (b.Rb.position - b.LastSentPos).sqrMagnitude > 0.0001f
|| Quaternion.Angle(b.Rb.rotation, b.LastSentRot) > 0.5f; || Quaternion.Angle(b.Rb.rotation, b.LastSentRot) > 0.5f;
if (!changed && now - b.LastSentTime < KeepAliveInterval) continue; if (!changed && now - b.LastSentTime < KeepAliveInterval) continue;
@@ -457,7 +463,7 @@ namespace MSCO
int id = r.ReadInt32(); int id = r.ReadInt32();
bool active = r.ReadBoolean(); bool active = r.ReadBoolean();
SyncedBody b; SyncedBody b;
if (!byId.TryGetValue(id, out b) || b.Rb == null) return; if (!byId.TryGetValue(id, out b) || b.Rb == null || IsAttached(b)) return;
if (!Wins(from, active, b.OwnerId, b.OwnerActive)) if (!Wins(from, active, b.OwnerId, b.OwnerActive))
{ {
// Wir besitzen es und gewinnen: nochmal melden, damit alle es wissen // Wir besitzen es und gewinnen: nochmal melden, damit alle es wissen
@@ -476,7 +482,7 @@ namespace MSCO
int id; Vector3 pos; Quaternion rot; Vector3 vel; int id; Vector3 pos; Quaternion rot; Vector3 vel;
ReadState(r, out id, out pos, out rot, out vel); ReadState(r, out id, out pos, out rot, out vel);
SyncedBody b; SyncedBody b;
if (!byId.TryGetValue(id, out b) || b.Rb == null || !IsValidPose(pos, rot)) continue; if (!byId.TryGetValue(id, out b) || b.Rb == null || !IsValidPose(pos, rot) || IsAttached(b)) continue;
// Nur dem aktuellen Besitzer folgen (oder freie Objekte übernehmen) // Nur dem aktuellen Besitzer folgen (oder freie Objekte übernehmen)
if (b.OwnerId != from && b.OwnerId != SyncedBody.NoOwner) continue; if (b.OwnerId != from && b.OwnerId != SyncedBody.NoOwner) continue;
SetRemote(b, from, b.OwnerId == from && b.OwnerActive); SetRemote(b, from, b.OwnerId == from && b.OwnerActive);
@@ -492,7 +498,7 @@ namespace MSCO
ReadState(r, out id, out pos, out rot, out vel); ReadState(r, out id, out pos, out rot, out vel);
SyncedBody b; SyncedBody b;
if (!byId.TryGetValue(id, out b) || b.Rb == null || b.OwnerId != from) return; if (!byId.TryGetValue(id, out b) || b.Rb == null || b.OwnerId != from) return;
if (!IsValidPose(pos, rot)) { SetFree(b); return; } if (!IsValidPose(pos, rot) || IsAttached(b)) { SetFree(b); return; }
b.Rb.transform.position = pos; b.Rb.transform.position = pos;
b.Rb.transform.rotation = rot; b.Rb.transform.rotation = rot;
RestoreKinematic(b); RestoreKinematic(b);
@@ -514,6 +520,7 @@ namespace MSCO
/// </summary> /// </summary>
void StopRunaway(SyncedBody b) void StopRunaway(SyncedBody b)
{ {
if (b.Attached) return;
Rigidbody rb = b.Rb; Rigidbody rb = b.Rb;
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;
@@ -527,6 +534,39 @@ namespace MSCO
ModConsole.Warning("[MSCO] Stopped " + b.RootName + " - it was flung away by the physics"); ModConsole.Warning("[MSCO] Stopped " + b.RootName + " - it was flung away by the physics");
} }
/// <summary>
/// Ist das Objekt gerade an ein Fahrzeug angebaut? Dann Besitz aufgeben und nicht mehr als loses Objekt behandeln.
/// Wird es wieder abgebaut, läuft der Sync ganz normal weiter. Gibt true zurück, solange es angebaut ist.
/// </summary>
bool UpdateAttached(SyncedBody b)
{
if (Time.time < b.NextAttachCheck) return b.Attached;
b.NextAttachCheck = Time.time + 0.5f;
bool attached = IsAttached(b);
if (attached == b.Attached) return attached;
b.Attached = attached;
if (attached)
{
if (IsRemoteOwned(b)) RestoreKinematic(b);
b.OwnerId = SyncedBody.NoOwner; // jeder Spieler erkennt das Anbauen selbst (Montage kommt über FsmSync)
b.OwnerActive = false;
}
else b.NoMotionClaimUntil = Time.time + HandoverCooldown; // gerade abgebaut: kurz zur Ruhe kommen lassen
return attached;
}
static bool IsAttached(SyncedBody b)
{
if (b.IsVehicle || b.Rb == null) return false;
// Unter einem anderen Rigidbody (z.B. am Auto eingehängt)
for (Transform p = b.Rb.transform.parent; p != null; p = p.parent)
if (p.GetComponent<Rigidbody>() != null) return true;
// Per Gelenk mit etwas verbunden, das nicht der Spieler ist (die Hand hält Items nicht über Gelenke fest)
foreach (Joint j in b.Rb.GetComponents<Joint>())
if (j != null && j.connectedBody != null && j.connectedBody.transform.root.name != "PLAYER") return true;
return false;
}
/// <summary>Position innerhalb der Karte, Drehung gültig (gleiche Regel wie auf dem Server).</summary> /// <summary>Position innerhalb der Karte, Drehung gültig (gleiche Regel wie auf dem Server).</summary>
static bool IsValidPose(Vector3 p, Quaternion q) static bool IsValidPose(Vector3 p, Quaternion q)
{ {
+12 -10
View File
@@ -88,8 +88,8 @@ namespace MSCO
// Körper: Karohemd/Jacke, Jeans, Gürtel, leichter Bauch (wie die Leute in MSC) // Körper: Karohemd/Jacke, Jeans, Gürtel, leichter Bauch (wie die Leute in MSC)
pelvis = Capsule("Pelvis", r, Jeans); pelvis = Capsule("Pelvis", r, Jeans);
torso = Textured(Capsule("Chest", r, Color.white), plaid, new Vector2(2f, 2f)); torso = Textured(Capsule("Chest", r, Color.white), plaid, new Vector2(4f, 4f));
belly = Textured(Capsule("Belly", r, Color.white), plaid, new Vector2(2f, 1f)); belly = Textured(Capsule("Belly", r, Color.white), plaid, new Vector2(4f, 2f));
belt = Box("Belt", r, new Color(0.2f, 0.13f, 0.08f)); belt = Box("Belt", r, new Color(0.2f, 0.13f, 0.08f));
buckle = Box("Buckle", r, Metal); buckle = Box("Buckle", r, Metal);
placket = Box("Buttons", r, jacketDark); placket = Box("Buttons", r, jacketDark);
@@ -102,8 +102,8 @@ namespace MSCO
shin[i] = Capsule("Shin", r, Jeans * 0.92f); shin[i] = Capsule("Shin", r, Jeans * 0.92f);
boot[i] = Box("Boot", r, Boots); boot[i] = Box("Boot", r, Boots);
sole[i] = Box("Sole", r, Sole); sole[i] = Box("Sole", r, Sole);
upperArm[i] = Textured(Capsule("UpperArm", r, Color.white), plaid, new Vector2(1f, 2f)); upperArm[i] = Textured(Capsule("UpperArm", r, Color.white), plaid, new Vector2(2f, 3f));
forearm[i] = Textured(Capsule("Forearm", r, Color.white), plaid, new Vector2(1f, 2f)); forearm[i] = Textured(Capsule("Forearm", r, Color.white), plaid, new Vector2(2f, 3f));
cuff[i] = Capsule("Cuff", r, jacketDark); cuff[i] = Capsule("Cuff", r, jacketDark);
hand[i] = Sphere("Hand", r, Skin, 0.1f); hand[i] = Sphere("Hand", r, Skin, 0.1f);
} }
@@ -159,7 +159,8 @@ namespace MSCO
brim.localPosition = new Vector3(0f, 0.07f, 0.15f); brim.localPosition = new Vector3(0f, 0.07f, 0.15f);
brim.localScale = new Vector3(0.2f, 0.014f, 0.1f); brim.localScale = new Vector3(0.2f, 0.014f, 0.1f);
brim.localRotation = Quaternion.Euler(8f, 0f, 0f); // Schirm leicht nach unten brim.localRotation = Quaternion.Euler(8f, 0f, 0f); // Schirm leicht nach unten
Transform patch = Box("CapPatch", headPivot, new Color(0.9f, 0.88f, 0.8f)); Color patchColor = Color.Lerp(jacketDark, Color.white, 0.35f);
Transform patch = Box("CapPatch", headPivot, patchColor);
patch.localPosition = new Vector3(0f, 0.115f, 0.118f); patch.localPosition = new Vector3(0f, 0.115f, 0.118f);
patch.localScale = new Vector3(0.06f, 0.035f, 0.01f); patch.localScale = new Vector3(0.06f, 0.035f, 0.01f);
patch.localRotation = Quaternion.Euler(-25f, 0f, 0f); patch.localRotation = Quaternion.Euler(-25f, 0f, 0f);
@@ -175,20 +176,21 @@ namespace MSCO
{ {
const int size = 16; const int size = 16;
var tex = new Texture2D(size, size, TextureFormat.RGB24, false); var tex = new Texture2D(size, size, TextureFormat.RGB24, false);
Color light = Color.Lerp(baseColor, Color.white, 0.18f); // Weiches Flanell-Karo: wenig Kontrast, dünne Linien - wirkt aus der Entfernung wie Stoff statt Schachbrett
Color dark = baseColor * 0.62f; Color light = Color.Lerp(baseColor, Color.white, 0.1f);
Color dark = baseColor * 0.78f;
for (int y = 0; y < size; y++) for (int y = 0; y < size; y++)
{ {
for (int x = 0; x < size; x++) for (int x = 0; x < size; x++)
{ {
bool bandX = (x / 4) % 2 == 0, bandY = (y / 4) % 2 == 0; bool bandX = (x / 4) % 2 == 0, bandY = (y / 4) % 2 == 0;
Color c = bandX && bandY ? dark : bandX || bandY ? baseColor : light; Color c = bandX && bandY ? dark : bandX || bandY ? baseColor : light;
if (x % 8 == 1 || y % 8 == 1) c = Color.Lerp(c, Color.black, 0.45f); // dünne dunkle Linien if (x % 8 == 1 || y % 8 == 1) c = Color.Lerp(c, Color.black, 0.25f); // dünne dunkle Linien
c.a = 1f; c.a = 1f;
tex.SetPixel(x, y, c); tex.SetPixel(x, y, c);
} }
} }
tex.filterMode = FilterMode.Point; tex.filterMode = FilterMode.Bilinear;
tex.wrapMode = TextureWrapMode.Repeat; tex.wrapMode = TextureWrapMode.Repeat;
tex.Apply(); tex.Apply();
return tex; return tex;
@@ -375,7 +377,7 @@ namespace MSCO
Segment(cuff[i], Vector3.Lerp(elbow, handTarget, 0.82f), Vector3.Lerp(elbow, handTarget, 0.9f), 0.105f, 0.105f); Segment(cuff[i], Vector3.Lerp(elbow, handTarget, 0.82f), Vector3.Lerp(elbow, handTarget, 0.9f), 0.105f, 0.105f);
hand[i].localPosition = handTarget; hand[i].localPosition = handTarget;
hand[i].localRotation = Quaternion.LookRotation(Vector3.forward, (handTarget - elbow).sqrMagnitude > 0f ? elbow - handTarget : Vector3.up); hand[i].localRotation = Quaternion.LookRotation(Vector3.forward, (handTarget - elbow).sqrMagnitude > 0f ? elbow - handTarget : Vector3.up);
hand[i].localScale = new Vector3(0.075f, 0.115f, 0.1f); hand[i].localScale = new Vector3(0.085f, 0.125f, 0.11f);
} }
} }