Doors

ACrimsonCoreDoor is a replicated, world-saved door that puts the whole interaction stack to work: the prompt, the context menu, interaction conditions, a lockpick channel and the save system. It swings or slides, has one or two leaves, and can optionally take a key (consumed on unlock if you choose), be lockpicked, carry a bolt that works from one side only, be knocked on, and close itself after a delay. One rule set drives the prompt, the menu and the server, so what a player is offered is always what the server will accept.

CrimsonCore - requires all Crimson plugins
CrimsonCore is free to download, but compiles only with all Crimson plugins installed. The door reads keys and lockpicks from CrimsonInventory, gates on CrimsonInteraction conditions, and saves through CrimsonSaveSystem. To build your own door on the standalone plugins, implement ICrimsonInteractableTarget (CrimsonInteraction) and ICrimsonContextActionProvider (CrimsonUI) the same way (see Interaction Integration and Context Menu).

What's included

Type or assetPurpose
ACrimsonCoreDoor (shows as Crimson Door)The door actor. Derives from ACrimsonPersistentActor and implements ICrimsonInteractableTarget (prompt) and ICrimsonContextActionProvider (menu). Lays out its frame and leaves from the meshes you assign.
FCrimsonDoorStateThe whole gameplay state: bOpen, bLocked, bBolted, bSwingTowardBack. Replicated and SaveGame; editable per placed instance so a door can start open, locked or bolted.
ECrimsonDoorActionOpen, Close, Unlock, Lock, Lockpick, Bolt, Unbolt, Knock (plus None).
UCrimsonCoreContextAction_DoorOne context menu entry per verb, gathered fresh in a fixed order; every rule defers to the door. Lock / Unlock / Bolt / Unbolt are hold-to-confirm rows.
UCrimsonCoreGameplayAbility_DoorInteractThe one handler ability every door option grants. Reads the verb from the trigger event and calls RequestDoorAction on the server.
B_CrimsonCore_DoorReady-to-place door Blueprint in /CrimsonCore/Interaction/Door/, with the meshes SM_CrimsonDoor_Leaf, SM_CrimsonDoor_FrameEdge and SM_CrimsonDoor_FrameCorner (/CrimsonCore/Interaction/Door/Meshes/).
GA_Door_LockpickThe shipped lockpick channel (/CrimsonCore/Interaction/Door/): a Blueprint of UCrimsonCoreGameplayAbility_InteractionDuration, 4 s, MaxDistanceFromTarget 250, bCancelWhenInputReleased on.
ItemDef_Key_Brass, ItemDef_Key_Iron, ItemDef_LockpickShowcase items in /CrimsonCore/Inventory/Items/. The keys carry the Item.Type.Key tag.
AI pathing needs a runtime navmesh
The door moves a nav modifier over its doorway (NavArea_Default while open, ClosedNavArea / LockedNavArea while shut, both NavArea_Null by default). That only reaches the navmesh if the navmesh rebuilds at runtime: set Runtime Generation to Dynamic Modifiers Only (or Dynamic) on your level's RecastNavMesh actor, or in Project Settings -> Navigation Mesh. With Static, the doorway keeps whatever area it had at build time. The plugin never changes this setting for you.

1. Place a door

Place B_CrimsonCore_Door (tick Show Plugin Content in the Content Browser settings to see it), or make your own Blueprint child of Crimson Door and assign its meshes. The blue editor arrow points at the door's Front side.

Reference implementation
Open B_CrimsonCore_Door (/CrimsonCore/Interaction/Door/). In Class Defaults study Crimson|Door|Meshes (LeafMesh, FrameEdgeMesh, FrameCornerMesh), Crimson|Door|Motion and Crimson|Door|Abilities (LockpickAbilityClass = GA_Door_Lockpick). Duplicate it, or create a Blueprint Class with parent Crimson Door and fill the same three meshes.

On the placed door pick Motion (Swing or Slide), tick Double Leaf for a pair, and set Auto Close Delay in seconds (0 keeps it open).

