- Passenger: auto ride-along only when the vehicle moves (speed from its motion, also for kinematic remote cars) - no more getting stuck while working in the engine bay or re-captured - Bolts with a function (drain plug: stage 0-5 = oil leak on) are synced as stage and applied through the bolt's own stage logic; world authority sends its state once after joining. Fixes endless oil leaking when players had the drain plug in different states - Admin menu VEHICLE tab: fluids with FILL/EMPTY, all Database parts with wear/damage, REPAIR/BREAK per part, REPAIR ALL, filter - Server profile: info and players as two equal cards, no wrapping - fsmdump also lists FSM variables Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
122 lines
6.4 KiB
C#
122 lines
6.4 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 (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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|