WoW:AddOn tutorial

From AddOn Studio
Revision as of 12:24, 8 May 2018 by 204.108.197.51 (talk)
Jump to navigation Jump to search

Template:NPOV I'll try to keep this brief. Note: The formatting alone makes this look longer than it really is. This AddOn will give you your character's (x, y) coordinates located under the Mini map and it will updated in real time as you move around. Minor programming experience with any language is recommended.

Begin: In a skeleton AddOn (bare bones) there are 3 files: an .xml, .toc, and .lua file all of which have the same name and located in a folder named the same which must be located in your Interface/AddOns folder. eg

C:\Program Files (x86)\World of Warcraft\Interface\AddOns\eCoordinates\

eCoordinates.toc

eCoordinates.xml

eCoordinates.lua

Open up any text editor. Create and save each of these files with their respected names and extensions. Note: If you have eCoordinates.toc.txt or similar, you will fail.


Warcraft will first read your .toc file as the program is first loading and logging into the server. Therefore, any edits must be saved prior to opening up WOW client for testing. If you edit this file at any point you must exit the game and reenter. The other two files may be edited while the game is open. Any changes you make to the .xml or .lua file can be seen after reloading [ /reload ] your screen.

Two dashes (--) are used to create comments in both .lua and .toc file. I may ask you to run something in your game client. It will be between 2 braces [ /reload ]. Copy and paste into your chat box while in-game

eCoordinates.toc contents Start

## Interface: 60200  -- Required. Game version. 
## Title: eCoordinates -- Name shown when viewing AddOn list.
## Version: 1.0        -- Version number (if there are multiple versions)
## Notes: Simple GPS  -- AddOn tooltip on loading screen. 
## Author: eSaith  -- Not required; author of 

 eCoordinates.lua   -- Required, lets WoW know what Lua file it's looking for.
eCoordinates.xml

[ /run print((select(4, GetBuildInfo()))) ] -- Will give you your Interface number. Replace 60200 with your current version

eCoordinates.xml contents

<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\..\FrameXML\UI.xsd">   
<Script File="eCoordinates.lua"/>
<Frame name="eCoordinates" parent="UIParent">
    <Scripts>
        <OnLoad function="eCoordinates_OnLoad"></OnLoad>
        <OnEvent function="eCoordinates_OnEvent"></OnEvent>
    </Scripts>
</Frame>
</Ui>

Bare bones: Line 1: <Ui xmlns="http://www.bliz" ... is required. All AddOns must have this. Don't read to deep into it; essentially just a magical code at the beginning of every XML file.

Line 2: <Script File="eCoordinates.lua"/> links this specific XML file to the specific eCoordinates.lua file. Later on, you may have multiple XML and LUA files referenced in the same XML file.

Line 3: Create a new frame. Everything between <Frame ...> and </Frame> give the frame its properties. 

Line 4: <Scripts> - Formally states there will be action occurring with this frame.

Line 5: <OnLoad ...></OnLoad> - First event to look for. When the AddOn loads (logging in, reloading, etc) call the associated function ("eCoordinates_OnLoad") found in the .lua file (line 2, eCoordinates.lua)

Line 6: <OnEvent...></Event> - Next action to look for. When an event occurs in WoW, if we had previously registered for an event, WoW will let us know that the event had occurred and will call this function found in the .lua file for us.

Line 7: </Script> - Closing Script tag.

Line 8: </Ui> - Closing UI tag.

Notice how all tags start and end with <Type>    (content)    </Type>

eCoordinates.lua contents - This is where the magic happens. It might look a little daunting at first, but that's alright. This content will be covered more in depth later.

local zone = nil
local TimeSinceLastUpdate = 0
 local function UpdateCoordinates(self, elapsed)
    if zone ~= GetRealZoneText() then
	zone = GetRealZoneText()
  	SetMapToCurrentZone()
    end
     TimeSinceLastUpdate = TimeSinceLastUpdate + elapsed
 	if TimeSinceLastUpdate > .5 then	
     	    TimeSinceLastUpdate = 0
   	    local posX, posY = GetPlayerMapPosition("player");		
 	    local x = math.floor(posX * 10000)/100
	    local y = math.floor(posY*10000)/100
	    eCoordinatesFontString:SetText("|c98FB98ff("..x..", "..y..")")	
 	end	
end
 
