Navigation menu
Personal tools
Not logged in
Talk
Contributions
Create account
Log in
Namespaces
WoW
Talk
English
Views
Read
Edit
History
More
Search
Navigation
Home
Random page
Help using wiki
Editions
for WoW
for WildStar
for Solar2D
Documentation
for WoW
for WildStar
Reference
WoW
⦁ FrameXML
⦁ AddOns
⦁ API
⦁ WoW Lua
WildStar
⦁ AddOns
⦁ API
⦁ WildStar Lua
Engine
Tools
What links here
Related changes
Special pages
Page information
Site
Recent Changes
Editing
WoW:Earth (AddOn)/Using earth to create a QuestLog
Jump to navigation
Jump to search
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
[[Earth (AddOn)]], a [[FrameXML]] library, is useful for rapidly creating Frames in WoW. By using Earth, it is possible to create a fancy quest log rather quickly. I'll go through the simple steps here. I will assume you already know how to create an addon and fill-in an example .toc file. You should also know how to specify a dependency in the .toc. You should also know how to use [[API getglobal|getglobal()]]. == Summary == The goal of this tutorial is to create a fancier quest log. Using the tools provided by Earth's Tree template, along with the functions available in [[Sea (AddOn)|Sea]] and [[Chronos (AddOn)]], we'll be able to show a complete list of the player's quests with very little trouble. * Tools Used: ** [[Sea (AddOn)]] ** [[Earth (AddOn)]] ** [[Chronos (AddOn)]] * Coding Time: 1 hour * Tutorial Length: 2-3 hours == Creating the XML file == The first step is to create the XML file. Normally, I just copy my favorite AddOn's Ui tag, then work from there: <nowiki><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"> </Ui></nowiki> Once you have that, the next stage is to setup your localization and code files. Having an independent localization.lua file will make it exceptionally easier for the lovable Frenchies and swanky Swedes to translate your [[AddOns|AddOn]] into their mother tongue. This goes inside the Ui tag: <nowiki><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"> <!-- Localization --> <Script file="localization.lua"/> <!-- Source --> <Script file="PartyQuests.lua"/> <!-- All Future XML goes here! --> </Ui></nowiki> <small>Now, because that looks ugly, I'm going to stop copy and pasting all of that and assume you can follow me. Good luck!</small> The next stage is to setup the window for the frame! Because we're inheriting from an Earth frame, we don't need to specify the default background texture. This works great for simple mods, like this quest log. <!-- Party Quests Frame --> <Frame name="PartyQuestsFrame" inherits="EarthFrameTemplate"> <Size> <AbsDimension x="384" y="490"/> </Size> <Anchors> <Anchor point="CENTER"> <Offset> <AbsDimension x="0" y="0"/> </Offset> </Anchor> </Anchors> </Frame> [[Image:Earth_tutorial_0.jpg|thumb|The empty frame was created from that bit of code.]] At this point, our Frame looks like this: <br clear=all> Woo hoo! Wasn't that easy? Okay, next we need to add an EarthTree. This will let us render information from a table to a nice, collapsable list (up to a decent limit). First, we add a <Frames> tag to the PartyQuestsFrame. Then we stick the EarthTree frame inside of it. <Frames> <!-- Main Data View --> <Frame name="$parentTree" inherits="EarthTreeTemplate"> <Anchors> <Anchor point="TOPLEFT" relativeTo="PartyQuestsFrame" relativePoint="TOPLEFT"> <Offset> <AbsDimension x="20" y="-75"/> </Offset> </Anchor> </Anchors> </Frame> </Frames> You may have noticed that I placed it at a very specific x/y set. I got those through experimentation. However, if you find yourself getting frustrated, I recommend you try the following trick: <Layers> <Layer level="BACKGROUND"> <Texture name="$parentBackground" setAllPoints="true"> <Color r="0" g="1" b="0" a="1.0" /> </Texture> </Layer> </Layers> Place that code snippet inside of the frame you are trying to position. This will force the entire frame to be colored green. You can then use this green box to help position the frame correctly and ensure your hit rectangles are 100% correct. You can disable it by setting a="0.0". Now, for the next stage, we need to specify something to load the actual data into the EarthTree. So, first things first, we add the event handler to the PartyQuestsFrame. <Scripts> <OnLoad> PartyQuests_Frame_OnLoad(); </OnLoad> </Scripts> == Starting the Code == Then, going into our PartyQuests.lua file, we create: function PartyQuests_Frame_OnLoad() local ebf = getglobal(this:GetName().."Tree".."ExpandButtonFrame"); ebf:SetPoint("TOPLEFT", ebf:GetParent():GetName(), "TOPLEFT", 50, 1); getglobal(this:GetName().."ExitButton"):Hide(); getglobal(this:GetName().."TitleText"):SetText(PARTYQUESTS_TITLE_TEXT); end First, we move the +All frame a bit to the right, then hide the Exit Button and set the Title text. This just makes it look nicer. Next, we add the dummy function: function PartyQuests_Frame_OnLoad() local ebf = getglobal(this:GetName().."Tree".."ExpandButtonFrame"); ebf:SetPoint("TOPLEFT", ebf:GetParent():GetName(), "TOPLEFT", 50, 1); getglobal(this:GetName().."ExitButton"):Hide(); getglobal(this:GetName().."TitleText"):SetText(PARTYQUESTS_TITLE_TEXT); local testTable = {1,2,3, {4, 5, {6,7}, 8}, 9}; EarthTree_LoadTable( getglobal(this:GetName().."Tree"), testTable, { onClick = function(a) Sea.io.printTable(a); end; } ); EarthTree_UpdateFrame( getglobal(this:GetName().."Tree") ); end EarthTable_LoadTable will put the data into the tree. Once the data is inside we don't need to call LoadTable unless the data changes. Then EarthTree_UpdateFrame will draw the information to the screen. For the sake of speed, I'm going to pass on screenshotting this. Try it out! == Parsing the Player's Quest List == The next step is to create a table which contains all of the Quests a player has. This part is kind of complicated, but I am probably going to commit my function to [[Sea (AddOn)|Sea]], so you don't have to worry about the details too much. First thing, write the function to get all of the information from the game. -- Generates the current player's quest list -- function PartyQuests_GetPlayerQuestTree() -- Expand everything ExpandQuestHeader(0); -- Build our quest list local numEntries, numQuests = GetNumQuestLogEntries(); local questList = {}; local activeTable = nil; for i=1, PARTYQUESTS_QUESTS_DISPLAYED, 1 do local questIndex = i; if ( questIndex <= numEntries ) then local title, level, questTag, isHeader, isCollapsed, isComplete = GetQuestLogTitle(questIndex); local color; if ( title ) then if ( isHeader ) then questList[title] = {}; activeTable = questList[title]; else if ( activeTable == nil ) then activeTable = questList; end local entry = {}; entry.title = title; entry.level = level; entry.tag = questTag; entry.complete = isComplete; table.insert(activeTable, entry); end end end end return questList; end Everything looks great, right? Uh oh! Whats this? We had to call [[API_ExpandQuestHeader|ExpandQuestHeader]] to ensure we got all of the quests. This means the player's collapsed quests will be expanded. He won't like that, so first thing we do is create a function to keep track of all of his collapsed quests and a function to re-collapse them when done. We also want to make sure no other function tries call us for an update while we're still parsing the information. (For you advanced programmers, I'm making sure my code is thread-safe). We add a bit called "PartyQuests_CurrentlyGettingQuestData" to ensure we can't call this funcion before it is finished. -- Generates the current player's quest list -- function PartyQuests_GetPlayerQuestTree() if ( PartyQuests_CurrentlyGettingQuestData ) then return true; end PartyQuests_CurrentlyGettingQuestData = true; -- Store the collapsed local collapsed = PartyQuests_StoreCollapsedQuests(); -- Expand everything ExpandQuestHeader(0); -- Build our quest list local numEntries, numQuests = GetNumQuestLogEntries(); <SNIP> -- Collapse again PartyQuests_CollapseStoredQuests(collapsed); -- Unlock the thread PartyQuests_CurrentlyGettingQuestData = false; return questList; end Whew! Okay. Next we implement those two quick functions: -- Stores the currently collapsed quests -- function PartyQuests_StoreCollapsedQuests() local collapsed = {}; for i=1, PARTYQUESTS_QUESTS_DISPLAYED, 1 do local title, level, questTag, isHeader, isCollapsed, isComplete = GetQuestLogTitle(i); if ( isCollapsed ) then table.insert(collapsed, title); end end return collapsed; end -- Collapse the quests we stored -- function PartyQuests_CollapseStoredQuests(collapseThese) for i=1, PARTYQUESTS_QUESTS_DISPLAYED, 1 do local title, level, questTag, isHeader, isCollapsed, isComplete = GetQuestLogTitle(i); if ( Sea.list.isInList(collapseThese, title) ) then CollapseQuestHeader(i); end end end * Author's Note: I've now added more advanced versions of these two functions to [[Sea (AddOn)|Sea]]. ** Check out [[Sea.wow.questLog.protectQuestLog]]() and [[Sea.wow.questLog.unprotectQuestLog]](). == Displaying the Quest List == Hey, that wasn't so bad! Just a couple of loops. We've saved a lot of frustration from our user's end, too! Okay, so now we have a fancy function to get all of the quests for the current player. Let's use this instead of our dummy data. function PartyQuests_Frame_OnLoad() local ebf = getglobal(this:GetName().."Tree".."ExpandButtonFrame"); ebf:SetPoint("TOPLEFT", ebf:GetParent():GetName(), "TOPLEFT", 50, 1); getglobal(this:GetName().."ExitButton"):Hide(); getglobal(this:GetName().."TitleText"):SetText(PARTYQUESTS_TITLE_TEXT); ''local questTree = PartyQuests_GetPlayerQuestTree();'' EarthTree_LoadTable( getglobal(this:GetName().."Tree"), questTree, { onClick = function(a) Sea.io.printTable(a); end; } ); EarthTree_UpdateFrame( getglobal(this:GetName().."Tree") ); end ;Ick!! What's this? The questTree didn't load? Oh no! What happened? : A) The quest log isn't always available OnLoad, so we need to wait for a bit before getting the info. We'll do this by moving all of the update code into another function: function PartyQuests_LoadGui () local frame = PartyQuestsFrame; local tree = PartyQuests_GetPlayerQuestTree(); -- Load up the data EarthTree_LoadTable( getglobal(frame:GetName().."Tree"), eTree ); -- Draw the data EarthTree_UpdateFrame( getglobal(frame:GetName().."Tree") ); end [[Image:Earth_tutorial_1.jpg|thumb|A table-accurate parsing of the data]] Then we'll add something to the PartyQuests_Frame_OnLoad() function: -- Event Handlers -- function PartyQuests_Frame_OnLoad() ''Chronos.afterInit(PartyQuests_LoadGui);'' local ebf = getglobal(this:GetName().."Tree".."ExpandButtonFrame"); ebf:SetPoint("TOPLEFT", ebf:GetParent():GetName(), "TOPLEFT", 50, 1); getglobal(this:GetName().."ExitButton"):Hide(); getglobal(this:GetName().."TitleText"):SetText(PARTYQUESTS_TITLE_TEXT); end [[Chronos.afterInit]] is a really nice tool, that lets us do something after the game-world has started. In our case, its populating our quest list and refreshing. Using code like this helps ensure we run into fewer errors, but we need to change all of the '''''this''''' references into '''''frame''''' references. So reload and you'll see that will cause the EarthTree to parse our tree by type, as well as create sublists for all of the members of this table. Check out the result! Not bad, eh? == Rendering Complex Trees == However, we're no slaves to simplicity, we're real coders, out to make life easier for our users. So we persist on, to make it even better. The first step is to convert our data-tree into an enhanced tree. The enhanced tree format for [[Earth (AddOn)]] is pretty complicated, but you can find out more about it by reading the EarthTree.lua file. Let's write a function to make an enhanced tree out of the simple one: -- Converts Quest Trees into Enhanced Tree -- function PartyQuests_ConvertQuestTreeToEnhancedTree(questTree, funcList) local enhancedTree = {}; for zone,questList in questTree do local zoneQuests = {}; zoneQuests.title = zone; zoneQuests.children = {}; for k,quest in questList do local entry = {}; entry.title = quest.title; entry.right = quest.tag; entry.tooltip = quest.level; entry.onClick = function (a) Sea.io.printTable(a) end; table.insert(zoneQuests.children,entry); end zoneQuests.right = table.getn(zoneQuests.children); zoneQuests.tooltip = table.getn(zoneQuests.children); table.insert(enhancedTree,zoneQuests); end return enhancedTree; end [[Image:Earth_tutorial_2.jpg|thumb|Wow, it looks like a real quest log.]] Then, change the function to use the LoadEnhanced call: function PartyQuests_OnQuestClick(a) -- Do something here end function PartyQuests_LoadGui () local frame = PartyQuestsFrame; local tree = PartyQuests_GetPlayerQuestTree(); local eTree = PartyQuests_ConvertQuestTreeToEnhancedTree(tree, {onClick=PartyQuests_OnQuestClick} ); -- Load up the data EarthTree_LoadEnhanced( getglobal(frame:GetName().."Tree"), eTree ); -- Draw the data EarthTree_UpdateFrame( getglobal(frame:GetName().."Tree") ); end There. This is a lot to soak up, but basically, every entry in the [[Earth EnhancedTree]] format has an entry called "children", which determines what elements go inside of it. "title" is the main text, "right" is the extra text on the right, "tooltip" is what appears when you mouse it over and onClick is what happens when you click it! Here's the result: Ooh! That was nice. However, it was kind of bland. All that white. So lets spice it up with some color. We're going to use the function the default QuestLog uses to set its colors. That will ensure we don't need to change our code if Blizzard changes theirs. [[Image:Earth_tutorial_3.jpg|thumb|Description]] -- Converts Quest Trees into Enhanced Tree -- function PartyQuests_ConvertQuestTreeToEnhancedTree(questTree, funcList) local enhancedTree = {}; -- Loop through the zone list for zone,questList in questTree do local zoneQuests = {}; zoneQuests.title = zone; zoneQuests.children = {}; -- Loop through the quest list for k,quest in questList do local entry = {}; entry.title = quest.title; entry.right = quest.tag; entry.tooltip = quest.level; -- Lets add some color '''''entry.titleColor = GetDifficultyColor(quest.level);''''' -- Add the event handlers entry.onClick = function (a) Sea.io.printTable(a) end; table.insert(zoneQuests.children,entry); end zoneQuests.right = table.getn(zoneQuests.children); zoneQuests.tooltip = table.getn(zoneQuests.children); table.insert(enhancedTree,zoneQuests); end return enhancedTree; end Doesn't that look pretty? But it still not enough. We need to complete this masterpiece by improving upon the original. Let's make use of that spiffy localization.lua file. Open it up and add the following: PARTYQUESTS_QUEST_TITLE = "\[%s\] %s"; PARTYQUESTS_QUEST_TITLE_TAG = "(%s)"; These are a couple of formatting strings that will help us make the Enhanced Tree look even better. "string.format" is another really nice function, that you can find more about over at http://www.lua.org, check out their documentation. These strings allow us to format those tags we'd like changed very quickly. Plus, if a different country prefers to use a different format, we can update it just by changing the localization.lua file. [[Image:Earth_tutorial_4.jpg|thumb|Beautiful. Simply Beautiful!]] -- Converts Quest Trees into Enhanced Tree -- function PartyQuests_ConvertQuestTreeToEnhancedTree(questTree, funcList) local enhancedTree = {}; -- Loop through the zone list for zone,questList in questTree do local zoneQuests = {}; zoneQuests.title = zone; zoneQuests.children = {}; -- Loop through the quest list for k,quest in questList do local entry = {}; -- Add the format strings here: -- Format it into [12] Quest of Danger '''''entry.title = string.format(PARTYQUESTS_QUEST_TITLE, quest.level, quest.title); ''''' -- Pretty up the (Elite) '''''if ( quest.tag ) then entry.right = string.format(PARTYQUESTS_QUEST_TITLE_TAG, quest.tag); end''''' -- Add a simple tooltip entry.tooltip = quest.level; -- Lets add some color entry.titleColor = GetDifficultyColor(quest.level); -- Add the event handlers entry.onClick = function (a) Sea.io.printTable(a) end; table.insert(zoneQuests.children,entry); end zoneQuests.right = table.getn(zoneQuests.children); zoneQuests.tooltip = table.getn(zoneQuests.children); table.insert(enhancedTree,zoneQuests); end return enhancedTree; end Beautiful. A work of art and it only took us about an hour to do. If you'd like to extend this work, you can set all of those onClick entries to do something more advanced than just print out a message. Likewise, you can make checkboxes show up by adding "checked=true" to each entry. == Rendering the Actual Quest Text == If you wish to see how to create the actual QuestLog, check out [[HOWTO: Use Earth to create a QuestLog/Part 2]]. Its a bit more grizzly than this tutorial. == Wrap-up == Well, that's all for this time. Stay tuned for next time, when I hope to tackle the mysteries of sending messages between two users via [[Sky (AddOn)]]. The code I used to create this tutorial can be found at: http://www.cosmosui.org/tutorials/EarthQuestLog_Example.zip
Summary:
Please note that all contributions to AddOn Studio are considered to be released under the Creative Commons Attribution-NonCommercial-ShareAlike (see
AddOn Studio Wiki:Copyrights
for details).
Submissions must be written by you, or copied from a public domain or similar free resource (see
AddOn Studio Wiki:Copyrights
for details).
Cancel
Editing help
(opens in new window)