Concept: Connecting Crimson Plugins
Every Crimson plugin works on its own. None of them depends on another Crimson plugin - not even CrimsonCommon - because Fab builds and ships each plugin alone. When you own two or more plugins, you connect them in YOUR project (or with CrimsonCore, the free integration example), never by making one plugin depend on another.
| You want | You need |
|---|---|
| One Crimson plugin in your game | Just that plugin. CrimsonCommon is optional. |
| Several Crimson plugins, wired together by you | Those plugins. Each plugin's wiki lists the delegates, hooks, settings, interfaces and feature states it exposes on its API Reference page; connect them in your own game module as described under Doing it yourself below. |
| Several Crimson plugins, wired together for you | CrimsonCore (free download from the Crimson Discord, not a Fab product). It needs every Crimson plugin plus CrimsonCommon installed, and joins them under Source/CrimsonCore/{Public,Private}/Integration/<Plugin>/. |
| The in-editor wiki, the dev-tool gallery, the message bus or the CommonGame-style player classes | CrimsonCommon. |
Integration/ folder is the worked example for every pairing: copy the classes you need into your own game module and rename them. They only use the plugins' public API.Every Crimson Fab plugin compiles and ships alone: it depends on no other Crimson plugin, not even CrimsonCommon. Each type has exactly one owning plugin. When a feature needs two plugins at once - an inventory item in a UI context menu, an ability set granted by equipment, the camera lock-on driving motion warping - the code that joins them lives in CrimsonCore, under Source/CrimsonCore/{Public,Private}/Integration/<Plugin>/.
How the plugins expose themselves
| Mechanism | What a plugin offers | Example |
|---|---|---|
| Delegates | A native ...Native multicast (FGameplayTag Channel, const Payload&) plus a BlueprintAssignable twin on the object that owns the event | UCrimsonInventoryManagerComponent::OnStackChangedNative, UCrimsonAbilitySystemComponent::OnAbilityProgress |
| Feature states | A ModularGameplay feature announced with ChangeFeatureInitState when a system is ready on an actor | CrimsonInventory::SystemReadyFeatureName, GameState feature Crimson.Experience.Loaded |
| Interfaces and extension points | An interface or instanced extension class outsiders implement | ICrimsonWarpTargetProvider, UCrimsonGameplayAbilityExtension, ICrimsonPoolableAdapter |
| Static hooks | A static delegate the plugin calls and CrimsonCore binds; unbound = standalone behavior | FCrimsonActionBarIntegrationHooks, UCrimsonCombatTextFilter::TagLabelResolver() |
| Settings | A class setting that lets outsiders substitute a subclass | UCrimsonInventorySettings::ItemInstanceClass, UCrimsonSettingRegistry::KeybindingSettingClass |
What CrimsonCore installs
| Folder | Integration | Joins | Installed by |
|---|---|---|---|
AbilitySystem/ | UCrimsonCoreAbilityExtension_FixedSlotUI, UCrimsonFixedAbilitySlotContext | AbilitySystem + UI | Ability Extensions |
AbilitySystem/ | UCrimsonCoreAbilityExtension_PawnFacing, UCrimsonCoreAbilityCameraStatics | AbilitySystem + Camera | Ability Extensions; static calls |
AbilitySystem/ | UCrimsonCoreComboModifierBridge | AbilitySystem + Input | Engine subsystem, automatic |
AbilitySystem/ | UCrimsonGameplayAbility_UseObject (the shared use-item ability) | AbilitySystem + Inventory | Grant once |
AbilitySystem/ | UCrimsonInteractionCondition_AttributeCompare | AbilitySystem + Interaction | Instanced condition |
AbilitySystemUI/ | UCrimsonContextAction_GrantAbility | AbilitySystem + UI | Context action in action sets |
ActionBar/ | UCrimsonCoreActionBarIntegrationSubsystem, UCrimsonCoreActionBarAbilityAdapter, UCrimsonCoreActionBarObjectAdapter, UCrimsonActionBarWidgetPayload | ActionBar + AbilitySystem + Inventory + Input + UI | Engine subsystem, automatic |
CombatText/ | UCrimsonCoreCombatTextTagLabelBridge | CombatText + UI | Engine subsystem, automatic |
Equipment/ | UCrimsonCoreEquipmentAbilitySetSubsystem | Equipment + AbilitySystem | World subsystem, automatic |
Experience/ | UCrimsonCoreExperienceLoadingScreenBridge, UCrimsonCoreExperienceStatics | Experience + LoadingScreen (+ CrimsonCommon sessions) | Engine subsystem, automatic; static calls |
Input/ | CrimsonCoreInputSensitivity::ToInputSensitivity | Settings + Input | Used by UCrimsonCoreLocalPlayer |
Interaction/ | UCrimsonCoreInteractionStatics, UCrimsonCoreInteractionDurationPayload | Interaction + UI | Static calls; Core duration ability |
InteractionUI/ | UCrimsonInteractionContextActionContributor, UCrimsonContextAction_FromInteractionOption | Interaction + UI | World subsystem, automatic |
Inventory/ | Context actions (UCrimsonInventoryContextAction_Use / _Drop / _Equip / _Unequip / _Buy, base UCrimsonInventoryQuantityContextAction), UCrimsonInventoryItemTypeActionSet, UCrimsonInventoryItemFragment_ContextActions, UCrimsonInventoryContextActionSettings, UCrimsonInventoryContextActionStatics | Inventory + UI (+ Equipment) | Action sets and item fragments; settings |
Inventory/ | UCrimsonCoreInventoryItemInstance | Inventory + UI + ActionBar | ItemInstanceClass setting |
Inventory/ | ACrimsonInteractableContainerActor, UCrimsonInteractionCondition_HasItem | Inventory + Interaction | Container base class; instanced condition |
Inventory/ | UCrimsonInventoryEquipmentStatics, UCrimsonInventoryIntegrationSubsystem, UCrimsonInventorySlotBindingResolverComponent | Inventory + Equipment + ActionBar | Static calls; world subsystem, automatic (it adds the resolver to player controllers) |
Inventory/ | UCrimsonCoreInventoryCheats | Inventory + UI + Interaction | Cheat manager extension |
MotionWarping/ | UCrimsonCoreLockOnWarpTargetProvider, UCrimsonCoreLockOnWarpTargetSubsystem | Camera + MotionWarping | GameInstance subsystem adds the provider to receiver pawns |
ObjectPool/ | UCrimsonCoreProjectilePoolingSubsystem | HitDetection + ObjectPool | Engine subsystem, automatic |
QuestSystem/ | UCrimsonCoreQuestMessageBridge, UCrimsonCoreQuestEvent_MessageReceived | QuestSystem + CrimsonCommon bus | GameInstance subsystem, automatic; instanced event |
Settings/ | UCrimsonCoreSettingKeyboardInput, UCrimsonCoreSettingsListEntry_KeyboardInput, UCrimsonPressAnyKeyWithModifiers | Settings + Input + UI | Registry KeybindingSettingClass; keybind row parent |
Settings/ | UCrimsonCorePerfStatSettings, UCrimsonCorePerfStatUserSettings, UCrimsonCorePerfStatSettingsLibrary, UCrimsonSettingValueDiscrete_PerfStat | Settings + UI | AddPerformanceStatsPage |
Teams/ | UCrimsonInteractionCondition_TeamAttitude | Teams + Interaction | Instanced condition |
Teams/ | UCrimsonCorePartyMessageForwarder | Teams + CrimsonCommon bus | GameInstance subsystem, automatic |
UI/ | UCrimsonCoreActionWidgetChordBridge | UI + Input | Engine subsystem, automatic |
UI/ | UCrimsonCoreRadialBindingProvider_SlotBinding, UCrimsonCoreRadialBindingBridge, UCrimsonSlotBindingDragDropOperation | UI + ActionBar | Wheel provider; engine subsystem |
UI/ | UCrimsonCoreRadialBindingComponent | UI + SaveSystem | Component on the player controller |
Forwarding events onto the message bus
Plugins no longer broadcast on CrimsonCommon's UCrimsonMessageSubsystem. For projects that want one global channel tree, bind the plugin's native delegate and re-broadcast; UCrimsonCorePartyMessageForwarder is the worked example.
#include "Messaging/CrimsonMessageSubsystem.h" // CrimsonCommon#include "Party/CrimsonPartySubsystem.h" // CrimsonTeamsPartySubsystem->OnPartyMessageNative.AddUObject(this, &ThisClass::HandlePartyMessage);void UMyForwarder::HandlePartyMessage(FGameplayTag Channel, const FCrimsonPartyMessage& Message){UCrimsonMessageSubsystem::Get(this).BroadcastMessage(Channel, Message);}
Waiting for a plugin to be ready
Components added by Game Feature actions arrive after their owner. Wait on the owning plugin's feature state rather than binding in BeginPlay; CrimsonCore may also use UCrimsonExperienceManagerComponent::CallOrRegister_OnExperienceLoaded* directly.
#include "Components/GameFrameworkComponentManager.h"#include "CrimsonInventoryTags.h"if (UGameFrameworkComponentManager* GFCM = UGameFrameworkComponentManager::GetForActor(Owner)){Handle = GFCM->RegisterAndCallForActorInitState(Owner, CrimsonInventory::SystemReadyFeatureName, FGameplayTag(),FActorInitStateChangedDelegate::CreateUObject(this, &ThisClass::HandleInventoryReady), /*bCallImmediately*/ true);}// Params.Implementer is the inventory manager. Unregister with UnregisterActorInitStateDelegate(Owner, Handle).
| Feature name | Actor | Implementer |
|---|---|---|
CrimsonInventory::SystemReadyFeatureName / RoutingSystemReadyFeatureName | Inventory owner / player controller | Manager / routing component |
CrimsonAbilitySystem::SystemReadyFeatureName | Player state | ASC |
CrimsonEquipment::SystemReadyFeatureName | Pawn | Slots component |
CrimsonTeams::SystemReadyFeatureName / PartySystemReadyFeatureName | Team agent owner / player state | Agent / party member component |
CrimsonSkillTreeTags::SystemReadyFeatureName | Skill tree owner | Skill tree manager |
Crimson.Experience.Loaded (CrimsonExperience::ExperienceLoadedFeatureName) | GameState | Experience manager component |
FGameplayTag() as the required state.Doing it yourself (without CrimsonCore)
- Put the glue in YOUR game module (or a small project plugin) that lists both Crimson plugins in its Build.cs. Never add a Crimson dependency to a Crimson plugin.
- Bind the plugins' delegates (
...Nativein C++, the BlueprintAssignable twin in Blueprint) and call the other plugin's public API from the handler. - Wait for readiness with ModularGameplay:
RegisterAndCallForActorInitState(Actor, <Plugin>::SystemReadyFeatureName, FGameplayTag(), ...); for the experience, the GameState featureCrimson.Experience.Loaded. - Where a plugin exposes a hook, setting or extension point (for example
FCrimsonActionBarIntegrationHooks,UCrimsonInventorySettings::ItemInstanceClass,UCrimsonGameplayAbility::Extensions), plug your own implementation in there. - If you gate pawn setup on
HaveAllFeaturesReachedInitState, register the plugins'Crimson.SystemReady.*states after your last init state, or the gate will wait forever (CrimsonCore does this inUCrimsonCoreInitStateSubsystem).