Map/Trapdoor.cs

A scene component representing a level exit trapdoor. It defines interaction text, usage conditions (players nearby and owning a key), plays ambient and open sounds, can be spawned at runtime with model and collider, and advances the game to the next level when used.

NetworkingFile Access
using System.Linq;
using Sandbox;

namespace BrickJam;

/// <summary>
/// Level exit that advances to the next level. Scene-System port of legacy <c>Trapdoor</c>.
/// Spawned at runtime by the Level subsystem at a <see cref="ValidTrapdoorPosition"/>.
/// </summary>
[Title( "Trapdoor" )]
[Category( "Map" )]
public sealed partial class Trapdoor : LegacyUsableComponent
{
	public override float InteractionDuration { get; set; } = 2f;

	public override string UseString
	{
		get => HasKey
			? (CanUse ? "proceed to the next level" : "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 );

	public string KeyName => MansionGame.Instance?.CurrentLevelType == LevelType.Mansion ? "Mansion Key" : "Dungeon Key";

	// UseString is the hover prompt shown on the LOCAL client, so check the local player's upgrades. The
	// legacy bug here checked User (the host-side interacting player), which is null while merely hovering -
	// so it always said "buy the key" even when you owned it. The actual use gate is CheckUpgrades(player).
	public bool HasKey => Player.Local?.HasUpgrade( KeyName ) ?? false;

	private SoundHandle windSound;

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

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

		var trapdoor = go.Components.Create<Trapdoor>();
		go.NetworkSpawn();
		return trapdoor;
	}

	protected override void OnStart()
	{
		windSound = Sound.Play( "sounds/doors/blizzard.sound", WorldPosition );
	}

	protected override void OnDestroy()
	{
		windSound?.Stop();
	}

	public override void Use( Player user )
	{
		if ( !CanUse )
			return;

		MansionGame.NextLevel();
		SoundExtensions.BroadcastPlay( "sounds/doors/dooropen.sound", WorldPosition );
	}

	public override bool CheckUpgrades( Player player ) => player.HasUpgrade( KeyName );
}