How-To: Author Styles in the Registry Editor

Goal: author a registry's styles and modifiers in the asset's own editor - picking every identifier from a dropdown instead of typing it from memory - preview the motion each row resolves to, and validate the whole asset before you hit play.

Prerequisites
A style registry and a motion set (Quick Start -> step 2), and the gameplay tags your damage code sends. A placement set is optional - see How-To: Position Pops. Plugin content is hidden in the Content Browser until you tick Settings -> Show Plugin Content.

1. Open the registry

Double-click the registry asset. It opens in its own editor rather than the stock details panel: a tree on the left, details in the middle, and a Pop Preview on the right.

Screenshot pendingImages/CrimsonCombatText/howto_registry_editor.png
The style registry editor: section tree, details for the selected row, and the Pop Preview showing the real pop widget.
Tree sectionWhat it lists
StylesOne row per Styles key, sorted by tag
Default StyleThe single DefaultStyle used when nothing matches
ModifiersOne row per Modifiers key, badged with its ApplyOrder
Advanced RulesOne row per AdvancedRules entry, badged with its Priority
SetsThe asset references: MotionSet, PlacementSet, FilterSet, and the authoring text style tables

Selecting a row shows only that row in the details pane, so you are editing one style at a time instead of scrolling a nested map. Row badges summarize what the row resolves to - its motion preset or inline, its placement or target, agg 0.50s for a row that declares its own time window, scope:Stagger for one that declares its own stagger window, summary:Summary for one that names a ScopeSummaryStyle, ledger:Damage for a row that inherits both window and scope from an ancestor, no ledger for a row under an aggregating ancestor that clears bInheritAggregation, and hold for a row whose AggregationDisplay is HoldWhileScoped. A badge turns amber when what it names is missing - a motion preset that is not in the set, a summary key that is not a Styles row - which is the same thing validation will tell you.

Verify
The tree lists your Styles keys under Styles. Clicking one swaps the details pane to that style alone, and clicking another swaps it again with no scrolling.

Start from a worked example

Before authoring anything by hand, press Add Examples in the toolbar - or Add Example Styles on the asset itself, in the stock details panel. It writes a complete worked set: 11 styles, 2 modifiers and 2 advanced rules, every one of them illustrating a decision you are about to make yourself.

KeyText rowMotionWhat it shows
DamageDefaultFloatUpThe parent row, and the ledger root for the whole damage branch. Damage.Type.Physical has no row of its own on purpose and falls back to this one - hierarchical lookup doing its job. It carries every accumulation feature at once: AggregationWindow 0.6, so an attacker's hits climb one number on that attacker's own screen, and ScopeTag Status.Stagger with ScopeSummaryStyle Damage.Summary. Every more specific Damage.* row declares no window of its own and inherits this one, so Fire, Blocked and Poison hits all accumulate here
Damage.SummaryImmuneCritPunchThe scope's payoff, and nothing else sends it. Its format is a plain <{ValueStyle}>{Value}</>. It sets bInheritAggregation to false, which keeps the payoff out of the ledger it is closing - a payoff that inherited its own root would feed the run it just paid off
Damage.Type.FireFireArcOutThe simplest child row: it changes the color and nothing else
Damage.Type.IceIceShiverTwo styles pointing at one preset
Damage.Type.LightningLightningShiverThe other half of that pair - retune Shiver once and both elements follow
Damage.Type.PoisonPoisonDriftNo window of its own, deliberately: poison ticks join Damage's ledger and climb the same number as everything else hitting that target, instead of stacking a column of 3s. Author an AggregationWindow on this row and poison splits off into a number of its own again - that one field is the whole choice
Damage.HealHealPulsePlacementPreset TargetBelow, so healing reads under the target while damage reads over it. It is also the one child row with a root of its own - AggregationWindow 0.6 - so healing climbs a separate number instead of adding into the damage total
Damage.Result.MissMissHoverA word rather than a number: {Value} never appears, so the format string carries the text
Damage.Result.ImmuneImmuneHoverShares Hover with Miss, and PlacementPreset ScreenNotification sends it to the HUD instead of over the target
Damage.Result.BlockedDefault + Block prefixSinkThe number falls away, because the blow did not land. A fully blocked hit is worth 0 and still joins Damage's ledger: it bumps the hit count and punches the running number without changing the figure or its look
Damage.Result.AbsorbedDefault + Absorb prefixSinkShares Sink with Blocked - the third pair of styles on one motion
Damage.Result.Crit (modifier)CritCritPunch overrideA modifier, not a style: 1.4x scale and a ! wrapper layered onto whatever style matched, so one row covers every damage type
Damage.Result.ShieldBroken (modifier)Block (on its companion)Hover (on its companion)A pure companion pop: every field that would change the number is left at its default, bSpawnCompanionPop is on, and the CompanionPop reads "Shield Broken" at offset (0, -36) so it sits above the figure. A shield breaking happened because of the damage rather than being a property of it, so it reads better as a second pop than as a wrapper
Advanced rule, Priority 10HealCritPunchA critical heal: the rule picks the base outright (plus sign, below the target, CritPunch) instead of the Damage.Heal row. The Crit modifier still layers on top - modifiers always apply after the base is chosen
Advanced rule, Priority 5Default + Block prefixSinkAn elemental hit that was blocked: blocked wins over the element, which the tag map cannot decide because both keys sit at the same depth
It never overwrites your work
A key you already authored is left exactly as it is, any example whose tag your project does not carry is skipped, and the two advanced rules are appended only when AdvancedRules is empty - so pressing the button twice does nothing the first press did not already do. The Damage tags are shipped by CrimsonCombatText so the set resolves in a project with no damage taxonomy of its own; swap them for yours and the same rows keep working.
Heal and Immune need a placement set
Those two rows name PlacementPreset tags, so point the registry's PlacementSet at a placement set - the shipped DA_CrimsonCombatTextPlacements (/CrimsonCombatText/) already carries both rows - or validation (step 6) reports them as errors and both pops fall back to the default anchor.
Verify
The tree fills with the eleven style keys above under Styles, Damage.Result.Crit and Damage.Result.ShieldBroken under Modifiers - the second badged companion - and two rows under Advanced Rules badged 10 and 5. Selecting Damage shows AggregationWindow 0.6, ScopeTag Status.Stagger and ScopeSummaryStyle Damage.Summary - which the tree badges as agg 0.60s, scope:Stagger and summary:Summary. Damage.Type.Poison shows an empty AggregationWindow and is badged ledger:Damage; Damage.Summary shows bInheritAggregation unticked and is badged no ledger.

