Satsuma no longer falls apart when driving together; automatic passenger seat

- A driven vehicle is no longer released after 3 s without data (a short hitch
  of the driver, e.g. saving, stopped the car instantly on the passenger's side
  and ripped the parts off, which then synced to everyone): 30 s timeout, on
  release it keeps the driver's velocity and part joints stay unbreakable for
  3 more seconds
- Cargo in a moving vehicle belongs to its driver (passengers no longer claim
  items in the car, which then followed late and kinematic at the driver's end
  and knocked seats and parts off)
- Passenger: sit down automatically by stepping into the open passenger door,
  also without a driver (seat from the game's DriverHeadPivot); Space or P to
  get out

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 10:23:25 +02:00
co-authored by Claude Opus 5.5
parent 73004340ce
commit 983291a4b3
4 changed files with 262 additions and 34 deletions
+14
View File
@@ -9,6 +9,20 @@
Web-Panel (Einstellungen), **OFFICIAL** vergeben nur die MSCO-Admins und wird hervorgehoben Web-Panel (Einstellungen), **OFFICIAL** vergeben nur die MSCO-Admins und wird hervorgehoben
- Braucht keinen neuen Server: die Infos kommen vom Master (msco.mbm.wtf) und funktionieren mit 2.8-Servern - Braucht keinen neuen Server: die Infos kommen vom Master (msco.mbm.wtf) und funktionieren mit 2.8-Servern
**Beifahrer**
- **Automatisch einsteigen**: in die offene Beifahrertür an den Sitz treten - man setzt sich von selbst hin, keine Taste
nötig. Aussteigen mit Leertaste oder der Beifahrer-Taste (P)
- Geht jetzt auch, **wenn noch niemand fährt** (Sitzposition kommt aus dem Fahrersitz des Spiels); fährt danach jemand
los, fährt man mit. Setzt man sich selbst ans Steuer, endet der Beifahrer-Modus
**Satsuma zerfällt nicht mehr beim gemeinsamen Fahren**
- Hing das Spiel des Fahrers kurz (z.B. beim Speichern für den Server), gab der Mitfahrer das Auto nach 3 s frei: es
blieb bei ihm schlagartig stehen, die Teile wollten mit Tempo weiter und rissen ab - und das „Teil abgefallen“ ging an
alle. Ein gefahrenes Auto wird jetzt erst nach 30 s ohne Daten freigegeben, rollt dann mit der Geschwindigkeit des
Fahrers weiter, und die Teile bleiben noch 3 s fest, bis alles eingeschwungen ist
- Ladung im Auto (Kisten, Werkzeug, Teile im Kofferraum oder auf dem Sitz) gehört jetzt immer dem Fahrer. Vorher
übernahm sie oft der Mitfahrer; beim Fahrer folgte sie dann verspätet und „unendlich schwer“ und schlug Sitze und Teile ab
**Mitfahren ohne Ruckeln** **Mitfahren ohne Ruckeln**
- Fahrer und Beifahrer sitzen beim anderen fest auf dem Sitz: die Figur wird relativ zum Auto übertragen und geglättet, - Fahrer und Beifahrer sitzen beim anderen fest auf dem Sitz: die Figur wird relativ zum Auto übertragen und geglättet,
statt in Weltkoordinaten hinter dem fahrenden Auto herzuhinken (vorher wurde der Beifahrer bei Tempo nach hinten gezogen) statt in Weltkoordinaten hinter dem fahrenden Auto herzuhinken (vorher wurde der Beifahrer bei Tempo nach hinten gezogen)
+12 -6
View File
@@ -1850,14 +1850,20 @@ namespace MSCO
StartCoroutine(ApplyWorldSnapshot(snapshot)); StartCoroutine(ApplyWorldSnapshot(snapshot));
} }
string passengerResult = null;
if (MSCO.PassengerKey != null && !overlays.AnyOpen && MSCO.PassengerKey.GetKeybindDown()) if (MSCO.PassengerKey != null && !overlays.AnyOpen && MSCO.PassengerKey.GetKeybindDown())
passengerResult = passenger.Toggle(localPlayer, playerCamera, objects, players);
else if (passenger.IsRiding && !overlays.AnyOpen && Input.GetKeyDown(KeyCode.Space))
{ {
string result = passenger.Toggle(localPlayer, playerCamera, objects, players); passenger.Exit(localPlayer, playerCamera); // aussteigen wie zu Fuß losspringen
if (result != null) passengerResult = "Got out";
{ }
SetStatus(result); else if (IsPeerReady && MenuHelper.InGame && localPlayer != null && !overlays.AnyOpen)
overlays.AddChat("", result, true); // im Spiel sichtbar, nicht nur in der Konsole passengerResult = passenger.AutoUpdate(localPlayer, playerCamera, objects, players); // in die Beifahrertür treten
} if (passengerResult != null)
{
SetStatus(passengerResult);
overlays.AddChat("", passengerResult, true); // im Spiel sichtbar, nicht nur in der Konsole
} }
bool ready = IsPeerReady; bool ready = IsPeerReady;
+115 -2
View File
@@ -30,6 +30,14 @@ namespace MSCO
// Gelenke der angebauten Teile, solange ein anderer das Fahrzeug fährt: unzerbrechlich (vorherige Bruchkraft/-moment) // Gelenke der angebauten Teile, solange ein anderer das Fahrzeug fährt: unzerbrechlich (vorherige Bruchkraft/-moment)
public Dictionary<Joint, Vector2> HeldJoints; public Dictionary<Joint, Vector2> HeldJoints;
public float NextPartScan; public float NextPartScan;
// Nach der Rückgabe erst so spät wieder zerbrechlich (bis dahin schwingen die Teile ein)
public float JointRestoreAt = float.MaxValue;
// Item liegt in diesem Fahrzeug (Kofferraum, Sitz ...): gehört dann dem, der das Fahrzeug fährt
public SyncedBody Carrier;
public float NextCarrierCheck;
// Fahrzeug: Umriss in lokalen Koordinaten (für "liegt im Auto")
public Bounds LocalBounds;
public bool HasLocalBounds;
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, // Am Auto angebautes Teil (Gelenk zum Fahrzeug oder unter einem anderen Rigidbody): bewegt sich mit dem Auto,
@@ -82,6 +90,12 @@ namespace MSCO
const float ReleaseAfterStill = 1f; const float ReleaseAfterStill = 1f;
const float SnapDistance = 10f; const float SnapDistance = 10f;
const float RemoteTimeout = 3f; // Besitzer meldet sich nicht mehr (z.B. Teil wurde dort montiert) const float RemoteTimeout = 3f; // Besitzer meldet sich nicht mehr (z.B. Teil wurde dort montiert)
// Ein gefahrenes Auto NICHT nach 3 s freigeben: hängt das Spiel des Fahrers kurz (z.B. beim Speichern), blieb es
// bei den anderen schlagartig stehen und die Teile rissen ab. Trennt der Fahrer, gibt ReleaseAllOf es frei.
const float DrivenVehicleTimeout = 30f;
const float JointSettleTime = 3f; // so lange nach der Rückgabe bleiben die Gelenke der Teile unzerbrechlich
const float CarrierCheckInterval = 0.4f;
const float CarrierMinSpeed = 1.5f; // ab dieser Geschwindigkeit nimmt ein Fahrzeug seine Ladung mit
const float RunawaySpeed = 90f; // m/s - schneller fährt in MSC nichts; darüber hat die Physik es weggeschleudert const float RunawaySpeed = 90f; // m/s - schneller fährt in MSC nichts; darüber hat die Physik es weggeschleudert
const float HandoverCooldown = 3f; // nach Freigabe/Übergabe keine Übernahme durch Bewegung const float HandoverCooldown = 3f; // nach Freigabe/Übergabe keine Übernahme durch Bewegung
const float MaxHoldDistance = 1.5f; // gehaltene Items sind so nah an der Kamera const float MaxHoldDistance = 1.5f; // gehaltene Items sind so nah an der Kamera
@@ -191,6 +205,9 @@ namespace MSCO
public bool IsRemoteOwnedBody(SyncedBody b) { return IsRemoteOwned(b); } public bool IsRemoteOwnedBody(SyncedBody b) { return IsRemoteOwned(b); }
/// <summary>Alle erfassten Fahrzeuge (Liste wird alle 2 s erneuert, nicht verändern).</summary>
public List<SyncedBody> AllVehicles() { return Vehicles(); }
/// <summary>Für "mp vehicles": Zustand aller erfassten Fahrzeuge (zur Fehlersuche).</summary> /// <summary>Für "mp vehicles": Zustand aller erfassten Fahrzeuge (zur Fehlersuche).</summary>
public List<string> DescribeVehicles() public List<string> DescribeVehicles()
{ {
@@ -258,7 +275,10 @@ namespace MSCO
public void Reset() public void Reset()
{ {
foreach (SyncedBody b in byId.Values) foreach (SyncedBody b in byId.Values)
{
if (IsRemoteOwned(b) && b.Rb != null) RestoreKinematic(b); if (IsRemoteOwned(b) && b.Rb != null) RestoreKinematic(b);
RestoreJoints(b);
}
byId.Clear(); byId.Clear();
byRb.Clear(); byRb.Clear();
rejected.Clear(); rejected.Clear();
@@ -338,13 +358,25 @@ namespace MSCO
b.LastPos = b.Rb.position; b.LastPos = b.Rb.position;
if (!b.Rb.gameObject.activeInHierarchy) continue; if (!b.Rb.gameObject.activeInHierarchy) continue;
if (UpdateAttached(b)) continue; // angebaut: bewegt sich mit dem Auto if (UpdateAttached(b)) continue; // angebaut: bewegt sich mit dem Auto
if (b.HeldJoints != null && !IsRemoteOwned(b) && Time.time >= b.JointRestoreAt) RestoreJoints(b);
bool active = b == held || b == driven; bool active = b == held || b == driven;
if (!IsRemoteOwned(b)) StopRunaway(b); if (!IsRemoteOwned(b)) StopRunaway(b);
// Ladung eines fahrenden Autos gehört dessen Fahrer: nur bei ihm wird sie mit dem Auto simuliert.
// Übernahm sie ein Mitfahrer, folgte sie beim Fahrer verspätet und kinematisch (unendlich schwer) und
// schlug Sitze und Teile ab.
SyncedBody carrier = active || b.IsVehicle ? null : CarrierOf(b);
bool carrierRemote = carrier != null && IsRemoteOwned(carrier);
bool carrierMine = carrier != null && IsLocal(carrier) && IsCarrying(carrier);
if (b.OwnerId == SyncedBody.NoOwner) if (b.OwnerId == SyncedBody.NoOwner)
{ {
if (active) Claim(b, true); if (active) Claim(b, true);
else if (motionClaimAllowed && Time.time >= b.NoMotionClaimUntil && IsMoving(b) && IsNear(b, player)) Claim(b, false); else if (carrierMine && IsMoving(b)) Claim(b, false);
else if (motionClaimAllowed && Time.time >= b.NoMotionClaimUntil && IsMoving(b) && IsNear(b, player) && !carrierRemote) Claim(b, false);
}
else if (IsLocal(b) && !active && carrierRemote && carrier.OwnerActive)
{
Release(b); // fährt ein anderer: der übernimmt die Ladung
} }
else if (IsLocal(b)) else if (IsLocal(b))
{ {
@@ -367,7 +399,8 @@ namespace MSCO
// zeigt oft noch auf ein altes Item; trägt es jemand an unserem Kopf vorbei, würden wir es ihm sonst entreißen // zeigt oft noch auf ein altes Item; trägt es jemand an unserem Kopf vorbei, würden wir es ihm sonst entreißen
bool grab = b == driven || !b.OwnerActive || JustPicked; bool grab = b == driven || !b.OwnerActive || JustPicked;
if (active && grab && Wins(LocalId, true, b.OwnerId, b.OwnerActive)) Claim(b, true); // Spieler greift sich das Objekt if (active && grab && Wins(LocalId, true, b.OwnerId, b.OwnerActive)) Claim(b, true); // Spieler greift sich das Objekt
else if (Time.time - b.LastRecvTime > RemoteTimeout) SetFree(b); else if (carrierMine && !b.OwnerActive) Claim(b, false); // liegt in unserem fahrenden Auto
else if (Time.time - b.LastRecvTime > (b.IsVehicle && b.OwnerActive ? DrivenVehicleTimeout : RemoteTimeout)) SetFree(b);
} }
} }
ReportConsumed(player); ReportConsumed(player);
@@ -498,6 +531,7 @@ namespace MSCO
b.Rb.isKinematic = true; b.Rb.isKinematic = true;
if (b.IsVehicle) if (b.IsVehicle)
{ {
b.JointRestoreAt = float.MaxValue;
HoldParts(b); HoldParts(b);
// Physik läuft mit 50 Hz, das Bild öfter: ohne Interpolation springt das Auto nur in Physik-Schritten // Physik läuft mit 50 Hz, das Bild öfter: ohne Interpolation springt das Auto nur in Physik-Schritten
// weiter - wer darin mitfährt, sieht die Welt ruckeln. Nur bei Fahrzeugen (Items setzt die Hand direkt). // weiter - wer darin mitfährt, sieht die Welt ruckeln. Nur bei Fahrzeugen (Items setzt die Hand direkt).
@@ -527,9 +561,24 @@ namespace MSCO
{ {
b.Rb.isKinematic = b.WasKinematic; b.Rb.isKinematic = b.WasKinematic;
if (b.InterpolationSet) b.Rb.interpolation = b.WasInterpolation; if (b.InterpolationSet) b.Rb.interpolation = b.WasInterpolation;
// Mitten in der Fahrt zurückgegeben (Fahrer übergibt, steigt aus ...): mit seiner Geschwindigkeit
// weiterrollen statt schlagartig stehenzubleiben - die Teile wollen sonst mit Tempo weiter und reißen ab
if (b.IsVehicle && !b.Rb.isKinematic)
{
b.Rb.velocity = Time.fixedTime - b.TargetTime < 0.5f ? b.TargetVel : Vector3.zero;
b.Rb.angularVelocity = Vector3.zero;
}
} }
b.InterpolationSet = false; b.InterpolationSet = false;
if (b.HeldJoints == null) return; if (b.HeldJoints == null) return;
if (b.IsVehicle) { b.JointRestoreAt = Time.time + JointSettleTime; return; } // erst nach dem Einschwingen
RestoreJoints(b);
}
void RestoreJoints(SyncedBody b)
{
b.JointRestoreAt = float.MaxValue;
if (b.HeldJoints == null) return;
foreach (KeyValuePair<Joint, Vector2> kv in b.HeldJoints) foreach (KeyValuePair<Joint, Vector2> kv in b.HeldJoints)
{ {
if (kv.Key == null) continue; // Teil wurde inzwischen abgebaut if (kv.Key == null) continue; // Teil wurde inzwischen abgebaut
@@ -539,6 +588,70 @@ namespace MSCO
b.HeldJoints = null; b.HeldJoints = null;
} }
// ---------------------------------------------------------------- Ladung in Fahrzeugen
/// <summary>Fährt das Fahrzeug gerade (nimmt also seine Ladung mit)?</summary>
bool IsCarrying(SyncedBody v)
{
if (v.Rb == null) return false;
if (IsRemoteOwned(v)) return v.OwnerActive || v.TargetVel.sqrMagnitude > CarrierMinSpeed * CarrierMinSpeed;
return v == Driven || (!v.Rb.isKinematic && v.Rb.velocity.sqrMagnitude > CarrierMinSpeed * CarrierMinSpeed);
}
/// <summary>In welchem Fahrzeug liegt das Item gerade (null = in keinem)? Zwischengespeichert.</summary>
SyncedBody CarrierOf(SyncedBody item)
{
if (Time.time < item.NextCarrierCheck) return item.Carrier != null && item.Carrier.Rb != null ? item.Carrier : null;
item.NextCarrierCheck = Time.time + CarrierCheckInterval;
item.Carrier = null;
Vector3 p = item.Rb.worldCenterOfMass;
foreach (SyncedBody v in Vehicles())
{
if (v.Rb == null || (v.Rb.position - p).sqrMagnitude > 15f * 15f) continue;
if (!v.HasLocalBounds) ComputeLocalBounds(v);
if (v.LocalBounds.Contains(v.Rb.transform.InverseTransformPoint(p))) { item.Carrier = v; break; }
}
return item.Carrier;
}
readonly List<SyncedBody> vehicleList = new List<SyncedBody>();
float nextVehicleList;
List<SyncedBody> Vehicles()
{
if (Time.time < nextVehicleList) return vehicleList;
nextVehicleList = Time.time + 2f;
vehicleList.Clear();
foreach (SyncedBody b in byId.Values) if (b.IsVehicle && b.Rb != null) vehicleList.Add(b);
return vehicleList;
}
/// <summary>Umriss des Fahrzeugs (alle festen Collider) in seinen lokalen Koordinaten, etwas nach oben erweitert.</summary>
static void ComputeLocalBounds(SyncedBody v)
{
Transform t = v.Rb.transform;
bool any = false;
var local = new Bounds();
foreach (Collider c in v.Rb.GetComponentsInChildren<Collider>())
{
if (c == null || c.isTrigger || !c.enabled) continue;
Bounds wb = c.bounds;
for (int i = 0; i < 8; i++)
{
Vector3 corner = wb.center + Vector3.Scale(wb.extents, new Vector3((i & 1) == 0 ? -1 : 1, (i & 2) == 0 ? -1 : 1, (i & 4) == 0 ? -1 : 1));
Vector3 lp = t.InverseTransformPoint(corner);
if (!any) { local = new Bounds(lp, Vector3.zero); any = true; }
else local.Encapsulate(lp);
}
}
if (!any) local = new Bounds(Vector3.zero, new Vector3(2f, 1.5f, 4f));
// Weltachsen-Umrisse sind bei schrägem Auto zu groß - etwas verkleinern, oben Platz für Ladung lassen
local.Expand(new Vector3(-0.2f, 0f, -0.2f));
local.max = new Vector3(local.max.x, local.max.y + 0.5f, local.max.z);
v.LocalBounds = local;
v.HasLocalBounds = true;
}
void SendOwnedStates() void SendOwnedStates()
{ {
sendList.Clear(); sendList.Clear();
+121 -26
View File
@@ -4,14 +4,21 @@ using UnityEngine;
namespace MSCO namespace MSCO
{ {
/// <summary> /// <summary>
/// Beifahrer: Neben einem Fahrzeug, das ein anderer Spieler fährt, per Taste einsteigen. /// Beifahrer: Wer in die offene Beifahrertür an den Sitz tritt, setzt sich automatisch hin - egal ob schon jemand
/// Der Sitz ist die Fahrerposition, auf die andere Seite gespiegelt (bei Fahrzeugen mit /// fährt oder das Auto noch steht (die Taste geht weiterhin, auch zum Aussteigen; Leertaste steigt ebenfalls aus).
/// Sitz in der Mitte, z.B. Traktor oder Moped: dahinter). Während der Fahrt wird der /// Der Sitz ist die Fahrerposition des Spiels (DriverHeadPivot), auf die andere Seite gespiegelt (bei Fahrzeugen mit
/// Spieler jedes Frame auf den Sitz gesetzt und dreht mit dem Fahrzeug mit; umschauen geht weiter. /// Sitz in der Mitte, z.B. Traktor oder Moped: dahinter). Während der Fahrt wird der Spieler jedes Frame auf den Sitz
/// gesetzt und dreht mit dem Fahrzeug mit; umschauen geht weiter.
/// </summary> /// </summary>
public class Passenger public class Passenger
{ {
const float EnterRange = 3f; // so nah muss man an der Karosserie stehen const float EnterRange = 3f; // per Taste: so nah muss man an der Karosserie stehen
const float AutoEnterRange = 1.0f; // automatisch: Kopf so nah am Beifahrersitz ...
const float AutoEnterSide = 0.6f; // ... und seitlich höchstens so weit außerhalb des Sitzes - an der
// geschlossenen Tür steht man weiter draußen, nur in der offenen Tür so nah
const float AutoEnterDwell = 0.35f; // ... so lange (nicht beim bloßen Vorbeilaufen)
const float AutoRearmRange = 2f; // nach dem Aussteigen erst wieder, wenn man so weit weg war
const float AutoCheckInterval = 0.15f;
const float CenterSeatThreshold = 0.2f; const float CenterSeatThreshold = 0.2f;
const float BehindCenterSeat = 0.7f; const float BehindCenterSeat = 0.7f;
const float ExitSideOffset = 1.4f; // beim Aussteigen so weit neben den Sitz const float ExitSideOffset = 1.4f; // beim Aussteigen so weit neben den Sitz
@@ -27,6 +34,13 @@ namespace MSCO
// Feste Collider des Spielers, die während der Fahrt aus sind (sonst stecken sie in Sitzen und Teilen des Autos) // Feste Collider des Spielers, die während der Fahrt aus sind (sonst stecken sie in Sitzen und Teilen des Autos)
readonly List<Collider> disabledColliders = new List<Collider>(); readonly List<Collider> disabledColliders = new List<Collider>();
readonly List<Rigidbody> frozenBodies = new List<Rigidbody>(); readonly List<Rigidbody> frozenBodies = new List<Rigidbody>();
// Fahrerkopf je Fahrzeug (lokal), aus DriverHeadPivot - null = nicht gefunden
readonly Dictionary<Rigidbody, Vector3?> driverHeads = new Dictionary<Rigidbody, Vector3?>();
float nextAutoCheck, dwellStart = -1f;
SyncedBody dwellVehicle;
bool autoArmed = true;
Vector3 lastExitSeat;
public bool IsRiding { get { return vehicle != null; } } public bool IsRiding { get { return vehicle != null; } }
/// <summary>Fahrzeug, in dem wir mitfahren (sonst null).</summary> /// <summary>Fahrzeug, in dem wir mitfahren (sonst null).</summary>
@@ -42,38 +56,109 @@ namespace MSCO
Exit(player, cam); Exit(player, cam);
return "Got out"; return "Got out";
} }
if (cam == null) return null; if (cam == null || objects.IsDriving) return null;
SyncedBody best = null; SyncedBody best = null;
RemotePlayer driver = null; Vector3 bestSeat = Vector3.zero;
float bestDist = EnterRange * EnterRange; float bestDist = EnterRange * EnterRange, nearest = float.MaxValue;
List<SyncedBody> driven = objects.RemoteDrivenVehicles(); foreach (SyncedBody b in objects.AllVehicles())
float nearest = float.MaxValue;
foreach (SyncedBody b in driven)
{ {
if (b.Rb == null || (b.Rb.position - player.position).sqrMagnitude > 30f * 30f) continue;
float d = DistanceToVehicleSq(b, player.position); float d = DistanceToVehicleSq(b, player.position);
Vector3? seat = SeatFor(b, objects, players);
if (seat == null) continue;
nearest = Mathf.Min(nearest, d); nearest = Mathf.Min(nearest, d);
PlayerInfo owner; if (d < bestDist) { bestDist = d; best = b; bestSeat = seat.Value; }
if (!players.TryGetValue(b.OwnerId, out owner) || owner.Avatar == null) continue;
if (d < bestDist) { bestDist = d; best = b; driver = owner.Avatar; }
} }
if (best == null) if (best == null)
{ {
// Genau sagen, woran es liegt if (nearest == float.MaxValue) return "Passenger: no vehicle nearby";
if (driven.Count == 0) return "Passenger: nobody is driving a vehicle right now (the driver must sit in the driver's seat)";
return "Passenger: get closer to the car (" + Mathf.Sqrt(nearest).ToString("0") + " m away, max " + EnterRange.ToString("0") + " m)"; return "Passenger: get closer to the car (" + Mathf.Sqrt(nearest).ToString("0") + " m away, max " + EnterRange.ToString("0") + " m)";
} }
Enter(best, bestSeat, player);
return "Riding as passenger (press again or Space to get out)";
}
Transform vt = best.Rb.transform; /// <summary>
Vector3 driverHead = vt.InverseTransformPoint(driver.HeadPosition); /// Jedes Frame (zu Fuß): steht man mit dem Kopf am Beifahrersitz (in der offenen Tür), automatisch hinsetzen.
seatHeadLocal = Mathf.Abs(driverHead.x) < CenterSeatThreshold /// Gibt einen Statustext zurück, wenn man eingestiegen ist.
? driverHead - new Vector3(0f, 0f, BehindCenterSeat) // Sitz in der Mitte: dahinter /// </summary>
: new Vector3(-driverHead.x, driverHead.y, driverHead.z); // gegenüber vom Fahrer public string AutoUpdate(Transform player, Camera cam, ObjectSync objects, IDictionary<int, PlayerInfo> players)
{
if (IsRiding || cam == null || player == null || objects.IsDriving) { dwellStart = -1f; return null; }
if (Time.time < nextAutoCheck) return null;
nextAutoCheck = Time.time + AutoCheckInterval;
Vector3 head = cam.transform.position;
vehicle = best; // Nach dem Aussteigen erst einmal weggehen, sonst säße man sofort wieder drin
lastYaw = vt.eulerAngles.y; if (!autoArmed)
{
if ((head - lastExitSeat).sqrMagnitude > AutoRearmRange * AutoRearmRange) autoArmed = true;
else return null;
}
SyncedBody found = null;
Vector3 foundSeat = Vector3.zero;
foreach (SyncedBody b in objects.AllVehicles())
{
if (b.Rb == null || (b.Rb.position - head).sqrMagnitude > 8f * 8f) continue;
Vector3? seat = SeatFor(b, objects, players);
if (seat == null) continue;
if ((b.Rb.transform.TransformPoint(seat.Value) - head).sqrMagnitude > AutoEnterRange * AutoEnterRange) continue;
Vector3 local = b.Rb.transform.InverseTransformPoint(head);
if (Mathf.Abs(local.x) > Mathf.Abs(seat.Value.x) + AutoEnterSide) continue;
found = b;
foundSeat = seat.Value;
break;
}
if (found == null || found != dwellVehicle)
{
dwellVehicle = found;
dwellStart = found != null ? Time.time : -1f;
return null;
}
if (Time.time - dwellStart < AutoEnterDwell) return null;
dwellStart = -1f;
dwellVehicle = null;
Enter(found, foundSeat, player);
return "Riding as passenger (Space or the passenger key to get out)";
}
void Enter(SyncedBody b, Vector3 seat, Transform player)
{
seatHeadLocal = seat;
vehicle = b;
lastYaw = b.Rb.transform.eulerAngles.y;
SetMovementEnabled(player, false); SetMovementEnabled(player, false);
return "Riding as passenger (press again to get out)"; }
/// <summary>Beifahrersitz (Kopfposition, lokal im Fahrzeug) - aus dem Fahrersitz des Spiels oder dem Fahrer.</summary>
Vector3? SeatFor(SyncedBody b, ObjectSync objects, IDictionary<int, PlayerInfo> players)
{
Vector3? driverHead;
if (!driverHeads.TryGetValue(b.Rb, out driverHead))
{
driverHead = null;
foreach (Transform t in b.Rb.GetComponentsInChildren<Transform>(true))
{
if (t.name != "DriverHeadPivot") continue;
driverHead = b.Rb.transform.InverseTransformPoint(t.position);
break;
}
driverHeads[b.Rb] = driverHead;
}
// Ohne DriverHeadPivot: nur, wenn gerade jemand fährt (dessen Kopf ist die Fahrerposition)
if (driverHead == null && objects.IsRemoteOwnedBody(b) && b.OwnerActive)
{
PlayerInfo owner;
if (players.TryGetValue(b.OwnerId, out owner) && owner.Avatar != null)
driverHead = b.Rb.transform.InverseTransformPoint(owner.Avatar.HeadPosition);
}
if (driverHead == null) return null;
Vector3 d = driverHead.Value;
return Mathf.Abs(d.x) < CenterSeatThreshold
? d - new Vector3(0f, 0f, BehindCenterSeat) // Sitz in der Mitte: dahinter
: new Vector3(-d.x, d.y, d.z); // gegenüber vom Fahrer
} }
/// <summary> /// <summary>
@@ -96,12 +181,17 @@ namespace MSCO
public void LateUpdate(Transform player, Camera cam, ObjectSync objects) public void LateUpdate(Transform player, Camera cam, ObjectSync objects)
{ {
if (!IsRiding) return; if (!IsRiding) return;
// Fahrer ausgestiegen oder Fahrzeug weg: automatisch aussteigen if (vehicle.Rb == null || cam == null)
if (vehicle.Rb == null || cam == null || !objects.IsRemoteOwnedBody(vehicle))
{ {
Exit(player, cam); Exit(player, cam);
return; return;
} }
// Selbst ans Steuer gesetzt (Fahrersitz des Spiels): Beifahrer-Modus ohne Umsetzen beenden
if (objects.IsDriving)
{
Reset();
return;
}
Transform vt = vehicle.Rb.transform; Transform vt = vehicle.Rb.transform;
float yaw = vt.eulerAngles.y; float yaw = vt.eulerAngles.y;
@@ -117,6 +207,8 @@ namespace MSCO
if (!IsRiding) return; if (!IsRiding) return;
if (player != null && vehicle.Rb != null) if (player != null && vehicle.Rb != null)
{ {
lastExitSeat = vehicle.Rb.transform.TransformPoint(seatHeadLocal);
autoArmed = false;
// Auf der Seite des Sitzes neben das Fahrzeug stellen // Auf der Seite des Sitzes neben das Fahrzeug stellen
float side = seatHeadLocal.x >= 0f ? 1f : -1f; float side = seatHeadLocal.x >= 0f ? 1f : -1f;
Vector3 outside = vehicle.Rb.transform.TransformPoint(seatHeadLocal + new Vector3(side * ExitSideOffset, 0f, 0f)); Vector3 outside = vehicle.Rb.transform.TransformPoint(seatHeadLocal + new Vector3(side * ExitSideOffset, 0f, 0f));
@@ -131,6 +223,9 @@ namespace MSCO
public void Reset() public void Reset()
{ {
vehicle = null; vehicle = null;
dwellVehicle = null;
dwellStart = -1f;
driverHeads.Clear();
SetMovementEnabled(null, true); SetMovementEnabled(null, true);
} }