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);
}
}