Event Handling and Communication Between Addons

Updated 21 October, 2026

Event Handling and Communication Between Addons

Nexus has a simple, string-based event system. Any addon can subscribe to an event by name and any addon can raise one, with an optional pointer to data. Nexus itself uses it to announce changes, the built-in ArcDPS integration uses it to pass on combat data, and addons use it to talk to each other without depending on each other's code. This guide covers subscribing, raising, targeting a single addon, designing your own events and the events that are available out of the box.

Subscribing to an event

An event handler takes a single void* argument, the event's payload. What it points to depends on the event:

void OnIdentityUpdated(void* aEventArgs)
{
	Mumble::Identity* identity = (Mumble::Identity*)aEventArgs;
	if (!identity) { return; }

	char msg[64];
	snprintf(msg, sizeof(msg), "Now playing %s.", identity->Name);
	APIDefs->Log(LOGL_INFO, "MyAddon", msg);
}

void AddonLoad(AddonAPI_t* aApi)
{
	APIDefs = aApi;
	APIDefs->Events_Subscribe(EV_MUMBLE_IDENTITY_UPDATED, OnIdentityUpdated);
}

void AddonUnload()
{
	APIDefs->Events_Unsubscribe(EV_MUMBLE_IDENTITY_UPDATED, OnIdentityUpdated);
}

Mumble::Identity is defined in the RCGG-lib-mumble-api header.

  • Events are identified by name only. You can subscribe to an event before anyone raises it, and before the addon that raises it is loaded.
  • Unsubscribe with the same name and the same function. Subscribing the same function twice means it is called twice.
  • Do not unsubscribe a handler from inside a handler of the same event. Do it in your unload function or from another place.

How events are delivered

Raising an event calls all of its subscribers immediately, one after another, on the thread that raised it. Events_Raise only returns once every handler has returned. This has a few consequences for handlers:

  • The payload is only valid during the call. If you need the data later, copy it.
  • Handlers may run on any thread. Nexus raises some events from the render thread and some from background threads, and ArcDPS combat events arrive on ArcDPS's thread. Protect shared data, and do not call ImGui from handlers.
  • Slow handlers slow down the raiser. A combat event handler that takes a millisecond delays ArcDPS and every addon after you, in every fight. Copy what you need, and process it elsewhere.

Raising events

Raise an event with a name and a pointer to its payload, or use a notification when there is nothing to send:

#define EV_MYADDON_TIMER_EXPIRED "EV_MYADDON_TIMER_EXPIRED"
#define EV_MYADDON_RESET         "EV_MYADDON_RESET"

struct TimerExpired_t
{
	uint32_t    TimerID;
	const char* Name;
};

void OnTimerExpired(uint32_t aID, const char* aName)
{
	TimerExpired_t payload{ aID, aName };
	APIDefs->Events_Raise(EV_MYADDON_TIMER_EXPIRED, &payload);
}

void Reset()
{
	APIDefs->Events_RaiseNotification(EV_MYADDON_RESET); /* Payload is nullptr. */
}

Because delivery is synchronous, the payload can live on the stack: it outlives every handler.

Targeted events

Sometimes an event is meant for one addon only, for example a reply to a request. Events_RaiseTargeted and Events_RaiseNotificationTargeted deliver an event only to the addon with the given signature. Nexus knows which addon a handler belongs to from the module its code lives in, so the receiver subscribes as usual.

A common pattern is request and reply. Addon A owns some data. Addon B asks for it, passing its own signature, and A replies only to B:

/* Addon A: answers requests. */
#define EV_MYADDON_REQUEST_STATUS "EV_MYADDON_REQUEST_STATUS"
#define EV_MYADDON_STATUS         "EV_MYADDON_STATUS"

struct Status_t
{
	uint32_t Version;
	bool     IsActive;
};

void OnStatusRequested(void* aEventArgs)
{
	if (!aEventArgs) { return; }
	uint32_t requester = *(uint32_t*)aEventArgs;

	Status_t status{ 1, s_IsActive };
	APIDefs->Events_RaiseTargeted(requester, EV_MYADDON_STATUS, &status);
}

/* In Load: */
APIDefs->Events_Subscribe(EV_MYADDON_REQUEST_STATUS, OnStatusRequested);
/* Addon B: asks for the status. */
void OnStatus(void* aEventArgs)
{
	Status_t* status = (Status_t*)aEventArgs;
	/* Copy what you need. */
}

/* In Load: */
APIDefs->Events_Subscribe(EV_MYADDON_STATUS, OnStatus);

uint32_t self = MY_SIGNATURE;
APIDefs->Events_Raise(EV_MYADDON_REQUEST_STATUS, &self);

Other addons subscribed to EV_MYADDON_STATUS do not receive B's reply. Use Events_RaiseNotificationTargeted(signature, name) for targeted events without a payload. If no handler of the target addon is subscribed, nothing happens.

The same pattern lets late subscribers catch up: when an addon loads after the data source, it requests the current state instead of waiting for the next change. The ArcDPS integration offers exactly this, see below.

Designing your own events

