- 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>
183 lines
6.8 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|