-
Notifications
You must be signed in to change notification settings - Fork 0
Runtime Adapters
Adapters are the third leg of LibCodex's hybrid load model. While bundled seeds and SavedVariables are static (loaded at startup, frozen during the session), adapters run during play and capture data from live game events.
For the registration API, see API Reference: Adapters. This page covers the built-in Runtime adapter and the patterns for writing your own.
An adapter is a function with the signature function(LC) ... end. You register it via LibCodex:RegisterAdapter(name, fn) from anywhere — your module file, an addon's OnLoad, even a ## OptionalDeps-loaded helper. Adapters run once at PLAYER_LOGIN via LC:RunAdapters() (called by the library's bootstrap event handler).
The LC argument is the library object. From there you reach modules with the typed accessors (LC:NPCs(), LC:Items(), etc.) and call :Add(entry) to push data in.
Adapters that need ongoing capture (most do) install their own event hooks during the initial run. The library doesn't manage that lifecycle — your adapter owns its event frame.
RunAdapters() wraps each call in pcall. An adapter that errors logs a chat warning, gets marked ran = true, and is not retried. Other adapters keep running. This is intentional: a misbehaving adapter shouldn't block the entire library load.
Adapters/Runtime.lua ships with the library and is registered under the name "Runtime". It captures the kind of data you'd otherwise need offline scrapes for:
| Event | What it captures |
|---|---|
NAME_PLATE_UNIT_ADDED |
NPC id, label, level, classification, faction side, current map position |
UPDATE_MOUSEOVER_UNIT |
Same as above, plus first-time GameObjects via tooltip |
PLAYER_TARGET_CHANGED |
Same as nameplate path |
PLAYER_FOCUS_CHANGED |
Same as nameplate path |
BAG_UPDATE_DELAYED |
Every itemID currently in player bags |
GET_ITEM_INFO_RECEIVED |
Drains async item-info requests and enriches Items entries |
LOOT_OPENED / LOOT_READY
|
Forward + reverse pointers: item-from-source, source-drops-item |
CHAT_MSG_LOOT |
Backup capture for items obtained via auto-loot AoE pulls |
COMBAT_LOG_EVENT_UNFILTERED |
Records most-recent kill GUID and position (used as loot fallback) |
QUEST_ACCEPTED |
Quest pickup point (mapID, x, y), faction side, chromie/expansion |
QUEST_TURNED_IN |
Marks the quest as turned-in |
QUEST_LOG_UPDATE |
Throttled refresh of every active quest entry (10s minimum) |
TAXIMAP_OPENED |
Walks every visible flight node, tags with player faction |
ENCOUNTER_START / ENCOUNTER_END
|
Boss encounter id/name/difficulty + per-character kill counter |
The adapter also performs an initial pass 2 seconds after login: a full bag scan, connected-realm sync, and a nameplate sweep. The 2-second delay lets WoW's APIs settle.
Runtime.lua includes a generic GUID decoder that classifies entities into npc, gameobject, or unknown. Both NPC and GameObject capture paths use it. GUIDs starting with Creature-, Vehicle-, or Pet- are treated as NPCs; GameObject- prefixes go to the GameObjects module.
Faction sides ("A", "H", "B") are inferred from UnitReaction relative to the player's faction. Friendly = same side, hostile = opposite, neutral = both. This is a heuristic; cross-faction zones can mislabel. Hand-curated entries with _handcrafted = true override.
GameObjects (chests, herb nodes, ore veins, mailboxes) aren't units, so UnitGUID and the nameplate system never see them. The adapter installs a TooltipDataProcessor post-call hook on Enum.TooltipDataType.GameObject (10.0.2+). On older clients without TooltipDataProcessor, the hook is a no-op.
The Runtime adapter reads settings from LibCodexDB:
-
autoScan(defaulttrue) — periodic nameplate sweep ticker. -
autoScanInterval(default5seconds, minimum2) — interval for the auto-scan ticker.
Both can be toggled at runtime:
local R = LibCodex.Runtime
R.SetAutoScan(true, 5) -- enable, 5-second interval
R.SetAutoScan(false) -- disable
print(R.IsAutoScanning()) -- true / falseFor debugging or just curiosity about what the Runtime adapter is seeing:
LibCodex.Runtime.SetVerbose(true)Or via slash command: /codex verbose on. Captures get logged to LibCodex.Log as they happen. Off by default.
The Runtime adapter exposes a few helpers for slash commands and other addons to call:
| Function | Notes |
|---|---|
R.ScanNow() |
One-shot scan: bags + every active unit token + nameplates + realms. Returns a report table with counts. |
R.ReadUnit(unit) |
Capture a single unit token ("target", "mouseover", etc.). |
R.ScanBag(bagID) |
Scan one bag. |
R.ScanAllBags() |
Scan bags 0..4. |
R.ScanNameplates() |
Walk every visible nameplate + boss frames + pet/pettarget. |
R.SyncRealms() |
Re-pull connected realms via GetAutoCompleteRealms. |
R.EnableFriendlyNameplates() |
Set the nameplateShow* CVars so friendly NPCs (vendors, quest-givers) show plates. |
In modern Retail, calling RegisterEvent on certain events from inside an addon-owned frame can trigger ADDON_ACTION_FORBIDDEN. Once an event is forbidden, repeated registration attempts pollute logs and can taint the secure execution path.
The Runtime adapter installs an ADDON_ACTION_FORBIDDEN watcher and persists offending event names to LibCodexDB.runtimeBadEvents. Future sessions skip those events silently. The adapter also checks C_EventUtils.IsEventValid (when available) before any RegisterEvent call — calling RegisterEvent with an unknown event name itself sets taint, even when wrapped in pcall.
The pattern, distilled to a sketch:
local LC = LibStub("LibCodex-1.0")
LC:RegisterAdapter("MyAddon.Quests", function(LC)
-- Anonymous frames: named frames can flag ADDON_ACTION_FORBIDDEN
-- in modern Retail. Anonymous is safer.
local f = CreateFrame("Frame")
f:RegisterEvent("QUEST_ACCEPTED")
f:SetScript("OnEvent", function(_, evt, questID)
local Quests = LC:Quests()
if Quests and type(questID) == "number" then
Quests:Add({
id = questID,
label = (C_QuestLog.GetTitleForQuestID
and C_QuestLog.GetTitleForQuestID(questID)) or nil,
sources = { "MyAddon.Quests" },
})
end
end)
end)Tag every entry your adapter pushes with a unique source name. That way, if a downstream consumer ever needs to filter or audit data by origin, your contributions are identifiable.
If you're enriching a single addon's local cache, just call Add directly from your own code. Adapters are for data you want LibCodex itself to retain across sessions and surface to other consumers. The persistence and merge plumbing all flow through the registered modules — anything you push via Add becomes part of LibCodexDB at logout.
LibCodex-1.0 © ChronicTinkerer · MIT License · Report an issue