Game/PhotoMode.cs
namespace Monolith;

/// <summary>
/// Capture tools for the store page. P hides the HUD, O takes a screenshot, and F6 video
/// recording hides the HUD by itself.
///
/// **Why this exists.** The store screenshot page asks you to avoid text and logos and let the
/// gameplay speak for itself, and this game draws a HUD every frame: a progress bar, a wallet, a
/// charge readout, a crosshair. Every screenshot taken normally has all of it in shot. There was
/// no way to get a clean frame without deleting components, so the useful screenshots were
/// effectively impossible to take.
///
/// **The HUD is HIDDEN, not disabled.** Turning the component off would stop its `OnUpdate`,
/// which is where the pause is decided, where the cursor is managed and where the charge marker
/// is projected. Half the game would quietly change behaviour in exactly the frames being
/// photographed. Setting a class that makes it invisible leaves every one of those systems
/// running, so what you capture is the real game with the overlay taken off the glass.
///
/// **Deliberately not automated.** An earlier idea was a camera tour that flew to a few angles
/// and captured on a timer. It would have produced pictures of an empty arena: what makes a
/// screenshot worth looking at here is a Spotter mid-lock, a floor cycle caught on the amber
/// frame, shrapnel rings on the ground with the chunks still in the air. A machine does not know
/// when that is happening and a player does. This puts the shutter under your thumb instead.
/// </summary>
public sealed class PhotoMode : Component
{
	/// <summary>
	/// Native resolution capture. Passing the real window size means the shot matches what you
	/// framed rather than being re-rendered at some other aspect and cropped by the store.
	/// </summary>
	private static readonly int Width = 1920;
	private static readonly int Height = 1080;

	// REAL clock. Photo mode deliberately works while the world is frozen, and a GameTimeSince
	// stops advancing there, so the shutter cooldown would never expire: you would get exactly
	// one screenshot per pause and no indication why the second did nothing. Same trap as the
	// state diagnostic in GameBootstrap.
	private TimeSince timeSinceShot = 99f;

	protected override void OnStart()
	{
		Log.Info( "[photo] ready. P hides the HUD, O captures 1920x1080, "
			+ "F6 records video and hides the HUD while it runs." );
	}

	protected override void OnUpdate()
	{
		// NOT gated on GameTime.Paused. Capturing with the world frozen is often the ONLY way to
		// get a clean frame of something fast, and a pause menu open behind a hidden HUD is
		// exactly the moment you want the shutter to work.

		WatchRecording();

		if ( Input.Pressed( "PhotoHud" ) )
		{
			Hud.PhotoHidden = !Hud.PhotoHidden;

			Log.Info( Hud.PhotoHidden
				? "[photo] HUD hidden. O to capture, P to bring it back."
				: "[photo] HUD restored." );
		}

		if ( !Input.Pressed( "PhotoShot" ) )
			return;

		// A held key would otherwise fill the folder in a second.
		if ( timeSinceShot < 0.4f )
			return;

		timeSinceShot = 0f;
		Capture();
	}

	private bool wasRecording;
	private bool hidForRecording;

	/// <summary>
	/// Hides the HUD for the duration of an F6 video recording, and puts it back afterwards.
	/// </summary>
	/// <remarks>
	/// **Recording cannot be started from code, so this hooks the state instead.** The engine
	/// exposes `Game.IsRecordingVideo` as a read-only property and documents F6 and a `video`
	/// console command as the ways in, but there is no published API to trigger either.
	/// `Sandbox.ConsoleSystem` exists as a type with no documented members, and guessing at a
	/// method signature there would be a compile error rather than something catchable.
	///
	/// Watching the flag gets the useful half anyway. The store wants gameplay without text or
	/// logos, and remembering to hide the HUD before every take is exactly the sort of thing you
	/// forget until you have recorded the good one with a progress bar across it.
	///
	/// Only restores the HUD if RECORDING hid it. Someone who pressed P first and then started
	/// recording wants it to stay hidden when they stop.
	/// </remarks>
	private void WatchRecording()
	{
		bool recording;

		try { recording = Game.IsRecordingVideo; }
		catch ( Exception ) { return; }

		if ( recording == wasRecording )
			return;

		wasRecording = recording;

		if ( recording )
		{
			hidForRecording = !Hud.PhotoHidden;

			if ( hidForRecording )
				Hud.PhotoHidden = true;

			Log.Info( "[photo] recording started, HUD hidden." );
			return;
		}

		if ( hidForRecording )
		{
			Hud.PhotoHidden = false;
			hidForRecording = false;
		}

		Log.Info( "[photo] recording stopped." );
	}

	private static void Capture()
	{
		try
		{
			Game.TakeHighResScreenshot( Width, Height );

			Log.Info( $"[photo] captured {Width}x{Height} to "
				+ "D:/SteamLibrary/steamapps/common/sbox/screenshots" );
		}
		catch ( Exception e )
		{
			// Falls back to the plain capture, which goes to the Steam screenshot library. Worth
			// having: a failed screenshot at the moment you finally caught a good frame is a
			// genuinely annoying way to lose one.
			Log.Warning( $"[photo] high-res capture failed ({e.Message}), trying plain." );

			try { Game.TakeScreenshot(); }
			catch ( Exception inner ) { Log.Warning( $"[photo] capture failed: {inner.Message}" ); }
		}
	}
}