using System; using System.Collections.Generic; using System.IO; using HutongGames.PlayMaker; using MSCLoader; using UnityEngine; namespace MSCO { /// /// Türen, Tore, Hauben, Klappen (Garagentor, Haustüren, Autotüren ...). Die Mod beobachtet die /// PlayMaker-FSMs dieser Objekte: Wechselt eine bei uns in einen neuen Zustand (z.B. "Open door"), /// wird dieser Zustand an die anderen geschickt und dort ebenfalls ausgelöst. Unabhängig davon, /// wie der Spieler das Objekt bedient hat (Klick, Taste, Hand-FSM ...). /// public class DoorSync { static readonly string[] Keywords = { "door", "gate", "hood", "bonnet", "trunk", "lid", "hatch" }; // Nicht die FSMs zum An-/Abbauen, Festschrauben, Lackieren, Speichern ... der Tür-TEILE: deren Zustände wurden // beim Mitspieler erzwungen - Türen hingen schief, lösten sich oder sprangen zurück. Montage läuft über FsmSync. static readonly string[] ExcludedFsms = { "removal", "assembly", "assemble", "boltcheck", "bolt", "screw", "paint", "data", "save", "load", "wiring", "setup", "damage", "tightness", "detach", "attach", "install" }; const float Cooldown = 1.5f; // Folge-Zustände der Animation nicht nochmal senden const float RescanInterval = 10f; // neue Objekte (z.B. angebaute Autotüren) aufnehmen class Tracked { public string Key; public PlayMakerFSM Fsm; public string LastState; public float QuietUntil; } readonly Dictionary byKey = new Dictionary(); float nextScan; public int Count { get { return byKey.Count; } } public void Reset() { byKey.Clear(); nextScan = 0f; } public void Update(Action send) { float now = Time.realtimeSinceStartup; if (now >= nextScan) { nextScan = now + RescanInterval; Scan(); } foreach (Tracked t in byKey.Values) { if (t.Fsm == null) continue; string state = t.Fsm.ActiveStateName; if (state == t.LastState) continue; t.LastState = state; if (now < t.QuietUntil) continue; // Folge einer eigenen oder fremden Aktion FsmState s = t.Fsm.Fsm.GetState(state); if (s == null || FsmSync.ChecksInputPublic(s)) continue; // nur "Maus zeigt drauf" o.ä. t.QuietUntil = now + Cooldown; string key = t.Key; send(MsgType.DoorState, NetSession.Build(w => { w.Write(key); w.Write(state); })); if (FsmSync.Debug) MscoLog.Info("[MSCO] >> DOOR " + key + " -> " + state); } } public void Apply(BinaryReader r) { ApplyState(r.ReadString(), r.ReadString()); } /// Zustand übernehmen (von einem Mitspieler oder aus dem Server-Snapshot). public void ApplyState(string key, string state) { Tracked t; if (!byKey.TryGetValue(key, out t) || t.Fsm == null) { Scan(); // Objekt vielleicht erst seit kurzem aktiv if (!byKey.TryGetValue(key, out t) || t.Fsm == null) { if (FsmSync.Debug) MscoLog.Warning("[MSCO] << DOOR not found: " + key); return; } } if (t.Fsm.ActiveStateName == state) return; FsmState target = t.Fsm.Fsm.GetState(state); if (target == null) return; t.QuietUntil = Time.realtimeSinceStartup + Cooldown; // Folge-Zustände nicht zurückschicken t.LastState = state; FsmSync.SwitchStateSilently(t.Fsm, target); if (FsmSync.Debug) MscoLog.Info("[MSCO] << DOOR " + key + " -> " + state); } void Scan() { foreach (PlayMakerFSM fsm in SceneCache.Fsms(20f)) { if (fsm == null) continue; string root = fsm.transform.root.name; if (root == "PLAYER" || root.StartsWith("MSCO_")) continue; if (!Matches(fsm.gameObject.name) && !Matches(fsm.FsmName) && !Matches(fsm.transform.parent != null ? fsm.transform.parent.name : "")) continue; if (IsPartLogic(fsm)) continue; string key = FsmSync.PathOf(fsm.transform) + "|" + fsm.FsmName; Tracked t; if (byKey.TryGetValue(key, out t) && t.Fsm == fsm) continue; byKey[key] = new Tracked { Key = key, Fsm = fsm, LastState = fsm.ActiveStateName }; } } static bool IsPartLogic(PlayMakerFSM fsm) { string f = fsm.FsmName.ToLower(), o = fsm.gameObject.name.ToLower(); if (o.StartsWith("trigger_") || o.StartsWith("bolt")) return true; foreach (string x in ExcludedFsms) if (f.Contains(x)) return true; return false; } static bool Matches(string name) { if (string.IsNullOrEmpty(name)) return false; string lower = name.ToLower(); foreach (string k in Keywords) if (lower.Contains(k)) return true; return false; } } }