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>
This commit is contained in:
2026-10-03 12:16:29 +02:00
co-authored by Claude Opus 5.5
parent 7b4a89d50f
commit 29a3697cc8
6 changed files with 235 additions and 15 deletions
+52 -7
View File
@@ -64,6 +64,8 @@ namespace MSCO
// übertragen und über die Stufen-Logik der Schraube angewendet (Position, Dichtung, Zähler des Teils)
public FsmState BoltDispatch;
public bool InitialSent;
public bool Functional; // Schraube schaltet etwas (Ablassschraube): Welt-Autorität gibt den Stand einmal vor
public bool Exact; // jede Änderung senden (Schrauben-Stufen, Festigkeit der Teile)
}
readonly Dictionary<int, Var> vars = new Dictionary<int, Var>();
@@ -122,14 +124,17 @@ namespace MSCO
ResolveSatsuma(objects);
foreach (Var v in vars.Values)
{
// Teil wird fester (eigenes Werkzeug): genau auf seinen Halter setzen (verrutschte Teile, z.B. nach der Sync)
if (v.Exact && v.Type == 1 && v.Fsm != null && v.Fsm.FsmName == "BoltCheck" && GetFloat(v) > v.LastF + 0.01f)
VehicleAdmin.SnapToPivot(v.Fsm.gameObject);
// Fährt/schiebt ein anderer das Fahrzeug, ist seine Version maßgeblich - nichts senden
int owner = objects.VehicleOwner(OwnerRoot(v.Root));
if (owner != SyncedBody.NoOwner && owner != localId) { if (Changed(v, false)) Remember(v); continue; }
// 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 && v.BoltDispatch == null;
if (v.BoltDispatch != null && worldAuthority && !v.InitialSent)
bool big = owner == SyncedBody.NoOwner && !v.Exact;
if (v.Functional && worldAuthority && !v.InitialSent)
{
// Einmal den eigenen Stand vorgeben, damit alle gleich sind (vorher auseinandergelaufene Schrauben)
v.InitialSent = true;
@@ -198,7 +203,8 @@ namespace MSCO
if (!IsTracked(root)) { Forget(fsm); continue; }
scanned[fsm] = fsm.transform.parent;
Forget(fsm);
if (RegisterFunctionalBolt(fsm, root)) continue;
RegisterTightness(fsm, root);
if (RegisterBolt(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;
@@ -223,7 +229,46 @@ namespace MSCO
/// 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)
/// <summary>
/// Festigkeit eines Teils (BoltCheck bzw. bei aufgeschraubten Teilen wie dem Ölfilter "Use"): zählt das Werkzeug
/// beim Schrauben hoch - beim Mitspieler drehte sich bisher nur die Schraube, das Teil blieb "lose" bzw. "fest".
/// </summary>
void RegisterTightness(PlayMakerFSM fsm, string root)
{
if (fsm.FsmName != "BoltCheck" && fsm.FsmName != "Use") return;
FsmFloat tightness = fsm.FsmVariables.FindFsmFloat("Tightness");
if (tightness == null) return;
if (fsm.FsmName == "Use" && fsm.gameObject.GetComponents<PlayMakerFSM>().Length > 0)
{
bool screwed = false;
foreach (PlayMakerFSM other in fsm.gameObject.GetComponents<PlayMakerFSM>()) if (other.FsmName == "Screw") screwed = true;
if (!screwed) return;
}
string name = FsmSync.PathOf(fsm.transform) + "|" + fsm.FsmName + "|Tightness";
int key = Hash(name);
Var existing;
if (vars.TryGetValue(key, out existing) && existing.Variable == tightness) return;
var v = new Var { Key = key, Name = name, Root = root, Variable = tightness, Type = 1, Fsm = fsm, Exact = true };
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);
}
}
/// <summary>
/// Jede Schraube (FSM "Screw" mit Stufe 0-8): Stufe als Zustand übertragen, angewendet über die Stufen-Logik der
/// Schraube. Schrauben, die etwas schalten (Ablassschraube), gibt die Welt-Autorität nach dem Beitreten einmal vor.
/// </summary>
bool RegisterBolt(PlayMakerFSM fsm, string root)
{
if (fsm.FsmName != "Screw") return false;
FsmInt stage = fsm.FsmVariables.FindFsmInt("Stage");
@@ -243,13 +288,11 @@ namespace MSCO
}
}
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 };
var v = new Var { Key = key, Name = name, Root = root, Variable = stage, Type = 2, Fsm = fsm, BoltDispatch = dispatch, Functional = switches, Exact = true };
Remember(v);
vars[key] = v;
List<int> keys;
@@ -428,7 +471,9 @@ namespace MSCO
case 1:
float f = BitConverter.ToSingle(value, 0);
if (float.IsNaN(f) || float.IsInfinity(f)) return; // nie ungültige Werte übernehmen
bool tighter = v.Exact && v.Fsm != null && v.Fsm.FsmName == "BoltCheck" && f > GetFloat(v) + 0.01f;
SetFloat(v, f);
if (tighter) VehicleAdmin.SnapToPivot(v.Fsm.gameObject); // vom Mitspieler festgezogen: Teil auf den Halter
break;
case 2:
int n = BitConverter.ToInt32(value, 0);