Validate stored vehicle/object poses, show positions in 'vehicles', admin: Satsuma to me

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-26 23:25:00 +02:00
co-authored by Claude Opus 5.5
parent b33cd82e68
commit 24cd485bcc
4 changed files with 73 additions and 3 deletions
+13
View File
@@ -318,6 +318,19 @@ namespace MSCO
} }
} }
/// <summary>Admin: Satsuma neben sich holen (z.B. wenn er verschwunden oder im Boden ist).</summary>
public void BringCar()
{
if (!CanCheat || localPlayer == null) return;
Vector3 right = localPlayer.right;
Vector3 pos = localPlayer.position + right * 4f;
Quaternion rot = Quaternion.LookRotation(localPlayer.forward, Vector3.up);
string result = objects.BringVehicle("SATSUMA", pos, rot);
overlays.Log(result);
overlays.AddChat("", result, true);
if (result.EndsWith("next to you")) AnnounceCheat("brought the Satsuma to themselves");
}
/// <summary>Cheats für alle sichtbar im Chat melden.</summary> /// <summary>Cheats für alle sichtbar im Chat melden.</summary>
void AnnounceCheat(string what) void AnnounceCheat(string what)
{ {
+57 -2
View File
@@ -102,6 +102,8 @@ 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 (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;
if (!b.Rb.isKinematic) if (!b.Rb.isKinematic)
@@ -132,7 +134,8 @@ namespace MSCO
if (!b.IsVehicle) continue; if (!b.IsVehicle) continue;
string owner = b.OwnerId == SyncedBody.NoOwner ? "free" : IsLocal(b) ? "ME" : "player " + b.OwnerId; string owner = b.OwnerId == SyncedBody.NoOwner ? "free" : IsLocal(b) ? "ME" : "player " + b.OwnerId;
lines.Add(b.RootName + " id=" + b.Id + " owner=" + owner + (b.OwnerActive ? " (active)" : "") + lines.Add(b.RootName + " id=" + b.Id + " owner=" + owner + (b.OwnerActive ? " (active)" : "") +
(b.Rb == null ? " [rigidbody gone]" : " kinematic=" + b.Rb.isKinematic + " speed=" + b.Rb.velocity.magnitude.ToString("0.0"))); (b.Rb == null ? " [rigidbody gone]" : " kinematic=" + b.Rb.isKinematic + " speed=" + b.Rb.velocity.magnitude.ToString("0.0") +
" pos=" + b.Rb.position.ToString("F0") + (b.Rb.gameObject.activeInHierarchy ? "" : " [inactive]")));
} }
if (lines.Count == 1) lines.Add("No vehicles registered! (SATSUMA, HAYOSIKO, ... not found as root objects with a Rigidbody)"); if (lines.Count == 1) lines.Add("No vehicles registered! (SATSUMA, HAYOSIKO, ... not found as root objects with a Rigidbody)");
return lines; return lines;
@@ -426,7 +429,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) continue; if (!byId.TryGetValue(id, out b) || b.Rb == null || !IsValidPose(pos, rot)) 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);
@@ -442,6 +445,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; }
b.Rb.transform.position = pos; b.Rb.transform.position = pos;
b.Rb.transform.rotation = rot; b.Rb.transform.rotation = rot;
b.Rb.isKinematic = b.WasKinematic; b.Rb.isKinematic = b.WasKinematic;
@@ -457,6 +461,57 @@ namespace MSCO
} }
} }
/// <summary>Position innerhalb der Karte, Drehung gültig (gleiche Regel wie auf dem Server).</summary>
static bool IsValidPose(Vector3 p, Quaternion q)
{
if (float.IsNaN(p.x) || float.IsNaN(p.y) || float.IsNaN(p.z) || float.IsInfinity(p.x) || float.IsInfinity(p.y) || float.IsInfinity(p.z))
return false;
if (Mathf.Abs(p.x) > 6000f || Mathf.Abs(p.z) > 6000f || p.y < -200f || p.y > 3000f) return false;
float len = Mathf.Sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w);
return len > 0.9f && len < 1.1f;
}
/// <summary>
/// Liegt die Position im Boden, auf die Bodenoberfläche heben. Kurzer Strahl ab 2 m über dem Punkt
/// (nicht aufs Garagendach), die eigenen Teile des Fahrzeugs werden ignoriert.
/// </summary>
static Vector3 AboveGround(Vector3 pos, Transform ignore)
{
float ground = float.MinValue;
foreach (RaycastHit hit in Physics.RaycastAll(pos + Vector3.up * 2f, Vector3.down, 4f))
{
if (hit.collider.isTrigger || (ignore != null && hit.transform.IsChildOf(ignore))) continue;
if (hit.transform.root.name == "PLAYER") continue;
ground = Mathf.Max(ground, hit.point.y);
}
return ground > pos.y - 0.2f ? new Vector3(pos.x, ground + 0.6f, pos.z) : pos;
}
/// <summary>
/// Admin: Fahrzeug (Wurzelname beginnt mit rootPrefix) an eine Position holen. Fährt es gerade ein
/// anderer Spieler, geht das nicht. Gibt einen Statustext zurück.
/// </summary>
public string BringVehicle(string rootPrefix, Vector3 pos, Quaternion rot)
{
foreach (SyncedBody b in byId.Values)
{
if (!b.IsVehicle || b.Rb == null || !b.RootName.StartsWith(rootPrefix)) continue;
if (IsRemoteOwned(b) && b.OwnerActive) return rootPrefix + " is being driven by another player";
Claim(b, false); // wir besitzen es jetzt -> Position geht an alle und an den Server
b.Rb.transform.position = AboveGround(pos, b.Rb.transform);
b.Rb.transform.rotation = rot;
if (!b.Rb.isKinematic)
{
b.Rb.velocity = Vector3.zero;
b.Rb.angularVelocity = Vector3.zero;
}
b.StillTime = 0f;
b.LastSentTime = -100f;
return rootPrefix + " is next to you";
}
return rootPrefix + " not found (mp vehicles shows all vehicles)";
}
static void WriteState(BinaryWriter w, SyncedBody b) static void WriteState(BinaryWriter w, SyncedBody b)
{ {
Vector3 p = b.Rb.position; Vector3 p = b.Rb.position;
+2
View File
@@ -294,6 +294,8 @@ namespace MSCO
if (GUILayout.Button("+10.000 MK", primary, GUILayout.Height(30))) mp.CheatMoney(10000f); if (GUILayout.Button("+10.000 MK", primary, GUILayout.Height(30))) mp.CheatMoney(10000f);
GUILayout.Space(6); GUILayout.Space(6);
if (GUILayout.Button("REFILL NEEDS", primary, GUILayout.Height(30))) mp.CheatNeeds(); if (GUILayout.Button("REFILL NEEDS", primary, GUILayout.Height(30))) mp.CheatNeeds();
GUILayout.Space(6);
if (GUILayout.Button("SATSUMA TO ME", primary, GUILayout.Height(30))) mp.BringCar();
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
GUILayout.Label("Cheats are announced in the chat. GOTO / BRING teleport you to a player or a player to you.", small); GUILayout.Label("Cheats are announced in the chat. GOTO / BRING teleport you to a player or a player to you.", small);
GUILayout.Space(10); GUILayout.Space(10);
+1 -1
View File
@@ -32,7 +32,7 @@ Multiplayer-Mod für **My Summer Car** (MSCLoader). Spiel mit bis zu 3 Spielern
- **T** – Chat - **T** – Chat
- **F8** – MSCO-Konsole mit allen Befehlen - **F8** – MSCO-Konsole mit allen Befehlen
- **/admin** – Admin-Menü für Server-Admins bzw. den P2P-Host: Spieler kicken/bannen, GOTO/BRING (teleportieren), - **/admin** – Admin-Menü für Server-Admins bzw. den P2P-Host: Spieler kicken/bannen, GOTO/BRING (teleportieren),
Cheats (+1.000 / +10.000 MK, Bedürfnisse auffüllen) – Cheats werden im Chat angezeigt Cheats (+1.000 / +10.000 MK, Bedürfnisse auffüllen, Satsuma zu sich holen) – Cheats werden im Chat angezeigt
- **P** – als Beifahrer ein-/aussteigen - **P** – als Beifahrer ein-/aussteigen
- Alle Tasten sind in den MSCLoader-Einstellungen der Mod änderbar - Alle Tasten sind in den MSCLoader-Einstellungen der Mod änderbar
- Discord Rich Presence („In the Menus“, Server, Spielerzahl) - Discord Rich Presence („In the Menus“, Server, Spielerzahl)