From 2502a0800e95dd7a5a41a0bdf44393fb44d5b0a4 Mon Sep 17 00:00:00 2001 From: MrMaho Date: Sat, 3 Oct 2026 17:04:55 +0200 Subject: [PATCH] Fix teleport to places landing in the wrong spot (2.10.5) Use the median of a place's parts and the dense group around it instead of the bounds of everything (scattered/disabled children pulled the center into the forest); only take top-level objects as the place root. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 6 ++++++ Cheats.cs | 57 +++++++++++++++++++++++++++++++++++++--------------- MSCO.cs | 2 +- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08b8fbd..57ce7db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2.10.5 + +- **Teleport zu Orten** (Admin, Reiter TELEPORT) landet wieder am Ort: vorher zählten alle weit verstreuten Teile + eines Ortes mit (Briefkasten, Wege, abgeschaltete Objekte) und man landete irgendwo im Wald; außerdem wurde + manchmal ein gleichnamiges Unterobjekt statt des Ortes genommen + ## 2.10.4 **Admin-Menü neu aufgebaut** diff --git a/Cheats.cs b/Cheats.cs index 33b330d..78dfa25 100644 --- a/Cheats.cs +++ b/Cheats.cs @@ -53,8 +53,10 @@ namespace MSCO }; /// - /// Wohin bei einem Teleport-Ziel? Neben das Objekt (Mitte seiner sichtbaren Teile, ein Stück außerhalb, damit man - /// nicht im Dach oder in einer Wand landet) und dort auf den Boden. + /// Wohin bei einem Teleport-Ziel? Zu einem Ort gehören oft weit verstreute Teile (Briefkasten, Wege, Strecken, + /// abgeschaltete Objekte) - deren gemeinsame Mitte liegt irgendwo im Wald. Deshalb: Median aller Teile (der Ort + /// selbst, Ausreißer zählen nicht), darum die dichte Gruppe (60 m), daneben ein Stück außerhalb auf den Boden + /// (nicht ins Dach oder in eine Wand). /// public static bool FindPlace(string objectName, out Vector3 position) { @@ -62,17 +64,28 @@ namespace MSCO GameObject root = FindRoot(objectName); if (root == null) return false; - Bounds bounds = new Bounds(root.transform.position, Vector3.zero); - bool any = false; + var centers = new List(); foreach (Renderer r in root.GetComponentsInChildren(true)) { - if (r == null || r.bounds.size == Vector3.zero) continue; - if (!any) { bounds = r.bounds; any = true; } - else bounds.Encapsulate(r.bounds); + if (r == null || r is ParticleRenderer || r is ParticleSystemRenderer) continue; + Vector3 size = r.bounds.size; + if (size == Vector3.zero || size.x > 150f || size.z > 150f) continue; // Gelände-/Wasserflächen + centers.Add(r.bounds.center); + } + if (centers.Count == 0) + { + Vector3 ground0; + position = Ground(root.transform.position, root.transform.position.y + 50f, out ground0) ? ground0 : root.transform.position; + return true; } - Vector3 center = bounds.center; - float ox = Mathf.Min(bounds.extents.x, 25f) + 3f, oz = Mathf.Min(bounds.extents.z, 25f) + 3f; + Vector3 median = new Vector3(Median(centers, 0), Median(centers, 1), Median(centers, 2)); + Bounds cluster = new Bounds(median, Vector3.zero); + foreach (Vector3 c in centers) + if (new Vector2(c.x - median.x, c.z - median.z).sqrMagnitude < 60f * 60f) cluster.Encapsulate(c); + + Vector3 center = cluster.center; + float ox = Mathf.Min(cluster.extents.x, 20f) + 4f, oz = Mathf.Min(cluster.extents.z, 20f) + 4f; Vector3[] candidates = { center + new Vector3(ox, 0f, 0f), center + new Vector3(-ox, 0f, 0f), @@ -81,20 +94,32 @@ namespace MSCO foreach (Vector3 c in candidates) { Vector3 ground; - if (Ground(c, bounds.max.y + 30f, out ground)) { position = ground; return true; } + if (Ground(c, cluster.max.y + 30f, out ground)) { position = ground; return true; } } - position = center; + position = center + Vector3.up * 2f; return true; } + static float Median(List points, int axis) + { + var values = new List(points.Count); + foreach (Vector3 p in points) values.Add(p[axis]); + values.Sort(); + return values[values.Count / 2]; + } + + /// Wurzelobjekt eines Ortes (nur oberste Ebene - "YARD" o.ä. gibt es auch als Unterobjekt). static GameObject FindRoot(string name) { - GameObject go = GameObject.Find(name); - if (go != null) return go; - // auch ausgeblendete Objekte (das Spiel schaltet entfernte Orte ab) + GameObject found = null; + // auch ausgeblendete Objekte (das Spiel schaltet entfernte Orte ab); aktive bevorzugt foreach (GameObject candidate in Resources.FindObjectsOfTypeAll()) - if (candidate.name == name && candidate.transform.parent == null && candidate.hideFlags == HideFlags.None) return candidate; - return null; + { + if (candidate.name != name || candidate.transform.parent != null || candidate.hideFlags != HideFlags.None) continue; + if (candidate.activeInHierarchy) return candidate; + if (found == null) found = candidate; + } + return found; } /// Höchster fester Boden unter einem Punkt (ohne Trigger). diff --git a/MSCO.cs b/MSCO.cs index f195a48..427439e 100644 --- a/MSCO.cs +++ b/MSCO.cs @@ -11,7 +11,7 @@ namespace MSCO /// Version der Mod. Kleine Updates: nur die dritte Stelle erhöhen (2.9.1, 2.9.2 ...) - sie bleiben mit /// Servern und Mitspielern derselben ersten zwei Stellen kompatibel. /// - public const string ModVersion = "2.10.4"; + public const string ModVersion = "2.10.5"; /// /// Netzwerk-Version (die ersten beiden Stellen). Nur erhöhen, wenn sich das Protokoll inkompatibel ändert - dann /// auch GameServer.ModVersion im Server.