MSCO 2.10.2: passenger only while moving, drain plug sync, admin VEHICLE tab, profile layout

- 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>
This commit is contained in:
2026-10-03 12:02:21 +02:00
co-authored by Claude Opus 5.5
parent adc6eff9e8
commit 7b4a89d50f
11 changed files with 462 additions and 41 deletions
+75 -3
View File
@@ -60,6 +60,10 @@ namespace MSCO
public float LastF;
public int LastI;
public bool LastB;
// Schraube mit Funktion (z.B. Ablassschraube der Ölwanne: offen = Öl läuft aus): die Stufe wird als Zustand
// übertragen und über die Stufen-Logik der Schraube angewendet (Position, Dichtung, Zähler des Teils)
public FsmState BoltDispatch;
public bool InitialSent;
}
readonly Dictionary<int, Var> vars = new Dictionary<int, Var>();
@@ -103,7 +107,7 @@ namespace MSCO
nextScan = -1f; // erster Suchlauf verteilt, siehe Update
}
public void Update(ObjectSync objects, int localId, Action<MsgType, byte[]> send)
public void Update(ObjectSync objects, int localId, Action<MsgType, byte[]> send, bool worldAuthority = false)
{
if (nextScan < 0f) nextScan = Time.time + 3.1f; // nicht alle Sync-Teile im selben Frame suchen lassen
if (Time.time >= nextScan)
@@ -124,7 +128,15 @@ namespace MSCO
// Eigenes Fahrzeug: jede Änderung. Freies Fahrzeug: nur deutliche Sprünge (Öl nachfüllen, tanken ...) -
// langsames Abdriften (z.B. Abkühlen) rechnet jeder selbst, sonst überschreiben sich alle gegenseitig
bool big = owner == SyncedBody.NoOwner;
bool big = owner == SyncedBody.NoOwner && v.BoltDispatch == null;
if (v.BoltDispatch != null && worldAuthority && !v.InitialSent)
{
// Einmal den eigenen Stand vorgeben, damit alle gleich sind (vorher auseinandergelaufene Schrauben)
v.InitialSent = true;
Remember(v);
outgoing.Add(v);
continue;
}
if (!Changed(v, big)) continue;
Remember(v);
outgoing.Add(v);
@@ -186,6 +198,7 @@ namespace MSCO
if (!IsTracked(root)) { Forget(fsm); continue; }
scanned[fsm] = fsm.transform.parent;
Forget(fsm);
if (RegisterFunctionalBolt(fsm, root)) continue;
// FSMs, die Größen/Positionen setzen (z.B. Füllstands-Anzeigen), NICHT anfassen: ein übertragener Wert
// kann dort eine Größe 0 ergeben - Unity stürzt dann in der Physik ab ("cleaning the mesh failed")
if (TouchesTransform(fsm)) continue;
@@ -205,6 +218,54 @@ namespace MSCO
if (vars.Count != before) MscoLog.Info("[MSCO] Vehicle state: " + vars.Count + " values synced");
}
/// <summary>
/// Schrauben, deren Stufen etwas ein-/ausschalten (ActivateGameObject - z.B. die Ablassschraube: Stufe 0-5 = das
/// Leck "Oil" ist an). Sonst laufen Schrauben über FsmSync (Ereignisse): ging eins verloren, war die Ablassschraube
/// bei einem offen und beim anderen zu - bei ihm lief dann endlos Öl aus (der Ölstand kam ja ständig nach).
/// </summary>
bool RegisterFunctionalBolt(PlayMakerFSM fsm, string root)
{
if (fsm.FsmName != "Screw") return false;
FsmInt stage = fsm.FsmVariables.FindFsmInt("Stage");
if (stage == null) return false;
FsmState dispatch = fsm.Fsm.GetState("Stages 2") ?? fsm.Fsm.GetState("Stages");
if (dispatch == null) return false;
bool switches = false;
try
{
foreach (FsmState state in fsm.FsmStates)
{
if (!state.ActionsLoaded) state.LoadActions();
if (state.Actions == null) continue;
foreach (FsmStateAction a in state.Actions)
if (a != null && a.GetType().Name == "ActivateGameObject") { switches = true; break; }
if (switches) break;
}
}
catch { return false; }
if (!switches) return false;
string name = FsmSync.PathOf(fsm.transform) + "|" + fsm.FsmName + "|Stage";
int key = Hash(name);
Var existing;
if (vars.TryGetValue(key, out existing) && existing.Variable == stage) return true;
var v = new Var { Key = key, Name = name, Root = root, Variable = stage, Type = 2, Fsm = fsm, BoltDispatch = dispatch };
Remember(v);
vars[key] = v;
List<int> keys;
if (!keysByFsm.TryGetValue(fsm, out keys)) keysByFsm[fsm] = keys = new List<int>();
keys.Add(key);
byte[] stored;
if (pending.TryGetValue(key, out stored))
{
pending.Remove(key);
byte[] value = new byte[stored.Length - 1];
Array.Copy(stored, 1, value, 0, value.Length);
Apply(v, stored[0], value);
}
return true;
}
static readonly string[] TransformActionWords = { "Scale", "Position", "Rotate", "Rotation", "Translate", "Parent" };
static bool TouchesTransform(PlayMakerFSM fsm)
@@ -369,7 +430,18 @@ namespace MSCO
if (float.IsNaN(f) || float.IsInfinity(f)) return; // nie ungültige Werte übernehmen
SetFloat(v, f);
break;
case 2: ((FsmInt)v.Variable).Value = BitConverter.ToInt32(value, 0); break;
case 2:
int n = BitConverter.ToInt32(value, 0);
if (v.BoltDispatch != null)
{
if (n < 0 || n > 8 || ((FsmInt)v.Variable).Value == n) { Remember(v); return; }
((FsmInt)v.Variable).Value = n;
if (v.Fsm != null && v.Fsm.gameObject.activeInHierarchy) FsmSync.SwitchStateSilently(v.Fsm, v.BoltDispatch);
if (FsmSync.Debug) MscoLog.Info("[MSCO] << BOLT " + v.Name + " -> " + n);
break;
}
((FsmInt)v.Variable).Value = n;
break;
default: ((FsmBool)v.Variable).Value = value[0] != 0; break;
}
Remember(v); // nicht als eigene Änderung zurückschicken