Satsuma: sync part state from CARPARTS/Database, paint, late-installed parts, more controls
- VehicleStateSync also tracks the CARPARTS and Database roots (wear, damaged parts, tire wear, oilpan oil, battery charge, carb/ignition settings, suspension, odometer); the Satsuma's driver is authoritative for them - Paint colors (FsmColor) as four float channels (compatible with old servers) and applied to the part's material - FSMs are re-examined when a part is installed/removed (path changes); before, anything not on the car at the first scan was never synced - ControlSync: handbrake, sun visors, seat recline, seat belt, CD player, interior light; pending states for controls not found yet - Late joiners: job/shop values and switch states from the server snapshot; P2P host resends all switch states every 15 s Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,18 @@
|
|||||||
- Geht jetzt auch, **wenn noch niemand fährt** (Sitzposition kommt aus dem Fahrersitz des Spiels); fährt danach jemand
|
- Geht jetzt auch, **wenn noch niemand fährt** (Sitzposition kommt aus dem Fahrersitz des Spiels); fährt danach jemand
|
||||||
los, fährt man mit. Setzt man sich selbst ans Steuer, endet der Beifahrer-Modus
|
los, fährt man mit. Setzt man sich selbst ans Steuer, endet der Beifahrer-Modus
|
||||||
|
|
||||||
|
**Satsuma vollständiger synchron**
|
||||||
|
- Zustand aller Satsuma-Teile wird jetzt übertragen - auch der Teile, die das Spiel außerhalb des Autos verwaltet
|
||||||
|
(CARPARTS und die Teile-Datenbank): Verschleiß (Kolben, Kupplung, Getriebe, Lichtmaschine ...), beschädigte Teile
|
||||||
|
(z.B. Kolben/Zylinderkopf nach Überhitzen), Reifen-Verschleiß, Öl im Ölwannen-Teil (Menge, Verschmutzung), Batterie-
|
||||||
|
Ladung, Vergaser-/Zündeinstellungen, Fahrwerk, Kilometerstand. Maßgeblich ist, wer den Satsuma gerade fährt
|
||||||
|
- **Lack**: Lackfarben werden übertragen und sofort auf das Teil gelegt
|
||||||
|
- Teile, die erst während des Spiels angebaut werden (Radio, Sitze, Armaturen ...), werden jetzt auch erfasst - vorher
|
||||||
|
wurden sie einmal als „nicht am Auto“ gemerkt und nie wieder angesehen
|
||||||
|
- Mehr Bedienelemente: Handbremse, Sonnenblenden, Sitzlehne, Gurt, CD-Spieler, Innenlicht
|
||||||
|
- **Später Beitretende** bekommen Schalter (Licht an, Motor läuft ...) und Jobs/Laden vom Server (ab Server 2.9.2);
|
||||||
|
im P2P gleicht der Host die Schalter alle 15 s ab
|
||||||
|
|
||||||
**Satsuma zerfällt nicht mehr beim gemeinsamen Fahren**
|
**Satsuma zerfällt nicht mehr beim gemeinsamen Fahren**
|
||||||
- Hing das Spiel des Fahrers kurz (z.B. beim Speichern für den Server), gab der Mitfahrer das Auto nach 3 s frei: es
|
- Hing das Spiel des Fahrers kurz (z.B. beim Speichern für den Server), gab der Mitfahrer das Auto nach 3 s frei: es
|
||||||
blieb bei ihm schlagartig stehen, die Teile wollten mit Tempo weiter und rissen ab - und das „Teil abgefallen“ ging an
|
blieb bei ihm schlagartig stehen, die Teile wollten mit Tempo weiter und rissen ab - und das „Teil abgefallen“ ging an
|
||||||
|
|||||||
+52
-11
@@ -21,7 +21,9 @@ namespace MSCO
|
|||||||
static readonly string[] Keywords =
|
static readonly string[] Keywords =
|
||||||
{
|
{
|
||||||
"ignition", "starter", "engine", "light", "choke", "wiper", "hazard", "indicator", "turnsignal", "blinker",
|
"ignition", "starter", "engine", "light", "choke", "wiper", "hazard", "indicator", "turnsignal", "blinker",
|
||||||
"horn", "heater", "fan", "radio", "key", "switch", "knob", "button"
|
"horn", "heater", "fan", "radio", "key", "switch", "knob", "button",
|
||||||
|
// Handbremse, Sonnenblenden, Sitzlehne, Gurt, CD-Spieler, Innenlicht, Tankdeckel
|
||||||
|
"handbrake", "sunvisor", "seattop", "seatbelt", "cdplayer", "interiorlight", "opencap"
|
||||||
};
|
};
|
||||||
// Montage/Speichern/Schrauben - läuft über den Objekt- bzw. FSM-Sync, hier nicht anfassen
|
// Montage/Speichern/Schrauben - läuft über den Objekt- bzw. FSM-Sync, hier nicht anfassen
|
||||||
static readonly string[] Excluded =
|
static readonly string[] Excluded =
|
||||||
@@ -51,6 +53,8 @@ namespace MSCO
|
|||||||
readonly Dictionary<int, Control> controls = new Dictionary<int, Control>();
|
readonly Dictionary<int, Control> controls = new Dictionary<int, Control>();
|
||||||
readonly HashSet<PlayMakerFSM> scanned = new HashSet<PlayMakerFSM>();
|
readonly HashSet<PlayMakerFSM> scanned = new HashSet<PlayMakerFSM>();
|
||||||
readonly List<Control> outgoing = new List<Control>();
|
readonly List<Control> outgoing = new List<Control>();
|
||||||
|
// Zustände für Schalter, die wir noch nicht kennen (Teil noch nicht angebaut/aktiv, Server-Snapshot vor dem Suchlauf)
|
||||||
|
readonly Dictionary<int, string> pending = new Dictionary<int, string>();
|
||||||
float nextScan, nextCheck;
|
float nextScan, nextCheck;
|
||||||
|
|
||||||
public int Count { get { return controls.Count; } }
|
public int Count { get { return controls.Count; } }
|
||||||
@@ -60,6 +64,7 @@ namespace MSCO
|
|||||||
{
|
{
|
||||||
controls.Clear();
|
controls.Clear();
|
||||||
scanned.Clear();
|
scanned.Clear();
|
||||||
|
pending.Clear();
|
||||||
nextScan = -1f; // erster Suchlauf verteilt, siehe Update
|
nextScan = -1f; // erster Suchlauf verteilt, siehe Update
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,32 +120,68 @@ namespace MSCO
|
|||||||
int key = r.ReadInt32();
|
int key = r.ReadInt32();
|
||||||
string state = r.ReadString();
|
string state = r.ReadString();
|
||||||
Control c;
|
Control c;
|
||||||
if (!controls.TryGetValue(key, out c) || c.Fsm == null) { nextScan = 0f; continue; }
|
if (!controls.TryGetValue(key, out c) || c.Fsm == null) { pending[key] = state; nextScan = 0f; continue; }
|
||||||
if (objects.VehicleOwner(c.Root) == localId) continue; // ich fahre: meine Schalter gelten
|
if (objects.VehicleOwner(c.Root) == localId) continue; // ich fahre: meine Schalter gelten
|
||||||
if (c.Fsm.ActiveStateName == state) { c.LastState = state; continue; }
|
SwitchTo(c, state);
|
||||||
FsmState target = c.Fsm.Fsm.GetState(state);
|
|
||||||
if (target == null) continue;
|
|
||||||
c.LastState = state; // nicht als eigene Änderung zurückschicken
|
|
||||||
FsmSync.SwitchStateSilently(c.Fsm, target);
|
|
||||||
c.LastState = c.Fsm.ActiveStateName; // FSM kann sofort weiterschalten (z.B. "Start" -> "Running")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SwitchTo(Control c, string state)
|
||||||
|
{
|
||||||
|
if (c.Fsm.ActiveStateName == state) { c.LastState = state; return; }
|
||||||
|
FsmState target = c.Fsm.Fsm.GetState(state);
|
||||||
|
if (target == null) return;
|
||||||
|
c.LastState = state; // nicht als eigene Änderung zurückschicken
|
||||||
|
FsmSync.SwitchStateSilently(c.Fsm, target);
|
||||||
|
c.LastState = c.Fsm.ActiveStateName; // FSM kann sofort weiterschalten (z.B. "Start" -> "Running")
|
||||||
|
}
|
||||||
|
|
||||||
void Scan()
|
void Scan()
|
||||||
{
|
{
|
||||||
int before = controls.Count;
|
int before = controls.Count;
|
||||||
foreach (PlayMakerFSM fsm in SceneCache.Fsms(20f))
|
foreach (PlayMakerFSM fsm in SceneCache.Fsms(20f))
|
||||||
{
|
{
|
||||||
if (fsm == null || !scanned.Add(fsm)) continue;
|
if (fsm == null || scanned.Contains(fsm)) continue;
|
||||||
string root = fsm.transform.root.name;
|
string root = fsm.transform.root.name;
|
||||||
if (!IsVehicle(root) || !Wanted(fsm.gameObject.name, fsm.FsmName)) continue;
|
// Noch nicht am Fahrzeug (z.B. Radio liegt in CARPARTS): beim nächsten Suchlauf nochmal ansehen
|
||||||
|
if (!IsVehicle(root)) continue;
|
||||||
|
scanned.Add(fsm);
|
||||||
|
if (!Wanted(fsm.gameObject.name, fsm.FsmName)) continue;
|
||||||
int key = Hash(FsmSync.PathOf(fsm.transform) + "|" + fsm.FsmName);
|
int key = Hash(FsmSync.PathOf(fsm.transform) + "|" + fsm.FsmName);
|
||||||
if (controls.ContainsKey(key)) continue;
|
if (controls.ContainsKey(key)) continue;
|
||||||
controls[key] = new Control { Key = key, Root = root, Fsm = fsm, LastState = fsm.ActiveStateName, WindowStart = Time.time };
|
var control = new Control { Key = key, Root = root, Fsm = fsm, LastState = fsm.ActiveStateName, WindowStart = Time.time };
|
||||||
|
controls[key] = control;
|
||||||
|
string waiting;
|
||||||
|
if (pending.TryGetValue(key, out waiting)) { pending.Remove(key); SwitchTo(control, waiting); }
|
||||||
}
|
}
|
||||||
if (controls.Count != before) MscoLog.Info("[MSCO] Vehicle controls (ignition, lights, engine ...): " + controls.Count + " synced");
|
if (controls.Count != before) MscoLog.Info("[MSCO] Vehicle controls (ignition, lights, engine ...): " + controls.Count + " synced");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// P2P-Host: alle Schalterzustände nochmal schicken (Licht an, Motor läuft ...) - für später Beigetretene und
|
||||||
|
/// falls eine Nachricht verloren ging. Fahrzeuge, die ein anderer fährt, gibt dieser vor.
|
||||||
|
/// </summary>
|
||||||
|
public void ResendAll(ObjectSync objects, int localId, Action<MsgType, byte[]> send)
|
||||||
|
{
|
||||||
|
outgoing.Clear();
|
||||||
|
foreach (Control c in controls.Values)
|
||||||
|
{
|
||||||
|
if (c.Fsm == null || c.Muted || string.IsNullOrEmpty(c.Fsm.ActiveStateName)) continue;
|
||||||
|
int owner = objects.VehicleOwner(c.Root);
|
||||||
|
if (owner != SyncedBody.NoOwner && owner != localId) continue;
|
||||||
|
outgoing.Add(c);
|
||||||
|
}
|
||||||
|
for (int start = 0; start < outgoing.Count; start += MaxPerMessage)
|
||||||
|
{
|
||||||
|
int from = start, count = Math.Min(MaxPerMessage, outgoing.Count - start);
|
||||||
|
send(MsgType.VehicleControls, NetSession.Build(w =>
|
||||||
|
{
|
||||||
|
w.Write(count);
|
||||||
|
for (int i = from; i < from + count; i++) { w.Write(outgoing[i].Key); w.Write(outgoing[i].Fsm.ActiveStateName); }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static bool Wanted(string objectName, string fsmName)
|
static bool Wanted(string objectName, string fsmName)
|
||||||
{
|
{
|
||||||
string o = objectName.ToLower(), f = fsmName.ToLower();
|
string o = objectName.ToLower(), f = fsmName.ToLower();
|
||||||
|
|||||||
+14
-1
@@ -65,6 +65,7 @@ namespace MSCO
|
|||||||
readonly TrafficSync traffic = new TrafficSync();
|
readonly TrafficSync traffic = new TrafficSync();
|
||||||
readonly HingeSync hinges = new HingeSync();
|
readonly HingeSync hinges = new HingeSync();
|
||||||
readonly ControlSync controls = new ControlSync();
|
readonly ControlSync controls = new ControlSync();
|
||||||
|
float nextControlResync;
|
||||||
readonly Toasts toasts = new Toasts();
|
readonly Toasts toasts = new Toasts();
|
||||||
float nextNeedsSend;
|
float nextNeedsSend;
|
||||||
const float NeedsInterval = 10f;
|
const float NeedsInterval = 10f;
|
||||||
@@ -1940,7 +1941,16 @@ namespace MSCO
|
|||||||
PartEnd("crash damage sync");
|
PartEnd("crash damage sync");
|
||||||
// Zündung, Motor, Licht, Choke ... der Fahrzeuge
|
// Zündung, Motor, Licht, Choke ... der Fahrzeuge
|
||||||
PartStart();
|
PartStart();
|
||||||
try { if (objects.ScanComplete && localPlayer != null) controls.Update(objects, localId, localPlayer.position, SendGame); }
|
try
|
||||||
|
{
|
||||||
|
if (objects.ScanComplete && localPlayer != null) controls.Update(objects, localId, localPlayer.position, SendGame);
|
||||||
|
// P2P: der Host gleicht regelmäßig alle Schalter ab (später Beigetretene, verlorene Nachrichten)
|
||||||
|
if (objects.ScanComplete && IsHost && !dedicated && Time.time >= nextControlResync)
|
||||||
|
{
|
||||||
|
nextControlResync = Time.time + 15f;
|
||||||
|
controls.ResendAll(objects, localId, SendGame);
|
||||||
|
}
|
||||||
|
}
|
||||||
catch (System.Exception e) { ReportError("vehicle controls sync", e); }
|
catch (System.Exception e) { ReportError("vehicle controls sync", e); }
|
||||||
PartEnd("vehicle controls sync");
|
PartEnd("vehicle controls sync");
|
||||||
// Türen, Motorhaube, Kofferraum der Fahrzeuge
|
// Türen, Motorhaube, Kofferraum der Fahrzeuge
|
||||||
@@ -2188,6 +2198,9 @@ namespace MSCO
|
|||||||
int needs = PlayerNeeds.Apply(r);
|
int needs = PlayerNeeds.Apply(r);
|
||||||
if (needs > 0) MscoLog.Info("[MSCO] Your own hunger/thirst/... restored (" + needs + " values)");
|
if (needs > 0) MscoLog.Info("[MSCO] Your own hunger/thirst/... restored (" + needs + " values)");
|
||||||
}
|
}
|
||||||
|
// Jobs/Laden und Schalter (Licht, Zündung, Motor ...) - Server ab 2.9.2
|
||||||
|
if (r.BaseStream.Length - r.BaseStream.Position >= 4) worldVars.Handle(r);
|
||||||
|
if (r.BaseStream.Length - r.BaseStream.Position >= 4) controls.Handle(r, objects, localId);
|
||||||
|
|
||||||
// Abgebaute Teile und gespawnte Items sind jetzt eigene Objekte: erfassen, dann alle Positionen setzen
|
// Abgebaute Teile und gespawnte Items sind jetzt eigene Objekte: erfassen, dann alle Positionen setzen
|
||||||
yield return null;
|
yield return null;
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ namespace MSCO
|
|||||||
/// <summary>Besitzer eines Fahrzeugs (nach Wurzelname), NoOwner wenn frei oder unbekannt.</summary>
|
/// <summary>Besitzer eines Fahrzeugs (nach Wurzelname), NoOwner wenn frei oder unbekannt.</summary>
|
||||||
public int VehicleOwner(string rootName)
|
public int VehicleOwner(string rootName)
|
||||||
{
|
{
|
||||||
|
if (rootName == null) return SyncedBody.NoOwner;
|
||||||
foreach (SyncedBody b in byId.Values)
|
foreach (SyncedBody b in byId.Values)
|
||||||
if (b.IsVehicle && b.RootName == rootName) return b.OwnerId;
|
if (b.IsVehicle && b.RootName == rootName) return b.OwnerId;
|
||||||
return SyncedBody.NoOwner;
|
return SyncedBody.NoOwner;
|
||||||
|
|||||||
+120
-19
@@ -34,9 +34,17 @@ namespace MSCO
|
|||||||
static readonly string[] Excluded =
|
static readonly string[] Excluded =
|
||||||
{
|
{
|
||||||
"install", "bolt", "tight", "detach", "attach", "rpm", "speed", "steer", "throttle", "gear", "raycast", "distance",
|
"install", "bolt", "tight", "detach", "attach", "rpm", "speed", "steer", "throttle", "gear", "raycast", "distance",
|
||||||
"leak", "rate"
|
"leak", "rate",
|
||||||
|
// Spieler-Werte (Database/PlayerDatabase: Schmutz, Verbrennungen ...) sind pro Spieler; Fluch-Geräusche,
|
||||||
|
// Ton-Verzerrung und Anzeige-Hilfswerte (GUI) rechnet jeder selbst
|
||||||
|
"player", "swear", "distortion", "gui"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Wurzeln außer den Fahrzeugen: Satsuma-Teile (lose und angebaut) und ihre Datenbank (Verschleiß, beschädigt,
|
||||||
|
// Lack, Füllstände ...). Maßgeblich ist dort wie beim Satsuma selbst, wer den Satsuma gerade fährt.
|
||||||
|
static readonly string[] PartRoots = { "CARPARTS", "Database" };
|
||||||
|
const string PartOwnerPrefix = "SATSUMA";
|
||||||
|
|
||||||
const float ScanInterval = 10f;
|
const float ScanInterval = 10f;
|
||||||
const float CheckInterval = 0.5f;
|
const float CheckInterval = 0.5f;
|
||||||
const int MaxPerMessage = 200;
|
const int MaxPerMessage = 200;
|
||||||
@@ -47,7 +55,9 @@ namespace MSCO
|
|||||||
public string Name; // Pfad|FSM|Variable (für die Diagnose "vars")
|
public string Name; // Pfad|FSM|Variable (für die Diagnose "vars")
|
||||||
public string Root;
|
public string Root;
|
||||||
public NamedVariable Variable;
|
public NamedVariable Variable;
|
||||||
public byte Type; // 1 float, 2 int, 3 bool
|
public byte Type; // 1 float, 2 int, 3 bool (übertragen); Farben als 4 einzelne Kanäle vom Typ 1
|
||||||
|
public int Channel = -1; // FsmColor: 0-3 = r/g/b/a
|
||||||
|
public PlayMakerFSM Fsm;
|
||||||
public float LastF;
|
public float LastF;
|
||||||
public int LastI;
|
public int LastI;
|
||||||
public bool LastB;
|
public bool LastB;
|
||||||
@@ -56,7 +66,12 @@ namespace MSCO
|
|||||||
readonly Dictionary<int, Var> vars = new Dictionary<int, Var>();
|
readonly Dictionary<int, Var> vars = new Dictionary<int, Var>();
|
||||||
readonly Dictionary<int, byte[]> pending = new Dictionary<int, byte[]>(); // noch nicht gefundene Werte
|
readonly Dictionary<int, byte[]> pending = new Dictionary<int, byte[]>(); // noch nicht gefundene Werte
|
||||||
readonly List<Var> outgoing = new List<Var>();
|
readonly List<Var> outgoing = new List<Var>();
|
||||||
readonly HashSet<PlayMakerFSM> scanned = new HashSet<PlayMakerFSM>();
|
// Erfasste FSMs mit dem Elternobjekt beim Erfassen: wird ein Teil an-/abgebaut, ändert sich sein Pfad (und damit
|
||||||
|
// die Schlüssel) - dann neu erfassen. FSMs außerhalb der Fahrzeuge/Teile werden bei jedem Suchlauf neu geprüft
|
||||||
|
// (vorher einmal als "nicht Fahrzeug" gemerkt und nie wieder angesehen: später angebaute Teile fehlten)
|
||||||
|
readonly Dictionary<PlayMakerFSM, Transform> scanned = new Dictionary<PlayMakerFSM, Transform>();
|
||||||
|
readonly Dictionary<PlayMakerFSM, List<int>> keysByFsm = new Dictionary<PlayMakerFSM, List<int>>();
|
||||||
|
string satsumaRoot;
|
||||||
float nextScan, nextCheck;
|
float nextScan, nextCheck;
|
||||||
|
|
||||||
public int Count { get { return vars.Count; } }
|
public int Count { get { return vars.Count; } }
|
||||||
@@ -69,7 +84,7 @@ namespace MSCO
|
|||||||
foreach (Var v in vars.Values)
|
foreach (Var v in vars.Values)
|
||||||
{
|
{
|
||||||
if (f.Length > 0 && (v.Name == null || !v.Name.ToLower().Contains(f))) continue;
|
if (f.Length > 0 && (v.Name == null || !v.Name.ToLower().Contains(f))) continue;
|
||||||
string value = v.Type == 1 ? ((FsmFloat)v.Variable).Value.ToString("0.###")
|
string value = v.Type == 1 ? GetFloat(v).ToString("0.###")
|
||||||
: v.Type == 2 ? ((FsmInt)v.Variable).Value.ToString()
|
: v.Type == 2 ? ((FsmInt)v.Variable).Value.ToString()
|
||||||
: ((FsmBool)v.Variable).Value.ToString();
|
: ((FsmBool)v.Variable).Value.ToString();
|
||||||
lines.Add(" " + v.Name + " = " + value);
|
lines.Add(" " + v.Name + " = " + value);
|
||||||
@@ -84,6 +99,8 @@ namespace MSCO
|
|||||||
vars.Clear();
|
vars.Clear();
|
||||||
pending.Clear();
|
pending.Clear();
|
||||||
scanned.Clear();
|
scanned.Clear();
|
||||||
|
keysByFsm.Clear();
|
||||||
|
satsumaRoot = null;
|
||||||
nextScan = -1f; // erster Suchlauf verteilt, siehe Update
|
nextScan = -1f; // erster Suchlauf verteilt, siehe Update
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,10 +116,11 @@ namespace MSCO
|
|||||||
nextCheck = Time.time + CheckInterval;
|
nextCheck = Time.time + CheckInterval;
|
||||||
|
|
||||||
outgoing.Clear();
|
outgoing.Clear();
|
||||||
|
ResolveSatsuma(objects);
|
||||||
foreach (Var v in vars.Values)
|
foreach (Var v in vars.Values)
|
||||||
{
|
{
|
||||||
// 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(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 ...) -
|
||||||
@@ -134,7 +152,7 @@ namespace MSCO
|
|||||||
byte[] value = r.ReadBytes(type == 3 ? 1 : 4);
|
byte[] value = r.ReadBytes(type == 3 ? 1 : 4);
|
||||||
Var v;
|
Var v;
|
||||||
if (!vars.TryGetValue(key, out v)) { pending[key] = Combine(type, value); continue; }
|
if (!vars.TryGetValue(key, out v)) { pending[key] = Combine(type, value); continue; }
|
||||||
if (from >= 0 && objects.VehicleOwner(v.Root) == localId) continue; // ich fahre gerade: meine Werte gelten
|
if (from >= 0 && objects.VehicleOwner(OwnerRoot(v.Root)) == localId) continue; // ich fahre gerade: meine Werte gelten
|
||||||
Apply(v, type, value);
|
Apply(v, type, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,18 +179,29 @@ namespace MSCO
|
|||||||
int before = vars.Count;
|
int before = vars.Count;
|
||||||
foreach (PlayMakerFSM fsm in SceneCache.Fsms(20f))
|
foreach (PlayMakerFSM fsm in SceneCache.Fsms(20f))
|
||||||
{
|
{
|
||||||
// Schon erfasste FSMs nicht jedes Mal neu durchgehen (Pfad + Hash kosten)
|
if (fsm == null) continue;
|
||||||
if (fsm == null || !scanned.Add(fsm)) continue;
|
// Schon erfasst und nicht umgehängt (an-/abgebaut): nichts zu tun (Pfad + Hash kosten)
|
||||||
|
Transform parent;
|
||||||
|
if (scanned.TryGetValue(fsm, out parent) && parent == fsm.transform.parent) continue;
|
||||||
string root = fsm.transform.root.name;
|
string root = fsm.transform.root.name;
|
||||||
if (!IsVehicle(root)) continue;
|
if (!IsTracked(root)) { Forget(fsm); continue; }
|
||||||
|
scanned[fsm] = fsm.transform.parent;
|
||||||
|
Forget(fsm);
|
||||||
// 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;
|
||||||
|
if (fsm.transform.root.name == "Database" && FsmSync.PathOf(fsm.transform).Contains("PlayerDatabase")) continue;
|
||||||
string prefix = FsmSync.PathOf(fsm.transform) + "|" + fsm.FsmName + "|";
|
string prefix = FsmSync.PathOf(fsm.transform) + "|" + fsm.FsmName + "|";
|
||||||
FsmVariables fv = fsm.FsmVariables;
|
FsmVariables fv = fsm.FsmVariables;
|
||||||
foreach (FsmFloat f in fv.FloatVariables) Register(prefix, root, f, 1);
|
foreach (FsmFloat f in fv.FloatVariables) Register(fsm, prefix, root, f, 1, -1);
|
||||||
foreach (FsmInt n in fv.IntVariables) Register(prefix, root, n, 2);
|
foreach (FsmInt n in fv.IntVariables) Register(fsm, prefix, root, n, 2, -1);
|
||||||
foreach (FsmBool b in fv.BoolVariables) Register(prefix, root, b, 3);
|
foreach (FsmBool b in fv.BoolVariables) Register(fsm, prefix, root, b, 3, -1);
|
||||||
|
// Lack-/Farbwerte (FsmColor) als vier Zahlen: alte Server/Mods kennen nur float/int/bool
|
||||||
|
foreach (FsmColor c in fv.ColorVariables)
|
||||||
|
{
|
||||||
|
if (c == null || !IsColorName(c.Name)) continue;
|
||||||
|
for (int ch = 0; ch < 4; ch++) Register(fsm, prefix, root, c, 1, ch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (vars.Count != before) MscoLog.Info("[MSCO] Vehicle state: " + vars.Count + " values synced");
|
if (vars.Count != before) MscoLog.Info("[MSCO] Vehicle state: " + vars.Count + " values synced");
|
||||||
}
|
}
|
||||||
@@ -200,15 +229,20 @@ namespace MSCO
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Register(string prefix, string root, NamedVariable variable, byte type)
|
void Register(PlayMakerFSM fsm, string prefix, string root, NamedVariable variable, byte type, int channel)
|
||||||
{
|
{
|
||||||
if (variable == null || string.IsNullOrEmpty(variable.Name) || !Wanted(variable.Name)) return;
|
if (variable == null || string.IsNullOrEmpty(variable.Name)) return;
|
||||||
int key = Hash(prefix + variable.Name);
|
if (channel < 0 && !Wanted(variable.Name)) return;
|
||||||
|
string name = prefix + variable.Name + (channel >= 0 ? "#" + "rgba"[channel] : "");
|
||||||
|
int key = Hash(name);
|
||||||
Var existing;
|
Var existing;
|
||||||
if (vars.TryGetValue(key, out existing) && existing.Variable == variable) return;
|
if (vars.TryGetValue(key, out existing) && existing.Variable == variable) return;
|
||||||
var v = new Var { Key = key, Name = prefix + variable.Name, Root = root, Variable = variable, Type = type };
|
var v = new Var { Key = key, Name = name, Root = root, Variable = variable, Type = type, Channel = channel, Fsm = fsm };
|
||||||
Remember(v);
|
Remember(v);
|
||||||
vars[key] = v;
|
vars[key] = v;
|
||||||
|
List<int> keys;
|
||||||
|
if (!keysByFsm.TryGetValue(fsm, out keys)) keysByFsm[fsm] = keys = new List<int>();
|
||||||
|
keys.Add(key);
|
||||||
|
|
||||||
byte[] stored;
|
byte[] stored;
|
||||||
if (pending.TryGetValue(key, out stored))
|
if (pending.TryGetValue(key, out stored))
|
||||||
@@ -234,13 +268,80 @@ namespace MSCO
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool IsTracked(string root)
|
||||||
|
{
|
||||||
|
return IsVehicle(root) || Array.IndexOf(PartRoots, root) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsColorName(string name)
|
||||||
|
{
|
||||||
|
string n = name.ToLower();
|
||||||
|
return n.Contains("paint") || n.Contains("color") || n.Contains("colour");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Werte einer umgehängten/verschwundenen FSM vergessen (Pfad und damit Schlüssel ändern sich).</summary>
|
||||||
|
void Forget(PlayMakerFSM fsm)
|
||||||
|
{
|
||||||
|
List<int> keys;
|
||||||
|
if (!keysByFsm.TryGetValue(fsm, out keys)) return;
|
||||||
|
foreach (int k in keys)
|
||||||
|
{
|
||||||
|
Var v;
|
||||||
|
if (vars.TryGetValue(k, out v) && v.Fsm == fsm) vars.Remove(k);
|
||||||
|
}
|
||||||
|
keysByFsm.Remove(fsm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Satsuma-Teile gehören dem, der den Satsuma fährt.</summary>
|
||||||
|
string OwnerRoot(string root)
|
||||||
|
{
|
||||||
|
return Array.IndexOf(PartRoots, root) >= 0 ? satsumaRoot : root;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResolveSatsuma(ObjectSync objects)
|
||||||
|
{
|
||||||
|
if (satsumaRoot != null) return;
|
||||||
|
foreach (SyncedBody b in objects.AllVehicles())
|
||||||
|
if (b.RootName != null && b.RootName.StartsWith(PartOwnerPrefix)) { satsumaRoot = b.RootName; return; }
|
||||||
|
}
|
||||||
|
|
||||||
|
static float GetFloat(Var v)
|
||||||
|
{
|
||||||
|
if (v.Channel < 0) return ((FsmFloat)v.Variable).Value;
|
||||||
|
Color c = ((FsmColor)v.Variable).Value;
|
||||||
|
return c[v.Channel];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetFloat(Var v, float f)
|
||||||
|
{
|
||||||
|
if (v.Channel < 0) { ((FsmFloat)v.Variable).Value = f; return; }
|
||||||
|
var cv = (FsmColor)v.Variable;
|
||||||
|
Color c = cv.Value;
|
||||||
|
c[v.Channel] = Mathf.Clamp(f, 0f, 4f);
|
||||||
|
cv.Value = c;
|
||||||
|
ShowPaint(v, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lack sichtbar machen: Die Lackier-FSM eines Teils setzt beim Lackieren die Materialfarbe ihres eigenen Meshes.
|
||||||
|
/// Beim Mitspieler kommt nur der Farbwert an - also dieselbe Farbe aufs Mesh des Teils legen.
|
||||||
|
/// </summary>
|
||||||
|
static void ShowPaint(Var v, Color c)
|
||||||
|
{
|
||||||
|
if (v.Fsm == null || v.Fsm.FsmName.IndexOf("paint", StringComparison.OrdinalIgnoreCase) < 0) return;
|
||||||
|
Renderer r = v.Fsm.GetComponent<Renderer>();
|
||||||
|
if (r == null || r.material == null) return;
|
||||||
|
r.material.color = c;
|
||||||
|
}
|
||||||
|
|
||||||
static bool Changed(Var v, bool bigOnly)
|
static bool Changed(Var v, bool bigOnly)
|
||||||
{
|
{
|
||||||
switch (v.Type)
|
switch (v.Type)
|
||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
float f = ((FsmFloat)v.Variable).Value;
|
float f = GetFloat(v);
|
||||||
float delta = Mathf.Abs(f - v.LastF);
|
float delta = Mathf.Abs(f - v.LastF);
|
||||||
|
if (v.Channel >= 0) return delta > 0.002f; // Farben: jede Änderung (Lackieren)
|
||||||
return bigOnly
|
return bigOnly
|
||||||
? delta > Mathf.Max(0.5f, Mathf.Abs(v.LastF) * 0.05f)
|
? delta > Mathf.Max(0.5f, Mathf.Abs(v.LastF) * 0.05f)
|
||||||
: delta > Mathf.Max(0.01f, Mathf.Abs(v.LastF) * 0.002f);
|
: delta > Mathf.Max(0.01f, Mathf.Abs(v.LastF) * 0.002f);
|
||||||
@@ -253,7 +354,7 @@ namespace MSCO
|
|||||||
{
|
{
|
||||||
switch (v.Type)
|
switch (v.Type)
|
||||||
{
|
{
|
||||||
case 1: v.LastF = ((FsmFloat)v.Variable).Value; break;
|
case 1: v.LastF = GetFloat(v); break;
|
||||||
case 2: v.LastI = ((FsmInt)v.Variable).Value; break;
|
case 2: v.LastI = ((FsmInt)v.Variable).Value; break;
|
||||||
default: v.LastB = ((FsmBool)v.Variable).Value; break;
|
default: v.LastB = ((FsmBool)v.Variable).Value; break;
|
||||||
}
|
}
|
||||||
@@ -267,7 +368,7 @@ 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
|
||||||
((FsmFloat)v.Variable).Value = f;
|
SetFloat(v, f);
|
||||||
break;
|
break;
|
||||||
case 2: ((FsmInt)v.Variable).Value = BitConverter.ToInt32(value, 0); break;
|
case 2: ((FsmInt)v.Variable).Value = BitConverter.ToInt32(value, 0); break;
|
||||||
default: ((FsmBool)v.Variable).Value = value[0] != 0; break;
|
default: ((FsmBool)v.Variable).Value = value[0] != 0; break;
|
||||||
|
|||||||
+22
-3
@@ -41,6 +41,8 @@ namespace MSCO
|
|||||||
readonly Dictionary<int, Var> vars = new Dictionary<int, Var>();
|
readonly Dictionary<int, Var> vars = new Dictionary<int, Var>();
|
||||||
readonly HashSet<PlayMakerFSM> scanned = new HashSet<PlayMakerFSM>();
|
readonly HashSet<PlayMakerFSM> scanned = new HashSet<PlayMakerFSM>();
|
||||||
readonly List<Var> outgoing = new List<Var>();
|
readonly List<Var> outgoing = new List<Var>();
|
||||||
|
// Werte für noch nicht gefundene FSMs (Server-Snapshot vor dem ersten Suchlauf): [typ, wert]
|
||||||
|
readonly Dictionary<int, KeyValuePair<byte, int>> pending = new Dictionary<int, KeyValuePair<byte, int>>();
|
||||||
float nextScan, nextCheck;
|
float nextScan, nextCheck;
|
||||||
|
|
||||||
public int Count { get { return vars.Count; } }
|
public int Count { get { return vars.Count; } }
|
||||||
@@ -49,6 +51,7 @@ namespace MSCO
|
|||||||
{
|
{
|
||||||
vars.Clear();
|
vars.Clear();
|
||||||
scanned.Clear();
|
scanned.Clear();
|
||||||
|
pending.Clear();
|
||||||
nextScan = -1f; // erster Suchlauf verteilt, siehe Update
|
nextScan = -1f; // erster Suchlauf verteilt, siehe Update
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,9 +105,13 @@ namespace MSCO
|
|||||||
int iv = 0; bool bv = false;
|
int iv = 0; bool bv = false;
|
||||||
if (type == 2) iv = r.ReadInt32(); else if (type == 3) bv = r.ReadBoolean(); else return;
|
if (type == 2) iv = r.ReadInt32(); else if (type == 3) bv = r.ReadBoolean(); else return;
|
||||||
Var v;
|
Var v;
|
||||||
if (!vars.TryGetValue(key, out v) || v.Type != type) continue;
|
if (!vars.TryGetValue(key, out v))
|
||||||
if (type == 2) ((FsmInt)v.Variable).Value = iv; else ((FsmBool)v.Variable).Value = bv;
|
{
|
||||||
Remember(v); // nicht zurückschicken
|
if (pending.Count < 20000) pending[key] = new KeyValuePair<byte, int>(type, type == 2 ? iv : (bv ? 1 : 0));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (v.Type != type) continue;
|
||||||
|
Set(v, type == 2 ? iv : (bv ? 1 : 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +138,18 @@ namespace MSCO
|
|||||||
var v = new Var { Key = key, Variable = variable, Type = type };
|
var v = new Var { Key = key, Variable = variable, Type = type };
|
||||||
Remember(v);
|
Remember(v);
|
||||||
vars[key] = v;
|
vars[key] = v;
|
||||||
|
KeyValuePair<byte, int> waiting;
|
||||||
|
if (pending.TryGetValue(key, out waiting))
|
||||||
|
{
|
||||||
|
pending.Remove(key);
|
||||||
|
if (waiting.Key == type) Set(v, waiting.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Set(Var v, int value)
|
||||||
|
{
|
||||||
|
if (v.Type == 2) ((FsmInt)v.Variable).Value = value; else ((FsmBool)v.Variable).Value = value != 0;
|
||||||
|
Remember(v); // nicht zurückschicken
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool Changed(Var v)
|
static bool Changed(Var v)
|
||||||
|
|||||||
Reference in New Issue
Block a user