using System.Collections.Generic; using System.Linq; using BSO.Net; namespace BSO { /// /// Mitspieler in der Welt: je Spieler eine eigene Entity mit dem Modell des eigenen Busses bzw. der eigenen Spielfigur /// (Models), jedes Bild auf den weich bewegten Stand gesetzt (Remote.Display), darueber ein Namensschild. Die Entities /// sind nur Grafik (VisBaseEntity_cl, keine Physik): man faehrt durch sie hindurch. /// public static class RemotePlayers { const float TagHeightBus = 420, TagHeightFoot = 215; // cm ueber dem Ursprung const float TagMaxDistance = 30000; // weiter weg: kein Schild (300 m) class Shown { public nint Entity; public int Slot = -1; public PlayerMode Mode; public string Model; public byte[] Tag; public Vec3 TagPos; public bool TagVisible; } static readonly Dictionary shown = new(); static bool warned; // Nach einem Szenenwechsel (Entity von der Engine entfernt) erst nach einer Pause neu anlegen - mitten im Abbau // der Welt eine Entity anzulegen liess das Spiel abstuerzen (Test 0.3.0: zweimal neu angelegt, dann Ende) static readonly System.Diagnostics.Stopwatch sceneCalm = System.Diagnostics.Stopwatch.StartNew(); const int RespawnDelayMs = 3000; public static void Update() { if (!Vision.CanSpawn) { if (!warned) { warned = true; Bootstrap.Log("Mitspieler in der Welt: Engine-Funktionen fehlen - nur Liste"); } return; } // Szene weg oder man ist nicht in der Welt (Menue, Laden): die Engine raeumt unsere Entities mit weg - nur vergessen, // nichts anfassen und nichts anlegen PlayerState me = LocalPlayer.State; if (!LocalPlayer.InScene || me.Mode == PlayerMode.Menu) { if (shown.Count > 0) Bootstrap.Log("Nicht in der Welt - Mitspieler-Modelle vergessen"); shown.Clear(); sceneCalm.Restart(); return; } // Gegangen oder getrennt foreach (int id in shown.Keys.Where(id => !Multiplayer.Players.ContainsKey(id)).ToList()) Remove(id); foreach (Multiplayer.Remote p in Multiplayer.Players.Values) { if (!p.HasState || p.State.Mode == PlayerMode.Menu) { Remove(p.Id); continue; } PlayerState s = p.Display; string model = s.Mode == PlayerMode.InBus ? Models.Vehicle : Models.Character; shown.TryGetValue(p.Id, out Shown v); // Ein-/Aussteigen oder besseres Modell bekannt (eigener Bus statt Ersatzfahrzeug): neu anlegen if (v != null && (v.Mode != s.Mode || v.Model != model)) { Remove(p.Id); v = null; } // Von der Engine entfernt? (z.B. beim Neuladen der Stadt) - nach einer Pause neu anlegen if (v != null && v.Entity != 0 && !Entities.StillThere(v.Slot, v.Entity)) { Bootstrap.Log($"{p.Name}: Modell von der Engine entfernt - lege es nach {RespawnDelayMs / 1000} s neu an"); shown.Remove(p.Id); v = null; sceneCalm.Restart(); } var pos = new Vec3(s.X, s.Y, s.Z); if (v == null) { if (sceneCalm.ElapsedMilliseconds < RespawnDelayMs) continue; v = new Shown { Mode = s.Mode, Model = model, Tag = Vision.ToAscii(p.Name) }; shown[p.Id] = v; if (model != null) Spawn(p, v, pos); else Bootstrap.Log($"{p.Name}: noch kein Modell fuer \"{s.Mode}\" bekannt - nur Namensschild"); } if (v.Entity != 0) { Vision.SetPosition(v.Entity, pos); Vision.SetOrientation(v.Entity, new Vec3(s.Yaw, s.Pitch, s.Roll)); } float dist = me.Mode == PlayerMode.Menu ? 0 : s.DistanceTo(me); v.TagVisible = dist < TagMaxDistance; v.TagPos = new Vec3(s.X, s.Y, s.Z + (s.Mode == PlayerMode.InBus ? TagHeightBus : TagHeightFoot)); v.Tag = Vision.ToAscii(me.Mode == PlayerMode.Menu ? p.Name : $"{p.Name} ({dist / 100:0} m)"); } } static void Spawn(Multiplayer.Remote p, Shown v, Vec3 pos) { v.Entity = Vision.CreateEntity("VisBaseEntity_cl", pos, v.Model); v.Slot = Entities.SlotOf(v.Entity); if (v.Entity == 0 || v.Slot < 0) { Bootstrap.Log($"{p.Name}: Entity mit {v.Model} liess sich nicht anlegen"); v.Entity = 0; return; } Bootstrap.Log($"{p.Name} erscheint in der Welt ({v.Mode}, {v.Model}, Platz {v.Slot})"); } static void Remove(int id) { if (!shown.Remove(id, out Shown v)) return; if (v.Entity != 0 && Entities.StillThere(v.Slot, v.Entity)) Vision.RemoveEntity(v.Entity); } /// Namensschilder - bei jedem Durchlauf wie der Text oben links (sonst flackern sie). public static void Draw() { if (!Vision.CanSpawn || !LocalPlayer.InScene) return; foreach (Shown v in shown.Values) if (v.TagVisible) Vision.DrawText3D(v.Tag, v.TagPos); } } }