MSCO 2.10.2: passenger only while moving, drain plug sync, admin VEHICLE tab, profile layout

- Passenger: auto ride-along only when the vehicle moves (speed from its motion, also for
  kinematic remote cars) - no more getting stuck while working in the engine bay or re-captured
- Bolts with a function (drain plug: stage 0-5 = oil leak on) are synced as stage and applied
  through the bolt's own stage logic; world authority sends its state once after joining.
  Fixes endless oil leaking when players had the drain plug in different states
- Admin menu VEHICLE tab: fluids with FILL/EMPTY, all Database parts with wear/damage,
  REPAIR/BREAK per part, REPAIR ALL, filter
- Server profile: info and players as two equal cards, no wrapping
- fsmdump also lists FSM variables

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 12:02:21 +02:00
co-authored by Claude Opus 5.5
parent adc6eff9e8
commit 7b4a89d50f
11 changed files with 462 additions and 41 deletions
+110 -6
View File
@@ -293,24 +293,128 @@ namespace MSCO
// Reiter
GUILayout.BeginHorizontal();
string[] tabs = { "PLAYERS", "TELEPORT", "ITEMS", "BANS" + (bannedIds != null && bannedIds.Count > 0 ? " (" + bannedIds.Count + ")" : ""), "WORLD" };
string[] tabs = { "PLAYERS", "TELEPORT", "VEHICLE", "ITEMS", "BANS" + (bannedIds != null && bannedIds.Count > 0 ? " (" + bannedIds.Count + ")" : ""), "WORLD" };
for (int i = 0; i < tabs.Length; i++)
{
if (GUILayout.Button(tabs[i], adminTab == i ? primary : secondary, GUILayout.Height(30), GUILayout.Width(120))) adminTab = i;
if (GUILayout.Button(tabs[i], adminTab == i ? primary : secondary, GUILayout.Height(30), GUILayout.Width(104))) adminTab = i;
GUILayout.Space(6);
}
GUILayout.EndHorizontal();
GUILayout.Space(10);
if (adminTab == 1) DrawAdminTeleport(mp);
else if (adminTab == 2) DrawAdminItems(mp);
else if (adminTab == 3) DrawAdminBans(mp);
else if (adminTab == 4) DrawAdminWorld(mp);
else if (adminTab == 2) DrawAdminVehicle(mp);
else if (adminTab == 3) DrawAdminItems(mp);
else if (adminTab == 4) DrawAdminBans(mp);
else if (adminTab == 5) DrawAdminWorld(mp);
else DrawAdminPlayers(mp);
GUILayout.EndArea();
}
Vector2 teleportScroll;
Vector2 teleportScroll, vehicleScroll;
Texture2D texBarBack, texBarFill, texBarLow;
string partFilter = "";
/// <summary>Reiter VEHICLE: Füllstände und Teile des Satsuma reparieren/zerstören (zum Testen).</summary>
void DrawAdminVehicle(MultiplayerManager mp)
{
if (!mp.CanCheat)
{
GUILayout.Label("Only works in the game.", small);
return;
}
if (texBarBack == null)
{
texBarBack = UiTheme.Solid(new Color(1f, 1f, 1f, 0.1f));
texBarFill = UiTheme.Solid(UiTheme.Online);
texBarLow = UiTheme.Solid(UiTheme.Danger);
}
GUILayout.BeginHorizontal();
if (GUILayout.Button("REPAIR ALL", primary, GUILayout.Height(30), GUILayout.Width(130)))
{
int n = VehicleAdmin.RepairAll();
Log(n + " part(s) repaired");
if (n > 0) mp.VehicleCheat("repaired the Satsuma (" + n + " parts)");
}
GUILayout.Space(6);
if (GUILayout.Button("REFRESH", secondary, GUILayout.Height(30), GUILayout.Width(100))) VehicleAdmin.Refresh(true);
GUILayout.Space(12);
GUILayout.Label("Filter", caption, GUILayout.Width(44));
partFilter = GUILayout.TextField(partFilter ?? "", 30, field, GUILayout.Height(30));
GUILayout.EndHorizontal();
GUILayout.Space(8);
vehicleScroll = GUILayout.BeginScrollView(vehicleScroll);
GUILayout.Label("FLUIDS", caption);
List<VehicleAdmin.Fluid> fluids = VehicleAdmin.Fluids;
if (fluids.Count == 0) GUILayout.Label("No fluids found (only works in the game).", small);
foreach (VehicleAdmin.Fluid f in fluids)
{
GUILayout.BeginHorizontal(rowStyle, GUILayout.Height(36));
GUILayout.Label(f.Label, chatName, GUILayout.Width(170));
Rect bar = GUILayoutUtility.GetRect(120, 36, GUILayout.ExpandWidth(true));
float frac = f.Max > 0f ? Mathf.Clamp01(f.Value.Value / f.Max) : 0f;
var back = new Rect(bar.x, bar.y + 15, bar.width, 6);
GUI.DrawTexture(back, texBarBack);
GUI.DrawTexture(new Rect(back.x, back.y, Mathf.Max(2f, back.width * frac), back.height), frac < 0.25f ? texBarLow : texBarFill);
GUILayout.Label(f.Value.Value.ToString("0.00") + " / " + f.Max.ToString("0.#"), small, GUILayout.Width(90));
if (GUILayout.Button("FILL", secondary, GUILayout.Height(26), GUILayout.Width(64)))
{
VehicleAdmin.SetFluid(f, true);
mp.VehicleCheat("filled up " + f.Label.ToLower());
}
GUILayout.Space(4);
if (GUILayout.Button("EMPTY", danger, GUILayout.Height(26), GUILayout.Width(70)))
{
VehicleAdmin.SetFluid(f, false);
mp.VehicleCheat("emptied " + f.Label.ToLower());
}
GUILayout.EndHorizontal();
GUILayout.Space(3);
}
GUILayout.Space(10);
GUILayout.Label("PARTS (wear: 100 = new)", caption);
string filter = (partFilter ?? "").Trim().ToLowerInvariant();
int shown = 0;
foreach (VehicleAdmin.Part p in VehicleAdmin.Parts)
{
if (filter.Length > 0 && !(p.Name + " " + p.Category).ToLowerInvariant().Contains(filter)) continue;
shown++;
bool installed = p.Installed == null || p.Installed.Value;
bool damaged = p.Damaged != null && p.Damaged.Value;
GUILayout.BeginHorizontal(rowStyle, GUILayout.Height(36));
GUILayout.BeginVertical(GUILayout.Width(220));
GUILayout.Label(p.Name, chatName);
GUILayout.Label(p.Category + (installed ? "" : " · not installed"), small);
GUILayout.EndVertical();
Rect bar = GUILayoutUtility.GetRect(100, 36, GUILayout.ExpandWidth(true));
if (p.Wear != null)
{
float frac = Mathf.Clamp01(p.Wear.Value / 100f);
var back = new Rect(bar.x, bar.y + 15, bar.width, 6);
GUI.DrawTexture(back, texBarBack);
GUI.DrawTexture(new Rect(back.x, back.y, Mathf.Max(2f, back.width * frac), back.height), frac < 0.3f ? texBarLow : texBarFill);
}
GUILayout.Label(damaged ? "DAMAGED" : p.Wear != null ? p.Wear.Value.ToString("0") + " %" : "OK", damaged ? danger : small, GUILayout.Width(84));
if (GUILayout.Button("REPAIR", secondary, GUILayout.Height(26), GUILayout.Width(76)))
{
VehicleAdmin.Repair(p);
mp.VehicleCheat("repaired " + p.Name);
}
GUILayout.Space(4);
if (GUILayout.Button("BREAK", danger, GUILayout.Height(26), GUILayout.Width(70)))
{
VehicleAdmin.Break(p);
mp.VehicleCheat("broke " + p.Name + " (testing)");
}
GUILayout.EndHorizontal();
GUILayout.Space(3);
}
if (shown == 0) GUILayout.Label(VehicleAdmin.Parts.Count == 0 ? "No parts found (only works in the game)." : "No part matches the filter.", small);
GUILayout.EndScrollView();
}
void DrawAdminTeleport(MultiplayerManager mp)
{