UI/MainMenuHost.razor
@using System;
@using System.Linq;
@using Sandbox;
@using Sandbox.UI;
@using Sandbox.UI.Navigation;
@inherits PanelComponent
<root class="main-menu-host @(IsMenuVisible ? "visible" : "hidden")">
@if ( IsMenuVisible )
{
<div class="menu-screen">
<img class="menu-background" />
<div class="menu-overlay"></div>
<div class="menu-shell">
<div class="menu-shell-chrome"></div>
@if ( !string.IsNullOrWhiteSpace( OMRSceneFlow.SessionNotice ) )
{
<div class="session-notice">@OMRSceneFlow.SessionNotice</div>
}
<div class="menu-brand">
@if ( ActiveTheme?.HasLogo == true )
{
<img class="menu-logo" />
}
@if ( ActiveMenuLayout?.ShowTitle != false )
{
<label class="menu-title">@MenuTitle</label>
}
@if ( ActiveMenuLayout?.ShowSubtitle != false )
{
<label class="menu-subtitle">@MenuSubtitle</label>
}
</div>
<NavigationHost class="menu-navigation" DefaultUrl="/" @ref=Navigator>
<ChildContent>
<div
class="navigator-canvas"
slot="navigator-canvas"
></div>
</ChildContent>
</NavigationHost>
</div>
</div>
}
</root>
@code
{
public static MainMenuHost Current { get; private set; }
[Property]
public RoundManager RoundManager { get; set; }
[Property, Group( "UI Foundation" ), Title( "Theme" )]
public OMRUITheme Theme { get; set; }
[Property, Group( "UI Foundation" ), Title( "Menu Layout" )]
public OMRMainMenuLayout MenuLayout { get; set; }
[Property, Group( "Scene Flow" ), Title( "Standalone Startup Menu" )]
public bool IsStandaloneMenuScene { get; set; }
[Property, Group( "Scene Flow" ), ShowIf( nameof( IsStandaloneMenuScene ), true ), ResourceType( "scene" )]
public string GameplayScenePath { get; set; } = "scenes/main.scene";
[Property, Group( "Developer Preview" ), Title( "Force Menu Visible" )]
public bool EnableDeveloperPreview { get; set; }
[Property, Group( "Developer Preview" ), ShowIf( nameof( EnableDeveloperPreview ), true ), Title( "Preview Page" )]
public OMRMenuPreviewPage DeveloperPreviewPage { get; set; } =
OMRMenuPreviewPage.Home;
public OMRUITheme ActiveTheme =>
Theme ?? OMRUIResources.DefaultTheme;
public OMRMainMenuLayout ActiveMenuLayout =>
MenuLayout ?? OMRUIResources.DefaultMenuLayout;
private string MenuTitle =>
ActiveTheme?.GameTitle ??
"ONE MORE ROUND";
private string MenuSubtitle =>
ActiveTheme?.Subtitle ??
"Dev Build Alpha 1.6 · BOT 9";
public NavigationHost Navigator { get; private set; }
public GameFlowManager FlowManager =>
RoundManager?.
GameObject?.
Components
.Get<GameFlowManager>();
public bool IsMenuVisible =>
EnableDeveloperPreview ||
IsStandaloneMenuScene ||
(
RoundManager is not null &&
RoundManager.Mode ==
SessionMode.Menu &&
(
LocalPlayerState is null ||
LocalPlayerState.PreMatchState !=
PreMatchPlayerState.Warmup
)
);
private SessionMode _lastMode =
SessionMode.Menu;
private GameObject _lastSettingsPlayer;
private string _nextMenuRoute =
"/";
private OMRGameplayEntry _pendingGameplayEntry =
OMRGameplayEntry.None;
private bool _sceneChangeRequested;
protected override void OnEnabled()
{
base.OnEnabled();
Current =
this;
}
protected override void OnDisabled()
{
if (
Current ==
this
)
{
Current =
null;
}
base.OnDisabled();
}
protected override void OnStart()
{
base.OnStart();
RoundManager ??=
Components.Get<RoundManager>();
if ( RoundManager is not null )
{
_lastMode =
RoundManager.Mode;
if ( !IsStandaloneMenuScene )
{
_pendingGameplayEntry =
OMRSceneFlow.ConsumeGameplayEntry();
}
}
}
protected override void OnUpdate()
{
base.OnUpdate();
RoundManager ??=
Components.Get<RoundManager>();
if ( IsMenuVisible )
{
OMRUIRuntimeStyle.ApplyMenu(
Panel,
ActiveTheme,
ActiveMenuLayout
);
Mouse.Visibility =
MouseVisibility.Visible;
Input.EnableVirtualCursor =
true;
}
ApplyDeveloperPreviewRoute();
ApplyPendingGameplayEntry();
ApplySettingsToNewLocalPlayer();
if ( RoundManager is null )
return;
if (
RoundManager.Mode !=
_lastMode
)
{
if (
RoundManager.Mode ==
SessionMode.Menu
)
{
string route =
string.IsNullOrWhiteSpace(
_nextMenuRoute
)
? "/"
: _nextMenuRoute;
_nextMenuRoute =
"/";
Navigate(
route
);
}
_lastMode =
RoundManager.Mode;
}
if ( !IsMenuVisible )
return;
if (
IsOnlineFlowVisible &&
Navigator is not null &&
Navigator.CurrentUrl !=
"/online"
)
{
Navigate(
"/online"
);
}
}
public void Navigate(
string url
)
{
Navigator?.
Navigate(
url,
true
);
}
public void GoBack()
{
if (
Navigator is null ||
!Navigator.GoBack()
)
{
Navigate(
"/"
);
}
}
public void FindMatch()
{
if ( FlowManager?.IsLeavingSession == true ) return;
if ( IsStandaloneMenuScene )
{
LoadGameplayScene(
OMRGameplayEntry.FindMatch
);
return;
}
if ( FlowManager is null )
return;
Navigate(
"/online"
);
FlowManager.FindMatch();
}
public void CancelMatchmaking()
{
if ( FlowManager is null )
return;
FlowManager.CancelMatchmaking();
Navigate(
"/play"
);
}
public void StartBotMatch()
{
if ( FlowManager?.IsLeavingSession == true ) return;
if ( IsStandaloneMenuScene )
{
LoadGameplayScene( OMRGameplayEntry.BotMatch );
return;
}
RoundManager?.SetMatchBotDifficulty( OMRSceneFlow.BotMatchDifficulty );
if ( FlowManager is not null )
FlowManager.StartBotMatchSession();
else if ( RoundManager is not null )
{
RoundManager.SetMatchBotEnabled( true );
RoundManager.StartMatchMode();
}
}
public void StartSandbox()
{
if ( FlowManager?.IsLeavingSession == true ) return;
if ( IsStandaloneMenuScene )
{
LoadGameplayScene(
OMRGameplayEntry.SandboxSetup
);
return;
}
if ( RoundManager is null )
return;
if ( FlowManager is not null )
{
FlowManager.StartSandboxSession();
}
else
{
RoundManager.StartSandboxMode();
}
}
public void OpenSandboxSetup()
{
if ( IsStandaloneMenuScene )
{
LoadGameplayScene(
OMRGameplayEntry.SandboxSetup
);
return;
}
Navigate( "/practice/sandbox" );
}
private void LoadGameplayScene(
OMRGameplayEntry entry
)
{
if (
_sceneChangeRequested ||
string.IsNullOrWhiteSpace( GameplayScenePath ) ||
Scene is null
)
{
Log.Warning(
"UI | Gameplay scene path is not configured"
);
return;
}
if ( Networking.IsActive )
{
Log.Warning(
"UI | Refusing a local startup-menu scene change while networking is active"
);
return;
}
_sceneChangeRequested = true;
OMRSceneFlow.RequestGameplayEntry( entry );
if ( !Scene.LoadFromFile( GameplayScenePath ) )
{
_sceneChangeRequested = false;
OMRSceneFlow.RequestGameplayEntry( OMRGameplayEntry.None );
Log.Error(
$"UI | Failed to load gameplay scene '{GameplayScenePath}'"
);
}
}
private void ApplyPendingGameplayEntry()
{
if (
_pendingGameplayEntry == OMRGameplayEntry.None ||
Navigator is null ||
RoundManager is null
)
return;
OMRGameplayEntry entry =
_pendingGameplayEntry;
_pendingGameplayEntry =
OMRGameplayEntry.None;
switch ( entry )
{
case OMRGameplayEntry.FindMatch:
Navigate( "/online" );
FlowManager?.FindMatch();
break;
case OMRGameplayEntry.BotMatch:
RoundManager?.SetMatchBotDifficulty( OMRSceneFlow.BotMatchDifficulty );
FlowManager?.StartBotMatchSession();
break;
case OMRGameplayEntry.SandboxSetup:
Navigate( "/practice/sandbox" );
break;
}
}
private void ApplyDeveloperPreviewRoute()
{
if (
!EnableDeveloperPreview ||
Navigator is null
)
return;
string route = DeveloperPreviewPage switch
{
OMRMenuPreviewPage.Play => "/play",
OMRMenuPreviewPage.Practice => "/practice",
OMRMenuPreviewPage.Settings => "/settings",
_ => "/"
};
if ( Navigator.CurrentUrl != route )
Navigate( route );
}
/// <summary>
/// Opens Sandbox Setup while preserving the current network
/// session and existing player objects.
/// </summary>
public void ReturnToSandboxSetup()
{
if (
RoundManager is null ||
!Networking.IsHost
)
return;
_nextMenuRoute =
"/practice/sandbox";
if ( FlowManager is not null )
{
FlowManager.ReturnToSandboxSetup();
}
else
{
RoundManager.ReturnToMenu();
}
}
/// <summary>
/// Fully leaves the current network session and returns to the
/// root menu.
/// </summary>
public void ReturnToMainMenu()
{
_nextMenuRoute =
"/";
if ( FlowManager is not null )
{
FlowManager.ReturnToMainMenu();
}
else
{
RoundManager?.ReturnToMenu();
}
}
public void ApplySettingsNow()
{
OMRPlayerSettings.ApplyToLocalPlayer();
}
private void ApplySettingsToNewLocalPlayer()
{
if (
RoundManager is null ||
RoundManager.Scene is null
)
return;
PlayerState localPlayer =
RoundManager.Scene
.GetAllComponents<PlayerState>()
.FirstOrDefault(
x =>
!x.IsProxy &&
!x.IsBot
);
GameObject playerObject =
localPlayer?.
GameObject;
if (
playerObject is null ||
playerObject ==
_lastSettingsPlayer
)
return;
_lastSettingsPlayer =
playerObject;
OMRPlayerSettings.ApplyToPlayer(
playerObject
);
}
public PlayerState LocalPlayerState
{
get
{
if (
RoundManager is null ||
RoundManager.Scene is null
)
return null;
return RoundManager.Scene
.GetAllComponents<PlayerState>()
.FirstOrDefault(
x =>
!x.IsProxy &&
!x.IsBot
);
}
}
public PlayerState OpponentPlayerState
{
get
{
if (
RoundManager is null ||
RoundManager.Scene is null
)
return null;
PlayerState local =
LocalPlayerState;
return RoundManager.Scene
.GetAllComponents<PlayerState>()
.FirstOrDefault(
x =>
x.IsReady &&
x != local
);
}
}
public void TogglePreMatchReady()
{
RoundManager?.
RequestPreMatchReady();
}
public void TogglePreMatchWarmup()
{
RoundManager?.
RequestPreMatchWarmup();
}
public void CyclePreMatchWeapon(
int direction
)
{
RoundManager?.
RequestCyclePreMatchWeapon(
direction
);
}
public string GetPreMatchWeaponName(
PlayerState player
)
{
return RoundManager?.
GetPreMatchWeaponName(
player
) ??
"NO WEAPON";
}
public int ConnectedPlayerCount
{
get
{
if (
RoundManager is null ||
RoundManager.Scene is null
)
return 0;
return RoundManager.Scene
.GetAllComponents<PlayerState>()
.Count(
x =>
x.IsReady
);
}
}
public bool IsOnlineFlowVisible
{
get
{
if ( FlowManager is null )
return false;
return
FlowManager.OnlineState !=
OnlineFlowState.Idle &&
FlowManager.OnlineState !=
OnlineFlowState.InMatch;
}
}
public int MatchmakingSeconds
{
get
{
if ( FlowManager is null )
return 0;
return (int)MathF.Ceiling(
MathF.Max(
0f,
FlowManager.MatchStartsAt -
Time.Now
)
);
}
}
public string LocalConnectionName
{
get
{
string name =
Connection.Local?.
DisplayName;
return string.IsNullOrWhiteSpace(
name
)
? "YOU"
: name;
}
}
public string OpponentConnectionName
{
get
{
if ( !Networking.IsActive )
return "OPPONENT";
Connection opponent;
if ( Networking.IsHost )
{
opponent =
Connection.All
.FirstOrDefault(
x =>
x.IsActive &&
!x.IsHost
);
}
else
{
opponent =
Connection.Host;
}
return string.IsNullOrWhiteSpace(
opponent?.
DisplayName
)
? "OPPONENT"
: opponent.DisplayName;
}
}
protected override int BuildHash()
{
HashCode hash =
new();
hash.Add( OMRSceneFlow.SessionNotice );
hash.Add(
RoundManager?.
Mode
);
hash.Add(
Navigator?.
CurrentUrl
);
hash.Add( EnableDeveloperPreview );
hash.Add( DeveloperPreviewPage );
hash.Add( IsStandaloneMenuScene );
hash.Add( ActiveTheme?.ContentHash() ?? 0 );
hash.Add( ActiveMenuLayout?.ContentHash() ?? 0 );
hash.Add(
ConnectedPlayerCount
);
hash.Add(
LocalConnectionName
);
hash.Add(
OpponentConnectionName
);
hash.Add(
LocalPlayerState?.
PreMatchState
);
hash.Add(
LocalPlayerState?.
WarmupWeaponIndex
);
hash.Add(
OpponentPlayerState?.
PreMatchState
);
hash.Add(
OpponentPlayerState?.
WarmupWeaponIndex
);
if ( FlowManager is not null )
{
hash.Add(
FlowManager.OnlineState
);
hash.Add(
FlowManager.ErrorMessage
);
hash.Add(
MatchmakingSeconds
);
}
return hash.ToHashCode();
}
}