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
+41 -16
View File
@@ -53,8 +53,10 @@ namespace MSCO
};
/// <summary>
/// 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).
/// </summary>
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<Vector3>();
foreach (Renderer r in root.GetComponentsInChildren<Renderer>(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<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)
{
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<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>