168 lines
7.1 KiB
C#
168 lines
7.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using HutongGames.PlayMaker;
|
|
using UnityEngine;
|
|
|
|
namespace MSCO
|
|
{
|
|
/// <summary>
|
|
/// Bedienelemente der Fahrzeuge: Zündschloss, Anlasser, Motor, Lichtschalter, Choke, Blinker, Scheibenwischer ...
|
|
/// Das Spiel schaltet damit Motor und Licht über PlayMaker-FSMs (Zustände wie "Off" / "Lights" / "Running").
|
|
/// Übertragen wird der aktive Zustand; beim anderen Spieler wird dieselbe FSM in denselben Zustand geschaltet,
|
|
/// sodass sein Spiel Motor, Geräusch, Scheinwerfer und Armaturen selbst richtig anschaltet.
|
|
/// Wer sendet: der Fahrer; ist das Fahrzeug frei, wer daneben steht bzw. drin sitzt (Schalter betätigt).
|
|
/// FSMs, die ständig den Zustand wechseln (interne Schleifen), werden nach kurzer Zeit nicht mehr übertragen.
|
|
/// </summary>
|
|
public class ControlSync
|
|
{
|
|
// Objekt- oder FSM-Name enthält eines davon
|
|
static readonly string[] Keywords =
|
|
{
|
|
"ignition", "starter", "engine", "light", "choke", "wiper", "hazard", "indicator", "turnsignal", "blinker",
|
|
"horn", "heater", "fan", "radio", "key", "switch", "knob", "button"
|
|
};
|
|
// Montage/Speichern/Schrauben - läuft über den Objekt- bzw. FSM-Sync, hier nicht anfassen
|
|
static readonly string[] Excluded =
|
|
{
|
|
"removal", "assembly", "assemble", "bolt", "screw", "data", "save", "load", "paint", "detach", "attach",
|
|
"install", "trigger", "tighten", "wear", "damage", "oil", "coolant", "fuel", "inspection"
|
|
};
|
|
|
|
const float ScanInterval = 10f;
|
|
const float CheckInterval = 0.1f;
|
|
const float NearRange = 5f;
|
|
const int FlipLimit = 12; // so viele Zustandswechsel ...
|
|
const float FlipWindow = 10f; // ... in dieser Zeit = interne Schleife, nicht übertragen
|
|
const int MaxPerMessage = 100;
|
|
|
|
class Control
|
|
{
|
|
public int Key;
|
|
public string Root;
|
|
public PlayMakerFSM Fsm;
|
|
public string LastState;
|
|
public float WindowStart;
|
|
public int Flips;
|
|
public bool Muted;
|
|
}
|
|
|
|
readonly Dictionary<int, Control> controls = new Dictionary<int, Control>();
|
|
readonly HashSet<PlayMakerFSM> scanned = new HashSet<PlayMakerFSM>();
|
|
readonly List<Control> outgoing = new List<Control>();
|
|
float nextScan, nextCheck;
|
|
|
|
public int Count { get { return controls.Count; } }
|
|
public int MutedCount { get { int n = 0; foreach (Control c in controls.Values) if (c.Muted) n++; return n; } }
|
|
|
|
public void Reset()
|
|
{
|
|
controls.Clear();
|
|
scanned.Clear();
|
|
nextScan = 0f;
|
|
}
|
|
|
|
public void Update(ObjectSync objects, int localId, Vector3 playerPos, Action<MsgType, byte[]> send)
|
|
{
|
|
if (Time.time >= nextScan)
|
|
{
|
|
nextScan = Time.time + ScanInterval;
|
|
Scan();
|
|
}
|
|
if (Time.time < nextCheck) return;
|
|
nextCheck = Time.time + CheckInterval;
|
|
|
|
outgoing.Clear();
|
|
foreach (Control c in controls.Values)
|
|
{
|
|
if (c.Fsm == null || c.Muted) continue;
|
|
string state = c.Fsm.ActiveStateName;
|
|
if (state == c.LastState || string.IsNullOrEmpty(state)) continue;
|
|
c.LastState = state;
|
|
|
|
if (Time.time - c.WindowStart > FlipWindow) { c.WindowStart = Time.time; c.Flips = 0; }
|
|
if (++c.Flips > FlipLimit)
|
|
{
|
|
c.Muted = true;
|
|
MscoLog.Info("[MSCO] Control not synced (changes all the time): " + c.Fsm.gameObject.name + " [" + c.Fsm.FsmName + "]");
|
|
continue;
|
|
}
|
|
|
|
int owner = objects.VehicleOwner(c.Root);
|
|
if (owner != SyncedBody.NoOwner && owner != localId) continue; // der Fahrer gibt vor
|
|
if (owner == SyncedBody.NoOwner && (c.Fsm.transform.position - playerPos).sqrMagnitude > NearRange * NearRange) 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].LastState); }
|
|
}));
|
|
}
|
|
}
|
|
|
|
public void Handle(BinaryReader r, ObjectSync objects, int localId)
|
|
{
|
|
int count = r.ReadInt32();
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
int key = r.ReadInt32();
|
|
string state = r.ReadString();
|
|
Control c;
|
|
if (!controls.TryGetValue(key, out c) || c.Fsm == null) { nextScan = 0f; continue; }
|
|
if (objects.VehicleOwner(c.Root) == localId) continue; // ich fahre: meine Schalter gelten
|
|
if (c.Fsm.ActiveStateName == state) { c.LastState = state; continue; }
|
|
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 Scan()
|
|
{
|
|
int before = controls.Count;
|
|
foreach (PlayMakerFSM fsm in SceneCache.Fsms(5f))
|
|
{
|
|
if (fsm == null || !scanned.Add(fsm)) continue;
|
|
string root = fsm.transform.root.name;
|
|
if (!IsVehicle(root) || !Wanted(fsm.gameObject.name, fsm.FsmName)) continue;
|
|
int key = Hash(FsmSync.PathOf(fsm.transform) + "|" + fsm.FsmName);
|
|
if (controls.ContainsKey(key)) continue;
|
|
controls[key] = new Control { Key = key, Root = root, Fsm = fsm, LastState = fsm.ActiveStateName, WindowStart = Time.time };
|
|
}
|
|
if (controls.Count != before) MscoLog.Info("[MSCO] Vehicle controls (ignition, lights, engine ...): " + controls.Count + " synced");
|
|
}
|
|
|
|
static bool Wanted(string objectName, string fsmName)
|
|
{
|
|
string o = objectName.ToLower(), f = fsmName.ToLower();
|
|
foreach (string x in Excluded) if (o.Contains(x) || f.Contains(x)) return false;
|
|
foreach (string k in Keywords) if (o.Contains(k) || f.Contains(k)) return true;
|
|
return false;
|
|
}
|
|
|
|
static bool IsVehicle(string root)
|
|
{
|
|
foreach (string v in ObjectSync.VehicleRoots) if (root.StartsWith(v)) return true;
|
|
return false;
|
|
}
|
|
|
|
static int Hash(string s)
|
|
{
|
|
unchecked
|
|
{
|
|
uint h = 2166136261;
|
|
foreach (byte c in Encoding.UTF8.GetBytes(s)) { h ^= c; h *= 16777619; }
|
|
return (int)h;
|
|
}
|
|
}
|
|
}
|
|
}
|