Files
BSO-Mod/LocalPlayer.cs
MrMahoandClaude Opus 5.5 51c1ebc938 0.4.0: Mitspieler in der Welt, BSO-Konto (Spiel-Ticket)
- 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>
2026-10-28 18:10:48 +01:00

145 lines
5.6 KiB
C#

using System.Diagnostics;
using BSO.Net;
namespace BSO
{
/// <summary>
/// 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.
/// </summary>
public static class LocalPlayer
{
const float RelockDistance = 3000; // cm: so weit weg von der Kamera, obwohl man faehrt -> anderer Bus
/// <summary>Ein verfolgtes Objekt: Platz in der Entity-Tabelle + Zeiger (zusammen = lebt noch).</summary>
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; }
/// <summary>cm pro Sekunde (aus der Bewegung des Busses bzw. der Figur).</summary>
public static float Speed { get; private set; }
public static PlayerMode Mode => !InScene ? PlayerMode.Menu : Character.Valid ? PlayerMode.OnFoot : Bus.Valid ? PlayerMode.InBus : PlayerMode.Menu;
/// <summary>Was an die anderen geht: Position/Drehung des Busses im Bus, sonst der Figur.</summary>
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();
if (character != null) Models.RememberCharacter(character.Value.Ptr);
}
if (Models.Bus == null && nearestCar != null) Models.RememberFallbackVehicle(nearestCar.Value.Ptr);
// 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();
Models.RememberBus(nearestCar.Value.Ptr);
}
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();
}
}
}