Architecture
This page documents the internal structure of Sami's Trial Helper for contributors and anyone extending the addon.
File overview
| File | Role |
|---|---|
SamisTrialHelper.txt |
ESO addon manifest — declares metadata, API version, dependencies, and load order |
SamisTrialHelper.xml |
Defines the top-level GUI control (SamisTrialHelperTLC) |
STH_Core.lua |
Core logic: record management, message chunking, chat sending, event registration |
STH_UI.lua |
All HUD rendering: player buttons, background sizing, hover effects |
STH_Utils.lua |
Stateless utility functions shared across modules |
STH_Settings.lua |
LibAddonMenu-2.0 settings panel registration |
SamiDebug.lua |
Conditional debug printer |
SamisTrialHelper.lua |
Entry point: chat event handler, addon lifecycle, zone change handler |
All modules attach themselves to the shared SamisTrialHelperAddon global table (STH), which is created once and reused across files.
Load order
The manifest loads files in this order:
SamisTrialHelper.xml ← GUI controls must exist before Lua runs
STH_Core.lua ← defines STH table and core functions
SamiDebug.lua ← depends on STH.settings
SamisTrialHelper.lua ← registers ESO events, depends on all modules
STH_Utils.lua ← stateless helpers
STH_UI.lua ← depends on STH_Utils
STH_Settings.lua ← depends on LibAddonMenu-2.0
Data flow
EVENT_CHAT_MESSAGE_CHANNEL
│
▼
handleChatMessage() (SamisTrialHelper.lua)
│
├─ itemsNotCollected() → scans links, filters to uncollected set pieces
│
├─ existing record?
│ ├─ yes → append new links, update UI button in place
│ └─ no → create new record, rebuild all buttons
│
└─ STH.ui.createPlayerButton(s)
Module details
STH_Core.lua
State:
| Field | Type | Description |
|---|---|---|
STH.uncollectedItems |
table | Map of playerName → record. Each record holds playerName, itemLinks[], and requested. |
STH.pendingMessages |
table | Queue of { text, channel } objects for multi-chunk sends. |
STH.lastMessage |
string | Most recently drafted message (for debugging). |
STH.lastRequestRecord |
table | Record associated with the last SendGroupMessage call. |
Key functions:
STH:CreateUncollectedItemRecord(fromDisplayName, fromName, itemLinks)
: Constructs a new record table. Prefers fromDisplayName (the @account name) over the character name.
STH:RemoveUncollectedItemRecord(record)
: Removes a record from STH.uncollectedItems by player name.
buildMessageChunks(uncollectedItems, fromDisplayName) (local)
: Splits item links into groups where each assembled message stays within 350 characters. Returns an array of fully formatted message strings. The fixed overhead per message ("I need these items: " prefix and " from <name>. [SamisTrialHelper]" suffix) is subtracted from the budget before items are packed in greedily.
STH:SendGroupMessage(record)
: Builds message chunks, stores chunks 2+ in STH.pendingMessages, registers onChannelMessageSent on EVENT_CHAT_MESSAGE_CHANNEL if there are pending chunks, then calls CHAT_SYSTEM:StartTextEntry with the first chunk.
onChannelMessageSent (local)
: Fires on every EVENT_CHAT_MESSAGE_CHANNEL event. Filters to messages sent by the local player (GetDisplayName()). Pops the next entry from STH.pendingMessages and calls StartTextEntry with it. Unregisters itself when the queue is empty.
!!! warning "Event parameter order"
EVENT_CHAT_MESSAGE_CHANNEL delivers parameters in the order:
eventCode, channelType, fromName, messageText, isCustomerService, fromDisplayName.
fromDisplayName is the sixth parameter. Using the wrong index returns the boolean isCustomerService instead.
STH_UI.lua
State:
| Field | Type | Description |
|---|---|---|
ui.playerButtons |
table | Pool of hidden, reusable row controls. |
ui.activePlayerButtons |
table | Map of playerName → row control for currently visible buttons. |
ui.playerButtonCount |
number | Monotonically increasing counter used to generate unique control names. |
Row control fields (set at runtime on each row):
| Field | Description |
|---|---|
row.label |
CT_LABEL child showing the player name and item count. |
row.closeButton |
CT_LABEL child showing the orange "X". |
row.playerDetails |
The record from STH.uncollectedItems bound to this row. |
row.rowIndex |
The 1-based vertical position of this row in the window. Stored so that update calls (no explicit index) can keep the button in place. |
Key functions:
ui.createPlayerButton(playerDetails, rowIndex)
: Creates or recycles a row control for the given player. If rowIndex is nil:
- If the player already has an active button,
row.rowIndexis reused (preserves position on update). - Otherwise the count of current active buttons
+ 1is used (new button goes at the bottom).
ui.removePlayerButton(playerName)
: Hides the row, clears its data and rowIndex, returns it to the pool, and calls resizeBackground. Hides the whole window if the last button was removed.
ui.resizeBackground()
: Iterates ui.activePlayerButtons, finds the maximum row width, and sets the background to maxWidth + 16 wide and 20 + 30 * count + 8 tall. Re-anchors the title label to the centre of the new background.
ui.clearPlayerButtons()
: Moves all active rows back to the pool in one pass and hides the window.
STH_Utils.lua
Stateless helpers accessible at STH.util:
| Function | Description |
|---|---|
util.replaceCharAt(str, pos, char) |
Returns str with the character at pos replaced by char. |
util.makeBracketLink(itemLink) |
Converts a raw ESO item link to bracket display form by setting byte 3 to "1". |
util.colorText(text, hex) |
Wraps text in ESO colour escape codes using a hex colour string. |
util.formatTime(sec) |
Formats seconds as MM:SS. |
util.getCurrentZoneId() |
Returns the zone ID for the local player's current zone. |
util.tableContains(t, elem) |
Linear search; returns true if elem is a value in t. |
util.hexToRgb(hex) |
Converts a #RRGGBB or RRGGBB string to normalised r, g, b, a floats. |
util.rgbToHex(r, g, b) |
Converts normalised RGB floats to an uppercase hex string. |
SamiDebug.lua
Provides SAMID:Print(fmt, ...), which calls string.format(fmt, ...) and prints to the chat window only when STH.settings.enableDebug is true. No-ops silently otherwise.
GUI control hierarchy
SamisTrialHelperTLC (TopLevelControl — movable, clamped to screen)
├── SamisTrialHelperTLCLabel (CT_LABEL, defined in XML)
├── SamisTrialHelperTLCBackground (CT_TEXTURE, created in ui.init)
├── SamisTrialHelperTLCTitle (CT_LABEL, created in ui.init)
└── SamisTrialHelperTLCRow1..N (CT_CONTROL, created on demand)
├── label (CT_LABEL — player name + item count)
└── closeButton (CT_LABEL — "X", mouse-enabled)
Row controls are not destroyed when a button is dismissed; they are hidden and placed in ui.playerButtons for reuse.
Adding a new setting
- Add a default value to
STH.defaultsinSTH_Core.lua. - Add a LAM2 option descriptor to the
optionsDatatable inSTH_Settings.lua. - Read
STH.settings.<key>wherever the feature is needed. - Document the new option in Configuration.