Verify
Play and walk up to the door: the prompt reads Open, the door swings away from you, and the prompt then reads Close. Open it again from the other side: it swings the other way. With Play As Client, the door moves on both windows.

2. Lock it with a key

Set KeyItem to an item definition and tick Door State -> Locked on the placed instance. A player carrying the key gets Unlock; one without it sees Locked with the key on the prompt's checklist and no key glyph. Tick bConsumeKeyOnUnlock to take one key out of the bag on unlock. Add more requirements to ExtraKeyConditions; they are checked with the key for both Lock and Unlock.

Screenshot pendingImages/CrimsonCore/door_prompt_locked_checklist.png
A locked door without its key: the prompt reads Locked, with the Brass Key requirement unmet and no key glyph.

Details panel of the placed door: Crimson|Door|Lock -> Key Item = ItemDef_Key_Brass, Crimson|Door -> Door State -> Locked ticked. To unlock it from script instead, on the server: Has Authority (Branch, True) -> Set Door Locked (New Locked unticked).

Verify
Without the key the prompt reads Locked with the key listed as unmet; the context menu shows Unlock (Brass Key) grayed out with "Requires Brass Key". Pick up the key: the prompt changes to Unlock, which you hold for 1 s (KeyHoldDuration), then Open. While the door is open, the menu's Lock is grayed out with "Close the door first".

3. Make it lockpickable (optional)

Tick bLockpickable, then add the requirements to LockpickConditions (or LockpickConditionSets), for example Has Item with a lockpick and Attribute Compare Health >= 50. Set LockpickAbilityClass to a duration ability (the shipped GA_Door_Lockpick, or UCrimsonCoreGameplayAbility_InteractionDuration itself for the default 3 s channel).

Screenshot pendingImages/CrimsonCore/door_lockpick_progress_ring.png
Picking a lock: the progress ring fills around the interaction key glyph while the channel runs.

