ui/OptionsMenu.razor

Razor UI panel for the Options menu. It renders volume sliders, crosshair size slider, toggle switches for DontConfirmRestart and DisableChat, and buttons for system settings and closing; it updates GameSettingsSystem and calls Manager and Networking APIs to apply changes and control game state.

NetworkingFile Access
@namespace Sandbox
@using Sandbox;
@using Sandbox.UI;
@using System;
@inherits Panel
@attribute [StyleSheet("OptionsMenu.razor.scss")]

<root>
	<div class="content">
		@* <div class="title">Options</div> *@

		<div class="header">Volume</div>

		<div class="option_row slider_row">
			<div class="option_label">Overall</div>
			<SliderControl style="min-width: 250px;" Value:bind="@GameSettingsSystem.Current.MasterVolume" Min=@(0f) Max=@(100f) Step=@(1) OnValueChanged=@SetMasterVolume></SliderControl>
		</div>

		<div class="option_row slider_row">
			<div class="option_label">SFX</div>
			<SliderControl style="min-width: 250px;" Value:bind="@GameSettingsSystem.Current.SfxVolume" Min=@(0f) Max=@(100f) Step=@(1) OnValueChanged=@SetSfxVolume></SliderControl>
		</div>

		<div class="option_row slider_row">
			<div class="option_label">Music</div>
			<SliderControl style="min-width: 250px;" Value:bind="@GameSettingsSystem.Current.MusicVolume" Min=@(0f) Max=@(100f) Step=@(1) OnValueChanged=@SetMusicVolume></SliderControl>
		</div>

		<div class="option_row slider_row crosshair_row">
			<div class="option_label crosshair_label">Crosshair Size</div>
			<SliderControl style="min-width: 250px;" Value:bind="@GameSettingsSystem.Current.CrosshairScale" Min=@(0.5f) Max=@(3f) Step=@(0.05f) OnValueChanged=@SetCrosshairScale></SliderControl>
		</div>

		<div class="section_spacer"></div>

		<div class="option_row toggle_row">
			<div class="option_label toggle_label" style="min-width: 350px; text-shadow: 4px 4px 0px black;">Don't Confirm Restart</div>
			<SwitchControl Value:bind="@DontConfirmRestartValue"></SwitchControl>
		</div>

		<div class="option_row toggle_row second_toggle_row">
			<div class="option_label toggle_label" style="min-width: 350px; text-shadow: 4px 4px 0px black;">Disable Chat</div>
			<SwitchControl Value:bind="@DisableChatValue"></SwitchControl>
		</div>

		@* <div class="option_row" style="margin-top: -2px;">
			<div class="header" style="font-size: 28px; padding-top: 0; color: #ccc;">Lobby Privacy</div>
			<DropDown style="margin-left: 12px; margin-right: -40px;" Value:bind=@LobbyPrivacyValue Options=@LobbyPrivacyOptions></DropDown>

		</div> *@


		@* <div class="blank"></div> *@

		<button class="gear_button" onclick=@OpenSystemSettings>⚙</button>
	</div>
	<button class="close_button @(Input.UsingController ? "controller" : "") @(_closeActivated ? "ctrl-activated" : _closeHeld ? "ctrl-held" : "")" onclick=@(() => Manager.Instance.SetOptionsMenuOpen(false))>
	</button>
</root>

@code
{
	public void Restart()
	{
		if (!Networking.IsHost)
			return;

		Manager.Instance.PlaySfxUIRpc("restart", pitch: Game.Random.Float(1.15f, 1.2f), volume: 0.8f);

		Manager.Instance.Restart();

		Manager.Instance.SetEscMenuOpen(false);
	}

	public void ReturnToLobby()
	{
		if (!Networking.IsHost)
			return;

		Manager.Instance.SetGameState(GameState.Lobby);
		Manager.Instance.SetEscMenuOpen(false);
	}

	public void TogglePause()
	{
		if (!Networking.IsHost)
			return;

		if(Manager.Instance.IsPaused)
			Manager.Instance.SetEscMenuOpen(false);

		Manager.Instance.SetPaused(!Manager.Instance.IsPaused);
	}

	public void Options()
	{
		Manager.Instance.SetOptionsMenuOpen(true);
	}

	void OpenSystemSettings()
	{
		Game.Overlay.ShowSettingsModal();
	}

	void SetMasterVolume(float value)
	{
		Manager.Instance.RefreshMasterMixerVolume();
		GameSettingsSystem.Save();
	}

	void SetSfxVolume(float value)
	{
		Manager.Instance.RefreshSfxMixerVolume();
		GameSettingsSystem.Save();
	}

	void SetMusicVolume(float value)
	{
		Manager.Instance.RefreshMusicMixerVolume();
		GameSettingsSystem.Save();
	}

	void SetCrosshairScale(float value)
	{
		var clamped = Math.Clamp( value, 0.5f, 3f );
		GameSettingsSystem.Current.CrosshairScale = clamped;

		var crosshair = Manager.Instance?.Components.Get<CrosshairComponent>();
		if ( crosshair != null )
			crosshair.Scale = clamped;

		GameSettingsSystem.Save();
	}

	bool _closeHeld;
	bool _closeActivated;

	public override void Tick()
	{
		base.Tick();
		if (!Input.UsingController)
		{
			_closeHeld = false;
			_closeActivated = false;
			return;
		}

		if (_closeActivated)
		{
			_closeActivated = false;
			Manager.Instance.SetOptionsMenuOpen(false);
			return;
		}

		bool closeDown = Input.Down("R") || Input.Down("Back");
		if (_closeHeld && !closeDown)
		{
			_closeHeld = false;
			_closeActivated = true;
			Manager.Instance.PlaySfxUI("click", pitch: 1.15f, volume: 0.75f);
		}
		else
		{
			if (!_closeHeld && closeDown) Manager.Instance.PlaySfxUI("click", pitch: 0.85f, volume: 0.6f);
			_closeHeld = closeDown;
		}
	}

	protected override int BuildHash()
	{
		return System.HashCode.Combine(
			Manager.Instance.IsOptionsMenuOpen,
			Input.UsingController,
			_closeHeld,
			_closeActivated
		);
	}

	void SetDontConfirmRestart(bool value)
	{
		Manager.Instance.DontConfirmRestart = value;
		GameSettingsSystem.Current.DontConfirmRestart = value;
		GameSettingsSystem.Save();
	}

	void SetDisableChat(bool value)
	{
		GameSettingsSystem.Current.DisableChat = value;
		GameSettingsSystem.Save();
	}

	bool DontConfirmRestartValue
	{
		get => GameSettingsSystem.Current.DontConfirmRestart;
		set => SetDontConfirmRestart( value );
	}

	bool DisableChatValue
	{
		get => GameSettingsSystem.Current.DisableChat;
		set => SetDisableChat( value );
	}

	List<Option> LobbyPrivacyOptions = new List<Option>()
	{
		new Option("Public", 0),
		new Option("Friends", 2),
		new Option("Private", 1),
	};

	int LobbyPrivacyValue
	{
		get => Manager.Instance.LobbyPrivacy;
		set
		{
			Manager.Instance.LobbyPrivacy = value;
			GameSettingsSystem.Save();

			if ( Networking.IsActive )
			{
				Networking.Disconnect();
				Manager.Instance.CreateLobby();
			}
		}
	}
}