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 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 17:04:55 +02:00
co-authored by Claude Opus 5.5
parent 13e5193616
commit 2502a0800e
3 changed files with 48 additions and 17 deletions
+6
View File
@@ -1,5 +1,11 @@
# Changelog # 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 ## 2.10.4
**Admin-Menü neu aufgebaut** **Admin-Menü neu aufgebaut**
+41 -16
View File
@@ -53,8 +53,10 @@ namespace MSCO
}; };
/// <summary> /// <summary>
/// Wohin bei einem Teleport-Ziel? Neben das Objekt (Mitte seiner sichtbaren Teile, ein Stück außerhalb, damit man /// Wohin bei einem Teleport-Ziel? Zu einem Ort gehören oft weit verstreute Teile (Briefkasten, Wege, Strecken,
/// nicht im Dach oder in einer Wand landet) und dort auf den Boden. /// 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).
/// </summary> /// </summary>
public static bool FindPlace(string objectName, out Vector3 position) public static bool FindPlace(string objectName, out Vector3 position)
{ {
@@ -62,17 +64,28 @@ namespace MSCO
GameObject root = FindRoot(objectName); GameObject root = FindRoot(objectName);
if (root == null) return false; if (root == null) return false;
Bounds bounds = new Bounds(root.transform.position, Vector3.zero); var centers = new List<Vector3>();
bool any = false;
foreach (Renderer r in root.GetComponentsInChildren<Renderer>(true)) foreach (Renderer r in root.GetComponentsInChildren<Renderer>(true))
{ {
if (r == null || r.bounds.size == Vector3.zero) continue; if (r == null || r is ParticleRenderer || r is ParticleSystemRenderer) continue;
if (!any) { bounds = r.bounds; any = true; } Vector3 size = r.bounds.size;
else bounds.Encapsulate(r.bounds); 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; Vector3 median = new Vector3(Median(centers, 0), Median(centers, 1), Median(centers, 2));
float ox = Mathf.Min(bounds.extents.x, 25f) + 3f, oz = Mathf.Min(bounds.extents.z, 25f) + 3f; 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 = Vector3[] candidates =
{ {
center + new Vector3(ox, 0f, 0f), center + new Vector3(-ox, 0f, 0f), center + new Vector3(ox, 0f, 0f), center + new Vector3(-ox, 0f, 0f),
@@ -81,20 +94,32 @@ namespace MSCO
foreach (Vector3 c in candidates) foreach (Vector3 c in candidates)
{ {
Vector3 ground; 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; return true;
} }
static float Median(List<Vector3> points, int axis)
{
var values = new List<float>(points.Count);
foreach (Vector3 p in points) values.Add(p[axis]);
values.Sort();
return values[values.Count / 2];
}
/// <summary>Wurzelobjekt eines Ortes (nur oberste Ebene - "YARD" o.ä. gibt es auch als Unterobjekt).</summary>
static GameObject FindRoot(string name) static GameObject FindRoot(string name)
{ {
GameObject go = GameObject.Find(name); GameObject found = null;
if (go != null) return go; // auch ausgeblendete Objekte (das Spiel schaltet entfernte Orte ab); aktive bevorzugt
// auch ausgeblendete Objekte (das Spiel schaltet entfernte Orte ab)
foreach (GameObject candidate in Resources.FindObjectsOfTypeAll<GameObject>()) foreach (GameObject candidate in Resources.FindObjectsOfTypeAll<GameObject>())
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;
} }
/// <summary>Höchster fester Boden unter einem Punkt (ohne Trigger).</summary> /// <summary>Höchster fester Boden unter einem Punkt (ohne Trigger).</summary>
+1 -1
View File
@@ -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 /// 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. /// Servern und Mitspielern derselben ersten zwei Stellen kompatibel.
/// </summary> /// </summary>
public const string ModVersion = "2.10.4"; public const string ModVersion = "2.10.5";
/// <summary> /// <summary>
/// Netzwerk-Version (die ersten beiden Stellen). Nur erhöhen, wenn sich das Protokoll inkompatibel ändert - dann /// Netzwerk-Version (die ersten beiden Stellen). Nur erhöhen, wenn sich das Protokoll inkompatibel ändert - dann
/// auch GameServer.ModVersion im Server. /// auch GameServer.ModVersion im Server.