Decompiled source of SpectrumB Milsim System v2.20.74
Mods/SpectrumB_Milsim_System.dll
Decompiled 4 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Buffers.Binary; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Numerics; using System.Reflection; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using AudioImportLib; using BoneLib; using BoneLib.BoneMenu; using BoneLib.BoneMenu.UI; using HarmonyLib; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSLZ.Bonelab; using Il2CppSLZ.Data; using Il2CppSLZ.Interaction; using Il2CppSLZ.Marrow; using Il2CppSLZ.Marrow.AI; using Il2CppSLZ.Marrow.Audio; using Il2CppSLZ.Marrow.Combat; using Il2CppSLZ.Marrow.Data; using Il2CppSLZ.Marrow.Interaction; using Il2CppSLZ.Marrow.Pool; using Il2CppSLZ.Marrow.Utilities; using Il2CppSLZ.Marrow.Warehouse; using Il2CppSLZ.VFX; using Il2CppSLZ.VRMK; using Il2CppSLZ.Vehicle; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppSystem.Reflection; using Il2CppTMPro; using LabFusion.Audio; using LabFusion.Data; using LabFusion.Entities; using LabFusion.Extensions; using LabFusion.Marrow; using LabFusion.Marrow.Extenders; using LabFusion.Marrow.Messages; using LabFusion.Marrow.Serialization; using LabFusion.MonoBehaviours; using LabFusion.Network; using LabFusion.Network.Serialization; using LabFusion.Player; using LabFusion.RPC; using LabFusion.Representation; using LabFusion.SDK.Metadata; using LabFusion.SDK.Modules; using LabFusion.Scene; using LabFusion.Senders; using LabFusion.Utilities; using LabFusion.Voice; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using Microsoft.Win32.SafeHandles; using SpectrumB.Content; using SpectrumB.Handheld; using SpectrumB.Milsim; using SpectrumB.UI.Core; using SpectrumB.UI.Core.Statistics; using SpectrumB_Modding_Hub; using SpectrumB_Modding_Hub.Modules; using SpectrumB_Modding_Hub.Modules.Admin; using SpectrumB_Modding_Hub.Modules.Ballistics; using SpectrumB_Modding_Hub.Modules.BulletFX; using SpectrumB_Modding_Hub.Modules.CloudSync; using SpectrumB_Modding_Hub.Modules.Combat; using SpectrumB_Modding_Hub.Modules.Corpses; using SpectrumB_Modding_Hub.Modules.Developer; using SpectrumB_Modding_Hub.Modules.Diagnostics; using SpectrumB_Modding_Hub.Modules.FusionSync; using SpectrumB_Modding_Hub.Modules.Localization; using SpectrumB_Modding_Hub.Modules.Medical; using SpectrumB_Modding_Hub.Modules.Milsim; using SpectrumB_Modding_Hub.Modules.Milsim.Handheld; using SpectrumB_Modding_Hub.Modules.Platforms; using SpectrumB_Modding_Hub.Modules.PlayerLifecycle; using SpectrumB_Modding_Hub.Modules.Rendering; using SpectrumB_Modding_Hub.Modules.Slings; using SpectrumB_Modding_Hub.Modules.SoundFramework; using SpectrumB_Modding_Hub.Modules.SpectrumBUI; using SpectrumB_Modding_Hub.Modules.VisualFX; using Steamworks; using UnityEngine; using UnityEngine.AI; using UnityEngine.Audio; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using UnityEngine.SceneManagement; using UnityEngine.UI; using UnityEngine.XR; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "SpectrumB Milsim System", "2.20.74", "SpectrumB", null)] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: MelonOptionalDependencies(new string[] { "AudioImportLib" })] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("SpectrumB_Milsim_System")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("SpectrumB release notes v1:\nGlobal statistics reset support and old-upload protection.\r\nClears old local match history and replays after reset.\r\nPCVR teams, loadouts, squads, radios and medical play.\r\nHost-synced walls and late-join recovery.\r\nSafer updates, Pack recovery and saved-game refresh.\r\nPairing, controller and quick-menu fixes.\r\nMatching SpectrumB builds required for multiplayer.\r\n")] [assembly: AssemblyFileVersion("2.20.74.0")] [assembly: AssemblyInformationalVersion("2.20.74+53c73f65b7b62586c96b19b170c1b025ee8ee329")] [assembly: AssemblyProduct("SpectrumB_Milsim_System")] [assembly: AssemblyTitle("SpectrumB_Milsim_System")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.20.74.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace SpectrumB.UI.Core { public enum UiActionId { None, Navigate, Back, GoHome, SetWorkspace, OpenLoadoutSelector, OpenSetupTemplates, OpenWorkflowEditor, OpenMedical, OpenLanguage, OpenMap, SelectTeam, LeaveTeam, SelectLoadout, ToggleReady, Deploy, ReturnToSpawn, RequestVehicle, OpenMedicalQuickMenu, ApproveJoinRequest, DenyJoinRequest, ApproveVehicleRequest, DenyVehicleRequest, ToggleTeamLock, SetSquadQueueFilter, SelectRosterPlayer, StartMatch, StopMatch, CleanupSpawnedItems, ClearCorpses, ClearWrecks, AutoAssignTeams, ToggleModEnabled, SetLanguage, AcceptCloudInvite, DeclineCloudInvite, SelectBodyRegion, CyclePatient, SetPageIndex, PreviewInject, SelectSpawn, SelectAutomaticSpawn, PlaceRally, RemoveRally, ChangeInitialVehicleCount, CycleInitialVehicleSpawn, SpawnFactionFlag, DeleteFactionFlag, OpenFeedbackDetails, TogglePlayersFrozen } public sealed class UiAction { public UiActionId Id; public UiPageId Page; public UiTeam Team; public int Number; public string Text = ""; public string PlayerId = ""; public bool Flag; public string ConfirmKey = ""; public static readonly UiAction None = new UiAction(); public bool IsNone => Id == UiActionId.None; public static UiAction Navigate(UiPageId page) { return new UiAction { Id = UiActionId.Navigate, Page = page }; } public static UiAction Of(UiActionId id) { return new UiAction { Id = id }; } public UiAction With(UiPageId page) { Page = page; return this; } public UiAction With(UiTeam team) { Team = team; return this; } public UiAction WithNumber(int number) { Number = number; return this; } public UiAction WithText(string text) { Text = text ?? ""; return this; } public UiAction WithFlag(bool flag) { Flag = flag; return this; } public UiAction Confirm(string key) { ConfirmKey = key ?? ""; return this; } public override string ToString() { return string.Format(CultureInfo.InvariantCulture, "{0}(page={1}, team={2}, n={3}, text={4})", Id, Page, Team, Number, Text); } } public sealed class UiActionResult { public bool Handled; public bool Success; public string Message = ""; public bool Navigated; public UiPageId NavigatedTo; public bool AwaitingConfirmation; public static UiActionResult Ok(string message = "") { return new UiActionResult { Handled = true, Success = true, Message = (message ?? "") }; } public static UiActionResult Failed(string message) { return new UiActionResult { Handled = true, Success = false, Message = (message ?? "") }; } public static UiActionResult Unhandled(string message = "") { return new UiActionResult { Handled = false, Success = false, Message = (message ?? "") }; } public static UiActionResult Route(UiPageId page, string message = "") { return new UiActionResult { Handled = true, Success = true, Navigated = true, NavigatedTo = page, Message = (message ?? "") }; } public static UiActionResult Arm(string message) { return new UiActionResult { Handled = true, Success = true, AwaitingConfirmation = true, Message = (message ?? "") }; } } public static class UiConfirmation { public const float WindowSeconds = 4f; public const string Instruction = "Press this button again."; public static string Label(string action, float remainingSeconds, bool compact = false) { int num = Math.Max(1, Math.Min(4, (int)Math.Ceiling(remainingSeconds))); string text = Regex.Replace(action ?? "", "<[^>]+>", "").Replace("\n", " ").Trim(); return "CONFIRM" + ((compact || text.Length == 0) ? "" : (" " + text.ToUpperInvariant())) + " · " + num.ToString(CultureInfo.InvariantCulture) + "s"; } public static string ButtonText(string action, float remainingSeconds, bool compact = false) { return Label(action, remainingSeconds, compact) + (compact ? "" : "\n<size=65%>Press this button again.</size>"); } } public static class UiDerive { public static bool DescribeCondition(UiMedicalSnapshot medical, out string line, out UiColor accent) { if (medical == null || !medical.Enabled) { line = "MEDICAL SYSTEM IS OFF"; accent = UiTheme.TextDim; return false; } switch (medical.State) { case UiMedicalState.Dead: line = "<b>YOU ARE DEAD</b> — waiting to respawn"; accent = UiTheme.HealthCrit; return true; case UiMedicalState.Respawning: line = "<b>RESPAWNING</b>"; accent = UiTheme.Orange; return true; case UiMedicalState.Incapacitated: line = "<b>YOU ARE DOWN</b> — someone has to revive you"; accent = UiTheme.HealthCrit; return true; case UiMedicalState.Critical: line = (medical.Bleeding ? "<b>CRITICAL</b> — bleeding out, treat now" : "<b>CRITICAL</b> — treat now"); accent = UiTheme.HealthCrit; return true; case UiMedicalState.Injured: line = (medical.Bleeding ? "<b>WOUNDED</b> — you are bleeding" : "<b>WOUNDED</b> — trauma is slowing you down"); accent = UiTheme.HealthWarn; return true; default: if (medical.Bleeding) { line = "<b>BLEEDING</b> — bandage it before it gets worse"; accent = UiTheme.HealthWarn; return true; } line = "<b>UNINJURED</b> — good to go"; accent = UiTheme.HealthGood; return false; } } public static bool MemberHasProblem(UiSnapshot state, UiPlayerSnapshot entry) { if (entry == null) { return true; } if (entry.Team == UiTeam.Unassigned || string.IsNullOrWhiteSpace(entry.LoadoutName)) { return true; } UiMedicalState medicalState = entry.MedicalState; if ((entry.IsReady || state.Session.SessionActive) && medicalState != UiMedicalState.Incapacitated && medicalState != UiMedicalState.Dead && medicalState != UiMedicalState.Respawning) { return medicalState == UiMedicalState.Critical; } return true; } public static string MemberState(UiSnapshot state, UiPlayerSnapshot entry) { if (entry.Team == UiTeam.Unassigned) { return "NO TEAM"; } if (string.IsNullOrWhiteSpace(entry.LoadoutName)) { return "NO LOADOUT"; } if (entry.MedicalState != UiMedicalState.Alive) { return entry.MedicalState.ToString().ToUpperInvariant(); } if (!state.Session.SessionActive) { if (!entry.IsReady) { return "NOT READY"; } return "READY"; } return "ACTIVE"; } public static List<UiPlayerSnapshot> LocalSquad(UiSnapshot state) { UiTeam team = (state.Local.HasPendingDeployment ? state.Local.CurrentTeam : state.Local.Team); return (from entry in state.Roster where entry.Team == team orderby (!MemberHasProblem(state, entry)) ? 1 : 0 select entry).ThenBy<UiPlayerSnapshot, string>((UiPlayerSnapshot entry) => entry.Name, StringComparer.OrdinalIgnoreCase).ToList(); } public static string LocalRoleSummary(UiSnapshot state) { string text = (state.Session.IsSquadLeader ? "SL" : "NONE"); if (!state.Session.CanManage) { return "COMMAND: " + text; } return "COMMAND: " + text + " • ADMIN: HOST"; } public static UiPageId PrimaryNextPage(UiSnapshot state) { return UiPageId.PlayerField; } public static string PrimaryNextTitle(UiPageId page) { return page switch { UiPageId.PlayerReadyLobby => "Complete player setup", UiPageId.PlayerReadyLive => "Complete deployment", UiPageId.PlayerField => "GAME", UiPageId.SquadCommand => "Resolve command decisions", UiPageId.HostSetupOverview => "Continue match setup", _ => "Continue", }; } public static string PrimaryNextDetail(UiPageId page) { return page switch { UiPageId.PlayerReadyLobby => "Choose team, choose loadout, ready up", UiPageId.PlayerReadyLive => "Team, loadout and deployment", UiPageId.PlayerField => "Team, loadout and deployment", UiPageId.SquadCommand => "Join and vehicle requests are waiting", UiPageId.HostSetupOverview => "Fix setup blockers before preflight", _ => "Open the active workspace", }; } public static string CompactMatchState(UiSnapshot state) { List<UiTeamSnapshot> activeTeams = state.ActiveTeams; if (activeTeams.Count == 0) { return "MATCH LIVE"; } return (state.Match.RespawnTokensEnabled ? string.Join(" / ", (from team in activeTeams.Take(2) select team.Tag + " " + team.Tokens).ToArray()) : "UNLIMITED LIVES") + " • " + state.Match.Objectives.Count + " OBJECTIVES"; } public static UiVehicleRequestSnapshot LocalPendingVehicleRequest(UiSnapshot state) { return state.VehicleRequests.FirstOrDefault((UiVehicleRequestSnapshot request) => (state.Local.PlatformId != 0L && request.RequesterPlatformId == state.Local.PlatformId) || string.Equals(request.RequesterName, state.Session.LocalPlayerName, StringComparison.OrdinalIgnoreCase)); } public static int IncompleteRosterCount(UiSnapshot state) { return state.Roster.Count((UiPlayerSnapshot entry) => entry.Team == UiTeam.Unassigned || string.IsNullOrWhiteSpace(entry.LoadoutName)); } public static UiColor HealthColor(float integrity01) { if (integrity01 >= 0.75f) { return UiTheme.HealthGood; } if (integrity01 >= 0.4f) { return UiTheme.HealthWarn; } return UiTheme.HealthCrit; } } public static class UiLiveLayout { public static bool CanRefresh(IReadOnlyList<UiElement> before, IReadOnlyList<UiElement> after) { if (before.Count != after.Count) { return false; } for (int i = 0; i < before.Count; i++) { UiElement uiElement = before[i]; UiElement uiElement2 = after[i]; if (uiElement.Kind != uiElement2.Kind || uiElement.Id != uiElement2.Id || uiElement.Visible != uiElement2.Visible || !uiElement.Position.Equals(uiElement2.Position) || !uiElement.Size.Equals(uiElement2.Size) || !CanRefresh(uiElement.Children, uiElement2.Children)) { return false; } if (uiElement is UiText uiText && uiElement2 is UiText uiText2) { if (uiText.FontSize != uiText2.FontSize || uiText.Alignment != uiText2.Alignment || uiText.RichText != uiText2.RichText) { return false; } continue; } if (uiElement is UiButton uiButton && uiElement2 is UiButton uiButton2) { if (uiButton.FontSize != uiButton2.FontSize || uiButton.Alignment != uiButton2.Alignment || uiButton.Transparent != uiButton2.Transparent || uiButton.Outlined != uiButton2.Outlined) { return false; } continue; } if (uiElement is UiDivider uiDivider && uiElement2 is UiDivider uiDivider2 && (!uiDivider.Color.Equals(uiDivider2.Color) || uiDivider.Alpha != uiDivider2.Alpha)) { return false; } if (uiElement is UiRectangle uiRectangle && uiElement2 is UiRectangle uiRectangle2 && (!uiRectangle.Color.Equals(uiRectangle2.Color) || uiRectangle.Alpha != uiRectangle2.Alpha || uiRectangle.Rotation != uiRectangle2.Rotation)) { return false; } if (uiElement is UiPanel uiPanel && uiElement2 is UiPanel uiPanel2 && (!uiPanel.Color.Equals(uiPanel2.Color) || uiPanel.Alpha != uiPanel2.Alpha)) { return false; } if (uiElement is UiFlagChip uiFlagChip && uiElement2 is UiFlagChip uiFlagChip2 && (uiFlagChip.FlagCode != uiFlagChip2.FlagCode || uiFlagChip.Tag != uiFlagChip2.Tag || !uiFlagChip.Color.Equals(uiFlagChip2.Color))) { return false; } if (uiElement is UiToggle uiToggle && uiElement2 is UiToggle uiToggle2 && (uiToggle.On != uiToggle2.On || uiToggle.Text != uiToggle2.Text)) { return false; } if (uiElement is UiProgressBar uiProgressBar && uiElement2 is UiProgressBar uiProgressBar2 && (uiProgressBar.Value01 != uiProgressBar2.Value01 || !uiProgressBar.FillColor.Equals(uiProgressBar2.FillColor) || !uiProgressBar.TrackColor.Equals(uiProgressBar2.TrackColor) || uiProgressBar.TrackAlpha != uiProgressBar2.TrackAlpha)) { return false; } if (uiElement is UiIconElement uiIconElement && uiElement2 is UiIconElement uiIconElement2 && (uiIconElement.Icon != uiIconElement2.Icon || !uiIconElement.Color.Equals(uiIconElement2.Color))) { return false; } } return true; } } public static class UiOverlays { private static UiVector2 V(float x, float y) { return new UiVector2(x, y); } public static void Compose(UiScreen screen, UiSnapshot state) { UiOverlaySnapshot overlays = state.Overlays; if (overlays.CaptureVisible) { ComposeCapture(screen, overlays); } if (state.Medical.DownTimerActive) { ComposeDownTimer(screen, state); } if (overlays.RuntimeNoticeVisible && !string.IsNullOrWhiteSpace(overlays.RuntimeNotice)) { ComposeRuntimeNotice(screen, overlays); } if (overlays.GameOverVisible) { ComposeGameOver(screen, state, overlays); } if (overlays.VrCursorVisible) { ComposeCursor(screen, overlays); } } private static void ComposeCapture(UiScreen screen, UiOverlaySnapshot overlay) { int num = ((overlay.CapturePercent >= 0) ? ((overlay.CapturePercent > 100) ? 100 : overlay.CapturePercent) : 0); screen.Overlays.Add(new UiPanel { Id = "CaptureBackdrop", Position = V(0f, 250f), Size = V(420f, 78f), Color = UiTheme.PanelDark, Alpha = 0.86f }); screen.Overlays.Add(new UiText { Id = "CaptureText", Position = V(0f, 268f), Size = V(392f, 30f), FontSize = 16f, Alignment = UiAlignment.Center, Text = "<b>" + overlay.CaptureVerb.ToUpperInvariant() + " • " + overlay.CaptureLabel.ToUpperInvariant() + "</b>", Color = UiTheme.Orange }); screen.Overlays.Add(new UiRectangle { Id = "CaptureTrack", Position = V(0f, 236f), Size = V(360f, 10f), Color = new UiColor(0f, 0f, 0f), Alpha = 0.5f, Sharp = true }); screen.Overlays.Add(new UiProgressBar { Id = "CaptureProgress", Position = V(0f, 236f), Size = V(360f, 10f), Value01 = (float)num / 100f, FillColor = ((num >= 100) ? UiTheme.HealthGood : UiTheme.Orange) }); screen.Overlays.Add(new UiText { Id = "CapturePercent", Position = V(0f, 218f), Size = V(392f, 22f), FontSize = 11f, Alignment = UiAlignment.Center, Text = num.ToString(CultureInfo.InvariantCulture) + "%", Color = UiTheme.TextDim }); } private static void ComposeDownTimer(UiScreen screen, UiSnapshot state) { UiMedicalSnapshot medical = state.Medical; float num = ((medical.DownSecondsRemaining < 0f) ? 0f : medical.DownSecondsRemaining); UiColor color = ((num <= 5f) ? UiTheme.HealthCrit : ((num <= 20f) ? UiTheme.HealthWarn : UiTheme.Orange)); screen.Overlays.Add(new UiPanel { Id = "DownBackdrop", Position = V(0f, 0f), Size = V(620f, 780f), Color = UiTheme.PanelDark, Alpha = 0.55f }); screen.Overlays.Add(new UiRectangle { Id = "DownAccent", Position = V(0f, 60f), Size = V(460f, 6f), Color = color, Alpha = 0.95f, Sharp = true }); screen.Overlays.Add(new UiText { Id = "DownState", Position = V(0f, 130f), Size = V(520f, 44f), FontSize = 26f, Alignment = UiAlignment.Center, Text = "<b>" + medical.State.ToString().ToUpperInvariant() + "</b>", Color = color }); screen.Overlays.Add(new UiText { Id = "DownTimer", Position = V(0f, 6f), Size = V(520f, 76f), FontSize = 54f, Alignment = UiAlignment.Center, Text = FormatCountdown(num), Color = UiTheme.TextWhite }); screen.Overlays.Add(new UiText { Id = "DownPrompt", Position = V(0f, -64f), Size = V(520f, 32f), FontSize = 15f, Alignment = UiAlignment.Center, Text = medical.DownPrompt, Color = UiTheme.TextDim }); if (!string.IsNullOrWhiteSpace(medical.DownDetail)) { screen.Overlays.Add(new UiText { Id = "DownDetail", Position = V(0f, -102f), Size = V(520f, 30f), FontSize = 12f, Alignment = UiAlignment.Center, Text = medical.DownDetail, Color = UiTheme.TextDim }); } } private static void ComposeRuntimeNotice(UiScreen screen, UiOverlaySnapshot overlay) { screen.Overlays.Add(new UiPanel { Id = "NoticeBackdrop", Position = V(0f, -250f), Size = V(540f, 64f), Color = UiTheme.PanelDark, Alpha = 0.9f }); screen.Overlays.Add(new UiRectangle { Id = "NoticeRail", Position = V(-266f, -250f), Size = V(4f, 48f), Color = UiTheme.Orange, Alpha = 0.96f }); screen.Overlays.Add(new UiText { Id = "NoticeText", Position = V(8f, -250f), Size = V(500f, 52f), FontSize = 13f, Alignment = UiAlignment.Left, Text = overlay.RuntimeNotice, Color = UiTheme.TextWhite }); } private static void ComposeGameOver(UiScreen screen, UiSnapshot state, UiOverlaySnapshot overlay) { UiTeamSnapshot uiTeamSnapshot = state.Team(overlay.GameOverWinner); UiColor color = uiTeamSnapshot?.Color ?? UiTheme.Orange; string text = ((!string.IsNullOrWhiteSpace(overlay.GameOverHeadline)) ? overlay.GameOverHeadline : ((uiTeamSnapshot != null) ? (uiTeamSnapshot.DisplayName.ToUpperInvariant() + " WINS") : "MATCH OVER")); screen.Overlays.Add(new UiPanel { Id = "GameOverBackdrop", Position = V(0f, 0f), Size = V(620f, 780f), Color = UiTheme.PanelDark, Alpha = 0.88f }); screen.Overlays.Add(new UiFlagChip { Id = "GameOverFlag", Position = V(0f, 120f), Size = V(180f, 120f), FlagCode = ((uiTeamSnapshot != null) ? uiTeamSnapshot.FlagCode : ""), Tag = ((uiTeamSnapshot != null) ? uiTeamSnapshot.Tag : ""), Color = color }); screen.Overlays.Add(new UiText { Id = "GameOverText", Position = V(0f, 10f), Size = V(540f, 60f), FontSize = 32f, Alignment = UiAlignment.Center, Text = "<b>" + text + "</b>", Color = color }); if (!string.IsNullOrWhiteSpace(overlay.GameOverDetail)) { screen.Overlays.Add(new UiText { Id = "GameOverDetail", Position = V(0f, -46f), Size = V(540f, 40f), FontSize = 14f, Alignment = UiAlignment.Center, Text = overlay.GameOverDetail, Color = UiTheme.TextDim }); } } private static void ComposeCursor(UiScreen screen, UiOverlaySnapshot overlay) { screen.Overlays.Add(new UiRectangle { Id = "VrCursorRing", Position = overlay.VrCursorPosition, Size = V(26f, 26f), Color = UiTheme.Orange, Alpha = 0.35f }); screen.Overlays.Add(new UiRectangle { Id = "VrCursorDot", Position = overlay.VrCursorPosition, Size = V(8f, 8f), Color = UiTheme.Orange, Alpha = 1f }); } public static string FormatCountdown(float seconds) { if (seconds < 0f) { seconds = 0f; } int num = (int)seconds; int num2 = num / 60; int num3 = num % 60; if (num2 <= 0) { return num3.ToString(CultureInfo.InvariantCulture); } return num2.ToString(CultureInfo.InvariantCulture) + ":" + num3.ToString("00", CultureInfo.InvariantCulture); } } public sealed class UiPageBuilder { private readonly UiScreen _screen; private readonly UiSnapshot _state; private bool _chrome; public UiScreen Screen => _screen; public UiSnapshot State => _state; public UiPageBuilder(UiPageId page, UiSnapshot state) { _state = state; _screen = new UiScreen { Page = page, Workspace = state.View.ActiveWorkspace, Feedback = (state.Feedback ?? "") }; } public UiScreen Build() { ApplyConfirmation(_screen.Elements); ApplyConfirmation(_screen.Chrome); return _screen; } private void ApplyConfirmation(List<UiElement> elements) { foreach (UiElement element in elements) { ApplyConfirmation(element.Children); if (element is UiButton { Enabled: not false } uiButton && !string.IsNullOrEmpty(uiButton.Action.ConfirmKey) && !(uiButton.Action.ConfirmKey != _state.View.ArmedConfirmKey)) { string text = uiButton.Text; bool flag = uiButton.Size.X < 120f; uiButton.Text = UiConfirmation.ButtonText(text, _state.View.ConfirmationRemainingSeconds, flag); uiButton.FontSize = (flag ? 13f : ((uiButton.Size.X < 260f) ? 15f : 19f)); uiButton.Tint = UiTheme.HealthWarn; uiButton.TintAlpha = 1f; uiButton.Transparent = false; uiButton.TextColor = UiTheme.Coffee; if (flag) { float x = ((uiButton.Position.X > 0f) ? (-190f) : 190f); uiButton.Add(new UiPanel { Id = uiButton.Id + "_ConfirmHint", Position = V(x, 0f), Size = V(260f, 48f), Color = UiTheme.Coffee, Alpha = 1f }); uiButton.Add(new UiText { Id = uiButton.Id + "_ConfirmInstruction", Position = V(x, 0f), Size = V(252f, 44f), FontSize = 12f, Alignment = UiAlignment.Center, Color = UiTheme.HealthWarn, Text = UiConfirmation.ButtonText(text, _state.View.ConfirmationRemainingSeconds) }); } } } } public UiPageBuilder MarkPreviewOnly() { _screen.PreviewOnly = true; return this; } public static UiVector2 V(float x, float y) { return new UiVector2(x, y); } private T AddTo<T>(T element, UiElement parent) where T : UiElement { if (parent != null) { parent.Add(element); } else if (_chrome) { _screen.Chrome.Add(element); } else { _screen.Elements.Add(element); } return element; } private void AsChrome(Action build) { bool chrome = _chrome; _chrome = true; try { build(); } finally { _chrome = chrome; } } public UiPanel Panel(string id, UiVector2 pos, UiVector2 size, UiColor color, float alpha, UiElement parent = null) { return AddTo(new UiPanel { Id = id, Position = pos, Size = size, Color = color, Alpha = alpha }, parent); } public UiRectangle Rect(string id, UiVector2 pos, UiVector2 size, UiColor color, float alpha, float rotation = 0f, UiElement parent = null) { return AddTo(new UiRectangle { Id = id, Position = pos, Size = size, Color = color, Alpha = alpha, Rotation = rotation }, parent); } public UiRectangle SharpRect(string id, UiVector2 pos, UiVector2 size, UiColor color, float alpha, UiElement parent = null) { return AddTo(new UiRectangle { Id = id, Position = pos, Size = size, Color = color, Alpha = alpha, Sharp = true }, parent); } public UiDivider Divider(UiVector2 pos, UiVector2 size, UiColor color, float alpha, UiElement parent = null) { return AddTo(new UiDivider { Id = "Divider", Position = pos, Size = size, Color = color, Alpha = alpha }, parent); } public UiText Text(string id, UiVector2 pos, UiVector2 size, float fontSize, UiAlignment alignment, string text, UiColor color, UiElement parent = null) { return AddTo(new UiText { Id = id, Position = pos, Size = size, FontSize = fontSize, Alignment = alignment, Text = Localize(text), Color = color }, parent); } public UiButton Button(string id, UiVector2 pos, UiVector2 size, string text, float fontSize, UiAction action, UiElement parent = null) { return AddTo(new UiButton { Id = id, Position = pos, Size = size, Text = Localize(text), FontSize = fontSize, Action = (action ?? UiAction.None) }, parent); } public UiButton TransparentButton(string id, UiVector2 pos, UiVector2 size, string text, float fontSize, UiAction action, UiColor? textColor = null, UiElement parent = null) { UiButton uiButton = Button(id, pos, size, text, fontSize, action, parent); uiButton.Transparent = true; uiButton.Outlined = false; uiButton.TintAlpha = 0f; if (textColor.HasValue) { uiButton.TextColor = textColor.Value; } return uiButton; } public UiButton ConfirmButton(string id, UiVector2 pos, UiVector2 size, string text, float fontSize, string confirmKey, UiAction action, UiElement parent = null) { return Button(id, pos, size, text, fontSize, (action ?? UiAction.None).Confirm(confirmKey), parent); } public UiIconElement Icon(string id, UiVector2 pos, float size, UiIcon icon, UiColor color, float alpha = 1f, UiElement parent = null) { return AddTo(new UiIconElement { Id = id, Position = pos, Size = V(size, size), Icon = icon, Color = color, Alpha = alpha }, parent); } public UiProgressBar Progress(string id, UiVector2 pos, UiVector2 size, float value01, UiColor fill, UiElement parent = null) { return AddTo(new UiProgressBar { Id = id, Position = pos, Size = size, Value01 = Clamp01(value01), FillColor = fill }, parent); } public UiToggle Toggle(string id, UiVector2 pos, UiVector2 size, float fontSize, string label, bool on, UiAction action, UiElement parent = null) { return AddTo(new UiToggle { Id = id, Position = pos, Size = size, FontSize = fontSize, Text = Localize(label), On = on, Action = (action ?? UiAction.None) }, parent); } public UiFlagChip Flag(string id, UiVector2 pos, UiVector2 size, string flagCode, string tag, UiColor color, UiElement parent = null) { return AddTo(new UiFlagChip { Id = id, Position = pos, Size = size, FlagCode = (flagCode ?? ""), Tag = (tag ?? ""), Color = color }, parent); } public void PageTitle(string title) { _screen.Title = title; bool home = _screen.Page == UiPageId.Home; AsChrome(delegate { Text("PageTitle", home ? V(-62f, 326f) : V(-56f, 326f), home ? V(428f, 44f) : V(368f, 44f), home ? 27f : 24f, UiAlignment.Left, "<b>" + title + "</b>", UiTheme.TextWhite); }); } private int WorkspaceActionCount() { if (_state.View.ActiveWorkspace == UiWorkspace.Squad && _state.Session.IsSquadLeader) { return _state.JoinRequests.Count + _state.VehicleRequests.Count; } return _state.Session.HasPendingCloudJoin ? 1 : 0; } public UiButton ActionRow(string name, UiVector2 pos, UiVector2 size, string title, string detail, string state, UiAction action, bool primary = false, bool warning = false, bool destructive = false) { detail = detail ?? ""; state = state ?? ""; UiColor uiColor = (destructive ? UiTheme.HealthCrit : (warning ? UiTheme.HealthWarn : (primary ? UiTheme.Orange : UiTheme.OutlineGray))); UiButton uiButton = Button(name, pos, size, "", 14f, action); uiButton.Tint = (primary ? UiTheme.Orange : (destructive ? UiTheme.HealthCrit : UiTheme.ButtonBrown)); uiButton.TintAlpha = (primary ? 0.88f : (destructive ? 0.2f : 0.72f)); Rect(name + "_Rail", V((0f - size.X) * 0.5f + 4f, 0f), V(4f, size.Y - 12f), uiColor, 0.96f, 0f, uiButton); UiColor color = (primary ? UiTheme.TextBlack : UiTheme.TextWhite); UiColor color2 = (primary ? UiTheme.TextBlack.WithAlpha(0.72f) : UiTheme.TextDim); bool flag = !string.IsNullOrWhiteSpace(state); float num = ((size.X < 220f) ? 44f : ((size.X < 320f) ? 72f : 124f)); float num2 = (0f - size.X) * 0.5f + 18f; float num3 = size.X * 0.5f - 14f - (flag ? (num + 12f) : 0f); float x = Math.Max(72f, num3 - num2); float x2 = (num2 + num3) * 0.5f; Text(name + "_Title", V(x2, (detail.Length > 0) ? 11f : 0f), V(x, 26f), (size.Y >= 68f) ? 17f : ((size.Y >= 58f) ? 16f : 14f), UiAlignment.Left, "<b>" + title + "</b>", color, uiButton); if (!string.IsNullOrWhiteSpace(detail)) { Text(name + "_Detail", V(x2, -14f), V(x, 22f), 11.5f, UiAlignment.Left, detail, color2, uiButton); } if (flag) { UiVector2 pos2 = V(size.X * 0.5f - 14f - num * 0.5f, 0f); Rect(name + "_StatePill", pos2, V(num, 28f), primary ? UiTheme.TextBlack : uiColor, primary ? 0.14f : 0.16f, 0f, uiButton); Text(name + "_State", pos2, V(num - 8f, 28f), (size.X < 320f) ? 10.5f : 11f, UiAlignment.Center, state, primary ? UiTheme.TextBlack : ((warning || destructive) ? uiColor : UiTheme.TextDim), uiButton); } return uiButton; } public void InfoRow(string name, UiVector2 pos, UiVector2 size, string title, string detail, string state = "", UiColor? accentOverride = null) { detail = detail ?? ""; state = state ?? ""; UiColor color = accentOverride ?? UiTheme.OutlineGray; Panel(name, pos, size, UiTheme.Coffee, 0.66f); Rect(name + "_Rail", V(pos.X - size.X * 0.5f + 4f, pos.Y), V(4f, size.Y - 12f), color, 0.88f); bool flag = !string.IsNullOrWhiteSpace(state); float num = ((size.X < 220f) ? 44f : ((size.X < 320f) ? 72f : 124f)); float num2 = (0f - size.X) * 0.5f + 18f; float num3 = size.X * 0.5f - 14f - (flag ? (num + 12f) : 0f); float x = Math.Max(72f, num3 - num2); float x2 = (num2 + num3) * 0.5f; Text(name + "_Title", pos + V(x2, (detail.Length > 0) ? 11f : 0f), V(x, 26f), (size.Y >= 68f) ? 17f : ((size.Y >= 58f) ? 16f : 14f), UiAlignment.Left, "<b>" + title + "</b>", UiTheme.TextWhite); if (!string.IsNullOrWhiteSpace(detail)) { Text(name + "_Detail", pos + V(x2, -14f), V(x, 22f), 11.5f, UiAlignment.Left, detail, UiTheme.TextDim); } if (flag) { UiVector2 pos2 = pos + V(size.X * 0.5f - 14f - num * 0.5f, 0f); Panel(name + "_StatePill", pos2, V(num, 28f), color, 0.14f); Text(name + "_State", pos2, V(num - 8f, 28f), (size.X < 320f) ? 10.5f : 11f, UiAlignment.Center, state, accentOverride ?? UiTheme.TextDim); } } public void Pager(string name, int currentPage, int itemCount, int pageSize, float y, UiActionId action) { int num = ((pageSize <= 0) ? 1 : ((itemCount + pageSize - 1) / pageSize)); if (num > 1) { int num2 = ClampPage(currentPage, itemCount, pageSize); Button(name + "_Previous", V(-86f, y), V(54f, 36f), "<", 20f, new UiAction { Id = action, Number = Math.Max(0, num2 - 1), Text = name }); Text(name + "_Page", V(0f, y), V(100f, 30f), 14f, UiAlignment.Center, (num2 + 1).ToString(CultureInfo.InvariantCulture) + " / " + num.ToString(CultureInfo.InvariantCulture), UiTheme.TextDim); Button(name + "_Next", V(86f, y), V(54f, 36f), ">", 20f, new UiAction { Id = action, Number = Math.Min(num - 1, num2 + 1), Text = name }); } } public void FeedbackBar(float y) { UiGroup uiGroup = AddTo(new UiGroup { Id = "FeedbackBar", Position = V(0f, y), Size = V(520f, 34f) }, null); uiGroup.Add(new UiPanel { Id = "FeedbackPill", Position = V(0f, 0f), Size = V(520f, 34f), Color = UiTheme.Coffee, Alpha = 0.72f }); uiGroup.Add(new UiRectangle { Id = "FeedbackDot", Position = V(-246f, 0f), Size = V(6f, 18f), Color = UiTheme.Orange, Alpha = 0.95f }); uiGroup.Add(new UiText { Id = "ActionFeedback", Position = V(-40f, 0f), Size = V(380f, 26f), FontSize = 12.5f, Alignment = UiAlignment.Left, Text = (_state.Feedback ?? ""), Color = UiTheme.TextDim, Dynamic = true }); Button("FeedbackDetails", V(206f, 0f), V(96f, 30f), "DETAILS", 12f, UiAction.Of(UiActionId.OpenFeedbackDetails), uiGroup); } public void SectionLabel(UiVector2 pos, string label) { Text("Section_" + label, pos, V(548f, 24f), 10.5f, UiAlignment.Left, label.ToUpperInvariant(), UiTheme.TextDim); } public void MetricChip(UiVector2 pos, string label, string value, UiColor accent) { Panel("Metric_" + label, pos, V(160f, 54f), UiTheme.Coffee, 0.7f); Text("MetricLabel_" + label, pos + V(0f, 13f), V(148f, 18f), 9.5f, UiAlignment.Center, label.ToUpperInvariant(), UiTheme.TextDim); Text("MetricValue_" + label, pos + V(0f, -11f), V(148f, 24f), 16f, UiAlignment.Center, "<b>" + value + "</b>", accent); } public void WorkspaceNavigation(UiPageId page) { AsChrome(delegate { WorkspaceNavigationBody(page); }); } private void WorkspaceNavigationBody(UiPageId page) { if (!UiPageIds.ShowsWorkspaceNavigation(page)) { return; } UiWorkspace[] array = UiRouter.AvailableWorkspaces(_state); if (array.Length == 1) { Text("WorkspaceSingle", V(0f, 277f), V(180f, 28f), 10.5f, UiAlignment.Center, "PLAYER", UiTheme.TextDim); return; } float num = ((array.Length == 3) ? 174f : 260f); float spacing = num + 8f; float[] array2 = SpreadPositions(array.Length, spacing); for (int i = 0; i < array.Length; i++) { UiWorkspace uiWorkspace = array[i]; bool flag = _state.View.ActiveWorkspace == uiWorkspace; int num2 = ((uiWorkspace == UiWorkspace.Squad) ? (_state.JoinRequests.Count + _state.VehicleRequests.Count) : 0); string text = uiWorkspace.ToString().ToUpperInvariant() + ((num2 > 0) ? (" " + num2) : ""); UiButton uiButton = Button("Workspace_" + uiWorkspace, V(array2[i], 277f), V(num, 34f), text, 10.5f, new UiAction { Id = UiActionId.SetWorkspace, Number = (int)uiWorkspace }); uiButton.Tint = (flag ? UiTheme.Orange : UiTheme.ButtonBrown); uiButton.TintAlpha = (flag ? 0.34f : 0.58f); if (flag) { Rect("WorkspaceRail_" + uiWorkspace, V(0f, -15f), V(num - 14f, 3f), UiTheme.Orange, 1f, 0f, uiButton); } } } public void TopNavigation(UiPageId page) { AsChrome(delegate { TopNavigationBody(page); }); } private void TopNavigationBody(UiPageId page) { UiButton parent = Button("SettingsGear", V(194f, 326f), V(36f, 36f), "", 10f, UiAction.Navigate(UiPageId.Settings)); Icon("SettingsGearIcon", V(0f, 0f), 22f, UiIcon.Settings, UiTheme.TextDim, 1f, parent); int num = UiNavigation.ActionableNotificationCount(_state); UiButton parent2 = Button("NotificationBell", V(238f, 326f), V(36f, 36f), "", 10f, UiAction.Navigate(UiPageId.Notifications)); if (num > 0) { Text("NotificationCount", V(250f, 338f), V(24f, 18f), 10f, UiAlignment.Center, num.ToString(), UiTheme.Orange); } Icon("NotificationBellIcon", V(0f, 0f), 20f, UiIcon.Notifications, UiTheme.Orange, 1f, parent2); if (page != UiPageId.Home) { TransparentButton("Back", V(-274f, 326f), V(44f, 44f), "<-", 18f, UiAction.Of(UiActionId.Back), UiTheme.Orange); } } public void FooterNavigation(UiPageId page) { AsChrome(delegate { FooterNavigationBody(page); }); } private void FooterNavigationBody(UiPageId page) { UiNavigation.Section[] array = UiNavigation.Sections(_state, page); float num = ((array.Length == 4) ? 130f : ((array.Length == 2) ? 264f : 174f)); float[] array2 = SpreadPositions(array.Length, num + 8f); for (int i = 0; i < array.Length; i++) { Button("Section_" + array[i].Label, V(array2[i], UiPageIds.ShowsWorkspaceNavigation(page) ? 244f : 277f), V(num, 26f), array[i].Label, 10f, UiAction.Navigate(array[i].Page)); } string text = UiNavigation.Breadcrumb(_state, page); if (text.Length > 0) { Text("HostBreadcrumb", V(0f, -246f), V(548f, 18f), 9f, UiAlignment.Left, text, UiTheme.TextDim); } Divider(V(0f, -320f), V(564f, 2f), UiTheme.OutlineGray, 0.62f); if (page != UiPageId.Home) { TransparentButton("FooterHome", V(-254f, -348f), V(76f, 38f), "HOME", 10f, UiAction.Of(UiActionId.GoHome), UiTheme.TextDim); } if (UiPageIds.IsMilsimPage(page) && !UiPageIds.ShowsWorkspaceNavigation(page)) { TransparentButton("FooterWorkspace", V(-162f, -348f), V(100f, 38f), _state.View.ActiveWorkspace.ToString().ToUpperInvariant(), 10.5f, new UiAction { Id = UiActionId.SetWorkspace, Number = (int)_state.View.ActiveWorkspace }, UiTheme.Orange); } TransparentButton("FooterUpdates", V(-40f, -348f), V(136f, 38f), "v" + _state.Session.ModVersion + " • UPDATES", 9.5f, UiAction.Navigate(UiPageId.AboutUpdates), (page == UiPageId.AboutUpdates) ? UiTheme.Orange : UiTheme.TextDim); string text2 = UiFormat.Short(_state.Session.LocalPlayerName, 10) + " • " + (_state.Session.NetworkActive ? "FUSION" : "SOLO"); Text("FooterIdentity", V(166f, -348f), V(236f, 28f), 10.5f, UiAlignment.Right, text2.ToUpperInvariant(), UiTheme.TextDim); } public void LanguageButton(UiPageId page) { AsChrome(delegate { bool flag = page == UiPageId.Language; UiButton uiButton = Button("LanguageGlobe", V(284f, 326f), V(40f, 40f), "", 10f, new UiAction { Id = UiActionId.OpenLanguage, Page = page }); uiButton.Tint = UiTheme.Coffee; uiButton.TintAlpha = 0.8f; Icon("LanguageGlobe_Icon", V(0f, 0f), 24f, UiIcon.Globe, flag ? UiTheme.Orange : UiTheme.TextWhite, 1f, uiButton); Text("LanguageGlobe_Code", V(284f, 299f), V(52f, 16f), 9f, UiAlignment.Center, _state.View.Language.ToUpperInvariant(), UiTheme.TextDim); }); } public static float[] SpreadPositions(int count, float spacing) { float[] array = new float[count]; float num = (float)(-(count - 1)) * spacing * 0.5f; for (int i = 0; i < count; i++) { array[i] = num + (float)i * spacing; } return array; } public static int ClampPage(int page, int itemCount, int pageSize) { if (pageSize <= 0) { return 0; } int num = Math.Max(1, (itemCount + pageSize - 1) / pageSize); if (page < 0) { return 0; } if (page < num) { return page; } return num - 1; } private static float Clamp01(float value) { if (!(value < 0f)) { if (!(value > 1f)) { return value; } return 1f; } return 0f; } private string Localize(string text) { if (text == null) { return ""; } text = UiTranslation.Translate(text, _state.View.TranslateInComposition ? _state.View.Language : "en"); if (!_state.View.LocalizationStressTest || text.Length == 0) { return text; } return UiStrings.Stretch(text); } } public static class UiFormat { public static string Literal(string value) { return (value ?? "").Replace("<", "‹").Replace(">", "›"); } public static string SideName(UiSnapshot state, int side) { string[] array = (from t in state.ActiveTeams where t.Side == side select Literal(t.Tag)).ToArray(); if (array.Length != 0) { return string.Join(" + ", array); } return "Side " + ((side == 1) ? "A" : "B"); } public static string SideScore(UiSnapshot state) { return Score(1) + " / " + Score(2); string Score(int side) { UiTeamSnapshot uiTeamSnapshot = state.ActiveTeams.FirstOrDefault((UiTeamSnapshot t) => t.Side == side); return SideName(state, side) + " " + ((uiTeamSnapshot == null) ? "—" : uiTeamSnapshot.Tokens.ToString(CultureInfo.InvariantCulture)); } } public static string Short(string value, int max) { if (string.IsNullOrEmpty(value)) { return ""; } return Literal((value.Length <= max) ? value : (value.Substring(0, Math.Max(1, max - 1)) + "…")); } public static string Plural(int count, string singular, string plural = null) { if (count == 1) { return singular; } return plural ?? (singular + "s"); } public static string Supply(int value) { if (value >= 0) { return value.ToString(CultureInfo.InvariantCulture); } return "∞"; } public static string Number(float value, string format = "0.#") { return value.ToString(format, CultureInfo.InvariantCulture); } public static string Strip(string richText) { if (string.IsNullOrEmpty(richText)) { return ""; } return richText.Replace("<b>", "").Replace("</b>", "").Replace("<i>", "") .Replace("</i>", ""); } public static string TeamTag(UiSnapshot state, UiTeam team) { if (team == UiTeam.Unassigned) { return "NONE"; } UiTeamSnapshot uiTeamSnapshot = state.Team(team); if (uiTeamSnapshot != null && !string.IsNullOrWhiteSpace(uiTeamSnapshot.Tag)) { return Literal(uiTeamSnapshot.Tag); } return team.ToString().ToUpperInvariant(); } public static string TeamName(UiSnapshot state, UiTeam team) { if (team == UiTeam.Unassigned) { return "Unassigned"; } UiTeamSnapshot uiTeamSnapshot = state.Team(team); if (uiTeamSnapshot != null && !string.IsNullOrWhiteSpace(uiTeamSnapshot.DisplayName)) { return Literal(uiTeamSnapshot.DisplayName); } return team.ToString(); } } public static class UiPageCatalog { private sealed class QueueRow { public bool Join; public int Id; public string Name = ""; public string Detail = ""; } private static readonly HashSet<UiPageId> SharedPages = new HashSet<UiPageId> { UiPageId.PlayerSpawns, UiPageId.InitialVehicles, UiPageId.FeedbackDetails, UiPageId.Home, UiPageId.PlayerTeamsLoadout, UiPageId.Notifications, UiPageId.PlayerReadyLobby, UiPageId.PlayerReadyLive, UiPageId.PlayerField, UiPageId.PlayerBrief, UiPageId.PlayerRoster, UiPageId.PlayerKitSupplies, UiPageId.PlayerVehicleRequest, UiPageId.SquadAssembly, UiPageId.SquadCommand, UiPageId.SquadQueue, UiPageId.SquadRoster, UiPageId.SquadPolicy, UiPageId.HostSetupOverview, UiPageId.HostLiveControl, UiPageId.HostWorld, UiPageId.HostPreflight, UiPageId.HostLiveActions }; private static readonly UiVector2 BodyFigureCenter = new UiVector2(0f, 50f); public static bool IsSharedWithBonelabRenderer(UiPageId page) { return SharedPages.Contains(page); } public static IEnumerable<UiPageId> AllPages() { return Enum.GetValues(typeof(UiPageId)).Cast<UiPageId>(); } public static UiScreen Compose(UiPageId page, UiSnapshot state) { if (state == null) { throw new ArgumentNullException("state"); } UiPageBuilder uiPageBuilder = new UiPageBuilder(page, state); if (!SharedPages.Contains(page)) { uiPageBuilder.MarkPreviewOnly(); } uiPageBuilder.TopNavigation(page); uiPageBuilder.LanguageButton(page); uiPageBuilder.WorkspaceNavigation(page); BuildPageBody(uiPageBuilder, page, state); uiPageBuilder.FooterNavigation(page); UiOverlays.Compose(uiPageBuilder.Screen, state); return uiPageBuilder.Build(); } private static void BuildPageBody(UiPageBuilder b, UiPageId page, UiSnapshot state) { switch (page) { case UiPageId.FeedbackDetails: BuildFeedbackDetails(b, state); break; case UiPageId.PlayerSpawns: BuildPlayerSpawns(b, state); break; case UiPageId.InitialVehicles: BuildInitialVehicles(b, state); break; case UiPageId.Home: BuildHome(b, state); break; case UiPageId.PlayerReadyLobby: BuildPlayerReadyRoom(b, state, live: false); break; case UiPageId.PlayerReadyLive: BuildPlayerReadyRoom(b, state, live: true); break; case UiPageId.PlayerField: BuildPlayerField(b, state); break; case UiPageId.PlayerBrief: BuildPlayerBrief(b, state); break; case UiPageId.PlayerRoster: BuildRoster(b, state, squadOnly: false); break; case UiPageId.PlayerKitSupplies: BuildPlayerKitSupplies(b, state); break; case UiPageId.PlayerVehicleRequest: BuildPlayerVehicleRequest(b, state); break; case UiPageId.SquadAssembly: BuildSquadLanding(b, state, state.Session.SessionActive); break; case UiPageId.SquadCommand: BuildSquadLanding(b, state, state.Session.SessionActive); break; case UiPageId.SquadQueue: BuildSquadCommandQueue(b, state); break; case UiPageId.SquadRoster: BuildSquadRoster(b, state); break; case UiPageId.SquadPolicy: BuildSquadPolicy(b, state); break; case UiPageId.HostSetupOverview: if (state.Session.SessionActive) { BuildHostLiveControl(b, state); } else { BuildHostSetupOverview(b, state); } break; case UiPageId.HostLiveControl: if (state.Session.SessionActive) { BuildHostLiveControl(b, state); } else { BuildHostSetupOverview(b, state); } break; case UiPageId.HostWorld: BuildHostWorld(b, state); break; case UiPageId.HostPreflight: if (state.Session.SessionActive) { BuildHostLiveControl(b, state); } else { BuildHostPreflight(b, state); } break; case UiPageId.HostLiveActions: BuildHostLiveActions(b, state); break; case UiPageId.Settings: BuildSettings(b, state); break; case UiPageId.SettingsCategory: BuildSettingsCategory(b, state); break; case UiPageId.Notifications: BuildNotifications(b, state); break; case UiPageId.Medical: BuildMedical(b, state); break; case UiPageId.MilsimMap: BuildMap(b, state); break; case UiPageId.PlayerTeamsLoadout: BuildTeamSelection(b, state); break; case UiPageId.Language: BuildLanguage(b, state); break; case UiPageId.AboutUpdates: BuildAboutUpdates(b, state); break; case UiPageId.MilsimLoadoutSelect: BuildLoadoutSelect(b, state); break; case UiPageId.MilsimOverview: BuildGameOverview(b, state); break; case UiPageId.Fusion: BuildFusion(b, state); break; default: BuildLegacyPlaceholder(b, page, state); break; } } private static void BuildLegacyPlaceholder(UiPageBuilder b, UiPageId page, UiSnapshot state) { b.PageTitle(SplitPascal(page.ToString())); b.Panel("LegacyBox", UiPageBuilder.V(0f, 60f), UiPageBuilder.V(548f, 260f), UiTheme.Coffee, 0.6f); b.Text("LegacyTitle", UiPageBuilder.V(0f, 150f), UiPageBuilder.V(500f, 30f), 16f, UiAlignment.Center, "<b>LEGACY UNITY PAGE</b>", UiTheme.HealthWarn); b.Text("LegacyBody", UiPageBuilder.V(0f, 70f), UiPageBuilder.V(500f, 120f), 12.5f, UiAlignment.Center, "This page is still drawn by DashboardView's own builder in BONELAB.\nIt has no shared composition yet, so the previewer will not\ninvent one - that is how a second UI starts.\n\nRoute and state below are real and are shared with the game.", UiTheme.TextDim); b.InfoRow("LegacyRoute", UiPageBuilder.V(0f, -60f), UiPageBuilder.V(548f, 56f), "Back goes to", SplitPascal(UiRouter.ParentPage(state, page).ToString()), "ROUTE"); b.InfoRow("LegacyWorkspace", UiPageBuilder.V(0f, -124f), UiPageBuilder.V(548f, 56f), "Workspace", UiRouter.WorkspaceForPage(page, state.View.ActiveWorkspace).ToString(), UiPageIds.IsMilsimPage(page) ? "MILSIM" : "SHELL"); } internal static string SplitPascal(string value) { if (string.IsNullOrEmpty(value)) { return ""; } StringBuilder stringBuilder = new StringBuilder(value.Length + 8); for (int i = 0; i < value.Length; i++) { if (i > 0 && char.IsUpper(value[i]) && !char.IsUpper(value[i - 1])) { stringBuilder.Append(' '); } stringBuilder.Append(value[i]); } return stringBuilder.ToString(); } private static void BuildPlayerSpawns(UiPageBuilder b, UiSnapshot s) { b.PageTitle("Choose spawn"); b.Text("SelectedSpawn", V(0f, 220f), V(540f, 40f), 13f, UiAlignment.Center, s.SpawnSelectionStatus, UiTheme.TextDim); List<UiSpawnSnapshot> list = s.Match.Spawns.Where((UiSpawnSnapshot p) => p.Eligible).ToList(); int num = UiPageBuilder.ClampPage(s.View.SpawnPage, list.Count, 4); float num2 = 150f; foreach (UiSpawnSnapshot item in list.Skip(num * 4).Take(4)) { b.ActionRow("ChooseSpawn_" + item.Id, V(0f, num2), V(548f, 64f), UiFormat.Literal(item.Name), (item.IsRally ? "Rally" : UiFormat.TeamTag(s, item.Team)) + (item.Damaged ? " · Damaged — unavailable" : (item.Threatened ? " · Enemy nearby" : "")), item.Damaged ? "DISABLED" : (item.Selected ? (item.Threatened ? "SELECTED !" : "SELECTED") : (item.Threatened ? "THREAT" : "SELECT")), new UiAction { Id = UiActionId.SelectSpawn, Number = item.Id }, item.Selected, item.Threatened || item.Damaged).Enabled = !item.Damaged && !item.Threatened; num2 -= 74f; } if (list.Count == 0) { b.Text("NoSpawnChoices", V(0f, 60f), V(540f, 60f), 16f, UiAlignment.Center, "No respawn points yet", UiTheme.TextDim); } b.Pager("PlayerSpawns", num, list.Count, 4, -166f, UiActionId.SetPageIndex); b.Button("AutomaticSpawn", V(0f, -222f), V(420f, 46f), "AUTOMATIC", 14f, UiAction.Of(UiActionId.SelectAutomaticSpawn)); b.FeedbackBar(-286f); } private static void BuildInitialVehicles(UiPageBuilder b, UiSnapshot s) { b.PageTitle("Initial vehicles"); if (!s.Session.CanManage) { b.Text("InitialVehiclesDenied", V(0f, 60f), V(540f, 60f), 16f, UiAlignment.Center, "Host Access required", UiTheme.TextDim); return; } b.Text("InitialVehiclesStatus", V(0f, 218f), V(540f, 42f), 12f, UiAlignment.Center, s.Session.SessionActive ? "Stop the match to edit initial vehicles" : s.InitialVehicleStatus, UiTheme.TextDim); List<UiInitialVehicleOption> initialVehicleOptions = s.InitialVehicleOptions; int num = UiPageBuilder.ClampPage(s.View.InitialVehiclePage, initialVehicleOptions.Count, 3); float num2 = 150f; foreach (UiInitialVehicleOption row in initialVehicleOptions.Skip(num * 3).Take(3)) { int team = (int)row.Team; string text = "Initial_" + team + "_" + initialVehicleOptions.IndexOf(row); UiSpawnSnapshot uiSpawnSnapshot = s.Match.Spawns.FirstOrDefault((UiSpawnSnapshot p) => p.Id == row.SpawnId); b.Text(text + "Name", V(-40f, num2), V(450f, 32f), 14f, UiAlignment.Left, UiFormat.TeamTag(s, row.Team) + " · " + UiFormat.Short(row.Name, 28), UiTheme.TextWhite); UiButton uiButton = b.Button(text + "Minus", V(152f, num2 - 36f), V(48f, 38f), "−", 18f, new UiAction { Id = UiActionId.ChangeInitialVehicleCount, Team = row.Team, Text = row.Barcode, Number = -1 }); UiButton uiButton2 = b.Button(text + "Plus", V(238f, num2 - 36f), V(48f, 38f), "+", 18f, new UiAction { Id = UiActionId.ChangeInitialVehicleCount, Team = row.Team, Text = row.Barcode, Number = 1 }); uiButton.Enabled = !s.Session.SessionActive && row.Count > 0; uiButton2.Enabled = !s.Session.SessionActive && row.Count < 16 && (row.Limit <= 0 || row.Count < row.Limit); b.Text(text + "Count", V(195f, num2 - 36f), V(38f, 34f), 15f, UiAlignment.Center, row.Count.ToString(), UiTheme.TextWhite); b.Button(text + "Spawn", V(-74f, num2 - 36f), V(376f, 38f), (uiSpawnSnapshot == null) ? "TEAM SPAWN" : UiFormat.Short(uiSpawnSnapshot.Name, 30), 12f, new UiAction { Id = UiActionId.CycleInitialVehicleSpawn, Team = row.Team, Text = row.Barcode }).Enabled = !s.Session.SessionActive && row.Count > 0; num2 -= 106f; } if (initialVehicleOptions.Count == 0) { b.Text("InitialVehiclesEmpty", V(0f, 40f), V(540f, 60f), 16f, UiAlignment.Center, "No team vehicles configured", UiTheme.TextDim); } b.Pager("InitialVehicles", num, initialVehicleOptions.Count, 3, -208f, UiActionId.SetPageIndex); b.FeedbackBar(-286f); } private static void BuildHostSetupOverview(UiPageBuilder b, UiSnapshot s) { b.PageTitle("SETUP"); bool startReady = s.Match.StartReady; string startReadinessSummary = s.Match.StartReadinessSummary; b.Panel("SetupIdentity", V(0f, 202f), V(548f, 44f), UiTheme.Coffee, 0.72f); b.Text("SetupTemplate", V(-58f, 202f), V(410f, 26f), 11f, UiAlignment.Left, "ACTIVE • " + UiFormat.Short(s.Match.ActiveTemplateName, 30), UiTheme.TextWhite); b.Text("SetupState", V(220f, 202f), V(96f, 26f), 10f, UiAlignment.Right, startReady ? "READY" : "INCOMPLETE", startReady ? UiTheme.HealthGood : UiTheme.HealthWarn); int activeFactionCount = s.Setup.ActiveFactionCount; b.ActionRow("SetupTemplates", V(-138f, 148f), V(264f, 62f), "SAVED SETUPS", "Load or save setup", "", new UiAction { Id = UiActionId.OpenSetupTemplates, Page = UiPageId.HostSetupOverview }); b.ActionRow("SetupTeams", V(138f, 148f), V(264f, 62f), "TEAMS & LOADOUTS", activeFactionCount + " active teams", "", new UiAction { Id = UiActionId.OpenWorkflowEditor, Page = UiPageId.SetupContent }, activeFactionCount < 2, activeFactionCount < 2); b.ActionRow("SetupWorld", V(-138f, 76f), V(264f, 62f), "World", s.Match.Objectives.Count + " objectives • " + s.Match.Spawns.Count + " spawns", "", UiAction.Navigate(UiPageId.HostWorld)); b.ActionRow("SetupRules", V(138f, 76f), V(264f, 62f), "Rules", "Flow, combat, medical, spawn", "", new UiAction { Id = UiActionId.OpenWorkflowEditor, Page = UiPageId.HostSettings }); int num = UiDerive.IncompleteRosterCount(s); b.ActionRow("SetupLobby", V(-138f, 4f), V(264f, 62f), "PLAYERS", s.Roster.Count + " " + UiFormat.Plural(s.Roster.Count, "player") + " • " + num + " incomplete", (num > 0) ? "CHECK" : "", new UiAction { Id = UiActionId.OpenWorkflowEditor, Page = UiPageId.MilsimTeams }, primary: false, num > 0); b.ActionRow("SetupContent", V(138f, 4f), V(264f, 62f), "Loadout Pack", UiFormat.Short(s.Match.ActivePackStatusLine, 42), "", new UiAction { Id = UiActionId.OpenWorkflowEditor, Page = UiPageId.MilsimPacks }); b.ActionRow("SetupPreflight", V(0f, -82f), V(548f, 70f), startReady ? "Ready to start" : "Check match setup", UiFormat.Short(startReadinessSummary, 56), startReady ? "START" : "REVIEW", UiAction.Navigate(UiPageId.HostPreflight), startReady, !startReady); b.FeedbackBar(-286f); } private static void BuildHostLiveControl(UiPageBuilder b, UiSnapshot s) { b.PageTitle("CONTROL"); int num = UiDerive.IncompleteRosterCount(s); _ = s.ActiveTeams; string text = (s.Match.RespawnTokensEnabled ? UiFormat.SideScore(s) : "UNLIMITED LIVES"); b.Panel("LiveMatchStrip", V(0f, 202f), V(548f, 44f), UiTheme.Coffee, 0.78f); b.Text("LiveScore", V(-70f, 202f), V(390f, 26f), 13f, UiAlignment.Left, "<b>" + text + "</b> • " + s.Roster.Count + " " + UiFormat.Plural(s.Roster.Count, "PLAYER", "PLAYERS"), UiTheme.TextWhite); b.Text("LiveExceptions", V(220f, 202f), V(100f, 26f), 10f, UiAlignment.Right, (num > 0) ? (num + " NEED SETUP") : "HEALTHY", (num > 0) ? UiTheme.HealthWarn : UiTheme.HealthGood); int num2 = s.Match.ObjectivesOwnedBySide(1); int num3 = s.Match.ObjectivesOwnedBySide(2); b.Text("LiveObjectives", V(0f, 164f), V(548f, 24f), 10.5f, UiAlignment.Left, "OBJECTIVES • SIDE A " + num2 + " / NEUTRAL " + (s.Match.Objectives.Count - num2 - num3) + " / SIDE B " + num3, UiTheme.TextDim); b.ActionRow("LiveFreeze", V(0f, 108f), V(548f, 60f), s.Session.PlayersFrozen ? "UNFREEZE" : "FREEZE", s.Session.PlayersFrozen ? "Players frozen" : "Players active", "", UiAction.Of(UiActionId.TogglePlayersFrozen)); b.ConfirmButton("LiveStop", V(0f, 34f), V(548f, 62f), "END MATCH", 16f, "live_stop_match", UiAction.Of(UiActionId.StopMatch)); b.ActionRow("LiveSetup", V(-138f, -44f), V(264f, 60f), "SETUP", "Teams & loadouts", "", new UiAction { Id = UiActionId.OpenWorkflowEditor, Page = UiPageId.SetupContent }); b.ActionRow("LiveAdvanced", V(138f, -44f), V(264f, 60f), "ADVANCED", "World cleanup", "", UiAction.Navigate(UiPageId.HostLiveActions)); b.FeedbackBar(-286f); } private static void BuildHostWorld(UiPageBuilder b, UiSnapshot s) { b.PageTitle("World"); b.Text("WorldLevel", V(0f, 220f), V(548f, 28f), 11f, UiAlignment.Left, "LEVEL • " + UiFormat.Short(s.Session.SceneName, 42) + (s.Session.SessionActive ? " • LIVE EDIT" : ""), UiTheme.TextDim); b.ActionRow("WorldObjectives", V(0f, 132f), V(548f, 100f), "Objectives", s.Match.Objectives.Count + " placed • capture radius " + UiFormat.Number(s.Match.CaptureRadius) + "m", "EDIT", new UiAction { Id = UiActionId.OpenWorkflowEditor, Page = UiPageId.MilsimPoints }); b.ActionRow("WorldSpawns", V(0f, 20f), V(548f, 100f), "Spawns", s.Match.Spawns.Count + " placed • shared and team coverage", "EDIT", new UiAction { Id = UiActionId.OpenWorkflowEditor, Page = UiPageId.MilsimRespawns }); b.ActionRow("WorldVehicles", V(0f, -194f), V(548f, 76f), "VEHICLES", s.Session.SessionActive ? "Team vehicles" : "Starting vehicles by team", "EDIT", UiAction.Navigate(s.Session.SessionActive ? UiPageId.MilsimFactionVehicles : UiPageId.InitialVehicles)); b.ActionRow("WorldAdvanced", V(0f, -92f), V(548f, 76f), "ADVANCED", "World cleanup", "OPEN", UiAction.Navigate(UiPageId.HostLiveActions)); } private static void BuildHostPreflight(UiPageBuilder b, UiSnapshot s) { b.PageTitle("Preflight"); bool startReady = s.Match.StartReady; string startReadinessSummary = s.Match.StartReadinessSummary; List<string> compatibilityWatchMods = s.Session.CompatibilityWatchMods; bool flag = compatibilityWatchMods.Count == 0; bool codeModGateClean = s.Session.CodeModGateClean; int num = s.Roster.Where((UiPlayerSnapshot entry) => entry.Team != UiTeam.Unassigned).ToList().Count((UiPlayerSnapshot entry) => entry.IsReady && !string.IsNullOrWhiteSpace(entry.LoadoutName)); int num2 = s.Roster.Count - num; b.Text("PreflightState", V(0f, 222f), V(548f, 28f), 13f, UiAlignment.Left, startReady ? "READY TO START" : "CONFIGURATION BLOCKED", startReady ? UiTheme.HealthGood : UiTheme.HealthCrit); PreflightRow(b, "PreflightPack", 174f, "Loadout Pack", s.Match.ActivePackLoadoutCount > 0, UiFormat.Short(s.Match.ActivePackStatusLine, 58), UiPageId.MilsimPacks); PreflightRow(b, "PreflightTeams", 116f, "TEAMS & LOADOUTS", s.Setup.ActiveFactionCount >= 2, s.Setup.ActiveFactionCount + " ACTIVE TEAMS", UiPageId.SetupContent); PreflightRow(b, "PreflightSpawns", 58f, "Team spawns", s.Setup.HasRequiredSpawns, s.Match.Spawns.Count + " PLACED", UiPageId.HostWorld); b.InfoRow("PreflightCodeModGate", V(0f, 0f), V(548f, 54f), "Code Mod Gate", UiFormat.Short(s.Session.CodeModGateStatus, 58), codeModGateClean ? "CLEAR" : "NOTICE", codeModGateClean ? UiTheme.HealthGood : UiTheme.HealthWarn); b.InfoRow("PreflightCompatibility", V(0f, -58f), V(548f, 54f), flag ? "Compatibility" : "Optional compatibility notice", flag ? "NO WATCH-LIST MODS DETECTED" : string.Join(", ", compatibilityWatchMods.ToArray()), flag ? "CLEAR" : "START ALLOWED", flag ? UiTheme.HealthGood : UiTheme.HealthWarn); PreflightRow(b, "PreflightPlayers", -116f, "Players", good: true, num + " DEPLOY NOW • " + num2 + " JOIN AFTER START", UiPageId.MilsimTeams); b.ActionRow("PreflightStart", V(0f, -190f), V(548f, 50f), startReady ? "Start match" : "Fix blockers before start", UiFormat.Short(startReadinessSummary, 60), startReady ? "START" : "BLOCKED", UiAction.Of(UiActionId.StartMatch), startReady, !startReady); b.FeedbackBar(-286f); } private static void PreflightRow(UiPageBuilder b, string name, float y, string title, bool good, string detail, UiPageId target) { b.ActionRow(name, V(0f, y), V(548f, 54f), title, detail, good ? "OK" : "FIX", UiAction.Navigate(target), primary: false, !good); } private static void BuildHostLiveActions(UiPageBuilder b, UiSnapshot s) { b.PageTitle("ADVANCED"); b.Text("LiveActionScope", V(0f, 220f), V(548f, 32f), 11f, UiAlignment.Left, s.Session.NetworkActive ? "HOST AUTHORITY • ALL PLAYERS" : "SOLO • OWNERSHIP-AWARE CLEANUP MAY BE LIMITED", UiTheme.TextDim); b.ConfirmButton("LiveCleanup", V(0f, 146f), V(548f, 60f), "CLEAN ALL SPAWNED ITEMS", 14f, "live_cleanup_all", UiAction.Of(UiActionId.CleanupSpawnedItems)); b.ConfirmButton("LiveCorpses", V(0f, 76f), V(548f, 60f), "CLEAR ALL CORPSES", 14f, "live_clear_corpses", UiAction.Of(UiActionId.ClearCorpses)); b.ConfirmButton("LiveWrecks", V(0f, 6f), V(548f, 60f), "CLEAR ALL VEHICLE WRECKS", 14f, "live_clear_wrecks", UiAction.Of(UiActionId.ClearWrecks)); b.Text("LiveStopScope", V(0f, -70f), V(520f, 30f), 10f, UiAlignment.Center, "STOP RETURNS PLAYERS TO THE ORIGINAL MAP SPAWN", UiTheme.TextDim); b.ConfirmButton("LiveStop", V(0f, -126f), V(548f, 72f), "END MATCH", 18f, "live_stop_match", UiAction.Of(UiActionId.StopMatch)); b.FeedbackBar(-286f); } private static UiVector2 V(float x, float y) { return new UiVector2(x, y); } private static void BuildHome(UiPageBuilder b, UiSnapshot s) { b.PageTitle("Main Menu"); int playerCount = s.Session.PlayerCount; b.Text("HomeConnection", V(0f, 226f), V(548f, 26f), 11f, UiAlignment.Left, (s.Session.SessionActive ? "LIVE" : "LOBBY") + " • " + (s.Session.NetworkActive ? "FUSION" : "SOLO") + " • " + playerCount + " " + UiFormat.Plural(playerCount, "PLAYER", "PLAYERS"), UiTheme.TextDim); b.Text("HomeRole", V(0f, 226f), V(548f, 26f), 11f, UiAlignment.Right, UiDerive.LocalRoleSummary(s), UiTheme.Orange); UiPageId page = UiDerive.PrimaryNextPage(s); b.ActionRow("HomeContinue", V(0f, 164f), V(548f, 74f), UiDerive.PrimaryNextTitle(page), UiDerive.PrimaryNextDetail(page), "OPEN", UiAction.Navigate(page), primary: true); b.ActionRow("HomeAccount", V(0f, 60f), V(548f, 66f), "ACCOUNT / ONLINE", "Pairing and servers", "", UiAction.Navigate(UiPageId.AccountPairing)); b.ActionRow("HomeServerBrowser", V(0f, -20f), V(548f, 66f), "SPECTRUMB SERVER BROWSER", "Browse and join servers", "OPEN", UiAction.Navigate(UiPageId.ServerBrowser)); } private static void BuildPlayerReadyRoom(UiPageBuilder b, UiSnapshot s, bool live) { BuildPlayerField(b, s); } private static void BuildTeamSelection(UiPageBuilder b, UiSnapshot s) { b.PageTitle("TEAM"); b.Text("TeamHint", V(0f, 218f), V(548f, 28f), 11f, UiAlignment.Left, s.Session.SessionActive ? "Applies on next deployment" : "Choose team", UiTheme.TextDim); TeamPicker(b, s, 140f); b.FeedbackBar(-286f); } private static void TeamPicker(UiPageBuilder b, UiSnapshot s, float y) { List<UiTeamSnapshot> activeTeams = s.ActiveTeams; if (activeTeams.Count == 0) { b.Text("TeamPickerEmpty", V(0f, y), V(520f, 40f), 14f, UiAlignment.Center, "NO TEAMS CONFIGURED", UiTheme.TextDim); return; } bool flag = activeTeams.Count == 4; int num = (flag ? 4 : (activeTeams.Count + (s.Local.HasTeam ? 1 : 0))); float num2 = ((num <= 2) ? 264f : ((num == 3) ? 174f : 130f)); float[] array = UiPageBuilder.SpreadPositions(num, num2 + 8f); if (flag) { num2 = 264f; } for (int i = 0; i < activeTeams.Count; i++) { UiTeamSnapshot uiTeamSnapshot = activeTeams[i]; bool flag2 = s.Local.Team == uiTeamSnapshot.Team; bool flag3 = false; UiButton uiButton = b.Button("TeamPick_" + uiTeamSnapshot.Team, flag ? V((i % 2 == 0) ? (-138f) : 138f, y + ((i < 2) ? 15f : (-23f))) : V(array[i], y), V(num2, flag ? 32f : 56f), UiFormat.Short(string.IsNullOrWhiteSpace(uiTeamSnapshot.DisplayName) ? uiTeamSnapshot.Tag : uiTeamSnapshot.DisplayName, flag ? 26 : ((num >= 4) ? 14 : 22)) + ((!flag3) ? "" : (flag ? " · REQUEST" : "\nREQUEST")), flag ? 12f : 14f, new UiAction { Id = UiActionId.SelectTeam, Team = uiTeamSnapshot.Team }.Confirm(flag2 ? "" : ("team_" + uiTeamSnapshot.Team))); uiButton.Tint = (flag2 ? uiTeamSnapshot.Color : UiTheme.ButtonBrown); uiButton.TintAlpha = (flag2 ? 0.56f : 0.62f); uiButton.TextColor = (flag2 ? UiTheme.TextWhite : UiTheme.TextDim); if (flag2) { b.Rect("TeamPickRail_" + uiTeamSnapshot.Team, V(0f, flag ? (-14f) : (-24f)), V(num2 - 18f, 3f), UiTheme.Orange, 1f, 0f, uiButton); } } if (s.Local.HasTeam) { UiButton uiButton2 = b.Button("TeamLeave", flag ? V(205f, 184f) : V(array[num - 1], y), flag ? V(130f, 24f) : V(num2, 56f), "LEAVE", (num >= 4) ? 11f : 12f, UiAction.Of(UiActionId.LeaveTeam).Confirm("leave_team")); uiButton2.Tint = UiTheme.HealthCrit; uiButton2.TintAlpha = 0.22f; } } private static void BuildPlayerField(UiPageBuilder b, UiSnapshot s) { b.PageTitle("GAME"); bool sessionActive = s.Session.SessionActive; bool flag = s.Medical.State == UiMedicalState.Dead || s.Medical.State == UiMedicalState.Respawning; string text = (sessionActive ? (flag ? ("RESPAWN " + Math.Ceiling(s.Local.RespawnRemaining) + "s") : (s.Local.LateJoinCountdownActive ? "DEPLOYING" : "LIVE")) : (s.Local.Ready ? "READY" : "NOT READY")); b.Text("GameStatus", V(0f, 220f), V(548f, 26f), 11f, UiAlignment.Left, text, UiTheme.Orange); b.ActionRow("GameTeam", V(0f, 158f), V(548f, 62f), "TEAM", s.Local.HasTeam ? UiFormat.TeamName(s, s.Local.Team) : "Choose Team", "", UiAction.Navigate(UiPageId.PlayerTeamsLoadout), !s.Local.HasTeam); b.ActionRow("GameLoadout", V(0f, 86f), V(548f, 62f), "LOADOUT", (!s.Local.HasTeam) ? "Unavailable until team selected" : (s.Local.HasLoadout ? s.Local.LoadoutName : "Choose Loadout"), "", new UiAction { Id = UiActionId.OpenLoadoutSelector, Page = UiPageId.PlayerField }, s.Local.HasTeam && !s.Local.HasLoadout, !s.Local.HasTeam).Enabled = s.Local.HasTeam; if (sessionActive) { b.ActionRow("GameSpawn", V(0f, 14f), V(548f, 62f), "SPAWN", s.SpawnSelectionStatus, "", UiAction.Navigate(UiPageId.PlayerSpawns)); } if (sessionActive && s.Local.HasPendingDeployment) { b.Text("GamePending", V(0f, -56f), V(548f, 48f), 10.5f, UiAlignment.Left, "CURRENT " + UiFormat.TeamName(s, s.Local.CurrentTeam) + " / " + s.Local.CurrentLoadoutName + "\nNEXT DEPLOYMENT " + UiFormat.TeamName(s, s.Local.Team) + " / " + s.Local.LoadoutName, UiTheme.TextDim); } bool flag2 = s.Local.HasTeam && s.Local.HasLoadout; bool flag3 = sessionActive && !flag && !s.Local.LateJoinPending; bool flag4 = flag2 && s.Local.CanDeploy && !s.Local.LateJoinCountdownActive; string title = (sessionActive ? (flag3 ? "REDEPLOY" : (flag ? "DEPLOY WHEN READY" : "DEPLOY")) : (s.Local.Ready ? "NOT READY" : "READY")); UiAction uiAction = UiAction.Of(sessionActive ? UiActionId.Deploy : UiActionId.ToggleReady); if (flag3) { uiAction.Confirm("game_redeploy"); } b.ActionRow("GamePrimary", V(0f, -128f), V(548f, 64f), title, (sessionActive && !flag4) ? s.Local.DeployBlockReason : "", "", uiAction, primary: true, sessionActive ? (!flag4) : (!flag2)).Enabled = (sessionActive ? flag4 : flag2); b.Button("GameBrief", V(-138f, -192f), V(264f, 38f), "OBJECTIVES", 11f, UiAction.Navigate(UiPageId.PlayerBrief)); b.Button("GameSupplies", V(138f, -192f), V(264f, 38f), "SUPPLIES", 11f, UiAction.Navigate(UiPageId.PlayerKitSupplies)); b.Button("GameOverview", V(-138f, -238f), V(264f, 38f), "GAME OVERVIEW", 11f, UiAction.Navigate(UiPageId.MilsimOverview)); b.Button("GameVehicles", V(138f, -238f), V(264f, 38f), "VEHICLE REQUEST", 11f, UiAction.Navigate(UiPageId.PlayerVehicleRequest)); b.FeedbackBar(-286f); } private static void BuildPlayerBrief(UiPageBuilder b, UiSnapshot s) { b.PageTitle("OBJECTIVES"); b.Text("ObjectiveScore", V(0f, 220f), V(548f, 26f), 12f, UiAlignment.Left, UiFormat.SideScore(s), UiTheme.Orange); int num = UiPageBuilder.ClampPage(s.View.ObjectivePage, s.Match.Objectives.Count, 5); float num2 = 166f; foreach (UiObjectiveSnapshot item in s.Match.Objectives.Skip(num * 5).Take(5)) { b.InfoRow("Objective_" + item.Id, V(0f, num2), V(548f, 56f), item.Name, (item.Owner == UiTeam.Unassigned) ? "NEUTRAL" : UiFormat.TeamName(s, item.Owner), item.Contested ? "CONTESTED" : ""); num2 -= 64f; } if (s.Match.Objectives.Count == 0) { b.Text("NoObjectives", V(0f, 40f), V(520f, 42f), 14f, UiAlignment.Center, "NO OBJECTIVES", UiTheme.TextDim); } b.Pager("Objectives", num, s.Match.Objectives.Count, 5, -206f, UiActionId.SetPageIndex); } private static void BuildRoster(UiPageBuilder b, UiSnapshot s, bool squadOnly) { b.PageTitle(squadOnly ? "SQUAD" : "TEAM"); UiTeam localTeam = (s.Local.HasPendingDeployment ? s.Local.CurrentTeam : s.Local.Team); List<UiPlayerSnapshot> list = (from entry in s.Roster where entry.Team == localTeam && localTeam != UiTeam.Unassigned orderby (entry.Team != localTeam) ? 1 : 0, entry.IsSquadLeader descending select entry).ThenBy<UiPlayerSnapshot, string>((UiPlayerSnapshot entry) => entry.Name, StringComparer.OrdinalIgnoreCase).ToList(); int num = UiPageBuilder.ClampPage(s.View.RosterPage, list.Count, 5); List<UiPlayerSnapshot> list2 = list.Skip(num * 5).Take(5).ToList(); UiTeamSnapshot uiTeamSnapshot = s.Team(localTeam); string value = ((uiTeamSnapshot != null) ? uiTeamSnapshot.SquadLeaderName : ""); string text = ((localTeam == UiTeam.Unassigned) ? (list.Count + " " + UiFormat.Plural(list.Count, "PLAYER", "PLAYERS") + " • NO SQUAD") : ("SQUAD LEADER: " + (string.IsNullOrWhiteSpace(value) ? "NOT ASSIGNED" : UiFormat.Short(value, 28)))); b.Text("RosterSummary", V(0f, 222f), V(548f, 26f), 11f, UiAlignment.Left, "<b>" + text + "</b>", UiTheme.Orange); float num2 = 170f; for (int num3 = 0; num3 < list2.Count; num3++) { UiPlayerSnapshot uiPlayerSnapshot = list2[num3]; string text2 = UiDerive.MemberState(s, uiPlayerSnapshot); UiColor uiColor = (UiDerive.MemberHasProblem(s, uiPlayerSnapshot) ? UiTheme.HealthWarn : (uiPlayerSnapshot.IsSquadLeader ? UiTheme.Orange : UiTheme.OutlineGray)); b.Panel("PlayerRoster_" + num3, V(0f, num2), V(548f, 52f), UiTheme.Coffee, 0.68f); b.Rect("PlayerRosterRail_" + num3, V(-270f, num2), V(4f, 38f), uiColor, 0.96f); b.Text("PlayerRosterName_" + num3, V(-118f, num2 + 8f), V(270f, 22f), 13f, UiAlignment.Left, "<b>" + UiFormat.Short(uiPlayerSnapshot.Name, 22) + "</b>" + (uiPlayerSnapshot.IsSquadLeader ? " • SL" : ""), UiTheme.TextWhite); b.Text("PlayerRosterKit_" + num3, V(-118f, num2 - 12f), V(300f, 20f), 11f, UiAlignment.Left, UiFormat.TeamTag(s, uiPlayerSnapshot.Team) + " • " + UiFormat.Short(uiPlayerSnapshot.LoadoutName, 22), UiTheme.TextDim); b.Text("PlayerRosterState_" + num3, V(198f, num2), V(120f, 24f), 10f, UiAlignment.Right, text2, (UiDerive.MemberHasProblem(s, uiPlayerSnapshot) || uiPlayerSnapshot.IsSquadLeader) ? uiColor : UiTheme.TextDim); num2 -= 60f; } if (list2.Count == 0) { b.Text("RosterEmpty", V(0f, 40f), V(520f, 54f), 16f, UiAlignment.Center, "NO PLAYERS IN THIS VIEW", UiTheme.TextDim); } b.Pager("WorkspaceRoster", num, list.Count, 5, -158f, UiActionId.SetPageIndex); if (!squadOnly) { b.Button("TeamVehicleRequest", V(0f, -222f), V(420f, 38f), "VEHICLE REQUEST", 11f, UiAction.Navigate(UiPageId.PlayerVehicleRequest)); } } private static void BuildPlayerKitSupplies(UiPageBuilder b, UiSnapshot s) { b.PageTitle("Loadout & Supplies"); string text = (s.Local.HasPendingDeployment ? s.Local.CurrentLoadoutName : s.Local.LoadoutName); b.Panel("KitIdentity", V(0f, 208f), V(548f, 68f), UiTheme.Coffee, 0.82f); b.Text("KitName", V(-74f, 221f), V(390f, 28f), 17f, UiAlignment.Left, "<b>" + UiFormat.Short(string.IsNullOrWhiteSpace(text) ? "NO LOADOUT" : text, 30) + "</b>", UiTheme.TextWhite); b.Text("KitFaction", V(-74f, 194f), V(390f, 22f), 10f, UiAlignment.Left, s.Local.HasTeam ? UiFormat.TeamName(s, s.Local.HasPendingDeployment ? s.Local.CurrentTeam : s.Local.Team) : "UNASSIGNED", UiTheme.TextDim); b.InfoRow("KitItems", V(0f, 122f), V(548f, 62f), "Issued items", s.Local.ItemPreview, s.Local.LoadoutItemCount + " " + UiFormat.Plural(s.Local.LoadoutItemCount, "ITEM", "ITEMS")); string detail = (s.Local.AmmoRuleSet ? ("LIGHT " + UiFormat.Supply(s.Local.AmmoLight) + " • MED " + UiFormat.Supply(s.Local.AmmoMedium) + " • HEAVY " + UiFormat.Supply(s.Local.AmmoHeavy)) : "INHERITED FROM PACK"); b.InfoRow("KitAmmo", V(0f, 50f), V(548f, 62f), "Ammunition", detail); b.InfoRow("KitMedical", V(0f, -22f), V(548f, 62f), "Medical supplies", s.Local.MedicalSupplyLine); b.InfoRow("KitWeight", V(0f, -94f), V(548f, 62f), "Carried weight", UiFormat.Number(s.Local.CarriedMassKg, "0.0") + " kg • movement " + UiFormat.Number(s.Local.MovementScalar * 100f, "0") + "%", s.Local.WeightTier.ToUpperInvariant()); b.Text("KitReadOnly", V(0f, -166f), V(520f, 30f), 10f, UiAlignment.Center, "Change loadout on GAME", UiTheme.TextDim); } private static void BuildPlayerVehicleRequest(UiPageBuilder b, UiSnapshot s) { b.PageTitle("Vehicle Request"); UiTeam team = (s.Local.HasPendingDeployment ? s.Local.CurrentTeam : s.Local.Team); List<UiVehicleSnapshot> list = ((team != UiTeam.Unassigned) ? s.AvailableVehicles.Where((UiVehicleSnapshot vehicle) => vehicle.Team == team).ToList() : new List<UiVehicleSnapshot>()); int num = UiPageBuilder.ClampPage(s.View.VehiclePage, list.Count, 5); List<UiVehicleSnapshot> list2 = list.Skip(num * 5).Take(5).ToList(); UiVehicleRequestSnapshot uiVehicleRequestSnapshot = UiDerive.LocalPendingVehicleRequest(s); b.Text("VehicleRequestState", V(0f, 222f), V(548f, 28f), 11f, UiAlignment.Left, (uiVehicleRequestSnapshot == null) ? "SELECT A VEHICLE" : ("PENDING • " + UiFormat.Short(uiVehicleRequestSnapshot.VehicleName, 28)), (uiVehicleRequestSnapshot == null) ? UiTheme.TextDim : UiTheme.HealthWarn); float num2 = 170f; for (int num3 = 0; num3 < list2.Count; num3++) { UiVehicleSnapshot uiVehicleSnapshot = list2[num3]; string text = ((uiVehicleSnapshot.MaxSpawns <= 0) ? (uiVehicleSnapshot.ActiveSpawns + "/∞") : (uiVehicleSnapshot.ActiveSpawns + "/" + uiVehicleSnapshot.MaxSpawns)); b.ActionRow("RequestVehicle_" + num3, V(0f, num2), V(548f, 52f), UiFormat.Literal(uiVehicleSnapshot.Name), uiVehicleSnapshot.VehicleClass + " • CREW " + uiVehicleSnapshot.CrewRequirement + " • ACTIVE " + text, (uiVehicleSnapshot.TicketCost <= 0) ? "FREE" : (uiVehicleSnapshot.TicketCost + ((uiVehicleSnapshot.TicketCost == 1) ? " LIFE" : " LIVES")), new UiAction { Id = UiActionId.RequestVehicle, Text = uiVehicleSnapshot.Name }); num2 -= 58f; } if (list2.Count == 0) { b.Text("VehicleEmpty", V(0f, 40f), V(520f, 70f), 16f, UiAlignment.Center, s.Local.HasTeam ? "NO VEHICLES CONFIGURED FOR YOUR TEAM" : "CHOOSE A TEAM BEFORE REQUESTING A VEHICLE", UiTheme.TextDim); } b.Pager("PlayerVehicles", num, list.Count, 5, -150f, UiActionId.SetPageIndex); b.FeedbackBar(-286f); } private static void BuildSquadLanding(UiPageBuilder b, UiSnapshot s, bool live) { b.PageTitle(live ? "Squad Command" : "Squad Assembly"); UiTeam team = (s.Local.HasPendingDeployment ? s.Local.CurrentTeam : s.Local.Team); UiTeamSnapshot uiTeamSnapshot = s.Team(team); List<UiPlayerSnapshot> list = UiDerive.LocalSquad(s); int count = s.JoinRequests.Count; int count2 = s.VehicleRequests.Count; int num = list.Count((UiPlayerSnapshot entry) => UiDerive.MemberHasProblem(s, entry)); _ = uiTeamSnapshot?.Locked ?? false; b.Panel("SquadStrip", V(0f, 218f), V(548f, 50f), UiTheme.Coffee, 0.78f); b.Text("SquadStripText", V(-34f, 218f), V(430f, 28f), 11f, UiAlignment.Left, UiFormat.TeamTag(s, team) + " • " + list.Count + " MEMBERS" + ((live && s.Match.RespawnTokensEnabled && uiTeamSnapshot != null) ? (" • " + uiTeamSnapshot.Tokens + " LIVES") : ""), UiTheme.TextWhite); b.Text("SquadProblemCount", V(220f, 218f), V(86f, 28f), 10f, UiAlignment.Right, (num > 0) ? (num + " " + UiFormat.Plural(num, "ISSUE", "ISSUES")) : "READY", (num > 0) ? UiTheme.HealthWarn : UiTheme.HealthGood); int num2 = count + count2; b.ActionRow("SquadDecision", V(0f, 146f), V(548f, 66f), (num2 > 0) ? (num2 + " " + UiFormat.Plural(num2, "decision") + " waiting") : "Command queue clear", count + " JOIN • " + count2 + " VEHICLE", (num2 > 0) ? "REVIEW" : "CLEAR", UiAction.Navigate(UiPageId.SquadQueue), num2 > 0); List<UiPlayerSnapshot> list2 = list.Where((UiPlayerSnapshot entry) => UiDerive.MemberHasProblem(s, entry)).Take(3).ToList(); float num3 = 72f; for (int num4 = 0; num4 < list2.Count; num4++) { UiPlayerSnapshot uiPlayerSnapshot = list2[num4]; b.ActionRow("SquadProblem_" + num4, V(0f, num3), V(548f, 52f), UiFormat.Literal(uiPlayerSnapshot.Name), UiFormat.Short(uiPlayerSnapshot.LoadoutName, 22), UiDerive.MemberState(s, uiPlayerSnapshot), new UiAction { Id = UiActionId.SelectRosterPlayer, PlayerId = uiPlayerSnapshot.PlatformId.ToString(CultureInfo.InvariantCulture), Flag = uiPlayerSnapshot.IsLocal, Text = uiPlayerSnapshot.Name, Page = UiPageId.MilsimPlayerEdit }, primary: false, warning: true); num3 -= 58f; } if (list2.Count == 0) { b.Panel("SquadReady", V(0f, 22f), V(548f, 94f), UiTheme.Coffee, 0.5f); b.Text("SquadReadyText", V(0f, 22f), V(510f, 48f), 15f, UiAlignment.Center, "SQUAD READY\nNO PLAYERS NEED HELP", UiTheme.HealthGood); } b.ActionRow("SquadRosterOpen", V(-138f, -142f), V(264f, 58f), live ? "Squad status" : "Squad roster", "Members and loadouts", "", UiAction.Navigate(UiPageId.SquadRoster)); b.ActionRow("SquadPolicyOpen", V(138f, -142f), V(264f, 58f), "Team policy", "Capacity and requests", "", UiAction.Navigate(UiPageId.SquadPolicy)); if (live) { b.Text("RallyStatus", V(0f, -184f), V(540f, 24f), 12f, UiAlignment.Center, s.Local.RallyStatus, UiTheme.TextDim); b.Button("PlaceRally", V(-138f, -222f), V(264f, 46f), "PLACE RALLY", 13f, UiAction.Of(UiActionId.PlaceRally)).Enabled = s.Local.CanPlaceRally; b.ConfirmButton("RemoveRally", V(138f, -222f), V(264f, 46f), "REMOVE RALLY", 13f, "remove_rally", UiAction.Of(UiActionId.RemoveRally)).Enabled = s.Local.CanRemoveRally; } b.FeedbackBar(-286f); } private static void BuildSquadCommandQueue(UiPageBuilder b, UiSnapshot s) { b.PageTitle("Command Queue"); List<UiJoinRequestSnapshot> joinRequests = s.JoinRequests; List<UiVehicleRequestSnapshot> vehicleRequests = s.VehicleRequests; UiSquadQueueFilter squadQueueFilter = s.View.SquadQueueFilter; int itemCount = squadQueueFilter switch { UiSquadQueueFilter.Vehicle => vehicleRequests.Count, UiSquadQueueFilter.Join => joinRequests.Count, _ => joinRequests.Count + vehicleRequests.Count, }; b.Button("QueueFilter", V(0f, 222f), V(300f, 38f), "FILTER • " + squadQueueFilter.ToString().ToUpperInvariant(), 11f, new UiAction { Id = UiActionId.SetSquadQueueFilter, Number = (int)(squadQueueFilter + 1) % 3 }); int num = UiPageBuilder.ClampPage(s.View.SquadQueuePage, itemCount, 4); int count = num * 4; List<QueueRow> list = new List<QueueRow>(); if (squadQueueFilter != UiSquadQueueFilter.Vehicle) { list.AddRange(joinRequests.Select((UiJoinRequestSnapshot request) => new QueueRow { Join = true, Id = request.Id, Name = request.RequesterName, Detail = "JOIN " + UiFormat.TeamTag(s, request.Team) })); } if (squadQueueFilter != UiSquadQueueFilter.Join) { list.AddRange(vehicleRequests.Select((UiVehicleRequestSnapshot request) => new QueueRow { Join = false, Id = request.Id, Name = request.RequesterName, Detail = request.VehicleName })); } List<QueueRow> list2 = list.Skip(count).Take(4).ToList(); float num2 = 164f; for (int num3 = 0; num3 < list2.Count; num3++) { QueueRow queueRow = list2[num3]; b.Panel("QueueRow_" + num3, V(0f, num2), V(548f, 72f), UiTheme.Coffee, 0.78f); b.Rect("QueueRail_" + num3, V(-270f, num2), V(4f, 56f), UiTheme.HealthWarn, 0.96f); b.Text("QueueName_" + num3, V(-130f, num2 + 13f), V(260f, 24f), 13f, UiAlignment.Left, "<b>" + UiFormat.Short(queueRow.Name, 20) + "</b>", UiTheme.TextWhite); b.Text("QueueDetail_" + num3, V(-130f, num2 - 13f), V(280f, 22f), 12f, UiAlignment.Left, (queueRow.Join ? "JOIN" : "VEHICLE") + " • " + UiFormat.Short(queueRow.Detail, 24), UiTheme.TextDim); b.Button("QueueApprove_" + num3, V(136f, num2), V(106f, 40f), "APPROVE", 12f, new UiAction { Id = (queueRow.Join ? UiActionId.ApproveJoinRequest : UiActionId.ApproveVehicleRequest), Number = queueRow.Id }); UiButton uiButton = b.Button("QueueDeny_" + num3, V(230f, num2), V(74f, 40f), "DENY", 12f, new UiAction { Id = (queueRow.Join ? UiActionId.DenyJoinRequest : UiActionId.DenyVehicleRequest), Number = queueRow.Id }); uiButton.Tint = UiTheme.HealthCrit; uiButton.TintAlpha = 0.2f; num2 -= 80f; } if (list2.Count == 0) { b.Text("QueueEmpty", V(0f, 40f), V(520f, 72f), 16f, UiAlignment.Center, "NO COMMAND DECISIONS WAITING", UiTheme.HealthGood); } b.Pager("SquadQueue", num, itemCount, 4, -174f, UiActionId.SetPageIndex); b.FeedbackBar(-286f); } private static void BuildSquadRoster(UiPageBuilder b, UiSnapshot s) { b.PageTitle("Squad Status"); List<UiPlayerSnapshot> list = UiDerive.LocalSquad(s); int num = UiPageBuilder.ClampPage(s.View.RosterPage, list.Count, 5); List<UiPlayerSnapshot> list2 = list.Skip(num * 5).Take(5).ToList(); int num2 = list.Count((UiPlayerSnapshot entry) => UiDerive.MemberHasProblem(s, entry)); b.Text("SquadRosterSummary", V(0f, 222f), V(548f, 28f), 11f, UiAlignment.Left, list.Count + " " + UiFormat.Plural(list.Count, "MEMBER", "MEMBERS") + " • " + num2 + " " + UiFormat.Plural(num2, "PLAYER NEEDS SETUP", "PLAYERS NEED SETUP"), (num2 > 0) ? UiTheme.HealthWarn : UiTheme.HealthGood); float num3 = 170f; for (int num4 = 0; num4 < list2.Count; num4++) { UiPlayerSnapshot uiPlayerSnapshot = list2[num4]; b.ActionRow("SquadRoster_" + num4, V(0f, num3), V(548f, 52f), UiFormat.Literal(uiPlayerSnapshot.Name), UiFormat.Short(uiPlayerSnapshot.LoadoutName, 24), UiDerive.MemberState(s, uiPlayerSnapshot), new UiAction { Id = UiActionId.SelectRosterPlayer, PlayerId = uiPlayerSnapshot.PlatformId.ToString(CultureInfo.InvariantCulture), Flag = uiPlayerSnapshot.IsLocal, Text = uiPlayerSnapshot.Name, Page = UiPageId.MilsimPlayerEdit }, primary: false, UiDerive.MemberHasProblem(s, uiPlayerSnapshot)); num3 -= 60f; } b.Pager("SquadRoster", num, list.Count, 5, -158f, UiActionId.SetPageIndex); b.FeedbackBar(-286f); } private static void BuildSquadPolicy(UiPageBuilder b, UiSnapshot s) { b.PageTitle("Team Policy"); UiTeam team = (s.Local.HasPendingDeployment ? s.Local.CurrentTeam : s.Local.Team); UiTeamSnapshot uiTeamSnapshot = s.Team(team); _ = uiTeamSnapshot?.Locked ?? false; List<UiPlayerSnapshot> list = UiDerive.LocalSquad(s); int num = uiTeamSnapshot?.MemberLimit ?? 0; b.Panel("PolicyState", V(0f, 190f), V(548f, 92f), UiTheme.Coffee, 0.78f); b.Text("PolicyName", V(0f, 207f), V(510f, 28f), 17f, UiAlignment.Center, "<b>" + UiFormat.TeamName(s, team) + "</b>", UiTheme.TextWhite); b.Text("PolicyCapacity", V(0f, 174f), V(510f, 22f), 10.5f, UiAlignment.Center, list.Count + "/" + ((num <= 0) ? "∞" : num.ToString()) + " MEMBERS • DIRECT JOIN", UiTheme.TextDim); b.InfoRow("PolicyJoin", V(0f, 72f), V(500f, 68f), "TEAM", "Players choose their team directly"); int count = s.JoinRequests.Count; b.ActionRow("PolicyRequests", V(0f, -76f), V(500f, 62f), "Pending join requests", "Review membership decisions", count.ToString(), new UiAction { Id = UiActionId.SetSquadQueueFilter, Number = 1, Flag = true }, count > 0); b.FeedbackBar(-286f); } private static void BuildSettings(UiPageBuilder b, UiSnapshot s) { b.PageTitle("Settings"); b.ActionRow("SettingsMaster", V(0f, 200f), V(548f, 72f), s.Session.ModEnabled ? "SpectrumB is on" : "SpectrumB is off", s.Session.ModEnabled ? "Turning it off stops Milsim, Medical and Fusion sync" : "Everything else stays locked until this is back on", s.Session.ModEnabled ? "ON" : "OFF", UiAction.Of(UiActionId.ToggleModEnabled), !s.Session.ModEnabled, warning: false, s.Session.ModEnabled); b.ActionRow("SettingsGameplay", V(-138f, 108f), V(264f, 62f), "Gameplay", "Movement, recoil, stamina", "", new UiAction { Id = UiActionId.Navigate, Page = UiPageId.SettingsCategory, Number = 0 }); b.ActionRow("SettingsAudio", V(138f, 108f), V(264f, 62f), "Audio & haptics", "Feedback volume and rumble", "", new UiAction { Id = UiActionId.Navigate, Page = UiPageId.SettingsCategory, Number = 1 }); b.ActionRow("SettingsInterface", V(-138f, 36f), V(264f, 62f), "Interface", "Cursor, overlays, motion", "", new UiAction { Id = UiActionId.Navigate, Page = UiPageId.SettingsCategory, Number = 2 }); b.ActionRow("SettingsGeneral", V(138f, 36f), V(264f, 62f), "General", "Medical menu button and diagnostics", "", new UiAction { Id = UiActionId.Navigate, Page = UiPageId.SettingsCategory, Number = 3 }); b.ActionRow("SettingsLanguage", V(0f, -46f), V(548f, 58f), "Language", UiStrings.Language(s.View.Language).NativeName, "CHANGE", new UiAction { Id = UiActionId.OpenLanguage, Page = UiPageId.Settings }); if (s.Session.DeveloperToolsAvailable) { b.ActionRow("SettingsDeveloper", V(0f, -114f), V(548f, 58f), "Developer Board", "Simulation and diagnostics", "DEV", UiAction.Navigate(UiPageId.DeveloperBoard), primary: false, warning: true); } else { b.InfoRow("SettingsDeveloperLocked", V(0f, -114f), V(548f, 58f), "Developer Board", "Restricted to dev builds and developer accounts", "LOCKED"); } b.FeedbackBar(-286f); } private static void BuildSettingsCategory(UiPageBuilder b, UiSnapshot s) { b.PageTitle("Settings"); b.Text("CategoryHint", V(0f, 222f), V(548f, 26f), 11f, UiAlignment.Left, "CATEGORY DETAIL IS STILL DRAWN BY THE LEGACY BUILDER IN GAME", UiTheme.TextDim); b.ActionRow("HandheldFlag", V(0f, 150f), V(548f, 58f), "Handheld Flag", s.HandheldFlag.Status, s.HandheldFlag.Button, s.HandheldFlag.Action); b.InfoRow("CategoryToggleSample", V(0f, 84f), V(548f, 58f), "Realistic recoil", "Per-weapon recoil curve", "ON"); b.InfoRow("CategoryStepperSample", V(0f, 18f), V(548f, 58f), "Recoil strength", "0.85", "STEP"); b.InfoRow("CategoryCycleSample", V(0f, -48f), V(548f, 58f), "Arms stamina", "Moderate", "CYCLE"); b.FeedbackBar(-286f); } private static void BuildNotifications(UiPageBuilder b, UiSnapshot s) { b.PageTitle("NOTIFICATIONS"); b.Text("InboxScope", V(0f, 222f), V(548f, 28f), 11f, UiAlignment.Left, "INVITES AND SYSTEM NOTICES", UiTheme.TextDim); float num = 154f; if (s.Session.HasPendingCloudJoin) { b.ActionRow("InboxInvite", V(0f, num), V(548f, 76f), UiFormat.Short(s.Session.PendingJoinServerName, 30), "Website lobby invitation", "INVITE", UiAction.None, primary: true); num -= 84f; b.Button("InboxAccept", V(-88f, num), V(156f, 42f), "ACCEPT", 12f, UiAction.Of(UiActionId.AcceptCloudInvite)); UiButton uiButton = b.Button("InboxDecline", V(88f, num), V(156f, 42f), "DECLINE", 12f, UiAction.Of(UiActionId.DeclineCloudInvite)); uiButton.Tint = UiTheme.HealthCrit; uiButton.TintAlpha = 0.2f; num -= 66f; } int num2 = (s.Session.HasPendingCloudJoin ? 3 : 5); int num3 = UiPageBuilder.ClampPage(s.View.NotificationPage, s.Notifications.Count, num2); List<UiNotificationSnapshot> list = s.Notifications.Skip(num3 * num2).Take(num2).ToList(); for (int i = 0; i < list.Count; i++) { UiNotificationSnapshot uiNotificationSnapshot = list[i]; UiColor value = ((uiNotificationSnapshot.Kind == "VEHICLE") ? UiTheme.Orange : ((uiNotificationSnapshot.Kind == "JOIN") ? UiTheme.HealthWarn : UiTheme.OutlineGray)); if (!uiNotificationSnapshot.Action.IsNone) { b.ActionRow("InboxNotice_" + i, V(0f, num), V(548f, 60f), uiNotificationSnapshot.Title, uiNotificationSnapshot.Detail, uiNotificationSnapshot.Kind, uiNotificationSnapshot.Action); } else { b.InfoRow("InboxNotice_" + i, V(0f, num), V(548f, 60f), uiNotificationSnapshot.Title, uiNotificationSnapshot.Detail, uiNotificationSnapshot.Kind, value); } num -= 64f; } b.Pager("Notifications", num3, s.Notifications.Count, num2, -224f, UiActionId.SetPageIndex); if (!s.Session.HasPendingCloudJoin && list.Count == 0) { b.Text("InboxEmpty", V(0f, 40f), V(520f, 60f), 16f, UiAlignment.Center, "NOTHING WAITING", UiTheme.TextDim); } b.FeedbackBar(-286f); } private static void BuildMedical(UiPageBuilder b, UiSnapshot s) { b.PageTitle("Medical"); b.ActionRow("PatientSelect", V(-150f, 224f), V(214f, 48f), "patient", s.Medical.PatientLabel, "", UiAction.Of(UiActionId.CyclePatient)); b.ActionRow("OpenQuickMenu", V(150f, 224f), V(214f, 48f), "quick menu", s.Medical.QuickMenuBinding, "OPEN", UiAction.Of(UiActionId.OpenMedicalQuickMenu)); BuildBodyDiagram(b, s); UiDerive.DescribeCondition(s.Medical, out var line, out var accent); b.ActionRow("RegionSelect", V(-150f, -96f), V(214f, 54f), "region", SplitPascal(s.Medical.SelectedRegion.ToString()), "", new UiAction { Id = UiActionId.SelectBodyRegion, Number = (int)(s.Medical.SelectedRegion + 1) % 7 }); b.InfoRow("MedicalStatus", V(140f, -96f), V(300f, 54f), UiFormat.Strip(line), UiFormat.Number(s.Medical.Overall01 * 100f, "0") + "% INTEGRITY", s.Medical.Bleeding ? "BLEED" : "", accent); b.Panel("TreatmentBox", V(0f, -228f), V(556f, 184f), UiTheme.Coffee, 0.38f); b.Text("TreatmentBoxLabel", V(0f, -150f), V(500f, 22f), 15f, UiAlignment.Center, "<b>treatments moved to the medical quick menu</b>", UiTheme.Orange); string text = string.Join(" | ", s.Medical.Supplies.Select((UiSupplySnapshot supply) => supply.Label + " <b>" + supply.Display + "</b>").ToArray()); b.Text("TreatmentBoxHint", V(0f, -198f), V(516f, 70f), 14f, UiAlignment.Center, (text.Length == 0) ? "NO SUPPLY DATA" : text, UiTheme.TextDim); b.Text("QuickMenuBinding", V(0f, -284f), V(460f, 48f), 14f, UiAlignment.Center, "open with <b>" + s.Medical.QuickMenuBinding + "</b>", UiTheme.TextDim); } private static void BuildBodyDiagram(UiPageBuilder b, UiSnapshot s) { b.Panel("BodyDiagramBg", V(0f, 66f), V(556f, 244f), UiTheme.PanelDark, 0.3f); UiBodyRegion[] array = new UiBodyRegion[7] { UiBodyRegion.Head, UiBodyRegion.Chest, UiBodyRegion.Abdomen, UiBodyRegion.RightArm, UiBodyRegion.LeftArm, UiBodyRegion.RightLeg, UiBodyRegion.LeftLeg }; for (int i = 0; i < array.Length; i++) { UiBodyRegion uiBodyRegion = array[i]; GetPartLayout(uiBodyRegion, out var partOffset, out var partSize, out var rot, out var tagPos); UiBodyRegionSnapshot uiBodyRegionSnapshot = s.Medical.Region(uiBodyRegion); float num = uiBodyRegionSnapshot?.Integrity01 ?? 1f; bool flag = uiBodyRegionSnapshot?.Bleeding ?? false; UiColor uiColor = ((uiBodyRegionSnapshot == null) ? UiTheme.HealthUnknown : (flag ? UiTheme.HealthBleed : UiDerive.HealthColor(num))); UiVector2 pos = BodyFigureCenter + partOffset; if (s.Medical.SelectedRegion == uiBodyRegion) { b.Rect("PartSel_" + uiBodyRegion, pos, partSize + new UiVector2(10f, 10f), UiTheme.Orange, 0.55f, rot); } b.Rect("Part_" + uiBodyRegion, pos, partSize, uiColor, 0.5f, rot); UiButton parent = b.Button("MedTag_" + uiBodyRegion, tagPos, V(132f, 30f), "", 12f, new UiAction { Id = UiActionId.SelectBodyRegion, Number = (int)uiBodyRegion }); b.Text("TagName_" + uiBodyRegion, V(6f, 7f), V(118f, 16f), 12f, UiAlignment.Left, ShortRegion(uiBodyRegion), UiTheme.TextWhite, parent); b.Text("TagVal_" + uiBodyRegion, V(-6f, 7f), V(118f, 16f), 12f, UiAlignment.Right, (uiBodyRegionSnapshot == null) ? "--" : UiFormat.Number(num * 100f, "0"), UiTheme.TextDim, parent); b.SharpRect("TagTrack_" + uiBodyRegion, V(0f, -8f), V(112f, 7f), new UiColor(0f, 0f, 0f), 0.4f, parent); b.Progress("TagFill_" + uiBodyRegion, V(0f, -8f), V(112f, 7f), num, uiColor, parent); } } private static void GetPartLayout(UiBodyRegion region, out UiVector2 partOffset, out UiVector2 partSize, out float rot, out UiVector2 tagPos) { switch (region) { case UiBodyRegion.Head: partOffset = V(0f, 94f); partSize = V(40f, 36f); rot = 0f; tagPos = V(0f, 180f); break; case UiBodyRegion.Chest: partOffset = V(0f, 50f); partSize = V(58f, 50f); rot = 0f; tagPos = V(-180f, 120f); break; case UiBodyRegion.Abdomen: partOffset = V(0f, 6f); partSize = V(48f, 42f); rot = 0f; tagPos = V(180f, 120f); break; case UiBodyRegion.RightArm: partOffset = V(-45f, 50f); partSize = V(17f, 68f); rot = -10f; tagPos = V(-186f, 58f); break; case UiBodyRegion.LeftArm: partOffset = V(45f, 50f); partSize = V(17f, 68f); rot = 10f; tagPos = V(186f, 58f); break; case UiBodyRegion.RightLeg: partOffset = V(-15f, -58f); partSize = V(19f, 82f); rot = 0f; tagPos = V(-162f, -6f); break; case UiBodyRegion.LeftLeg: partOffset = V(15f, -58f); partSize = V(19f, 82f); rot = 0f; tagPos = V(162f, -6f); break; default: partOffset = UiVector2.Zero; partSize = V(20f, 20f); rot = 0f; tagPos = UiVector2.Zero; break; } } private static string ShortRegion(UiBodyRegion region) { return region switch { UiBodyRegion.Head => "HEAD", UiBodyRegion.Chest => "CHEST", UiBodyRegion.Abdomen => "ABDOMEN", UiBodyRegion.LeftArm => "L ARM", UiBodyRegion.RightArm => "R ARM", UiBodyRegion.LeftLeg => "L LEG", UiBodyRegion.RightLeg => "R LEG", _ => "?", }; } private static void BuildMap(UiPageBuilder b, UiSnapshot s) { b.PageTitle("Map & Score"); UiVector2 uiVector = V(0f, 40f); b.Panel("MapBg", uiVector, V(500f, 500f), UiTheme.PanelDark, 0.92f); if (s.Map.BackgroundKind == "grid") { float num = 62.5f; for (int i = 1; i < 8; i++) { float num2 = -250f + (float)i * num; b.SharpRect("MapGridV_" + i, uiVector + V(num2, 0f), V(1f, 500f), UiTheme.OutlineGray, 0.35f); b.SharpRect("MapGridH_" + i, uiVector + V(0f, num2), V(500f, 1f), UiTheme.OutlineGray, 0.35f); } } b.Rect("MapFrame", uiVector, V(500f, 500f), UiTheme.OutlineGray, 0.9f); foreach (UiMapUnitSnapshot unit in s.Map.Units) { UiVector2 uiVector2 = uiVector + V(unit.Position.X * 500f, unit.Position.Y * 500f); UiColor uiColor = ((unit.Kind == UiMapMarkerKind.Explosion) ? UiTheme.HealthCrit : ((unit.Kind == UiMapMarkerKind.Objective) ? UiTheme.Orange : ((unit.Kind == UiMapMarkerKind.Spawn) ? UiTheme.TextDim : ((unit.Team != UiTeam.Unassigned) ? UiTheme.TeamColor(unit.Team) : UiTheme.TextDim)))); switch (unit.Kind) { case UiMapMarkerKind.Objective: b.Rect("MapObj_" + unit.Label, uiVector2, V(22f, 22f), uiColor, 0.9f, 45f); if (unit.Progress01 >= 0f) { b.Progress("MapObjProgress_" + unit.Label, uiVector2 + V(0f, -20f), V(40f, 5f), unit.Progress01, UiTheme.Orange); } break; case UiMapMarkerKind.Spawn: b.Rect("MapSpawn_" + unit.Label, uiVector2, V(16f, 16f), uiColor, 0.75f); break; case UiMapMarkerKind.Vehicle: b.Rect("MapVeh_" + unit.Label, uiVector2, V(20f, 12f), uiColor, 0.9f, unit.HeadingDegrees); break; case UiMapMarkerKind.Explosion: b.Rect("MapBoom_" + unit.Label, uiVector2, V(26f, 26f), uiColor, 0.55f, 45f); break; case UiMapMarkerKind.Wounded: b.SharpRect("MapWoundH_" + unit.Label, uiVector2, V(18f, 5f), UiTheme.HealthCrit, 1f); b.SharpRect("MapWoundV_" + unit.Label, uiVector2, V(5f, 18f), UiTheme.HealthCrit, 1f); break; default: if (s.Map.VisionConesEnabled && !unit.Wounded) { b.Rect("MapCone_" + unit.Label, uiVector2, V(10f, 34f), uiColor, 0.16f, unit.HeadingDegrees); } b.Rect("MapUnit_" + unit.Label, uiVector2, V(unit.IsLocal ? 16f : 12f, unit.IsLocal ? 16f : 12f), unit.IsLocal ? UiTheme.Orange : uiColor, 0.95f); break; } if (!string.IsNullOrEmpty(unit.Label) && unit.Kind != UiMapMarkerKind.Player) { b.Text("MapLabel_" + unit.Label, uiVector2 + V(0f, 18f), V(90f, 16f), 8.5f, UiAlignment.Center, unit.Label.ToUpperInvariant(), UiTheme.TextDim); } } b.Text("MapLevel", V(0f, 288f), V(548f, 24f), 10.5f, UiAlignment.Left, "LEVEL • " + UiFormat.Short(s.Map.LevelName, 40), UiTheme.TextDim); List<UiTeamSnapsho