How to get a PreHook or PostHook to work? - ESOUI
Thread Tools Display Modes
07/06/21, 09:28 PM   #1
AddOn Author - Click to view addons
Join Date: Jul 2020
Posts: 4
How to get a PreHook or PostHook to work?

So I have a simple problem I'd like to solve.

When in Battlegrounds, I'd like the scoreboard to show both the account name and the character name, not just one or the other.

So a player would show up in the scoreboard as:
@someplayer (Some Character)

The battleground LUA code (https://esoapi.uesp.net/100035/src/i...board.lua.html) contains the following:
function Battleground_Scoreboard_Player_Row:UpdateRow()
    local data = self.data
    local primaryName = ZO_GetPrimaryPlayerName(data.displayName, data.characterName)
    local formattedName = zo_strformat(SI_PLAYER_NAME, primaryName)
That ZO_GetPrimaryPlayerName returns either the account name or the character name. That function is used in a lot of places, so I don't want to hook that.

If I wanted to just hook the Battleground_Scoreboard_Player_Row:UpdateRow function so that I could replace the call to ZO_GetPrimaryPlayerName with my own function, how do I do that?

Specifically, what I'm unclear on is in my function that is called, how do I have access to the data that the real Battleground_Scoreboard_Player_Row:UpdateRow function has? Because my function would replace that call to ZO_GetPrimaryPlayerName with my own code.

I hope this makes sense. This is opaque as there isn't much documentation.

  Reply With Quote
07/07/21, 01:22 AM   #2
Super Moderator
Baertram's Avatar
WoWInterface Super Mod
AddOn Author - Click to view addons
Join Date: Mar 2014
Posts: 3,687
You need to find out what the "object" the "class" Battleground_Scoreboard_Player_Row is referencing to.
If e.g. Battleground_Scoreboard_Player_Row = ZO_Object or ZO_SubClass there will be some kind of global object like
BATTLEGROUND_KEYBOARD.list or similar where you will find the rows in.
e.g. see the class def
e.g. https://github.com/esoui/esoui/blob/...board.lua#L665

Most times it either defined in the XML files via the OnInitialization or within the lua file. at the bottom of the class/file
BATTLEGROUND_KEYBOARD = ZO_Battleground_Scoreboard_Player_Row:New(control) or similar.

Unfortunately it is using an object pool (a pool of controls that will be re-used, as the scrolling is not creating new controls but re-using other ones created before. e.. show 10 rows = create 10 controls. On scrolling re-assign row 11 to 1, 12 to 2 and so on.

There does not seem to exist and object variable you could hook into as all is local.
But the factory function PlayerRowFactory of the control pool is used within
So the created object of Battleground_Scoreboard_Alliance_Panel will have the rows within self.sortedPlayerRows
And this alliance panel will be created as a loop here:

And assigned to self.alliancePanels

where self = Battleground_Scoreboard_Fragment
And the global for that fragment is assigned here:
BATTLEGROUND_SCOREBOARD_FRAGMENT = Battleground_Scoreboard_Fragment:New(control)

So BATTLEGROUND_SCOREBOARD_FRAGMENT.alliancePanels contains the panels of the alliances.
And each alliancePanel should have a list of sortedPlayerRows.
You can use merTorchbug or zgoo addons to inspect the BATTLEGROUND_SCOREBOARD_FRAGMENT and click throught the attributes and tables below, e.g. the __index shows you the functions and attributes of the "classes" it derives from.

So you can check what each panels in BATTLEGROUND_SCOREBOARD_FRAGMENT.alliancePanels sortedPlayerRows looks like, and if the function
"UpdateRow" is given there.
If this is the case build some loops like this in the end

Lua Code:
  1. for panelIndex, panelData in ipairs(BATTLEGROUND_SCOREBOARD_FRAGMENT.alliancePanels) do
  2.    for rowIndex, rowData in ipairs(panelData.sortedPlayerRows) do
  3.      --As the rows are a control pool and get "re-used" the hooks should not be done twice on the same rows!
  4.      if not rowData.postHookWasDone then
  5.         SecurePostHook(rowData, "UpdateRow", function(rowControl)
  6.            --Change the stuff you want to change at the row
  7.         end)
  8.         rowData .postHookWasDone = true
  9.      end
  10.    end
  11. end

Could be that rowData contains a .control which actually is teh control of the row and you need to posthook into than then, instead of rowData.

Call these hooks on start of your addon or via a callback function to BATTLEGROUND_SCOREBOARD_FRAGMENT:StateChange
See the wiki:

You could also try to use a handler of the row to set the hook at
rowData (or rowData.control) :SetHandler("OnEffectivelyShown", function(rowControl)
--do your changes here
You could change rowControl.data.displayName or rowControl.data.characterName then to contain both, char and displayName according to your needs.
The normal UpdateRow function would use this then via ZO_GetPrimaryPlayerName and just output both.
But it could be that some other functions of the row will just overwrite your changes again then, as they get called after the handler OnEffectivelyShown was fired.

But also make sure the handler will only be added once as the controls are re-used via the control pool!

Maybe this all is not needed and you could even directly posthook into the "class" function like this:
SecurePostHook(Battleground_Scoreboard_Player_Row, "UpdateRow", yourcallbackfunc)

Sometimes ZOs also provides global funcitons like ZO_BattlegroundKeyboard_OnRowUpdate which reference these lines directly, so you are able to hook these directly
SecurePostHook("ZO_BattlegroundKeyboard_OnRowUpdate", yourcallbackfunc)
e.g. there exists ZO_Battleground_Scoreboard_Player_Row_OnMouseDown

Check the source files of the battleground how it is build and how to find the object to hook into:

Last edited by Baertram : 07/07/21 at 01:28 AM.
  Reply With Quote
07/07/21, 08:34 AM   #3
AddOn Author - Click to view addons
Join Date: Jul 2020
Posts: 4
Baertram, I greatly appreciate your detailed response.

I'm still digesting it, but it's clear that I would have been flailing trying to figure out how this works, so thanks so much for sharing your knowledge generously!
  Reply With Quote
07/08/21, 08:44 AM   #4
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 35
You can try this approach instead of hooks:

Lua Code:
  1. local originalFunction = Battleground_Scoreboard_Player_Row.UpdateRow
  2. Battleground_Scoreboard_Player_Row.UpdateRow = function(self)
  3.     -- Put any code you like here, call the original function if needed...
  4.     local data = self.data
  5.     originalFunction(self)
  6.     self.nameLabel:SetText(data.displayName + data.characterName)
  7. end

I don't even know if it'll work, but theoretically it should Unfortunately, hooks have very limited use, so if you want to change ZOS code, then you'll probably need to do it the "dirty" way.
  Reply With Quote
07/08/21, 11:40 AM   #5
Super Moderator
Baertram's Avatar
WoWInterface Super Mod
AddOn Author - Click to view addons
Join Date: Mar 2014
Posts: 3,687
Yes, it should work as well. But I'd always prefer the secure post hook instead of overwriting the code in total.
It should do the trick to change the contents of the name label without breaking other code that prehooks the same variable e.g.
Depending on the load order of the addons you use this is more stable, or at least does not break other addons.

But depending on the use case andy.s is correct. You often cannot achieve the stuff you want with a hook and need to overwrite the functions too.
  Reply With Quote

ESOUI » Developer Discussions » General Authoring Discussion » How to get a PreHook or PostHook to work?

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off