Context Menu

Surfaces a dynamic list of actions on any target — items (Use / Drop / Split), doors (Lock / Unlock / Open / Close), NPCs, world pickups — with either a modal cursor-anchored popup or a world-anchored panel that tracks a USceneComponent (Cyberpunk scan-mode / Dishonored body-interaction feel).

Why it lives in CrimsonCore
Per the Cardinal Rule, no sibling Crimson plugin may depend on another. The context menu bridges CrimsonInventory + CrimsonInteraction + CrimsonUI — that cross-plugin glue can only live in CrimsonCore. The contracts (action base, interfaces, replicated component) are in CrimsonCommon; the presentation widgets + subsystem are in CrimsonUI; CrimsonInventory and CrimsonInteraction each expose a function-library helper. CrimsonCore's UCrimsonCoreContextMenuSubsystem is the only place that imports all of them.

Plugin pieces involved

PluginProvides
CrimsonCommonUCrimsonContextAction (+ _GrantAbility / _FromInteractionOption), UCrimsonContextActionSet (reusable action Data Asset), UCrimsonContextActionGatherer (the single gather path), ICrimsonContextActionProvider, ICrimsonContextActionContributor + UCrimsonContextActionContributorRegistry (inject actions from other systems), FCrimsonContextActionContext, FCrimsonContextActionHandle, UCrimsonContextActionPlayerComponent, native tags
CrimsonUIUCrimsonContextMenuSubsystem, widget bases (_Modal + _World + _Radial), entry widget (native click/hover), screen + entry VMs, confirmation-dialog routing, UCrimsonUISettings default widget classes
CrimsonInventoryUCrimsonInventoryItemFragment_ContextActions, UCrimsonInventoryItemTypeActionSet, GetContextActionsForItem helper
CrimsonInteractionUCrimsonInteractionStatics::GetContextActionsForActor (wraps existing FCrimsonInteractionOptions)
CrimsonCoreUCrimsonCoreContextMenuSubsystemShowContextMenuForItem / ShowContextMenuForActor (the public entry point) + ExecuteDefaultActionForItem / ForActor (double-click / tap fast path)

Integration steps

1. Enable the plugins

Open Edit → Plugins, search Crimson, and tick all five so each is enabled: CrimsonCommon, CrimsonUI, CrimsonInventory, CrimsonInteraction, and CrimsonCore. CrimsonCore depends on them, so the Plugins window flags them as required. Restart the editor when prompted — for a Blueprint project that's the whole install, with no .uproject or .Build.cs to edit.

2. Register the UI.Layer.ContextMenu layer

Open your UCrimsonHUDLayout Blueprint. Add a UCommonActivatableWidgetStack named e.g. Stack_ContextMenu. Wire RegisterLayer to attach it under the native tag.

cpp
// C++ — anywhere in your HUD layout init (typically RegisterLayers):
#include "ContextMenu/CrimsonContextMenuTags.h"
RegisterLayer(CrimsonContextMenuTags::UI_Layer_ContextMenu, Stack_ContextMenu);
BP equivalent
In BP, drag from RegisterLayer and pass the GameplayTag literal UI.Layer.ContextMenu and the stack widget reference. Same effect.

3. Create the menu widget Blueprints

Three widgets need BP subclasses. C++ bases own all logic; BP supplies visuals.

C++ baseSuggested BP nameRequired bindings
UCrimsonContextMenuWidget_ModalWBP_ContextMenu_ModalContentPanel (required BindWidget; top-left anchor, auto size) + EntryBox (Dynamic Entry Box, auto-populated by C++) inside it
UCrimsonContextMenuWidget_WorldWBP_ContextMenu_WorldSame as Modal - ContentPanel (required) tracks the world target each tick; EntryBox inside it
UCrimsonContextMenuEntryWidgetWBP_ContextMenuEntryLayout for one action button. Bind to EntryViewModel's DisplayName / Tooltip / DisabledReason / Icon / bIsEnabled / bIsDestructive / bIsFocused via MVVM. Hover-to-focus and click-to-confirm are wired natively