2. Add a style or a modifier

Use the toolbar: Add Style, Add Modifier, Add Rule, Duplicate (copies the selected row to a new key) and Delete. Adding or duplicating opens a tag picker for the new key. Every edit is a transaction, so Ctrl+Z puts back a row you deleted.

Style and modifier keys are YOUR tags
This picker is deliberately unfiltered. Styles, Modifiers and ScopeTag match the tags your game already sends - Damage.Fire, Crit, State.Stagger - so constraining them would mean re-tagging your damage system to use the plugin. Only the plugin's own subtrees (CrimsonCombatText.Motion, CrimsonCombatText.Placement, CrimsonCombatText.Filter) are restricted.
Verify
The new key appears in the tree, sorted into place, and is selected with an empty style in the details pane. Ctrl+Z removes it again and the tree follows.

3. Fill the fields from dropdowns

Seven fields on a style, and three on a modifier, are identifiers that have to match something authored elsewhere. Each is a dropdown of what actually exists:

FieldOnOffers
PrefixStyleTag, ValueStyleTagStyleRow names across the registry's TextStyleTables (step 4)
AnimationNameStyleAnimations on the widget at WidgetClassPath in Project Settings -> Crimson -> Crimson Combat Text
MotionPresetStylePreset tags in the registry's MotionSet
PlacementPresetStyleThe PlacementId of every row in the registry's PlacementSet
ScopeSummaryStyleStyleKeys of this registry's own Styles map
ValueStyleTagModifierThe same rich text row names, enabled by the inline override checkbox
MotionPresetOverrideModifierThe same motion preset tags
PlacementPresetOverrideModifierThe same placement ids

Every list is gathered fresh each time you open a combo, so a preset you just added in the motion set is offered immediately. (None) is always the first entry, because clearing a field has to be as easy as setting one, and a text style row's tooltip names the table that defines it.

