using System; using System.IO; using HutongGames.PlayMaker; using UnityEngine; namespace MSCO { /// /// Admin-Menü, Reiter WORLD: Uhrzeit und Wetter umstellen. Beides läuft im Spiel über PlayMaker: /// - Uhr: die Sonnen-FSM hat je 2 Stunden einen Zustand ("00-02" ... "22-00") und die Zahl "Time"; beim Laden /// setzt das Spiel "Time" und wählt in "State 1" den passenden Zustand - genau so stellen wir die Zeit. /// - Wetter: die FSM "Weather" (Wolkenfront) mit den Zuständen "No weather", "Rain" und "Thunder"; bei Regen /// wird die Front über den Admin gesetzt, damit es dort auch regnet. /// Die anderen Spieler bekommen den Befehl über eine MSCO-Nachricht und führen ihn genauso aus. /// public static class WorldAdmin { public const string MessageId = "msco.world"; public const byte KindHour = 1, KindWeather = 2; public static readonly string[] WeatherStates = { "No weather", "Rain", "Thunder" }; public static readonly string[] WeatherEvents = { "NORAIN", "RAIN", "THUNDER" }; public static readonly string[] WeatherNames = { "clear", "rain", "thunder" }; static PlayMakerFSM sun, weather; static float searchedAt = -100f; static void Find() { if (sun != null && weather != null) return; if (Time.realtimeSinceStartup - searchedAt < 3f) return; searchedAt = Time.realtimeSinceStartup; foreach (PlayMakerFSM f in SceneCache.Fsms(5f)) { if (f == null) continue; if (sun == null && f.FsmVariables.FindFsmInt("Time") != null && f.Fsm.GetState("12-14") != null && f.Fsm.GetState("22-00") != null && f.Fsm.GetState("State 1") != null && f.Fsm.GetState("Next day") != null) sun = f; if (weather == null && f.FsmName == "Weather" && f.Fsm.GetState("No weather") != null && f.Fsm.GetState("Rain") != null) weather = f; } } // ---------------------------------------------------------------- Anzeige /// Aktueller 2-Stunden-Block der Uhr (Startstunde) oder -1. public static int CurrentHour { get { Find(); string s = sun != null ? sun.ActiveStateName : null; int h; if (s != null && s.Length == 5 && s[2] == '-' && int.TryParse(s.Substring(0, 2), out h)) return h; return -1; } } /// Aktuelles Wetter (0 klar, 1 Regen, 2 Gewitter) oder -1. public static int CurrentWeather { get { Find(); if (weather == null) return -1; FsmInt type = weather.FsmVariables.FindFsmInt("WeatherType"); if (type == null) return -1; FsmState check = weather.Fsm.GetState("State 2"); for (int i = 0; i < WeatherEvents.Length; i++) { int value; if (check != null && IntCompareValue(check, WeatherEvents[i], out value) && value == type.Value) return i; } return -1; } } // ---------------------------------------------------------------- Ausführen /// Befehl lokal ausführen. Gibt null zurück oder einen Fehlertext. public static string Apply(byte kind, int value, Vector3 position) { Find(); try { if (kind == KindHour) return SetHour(value); if (kind == KindWeather) return SetWeather(value, position); } catch (Exception e) { MscoLog.Error("[MSCO] world admin: " + e); return "failed: " + e.Message; } return "unknown command"; } static string SetHour(int hour) { if (sun == null) return "Clock not found."; int start = ((hour % 24 + 24) % 24) / 2 * 2; string block = start.ToString("00") + "-" + ((start + 2) % 24).ToString("00"); FsmState target = sun.Fsm.GetState(block); if (target == null) return "Clock state " + block + " not found."; // wie beim Laden: "Time" setzen, "State 1" wählt daraus den Zustand (und stellt Licht/Farben ein) FsmState dispatch = sun.Fsm.GetState("State 1"); int timeValue; if (dispatch != null && IntCompareValue(dispatch, block, out timeValue)) { sun.FsmVariables.FindFsmInt("Time").Value = timeValue; FsmSync.SwitchStateSilently(sun, dispatch); } else FsmSync.SwitchStateSilently(sun, target); return null; } static string SetWeather(int kind, Vector3 position) { if (weather == null) return "Weather not found."; if (kind < 0 || kind >= WeatherStates.Length) return "Unknown weather."; FsmState state = weather.Fsm.GetState(WeatherStates[kind]); if (state == null) return "Weather state not found."; FsmSync.SwitchStateSilently(weather, state); if (kind > 0) MoveFrontOver(position); return null; } /// /// Wolkenfront über eine Stelle setzen: "Move clouds" setzt jedes Bild die Position aus PosX/PosZ - also diese /// beiden so wählen, dass die Front über der Stelle steht (Koordinatenraum aus der SetPosition-Aktion). /// static void MoveFrontOver(Vector3 position) { FsmState move = weather.Fsm.GetState("Move clouds"); if (move == null) return; if (!move.ActionsLoaded) move.LoadActions(); if (move.Actions == null) return; foreach (FsmStateAction a in move.Actions) { if (a == null || a.GetType().Name != "SetPosition") continue; Type t = a.GetType(); var owner = Field(t, a, "gameObject") as FsmOwnerDefault; var x = Field(t, a, "x") as FsmFloat; var z = Field(t, a, "z") as FsmFloat; object space = Field(t, a, "space"); GameObject go = owner != null ? weather.Fsm.GetOwnerDefaultTarget(owner) : null; if (go == null) continue; bool local = space is Space && (Space)space == Space.Self && go.transform.parent != null; Vector3 p = local ? go.transform.parent.InverseTransformPoint(position) : position; SetVar(x, p.x); SetVar(z, p.z); go.transform.position = new Vector3(position.x, go.transform.position.y, position.z); return; } } static void SetVar(FsmFloat field, float value) { if (field == null || field.IsNone || string.IsNullOrEmpty(field.Name)) return; FsmFloat v = weather.FsmVariables.FindFsmFloat(field.Name); if (v != null) v.Value = value; } static object Field(Type t, object o, string name) { var f = t.GetField(name); return f != null ? f.GetValue(o) : null; } /// In einem Verteiler-Zustand die Zahl finden, bei der IntCompare das Event auslöst ("gleich"). static bool IntCompareValue(FsmState state, string eventName, out int value) { value = 0; if (!state.ActionsLoaded) state.LoadActions(); if (state.Actions == null) return false; foreach (FsmStateAction a in state.Actions) { if (a == null || a.GetType().Name != "IntCompare") continue; Type t = a.GetType(); var equal = Field(t, a, "equal") as FsmEvent; var number = Field(t, a, "integer2") as FsmInt; if (equal == null || number == null || equal.Name != eventName) continue; value = number.Value; return true; } return false; } // ---------------------------------------------------------------- Netz public static byte[] Build(byte kind, int value, Vector3 position) { using (var ms = new MemoryStream()) using (var w = new BinaryWriter(ms)) { w.Write(kind); w.Write(value); w.Write(position.x); w.Write(position.y); w.Write(position.z); w.Flush(); return ms.ToArray(); } } public static void Receive(byte[] data) { if (!MenuHelper.InGame || data == null || data.Length < 17) return; using (var r = new BinaryReader(new MemoryStream(data))) { byte kind = r.ReadByte(); int value = r.ReadInt32(); var pos = new Vector3(r.ReadSingle(), r.ReadSingle(), r.ReadSingle()); string error = Apply(kind, value, pos); if (error != null) MscoLog.Warning("[MSCO] world admin from other player: " + error); } } } }