using System.Diagnostics; using BSO.Net; namespace BSO { /// /// Der eigene Spieler: Kamera, Spielfigur (KeyControlledCharacter) und eigener Bus (CarVehicle). Im Bus verschwindet /// die Figur aus der Szene - nur dann wird der eigene Bus festgelegt (das CarVehicle naechst der Kamera, im Cockpit /// ~5 m) und bleibt es, bis er verschwindet oder man in einem anderen Bus sitzt. Sonst springt die Zuordnung zu /// vorbeifahrendem Verkehr. Die Engine rechnet in cm. /// public static class LocalPlayer { const float RelockDistance = 3000; // cm: so weit weg von der Kamera, obwohl man faehrt -> anderer Bus /// Ein verfolgtes Objekt: Platz in der Entity-Tabelle + Zeiger (zusammen = lebt noch). public class Tracked { public int Slot = -1; public nint Ptr; public Vec3 Pos, Rot; public bool Valid => Ptr != 0; public void Set(EntityInfo? e) { Ptr = e?.Ptr ?? 0; Slot = e?.Slot ?? -1; } public void Refresh() { if (Ptr != 0 && !Entities.StillThere(Slot, Ptr)) Ptr = 0; if (Ptr == 0) return; Pos = Vision.GetPosition(Ptr); Rot = Vision.GetOrientation(Ptr); } } public static readonly Tracked Bus = new(), Character = new(); public static bool InScene { get; private set; } public static Vec3 Camera { get; private set; } /// cm pro Sekunde (aus der Bewegung des Busses bzw. der Figur). public static float Speed { get; private set; } public static PlayerMode Mode => !InScene ? PlayerMode.Menu : Character.Valid ? PlayerMode.OnFoot : Bus.Valid ? PlayerMode.InBus : PlayerMode.Menu; /// Was an die anderen geht: Position/Drehung des Busses im Bus, sonst der Figur. public static PlayerState State { get { PlayerMode mode = Mode; Tracked t = mode == PlayerMode.InBus ? Bus : Character; if (mode == PlayerMode.Menu) return new PlayerState { Mode = mode }; return new PlayerState { Mode = mode, X = t.Pos.X, Y = t.Pos.Y, Z = t.Pos.Z, Yaw = t.Rot.X, Pitch = t.Rot.Y, Roll = t.Rot.Z, Speed = Speed }; } } static bool sceneLogged; static readonly Stopwatch speedClock = new(), findClock = new(); static Vec3 lastPos; static PlayerMode lastMode; public static void Update() { nint cam = Vision.MainCamera; InScene = cam != 0 && Rtti.ClassName(cam) != null; // sonst Menue / Laden if (!InScene) return; Camera = Vision.GetPosition(cam); if (!sceneLogged) { sceneLogged = true; Bootstrap.Log("Szene da, Kamera bei " + Camera); } Bus.Refresh(); Character.Refresh(); // Alle Entities durchsuchen kostet - 4x pro Sekunde reicht if (!findClock.IsRunning || findClock.ElapsedMilliseconds >= 250) { findClock.Restart(); Find(); } UpdateSpeed(); } static void Find() { EntityInfo? nearestCar = null, character = null; float bestDist = float.MaxValue; foreach (EntityInfo e in Entities.Scan()) { if (e.Class == "KeyControlledCharacter") character = e; if (e.Class != "CarVehicle") continue; float d = (e.Pos - Camera).Length; if (d < bestDist) { bestDist = d; nearestCar = e; } } if ((character?.Ptr ?? 0) != Character.Ptr) { Bootstrap.Log(character != null ? $"Spieler = KeyControlledCharacter in Platz {character.Value.Slot}" : "Spielfigur weg - Spieler sitzt im Bus"); Character.Set(character); Character.Refresh(); } // Bus nur festlegen, wenn man drinsitzt (keine Spielfigur), und nur wechseln, wenn der alte fehlt oder weit weg ist bool driving = character == null; bool keep = Bus.Valid && (!driving || (Bus.Pos - Camera).Length < RelockDistance); if (keep || !driving || nearestCar == null || nearestCar.Value.Ptr == Bus.Ptr) return; Bootstrap.Log($"Eigener Bus = CarVehicle in Platz {nearestCar.Value.Slot}, {bestDist / 100:0.0} m von der Kamera"); Bus.Set(nearestCar); Bus.Refresh(); } static void UpdateSpeed() { PlayerMode mode = Mode; Vec3 pos = mode == PlayerMode.InBus ? Bus.Pos : Character.Pos; if (mode != lastMode || !speedClock.IsRunning) { lastMode = mode; lastPos = pos; speedClock.Restart(); Speed = 0; return; } double seconds = speedClock.Elapsed.TotalSeconds; if (seconds < 0.25) return; Speed = (float)((pos - lastPos).Length / seconds); lastPos = pos; speedClock.Restart(); } } }