This tutorial will function both as a simple tutorial for those who may not have any experience with WildStar’s addon system, AND as a way of testing our syntax highlighting plugins for Lua and how well tutorial posting works in this theme.
Before we dive into any real addons that have sprung up during the WildStar beta (or even any addons with a real use), it would be worthwhile to cover a bit of how WildStar handles addons, and the process by which create them. If you’re already familiar with addon development, much of this may be redundant; I apologize. The final form of the addon will also be included as a .zip file at the end of the tutorial post.
Apollo and Houston
What is Apollo?
You may have seen the term “Apollo” thrown around in relation to WildStar before. Either in Twitter conversations between addon developers, in interviews with Bitwise (Jon Wiesman, Carbine’s lead client developer and the creator of the addon system) or in forum threads. And more than once, I’ve had someone ask “what the heck is ‘Apollo’, anyway?”
Lua is a language which can be embedded in just about anything, but if that’s all you do then the Lua programs won’t be very interesting. If I can add things, subtract them, perform calculations and so on but have no way to actually interact with the host program… well, it’s not very useful. So you have to inject your own APIs into the Lua virtual machine. These APIs will provide functions that call back into the hosting program.
In the case of WildStar, that API which the WildStar client provides to the Lua VM is called “Apollo”. All communication between your addon and the game client will be handled by Apollo.
In many games, either the base UI is written separately from the addon system, or the game developer has access to functionality that addon developers don’t have. Unusually, Carbine holds themselves to the same restrictions that addon authors have; anything they can do as a UI addon, we can as well. Bitwise refers to this as “peer level functionality”.
There are a few exceptions; user-provided addons cannot be used before you’re in the game world, so we can’t replace the login screen or character creation, or anything—like the options menu—which runs outside of the game instance. But within the game itself, we have the same sandbox to play in that Carbine’s own UI developers have.
What is Houston?
Houston is the development environment for WildStar addons. It’s used in-house by Carbine’s own developers, as well as by the third-party addon developers. Houston provides a way to access the resources encoded in WildStar’s asset library—all the sprites, forms and stock addons—as well as to create new addons, design forms and import assets like images and sounds into your own addon.
Houston is included with a stock installation of WildStar; there’s no separate download to pick up from somewhere on Carbine’s website. The Houston.exe file can be found in the Client or Client64 directory under your WildStar installation. However, keep in mind that the game cannot be patched while Houston is running, as Houston needs to have the game asset files open (and Houston itself is updated as part of any patch).
Once Houston’s open, you can either load an addon that’s already on disk, load a stock Carbine addon from within the game to look at, or create a new addon.
When you choose to make a new addon, Houston will present you with a dialog asking you to provide some information about your addon.
We’ll turn on the slashcommand and timer options; all they do is generate some boilerplate code for us, but it provides a convenient sample we can work with.
When you click OK, you’ll find that Houston has created the project for you, and it contains two files: NASASample.lua and NASASample.xml. There is actually also a third file, toc.xml, which represents the addon bundle itself and is what Houston actually has ‘open’.
Anatomy of an Addon
Let’s double-click on NASASample.lua, to open that in the Lua editor.
You’ll notice that the Lua editor shows an entry for each function in each Lua file of the project, giving you a quick way to skip to any of those functions.
Let’s break down the standard addon functions we’re using here.
- new is called to create an instance of the addon class.
- Init is called to set up an instance of the class.
- OnLoad is called when the addon is loading. Every addon must support this.
There are some additional standard functions an addon may optionally provide for additional functionality:
- OnConfigure can be implemented if the addon wants to have a button in the WildStar configuration menu.
- OnSave and OnRestore can be implemented to save and load data, which we will cover in the AngryBars tutorial.
You will notice in our sample addon, there are a handful of other functions already provided for us:
- OnNASASample has been automatically generated for the /nasample command we provided in the addon creation step.
- OnTimer has been automatically generated for the timer we requested in the addon creation step.
- OnOK and OnCancel have been automatically generated for the OK and Cancel buttons of the default form that Houston has created for us.
Let’s take a quick look into a few of the functions as we flow through the system. Let’s start with Init.
31 32 33
function NASASample:Init() Apollo.RegisterAddon(self) end
The first thing you’ll notice is that the function is named ‘NASASample:Init’. The : is a special character in Lua, and basically ensures a hidden parameter of ‘self’ at the beginning of the function which will always have the object that function is on.
RegisterAddon takes a single Lua object, which is used as the main class of the addon. That object is what OnLoad, OnConfigure, OnSave, OnRestore and all the other stock Apollo functions will be called on. By calling RegisterAddon, we have actually set the addon up in Apollo.
OnLoad is where most of the setup of an addon is usually done:
39 40 41 42 43 44 45 46 47 48 49
function NASASample:OnLoad() -- Register handlers for events, slash commands and timer, etc. -- e.g. Apollo.RegisterEventHandler("KeyDown", "OnKeyDown", self) Apollo.RegisterSlashCommand("nasample", "OnNASASampleOn", self) Apollo.RegisterTimerHandler("OneSecTimer", "OnTimer", self) -- load our forms self.wndMain = Apollo.LoadForm("NASASample.xml", "NASASampleForm", nil, self) self.wndMain:Show(false) end
You can see that Houston has already filled out several things for us:
- Apollo.RegisterSlashCommand registers a new command the player can use. It takes three parameters: a command (nasample), a function name (“OnNASASampleOn”) and a Lua object on which that function will be called (self). In this case, if you typed “/nasample” as a command the chat input box, the OnNASASample function on this class would be called.
- Apollo.RegisterTimerHandler takes three parameters: a timer name (“OneSecTimer”), a function name (“OnTimer”) and a Lua object on which that function will be called (self). In this case, “OneSecTimer” is a special timer which fires once per second, so we don’t need to create a timer and set the duration.
- Apollo.LoadForm will load a form—Apollo’s UI elements—from the UI XML file specified. It takes four parameters: a filename or an xmlDoc (in this case “NASASample.xml”), the name of the form in that file (“NASASampleForm”), the parent form to load this form as a child of or nil if it just sits at the top UI layer, and the object on which all control callbacks will be made. It returns an object representing that form; in this case, we’re storing that as the ‘wndMain’ parameter on our addon class.
The next line, you will notice, has another of those : characters mentioned earlier. “self.wndMain” is the form we just stored off, and “Show” is a function on that form. Calling “self.wndMain:Show(false)” ensures the form is not visible.
Similarly, the OnNASASample function calls self.wndMain:Show(true), making the window visible. The OnOK and OnCancel functions hide the window once again.
The last function we have is the OnTimer function:
63 64 65
function NASASample:OnTimer() Print("NASASample:OnTimer()") end
This is a simple one. Print() is a global function which just outputs the string onto the “Debug” channel of the game’s chat system. Debug is a special channel you cannot talk on, and which is only local to your client, but which can be added to a tab or hidden like any other channel. In this case, it will print the string “NASASample:OnTimer()” every time the timer function we registered is called, once a second.
This isn’t a terribly interesting addon. Let’s spice it up slightly.
The Form Editor
Going back to the project tab and double-clicking on NASASample.xml will open a new tab in Houston, loading that file into the form editor.
Much like the Lua editor, the form editor breaks down an XML file into each of the forms it contains, and the hierarchy of UI elements contained within.
In this case, Houston has created a dialog window for us which contains three buttons (a close button in the upper right corner, and OK and Cancel buttons in the lower right) and a title.
If we double-click on our NASASampleForm, it will be loaded into the form editor for us, and the properties dialog for that form opened:
One of the first things that becomes clear is that Apollo positions everything in a slightly different way: each point (left, top, right, bottom) is defined as an anchor and an offset.
For anchors on left and right, ‘0’ means the left side of the parent container and ‘1’ means the right. For top and bottom, an anchor of ‘0’ means the top and ‘1’ means the bottom. You can also have named anchors on other forms, to allow them to be positioned relative to each other, but for now we’re just going to care about those.
In this case, our anchors are (0,0,0,0), and the offsets are (61,52,656,847). This means the left edge of the form will be 61 units past the left edge of the screen (as the screen is our parent container here), the right edge 656 units from the left edge of the screen, the top of our form 52 units from the top of the screen, and the bottom 847 units from the top of the screen.
But what if we want the dialog to never be taller than our screen? Let’s change the bottom to be an anchor point of 1 (the bottom of the screen), and the offset to be -50.
Now our dialog fits within the small preview I was using to take these screenshots, but would get taller as the screen got taller.
“Title” isn’t a very interesting name for the addon’s form, either. Let’s go change that. Double-click the ‘Title’ element, and change the Text value:
Okay, so we’ve seen how we can change the attributes of an element on the form. But how does it actually do things with the addon itself? Let’s take a look at our OK button to see. Double-click on the OkButton element, and then click on the ‘Events’ tab:
The ButtonSignal event has been bound to “OnOK”. ButtonSignal is one of many potential events you can add handlers for; in this case, ButtonSignal is sent whenever a button is clicked. OnOK, you will notice, is the name of a function back in our Lua handler.
Looking back at this line:
self.wndMain = Apollo.LoadForm("NASASample.xml", "NASASampleForm", nil, self)
You’ll notice the ‘self’ on the end there. That object is dealing with all event handlers from the form we loaded there. Since it was our addon class, when you click on the OK button, it will call “OnOK” on our addon class.
This may seem a bit much, but it can be very useful in complex addons where you may need to break down form handlers, timers and such not into smaller classes for easy maintenance.
So, let’s make our dialog slightly more interesting. We’ll select the NASASampleForm again, then go up and click on the “Window” icon to add a new generic form element as a child of our NASASampleForm. We’ll name it “Icon” and set the anchors to (0,0,1,1) and the offsets to (21,66,-21,-91). Since it is contained within the NASASampleForm, the anchors refer to the boundaries of the form, and we’ll end up with a nice little container.
Since we’ve named it Icon, let’s pick a graphic to put in. You can click the “…” next to the Sprite field, and you’ll find yourself in WildStar’s sprite library. This will let you browse through all the sprites that are available, and enter a search pattern to filter them on.
In this case, let’s enter CRB_DEMO and filter on that. We’ll find a number of assets which are used for the trade show demos:
We’ll pick the spellslinger and click ‘OK’. The image hasn’t appeared yet, however, so we’ll need to click into the ‘Styles’ tab and check ‘Picture’ to let the form editor know that, yes, this particular element contains a picture. Now, we have a Spellslinger:
Now we’re going to add one more element. So we’ll go back to add another generic element, one called Text. This time, we’ll set the anchors to (0,0,0,0) and the offsets to (317,119,565,442). We’ll get a skinny box next to the spellslinger. Because it’s somewhat narrow, we’ll go to the “TextFlags” tab and check “DT_WORDBREAK” to turn on the word-break flag, which means text will wrap onto multiple lines in the element:
Now our dialog is done, but we can still make this a little more interesting.
Accessing Form Elements
Let’s flip back to the Lua editor, and go to our “OnNASASample” function. We’re going to add some to the function:
58 59 60 61 62 63 64 65
function NASASample:OnNASASampleOn() local drPlayer = GameLib.GetPlayerUnit() local strName = drPlayer and drPlayer:GetName() or "player" self.wndMain:FindChild("Text"):SetText("Well, shoot, " .. strName .. "! Y'all made an addon!") self.wndMain:Show(true) -- show the window end
So, now, before we show the window, we’re calling ‘GetPlayerUnit()’ on the GameLib library, which will return an object representing the WildStar “Unit” for the character. Units are anything within the world; players, mobs, props, harvestable resources, lore objects and so on. If, by some chance, you’ve managed to call this function without being fully loaded into the world, GameLib.GetPlayerUnit() would return nil.
Once we have the player unit, we’re going to get the name or, if the player happens to be nil, we’re going to use the placeholder string “player”.
Then we’re going to find the child of our main form named “Text”, and set the text value of that form element to be the string we’ve provided, including the character name. And then, as before, we’re going to show the window.
Let’s save our addon (File, Save All) and load up WildStar.
Using Our Addon
After you manage to tear yourself away from Jeff Kurtenacker’s awesome title theme long enough to actually log your character in, you’ll quickly notice something: your chat window is full of a bunch of lines saying “[Debug] NASASample:OnTimer()”, one appearing every second.
Whups, we forgot to remove that sample timer. Let’s go back into the Lua file, and we can remove the entire OnTimer function, as well as the RegisterTimerHandler call in OnLoad. Once that is done, type “/reloadui” into your chat input area and hit enter. Your UI will be refreshed, the addons reloaded, and you’ll see the timer has cleared up.
Now that we’re not flooding our chat log, you’ll want to enter that command we registered. Type “/nasample” into the chat log, and you’ll notice that it shows as a valid command. Hit enter, and our dialog will show up:
As you can see, our simplistic little form contains the text we set, including the character name. Pax is thrilled to get some recognition.
Congratulations! You’ve made a simple addon, and learned a bit of how to make your way around Houston, probably in the space of about 15-20 minutes. While it’s not a terribly interesting or complicated addon, the knowledge you’ve gained here will help in building considerably more complicated addons, both on your own and in following along with more complicated tutorials on here.
Please feel free to provide commentary on this tutorial; we’re still feeling out the format and process, and if there are parts that needed to be clearer, it’s best to tweak things now with the simplest possible tutorial!