Details panel: Crimson|Door|Lock -> Lockpickable ticked; Lockpick Conditions -> + add Has Item (ItemDefinition = ItemDef_Lockpick) and Attribute Compare (Health, >=, 50); Crimson|Door|Abilities -> Lockpick Ability Class = GA_Door_Lockpick. To start the channel from your own UI on the owning client: Is Locally Controlled (Branch, True) -> Try Begin Lockpick Local (Pawn = the player's pawn).

Verify
Without a lockpick the prompt's checklist shows the lockpick and Health requirements. With both met, a press starts a ring around the key glyph; walking away or releasing the key cancels it, and completing it unlocks the door. Pick Lock from the context menu starts the same channel. With Play As Client, the client sees the door unlock only after the server's channel completes.

4. Add a one-sided bolt (optional)

Tick bHasBolt and set BoltSide (Back by default). Only a player on that side can bolt or unbolt; from the other side the prompt reads Barred ("Barred from the other side") and the menu's Unbolt is grayed out. Bolting needs the door closed. Bolt and Unbolt are held for 3 s (BoltHoldDuration), on the prompt and in the menu. Tick Door State -> Bolted to start it barred.

Verify
From the back the prompt reads Unbolt, and the door unbolts only after a 3 s hold. From the front it reads Barred with no key glyph, and the menu lists Unbolt grayed out with "Barred from the other side".

How the prompt and the menu decide

Three functions on the door hold every rule; the prompt, the menu and the server all call them.

Listing - `ShouldListAction`. Whether the menu lists a verb at all. It reads only replicated state, so client and server build the same list.

VerbListed when
Openthe door is closed
Closethe door is open
Unlocklocked and KeyItem is set
Lockunlocked and KeyItem is set
Pick Locklocked and bLockpickable
BoltbHasBolt and not bolted
Unboltbolted
Knockalways

Availability and reasons - `GetActionAvailability`. Returns Hidden, Disabled with a reason, or Enabled. A disabled row is grayed out with its reason, and the server refuses anything that is not Enabled. Side checks live here, never in the listing, so a client and server that disagree about a pawn standing on the door plane produce a refusal rather than a mismatched menu.

VerbDisabled whenReason shown
Openbolted"Unbolt it first" (bolt side) or "Barred from the other side"
Openlocked"Locked"
Lock, Boltthe door is open"Close the door first"
Unlock, Lockthe key or ExtraKeyConditions failthe first failure, e.g. "Requires Brass Key"
Pick LockLockpickConditions / LockpickConditionSets failthe first failure
Boltused from the side opposite BoltSide"The bolt is on the other side"
Unboltused from the side opposite BoltSide"Barred from the other side"
Knockduring KnockCooldownno text; the row shows a cooldown sweep
Screenshot pendingImages/CrimsonCore/door_context_menu_disabled_reasons.png
The context menu on a locked door without its key: Unlock grayed out with its reason, Knock available.

Primary action - `GetPrimaryAction`. What one press of the interaction prompt does, checked in order. The menu marks the same verb as its default row.

OrderDoor statePrompt
1openClose
2boltedUnbolt from BoltSide; otherwise an informational Barred
3lockedUnlock if the key passes; else Pick Lock if the lockpick requirements pass; else Locked with the key checklist (with no KeyItem: Locked, "It won't budge")
4otherwiseOpen

Hold times - `GetActionHoldDuration`. Lock and Unlock take a KeyHoldDuration hold (1 s by default), Bolt and Unbolt a BoltHoldDuration hold (3 s); every other verb is instant. The same duration applies on both surfaces: the prompt offers an available Unlock or Unbolt as a Hold option and fills the progress ring around the key glyph, and the context menu marks those four rows HoldToConfirm with the door's duration in HoldToConfirmSeconds, so the row fills while it is held and fires when full. Releasing early cancels. The informational Locked and Barred prompts stay instant. Set either duration to 0 to make those verbs instant.

Property (Crimson|Door|Behavior)DefaultVerbs
KeyHoldDuration1 sLock, Unlock
BoltHoldDuration3 sBolt, Unbolt

Multiplayer and save

  • Server owns the state. RequestDoorAction (server, BlueprintAuthorityOnly) is the one validated entry point: reach (MaxUseDistance, 550 by default), then GetActionAvailability, then apply. Prompt presses and menu clicks both end there. It refuses Lockpick; a lock is only picked by a completed channel.
  • Prompt presses carry the verb. On the pressing client, CustomizeInteractionEventData writes GetPrimaryAction into the trigger event's EventMagnitude; UCrimsonCoreGameplayAbility_DoorInteract decodes it on the server. Two players pressing Open at once both get an open door, never a toggle back to shut.
  • Holds are a UX delay, not an authority check. The hold is timed on the owning client, on the prompt and in the menu; nothing reaches the server until it completes, and the server then validates the verb through RequestDoorAction like any other.
  • The menu runs on the server, except Pick Lock, which runs on the owning client because its channel is a locally predicted ability.
  • Motion is local on every machine, the dedicated server included, so leaf collision is real; transforms never replicate. A late-joining client, or one receiving a restored save, snaps the door into place with no sound.
  • Blocking. A leaf that would move into a pawn holds; after a moment the server reverses it (bReverseWhenBlocked) and fires On Door Blocked. Auto-close waits while the doorway is occupied.
  • Events. OnDoorStateChanged(Previous, Current), On Door State Changed, On Door Motion Finished and On Knocked fire on every machine. Knock and the locked-door rattle are unreliable multicasts.
  • Save. DoorState is SaveGame and saves with the world: open, locked and bolted state and the swing direction come back on load. A consumed key stays consumed through the player's inventory save.

Mesh pivot rules

The layout reads mesh bounds, not pivots, so most door meshes work unchanged.

  • Leaf. Width is the Y extent, height the Z extent; the thickness is centered on the door plane and the bottom sits on the floor. The hinge is the vertical edge nearest the mesh origin, so a leaf with its pivot on the hinge works whichever way it extends (+Y or -Y). A centered pivot hinges on the min-Y edge.
  • Double door. The right leaf is a Y mirror of the left one (negative Y scale, not a 180-degree turn), so both keep their front faces on the Front side. Model one leaf only.
  • Frame. FrameEdgeMesh is a vertical post, stretched to the leaf height on each side; the same mesh rolled 90 degrees is the lintel. FrameCornerMesh fills the two top corners. Any of them may be left empty.
  • The interaction volume and the nav modifier are sized to the doorway in the same pass.
Screenshot pendingImages/CrimsonCore/door_double_sliding.png
A double sliding door part-way open: two mirrored leaves sliding into the wall.

Build a door showcase

Place six B_CrimsonCore_Doors in a level of your own and configure each as below, plus three B_CrimsonItemSpawnerPads (/CrimsonCore/GameplayTesting/) for ItemDef_Key_Brass, ItemDef_Key_Iron and ItemDef_Lockpick. Set the level's RecastNavMesh to Dynamic Modifiers Only first.

DoorConfigurationTry
UnlockedSwing, AutoCloseDelay 5It swings away from you on either side; a second player in its path makes it reverse; auto-close waits while the doorway is occupied.
Key lockedLocked, KeyItem = Brass KeyLocked with a checklist without the key; Unlock (a 1 s hold) -> Open with it; Lock grayed while open.
Consume keyLocked, KeyItem = Iron Key, bConsumeKeyOnUnlockThe Iron Key leaves the bag on unlock.
One-sided boltbHasBolt, BoltSide = Back, starts boltedBack side can Unbolt with a 3 s hold; front side reads Barred, Unbolt grayed.
Double slidingMotion = Slide, bDoubleLeaf, AutoCloseDelay 4Two mirrored leaves slide into the wall.
LockpickableLocked, Brass Key, bLockpickable; Has Item (Lockpick) + Attribute Compare (Health >= 50)Checklist shows Lockpick + Health; a 4 s ring; walking away or releasing the key cancels; menu Pick Lock works.
Verify
Knock on any door from the context menu: the knock plays on every machine and the row grays out with a cooldown sweep. With the editor viewport's navigation display on (P), the doorway area changes as the door opens and closes.

API reference

Function (on ACrimsonCoreDoor)Runs onPurpose
RequestDoorAction(Instigator, Action)ServerValidated player action: reach, availability, apply. True when applied. Refuses Lockpick.
GetActionAvailability(Instigator, Action, OutReason)AnyHidden / Disabled (with reason) / Enabled.
GetPrimaryAction(Instigator)AnyThe verb a prompt press performs; None when the prompt is only a message.
ShouldListAction(Action)AnyWhether the menu lists the verb (replicated state only).
GetActionHoldDuration(Action)AnySeconds the verb is held on the prompt and in the menu: KeyHoldDuration for Lock / Unlock, BoltHoldDuration for Bolt / Unbolt, 0 (instant) otherwise.
SetDoorOpen(bNewOpen, Instigator) / SetDoorLocked(bNewLocked) / SetDoorBolted(bNewBolted)ServerScripting setters with no instigator checks. The optional instigator picks the swing direction.
TryBeginLockpickLocal(Pawn)Owning clientStarts the lockpick channel on the pawn's ability system.
IsOpen / IsLocked / IsBolted / GetDoorStateAnyReplicated state.
GetSideOf(Actor) / IsWithinReach(Instigator)AnyFront (+X) or Back; within MaxUseDistance.
GetKnockCooldown(OutRemaining01, OutRemainingSeconds)AnyTrue while knocking is cooling down.
GetKeyDisplayNameAnyThe key item's display name, empty with no KeyItem.
EncodeAction / DecodeAction / GetActionDisplayNameAnyVerb to event magnitude and back; the verb's display text.
OnDoorStateChangedAllDelegate (Previous, Current) after every state change, restores included.

See also

  • Interaction Integration - the interact ability, prompt widget and progress ring the door relies on.
  • Context Menu - how the menu gathers, validates and executes the door's entries.
  • Inventory Showcases - spawner pads and the item definitions the keys are built from.