- 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>
143 lines
7.5 KiB
C#
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 <text>". 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;
|
|
}
|
|
}
|
|
}
|