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 =
{
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];
}
+46 -6
View File
@@ -27,6 +27,10 @@ namespace MSCO
public float NextPartScan;
public float StillTime;
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 Quaternion TargetRot;
@@ -107,6 +111,7 @@ namespace MSCO
SyncedBody b;
if (!byId.TryGetValue(id, out b) || b.Rb == null || b.OwnerId != SyncedBody.NoOwner) 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
b.Rb.transform.position = pos;
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.gameObject.activeInHierarchy) continue;
if (UpdateAttached(b)) continue; // angebaut: bewegt sich mit dem Auto
bool active = b == held || b == driven;
if (!IsRemoteOwned(b)) StopRunaway(b);
@@ -292,7 +298,7 @@ namespace MSCO
{
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 (Time.time - b.AvatarHoldTime < 0.2f) continue; // sitzt gerade in der Hand eines Mitspielers
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)
{
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 (!Wins(playerId, true, b.OwnerId, b.OwnerActive)) return;
@@ -428,7 +434,7 @@ namespace MSCO
float now = Time.time;
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
|| Quaternion.Angle(b.Rb.rotation, b.LastSentRot) > 0.5f;
if (!changed && now - b.LastSentTime < KeepAliveInterval) continue;
@@ -457,7 +463,7 @@ namespace MSCO
int id = r.ReadInt32();
bool active = r.ReadBoolean();
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))
{
// 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;
ReadState(r, out id, out pos, out rot, out vel);
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)
if (b.OwnerId != from && b.OwnerId != SyncedBody.NoOwner) continue;
SetRemote(b, from, b.OwnerId == from && b.OwnerActive);
@@ -492,7 +498,7 @@ namespace MSCO
ReadState(r, out id, out pos, out rot, out vel);
SyncedBody b;
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.rotation = rot;
RestoreKinematic(b);
@@ -514,6 +520,7 @@ namespace MSCO
/// </summary>
void StopRunaway(SyncedBody b)
{
if (b.Attached) return;
Rigidbody rb = b.Rb;
if (rb.isKinematic || rb.velocity.sqrMagnitude < RunawaySpeed * RunawaySpeed) return;
rb.velocity = Vector3.zero;
@@ -527,6 +534,39 @@ namespace MSCO
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>
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)
pelvis = Capsule("Pelvis", r, Jeans);
torso = Textured(Capsule("Chest", r, Color.white), plaid, new Vector2(2f, 2f));
belly = Textured(Capsule("Belly", r, Color.white), plaid, new Vector2(2f, 1f));
torso = Textured(Capsule("Chest", r, Color.white), plaid, new Vector2(4f, 4f));
belly = Textured(Capsule("Belly", r, Color.white), plaid, new Vector2(4f, 2f));
belt = Box("Belt", r, new Color(0.2f, 0.13f, 0.08f));
buckle = Box("Buckle", r, Metal);
placket = Box("Buttons", r, jacketDark);
@@ -102,8 +102,8 @@ namespace MSCO
shin[i] = Capsule("Shin", r, Jeans * 0.92f);
boot[i] = Box("Boot", r, Boots);
sole[i] = Box("Sole", r, Sole);
upperArm[i] = Textured(Capsule("UpperArm", r, Color.white), plaid, new Vector2(1f, 2f));
forearm[i] = Textured(Capsule("Forearm", 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(2f, 3f));
cuff[i] = Capsule("Cuff", r, jacketDark);
hand[i] = Sphere("Hand", r, Skin, 0.1f);
}
@@ -159,7 +159,8 @@ namespace MSCO
brim.localPosition = new Vector3(0f, 0.07f, 0.15f);
brim.localScale = new Vector3(0.2f, 0.014f, 0.1f);
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.localScale = new Vector3(0.06f, 0.035f, 0.01f);
patch.localRotation = Quaternion.Euler(-25f, 0f, 0f);
@@ -175,20 +176,21 @@ namespace MSCO
{
const int size = 16;
var tex = new Texture2D(size, size, TextureFormat.RGB24, false);
Color light = Color.Lerp(baseColor, Color.white, 0.18f);
Color dark = baseColor * 0.62f;
// Weiches Flanell-Karo: wenig Kontrast, dünne Linien - wirkt aus der Entfernung wie Stoff statt Schachbrett
Color light = Color.Lerp(baseColor, Color.white, 0.1f);
Color dark = baseColor * 0.78f;
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
bool bandX = (x / 4) % 2 == 0, bandY = (y / 4) % 2 == 0;
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;
tex.SetPixel(x, y, c);
}
}
tex.filterMode = FilterMode.Point;
tex.filterMode = FilterMode.Bilinear;
tex.wrapMode = TextureWrapMode.Repeat;
tex.Apply();
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);
hand[i].localPosition = handTarget;
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);
}
}