diff --git a/MultiplayerManager.cs b/MultiplayerManager.cs index 109f8db..cc9fbd8 100644 --- a/MultiplayerManager.cs +++ b/MultiplayerManager.cs @@ -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]; } diff --git a/ObjectSync.cs b/ObjectSync.cs index 69a92da..d2f5acb 100644 --- a/ObjectSync.cs +++ b/ObjectSync.cs @@ -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 /// 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"); } + /// + /// 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. + /// + 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() != 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()) + if (j != null && j.connectedBody != null && j.connectedBody.transform.root.name != "PLAYER") return true; + return false; + } + /// Position innerhalb der Karte, Drehung gültig (gleiche Regel wie auf dem Server). static bool IsValidPose(Vector3 p, Quaternion q) { diff --git a/RemotePlayer.cs b/RemotePlayer.cs index 735e2e7..68027ba 100644 --- a/RemotePlayer.cs +++ b/RemotePlayer.cs @@ -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); } }