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
+13
View File
@@ -1,5 +1,18 @@
# Changelog # Changelog
## 2.10.3
**Schrauben synchron**
- Jede Schraube wird jetzt als Stufe (0–8) übertragen und die Festigkeit jedes Teils dazu: vorher drehte sich beim
Mitspieler nur die Schraube, sein Teil blieb „lose“ bzw. „fest“ (die Festigkeit zählt im Spiel das Werkzeug hoch) –
Teile ließen sich dann nicht abbauen oder fielen ab
- Wird ein Teil festgezogen (Werkzeug, Admin oder vom Mitspieler), springt es exakt auf seinen Halter – verrutschte
Teile sitzen wieder richtig (nicht bei eingedrehten Teilen wie Zündkerzen/Ölfilter und nicht bei Türen/Hauben)
**Admin-Menü, Reiter VEHICLE**
- Je Teil **TIGHTEN** / **LOOSEN** (alle Schrauben ganz fest bzw. ganz lose, wie mit dem Werkzeug) und oben
**TIGHTEN ALL** / **LOOSEN ALL**; Anzeige „bolted“/„loose“; das Fenster ist auf diesem Reiter breiter
## 2.10.2 ## 2.10.2
- **Beifahrer**: automatisch festgehalten wird man nur noch, wenn das Fahrzeug fährt (egal wer fährt) – am stehenden - **Beifahrer**: automatisch festgehalten wird man nur noch, wenn das Fahrzeug fährt (egal wer fährt) – am stehenden
+21
View File
@@ -17,6 +17,7 @@ namespace MSCO
public static string Write(string filter) public static string Write(string filter)
{ {
string[] filters = (filter ?? "").ToLowerInvariant().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 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"; if (filters.Length == 0) return "fsmdump <text>[,text2] - e.g. fsmdump garage";
var sb = new StringBuilder(); var sb = new StringBuilder();
int count = 0; int count = 0;
@@ -51,6 +52,26 @@ namespace MSCO
return count + " FSM(s) written to " + file; 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) static void Dump(StringBuilder sb, PlayMakerFSM fsm, string path)
{ {
sb.Append("==== ").Append(path).Append(" | ").Append(fsm.FsmName).Append(" | active ").Append(fsm.gameObject.activeInHierarchy) sb.Append("==== ").Append(path).Append(" | ").Append(fsm.FsmName).Append(" | active ").Append(fsm.gameObject.activeInHierarchy)
+1 -1
View File
@@ -11,7 +11,7 @@ namespace MSCO
/// Version der Mod. Kleine Updates: nur die dritte Stelle erhöhen (2.9.1, 2.9.2 ...) - sie bleiben mit /// Version der Mod. Kleine Updates: nur die dritte Stelle erhöhen (2.9.1, 2.9.2 ...) - sie bleiben mit
/// Servern und Mitspielern derselben ersten zwei Stellen kompatibel. /// Servern und Mitspielern derselben ersten zwei Stellen kompatibel.
/// </summary> /// </summary>
public const string ModVersion = "2.10.2"; public const string ModVersion = "2.10.3";
/// <summary> /// <summary>
/// Netzwerk-Version (die ersten beiden Stellen). Nur erhöhen, wenn sich das Protokoll inkompatibel ändert - dann /// Netzwerk-Version (die ersten beiden Stellen). Nur erhöhen, wenn sich das Protokoll inkompatibel ändert - dann
/// auch GameServer.ModVersion im Server. /// auch GameServer.ModVersion im Server.
+34 -3
View File
@@ -270,7 +270,7 @@ namespace MSCO
void DrawAdmin(MultiplayerManager mp) void DrawAdmin(MultiplayerManager mp)
{ {
float width = Mathf.Min(720, Screen.width - 40), height = Mathf.Min(580, Screen.height - 40); float width = Mathf.Min(adminTab == 2 ? 940 : 720, Screen.width - 40), height = Mathf.Min(adminTab == 2 ? 680 : 580, Screen.height - 40);
var rect = new Rect((Screen.width - width) / 2f, (Screen.height - height) / 2f, width, height); var rect = new Rect((Screen.width - width) / 2f, (Screen.height - height) / 2f, width, height);
UiTheme.DrawShadow(rect); UiTheme.DrawShadow(rect);
GUI.Box(rect, GUIContent.none, panel); GUI.Box(rect, GUIContent.none, panel);
@@ -338,7 +338,21 @@ namespace MSCO
if (n > 0) mp.VehicleCheat("repaired the Satsuma (" + n + " parts)"); if (n > 0) mp.VehicleCheat("repaired the Satsuma (" + n + " parts)");
} }
GUILayout.Space(6); GUILayout.Space(6);
if (GUILayout.Button("REFRESH", secondary, GUILayout.Height(30), GUILayout.Width(100))) VehicleAdmin.Refresh(true); if (GUILayout.Button("TIGHTEN ALL", secondary, GUILayout.Height(30), GUILayout.Width(110)))
{
int n = VehicleAdmin.SetAllBolts(true);
Log(n + " part(s) tightened");
if (n > 0) mp.VehicleCheat("tightened all bolts (" + n + " parts)");
}
GUILayout.Space(6);
if (GUILayout.Button("LOOSEN ALL", danger, GUILayout.Height(30), GUILayout.Width(106)))
{
int n = VehicleAdmin.SetAllBolts(false);
Log(n + " part(s) loosened");
if (n > 0) mp.VehicleCheat("loosened all bolts (" + n + " parts, testing)");
}
GUILayout.Space(6);
if (GUILayout.Button("REFRESH", secondary, GUILayout.Height(30), GUILayout.Width(90))) VehicleAdmin.Refresh(true);
GUILayout.Space(12); GUILayout.Space(12);
GUILayout.Label("Filter", caption, GUILayout.Width(44)); GUILayout.Label("Filter", caption, GUILayout.Width(44));
partFilter = GUILayout.TextField(partFilter ?? "", 30, field, GUILayout.Height(30)); partFilter = GUILayout.TextField(partFilter ?? "", 30, field, GUILayout.Height(30));
@@ -397,7 +411,24 @@ namespace MSCO
GUI.DrawTexture(back, texBarBack); GUI.DrawTexture(back, texBarBack);
GUI.DrawTexture(new Rect(back.x, back.y, Mathf.Max(2f, back.width * frac), back.height), frac < 0.3f ? texBarLow : texBarFill); GUI.DrawTexture(new Rect(back.x, back.y, Mathf.Max(2f, back.width * frac), back.height), frac < 0.3f ? texBarLow : texBarFill);
} }
GUILayout.Label(damaged ? "DAMAGED" : p.Wear != null ? p.Wear.Value.ToString("0") + " %" : "OK", damaged ? danger : small, GUILayout.Width(84)); string boltText = p.Tightness != null ? (p.Bolted != null && p.Bolted.Value ? " bolted" : " loose") : "";
GUILayout.Label((damaged ? "DAMAGED" : p.Wear != null ? p.Wear.Value.ToString("0") + " %" : "OK") + boltText, damaged ? danger : small, GUILayout.Width(120));
if (p.HasBolts && installed)
{
if (GUILayout.Button("TIGHTEN", secondary, GUILayout.Height(26), GUILayout.Width(80)))
{
VehicleAdmin.SetBolts(p, true);
mp.VehicleCheat("tightened " + p.Name);
}
GUILayout.Space(4);
if (GUILayout.Button("LOOSEN", secondary, GUILayout.Height(26), GUILayout.Width(76)))
{
VehicleAdmin.SetBolts(p, false);
mp.VehicleCheat("loosened " + p.Name);
}
GUILayout.Space(4);
}
else GUILayout.Space(164);
if (GUILayout.Button("REPAIR", secondary, GUILayout.Height(26), GUILayout.Width(76))) if (GUILayout.Button("REPAIR", secondary, GUILayout.Height(26), GUILayout.Width(76)))
{ {
VehicleAdmin.Repair(p); VehicleAdmin.Repair(p);
+114 -4
View File
@@ -22,8 +22,14 @@ namespace MSCO
public sealed class Part public sealed class Part
{ {
public string Name, Category; public string Name, Category;
public FsmBool Damaged, Installed; public FsmBool Damaged, Installed, Bolted;
public FsmFloat Wear; public FsmFloat Wear;
// Schrauben des eingebauten Teils (BoltCheck zählt die Festigkeit, die Schrauben haben je eine Stufe 0-8)
public PlayMakerFSM BoltCheck;
public FsmFloat Tightness;
public float MaxTightness;
public readonly List<PlayMakerFSM> Bolts = new List<PlayMakerFSM>();
public bool HasBolts { get { return Bolts.Count > 0 || Tightness != null; } }
} }
static readonly List<Fluid> fluids = new List<Fluid>(); static readonly List<Fluid> fluids = new List<Fluid>();
@@ -53,6 +59,7 @@ namespace MSCO
parts.Clear(); parts.Clear();
if (!MenuHelper.InGame) return; if (!MenuHelper.InGame) return;
var byName = new Dictionary<string, PlayMakerFSM>(); var byName = new Dictionary<string, PlayMakerFSM>();
var partsByDb = new Dictionary<GameObject, Part>();
foreach (PlayMakerFSM fsm in Resources.FindObjectsOfTypeAll<PlayMakerFSM>()) foreach (PlayMakerFSM fsm in Resources.FindObjectsOfTypeAll<PlayMakerFSM>())
{ {
if (fsm == null || fsm.FsmName != "Data" || fsm.gameObject.hideFlags != HideFlags.None) continue; if (fsm == null || fsm.FsmName != "Data" || fsm.gameObject.hideFlags != HideFlags.None) continue;
@@ -65,14 +72,17 @@ namespace MSCO
FsmBool installed = fsm.FsmVariables.FindFsmBool("Installed"); FsmBool installed = fsm.FsmVariables.FindFsmBool("Installed");
FsmBool damaged = fsm.FsmVariables.FindFsmBool("Damaged"); FsmBool damaged = fsm.FsmVariables.FindFsmBool("Damaged");
FsmFloat wear = fsm.FsmVariables.FindFsmFloat("Wear"); FsmFloat wear = fsm.FsmVariables.FindFsmFloat("Wear");
if (damaged == null && wear == null) continue; var part = new Part
parts.Add(new Part
{ {
Name = Pretty(t.name), Name = Pretty(t.name),
Category = category.Replace("Database", ""), Category = category.Replace("Database", ""),
Damaged = damaged, Installed = installed, Wear = wear, Damaged = damaged, Installed = installed, Wear = wear,
}); Bolted = fsm.FsmVariables.FindFsmBool("Bolted"),
};
partsByDb[fsm.gameObject] = part;
if (damaged != null || wear != null || part.Bolted != null) parts.Add(part);
} }
FindBolts(partsByDb);
parts.Sort((a, b) => string.Compare(a.Category + a.Name, b.Category + b.Name, StringComparison.Ordinal)); parts.Sort((a, b) => string.Compare(a.Category + a.Name, b.Category + b.Name, StringComparison.Ordinal));
foreach (string[] def in FluidDefs) foreach (string[] def in FluidDefs)
@@ -88,6 +98,106 @@ namespace MSCO
} }
} }
/// <summary>
/// Zu jedem Database-Teil das eingebaute Teil: dessen BoltCheck zeigt mit "db_ThisPart" auf den Database-Eintrag,
/// die Schrauben (FSM "Screw") liegen darunter.
/// </summary>
static void FindBolts(Dictionary<GameObject, Part> partsByDb)
{
foreach (PlayMakerFSM fsm in UnityEngine.Object.FindObjectsOfType<PlayMakerFSM>())
{
if (fsm == null || fsm.FsmName != "BoltCheck") continue;
FsmGameObject db = fsm.FsmVariables.FindFsmGameObject("db_ThisPart");
Part part;
if (db == null || db.Value == null || !partsByDb.TryGetValue(db.Value, out part)) continue;
part.BoltCheck = fsm;
part.Tightness = fsm.FsmVariables.FindFsmFloat("Tightness");
part.MaxTightness = ClampMax(fsm);
foreach (PlayMakerFSM bolt in fsm.GetComponentsInChildren<PlayMakerFSM>())
if (bolt != null && bolt.FsmName == "Screw" && bolt.FsmVariables.FindFsmInt("Stage") != null) part.Bolts.Add(bolt);
if (part.MaxTightness <= 0f) part.MaxTightness = Mathf.Max(1, part.Bolts.Count) * 8f;
}
}
/// <summary>Höchste Festigkeit eines Teils: das FloatClamp im BoltCheck ("Bolts OFF").</summary>
static float ClampMax(PlayMakerFSM boltCheck)
{
try
{
foreach (FsmState state in boltCheck.FsmStates)
{
if (!state.ActionsLoaded) state.LoadActions();
if (state.Actions == null) continue;
foreach (FsmStateAction a in state.Actions)
{
if (a == null || a.GetType().Name != "FloatClamp") continue;
var max = a.GetType().GetField("maxValue") != null ? a.GetType().GetField("maxValue").GetValue(a) as FsmFloat : null;
if (max != null && max.Value > 0f) return max.Value;
}
}
}
catch { }
return 0f;
}
/// <summary>
/// Eingebautes Teil exakt auf seinen Halter (pivot_...) setzen - dort sitzt es im Spiel immer bei Position/Drehung 0.
/// Nicht bei Teilen, die selbst eingedreht werden (Zündkerze, Ölfilter: Tiefe = Stufe), und nicht bei Türen/Hauben
/// (Scharnier). Gibt true zurück, wenn es verrutscht war.
/// </summary>
public static bool SnapToPivot(GameObject part)
{
if (part == null) return false;
Transform t = part.transform;
if (t.parent == null || !t.parent.name.StartsWith("pivot_")) return false;
if (part.GetComponent<HingeJoint>() != null) return false;
foreach (PlayMakerFSM fsm in part.GetComponents<PlayMakerFSM>()) if (fsm.FsmName == "Screw") return false;
if (t.localPosition.sqrMagnitude < 0.0004f * 0.0004f && Quaternion.Angle(t.localRotation, Quaternion.identity) < 0.05f) return false;
t.localPosition = Vector3.zero;
t.localRotation = Quaternion.identity;
Rigidbody rb = part.GetComponent<Rigidbody>();
if (rb != null && !rb.isKinematic)
{
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
}
return true;
}
/// <summary>Alle Schrauben eines Teils ganz fest (true) oder ganz lose (false) - wie mit dem Werkzeug.</summary>
public static int SetBolts(Part p, bool tight)
{
int changed = 0;
foreach (PlayMakerFSM bolt in p.Bolts)
{
if (bolt == null || !bolt.gameObject.activeInHierarchy) continue;
FsmInt stage = bolt.FsmVariables.FindFsmInt("Stage");
FsmState dispatch = bolt.Fsm.GetState("Stages 2") ?? bolt.Fsm.GetState("Stages");
if (stage == null || dispatch == null) continue;
int target = tight ? 8 : 0;
if (stage.Value == target) continue;
stage.Value = target;
FsmSync.SwitchStateSilently(bolt, dispatch); // Schraube an ihre Stelle, Zähler des Teils (Array) aktualisieren
changed++;
}
if (p.Tightness != null)
{
float target = tight ? p.MaxTightness : 0f;
if (Mathf.Abs(p.Tightness.Value - target) > 0.01f) { p.Tightness.Value = target; changed++; }
}
if (tight && p.BoltCheck != null && SnapToPivot(p.BoltCheck.gameObject)) changed++;
return changed;
}
public static int SetAllBolts(bool tight)
{
Refresh(true);
int n = 0;
foreach (Part p in parts)
if (p.HasBolts && (p.Installed == null || p.Installed.Value) && SetBolts(p, tight) > 0) n++;
return n;
}
/// <summary>"RadiatorHose1" -> "Radiator Hose 1", "Drumbrake_RR" -> "Drumbrake RR".</summary> /// <summary>"RadiatorHose1" -> "Radiator Hose 1", "Drumbrake_RR" -> "Drumbrake RR".</summary>
static string Pretty(string name) static string Pretty(string name)
{ {
+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) // übertragen und über die Stufen-Logik der Schraube angewendet (Position, Dichtung, Zähler des Teils)
public FsmState BoltDispatch; public FsmState BoltDispatch;
public bool InitialSent; 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>(); readonly Dictionary<int, Var> vars = new Dictionary<int, Var>();
@@ -122,14 +124,17 @@ namespace MSCO
ResolveSatsuma(objects); ResolveSatsuma(objects);
foreach (Var v in vars.Values) 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 // Fährt/schiebt ein anderer das Fahrzeug, ist seine Version maßgeblich - nichts senden
int owner = objects.VehicleOwner(OwnerRoot(v.Root)); int owner = objects.VehicleOwner(OwnerRoot(v.Root));
if (owner != SyncedBody.NoOwner && owner != localId) { if (Changed(v, false)) Remember(v); continue; } 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 ...) - // 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 // langsames Abdriften (z.B. Abkühlen) rechnet jeder selbst, sonst überschreiben sich alle gegenseitig
bool big = owner == SyncedBody.NoOwner && v.BoltDispatch == null; bool big = owner == SyncedBody.NoOwner && !v.Exact;
if (v.BoltDispatch != null && worldAuthority && !v.InitialSent) if (v.Functional && worldAuthority && !v.InitialSent)
{ {
// Einmal den eigenen Stand vorgeben, damit alle gleich sind (vorher auseinandergelaufene Schrauben) // Einmal den eigenen Stand vorgeben, damit alle gleich sind (vorher auseinandergelaufene Schrauben)
v.InitialSent = true; v.InitialSent = true;
@@ -198,7 +203,8 @@ namespace MSCO
if (!IsTracked(root)) { Forget(fsm); continue; } if (!IsTracked(root)) { Forget(fsm); continue; }
scanned[fsm] = fsm.transform.parent; scanned[fsm] = fsm.transform.parent;
Forget(fsm); 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 // 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") // kann dort eine Größe 0 ergeben - Unity stürzt dann in der Physik ab ("cleaning the mesh failed")
if (TouchesTransform(fsm)) continue; 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 /// 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). /// bei einem offen und beim anderen zu - bei ihm lief dann endlos Öl aus (der Ölstand kam ja ständig nach).
/// </summary> /// </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; if (fsm.FsmName != "Screw") return false;
FsmInt stage = fsm.FsmVariables.FindFsmInt("Stage"); FsmInt stage = fsm.FsmVariables.FindFsmInt("Stage");
@@ -243,13 +288,11 @@ namespace MSCO
} }
} }
catch { return false; } catch { return false; }
if (!switches) return false;
string name = FsmSync.PathOf(fsm.transform) + "|" + fsm.FsmName + "|Stage"; string name = FsmSync.PathOf(fsm.transform) + "|" + fsm.FsmName + "|Stage";
int key = Hash(name); int key = Hash(name);
Var existing; Var existing;
if (vars.TryGetValue(key, out existing) && existing.Variable == stage) return true; 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); Remember(v);
vars[key] = v; vars[key] = v;
List<int> keys; List<int> keys;
@@ -428,7 +471,9 @@ namespace MSCO
case 1: case 1:
float f = BitConverter.ToSingle(value, 0); float f = BitConverter.ToSingle(value, 0);
if (float.IsNaN(f) || float.IsInfinity(f)) return; // nie ungültige Werte übernehmen 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); SetFloat(v, f);
if (tighter) VehicleAdmin.SnapToPivot(v.Fsm.gameObject); // vom Mitspieler festgezogen: Teil auf den Halter
break; break;
case 2: case 2:
int n = BitConverter.ToInt32(value, 0); int n = BitConverter.ToInt32(value, 0);