Files
MSCO/WorldSync.cs
MrMahoandClaude Opus 5.5 461d94c5fc MSCO 2.9.0: standalone without MSCLoader (Unity Doorstop)
- Bootstrap: Doorstop entry point, loads Harmony/Newtonsoft from the MSCO folder, hooks
  PlayMakerArrayListProxy.Awake and starts MSCO in the main menu; skips the news screen
- No MSCLoader reference anymore; key bindings in PlayerPrefs, bind console command
- Harmony 1.2 and Newtonsoft.Json in lib/ (MIT notices); build copies to <game>\MSCO
- README/CHANGELOG updated

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:36:28 +02:00

183 lines
6.8 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using HutongGames.PlayMaker;
using UnityEngine;
namespace MSCO
{
/// <summary>
/// Uhrzeit und Wetter. Ein Spieler ist "Weltuhr" (P2P: der Host; dedizierter Server: der Spieler
/// mit der kleinsten ID) und schickt regelmäßig den Zustand der Uhr- und Wetter-FSMs
/// (aktiver Zustand + alle Zahlen-/Ja-Nein-Variablen + Drehung, z.B. der Sonne) sowie globale
/// Werte wie den Wochentag. Alle anderen übernehmen diesen Zustand.
/// </summary>
public class WorldSync
{
// FSMs, die Uhrzeit und Wetter steuern
static readonly string[] TrackedFsmNames = { "Clock", "Weather" };
// Globale PlayMaker-Variablen (Wochentag, Tageszeit)
static readonly string[] TrackedGlobals = { "GlobalDay", "TimeOfDay" };
const float SendInterval = 2f;
const float DiscoverRetry = 5f;
class Tracked
{
public string Key;
public PlayMakerFSM Fsm;
}
readonly Dictionary<string, Tracked> tracked = new Dictionary<string, Tracked>();
float sendTimer;
float nextDiscover;
bool discovered;
public int TrackedCount { get { return tracked.Count; } }
public void Reset()
{
tracked.Clear();
discovered = false;
nextDiscover = 0f;
}
// ---------------------------------------------------------------- Senden (Weltuhr)
public void Update(bool isAuthority, Action<MsgType, byte[]> send, float dt)
{
if (!discovered && Time.time >= nextDiscover) Discover();
if (!isAuthority || tracked.Count == 0) return;
sendTimer += dt;
if (sendTimer < SendInterval) return;
sendTimer = 0f;
send(MsgType.WorldTime, BuildState());
}
byte[] BuildState()
{
var alive = new List<Tracked>();
foreach (Tracked t in tracked.Values) if (t.Fsm != null) alive.Add(t);
return NetSession.Build(w =>
{
w.Write(alive.Count);
foreach (Tracked t in alive)
{
w.Write(t.Key);
w.Write(t.Fsm.ActiveStateName ?? "");
Quaternion rot = t.Fsm.transform.localRotation;
w.Write(rot.x); w.Write(rot.y); w.Write(rot.z); w.Write(rot.w);
WriteVariables(w, t.Fsm.FsmVariables, null);
}
WriteVariables(w, FsmVariables.GlobalVariables, TrackedGlobals);
});
}
static void WriteVariables(BinaryWriter w, FsmVariables vars, string[] only)
{
var floats = Filter(vars.FloatVariables, only);
w.Write(floats.Count);
foreach (FsmFloat v in floats) { w.Write(v.Name); w.Write(v.Value); }
var ints = Filter(vars.IntVariables, only);
w.Write(ints.Count);
foreach (FsmInt v in ints) { w.Write(v.Name); w.Write(v.Value); }
var bools = Filter(vars.BoolVariables, only);
w.Write(bools.Count);
foreach (FsmBool v in bools) { w.Write(v.Name); w.Write(v.Value); }
}
static List<T> Filter<T>(T[] vars, string[] only) where T : NamedVariable
{
var list = new List<T>();
if (vars == null) return list;
foreach (T v in vars)
if (v != null && !string.IsNullOrEmpty(v.Name) && (only == null || Array.IndexOf(only, v.Name) >= 0))
list.Add(v);
return list;
}
// ---------------------------------------------------------------- Empfangen
public void Apply(BinaryReader r)
{
if (!discovered) Discover();
int count = r.ReadInt32();
for (int i = 0; i < count; i++)
{
string key = r.ReadString();
string state = r.ReadString();
var rot = new Quaternion(r.ReadSingle(), r.ReadSingle(), r.ReadSingle(), r.ReadSingle());
Tracked t;
PlayMakerFSM fsm = tracked.TryGetValue(key, out t) ? t.Fsm : null;
ReadVariables(r, fsm != null ? fsm.FsmVariables : null);
if (fsm == null) continue;
fsm.transform.localRotation = rot;
if (!string.IsNullOrEmpty(state) && fsm.ActiveStateName != state)
{
FsmState target = fsm.Fsm.GetState(state);
if (target != null) FsmSync.SwitchStateSilently(fsm, target); // z.B. Regen beginnt/hört auf
}
}
ReadVariables(r, FsmVariables.GlobalVariables);
}
static void ReadVariables(BinaryReader r, FsmVariables vars)
{
int floats = r.ReadInt32();
for (int i = 0; i < floats; i++)
{
string name = r.ReadString();
float value = r.ReadSingle();
FsmFloat v = vars != null ? vars.FindFsmFloat(name) : null;
if (v != null) v.Value = value;
}
int ints = r.ReadInt32();
for (int i = 0; i < ints; i++)
{
string name = r.ReadString();
int value = r.ReadInt32();
FsmInt v = vars != null ? vars.FindFsmInt(name) : null;
if (v != null) v.Value = value;
}
int bools = r.ReadInt32();
for (int i = 0; i < bools; i++)
{
string name = r.ReadString();
bool value = r.ReadBoolean();
FsmBool v = vars != null ? vars.FindFsmBool(name) : null;
if (v != null) v.Value = value;
}
}
// ---------------------------------------------------------------- Suchen
void Discover()
{
nextDiscover = Time.time + DiscoverRetry;
tracked.Clear();
foreach (PlayMakerFSM fsm in SceneCache.Fsms(20f))
{
if (fsm == null) continue;
if (Array.IndexOf(TrackedFsmNames, fsm.FsmName) < 0) continue;
string root = fsm.transform.root.name;
if (root == "PLAYER" || root.StartsWith("MSCO_")) continue;
string key = FsmSync.PathOf(fsm.transform) + "|" + fsm.FsmName;
if (!tracked.ContainsKey(key)) tracked[key] = new Tracked { Key = key, Fsm = fsm };
}
if (tracked.Count == 0) return; // Welt noch nicht fertig geladen - später nochmal
discovered = true;
MscoLog.Info("[MSCO] Time/weather sync: " + tracked.Count + " FSMs");
if (FsmSync.Debug)
foreach (string key in tracked.Keys) MscoLog.Info("[MSCO] " + key);
}
}
}