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 wantYou need
One Crimson plugin in your gameJust that plugin. CrimsonCommon is optional.
Several Crimson plugins, wired together by youThose 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 youCrimsonCore (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 classesCrimsonCommon.
CrimsonCore is the reference implementation
Even if you do not use CrimsonCore, its 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

MechanismWhat a plugin offersExample
DelegatesA native ...Native multicast (FGameplayTag Channel, const Payload&) plus a BlueprintAssignable twin on the object that owns the eventUCrimsonInventoryManagerComponent::OnStackChangedNative, UCrimsonAbilitySystemComponent::OnAbilityProgress
Feature statesA ModularGameplay feature announced with ChangeFeatureInitState when a system is ready on an actorCrimsonInventory::SystemReadyFeatureName, GameState feature Crimson.Experience.Loaded
Interfaces and extension pointsAn interface or instanced extension class outsiders implementICrimsonWarpTargetProvider, UCrimsonGameplayAbilityExtension, ICrimsonPoolableAdapter
Static hooksA static delegate the plugin calls and CrimsonCore binds; unbound = standalone behaviorFCrimsonActionBarIntegrationHooks, UCrimsonCombatTextFilter::TagLabelResolver()
SettingsA class setting that lets outsiders substitute a subclassUCrimsonInventorySettings::ItemInstanceClass, UCrimsonSettingRegistry::KeybindingSettingClass

What CrimsonCore installs

FolderIntegrationJoinsInstalled by
AbilitySystem/UCrimsonCoreAbilityExtension_FixedSlotUI, UCrimsonFixedAbilitySlotContextAbilitySystem + UIAbility Extensions
AbilitySystem/UCrimsonCoreAbilityExtension_PawnFacing, UCrimsonCoreAbilityCameraStaticsAbilitySystem + CameraAbility Extensions; static calls
AbilitySystem/UCrimsonCoreComboModifierBridgeAbilitySystem + InputEngine subsystem, automatic
AbilitySystem/UCrimsonGameplayAbility_UseObject (the shared use-item ability)AbilitySystem + InventoryGrant once
AbilitySystem/UCrimsonInteractionCondition_AttributeCompareAbilitySystem + InteractionInstanced condition
AbilitySystemUI/UCrimsonContextAction_GrantAbilityAbilitySystem + UIContext action in action sets
ActionBar/UCrimsonCoreActionBarIntegrationSubsystem, UCrimsonCoreActionBarAbilityAdapter, UCrimsonCoreActionBarObjectAdapter, UCrimsonActionBarWidgetPayloadActionBar + AbilitySystem + Inventory + Input + UIEngine subsystem, automatic
CombatText/UCrimsonCoreCombatTextTagLabelBridgeCombatText + UIEngine subsystem, automatic
Equipment/UCrimsonCoreEquipmentAbilitySetSubsystemEquipment + AbilitySystemWorld subsystem, automatic
Experience/UCrimsonCoreExperienceLoadingScreenBridge, UCrimsonCoreExperienceStaticsExperience + LoadingScreen (+ CrimsonCommon sessions)Engine subsystem, automatic; static calls
Input/CrimsonCoreInputSensitivity::ToInputSensitivitySettings + InputUsed by UCrimsonCoreLocalPlayer
Interaction/UCrimsonCoreInteractionStatics, UCrimsonCoreInteractionDurationPayloadInteraction + UIStatic calls; Core duration ability
InteractionUI/UCrimsonInteractionContextActionContributor, UCrimsonContextAction_FromInteractionOptionInteraction + UIWorld subsystem, automatic
Inventory/Context actions (UCrimsonInventoryContextAction_Use / _Drop / _Equip / _Unequip / _Buy, base UCrimsonInventoryQuantityContextAction), UCrimsonInventoryItemTypeActionSet, UCrimsonInventoryItemFragment_ContextActions, UCrimsonInventoryContextActionSettings, UCrimsonInventoryContextActionStaticsInventory + UI (+ Equipment)Action sets and item fragments; settings
Inventory/UCrimsonCoreInventoryItemInstanceInventory + UI + ActionBarItemInstanceClass setting
Inventory/ACrimsonInteractableContainerActor, UCrimsonInteractionCondition_HasItemInventory + InteractionContainer base class; instanced condition
Inventory/UCrimsonInventoryEquipmentStatics, UCrimsonInventoryIntegrationSubsystem, UCrimsonInventorySlotBindingResolverComponentInventory + Equipment + ActionBarStatic calls; world subsystem, automatic (it adds the resolver to player controllers)
Inventory/UCrimsonCoreInventoryCheatsInventory + UI + InteractionCheat manager extension
MotionWarping/UCrimsonCoreLockOnWarpTargetProvider, UCrimsonCoreLockOnWarpTargetSubsystemCamera + MotionWarpingGameInstance subsystem adds the provider to receiver pawns
ObjectPool/UCrimsonCoreProjectilePoolingSubsystemHitDetection + ObjectPoolEngine subsystem, automatic
QuestSystem/UCrimsonCoreQuestMessageBridge, UCrimsonCoreQuestEvent_MessageReceivedQuestSystem + CrimsonCommon busGameInstance subsystem, automatic; instanced event
Settings/UCrimsonCoreSettingKeyboardInput, UCrimsonCoreSettingsListEntry_KeyboardInput, UCrimsonPressAnyKeyWithModifiersSettings + Input + UIRegistry KeybindingSettingClass; keybind row parent
Settings/UCrimsonCorePerfStatSettings, UCrimsonCorePerfStatUserSettings, UCrimsonCorePerfStatSettingsLibrary, UCrimsonSettingValueDiscrete_PerfStatSettings + UIAddPerformanceStatsPage
Teams/UCrimsonInteractionCondition_TeamAttitudeTeams + InteractionInstanced condition
Teams/UCrimsonCorePartyMessageForwarderTeams + CrimsonCommon busGameInstance subsystem, automatic
UI/UCrimsonCoreActionWidgetChordBridgeUI + InputEngine subsystem, automatic
UI/UCrimsonCoreRadialBindingProvider_SlotBinding, UCrimsonCoreRadialBindingBridge, UCrimsonSlotBindingDragDropOperationUI + ActionBarWheel provider; engine subsystem
UI/UCrimsonCoreRadialBindingComponentUI + SaveSystemComponent 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.

cpp
#include "Messaging/CrimsonMessageSubsystem.h" // CrimsonCommon
#include "Party/CrimsonPartySubsystem.h" // CrimsonTeams
PartySubsystem->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.

cpp
#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 nameActorImplementer
CrimsonInventory::SystemReadyFeatureName / RoutingSystemReadyFeatureNameInventory owner / player controllerManager / routing component
CrimsonAbilitySystem::SystemReadyFeatureNamePlayer stateASC
CrimsonEquipment::SystemReadyFeatureNamePawnSlots component
CrimsonTeams::SystemReadyFeatureName / PartySystemReadyFeatureNameTeam agent owner / player stateAgent / party member component
CrimsonSkillTreeTags::SystemReadyFeatureNameSkill tree ownerSkill tree manager
Crimson.Experience.Loaded (CrimsonExperience::ExperienceLoadedFeatureName)GameStateExperience manager component
Keep the required state empty
These features are not registered, ordered init states: pass FGameplayTag() as the required state.

Doing it yourself (without CrimsonCore)

  1. 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.
  2. Bind the plugins' delegates (...Native in C++, the BlueprintAssignable twin in Blueprint) and call the other plugin's public API from the handler.
  3. Wait for readiness with ModularGameplay: RegisterAndCallForActorInitState(Actor, <Plugin>::SystemReadyFeatureName, FGameplayTag(), ...); for the experience, the GameState feature Crimson.Experience.Loaded.
  4. Where a plugin exposes a hook, setting or extension point (for example FCrimsonActionBarIntegrationHooks, UCrimsonInventorySettings::ItemInstanceClass, UCrimsonGameplayAbility::Extensions), plug your own implementation in there.
  5. 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 in UCrimsonCoreInitStateSubsystem).