Amber means it does not exist
A value that no longer matches anything is still shown - in amber, marked (not in list) - rather than silently replaced. That is a typo or a row you deleted, and at runtime it falls back (to the inline motion, to the default anchor, to the widget's default text style) instead of doing what the field says.
An unconfigured source is not an error
With no MotionSet assigned, no PlacementSet, no TextStyleTables or no widget class, the field falls back to its ordinary editor plus an amber hint naming exactly what to set. Nothing is hidden and nothing is blocked - you can always type a value the dropdown has no way to know about.
A companion pop is a whole style
Tick bSpawnCompanionPop on a modifier and a complete FCrimsonCombatTextStyle opens up beneath it - its own format string, text rows, motion preset, placement and aggregation window, each with the same dropdowns and the same validation as a Styles row. Leave every other field of the modifier at its default and you get a pure companion: a second pop beside the number rather than a change to it. See Concept: Style Resolution, Motion & Rendering -> Companion pops.

4. List your rich text table so row names can be picked

Select Sets in the tree and add the rich text style table your combat text widget uses to Text Style Tables (Authoring) - DT_NumberPop_TextStyle (/CrimsonCombatText/) if you are using the shipped widget. Its row names then fill the PrefixStyleTag / ValueStyleTag dropdowns, and validation can check them.

Editor-only, and it changes nothing at runtime
TextStyleTables is authoring convenience and is never read when the game runs. A pop resolves its row names against the table its own RichTextBlock carries in TextStyleSet - listing the table here only teaches this asset which names are legal. Point it at the same table the widget uses, or the dropdown will offer names the widget cannot draw.
Verify
ValueStyleTag on a style now opens a list containing your table's row names (Default, Crit, Heal, Fire, Ice, Poison, Lightning, Miss, Block, Immune and Absorb for the shipped table) instead of a blank name field. Clear the array again and the field reverts to a plain name box with an amber hint.

5. Preview the pop, in either editor

The registry editor's Pop Preview shows the pop itself, not just its path. It creates the widget from WidgetClassPath inside the editor and drives it through the same ApplyCombatText and UpdateView calls the game uses, so what you see is the rich text with the widget's own style table, the motion the row resolves to (the MotionSet preset when MotionPreset is set and the set has that row, otherwise the inline Motion), the value-emphasis scale and tint, and the widget animation named by AnimationName when the widget has one. Its inputs are a Value and a Concurrent hits count; a row that accumulates - its own window or an inherited one - shows one merged total instead of a fan, holding at MergeHoldAlpha and punching on a cycle exactly as it will in game, no rewind and no restart. Select a modifier and it previews that modifier layered on the Default Style - unless the modifier spawns a companion pop, in which case it previews the companion, which is the only pop that modifier actually produces. Placement is not simulated - the crosshair is the anchor. With no widget class configured, it falls back to a Slate rich-text render using the registry's first Text Style Table.

To change how a motion feels, edit the preset itself: double-click the motion set. It opens the same way - a tree of preset tags, details for the selected preset, and an animated preview - with a toolbar of Add Preset, Duplicate, Delete and Add Built-Ins, and a tag picker filtered to CrimsonCombatText.Motion.

Screenshot pendingImages/CrimsonCombatText/howto_motion_set_editor.png
The motion set editor: preset tree, the selected preset's fields, and the animated preview showing the fan and the end-height ladder.
Verify
Selecting a style whose MotionPreset is CrimsonCombatText.Motion.ArcOut previews an arc; clearing that preset previews the row's inline Motion instead. In the motion set editor, raising RiseStaggerPerSlot visibly ladders the preview's end heights - see How-To: Author Motion Presets.

6. Validate the asset

Right-click the registry in the Content Browser -> Asset Actions -> Validate Assets (a project-wide validation pass runs the same checks). It catches what a tag map cannot:

CheckResult
MotionPreset / MotionPresetOverride set, but there is no MotionSet - or it is not a row in itError
PlacementPreset / PlacementPresetOverride set, but there is no PlacementSet - or no row carries it as its PlacementIdError
ScopeSummaryStyle that is not a key in StylesError
ScopeSummaryStyle naming a row that inherits back into the same ledger root - a payoff that would re-enter the run it closesError
PrefixStyleTag / ValueStyleTag that no listed table definesWarning

Every message names where it came from - Styles[Damage.Fire], DefaultStyle, Modifiers[Crit], AdvancedRules[0].Style - and which field is wrong, so you can go straight to that row in the tree.

Row names are checked only when you listed a table
An empty TextStyleTables means "not set up", not "every row name is wrong", so row names are skipped and the pass says so once as an info message. The summary-style error is about a real loop: a payoff that inherits the root it is closing would feed the very run it just ended, so give the summary row bInheritAggregation false or a window of its own.
Verify
Point a style's MotionPreset at a preset you then delete from the motion set, revalidate, and the error names that style's key. Fix it from the dropdown and the pass comes back clean.

7. Confirm it resolves the way you authored it

Validation proves the identifiers exist. It does not prove the right row wins - that is tag resolution, and it is otherwise invisible.

In PIE, open the Dev Tools (Tools -> Combat Text Dev Tools, or Crimson.CombatText.DevTools) and use the Resolver tab: type a tag container and it names the winning Styles row, the Modifiers that layered on and in what order, the motion that came out, the ledger root the row accumulates under (its own, inherited from a named ancestor, or none) and which figure it shows. See How-To: Test Pops with the Dev Tools.

To do the same from a graph: Make Gameplay Tag Container (your event's tags) -> Resolve Style (Target = a reference to your registry asset), then break the returned struct and read Style Key and Style -> Motion Preset.

Verify
Resolving Damage.Fire + Crit names your Damage.Fire row (or its closest authored parent) and shows the Crit modifier applied, with the motion preset the modifier overrode to. Resolution reads only the asset, so the server resolving a hit's ledger root and a client resolving its look reach the same answer - what you see here is what every player gets.

See also

  • How-To: Author Motion Presets - the two new end-height fields, and shaping curves
  • How-To: Test Pops with the Dev Tools - the Resolver tab, and firing bursts at a target
  • Concept: Style Resolution, Motion & Rendering - the six resolution steps, and the end-height ladder
  • API Reference - UCrimsonCombatTextRegistry, FCrimsonCombatTextStyle, FCrimsonCombatTextStyleModifier