Inside ContentPanel, add a Dynamic Entry Box named exactly EntryBox and set its Entry Widget Class to WBP_ContextMenuEntry. C++ creates the entries automatically on activation and refresh - no MVVM view binding needed. (A UListView named EntryList is also supported for long scrollable menus, but a virtualizing list inside the auto-sized panel must be given a bounded height or it generates only one row.) A missing ContentPanel is a Blueprint compile error; missing both entry hosts logs a warning at activation.

4. Wire input bindings in the menu widget BP

Mouse needs no wiring — entries confirm on click and focus on hover natively. For gamepad/keyboard, the widget base exposes BlueprintCallable methods — in OnActivated, register UI action bindings for each of these tags.

UI action tagCalls
UI.Action.ContextMenu.ConfirmConfirmFocusedEntry
UI.Action.ContextMenu.CancelRequestClose
UI.Action.ContextMenu.Cycle.NextCycleNext
UI.Action.ContextMenu.Cycle.PreviousCyclePrevious
UI.Action.ContextMenu.Navigate.Up/DownHandled by CommonUI focus chain — no binding needed
Cycle bindings unlock cursor-less use
Cycle.Next / Cycle.Previous are the canonical shoulder-button pattern (Cyberpunk scan mode, Dishonored body interactions, Witcher 3 sign-switching). Bind LB/RB on gamepad and Q/E on K&M so players can pick options without ever touching the cursor. The VM skips disabled entries and wraps around at the ends.

5. Map the UI action tags to UInputAction assets

In your project's UCommonUIInputData (or UCrimsonInputConfig, whichever feeds the CommonUI action router), add one row per tag.

text
UI.Action.ContextMenu.Confirm -> IA_UI_Confirm
UI.Action.ContextMenu.Cancel -> IA_UI_Cancel
UI.Action.ContextMenu.Navigate.Up -> IA_UI_NavigateUp
UI.Action.ContextMenu.Navigate.Down -> IA_UI_NavigateDown
UI.Action.ContextMenu.Cycle.Next -> IA_UI_CycleNext (RB / E)
UI.Action.ContextMenu.Cycle.Previous -> IA_UI_CyclePrevious (LB / Q)

6. Attach the player component

UCrimsonContextActionPlayerComponent carries the Server_ExecuteContextAction RPC. Choose one attachment path; the component sets SetIsReplicatedByDefault(true) automatically.

cpp
// Option A — C++ subclass of your player controller:
ACrimsonPlayerController::ACrimsonPlayerController()
{
ContextActionComponent = CreateDefaultSubobject<UCrimsonContextActionPlayerComponent>(
TEXT("ContextActionComponent"));
}

Option B — add a GFA_AddComponents entry to your experience definition targeting APlayerController.
Option C — add the component in your PC Blueprint's Components panel.

7. Configure per-item-type defaults (optional)

For "all weapons get Inspect/Drop", "all consumables get Use/Drop":

StepAction
1Content Browser → Miscellaneous → Data Asset → CrimsonInventoryItemTypeActionSet
2Add rows mapping Item.Type.* tags to instanced action lists
3Project Settings → Crimson → Crimson Inventory → Default Context Action Type Set → soft ref the asset

8. Author per-item actions

Open any UCrimsonInventoryItemDefinition and add UCrimsonInventoryItemFragment_ContextActions to its Fragments array. Populate the fragment's Actions array with instanced subclasses of UCrimsonContextAction.

ActionTag
Set a GameplayTag per action (e.g. ContextAction.Item.Use). It names the action on the wire: server-side resolution matches GetActionId() (the tag name, or the class name when unset) plus its occurrence among same-id entries — robust against unrelated list shifts. Duplicate tags within one provider are legal but rely on stable relative order.