function eCoordinates_OnLoad(self, event,...) 
    self:RegisterEvent("ADDON_LOADED")	
end
 function eCoordinates_OnEvent(self, event, ...) 
     if event == "ADDON_LOADED" and ... == "eCoordinates" then
        self:UnregisterEvent("ADDON_LOADED")		
	    eCoordinates:SetSize(100, 50)
            eCoordinates:SetPoint("TOP", "Minimap", "BOTTOM", 5, -5)
    	    eCoordinates:SetScript("OnUpdate", UpdateCoordinates)
	    local coordsFont =    eCoordinates:CreateFontString("eCoordinatesFontString", "ARTWORK", "GameFontNormal")
 	    coordsFont:SetPoint("CENTER", "eCoordinates", "CENTER", 0, 0)
	    coordsFont:Show()
 	    eCoordinates:Show()		
	end
end


Remember in the .xml file, on line 5, we were looking for the first OnLoad event.

function eCoordinates_OnLoad(self, event,...) 
    self:RegisterEvent("ADDON_LOADED")	
 end

All AddOns are partially loaded to begin with, and then finish loading in no particular order. At some point when our AddOn is loading, WOW creates our frame stated in our .xml file. It just so happens that our frame in the .xml file is created prior to our AddOn being fully loaded. With this being the case, we let WoW know that we want to register for a certain event. More specifically, the "AddOn_LOADED" event. We want to know when our AddOn has been fully loaded. We may find that a few AddOn's may load prior to ours. Remember, the frame is what relays the trigger information to our XML file; as such, it makes sense that the frame must load first in order to trigger that loading event and set off our code.

Note: Frequently, hundreds of events occur in WoW behind the scenes. If we want to know about a specific event, we must register for it and WoW will send us a memo when this event does occur so that we may react to it. 

Looking back on line 6 in the .xml file, once any AddOn has fully loaded, the "eCoordinates_OnEvent" function is called. We do a check to see if event == "AddOn_LOADED" and the ... == "eCoordinates".

Once our AddOn has loaded, event will equal "AddOn_LOADED" and the ... will equal "eCoordinates". The ... (called a vararg) looks a little confusing, but it's actually just another variable. From here, the rest of the if-body proceeds.

 self:UnregisterEvent("AddOn_LOADED")

-- allows us to stop listening when any AddOn has finished loading. Remember, we're only concerned with our own addon, so once we know it loaded, we can stop checking the loading addons.

Time to make something we can actually see and use. Two things will be created: a new GUI frame and a string. The string will allow us to see the x and y coordinates of our character. The string will sit inside of the frame. Both the frame and string must be visible for us to see their contents.

Our eCoordinates_OnEvent method from above:
function eCoordinates_OnEvent(self, event, ...) 
     if event == "ADDON_LOADED" and ... == "eCoordinates" then
 	self:UnregisterEvent("ADDON_LOADED")		
        eCoordinates:SetSize(100, 50)
        eCoordinates:SetPoint("TOP", "Minimap", "BOTTOM", 5, -5)
	eCoordinates:SetScript("OnUpdate", UpdateCoordinates)
	local coordsFont = eCoordinates:CreateFontString("eCoordinatesFontString", "ARTWORK", "GameFontNormal")
 	coordsFont:SetPoint("CENTER", "eCoordinates", "CENTER", 0, 0)
	coordsFont:Show()
	eCoordinates:Show()	
    end
 end
eCoordinates:SetSize(100, 50)

-- Note that the SetSize function sets the size of the frame, but does not change the size of the font string.

eCoordinates:SetPoint("TOP", "Minimap", "BOTTOM", 5, -5)

-- The "TOP" of our created frame will sit at the BOTTOM of the Minimap, with an offset x of 5 and y of -5. For reference on SetPoint (and GUI positioning in general,) see this link . All the methods contained therein pertain to movement, position, and manipulation of frames/GUIs.

eCoordinates:SetScript("OnUpdate", UpdateCoordinates)

-- Similar to what we did in the XML file earlier, this line references another section of code (the UpdateCoordinates function.) This frame will listen to the OnUpdate event. The OnUpdate occurs as fast as your FPS. Note: When an update occurs (~30 - 60x per second, UpdateCoordinates function will be called each time. This allows for your x and y coodinates to be updated very frequently.)

