Skip to content

Bosses

Per-kind companion to Mod hooks (“Enemies & bosses”, “level-load pipeline”). Scope: one custom boss encounter — a tagged enemy with an oCDtBossTimerUiControllerEntityCpnt component, wired into the existing named-event spawn fabric, guaranteed to drop a reward.

Summary — bosses are tagged enemies with a controller component

  1. oCDtBossTimerUiControllerEntityCpnt is the runtime component driving the boss-fight UI / HP bar. It is not in class_registry.json — it’s a component, not a UID-keyed serializable record (same shape as oCDtEntityCpntMagicalObject).
  2. oCDtBossTimerUiControllerEntityCpntSettings is the authored settings container — what asset bytes describe. Its four embedded oCEntityCpntPicker slots are the fields a level designer fills in.
  3. The encounter is gated by oCGameNamedEvents named BOSS_FIGHTING_START / BOSS_ACTIVATED / BOSS_FIGHTING_STOP / BOSS_DEFEATED. Each string is CRC32-keyed at static init into a _DAT_* slot; FUN_14027fde0 is the master listener-binder wiring those keys to per-listener thunks.
  4. Bosses are otherwise vanilla oCDtEnemyDefinition records (UID 0x176debb7, size 0x350, library 0x1414118c0) — “is a boss” is a bit in the enemy’s oCCustomFlagList, not a separate class. Flag bits are unnamed in the binary ("Flag 0"..”Flag 63”` are the only labels).

The chain

oCDtEnemyDefinition data class (UID 0x176debb7)
+-- oCCustomFlagList tag list — one bit = "is_boss"
|
+-- oCDtBossTimerUiControllerEntityCpntSettings per-encounter cfg
+-- picker @ +0xf8 target enemy / arena anchor
+-- picker @ +0x138 intro / cinematic ref
+-- picker @ +0x250 music cue / FMOD event
+-- picker @ +0x310 post-kill reward (oCDtRewardDefinition)
|
v (resolved at level-load)
oCDtBossTimerUiControllerEntityCpnt runtime component (UI + HP bar + timer)
+-- oCEntitySpawner embedded at +0x68
+-- bool signals at +0x18 (is_active) / +0x38 (ever_activated)

Named-event keys: BOSS_FIGHTING_START_DAT_1412c0430, BOSS_ACTIVATED_DAT_1412bfcb8, BOSS_DEFEATED (string 0x140ef1788), BOSS_FIGHTING_STOP (string 0x140ef1798).

Runtime component layout

From the ctor + dtor. Mirrors oCDtEntityCpntHeroSpawner (same spawner-shaped pattern).

OffsetField
+0x00vftable (oIEntityCpnt first, then specialized)
+0x08..+0x10parent / scene-graph backrefs (0 at ctor)
+0x18EntityCpntValueSignal<bool>is_active (boss enters fight)
+0x20..+0x30signal body (listener list head/tail)
+0x38EntityCpntValueSignal<bool>ever_activated (first encounter)
+0x40..+0x50signal body
+0x64low-nibble-masked status byte (& 0xf0 cleared by ctor)
+0x68embedded oCEntitySpawnerwhat actually creates the boss entity
+0x70..+0x88spawner body; 0xffffffffffffffff is the unresolved-prefab sentinel
+0x90..+0xb8spawner state (parent entity backref, prefab cache)
+0xc0..+0xe8controller state — boss-fight HUD / HP bar context (unconfirmed)
+0xd8, +0xe8heap signal-listener pointers; released by the dtor when non-null
+0xe4, +0xf4ints — phase index / phase count (unconfirmed; inferred from the dtor’s matched pair of refcounted listeners)

Settings layout

The settings struct is the authoring record. Each picker is an oCEntityCpntPicker — the same “pick one named resource” primitive items use.

OffsetFieldNotes
+0x00 / +0x08primary / secondary vftablemultiple-inheritance ABI
+0xf8picker 1target entity — anchor to attach the boss prefab to
+0x100 / +0x108picker 1 name ptr / hash0x80000000 = unresolved
+0x118picker 1 resolved oCMetaClass*fixed up by the resolve pass
+0x138picker 2intro cinematic / intro entity (unconfirmed)
+0x158picker 2 resolved meta
+0x178scalar/array slot (no picker vftable)likely per-phase settings or HP cutoffs (unconfirmed)
+0x1b0array-tail pointerresolves to +0x278
+0x250picker 3music cue, likely over the FMOD event library (unconfirmed)
+0x290scalar/array slotpossibly the oCCustomFlagList of fight tags
+0x2c8picker target ptrresolved-meta sentinel
+0x310picker 4reward — the guaranteed-drop oCDtRewardDefinition
+0x330picker 4 resolved meta

Deserialize calls the sub-object reader six times, one per field. The sixth (+0x310) is gated on the cooked-asset version-tag test for 0x17e9a0ae, so the reward picker is a newer schema addition and may be absent on older records. A mod must emit version tag 0x17e9a0ae for the reward picker to deserialize.

How the boss actually appears

There is no direct factory call for bosses in the level-load resource loader. Instead:

  1. Stage 3 (Enemies settings loading) walks oCTLibrary<oCDtEnemyDefinition> and filters via EnemyDefInternal::SearchFilter + oCCustomFlagFilter. Survivors join the camp pool.
  2. Stage 12 (Generate enemy camps) fires a named event; camp-spawner components instantiate enemies. A boss enemy lands in the pool with the right tag, and the camp’s oCDtEnemyFlagListEntitySelectorToSpawnEntityCpntSettings picks it because its oCCustomFlagList matches.
  3. When the player approaches the arena, the attached controller wakes — its is_active signal flips, the engine raises BOSS_FIGHTING_START, and the listener-binder’s MapSceneContext closure runs.
  4. On death BOSS_DEFEATED fires and the reward picker at +0x310 dispatches through the reward-selector chain (Rewards).

So the “spawn the boss” hook is not a call — it’s the combination of a tagged enemy definition clearing the stage-3 filter, a controller component attached to that enemy or its arena anchor, and the existing camp machinery doing the creation at stage 12. Modders don’t fire any new event: with the boss tag and the component, the engine handles the rest.

What the “boss flag” is, concretely

The engine walks every enemy in the library and tests each against oCCustomFlagFilter; the camp’s selector carries its own oCCustomFlagList. A boss is an enemy whose bitfield has the bit the boss-arena camp filter demands. The exact bit index isn’t string-labelled, so in practice: read a vanilla boss’s asset oCCustomFlagList bitfield, and copy it onto the custom enemy.

Insertion recipe

  1. Enemy definition — clone a vanilla boss oCDtEnemyDefinition; set the resource path/name, HP/damage scalars, and the oCCustomFlagList bits to include the boss flag the stage-3 camp filter looks for.
  2. BossTimer settings — picker +0xf8 → arena anchor entity name, +0x138 → intro entity, +0x250 → FMOD music cue, +0x310 → reward definition, with version tag 0x17e9a0ae in the cooked stream.
  3. Component attach — at level load a controller must sit on either the boss enemy or its arena anchor; it reads its settings through the normal entity-component plumbing.
  4. Arena — the map’s camp must include a spawn tag matching the new enemy’s flag list, or route through a per-map selector patch.

Unknown fields fall back to clone-from-base: copy a vanilla boss’s bytes verbatim and patch only the offsets above, carrying a synthesized: {offset: value} map and a cloned_from field so the apply layer can audit which bytes are real schema and which are inherited.

Harder bosses vs “all moves at once”

  • Harder bosses (HP / damage scaling) — reachable today. A boss is an enemy entity: scale it via the difficulty levers (Game Difficulty 0x18700873, Camp Difficulty Modifier 0x187aaecf; see Game modifiers) or per-entity stat overrides. A “slider to 1000%” is thin UI over a value multiplier — no RE wall.
  • “All moves at once” — hard and unmapped. Ability sequencing lives in the boss behaviour tree plus per-ability controllers (cooldown / windup / phase gates), not a scalar. True simultaneity needs BHV-asset surgery or a per-tick ability-force-trigger hook, which is engine-AI and MP-determinism sensitive. The closest tractable approximation is driving ability cooldowns toward zero, if they surface as entity-value keys.