9. Show the menu from your gameplay code

cpp
// Inventory item — modal, cursor-anchored:
ULocalPlayer* LP = PC->GetLocalPlayer();
UCrimsonCoreContextMenuSubsystem* CM = LP->GetSubsystem<UCrimsonCoreContextMenuSubsystem>();
FCrimsonContextMenuRequest Request;
Request.PresentationMode = ECrimsonContextMenuPresentationMode::Modal;
Request.Header = FText::FromString(TEXT("Item Actions"));
Request.bAnchorToCursor = true;
// Widget class is optional — unset falls back to
// UCrimsonUISettings::DefaultModalContextMenuClass (Project Settings -> Crimson -> Crimson UI).
CM->ShowContextMenuForItem(ItemInstance, Request);
cpp
// Door / NPC — world-anchored:
FCrimsonContextMenuRequest Request;
Request.PresentationMode = ECrimsonContextMenuPresentationMode::World;
Request.Header = FText::FromString(TEXT("Door"));
// Request.WorldWidgetClass optional (falls back to UCrimsonUISettings::DefaultWorldContextMenuClass).
// Request.TargetAnchor is auto-filled to Actor->GetRootComponent() if not set.
CM->ShowContextMenuForActor(FocusedDoor, Request);

Typical trigger points: UCrimsonInventorySlotWidget::OnContextMenuRequested on right-click, a 'Hold E to see actions' branch on your interaction ability, or a direct HUD button. For the double-click / tap fast path that skips the menu, call ExecuteDefaultActionForItem / ExecuteDefaultActionForActor — it dispatches the first enabled action flagged bIsDefault (else the first enabled action).

Discovery rules

Target typeHow actions are gathered
UCrimsonInventoryItemInstanceICrimsonContextActionProvider is auto-implemented; merges Fragment + type registry (fragment wins by ActionTag); sorted by SortPriority. The registry is always the project default — no per-call override (it would desync the server re-gather)
Actor implementing ICrimsonContextActionProviderUsed directly (your custom gather)
Actor implementing only ICrimsonInteractableTargetThe gatherer wraps each FCrimsonInteractionOption from the actor and its interactable components in UCrimsonContextAction_FromInteractionOption — no actor code changes required
Any target (additionally)Contributors registered with UCrimsonContextActionContributorRegistry inject extra actions after the target's own gather (quests, trading, ...) - on client and server alike
Server validation
When the player picks an entry, the client sends FCrimsonContextActionHandle { Provider, ActionId, Occurrence, Payload }. The server re-runs UCrimsonContextActionGatherer::GatherActions (never trusts the client array), asks the provider's ValidateContextActionRequest (ownership), resolves by id + occurrence, re-checks GetAvailability == Enabled and ValidateExecution (range for interactables), and only then calls ExecuteAction(Context, Payload) under HasAuthority(). Stale requests drop with a log — never a kick.

Out of the box: ACrimsonInteractableActor

ACrimsonInteractableActor ships with the full flow pre-wired: it implements ICrimsonContextActionProvider, and with bShowContextMenuOnInteract (default on) a successful interaction opens the world-anchored menu on the interacting player. Author actions as Crimson Context Action Set Data Assets referenced in ContextActionSets - a plain asset picker, shareable across every door/lever, with asset edits propagating to all of them. There is deliberately no inline action array on the actor (Instanced arrays on level-placed actors keep stale per-instance copies).

