Files
MSCO/FsmDump.cs
T
MrMahoandClaude Opus 5.5 29a3697cc8 MSCO 2.10.3: bolt stages and part tightness synced, snap to pivot, admin tighten/loosen
- Every bolt (Screw FSM, stage 0-8) and every part's tightness (BoltCheck/Use "Tightness", counted
  by the tool) is synced as state - before only the bolt turned on other clients, the part stayed
  loose or tight there
- Tightening a part (tool, admin, other player) snaps it exactly onto its pivot (installed parts
  sit at local 0), except screw-in parts and hinged doors/hoods
- Admin VEHICLE tab: TIGHTEN/LOOSEN per part, TIGHTEN ALL/LOOSEN ALL, bolted/loose state
- fsmdump "pivots": installed part offsets

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:16:29 +02:00

143 lines
7.5 KiB
C#

using System;
using System.IO;
using System.Reflection;
using System.Text;
using HutongGames.PlayMaker;
using UnityEngine;
namespace MSCO
{
/// <summary>
/// Diagnose: PlayMaker-FSMs (Zustände, Aktionen mit Werten, Übergänge) und Scharniere von Objekten, deren Pfad einen
/// Suchtext enthält, in MSCO\fsmdump.txt schreiben. Konsole (F7): "fsmdump &lt;text&gt;". Mit der Startoption
/// "-msco-dump text1,text2" automatisch 25 s nach dem Laden des Spiels.
/// </summary>
public static class FsmDump
{
public static string Write(string filter)
{
string[] filters = (filter ?? "").ToLowerInvariant().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (filter != null && filter.Trim().ToLowerInvariant() == "pivots") return WritePivots();
if (filters.Length == 0) return "fsmdump <text>[,text2] - e.g. fsmdump garage";
var sb = new StringBuilder();
int count = 0;
foreach (PlayMakerFSM fsm in Resources.FindObjectsOfTypeAll<PlayMakerFSM>())
{
if (fsm == null || fsm.gameObject == null || fsm.gameObject.hideFlags != HideFlags.None) continue;
string path, lower;
try { path = FsmSync.PathOf(fsm.transform); lower = (path + "|" + fsm.FsmName).ToLowerInvariant(); }
catch { continue; }
if (Array.FindIndex(filters, f => lower.Contains(f.Trim())) < 0) continue;
if (++count > 400) break;
try { Dump(sb, fsm, path); }
catch (Exception e) { sb.Append(" (error: ").Append(e.Message).Append(")").AppendLine(); }
}
foreach (HingeJoint j in Resources.FindObjectsOfTypeAll<HingeJoint>())
{
if (j == null || j.gameObject.hideFlags != HideFlags.None) continue;
string path = FsmSync.PathOf(j.transform);
if (Array.FindIndex(filters, f => path.ToLowerInvariant().Contains(f.Trim())) < 0) continue;
Rigidbody rb = j.GetComponent<Rigidbody>();
try {
sb.Append("HINGE ").Append(path).Append(" -> ").Append(j.connectedBody != null ? FsmSync.PathOf(j.connectedBody.transform) : "-")
.Append(" | active ").Append(j.gameObject.activeInHierarchy).Append(" angle ").Append(j.angle.ToString("0.0"))
.Append(" limits ").Append(j.useLimits).Append(' ').Append(j.limits.min.ToString("0")).Append("..").Append(j.limits.max.ToString("0"))
.Append(" spring ").Append(j.useSpring).Append(' ').Append(j.spring.spring.ToString("0")).Append('/').Append(j.spring.targetPosition.ToString("0"))
.Append(" motor ").Append(j.useMotor)
.Append(" kinematic ").Append(rb != null && rb.isKinematic).Append(" break ").Append(j.breakForce).AppendLine();
} catch (Exception e) { sb.Append(" (error: ").Append(e.Message).Append(")").AppendLine(); }
}
string file = Path.Combine(Bootstrap.Folder ?? ".", "fsmdump.txt");
File.WriteAllText(file, sb.ToString());
return count + " FSM(s) written to " + file;
}
/// <summary>Eingebaute Teile: Lage relativ zu ihrem Halter (pivot_...) - ist sie immer 0?</summary>
static string WritePivots()
{
var sb = new StringBuilder();
int count = 0;
foreach (Transform t in Resources.FindObjectsOfTypeAll<Transform>())
{
if (t == null || t.gameObject.hideFlags != HideFlags.None || !t.name.StartsWith("pivot_")) continue;
foreach (Transform child in t)
{
count++;
sb.Append(FsmSync.PathOf(child)).Append(" | pos ").Append(child.localPosition.ToString("F4"))
.Append(" rot ").Append(child.localEulerAngles.ToString("F1")).Append(" active ").Append(child.gameObject.activeInHierarchy).AppendLine();
}
}
string file = Path.Combine(Bootstrap.Folder ?? ".", "fsmdump.txt");
File.WriteAllText(file, sb.ToString());
return count + " installed part(s) written to " + file;
}
static void Dump(StringBuilder sb, PlayMakerFSM fsm, string path)
{
sb.Append("==== ").Append(path).Append(" | ").Append(fsm.FsmName).Append(" | active ").Append(fsm.gameObject.activeInHierarchy)
.Append(" | state ").Append(fsm.ActiveStateName).AppendLine();
try
{
FsmVariables fv = fsm.FsmVariables;
var vb = new StringBuilder();
foreach (FsmFloat f in fv.FloatVariables) vb.Append(" ").Append(f.Name).Append("=").Append(f.Value.ToString("0.###"));
foreach (FsmInt n in fv.IntVariables) vb.Append(" ").Append(n.Name).Append("=").Append(n.Value);
foreach (FsmBool b in fv.BoolVariables) vb.Append(" ").Append(b.Name).Append("=").Append(b.Value);
foreach (FsmString t in fv.StringVariables) vb.Append(" ").Append(t.Name).Append("=\"").Append(t.Value).Append("\"");
if (vb.Length > 0) sb.Append(" VARS").Append(vb).AppendLine();
}
catch { }
if (fsm.FsmGlobalTransitions != null)
foreach (FsmTransition t in fsm.FsmGlobalTransitions)
sb.Append(" GLOBAL ").Append(t.EventName).Append(" -> ").Append(t.ToState).AppendLine();
if (fsm.FsmStates == null) return;
foreach (FsmState s in fsm.FsmStates)
{
if (s == null) continue;
if (!s.ActionsLoaded) s.LoadActions();
sb.Append(" [").Append(s.Name).Append(']');
if (s.Transitions != null) foreach (FsmTransition t in s.Transitions) if (t != null) sb.Append(" ").Append(t.EventName).Append("->").Append(t.ToState);
sb.AppendLine();
if (s.Actions == null) continue;
foreach (FsmStateAction a in s.Actions)
{
if (a == null) continue;
sb.Append(" ").Append(a.GetType().Name).Append(Fields(a)).AppendLine();
}
}
}
static string Fields(FsmStateAction a)
{
var sb = new StringBuilder();
foreach (FieldInfo f in a.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance))
{
object v;
try { v = f.GetValue(a); } catch { continue; }
string text = Describe(v);
if (text == null) continue;
sb.Append(' ').Append(f.Name).Append('=').Append(text);
}
return sb.ToString();
}
static string Describe(object v)
{
if (v == null) return null;
var named = v as NamedVariable;
if (named != null)
{
string value;
try { value = Convert.ToString(v.GetType().GetProperty("Value").GetValue(v, null)); } catch { value = "?"; }
return (string.IsNullOrEmpty(named.Name) ? "" : "{" + named.Name + "}") + value;
}
var ev = v as FsmEvent;
if (ev != null) return "event:" + ev.Name;
var owner = v as FsmOwnerDefault;
if (owner != null) return owner.OwnerOption == OwnerDefaultOption.UseOwner ? "owner" : Describe(owner.GameObject);
if (v is string || v is bool || v is int || v is float || v.GetType().IsEnum) return Convert.ToString(v);
return null;
}
}
}