How-To: Write Your Own Filter (Optional)

Goal: add a visibility rule the three built-in classes cannot express - "hide chip damage", "only show text for my current target", "hide numbers from my own summons". One override in Blueprint or C++, and the rule picks up a settings row and a dev-tools checkbox with no other wiring.

Prerequisites
You completed How-To: Filter Which Pops a Player Sees and have a UCrimsonCombatTextFilterSet assigned on your registry.

1. Declare a tag for it

Add one tag under CrimsonCombatText.Filter - Project Settings > Project > GameplayTags, or your own Config/Tags ini. The tag is the row's identity: it is the settings storage key and the key for the player's overrides, so renaming it resets whatever the player had chosen.

ini
GameplayTagList=(Tag="CrimsonCombatText.Filter.HideChipDamage",DevComment="Hides damage numbers below the player's threshold.")

2. Subclass and override Matches

Answer one question: does this rule describe this event? Return true and the chain applies your MatchResult (or DisabledResult when the player has the row off). Return false and evaluation moves to the next row. You never return a verdict yourself.

Content Browser > right-click > Blueprint Class > search CrimsonCombatTextFilter and pick it as the parent.

In the new Blueprint, open My Blueprint -> Functions, hover Override and choose Matches. In the override graph: Matches -> Break CrimsonCombatTextFilterContext -> Abs (float) on the broken-out Value -> Less Than (float) against the broken-out Scalar Parameter -> Return Node (Return Value = the comparison). You are answering does this rule describe this event? - never a show-or-hide verdict.

Then open Class Defaults and fill in FilterId (the tag from step 1) plus the MatchResult / DisabledResult pair from the table in step 3.

Rows must be stateless
A filter instance is shared by every pop that runs through the set - the same rule placements live by. Matches is const for that reason. Do not cache anything about one event on the object.

3. Fill in the row's identity

Add your class as a row on the filter set and set these. Everything except FilterId has a usable default.

PropertyWhy it matters
FilterIdRequired. The tag from step 1. Also the settings key and the override key
DisplayNameLabel shown in the settings row and dev tools. Empty falls back to the raw FilterId tag
DescriptionTooltip on the settings row, and the blurb in the dev tools
MatchResult / DisabledResultWhat a match means when the row is on, and when it is off
bUserConfigurableClear it for policy the player should not be able to switch off. The row then stays pinned to bEnabledByDefault
bHasScalarParameter + ScalarRange / ScalarStep / ScalarDefaultAdds a slider beneath the toggle. Read it in Matches from Context.ScalarParameter
Rename it without touching the asset
GetFilterDisplayName() asks UCrimsonCombatTextFilter::TagLabelResolver() first whenever something has bound it. Bind that static delegate (C++, (FilterId, Fallback) -> FText) to a shared, localizable label table, and every filter can be renamed there without opening the filter set. CrimsonCore binds it automatically to its project tag-label settings.

4. What the Context gives you

FCrimsonCombatTextFilterContext is built once per event, before the chain runs, and handed to every row unchanged.

FieldMeaning
InstigatorActor / TargetActorWeak pointers, already normalized to the avatar pawn. Frequently null on clients - always check
InstigatorRelationship / TargetRelationshipSelf / Ally / Enemy / Unknown, resolved once via your team hook. A null side is passed to that hook like any other and reads as Ally; Unknown means there was no viewing pawn at all
bViewerIsInstigator / bViewerIsTargetDirect identity checks against the viewing pawn
ContextTagsThe tags the pop was requested with, before style resolution
ValueThe pop's value, sign as the sender used it
EventWorldLocation / bHasEventWorldLocationServer-resolved anchor. Survives a null target
ViewingPawn / ViewingPlayerWho the text is being drawn for
ScalarParameterYour row's tunable - the player's override, or your ScalarDefault
Null instigators are normal
A null actor is not classified separately - it goes to the IsActorFriendly team hook like any other, and the hook answers friendly for a null side unless you say otherwise, so those events read as Ally and stay visible. This matters: a GAS effect context carries the ASC's OwnerActor as its instigator, which is null on any client where that actor is not relevant, and null outright for effects built without one. Treating those as their own relationship would make them match no row in the default chain and vanish into DefaultResult - silently, and indistinguishably from a broken plugin. Unknown is produced only when there is no viewing pawn at all.
Actors arrive normalized to the avatar
Both sides are run through UCrimsonCombatTextPlayerSettings::ResolveEventActor before the context is built. A GAS effect context carries the ASC's owner as its instigator - the PlayerState in the standard setup - while the target side is the avatar pawn. Comparing those directly would mean a player's own damage never reads as Self, so both are resolved to the pawn first, via IAbilitySystemInterface with a Controller/PlayerState fallback.
Keep it cheap
Matches runs on every incoming pop, on the game thread, before anything is spawned. The expensive work - the team query - is already done for you and cached in the Context; do not re-query it.

5. Place it in the chain

Add the row to the set and drag it to where it belongs. Position is behavior: a Hide rule near the top pre-empts everything below it, and a Show rule near the top wins before any restriction gets a look.

Verify
Run Crimson.CombatText.DevTools in PIE and open the Filters tab. Your row appears in order with its class name and description, a checkbox, and a slider if it has a scalar - with no change to the dev tools. It also shows up in the settings screen.

See also

  • How-To: Filter Which Pops a Player Sees - the chain, the verdicts, and the built-in rows.
  • How-To: Expose Pop Options as User Settings - how your row reaches the options menu.
  • API Reference - the full UCrimsonCombatTextFilter surface.