Skip to content

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.rowIndex is reused (preserves position on update).
  • Otherwise the count of current active buttons + 1 is 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

  1. Add a default value to STH.defaults in STH_Core.lua.
  2. Add a LAM2 option descriptor to the optionsData table in STH_Settings.lua.
  3. Read STH.settings.<key> wherever the feature is needed.
  4. Document the new option in Configuration.