Time to make the visible string.

local coordsFont = eCoordinates:CreateFontString("eCoordinatesFontString", "ARTWORK", "GameFontNormal")

-- Create a font string named eCoordinateFontString that will hold our x and y values. "ARTWORK" and "GameFontNormal" parameters are required.

coordsFont:SetPoint("CENTER", "eCoordinates", "CENTER", 0, 0)

-- required! This set's the location string to the frame. Otherwise, the font string wouldn't know where to locate itself compared to the frame and therefore would never show.

coordsFont:Show()

-- Show the font string so that we can see it. (Note that it still won't be visible yet, since it's contained in an invisible frame.)

eCoordinates:Show()

-- Show the frame so that we can see the frame and its contents - which includes the font string. Now all of it should show up.


Half way done with the programming! *breather* ... And here we go!

Our UpdateCoordinates function from above (and some other variables too!):
local zone = nil
local TimeSinceLastUpdate = 0
 local function UpdateCoordinates(self, elapsed)
    if zone ~= GetRealZoneText() then
	zone = GetRealZoneText()
  	SetMapToCurrentZone()
    end
     TimeSinceLastUpdate = TimeSinceLastUpdate + elapsed
 	if TimeSinceLastUpdate > .5 then	
     	    TimeSinceLastUpdate = 0
   	    local posX, posY = GetPlayerMapPosition("player");		
 	    local x = math.floor(posX * 10000)/100
	    local y = math.floor(posY*10000)/100
	    eCoordinatesFontString:SetText("|c98FB98ff("..x..", "..y..")")	
 	end	
end


local zone = nil 

-- A variable that let's us know if we have already asked the server and verified that we are in the correct zone. A check needs to be made once per zone to get an update of our location. Otherwise, the x and y values may be 0.

local TimeSinceLastUpdate = 0

-- We will use this variable to sum up elapsed times the function, UpdateCoordinates, has been called.

local function UpdateCoordinates(self, elapsed) 

-- Self is our current frame. Elapsed is the time since this function was last called. Coming from other languages, this syntax will seem nonsensical; it really just takes some time to get used to.

if zone ~= GetRealZoneText() then 

-- Ask the server the current zone we are in. If it equals to our variable Zone then we have already set our AddOn to this zone. Otherwise, we haven't and continue into the if-block

zone = GetRealZoneText()

-- Set our zone variable to this current zone.

SetMapToCurrentZone() 

-- Call function SetMapToCurrentZone() so that our AddOn is set to this zone.

TimeSinceLastUpdate = TimeSinceLastUpdate + elapsed 

-- add up the saved prior to with the elapsed time specifically for the next code line

if TimeSinceLastUpdate > .5 then 

-- since updating 30 - 60+ times a second is not necessary, let's just update our current location once every half second.  

TimeSinceLastUpdate = 0 

-- If it's been more than a second, let update! First, clear our old summed elapsed time so we can start fresh again in ~1/60 seconds

local posX, posY = GetPlayerMapPosition("player");  

-- Call the function GetPlayerMapPosition("player"), that will return our x and y coordinates; these will then be placed into our posX and posY variables, respectively.

local x = math.floor(posX * 10000)/100 
local y = math.floor(posY*10000)/100  

-- The returned values will be between 0 and 1. A little math allows an arbitrary number, say.7865 into 78.65, for both the x and y values.

eCoordinatesFontString:SetText("|c98FB98ff("..x..", "..y..")")

-- Set the x and y coordinate text to an interesting font. 98FB98 references a funky greenish color. And you're done!


At this point, the function UpdateCoordinates will be called every ~1/60 seconds, but the text will only update every half-second. Nuanced timings will only really matter when performance is key, so again, you don't really need to worry about this.


Some generally helpful advice: Take this one step at a time. You can use [print("Hello, World")] in any of the .lua functions to see if that function is ever called (this is called a debug statement, and it's used to check the values of variables at certain positions of your code.) These debug statements will print into your console, so you'll be able to see your variables update in real time. Think console.log("Message Here") for web developers, etc. You should be able to copy and paste the first 3 boxes into the correct files and load up your game without any problems - just check the version number!

For information on the Widget API, which is where the SetSize, SetPoint, and SetScript is coming from, check around here . For information on the actual WoW API, for event handling and more player-related activity, see here.Good luck and happy coding!