UI/MapRatingModal.razor

A Razor UI panel that displays a modal for rating a map with 1-5 stars. It shows the map title, handles mouse hover and controller navigation, submits ratings to GameStats, and manages a pending rating identifier from the lobby flow.

File Access
@using Sandbox;
@using Sandbox.UI;
@using Machines.Components;
@using Machines.Resources;
@using Machines.Systems;

@namespace Machines.UI
@inherits Panel

@{
	var map = ResolveMap();
	var title = map?.Title ?? "this map";
}

@if ( _ident != null )
{
	<root class="rating-modal @(Input.UsingController ? "controller" : "mouse")">
		<div class="backdrop"></div>
		<div class="card">
			<div class="eyebrow">RATE THE MAP</div>
			<div class="question">Did you enjoy @title?</div>

			<div class="stars" onmouseout=@(() => _hover = 0)>
				@for ( int i = 1; i <= 5; i++ )
				{
					var star = i;
					var filled = star <= (_hover > 0 ? _hover : _cursor);
					<div class="star @(filled ? "on" : "")" onmouseover=@(() => _hover = star) onclick=@(() => Submit( star ))>
						<i>@(filled ? "star" : "star_outline")</i>
					</div>
				}
			</div>

			<div class="dismiss" onclick=@Dismiss>Maybe later</div>
		</div>
	</root>
}
else
{
    <root class="rating-modal">
    </root>
}

@code
{
	// Dev: open the rating modal for the map currently selected in the menu.
	[ConCmd( "rate_map" )]
	public static void OpenForSelectedMap()
	{
		var flow = LobbyFlow.Current;
		var ident = flow?.LocalBrowsedMapIdent ?? flow?.SelectedMapIdent;

		if ( string.IsNullOrEmpty( ident ) )
		{
			Log.Warning( "rate_map: no map selected." );
			return;
		}

		GameStats.PendingRatingMapIdent = ident;
	}

	// Map currently being rated; null when the modal is closed.
	private string _ident;

	// Hovered star (mouse) and controller cursor position, 1-5.
	private int _hover;
	private int _cursor = 3;

	private MapResource ResolveMap()
	{
		if ( string.IsNullOrEmpty( _ident ) )
			return null;

		return ResourceLibrary.Get<MapResource>( _ident );
	}

	public override void Tick()
	{
		base.Tick();

		// Adopt a pending rating once we're back in the lobby.
		if ( _ident == null && !string.IsNullOrEmpty( GameStats.PendingRatingMapIdent ) )
		{
			_ident = GameStats.PendingRatingMapIdent;
			_hover = 0;
			_cursor = 3;
		}

		if ( _ident == null || !Input.UsingController )
			return;

		// Controller navigation; clear actions so they don't bleed into the lobby.
		if ( Input.Pressed( "MenuLeft" ) ) { Input.Clear( "MenuLeft" ); _cursor = System.Math.Max( 1, _cursor - 1 ); }
		if ( Input.Pressed( "MenuRight" ) ) { Input.Clear( "MenuRight" ); _cursor = System.Math.Min( 5, _cursor + 1 ); }
		if ( Input.Pressed( "MenuSelect" ) ) { Input.Clear( "MenuSelect" ); Submit( _cursor ); }
		if ( Input.Pressed( "MenuBack" ) ) { Input.Clear( "MenuBack" ); Dismiss(); }
	}

	private void Submit( int stars )
	{
		GameStats.SetMapRating( _ident, stars );
		Sound.Play( "button_accept" );
		Close();
	}

	private void Dismiss() => Close();

	private void Close()
	{
		_ident = null;
		_hover = 0;
		GameStats.PendingRatingMapIdent = null;
	}

	protected override int BuildHash() => System.HashCode.Combine( _ident, _hover, _cursor, Input.UsingController );
}