Search code examples
eventsluaworld-of-warcraft

How can I efficiently respond to ingame target selection events?


I come from a C#/Java background and have never touched Lua before.

I want the addon to show a message (default message window) that prints out the class of the target whenever I click on and target another player, and ONLY when I target a player. I have two files, SpeccySpecs.lua (contains the functions needed to handle the event) and SpeccySpecs.xml (contains the frame to run the function). Whenever I run the addon ingame, I am getting nil errors because my Core.lua file is returning nil when requiring the .xml file and my .xml file returns a nil from the OnTarget() function in SpeccySpecs.lua.

I have tried to solve this in multiple ways, one of which was by creating a local table and requiring it in Core.lua, where I'd eventually call the function inside the table, but it also returned nil. I've been using sites such as:

https://wowpedia.fandom.com/wiki/Events

https://wowwiki-archive.fandom.com/wiki/Event_API

https://www.lua.org/

But what I've tried simply hasn't worked, and I assume I'm forgetting something small in the Lua code.

SpeccySpecs.lua

local function getPlayerClass()
    local playerClassName = nil;
    local playerClassFilename = nil;
    local playerClassId = nil;

    if UnitClass("target") ~= nil
    then
        playerClassName, playerClassFilename, playerClassId = UnitClass("target");
        if playerClassName == 1 -- Warrior
        then message ("It's a warrior")
        elseif playerClassId == 2 -- Paladin
        then message ("It's a paladin")
        elseif playerClassId == 3 -- Hunter
        then message ("It's a hunter")
        elseif playerClassId == 4 -- Rogue
        then message ("It's a rogue")
     elseif playerClassId == 5 -- Priest
        then message ("It's a priest")
        elseif playerClassId == 6 -- Death Knight
        then message ("It's a death knight")
        elseif playerClassId == 7 -- Shaman
        then message ("It's a shaman")
        elseif playerClassId == 8 -- Mage
        then message ("It's a mage")
        elseif playerClassId == 9 -- Warlock
        then message ("It's a warlock")
        elseif playerClassId == 10 -- Monk
        then message ("It's a monk")
        elseif playerClassId == 11 -- Druid
        then message ("It's a druid")
        elseif playerClassId == 12 -- Demon Hunter
        then message ("It's a demon hunter")
        else message ("That class does not exist")
        end
    else message ("Please target a player")
    end
end

function OnTarget(self, event, ...)
    print(event)
    return getPlayerClass()
end

SpeccySpecs.xml

<UI>
    <Script file="SpeccySpecs.lua"/>
    <Frame name="SpeccyFrame">
        <Scripts>
            <OnEvent function="OnTarget"/>
        </Scripts>
    </Frame>
</UI>

Core.lua

PlayerClassFrame = require "SpeccySpecs.xml";
return PlayerClassFrame

Thanks in advance for the help, everyone.


Solution

  • Goes like this:

    make frame (yours is in xml) > OnLoad event handler > register for events > event fired > handle events

    1) You don't need the require or the core.lua:

    WoW has its own explicit ordered loading process that uses toc files and includes in xml.

    Your toc file probably looks like:

    # some stuff    
    
    MyAddon.xml
    

    Then your MyAddon.xml loads:

    <UI>
        <Script file="MyAddon.lua"/>
        <Frame name="MyAddonFrame">
    

    Which then loads the .lua file first, so that all the things in the .lua file are available to be referenced as the frame xml loads.

    2) You are missing registering for a specific event:

    For example:

    <OnLoad> self:RegisterEvent("PLAYER_ENTERING_WORLD") </OnLoad>
    

    So that you end up with something like:

    <OnLoad> self:RegisterEvent("PLAYER_ENTERING_WORLD") </OnLoad>
    <OnEvent> print("Event name " .. event) </OnEvent>
    

    You need to go find a specific event that deals with when something is targeted that comes as close as possible to fitting what you need.

    3) You need to pass the telemetry from the event to the handler function:

    <OnEvent> MyAddon:OnEvent(self, event, ...) </OnEvent>
    

    The self is the current frame, event is given to you hind the scenes as OnEvent here just becomes compiled into a regular Lua function and gets event passed to it, and ... is a Lua type of list used for unnamed parameters, which you can break out into Lua variables later.

    4) You need a repeatable way to expose your Lua code across files

    This needs to be in a way that does not clash with WoW or other addons.

    MyAddon = {}
    

    Is one way. This makes a single Lua table with a global unique name that you can stick functions in so they can be found elsewhere outside that one file.

    Anything in WoW that is not 'local' goes into a single environment Lua global name space across all addons and wow.

    5) You need to accept the parameters for telemetry from the event:

    And process the telemetry to eek out what you more specifically need

    function MyAddon:OnEvent(frame, event, ...)
        if event == "SOME_EVENT_NAME" then
            local _, name = ...
            if is this some player then
                MyAddon:SomePlayerDidSomething(name)
            end
        elseif event == "SOME_EVENT_NAME" then
        end
    end
    

    This should get you started for the basic addon process.


    After all that is working, then you need a way to make the business end more data driven, which will in theory make it more code-size and speed efficient.

    6) Add a table to lookup strings for ids:

    MyAddon.classNames = {
        [1] = "warrior",
        [2] = "paladin",
        [3] = "hunter",
    }
    

    See https://www.lua.org/pil/3.6.html

    Which will allow you to construct code more like this:

    local className = self.classNames[playerClassId]
    if className then
        message("It's a " .. className .. ".")
    end
    

    This should get you started overall. It's hard to find the right resource often to get you off the ground at the very beginning. You got pretty far.