- 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>
64 lines
2.8 KiB
C#
64 lines
2.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace BSO
|
|
{
|
|
/// <summary>
|
|
/// Modelldatei einer Entity finden (z.B. Models\Vehicles\Citaro\Citaro12m\...\.model). Die .pak-Dateien des Spiels sind
|
|
/// verschluesselt und VisBaseEntity_cl::GetMesh ist nicht exportiert - also: Entity -> Mesh (Zeiger in der Entity) ->
|
|
/// Dateiname (char* im Mesh) durchsuchen. Jeder Zeiger wird vorher geprueft (Mem).
|
|
/// </summary>
|
|
public static class Models
|
|
{
|
|
/// <summary>Modell des eigenen Busses / der eigenen Figur - werden fuer die Mitspieler benutzt.</summary>
|
|
public static string Bus { get; private set; }
|
|
public static string Character { get; private set; }
|
|
|
|
static readonly Dictionary<nint, string> byEntity = new();
|
|
|
|
/// <summary>Solange man noch nicht im eigenen Bus sass: irgendein Fahrzeug aus der Szene.</summary>
|
|
static string fallbackVehicle;
|
|
public static string Vehicle => Bus ?? fallbackVehicle;
|
|
|
|
public static void RememberBus(nint entity) => Bus ??= Remember(entity, "Bus");
|
|
public static void RememberCharacter(nint entity) => Character ??= Remember(entity, "Spielfigur");
|
|
public static void RememberFallbackVehicle(nint entity) => fallbackVehicle ??= Remember(entity, "Fahrzeug (Ersatz, bis du im Bus sitzt)");
|
|
|
|
static string Remember(nint entity, string what)
|
|
{
|
|
string file = Find(entity);
|
|
Bootstrap.Log(file != null ? $"Modell {what}: {file}" : $"Modell {what}: nicht gefunden");
|
|
return file;
|
|
}
|
|
|
|
/// <summary>Modelldatei der Entity oder null.</summary>
|
|
public static string Find(nint entity)
|
|
{
|
|
if (byEntity.TryGetValue(entity, out string known)) return known;
|
|
string found = null;
|
|
for (int off = 8; off < 0x400 && found == null; off += 8)
|
|
{
|
|
if (!Mem.TryReadPtr(entity + off, out nint p) || p == 0 || Mem.IsImage(p)) continue;
|
|
found = ModelString(p); // Dateiname direkt in der Entity
|
|
for (int off2 = 0; off2 < 0x100 && found == null; off2 += 8)
|
|
{
|
|
// ... oder im Mesh, auf das die Entity zeigt
|
|
if (!Mem.TryReadPtr(p + off2, out nint q) || q == 0 || Mem.IsImage(q)) continue;
|
|
found = ModelString(q);
|
|
}
|
|
}
|
|
byEntity[entity] = found;
|
|
return found;
|
|
}
|
|
|
|
static string ModelString(nint p)
|
|
{
|
|
if (!Mem.IsReadable(p, 8)) return null;
|
|
string s = Mem.ReadAscii(p, 260);
|
|
if (s.Length < 8 || !s.EndsWith(".model", StringComparison.OrdinalIgnoreCase)) return null;
|
|
foreach (char c in s) if (c < 0x20 || c > 0x7e) return null;
|
|
return s;
|
|
}
|
|
}
|
|
}
|