- 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>
145 lines
5.6 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|