using System.Collections.Generic; using System.Reflection; using HutongGames.PlayMaker; using MSCO; using UnityEngine; [assembly: AssemblyTitle("Show Bolt Size")] [assembly: AssemblyProduct("Show Bolt Size")] [assembly: AssemblyDescription("Shows the size of the bolt you look at, so you pick the right wrench.")] [assembly: AssemblyVersion("1.0.2.0")] [assembly: AssemblyFileVersion("1.0.2.0")] [assembly: AssemblyInformationalVersion("1.0.2")] namespace ShowBoltSize { /// /// Zeigt unter dem Fadenkreuz die Größe der Schraube, auf die man schaut ("10 mm"). Jede Schraube hat die FSM "Screw" /// mit der Variable "BoltSize". Deren Kollider sind nur mit Werkzeug in der Hand aktiv - deshalb kein Raycast, sondern: /// die Schraube, die am nächsten an der Blickrichtung liegt (bis 1,3 m). Nur Anzeige, ändert nichts am Spiel. /// public class ShowBoltSize : MscoMod { public override string ID { get { return "showboltsize"; } } public override string Name { get { return "Show Bolt Size"; } } public override string Version { get { return "1.0.2"; } } public override string Author { get { return "MBM"; } } const float MaxDistance = 1.5f; const float AimTolerance = 0.05f; // so weit (m) darf die Schraube neben der Blicklinie liegen const float ScanInterval = 5f; sealed class Bolt { public Transform Transform; public Renderer Renderer; // sichtbarer Schraubenkopf (Kind-Objekt) - sein Mittelpunkt statt des Drehpunkts public FsmFloat SizeF; public FsmInt SizeI; } readonly List bolts = new List(); bool inGame; float nextScan; string label; GUIStyle style; Camera camera; bool reported; int diagnostics; // die ersten Male ins Log schreiben, was in Blickrichtung liegt float nextDiagnostic; bool shownOnce; public override void OnMenuLoad() { inGame = false; bolts.Clear(); label = null; camera = null; reported = false; } public override void OnGameLoad() { inGame = true; nextScan = 0f; } /// /// Spielerkamera: Camera.main gibt es in My Summer Car nicht immer (die Kamera des Spielers ist "FPSCamera"). /// Camera FindCamera() { if (camera != null && camera.enabled && camera.gameObject.activeInHierarchy) return camera; camera = null; foreach (Camera c in Camera.allCameras) if (c.name == "FPSCamera") { camera = c; break; } if (camera == null) camera = Camera.main; return camera; } static Vector3 Center(Bolt b) { return b.Renderer != null ? b.Renderer.bounds.center : b.Transform.position; } public override void Update() { label = null; if (!inGame) return; Camera cam = FindCamera(); if (cam == null) return; if (Time.realtimeSinceStartup >= nextScan) Scan(cam); // Die Schraube, die am nächsten an der Blicklinie liegt (Abstand zur Linie, nicht Winkel) Transform eye = cam.transform; Vector3 eyePos = eye.position, forward = eye.forward; Bolt best = null, nearest = null; float bestOff = float.MaxValue, nearestOff = float.MaxValue; for (int i = 0; i < bolts.Count; i++) { Bolt b = bolts[i]; if (b.Transform == null || !b.Transform.gameObject.activeInHierarchy) continue; Vector3 to = Center(b) - eyePos; float along = Vector3.Dot(to, forward); if (along < 0.05f || along > MaxDistance + 0.5f) continue; float off = Vector3.Cross(forward, to).magnitude; // Abstand zur Blicklinie if (off < nearestOff) { nearestOff = off; nearest = b; } if (along <= MaxDistance && off < AimTolerance && off < bestOff) { bestOff = off; best = b; } } if (best != null) { label = Describe(best); if (!shownOnce) { shownOnce = true; Log("first bolt: " + Info(best, eyePos, forward) + " -> " + label); } } else if (nearest != null && diagnostics < 8 && Time.realtimeSinceStartup >= nextDiagnostic) { // Hilft bei der Fehlersuche: was liegt in Blickrichtung, warum wird nichts angezeigt? nextDiagnostic = Time.realtimeSinceStartup + 4f; if (nearestOff < 0.3f) { diagnostics++; Log("nearest bolt (not shown): " + Info(nearest, eyePos, forward)); } } } static string Info(Bolt b, Vector3 eyePos, Vector3 forward) { Vector3 to = Center(b) - eyePos; float raw = b.SizeF != null ? b.SizeF.Value : (b.SizeI != null ? b.SizeI.Value : -1); return b.Transform.name + " under " + (b.Transform.parent != null ? b.Transform.parent.name : "-") + ", distance " + Vector3.Dot(to, forward).ToString("0.00") + " m, off the line " + (Vector3.Cross(forward, to).magnitude * 100f).ToString("0") + " cm" + ", angle " + Vector3.Angle(forward, to).ToString("0.0") + "°, BoltSize=" + raw + (b.Renderer != null ? "" : " (no renderer)"); } /// /// Schrauben aus MSCOs gemeinsamer Liste aller FSMs holen (SceneCache) - nicht selbst das ganze Spiel durchsuchen, /// das ließ das Spiel alle paar Sekunden kurz hängen. /// void Scan(Camera cam) { nextScan = Time.realtimeSinceStartup + ScanInterval; bolts.Clear(); foreach (PlayMakerFSM fsm in SceneCache.Fsms(30f)) { if (fsm == null || fsm.FsmName != "Screw") continue; FsmFloat f = fsm.FsmVariables.FindFsmFloat("BoltSize"); FsmInt i = fsm.FsmVariables.FindFsmInt("BoltSize"); if (f == null && i == null) continue; bolts.Add(new Bolt { Transform = fsm.transform, SizeF = f, SizeI = i, Renderer = fsm.GetComponentInChildren() }); } if (!reported) { reported = true; Log("camera " + cam.name + ", " + bolts.Count + " bolts with a size"); } } /// Größe in mm. Das Spiel speichert sie je nach Schraube als 10 oder als 1.0 (= 10 mm). static string Describe(Bolt b) { float value = b.SizeF != null ? b.SizeF.Value : b.SizeI.Value; if (value <= 0f) return "? mm"; // Größe unbekannt - aber die Schraube wurde erkannt float mm = value < 2.5f ? value * 10f : value; return Mathf.RoundToInt(mm) + " mm"; } public override void OnGUI() { if (label == null) return; if (style == null) { style = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter, fontSize = 16, fontStyle = FontStyle.Bold }; style.normal.textColor = Color.white; } var r = new Rect(Screen.width / 2f - 50f, Screen.height / 2f + 22f, 100f, 26f); Color old = GUI.color; GUI.color = new Color(0f, 0f, 0f, 0.6f); GUI.DrawTexture(r, Texture2D.whiteTexture); GUI.color = old; GUI.Label(r, label, style); } } }