Property / functionWhat it does
ContextActionSetsThe actions this actor offers - references to UCrimsonContextActionSet Data Assets.
bShowContextMenuOnInteractOpen the menu automatically after a successful interaction (on the interacting player only).
bAddTestActionsWhenNoneAuthoredWhile no set is referenced, gather two transient Debug Message test actions so the round-trip is testable the moment the actor is placed. Disable for production actors.
ContextMenuHeaderTitle text of the menu.
ShowContextMenu(Instigator)BlueprintCallable manual trigger (no-op unless the instigator is the locally controlled player) - use with the bool off for custom flows.
Blocking the camera (or anything) while a menu is open
On the menu widget's class defaults set EffectWhileOpen to an Infinite GameplayEffect whose Granted Tags add e.g. Camera.Blocked, and put that tag in your camera-turn abilities' ActivationBlockedTags. The GE is applied to the owning pawn's ASC when the menu opens and removed on every close path (pick, click-outside, walk-away, target death), so the block can never leak. LooseTagsWhileOpen is the lightweight tag-only alternative. Mouse-look is already suspended while a menu is open (menus run GameMouseCaptureMode = NoCapture).

World-menu lifetime and feel are per-asset options on the widget Blueprint (UCrimsonContextMenuWidget_World class defaults): AutoCloseDistance closes the menu when the pawn walks away (the server-side crimson.ContextMenu.MaxExecuteDistance check backs it up), bCloseOnClickOutside gives the dialog-border dismiss pattern, and bScaleWithDistance / bFadeWhenOccluded fake depth and stop the menu drawing through walls.

Subclassing UCrimsonContextAction

cpp
UCLASS(DisplayName = "Drop Item")
class UMyAction_DropItem : public UCrimsonContextAction
{
GENERATED_BODY()
public:
virtual bool IsAvailable_Implementation(const FCrimsonContextActionContext& Context) const override
{
return Cast<UCrimsonInventoryItemInstance>(Context.Target.Get()) != nullptr;
}
virtual void ExecuteAction_Implementation(const FCrimsonContextActionContext& Context, const FInstancedStruct& Payload) override
{
// Runs under HasAuthority() on the server — do the deed here.
// Payload is empty for plain clicks; ConfirmEntryWithPayload can pass arguments.
}
};

Or create a Blueprint subclass directly — UCrimsonContextAction is Blueprintable, EditInlineNew, DefaultToInstanced, so designers can author actions inline on item fragments without C++.

Common pitfalls

Menu opens then immediately closes
The BP subclass overrode InputConfig to Default while the parent layer is Game — no UI input flows. Either leave the C++ default (GameAndMenu) or set the BP explicitly.
Click doesn't reach server
UCrimsonContextActionPlayerComponent not attached to the player controller. Check via PC->FindComponentByClass<UCrimsonContextActionPlayerComponent>() returning non-null.
Server logs 'not found on provider - gather list shifted'
Client and server gathers disagree. Make sure your GetAvailability and GatherContextActions implementations are deterministic — no per-machine state, no random ordering.
Interactable action does nothing at long range
Intended anti-cheat: interaction-derived actions fail a server-side range check beyond crimson.ContextMenu.MaxExecuteDistance (default 1000 cm; <= 0 disables).
World menu doesn't track target
Request.TargetAnchor is unset. ShowContextMenuForActor auto-fills it from the actor's root, but if you call UCrimsonContextMenuSubsystem::ShowContextMenu directly you must set it manually.
No cursor but menu still wants one
Set Request.bAnchorToCursor = false and provide a fixed ScreenAnchor (e.g. screen center for gamepad-only flows).
Source locations
Contracts + replicated component: Plugins/CrimsonCommon/Source/CrimsonCommon/Public/ContextMenu/. Subsystem + widgets + VMs: Plugins/CrimsonUI/Source/CrimsonUI/Public/ContextMenu/. Item fragment + type registry: Plugins/CrimsonInventory/Source/CrimsonInventory/Public/Fragments/ + Public/CrimsonInventoryItemTypeActionSet.h. Interaction helper: Plugins/CrimsonInteraction/Source/CrimsonInteraction/Public/CrimsonInteractionStatics.h. Orchestrator: Plugins/CrimsonCore/Source/CrimsonCore/Public/ContextMenu/CrimsonCoreContextMenuSubsystem.h.