Map/LegacyMapEntities.cs

Map entity component definitions for legacy map objects. Defines DoorState enum and several components used by map-placed entities (usable components, player spawn, loot spawners, door behaviour and simple markers). It sets up default properties, sync fields, interaction behaviour, and initializes a LockedComponent when StartLocked is used and a model has a lock attachment.

Networking
using Sandbox;

namespace BrickJam;

public enum DoorState : sbyte
{
	Opening = 1,
	Closing = -1,
	Open = 2,
	Closed = 3,
}

public abstract partial class LegacyMapComponent : Component
{
	public Vector3 Position => GameObject.WorldPosition;
	public Rotation Rotation => GameObject.WorldRotation;
}

public abstract partial class LegacyUsableComponent : LegacyMapComponent
{
	[Property] public virtual float InteractionDuration { get; set; } = 1f;
	[Property] public virtual string UseString { get; set; } = "use";

	/// <summary>Can this be interacted with right now?</summary>
	public virtual bool CanUse => true;

	/// <summary>Whether the interaction hint should sit at the world centre of the object.</summary>
	public virtual bool ShouldCenterInteractionHint => true;

	/// <summary>Prompt shown while the object is locked.</summary>
	public virtual string LockText => "lockpick";

	/// <summary>Whether the object starts locked when spawned.</summary>
	public virtual bool StartLocked => false;

	/// <summary>Replicated locked state (lock object/lockpicking deferred to the interactions system).</summary>
	[Sync] public bool Locked { get; set; }

	/// <summary>The player currently interacting with this object. Host-side bookkeeping for now.</summary>
	public Player User { get; set; }

	/// <summary>Upgrade gate hook; defaults to allowing use. The upgrades system refines this later.</summary>
	public virtual bool CheckUpgrades( Player user ) => true;

	/// <summary>Invoked when an interaction completes. Overridden by concrete usables.</summary>
	public virtual void Use( Player user ) { }

	protected override void OnAwake()
	{
		// Every usable must carry the "usable" tag so the player's interaction trace
		// (Scene.Trace...WithTag("usable")) can find it - including map-placed doors/shops.
		GameObject.Tags.Add( "usable" );
	}

	protected override void OnStart()
	{
		if ( !Networking.IsHost || !StartLocked )
			return;

		// Legacy parity (UsableEntity.OnNewModel): a StartLocked usable spawns locked, and gets a
		// physical padlock when its model exposes a "lock" attachment. Without this, ShopDoor.StartLocked
		// was dead - the mansion door spawned openable and the lockpick-to-proceed loop never triggered.
		Locked = true;

		var attachment = Components.Get<ModelRenderer>()?.Model?.Attachments.Get( "lock" );
		if ( attachment is not null )
			Components.GetOrCreate<LockedComponent>().Initialize( attachment.LocalTransform );
	}
}

public sealed partial class PlayerSpawn : LegacyMapComponent
{
	[Property] public LevelType LevelType { get; set; } = LevelType.None;
}

public sealed partial class ValidTrapdoorPosition : LegacyMapComponent
{
	[Property] public LevelType LevelType { get; set; } = LevelType.None;
}

public sealed partial class ValidFinalDoorPosition : LegacyMapComponent
{
}

public sealed partial class LootSpawner : LegacyMapComponent
{
	[Property] public LootPrefab LootToSpawn { get; set; }
	[Property] public bool IsContainer { get; set; }
	[Property] public float ChanceToSpawn { get; set; } = 0.5f;
	[Property] public LevelType LevelType { get; set; } = LevelType.None;
}

public sealed partial class PissingGuySpawner : LegacyMapComponent
{
	[Property] public float ChanceToSpawn { get; set; } = 0.35f;
}

public sealed partial class Door : LegacyUsableComponent
{
	[Property, Sync] public DoorState State { get; set; } = DoorState.Closed;
	[Property] public LevelType LevelType { get; set; } = LevelType.None;
	[Property] public bool AlternativeModel { get; set; }

	[Property] public override float InteractionDuration { get; set; } = 0.3f;

	public override string UseString
	{
		get => (State == DoorState.Open || State == DoorState.Opening) ? "close the door" : "open the door";
		set { }
	}
}

public sealed partial class SlipperySign : LegacyMapComponent
{
}