How-To: Test Pops with the Dev Tools (Optional)
Goal: exercise the whole pop pipeline without real gameplay events - fire test broadcasts at any pawn or pop-target actor, live-edit the per-player filters, burst-test the pool cap, and inspect replication wiring - using the live Crimson.CombatText.DevTools window. Editor-only: the tool lives in the editor module and never ships.
Crimson.CombatText.DevTools.What each tab shows
| Tab | Shows | Use it to |
|---|---|---|
| Spawn | Searchable Target and Instigator combos listing every pawn in the server world (players, enemies, NPCs, summons) plus any actor carrying a `UCrimsonCombatTextTargetComponent` - player entries first, with a Refresh button (the list also re-scans automatically when a listed actor dies). Instigator offers None for the third-party path. Below: Value with an optional 50%-150% randomize, comma-separated context tags, Prefix/Custom text, and a Burst count (1-200). Two send buttons: Broadcast (server path) and Request direct (local path). Two more sections follow, Scope docked below and Apply gameplay effect stacked behind the test-event form: Scope (open and close an accumulation window on the target from the server - by hand, or by a replicated loose tag on its ASC) and Apply Gameplay Effect (apply any project UGameplayEffect to the target on the server). | Fire the real combat pairings without gameplay code: enemy damaging a player (Instigator = enemy, Target = you), player damaging an enemy, or player vs player. Broadcast exercises replication, per-player filters, and server culling on every client; the direct request queues on the selected context's subsystem only and skips the player-settings filter. The two groups take it further: drive a stagger window by hand, or apply a real GameplayEffect so the pops come out of your own damage path. |
| Filters | The selected context's local UCrimsonCombatTextPlayerSettings, live-editable: all six visibility toggles, widget scale, format override, max display distance - plus the configured heal/crit tags and which team hook is active (Blueprint override / C++ delegate / none). Offers an Add component now button when the component is missing. | Flip a filter and immediately re-broadcast to see it take effect - no options menu required. |
| Status | A project-settings snapshot (registry/widget paths, ServerCullDistance, the distance-scale curve path or (none - constant size), pool sizes), whether the server GameState has the replication component, the selected context's subsystem readiness with loaded assets and active/pooled/queued counts, and whether the client cull component is present on the local PlayerController. | Diagnose 'no pops' in seconds: unassigned assets, a missing GameState component, or a not-yet-loaded subsystem all show here. |
UCrimsonCombatTextReplicationComponent (the tab says so). That is the same wiring Quick Start step 5 sets up - the tool cannot substitute for it.The Scope group
The Spawn tab's Scope section opens and closes an accumulation window on the server by hand, so you can watch a ScopeTag style climb one running total and pay it off without waiting for a real stagger. Walk a whole window end to end:
- Pick a Scope tag. The picker is scoped to the loaded registry, so it offers the
ScopeTagandScopeSummaryStylevalues your styles actually name -Status.Staggerif you seeded the example styles. A scope is a single tag; select more than one and the group says which it is using. - Press Add loose tag (server, replicated). This puts the tag on the server copy of the target's
AbilitySystemComponentwithTagAndCountToAll- the GAS path, exercised the way it ships, and the path the server watches for the tag being removed. Without an ASC on the target, press Begin scope (server) instead: it callsBeginCombatTextScopeon the server GameState'sUCrimsonCombatTextReplicationComponent, which is the manual path a project without GAS uses. - Broadcast several hits at that target, tagged so they resolve to the scoped style (
Damagefor the example set). Watch the Running total readout climb. - Pick a Scope instigator - The server's player pawn 0, Selected candidate (the actor in the Spawn tab's Instigator combo), or None (the unknown-attacker row) - to choose which ledger row the readout shows. It selects a readout only; it changes nothing about what the server accumulates. The server keeps one row per attacker, so the readout prints that one row.
- Press Remove loose tag (server) (or End scope (server)) to close the window.
(scope is open). With the example set's default FadeWithMotion each hit re-pops the figure rather than pinning it. Removing the tag pays the run off with a pop styled by ScopeSummaryStyle (Damage.Summary for the example set), and the readout drops back to 0.Send hits from a second instigator and they land as plain pops beside your number instead of joining it - a running total is personal. That attacker's payoff goes to their client, not yours.
The two server buttons call the same API a project without GAS uses. It lives on the GameState's replication component and is authority-only:
Get Game State -> Get Component by Class (CrimsonCombatTextReplicationComponent) -> Begin Combat Text Scope (Target = the actor, Scope Tag = your state tag), later -> End Combat Text Scope with the same pair. Both run on the server: call them from a GameMode, a server-side ability, or any graph gated on Has Authority. On a client they warn and do nothing. Get Combat Text Scope Total is a pure node returning what one attacker's row holds so far; its Instigator pin is required - pass the attacker, or leave it empty to read the unknown-attacker row.
The Apply Gameplay Effect group
A synthetic broadcast proves the registry resolves. It does not prove your damage code sends the tags you think it does. The Apply gameplay effect section (stacked behind the test-event form on the Spawn tab) applies any UGameplayEffect in the project to the target, on the server, so the pops come out of your real damage path instead of the tool's.
- Pick a Target (and optionally an Instigator) in the Spawn tab's combos. The instigator falls back to the target, so a self-inflicted test still carries a source and the filter chain sees a relationship rather than
Unknown. - Choose an Effect from the searchable list - it enumerates every
UGameplayEffectclass and Blueprint in the project - and set a Level. Refresh re-scans after you author a new one. - If the effect reads its magnitude from a SetByCaller, type that tag into the SetByCaller box and set the magnitude beside it. Leave it empty for effects that carry their own magnitudes.
- Press Apply to target (server). The spec is built on the server's
AbilitySystemComponentand applied to self; the result line names the effect, the level, the target and the magnitude it set.
Applied [...] at level 1 to [...], and pops appear on every client the filter chain admits. A red Apply failed line names the reason instead: the class would not load, the target has no AbilitySystemComponent, the effect produced no spec, or the SetByCaller text is not a registered gameplay tag.Damage.Type.Poison - that row declares no window of its own, so the ticks join Damage's ledger and climb one number instead of stacking a column of 3s. Apply it here rather than broadcasting bursts, because the point is that your effect's period and the root style's window interact. On the full stack, CrimsonCore ships GE_CrimsonDamage_Poison_Periodic for exactly this and its damage effects read SetByCaller.Damage.Base (CrimsonCore - requires all Crimson plugins).Testing recipes
| Recipe | Steps | Expect |
|---|---|---|
| Verify a filter toggle | Run 2+ players. On the Spawn tab set Instigator to another player and broadcast; then on the Filters tab (client context) turn Show ally damage dealt off and broadcast again. | The first broadcast pops on that client; the second is filtered out. Set Instigator to None to exercise the third-party/null-instigator path instead. |
| Verify incoming vs dealt damage | Set Instigator to an enemy pawn and Target to your player pawn, broadcast; then swap them (you hit the enemy) and broadcast again. | The first pop is governed by Show incoming damage (always-see-what-hits-you), the second by Show my damage dealt - toggle each on the Filters tab to confirm. |
| Verify the MaxActiveTexts cap | Set Burst count to 100+ and broadcast (or request direct). | Active pops never exceed MaxActiveTexts (the Status tab shows the live active/pooled counts) - the oldest pops are recycled early instead of the widget count growing. |
| Verify server culling | Set ServerCullDistance low in Project Settings, run 2 clients far apart, and broadcast at one of them. | Only the nearby client shows the pop; the Status tab on each client reports whether the client cull component is present (culled path) or absent (multicast path). |
See also
- How-To: Filter Which Pops a Player Sees - what each Filters-tab knob does.
- How-To: Cull Pops on the Server - the culled delivery path the recipes exercise.
- How-To: Accumulate Damage Over a Stagger Window - what the Scope group is driving, and how to author it.
- Concept: Style Resolution, Motion & Rendering - the pool guards the burst recipe stresses.