diff --git a/MultiplayerManager.cs b/MultiplayerManager.cs index 7d8e0c5..2ceeb71 100644 --- a/MultiplayerManager.cs +++ b/MultiplayerManager.cs @@ -318,6 +318,19 @@ namespace MSCO } } + /// Admin: Satsuma neben sich holen (z.B. wenn er verschwunden oder im Boden ist). + 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"); + } + /// Cheats für alle sichtbar im Chat melden. void AnnounceCheat(string what) { diff --git a/ObjectSync.cs b/ObjectSync.cs index 6e23d6d..4984abd 100644 --- a/ObjectSync.cs +++ b/ObjectSync.cs @@ -102,6 +102,8 @@ 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 (b.IsVehicle) pos = AboveGround(pos, b.Rb.transform); // gespeicherte Position evtl. knapp im Boden b.Rb.transform.position = pos; b.Rb.transform.rotation = rot; if (!b.Rb.isKinematic) @@ -132,7 +134,8 @@ namespace MSCO if (!b.IsVehicle) continue; 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)" : "") + - (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)"); return lines; @@ -426,7 +429,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) continue; + if (!byId.TryGetValue(id, out b) || b.Rb == null || !IsValidPose(pos, rot)) 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); @@ -442,6 +445,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; } b.Rb.transform.position = pos; b.Rb.transform.rotation = rot; b.Rb.isKinematic = b.WasKinematic; @@ -457,6 +461,57 @@ namespace MSCO } } + /// Position innerhalb der Karte, Drehung gültig (gleiche Regel wie auf dem Server). + 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; + } + + /// + /// 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. + /// + 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; + } + + /// + /// 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. + /// + 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) { Vector3 p = b.Rb.position; diff --git a/Overlays.cs b/Overlays.cs index 8071e10..3c2d6be 100644 --- a/Overlays.cs +++ b/Overlays.cs @@ -294,6 +294,8 @@ namespace MSCO if (GUILayout.Button("+10.000 MK", primary, GUILayout.Height(30))) mp.CheatMoney(10000f); GUILayout.Space(6); 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.Label("Cheats are announced in the chat. GOTO / BRING teleport you to a player or a player to you.", small); GUILayout.Space(10); diff --git a/README.md b/README.md index 65576e2..5665535 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Multiplayer-Mod für **My Summer Car** (MSCLoader). Spiel mit bis zu 3 Spielern - **T** – Chat - **F8** – MSCO-Konsole mit allen Befehlen - **/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 - Alle Tasten sind in den MSCLoader-Einstellungen der Mod änderbar - Discord Rich Presence („In the Menus“, Server, Spielerzahl)