Skip to content

cbtevent

cbtevent is the struct arcdps passes as the ev parameter of the combat / combat_local callbacks (see the combat callback reference).

It is not defined in the API README. The README’s only statement about it is:

ev parameter is cbtevent as in evtc documentation.

The authoritative layout lives in the separate EVTC combat-log format documentation (https://www.deltaconnected.com/arcdps/evtc/README.txt), which this page is sourced from. That document also states the struct is revision 1 (matching the revision parameter of the combat callbacks, “will most likely be 1”):

combat event logging (revision 1, when header[12] == 1). all fields except time are event-specific, refer to descriptions of events above

That last sentence is the key fact for reading this page: time is the only field the notes call out as generally stable across events — with three documented exceptions where it is reinterpreted as an inline data buffer rather than a timestamp:

State-change typetime is reinterpreted as
CBTS_BUFFFORMULA(float*)&time → float[9]: type, attribute1, attribute2, parameter1, parameter2, parameter3, trait_condition_source, trait_condition_self, content_reference
CBTS_SKILLINFO(float*)&time → float[4]: cost, range0, range1, tooltiptime
CBTS_INTEGRITY(char*)&time → char[32]: a short null-terminated message with the reason

Treat time as a timestamp only after ruling those three out. Every other field’s meaning depends on the value of is_statechange (which state-change type the event is) — the same byte offset carries different data for, say, CBTS_COMBAT versus CBTS_POSITION versus CBTS_BUFFINFO. The full per-event-type field mapping is on the statechange payloads page; this page documents the struct’s fixed C layout and the fields’ generic/most-common (CBTS_COMBAT) roles.

C declaration

typedef struct cbtevent {
uint64_t time; /* timegettime() at time of event */
uint64_t src_agent;
uint64_t dst_agent;
int32_t value;
int32_t buff_dmg;
uint32_t overstack_value;
uint32_t skillid;
uint16_t src_instid;
uint16_t dst_instid;
uint16_t src_master_instid;
uint16_t dst_master_instid;
uint8_t iff;
uint8_t buff;
uint8_t result;
uint8_t is_activation;
uint8_t is_buffremove;
uint8_t is_ninety;
uint8_t is_fifty;
uint8_t is_moving;
uint8_t is_statechange;
uint8_t is_flanking;
uint8_t is_shields;
uint8_t is_offcycle;
uint8_t pad61;
uint8_t pad62;
uint8_t pad63;
uint8_t pad64;
} cbtevent;

Total size is 64 bytes with standard x64 struct alignment (no explicit #pragma pack is documented, and none is needed — the field widths sum to 64 bytes with every member already naturally aligned). The trailing pad61–pad64 byte names correspond to 1-indexed byte offsets 61–64 of the struct, which is consistent with that natural-alignment layout — a useful sanity check when porting the struct, but the offset column below is derived from the field widths and standard C alignment rules, not stated explicitly by the source docs.

Field reference

OffsetC typeNameMeaning
0uint64_ttimetimeGetTime() at time of registering the event — except for CBTS_BUFFFORMULA, CBTS_SKILLINFO and CBTS_INTEGRITY, which reinterpret these 8 bytes as an inline data buffer (see the table above).
8uint64_tsrc_agentEvent-specific (see cbtstatechange mapping). For CBTS_COMBAT: source agent.
16uint64_tdst_agentEvent-specific. For CBTS_COMBAT: target agent.
24int32_tvalueEvent-specific. For CBTS_COMBAT: combined shield+health strike damage.
28int32_tbuff_dmgEvent-specific. For CBTS_COMBAT: combined shield+health buff damage.
32uint32_toverstack_valueEvent-specific. For CBTS_COMBAT: shield damage.
36uint32_tskillidEvent-specific. For CBTS_COMBAT: the damage skill id.
40uint16_tsrc_instidId of the source agent as it appears in-game at the time of the event. Out-of-range agents may still have this set even when src_agent is zero.
42uint16_tdst_instidId of the target agent as it appears in-game at the time of the event. Out-of-range agents may still have this set even when dst_agent is zero.
44uint16_tsrc_master_instidIf src_agent has a master (e.g. is a minion), equals the instance id of the master; zero otherwise.
46uint16_tdst_master_instidIf dst_agent has a master (e.g. is a minion), equals the instance id of the master; zero otherwise.
48uint8_tiffEvent-specific. For CBTS_COMBAT: friend/foe relationship, of enum iff. See enum reference.
49uint8_tbuffEvent-specific. For CBTS_COMBAT: whether the skill is a buff (as opposed to a direct strike).
50uint8_tresultEvent-specific. For CBTS_COMBAT: combat result, of enum cbtresult. See enum reference.
51uint8_tis_activationEvent-specific. Skill-activation state, of enum cbtanimation — used by CBTS_ANIMATIONSTOP.
52uint8_tis_buffremoveEvent-specific. Buff-remove kind, of enum cbtbuffremove — used by buff-remove events.
53uint8_tis_ninetyEvent-specific. For CBTS_COMBAT/buff-apply/-remove events: source is above 90% health.
54uint8_tis_fiftyEvent-specific. For CBTS_COMBAT/buff-apply/-remove events: target is below 50% health.
55uint8_tis_movingEvent-specific. For CBTS_COMBAT/buff-apply/-remove events: bit 0 set if source is moving, bit 1 set if target is moving.
56uint8_tis_statechangeWhich cbtstatechange enum value this event is — the discriminant that determines how every other field (except time) is interpreted. See enum reference.
57uint8_tis_flankingEvent-specific. For CBTS_COMBAT/buff-apply/-remove events: source is flanking target.
58uint8_tis_shieldsEvent-specific. For CBTS_COMBAT: damage was partially or wholly absorbed by barrier.
59uint8_tis_offcycleEvent-specific. For CBTS_COMBAT: target was downed at the time of the event.
60uint8_tpad61Padding for most event types; repurposed as event-specific data by several state-change types (e.g. a uint32_t “trackable id” spanning pad61–pad64 for several CBTS_* events). See enum reference for which.
61uint8_tpad62Padding for most event types; repurposed by some state-change types. Undocumented outside those specific mappings.
62uint8_tpad63Padding for most event types; repurposed by some state-change types. Undocumented outside those specific mappings.
63uint8_tpad64Padding for most event types; repurposed by some state-change types. Undocumented outside those specific mappings.

FFI mappings

These mirror the C declaration’s field order, widths, and (default, no explicit packing documented) alignment.

Rust

#[repr(C)]
pub struct CbtEvent {
pub time: u64,
pub src_agent: u64,
pub dst_agent: u64,
pub value: i32,
pub buff_dmg: i32,
pub overstack_value: u32,
pub skillid: u32,
pub src_instid: u16,
pub dst_instid: u16,
pub src_master_instid: u16,
pub dst_master_instid: u16,
pub iff: u8,
pub buff: u8,
pub result: u8,
pub is_activation: u8,
pub is_buffremove: u8,
pub is_ninety: u8,
pub is_fifty: u8,
pub is_moving: u8,
pub is_statechange: u8,
pub is_flanking: u8,
pub is_shields: u8,
pub is_offcycle: u8,
pub pad61: u8,
pub pad62: u8,
pub pad63: u8,
pub pad64: u8,
}

Python (ctypes)

import ctypes
class CbtEvent(ctypes.Structure):
_fields_ = [
("time", ctypes.c_uint64),
("src_agent", ctypes.c_uint64),
("dst_agent", ctypes.c_uint64),
("value", ctypes.c_int32),
("buff_dmg", ctypes.c_int32),
("overstack_value", ctypes.c_uint32),
("skillid", ctypes.c_uint32),
("src_instid", ctypes.c_uint16),
("dst_instid", ctypes.c_uint16),
("src_master_instid", ctypes.c_uint16),
("dst_master_instid", ctypes.c_uint16),
("iff", ctypes.c_uint8),
("buff", ctypes.c_uint8),
("result", ctypes.c_uint8),
("is_activation", ctypes.c_uint8),
("is_buffremove", ctypes.c_uint8),
("is_ninety", ctypes.c_uint8),
("is_fifty", ctypes.c_uint8),
("is_moving", ctypes.c_uint8),
("is_statechange", ctypes.c_uint8),
("is_flanking", ctypes.c_uint8),
("is_shields", ctypes.c_uint8),
("is_offcycle", ctypes.c_uint8),
("pad61", ctypes.c_uint8),
("pad62", ctypes.c_uint8),
("pad63", ctypes.c_uint8),
("pad64", ctypes.c_uint8),
]

Node.js (koffi)

const koffi = require("koffi");
const cbtevent = koffi.struct("cbtevent", {
time: "uint64_t",
src_agent: "uint64_t",
dst_agent: "uint64_t",
value: "int32_t",
buff_dmg: "int32_t",
overstack_value: "uint32_t",
skillid: "uint32_t",
src_instid: "uint16_t",
dst_instid: "uint16_t",
src_master_instid: "uint16_t",
dst_master_instid: "uint16_t",
iff: "uint8_t",
buff: "uint8_t",
result: "uint8_t",
is_activation: "uint8_t",
is_buffremove: "uint8_t",
is_ninety: "uint8_t",
is_fifty: "uint8_t",
is_moving: "uint8_t",
is_statechange: "uint8_t",
is_flanking: "uint8_t",
is_shields: "uint8_t",
is_offcycle: "uint8_t",
pad61: "uint8_t",
pad62: "uint8_t",
pad63: "uint8_t",
pad64: "uint8_t",
});

Layout corroboration

The offset table above (derived from natural alignment) is confirmed by independent implementations: community Rust bindings declare the identical #[repr(C)] layout, and at least one from-scratch reimplementation carries a compile-time test asserting sizeof == 64 and skillid at byte offset 36 — matching the derived table exactly.

One field nuance recorded by the community bindings (not in the official notes): for strike events, is_flanking values lie “in a range of 1 to 135 degrees where 135 is rear” — i.e. it is an angle indicator, not a plain boolean.

Reading a cbtevent in practice

Because every field but time is reinterpreted per event type, the first thing any consumer does is dispatch on is_statechange. If it is nonzero the event is a state-change; interpret its fields per the statechange payloads page. If it is zero the event is a CBTS_COMBAT event, and you branch on buff to pick which field carries the damage:

void handle(const cbtevent* ev) {
if (ev->is_statechange) {
/* state-change event: fields mean what the payloads page
says for this cbtstatechange value. */
dispatch_statechange(ev);
return;
}
/* is_statechange == 0 -> CBTS_COMBAT. */
if (ev->buff == 0) {
/* direct strike: value is the combined shield+health damage.
Only these results actually connected. */
if (ev->result == CBTR_STRIKE_DAMAGENORMAL ||
ev->result == CBTR_STRIKE_DAMAGECRIT ||
ev->result == CBTR_STRIKE_DAMAGEGLANCE) {
credit_strike(ev->src_agent, ev->value);
}
/* other results (block/evade/absorb/CC/etc.) are not
applied health damage — see the cbtresult table. */
} else if (ev->buff == 1 && ev->value == 0) {
/* condition/DoT tick: damage is in buff_dmg. buff == 1 with
value != 0 is a buff *application* (duration in value),
not damage. */
credit_buff_dmg(ev->src_agent, ev->buff_dmg);
}
}

Two fields worth calling out are is_activation and is_buffremove. Current builds deliver skill activation and buff removal as their own state-change types (CBTS_ANIMATIONSTART/CBTS_ANIMATIONSTOP and CBTS_BUFFREMOVE_* — the buff-remove statechanges were added in the may-2026 build), so they arrive on the is_statechange != 0 path above. Older logs may instead carry these as CBTS_COMBAT events with the is_activation/is_buffremove fields set — so if you parse historical logs, guard those fields in the combat branch too (as the combat-callback recipe does).

See the cbtresult table for the full result codes, and Reading damage from logs for the complete summation rules (barrier, minions, and which results to exclude).

See also