using System; using System.Collections.Generic; using HutongGames.PlayMaker; using UnityEngine; namespace MSCO { /// /// Admin-Menü, Reiter VEHICLE: Füllstände und Teile des Satsuma aus der "Database" des Spiels anzeigen und zum Testen /// reparieren/zerstören. Jedes Teil hat dort ein Objekt mit der FSM "Data" (Installed, Bolted, Damaged, teils Wear); /// die Füllstände stehen in Tank, Ölwanne, Brems- und Kupplungszylinder. Die Werte überträgt VehicleStateSync an alle. /// public static class VehicleAdmin { public sealed class Fluid { public string Label; public FsmFloat Value; public float Max; } public sealed class Part { public string Name, Category; public FsmBool Damaged, Installed, Bolted; public FsmFloat Wear; // Schrauben des eingebauten Teils (BoltCheck zählt die Festigkeit, die Schrauben haben je eine Stufe 0-8) public PlayMakerFSM BoltCheck; public FsmFloat Tightness; public float MaxTightness; public readonly List Bolts = new List(); public bool HasBolts { get { return Bolts.Count > 0 || Tightness != null; } } } static readonly List fluids = new List(); static readonly List parts = new List(); static float scannedAt = -100f; public static List Fluids { get { Refresh(false); return fluids; } } public static List Parts { get { Refresh(false); return parts; } } // Füllstand: Teil in der Database, Variable, Anzeige, Maximum (Tank: aus MaxCapacity) static readonly string[][] FluidDefs = { new[] { "FuelTank", "FuelLevel", "Fuel", "36" }, new[] { "Oilpan", "Oil", "Engine oil", "3" }, new[] { "BrakeMasterCylinder", "BrakeFluidF", "Brake fluid (front)", "1" }, new[] { "BrakeMasterCylinder", "BrakeFluidR", "Brake fluid (rear)", "1" }, new[] { "ClutchMasterCylinder", "ClutchFluid", "Clutch fluid", "0.5" }, new[] { "Radiator", "Water", "Coolant", "6" }, }; /// Database neu einlesen (höchstens alle 5 s, außer erzwungen). public static void Refresh(bool force) { if (!force && Time.realtimeSinceStartup - scannedAt < 5f) return; scannedAt = Time.realtimeSinceStartup; fluids.Clear(); parts.Clear(); if (!MenuHelper.InGame) return; var byName = new Dictionary(); var partsByDb = new Dictionary(); foreach (PlayMakerFSM fsm in Resources.FindObjectsOfTypeAll()) { if (fsm == null || fsm.FsmName != "Data" || fsm.gameObject.hideFlags != HideFlags.None) continue; Transform t = fsm.transform; if (t.root.name != "Database" || t.parent == null || t.parent.parent != t.root) continue; string category = t.parent.name; if (category == "DatabaseOrders" || category == "PlayerDatabase") continue; // Bestellungen/Spieler: keine Fahrzeugteile byName[t.name] = fsm; FsmBool installed = fsm.FsmVariables.FindFsmBool("Installed"); FsmBool damaged = fsm.FsmVariables.FindFsmBool("Damaged"); FsmFloat wear = fsm.FsmVariables.FindFsmFloat("Wear"); var part = new Part { Name = Pretty(t.name), Category = category.Replace("Database", ""), Damaged = damaged, Installed = installed, Wear = wear, Bolted = fsm.FsmVariables.FindFsmBool("Bolted"), }; partsByDb[fsm.gameObject] = part; if (damaged != null || wear != null || part.Bolted != null) parts.Add(part); } FindBolts(partsByDb); parts.Sort((a, b) => string.Compare(a.Category + a.Name, b.Category + b.Name, StringComparison.Ordinal)); foreach (string[] def in FluidDefs) { PlayMakerFSM fsm; if (!byName.TryGetValue(def[0], out fsm)) continue; FsmFloat value = fsm.FsmVariables.FindFsmFloat(def[1]); if (value == null) continue; float max = float.Parse(def[3], System.Globalization.CultureInfo.InvariantCulture); FsmFloat capacity = fsm.FsmVariables.FindFsmFloat("MaxCapacity"); if (def[0] == "FuelTank" && capacity != null && capacity.Value > 0f) max = capacity.Value; fluids.Add(new Fluid { Label = def[2], Value = value, Max = max }); } } /// /// Zu jedem Database-Teil das eingebaute Teil: dessen BoltCheck zeigt mit "db_ThisPart" auf den Database-Eintrag, /// die Schrauben (FSM "Screw") liegen darunter. /// static void FindBolts(Dictionary partsByDb) { foreach (PlayMakerFSM fsm in UnityEngine.Object.FindObjectsOfType()) { if (fsm == null || fsm.FsmName != "BoltCheck") continue; FsmGameObject db = fsm.FsmVariables.FindFsmGameObject("db_ThisPart"); Part part; if (db == null || db.Value == null || !partsByDb.TryGetValue(db.Value, out part)) continue; part.BoltCheck = fsm; part.Tightness = fsm.FsmVariables.FindFsmFloat("Tightness"); part.MaxTightness = ClampMax(fsm); foreach (PlayMakerFSM bolt in fsm.GetComponentsInChildren()) if (bolt != null && bolt.FsmName == "Screw" && bolt.FsmVariables.FindFsmInt("Stage") != null) part.Bolts.Add(bolt); if (part.MaxTightness <= 0f) part.MaxTightness = Mathf.Max(1, part.Bolts.Count) * 8f; } } /// Höchste Festigkeit eines Teils: das FloatClamp im BoltCheck ("Bolts OFF"). static float ClampMax(PlayMakerFSM boltCheck) { try { foreach (FsmState state in boltCheck.FsmStates) { if (!state.ActionsLoaded) state.LoadActions(); if (state.Actions == null) continue; foreach (FsmStateAction a in state.Actions) { if (a == null || a.GetType().Name != "FloatClamp") continue; var max = a.GetType().GetField("maxValue") != null ? a.GetType().GetField("maxValue").GetValue(a) as FsmFloat : null; if (max != null && max.Value > 0f) return max.Value; } } } catch { } return 0f; } /// /// Eingebautes Teil exakt auf seinen Halter (pivot_...) setzen - dort sitzt es im Spiel immer bei Position/Drehung 0. /// Nicht bei Teilen, die selbst eingedreht werden (Zündkerze, Ölfilter: Tiefe = Stufe), und nicht bei Türen/Hauben /// (Scharnier). Gibt true zurück, wenn es verrutscht war. /// public static bool SnapToPivot(GameObject part) { if (part == null) return false; Transform t = part.transform; if (t.parent == null || !t.parent.name.StartsWith("pivot_")) return false; if (part.GetComponent() != null) return false; foreach (PlayMakerFSM fsm in part.GetComponents()) if (fsm.FsmName == "Screw") return false; if (t.localPosition.sqrMagnitude < 0.0004f * 0.0004f && Quaternion.Angle(t.localRotation, Quaternion.identity) < 0.05f) return false; t.localPosition = Vector3.zero; t.localRotation = Quaternion.identity; Rigidbody rb = part.GetComponent(); if (rb != null && !rb.isKinematic) { rb.velocity = Vector3.zero; rb.angularVelocity = Vector3.zero; } return true; } /// Alle Schrauben eines Teils ganz fest (true) oder ganz lose (false) - wie mit dem Werkzeug. public static int SetBolts(Part p, bool tight) { int changed = 0; foreach (PlayMakerFSM bolt in p.Bolts) { if (bolt == null || !bolt.gameObject.activeInHierarchy) continue; FsmInt stage = bolt.FsmVariables.FindFsmInt("Stage"); FsmState dispatch = bolt.Fsm.GetState("Stages 2") ?? bolt.Fsm.GetState("Stages"); if (stage == null || dispatch == null) continue; int target = tight ? 8 : 0; if (stage.Value == target) continue; stage.Value = target; FsmSync.SwitchStateSilently(bolt, dispatch); // Schraube an ihre Stelle, Zähler des Teils (Array) aktualisieren changed++; } if (p.Tightness != null) { float target = tight ? p.MaxTightness : 0f; if (Mathf.Abs(p.Tightness.Value - target) > 0.01f) { p.Tightness.Value = target; changed++; } } if (tight && p.BoltCheck != null && SnapToPivot(p.BoltCheck.gameObject)) changed++; return changed; } public static int SetAllBolts(bool tight) { Refresh(true); int n = 0; foreach (Part p in parts) if (p.HasBolts && (p.Installed == null || p.Installed.Value) && SetBolts(p, tight) > 0) n++; return n; } /// "RadiatorHose1" -> "Radiator Hose 1", "Drumbrake_RR" -> "Drumbrake RR". static string Pretty(string name) { var sb = new System.Text.StringBuilder(); for (int i = 0; i < name.Length; i++) { char c = name[i]; if (c == '_') { sb.Append(' '); continue; } if (i > 0 && ((char.IsUpper(c) && char.IsLower(name[i - 1])) || (char.IsDigit(c) && !char.IsDigit(name[i - 1])))) sb.Append(' '); sb.Append(c); } return sb.ToString(); } public static void SetFluid(Fluid f, bool full) { if (f.Value != null) f.Value.Value = full ? f.Max : 0f; } public static void Repair(Part p) { if (p.Damaged != null) p.Damaged.Value = false; if (p.Wear != null) p.Wear.Value = 100f; } public static void Break(Part p) { if (p.Damaged != null) p.Damaged.Value = true; if (p.Wear != null) p.Wear.Value = 0f; } public static int RepairAll() { Refresh(true); int n = 0; foreach (Part p in parts) { if (p.Installed != null && !p.Installed.Value) continue; bool needs = (p.Damaged != null && p.Damaged.Value) || (p.Wear != null && p.Wear.Value < 100f); if (!needs) continue; Repair(p); n++; } return n; } } }