- Mitspieler als Bus/Spielfigur mit Namensschild, weich bewegt (RemotePlayers, Models) - Ohne gueltiges Spiel-Ticket vom Launcher kein Multiplayer; Name kommt aus dem Konto - Protokoll 2 (Hello mit Ticket), IPv6-Adressen - Nach Szenenwechsel erst nach 3 s neu anlegen (Absturz im Test 0.3.0) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
126 lines
5.5 KiB
C#
126 lines
5.5 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using BSO.Net;
|
|
|
|
namespace BSO
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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<int, Shown> 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);
|
|
}
|
|
|
|
/// <summary>Namensschilder - bei jedem Durchlauf wie der Text oben links (sonst flackern sie).</summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|