Map/FinalDoor.cs

Scene component representing the final exit door in the map. Displays a hover prompt based on whether the local player has bought an "Exit Key", checks that all alive players are within range to allow use, awards escape stats/achievements to alive players, credits the level escape and restarts the game when used. Also includes a Create helper that spawns the door GameObject with model, collider and tags.

Networking
using System.Linq;
using Sandbox;

namespace BrickJam;

/// <summary>
/// Final level exit (end of a run). Scene-System port of legacy <c>FinalDoor</c>. Spawned at runtime
/// by the Bathrooms level at a <see cref="ValidFinalDoorPosition"/>.
/// </summary>
[Title( "Final Door" )]
[Category( "Map" )]
public sealed partial class FinalDoor : LegacyUsableComponent
{
	public override float InteractionDuration { get; set; } = 1.7f;

	public override string UseString
	{
		get => HasKey
			? (CanUse ? "exit the mansion" : "ALL PLAYERS NEED TO BE NEARBY TO PROCEED")
			: "YOU NEED TO BUY THE KEY TO PROCEED";
		set { }
	}

	public override bool CanUse => Scene.GetAllComponents<Player>()
		.Where( x => x.IsAlive )
		.All( x => x.WorldPosition.Distance( WorldPosition ) <= 400f );

	// Hover prompt shown on the LOCAL client - check the local player, not the host-side User (which is null
	// while merely hovering, so it always said "buy the key" even when owned). Use gate = CheckUpgrades.
	public bool HasKey => Player.Local?.HasUpgrade( "Exit Key" ) ?? false;

	public static FinalDoor Create( Vector3 position, Rotation rotation )
	{
		var go = new GameObject( true, "FinalDoor" );
		go.WorldPosition = position;
		go.WorldRotation = rotation;
		go.Tags.Add( "door", "usable" );

		var model = Model.Load( "models/furniture/exit_door.vmdl" );
		go.Components.Create<ModelRenderer>().Model = model;
		go.Components.Create<ModelCollider>().Model = model;

		var door = go.Components.Create<FinalDoor>();
		go.NetworkSpawn();
		return door;
	}

	public override void Use( Player user )
	{
		// Escaped! Credit every survivor (Services stat + achievement), then end the run.
		foreach ( var p in Scene.GetAllComponents<Player>().Where( x => x.IsAlive ) )
		{
			p.TrackStat( GameStats.Escapes, 1 );
			p.TrackAchievement( GameStats.AchEscape );

			// Hoarder's Paradise: extracted with a completely full inventory.
			if ( p.Inventory is { } inv && inv.Count >= inv.Limit )
				p.TrackAchievement( GameStats.AchHoardersParadise );
		}

		// Grand Tour (Bathrooms leg) + Left For Dead, same crediting as a trapdoor escape.
		MansionGame.CreditLevelEscape( LevelType.Bathrooms );

		MansionGame.RestartGame(); // TODO: Give bonus / show ending
	}

	public override bool CheckUpgrades( Player player ) => player.HasUpgrade( "Exit Key" );
}