Events become part of your addon's public interface as soon as another addon uses them. A little care keeps them stable:

  • Document the payload. Publish the struct definitions and say how long the data is valid, ideally in a small header other developers can include.
  • Plan for change. Never change the layout of a published payload. Add a new event, or include a version or size field from the start so receivers can check what they got.
  • Use plain data. Fixed-size integers, plain structs and C strings work for every addon, including those written in other languages. Avoid C++ classes, STL containers and anything that needs a specific compiler.

Events suit notifications and one-off messages. For state that other addons should be able to read at any time, use a shared resource instead, and raise an event when it changes.

Events raised by Nexus

Event Payload Raised when
EV_MUMBLE_IDENTITY_UPDATED Mumble::Identity* The player identity in MumbleLink changed: another character, map, commander tag, interface size and so on. Also raised right after any addon loads.
EV_WINDOW_RESIZED None The game window or its buffers were resized. Read the new size from NexusLinkData_t.
EV_ADDON_LOADED uint32_t* signature An addon finished loading.
EV_ADDON_UNLOADED uint32_t* signature An addon was unloaded.
EV_VOLATILE_ADDON_DISABLED uint32_t* signature A volatile addon was not loaded because the game was updated.
EV_INPUTBIND_UPDATED None The user changed a keybind.

The first four are defined in Nexus.h. For the others, use the name as a string.

ArcDPS events

When ArcDPS is loaded, the built-in ArcDPS integration of Nexus forwards ArcDPS's combat API as events. Your addon does not need to be an ArcDPS extension, and it does not need to know how ArcDPS was installed.

Combat events

Event Payload Contains
EV_ARCDPS_COMBATEVENT_SQUAD_RAW EvCombatData* ArcDPS's squad combat callback (combat).
EV_ARCDPS_COMBATEVENT_LOCAL_RAW EvCombatData* ArcDPS's local combat callback (combat_local), covering everything in your area.

The payload wraps the arguments of ArcDPS's combat callback. The CombatEvent and AgentShort types are ArcDPS's cbtevent and ag, also available in the RCGG-lib-arcdps-api header:

struct EvCombatData
{
	ArcDPS::CombatEvent* ev;
	ArcDPS::AgentShort*  src;
	ArcDPS::AgentShort*  dst;
	char*                skillname;
	uint64_t             id;
	uint64_t             revision;
};

The meaning of every field is described in ArcDPS's own API documentation. These events arrive on ArcDPS's thread at a high rate in large fights, so copy what you need and return quickly.

Squad, self and target

The integration also tracks ArcDPS's agent updates and raises simpler events for them:

Event Payload Raised when
EV_ACCOUNT_NAME const char* Your own account name became known.
EV_ARCDPS_SELF_JOIN, EV_ARCDPS_SELF_LEAVE EvAgentUpdate* Your own character appeared or left, for example after a map change.
EV_ARCDPS_SQUAD_JOIN, EV_ARCDPS_SQUAD_LEAVE EvAgentUpdate* A squad member joined or left.
EV_ARCDPS_TARGET_CHANGED EvAgentUpdate* Your target changed.
struct EvAgentUpdate
{
	char      account[64];   /* Account name. */
	char      character[64]; /* Character name. */
	uintptr_t id;            /* Agent ID. */
	uintptr_t instanceId;    /* Instance ID, unique per map. */
	uint32_t  added;         /* Agent was added (1) or removed (0). */
	uint32_t  target;        /* Agent is the new target. */
	uint32_t  Self;          /* Agent is you. */
	uint32_t  prof;          /* Profession. */
	uint32_t  elite;         /* Elite specialization. */
	uint16_t  team;          /* Team. */
	uint16_t  subgroup;      /* Squad subgroup. */
};

If your addon loads after these events were raised, ask the integration to send them again. It replies with the regular events:

Raise this notification To receive
EV_REQUEST_ACCOUNT_NAME EV_ACCOUNT_NAME
EV_REPLAY_ARCDPS_SELF_JOIN EV_ARCDPS_SELF_JOIN
EV_REPLAY_ARCDPS_SQUAD_JOIN EV_ARCDPS_SQUAD_JOIN for every current squad member
EV_REPLAY_ARCDPS_TARGET_CHANGED EV_ARCDPS_TARGET_CHANGED
/* In Load, after subscribing: */
APIDefs->Events_Subscribe("EV_ARCDPS_SQUAD_JOIN", OnSquadJoin);
APIDefs->Events_RaiseNotification("EV_REPLAY_ARCDPS_SQUAD_JOIN");

The replies are raised shortly afterwards from a separate thread, not during your own call. They are broadcast to all subscribers, so make your handlers tolerate receiving the same member twice. If the data is not known yet, for example because you are not in a squad, there is no reply.

Unofficial Extras

If the Unofficial Extras ArcDPS extension is installed, its callbacks are forwarded as well. The payload types are defined in its Definitions.h.

Event Payload
EV_UNOFFICIAL_EXTRAS_SQUAD_UPDATE Pointer to a struct of UserInfo* and uint64_t count
EV_UNOFFICIAL_EXTRAS_LANGUAGE_CHANGED Language*
EV_UNOFFICIAL_EXTRAS_KEYBIND_CHANGED KeyBinds::KeyBindChanged*
EV_UNOFFICIAL_EXTRAS_CHAT_MESSAGE ChatMessageInfo*