2.8: sync bought/consumed items, wheel spin, crash damage, jobs/shop, traffic
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+155
@@ -0,0 +1,155 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using HutongGames.PlayMaker;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MSCO
|
||||
{
|
||||
/// <summary>
|
||||
/// Jobs, Aufträge und Laden: deren Fortschritt steht in PlayMaker-FSMs (z.B. "Auftrag angenommen", Anzahl
|
||||
/// geleerter Klärgruben, bestellte Teile). Übertragen werden nur Ganzzahlen und Schalter - keine Kommazahlen
|
||||
/// (Timer, Positionen), die jeder selbst weiterzählt. Wer etwas ändert, schickt es; Werte, die ständig hin und her
|
||||
/// springen (interne Zähler), werden nach kurzer Zeit nicht mehr übertragen.
|
||||
/// </summary>
|
||||
public class WorldVarSync
|
||||
{
|
||||
/// <summary>Wurzelobjekte mit Jobs/Aufträgen/Laden.</summary>
|
||||
public static readonly string[] Roots = { "JOBS", "STORE", "REPAIRSHOP", "INSPECTION", "LANDFILL", "DANCEHALL", "COTTAGE", "RALLY" };
|
||||
|
||||
const float ScanInterval = 15f;
|
||||
const float CheckInterval = 0.5f;
|
||||
const float MinResend = 2f; // höchstens so oft pro Wert
|
||||
const int FlipLimit = 6; // so viele Änderungen ...
|
||||
const float FlipWindow = 30f; // ... in dieser Zeit = interner Zähler, nicht mehr übertragen
|
||||
const int MaxPerMessage = 200;
|
||||
|
||||
class Var
|
||||
{
|
||||
public int Key;
|
||||
public NamedVariable Variable;
|
||||
public byte Type; // 2 int, 3 bool
|
||||
public int LastI;
|
||||
public bool LastB;
|
||||
public float LastSent = -100f;
|
||||
public float WindowStart;
|
||||
public int Flips;
|
||||
public bool Muted;
|
||||
}
|
||||
|
||||
readonly Dictionary<int, Var> vars = new Dictionary<int, Var>();
|
||||
readonly HashSet<PlayMakerFSM> scanned = new HashSet<PlayMakerFSM>();
|
||||
readonly List<Var> outgoing = new List<Var>();
|
||||
float nextScan, nextCheck;
|
||||
|
||||
public int Count { get { return vars.Count; } }
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
vars.Clear();
|
||||
scanned.Clear();
|
||||
nextScan = 0f;
|
||||
}
|
||||
|
||||
public void Update(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 (Var v in vars.Values)
|
||||
{
|
||||
if (v.Muted || !Changed(v)) continue;
|
||||
if (Time.time - v.LastSent < MinResend) continue; // später nochmal prüfen
|
||||
Remember(v);
|
||||
if (Time.time - v.WindowStart > FlipWindow) { v.WindowStart = Time.time; v.Flips = 0; }
|
||||
if (++v.Flips > FlipLimit) { v.Muted = true; continue; }
|
||||
v.LastSent = Time.time;
|
||||
outgoing.Add(v);
|
||||
}
|
||||
|
||||
for (int start = 0; start < outgoing.Count; start += MaxPerMessage)
|
||||
{
|
||||
int from = start, count = Math.Min(MaxPerMessage, outgoing.Count - start);
|
||||
send(MsgType.WorldVars, NetSession.Build(w =>
|
||||
{
|
||||
w.Write(count);
|
||||
for (int i = from; i < from + count; i++)
|
||||
{
|
||||
Var v = outgoing[i];
|
||||
w.Write(v.Key);
|
||||
w.Write(v.Type);
|
||||
if (v.Type == 2) w.Write(v.LastI); else w.Write(v.LastB);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(BinaryReader r)
|
||||
{
|
||||
int count = r.ReadInt32();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int key = r.ReadInt32();
|
||||
byte type = r.ReadByte();
|
||||
int iv = 0; bool bv = false;
|
||||
if (type == 2) iv = r.ReadInt32(); else if (type == 3) bv = r.ReadBoolean(); else return;
|
||||
Var v;
|
||||
if (!vars.TryGetValue(key, out v) || v.Type != type) continue;
|
||||
if (type == 2) ((FsmInt)v.Variable).Value = iv; else ((FsmBool)v.Variable).Value = bv;
|
||||
Remember(v); // nicht zurückschicken
|
||||
}
|
||||
}
|
||||
|
||||
void Scan()
|
||||
{
|
||||
int before = vars.Count;
|
||||
foreach (PlayMakerFSM fsm in SceneCache.Fsms(5f))
|
||||
{
|
||||
if (fsm == null || !scanned.Add(fsm)) continue;
|
||||
if (Array.IndexOf(Roots, fsm.transform.root.name) < 0) continue;
|
||||
string prefix = FsmSync.PathOf(fsm.transform) + "|" + fsm.FsmName + "|";
|
||||
FsmVariables fv = fsm.FsmVariables;
|
||||
foreach (FsmInt n in fv.IntVariables) Register(prefix, n, 2);
|
||||
foreach (FsmBool b in fv.BoolVariables) Register(prefix, b, 3);
|
||||
}
|
||||
if (vars.Count != before) MscoLog.Info("[MSCO] Jobs/shop: " + vars.Count + " values synced");
|
||||
}
|
||||
|
||||
void Register(string prefix, NamedVariable variable, byte type)
|
||||
{
|
||||
if (variable == null || string.IsNullOrEmpty(variable.Name)) return;
|
||||
int key = Hash(prefix + variable.Name);
|
||||
if (vars.ContainsKey(key)) return;
|
||||
var v = new Var { Key = key, Variable = variable, Type = type };
|
||||
Remember(v);
|
||||
vars[key] = v;
|
||||
}
|
||||
|
||||
static bool Changed(Var v)
|
||||
{
|
||||
return v.Type == 2 ? ((FsmInt)v.Variable).Value != v.LastI : ((FsmBool)v.Variable).Value != v.LastB;
|
||||
}
|
||||
|
||||
static void Remember(Var v)
|
||||
{
|
||||
if (v.Type == 2) v.LastI = ((FsmInt)v.Variable).Value; else v.LastB = ((FsmBool)v.Variable).Value;
|
||||
}
|
||||
|
||||
static int Hash(string s)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
uint h = 2166136261;
|
||||
foreach (byte c in Encoding.UTF8.GetBytes(s)) { h ^= c; h *= 16777619; }
|
||||
return (int)h;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user