Jekyll2023-10-16T18:53:15-07:00https://www.jimhribar.com/feed.xmlCan’t. Stop. Coding.personal descriptionJim HribarNo room for a train table? No Problem!2023-09-02T00:00:00-07:002023-09-02T00:00:00-07:00https://www.jimhribar.com/ho-scale-wall-mount<p>No room for a train table? No problem!</p>
<p>Growing up I had a train table in my basement. It was something no one else I knew had. To this day that remains true. I still have a fondness for model trains because of that, and saved the trains for years even after moving out from my childhood home.</p>
<p>This year, I finally got around to taking those trains out of storage. They were in much rougher shape than I remembered, but I wasn’t going to let that stop me. I bought some new engines and track and went to work restoring the old train cars. But there was a problem… I didn’t have a train table. Where were would I setup the trains? I wanted the train in my office, but there was no room for any HO scale layout. Or so I thought.</p>
<p>After some pondering, I set out to see if I could develop some type of wall mount system using my limited 3D modeling skills. The goals were simple. (1) There should be no need to modify the track itself. (2) The parts should be mountable to the wall quickly. (3) The parts should be easily printable as I was going to need quite a few for the project.</p>
<p>After a few days of modeling, prototyping and printing I had the basic designs down. But a new challenge appeared. My office ceiling slopes to follow the curve of the roof, so either I would have to have a inclining and declining slopes making running the train a manual process, or I’d have to create some type of bridge across my ceiling before the slope… and that’s just what I did.</p>
<p>It didn’t take long to install the parts into the wall using the jig system I created, and slight leveling adjustments were easy since I only needed a single drywall screw per mount. Anyways, that’s enough about the process. Let’s see the train!</p>
<p>I’m very happy with the result and see a lot of potential for others using this system in their own projects. Because of that, I’ve made the parts available for order through HackDTW and will be selling them made to order for anyone interested.</p>
<p>I’d estimate around 40 wall mounts and 8 corner mounts would be required for a 10x10 room. The placement jig is optional, but it does help in the mounting process. As is the bridge truss. It’s cool, but not required for a flat ceiling. I will be selling the kit, and additional parts, like the truss, for larger installations separately. The only other parts required would be a compatible track. I’d recommend Bachmann Trains - Snap-Fit E-Z TRACK as that is what I’ve used and can confirm compatibility, but others may work just as well with or without modification.</p>
<p>I’ve also continued to play with the idea of additional designs for the wall mounts as well as compatibility with different scales and track types. If there is enough interest I may design and release those in the future as well. Until then, I run my train all the time and have yet to have a major derailment. That may change once my cats find a way to reach the train…</p>Jim HribarNo room for a train table? No problem!How to Replace Docker Desktop with Ubuntu Multipass2021-09-03T00:00:00-07:002021-09-03T00:00:00-07:00https://www.jimhribar.com/replace-docker-desktop-with-ubuntu-multipass<p>Recently <a href="https://www.docker.com/">Docker</a> decided to change the <a href="https://www.docker.com/products/docker-desktop">Docker Desktop</a> pricing model. While reasonable, it is an additional reoccurring cost for basic tooling that remains easily accessible and free on other platforms. <a href="https://multipass.run/">Ubuntu Multipass</a> is one of numerous alternatives that can replace Docker Desktop on Macs and Windows.</p>
<p align="center">
<a href="https://www.teepublic.com/sticker/1212604-multipass">
<img src="/images/multipass.png" />
</a>
</p>
<h2 id="getting-started">Getting Started</h2>
<p>The following steps have been tested on an Intel Macbook w/ 16gb of ram after removing Docker Desktop.</p>
<h2 id="installing-multipass">Installing Multipass</h2>
<p>Ideally, use <code class="language-plaintext highlighter-rouge">brew install multipass</code>, however there’s currently a bug (<em>sftp server: handle_stat: cannot stat</em> <a href="https://github.com/canonical/multipass/issues/2203">#2203</a>) which results in log files filling your hard drive. As of time of writing it is recommended to use the package referenced in the issue.</p>
<h2 id="configuring-a-shell">Configuring a Shell</h2>
<p>The following configuration values can be modified, but those presented have been tested successfully with a large application stack. Additionally, changing the shell name will require changes to commands that follow in this guide.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>multipass launch --name development --cpus 4 --mem 8g --disk 100G
</code></pre></div></div>
<h2 id="entering-the-shell">Entering the Shell</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>multipass shell development
</code></pre></div></div>
<p align="center">
<img src="/images/multipass-shell-development.png" />
</p>
<p>All command from this point forward, unless specified otherwise, are to be done from within the multipass shell.</p>
<h2 id="installing-docker">Installing Docker</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
sudo apt install docker-compose
</code></pre></div></div>
<h2 id="optionally-configure-sudoless-docker">Optionally, Configure Sudoless Docker</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install -y uidmap
dockerd-rootless-setuptool.sh install
echo "export DOCKER_HOST=unix:///run/user/1000/docker.sock" >> ~/.bashrc
source ~/.bashrc
</code></pre></div></div>
<h2 id="optionally-configure-aws">Optionally, Configure AWS</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install awscli
aws configure
$(aws ecr get-login --no-include-email --region us-west-2)
</code></pre></div></div>
<h2 id="probably-configure-github">Probably, Configure GitHub</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git config --global user.email "you@example.com"
git config --global user.name "Your Name"
ssh-keygen -t ed25519 -C "your_email@example.com"
cat ~/.ssh/id_ed25519.pub
</code></pre></div></div>
<p>You can add the new key here: <a href="https://github.com/settings/keys">SSH and GPG keys</a>.</p>
<h2 id="maybe-transfer-existing-source-code">Maybe, Transfer Existing Source Code</h2>
<p>It may be more or less work to transfer your existing code as there will be configuration differences between Mac and Linux. It’s also possible to run off the mount, but you may experience high cpu usage depending on the application’s disk activities.</p>
<h3 id="on-the-mac">On the Mac:</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>multipass mount ~/git/ development:/home/ubuntu/git-mac/
</code></pre></div></div>
<h3 id="in-the-multipass-shell">In the Multipass shell:</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cp -Rv git-mac/ git
</code></pre></div></div>
<h3 id="on-the-mac-1">On the Mac:</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>multipass umount development
</code></pre></div></div>
<h2 id="optionally-configure-privileged-port-access-in-the-virtual-machine">Optionally, Configure Privileged Port Access in the Virtual Machine</h2>
<p>While Docker is perfectly happy trying to bind to any port, the <a href="https://ubuntu.com/">Ubuntu</a> install needs some coaxing:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo "net.ipv4.ip_unprivileged_port_start=80" | sudo tee -a /etc/sysctl.conf
sudo sysctl --system
</code></pre></div></div>
<p>You can now start as containers as usual: <code class="language-plaintext highlighter-rouge">docker-compose up</code></p>
<h2 id="as-needed-configure-additionally-software">As needed, Configure Additionally Software</h2>
<p>If you plan to run some things in the container without docker, you may need some extra tools, e.g.:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install npm
</code></pre></div></div>
<p>If you’ve transfered source code, the stored configuration may have environment specific tools which need cleared. For example the <code class="language-plaintext highlighter-rouge">package-lock.json</code> should not be checked in from Linux until you’ve standardized, or it could break Mac installations and the <code class="language-plaintext highlighter-rouge">node_modules</code> should be re-installed.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rm -r ./node_modulesrm package-lock.json
npm install
</code></pre></div></div>
<p>You can now start non-docker projects as well, e.g. <code class="language-plaintext highlighter-rouge">npm start</code></p>
<h2 id="finding-ip-addresses">Finding IP Addresses</h2>
<p>Routing could have some new complexity now that the stack has changed. Finding the right IP addresses could pose some challenges. Tinkering will be required, here are some tips.</p>
<p>To see the IP addresses, and other information on the running virtual machines:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>multipass info --all
</code></pre></div></div>
<p align="center">
<img src="/images/multipass-info-all.png" />
</p>
<p>And on the mac:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ifconfig
</code></pre></div></div>
<p align="center">
<img src="/images/ifconfig.png" />
</p>
<p>In some instances, <code class="language-plaintext highlighter-rouge">host.docker.internal</code> and the magic <code class="language-plaintext highlighter-rouge">host-gateway</code> could be useful in your <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> files:</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3'</span>
<span class="na">services</span><span class="pi">:</span>
<span class="na">nginx</span><span class="pi">:</span>
<span class="na">extra_hosts</span><span class="pi">:</span>
<span class="s">host.docker.internal</span><span class="pi">:</span> <span class="s">host-gateway</span>
</code></pre></div></div>
<p>For trickier bits, an environment variable set from <code class="language-plaintext highlighter-rouge">.env</code> could be useful; more info here: <a href="https://docs.docker.com/compose/environment-variables/">Environment Variables in Compose</a></p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3'</span>
<span class="na">services</span><span class="pi">:</span>
<span class="na">nginx</span><span class="pi">:</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">ENV_SOME_HOST=${ENV_SOME_HOST}</span>
</code></pre></div></div>
<p>A set of example <code class="language-plaintext highlighter-rouge">docker-compose.override.yml</code> files could be useful to store in your repositories as there will likely be differences as staff settle on different solutions.</p>
<h2 id="ssh">SSH</h2>
<p>Since your essentially running virtual machines, you can even setup network access.</p>
<p>In the multipass shell:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo adduser someuser
sudo vi /etc/ssh/sshd_config
</code></pre></div></div>
<p>Ensure settings <code class="language-plaintext highlighter-rouge">PasswordAuthentication yes</code> and <code class="language-plaintext highlighter-rouge">PermitEmptyPasswords no</code> are set.</p>
<p>Restart:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo /etc/init.d/ssh restart
</code></pre></div></div>
<p>Now you can ssh in from the mac:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh someuser@your-vm-ip
</code></pre></div></div>
<p align="center">
<img src="/images/ssh-someuser-your-vm-ip.png" />
</p>
<h2 id="further-reading">Further Reading</h2>
<p>Without the obfuscation of the virtual machine, tools like <code class="language-plaintext highlighter-rouge">ansible</code> can now be used on top of <code class="language-plaintext highlighter-rouge">multipass</code> or <code class="language-plaintext highlighter-rouge">ssh</code> to provison new environments with reusable runbooks. <a href="https://amzn.to/3kUwz1M">Ansible for DevOps: Server and configuration management for humans</a> would be an great read on that topic. Additionally, it’s assumed the reader has familiarity with Linux considering the pre-existing knowledge from Docker, but to learn more about Ubuntu I’d suggest picking up <a href="https://amzn.to/3n2g5aK">Ubuntu Linux Unleashed</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>While Docker Desktop is very convenient, Ubuntu Multipass is likely an upgrade and a swiss army knife for development. The <code class="language-plaintext highlighter-rouge">multipass</code> command can be used to run any number of Linux instances, which can run docker images and other applications or development environments on a Mac. You can even expose a full desktop and access it via remote desktop if you are brave enough. While that’s cool, a great side effect is that each instance is stored in an image file, which can be pre-configured and shared to new and existing engineers.</p>Jim HribarRecently Docker decided to change the Docker Desktop pricing model. While reasonable, it is an additional reoccurring cost for basic tooling that remains easily accessible and free on other platforms. Ubuntu Multipass is one of numerous alternatives that can replace Docker Desktop on Macs and Windows.A Beginner’s Guide to Developing an Addon for World of Warcraft Classic2019-09-25T00:00:00-07:002019-09-25T00:00:00-07:00https://www.jimhribar.com/developing-wow-addons<p>I never experienced <a href="https://worldofwarcraft.com">World of Warcraft</a>. I played other <a href="https://www.blizzard.com">Blizzard</a> games, both before and after its release, including the original <a href="https://en.wikipedia.org/wiki/Warcraft">Warcraft</a> series and <a href="https://en.wikipedia.org/wiki/Diablo_(series)">Diablo</a>. I skipped over WoW for reasons I can’t completely recall. During the hype around “classic” I tried “retail” for a moment and abruptly stopped due to a move to a new home. I was tempted to play again after learning about “vanilla” servers but had already mentally committed myself to wait for the official servers. Classic finally launched on August 26, 2019 and I joined in as to not miss out on the twice in a lifetime experience.</p>
<p>Minutes passed during my group’s first play of the game and the chatter started about all the addons they just had to install. Unlike me they had played on and off for the last fifteen years, both vanilla and retail. Not having a clue, I played for a few sittings before getting the itch. What addons were out there and what they could do for me? Everyone had so much experience I thought it was only fair that I bridge the gap. I installed quite a few addons while feeling like a kid in a candy store. With all my handicaps addressed I was firmly hooked.</p>
<p>Then, out of nowhere, my curiosity got the best of me. <em>How do these things work?!</em></p>
<p>This article assumes you have at least some basic knowledge in software development and are at least interested in how the World of Warcraft addon system works. I’m a software engineer by trade but I’ve never written a game addon before, so it’s not a requirement that you have either. If you’ve used or written a macro in game you’re already ahead.</p>
<h2 id="setting-up-the-development-environment">Setting up the Development Environment</h2>
<p>We’re going to need an editor to develop this addon. Older tutorials will recommend you the most basic of text editors, which will work, but we can do better than that. If you don’t already have a preference I would recommend downloading <a href="https://code.visualstudio.com">Microsoft Visual Studio Code</a>. That should be more than enough for this journey and enable future programming endeavors. I have crossed paths with vscode plugins touting both <a href="https://marketplace.visualstudio.com/search?term=lua&target=VSCode&category=All%20categories&sortBy=Relevance">Lua</a> and <a href="https://marketplace.visualstudio.com/search?term=wow&target=VSCode&category=All%20categories&sortBy=Relevance">World of Warcraft</a> support but the editor is sufficient out of the box for what is needed here. Do explore the available extensions and make your own decision on their usefulness.</p>
<h2 id="creating-a-folder-for-the-addon">Creating a Folder for the Addon</h2>
<p>How you manage your project long term is up to you. I’d recommend using <a href="https://github.com">GitHub</a> to manage your code and only update your local addon directory when you are ready to test a new version, possibly with a deployment script. For now, since we are just getting started, let’s just work out of the <code class="language-plaintext highlighter-rouge">Addons</code> directory.</p>
<p>Provided you haven’t changed the default install location the <code class="language-plaintext highlighter-rouge">World of Warcraft</code> directory will either be located in <code class="language-plaintext highlighter-rouge">C:\Program Files</code> or <code class="language-plaintext highlighter-rouge">C:\Program Files (X86)</code> on Windows or the <code class="language-plaintext highlighter-rouge">/Applications/</code> on a Mac. Depending on what you have installed you will may see <code class="language-plaintext highlighter-rouge">_classic_</code> and <code class="language-plaintext highlighter-rouge">_retail_</code> and <code class="language-plaintext highlighter-rouge">_ptr_</code> folders in that directory. Open <code class="language-plaintext highlighter-rouge">_classic_/Interface/Addons</code> and create a <code class="language-plaintext highlighter-rouge">Sandbox</code> folder. An addon is born!</p>
<h2 id="creating-the-table-of-contents">Creating the Table of Contents</h2>
<p>In the <code class="language-plaintext highlighter-rouge">Sandbox</code> folder create a new text file named <code class="language-plaintext highlighter-rouge">Sandbox.toc</code>. This file provides the information required about the addon to the WoW client. If you’ve ever looked at package.json in a JavaScript project for example, this serves a similar purpose.</p>
<p>Add the following to the file:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## Interface: 11302
## Title: Sandbox
## Notes: A place to experiment.
## Author: haxorjim
## Version: 1.0
Sandbox.lua
</code></pre></div></div>
<p>The correct value for <code class="language-plaintext highlighter-rouge">Interface</code> can be found in game by running the following command: <code class="language-plaintext highlighter-rouge">/run print((select(4, GetBuildInfo())));</code> At the time of writing classic is currently 11302. If you intend on targeting retail or vanilla instead be sure to update that value.</p>
<p>Following that, I believe <code class="language-plaintext highlighter-rouge">Title</code>, <code class="language-plaintext highlighter-rouge">Notes</code>, <code class="language-plaintext highlighter-rouge">Author</code> and <code class="language-plaintext highlighter-rouge">Version</code> are self explanatory. There are quite a few other values that I’ve observed in toc files, I would recommend peeking into other addons to discover those.</p>
<p>The final line <code class="language-plaintext highlighter-rouge">Sandbox.lua</code> is a pointer to where the actual code will be stored. At least one file is required, but for larger plugins multiple files can be referenced here and all will be loaded at runtime.</p>
<p>Out of curiosity I played around with changing directory names, file names and removing more details from the toc file. There wasn’t much that really caused the addon to fail to function. It’s definitely going to be good practice to have a detailed table of contents file, so the above is what I’d recommend as the minimum.</p>
<h2 id="writing-some-code">Writing Some Code</h2>
<p>World of Warcraft addons are written in <a href="https://www.lua.org">Lua</a> which touts itself as “a powerful, efficient, lightweight, embeddable scripting language.” Lua should not be much effort to pick up if you have at least one language under your belt. If not, it will still be an excellent first language to learn.</p>
<p>To begin coding in Lua, create a <code class="language-plaintext highlighter-rouge">Sandbox.lua</code> file in the <code class="language-plaintext highlighter-rouge">Sandbox</code> folder that we created earlier and add the following code:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Sandbox</span> <span class="o">=</span> <span class="p">{</span> <span class="p">}</span>
<span class="k">function</span> <span class="nf">Sandbox</span><span class="p">:</span><span class="n">HelloWorld</span><span class="p">()</span>
<span class="n">message</span><span class="p">(</span><span class="s2">"Hello World!"</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">function</span> <span class="nf">Sandbox</span><span class="p">:</span><span class="n">HideGryphons</span><span class="p">()</span>
<span class="n">MainMenuBarLeftEndCap</span><span class="p">:</span><span class="n">Hide</span><span class="p">()</span>
<span class="n">MainMenuBarRightEndCap</span><span class="p">:</span><span class="n">Hide</span><span class="p">()</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Additionally select one of the following samples of code and add it to the very end of the file:</p>
<p>Option A:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Sandbox</span><span class="p">:</span><span class="n">HelloWorld</span><span class="p">()</span>
</code></pre></div></div>
<p>Option B:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Sandbox</span><span class="p">:</span><span class="n">HideGryphons</span><span class="p">()</span>
</code></pre></div></div>
<h2 id="trying-it-out">Trying It Out</h2>
<p>Time to boot up the game… No playing! Focus! When you enter the game one of two things should happen. If you selected Option A you should see “Hello World!” displayed in a graphical window:</p>
<p align="center">
<img src="/images/message.png" />
</p>
<p>If you selected Option B the results are a bit more mysterious. Where have the gryphons gone?</p>
<p align="center">
<img src="/images/gryphons.png" />
</p>
<p>Congratulations! Your first addon is complete!</p>
<h2 id="beyond-the-basics">Beyond the Basics</h2>
<p>There’s a lot more general programming that can be done within a addon but I won’t be going deep into Lua here at all as there are plenty of books already available to reference for the subject. There are even books such as <a href="https://amzn.to/3cd3OHS">Beginning Lua with World of Warcraft Add-ons</a> and <a href="https://amzn.to/3b2EiF3">World of Warcraft Programming: A Guide and Reference for Creating WoW Addons</a> specifically for World of Warcraft.</p>
<p>Finding out how to create or modify different elements in the WoW client is challenging and requires knowledge of the available API. The best resources for the API I’ve found so far are <a href="https://wow.gamepedia.com">Wowpedia</a> and <a href="https://wowwiki.fandom.com">WoWWiki</a>.</p>
<p>Additionally a great way to learn how to do things is to take a look at Lua files in existing addons either that you have installed or those available on the web. Do be advised, mileage will vary with what is supported in different World of Warcraft client versions.</p>
<h2 id="limitations--workarounds">Limitations & Workarounds</h2>
<p>There were a couple of interesting limitations I found while writing this article. The first is to call out to a url to GET or POST data. If it did exist, could have some pretty serious malicious abuses, so I’m not surprised it’s not there. It would have definitely been useful for some slick addons.</p>
<p>Another limitation is the inability to save data to an external file or read from an external file. This isn’t too limiting to addon developers as you have access to saved variables and can load as many Lua files as you please. Some plugins like <a href="https://www.curseforge.com/wow/addons/censusplusclassic">CensusClassicPlus</a> are getting around this limitation by having users upload files from their <code class="language-plaintext highlighter-rouge">SavedVariables</code> folder after logging out of the game.</p>
<p>Despite not being able to communicate directly over the Internet, addons still appear to be able to communicate with each other with some tricks. For example <a href="https://www.curseforge.com/wow/addons/real-mob-health">RealMobHealth</a> and <a href="https://www.curseforge.com/wow/addons/classicthreatmeter">ClassicThreatMeter</a> are using in game chat to send data between users in the current group who also have the addon installed.</p>
<p>Finally, not so much a limitation but a consideration is the variances betwen capabilities in the API between game versions. Since now we have classic, vanilla (which is multiple versions in itself) and retail the API is not always the same. Plugins will need to be tested and coded to work on all platforms, or only on a specific platform as not all are created equal. One way to adapt would be to switch addon executation paths programatically based on the game version retrieved from <code class="language-plaintext highlighter-rouge">GetBuildInfo()</code>.</p>
<h2 id="debugging">Debugging</h2>
<p>While you are developing it will be useful to stay logged into the game. This allows you to switch in and out to make some changes to the addon, run <code class="language-plaintext highlighter-rouge">/reload</code> to restart the UI and continue debugging. Majority of the time your changes will be reflected unless you are doing something with changing files names or the toc file. It will also be useful to execute <code class="language-plaintext highlighter-rouge">/console scriptErrors 1</code> during your coding session so that when Lua errors occur they are displayed immediately. Just don’t forget to turn it off using <code class="language-plaintext highlighter-rouge">/console scriptErrors 0</code> before you go back to normal gameplay. As always, <code class="language-plaintext highlighter-rouge">print</code> and <code class="language-plaintext highlighter-rouge">message</code> functions will be your friend! Finally, there are also some interesting looking addons that may be of additional assistance including <a href="https://www.curseforge.com/wow/addons/bugsack">BugSack</a> and <a href="https://www.curseforge.com/wow/addons/bug-grabber">BugGrabber</a> which “eases the process of viewing bugs”.</p>
<h2 id="testing">Testing</h2>
<p>While your addon may not grow large enough to justify any robust testing patterns it is a good idea to consider at least some basic testing strategies before things turn into spaghetti. The structure that was snuck into the example addon it allows for manual testing of functions by running manual commands, for example:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/run Sandbox:HelloWorld()
</code></pre></div></div>
<p>A surprising option that is available is <a href="https://www.curseforge.com/wow/addons/wow-unit">WowUnit</a> which “allows you to easily write unit tests for your World of Warcraft addons and provides an interface to monitor them”. This is probably the best choice for the majority of addons that require testing.</p>
<p>Lastly, for addons with considerable internal logic, it is very possible Lua itself could be used to test the addon without requiring the WoW client to be running. This would require mocking out parts of the WoW API and using a Lua interpreter to run your tests. While the most advanced option it would also be the most extendable.</p>
<h2 id="publishing">Publishing</h2>
<p>I assumed addons would need to manually acquired and installed by hand. However the <a href="https://www.twitch.tv">Twitch</a> client can automatically install addons for you as well as keep them in sync across machines using their client. You don’t have to be a streamer to use it. It works in conjunction with <a href="https://www.curseforge.com">Curseforge</a>, a directory of addons for multiple games. With millions of downloads for the most popular addons I would definitely recommend publishing to get exposure. In addition I’d again recommend using <a href="https://github.com">GitHub</a> to allow others to collaborate and help maintain your addon. If you end up being inspired to create an addon, here are the instructions for <a href="https://authors.curseforge.com/knowledge-base/projects/99-creating-and-submitting-a-project">creating and submitting a project</a>.</p>
<h2 id="final-thoughts">Final Thoughts</h2>
<p>I started this article to learn more about how addons worked inside World of Warcraft. What I discovered was a robust framework with all the tools I would expect from a development environment available to me. I’ve found myself torn between playing the game and continuing to look under the hood. There are numerous addons already available for WoW but nothing I’ve come across has felt like the end all be all. Much more could be done and there is considerable room for improvement. If the feedback to this article is positive I would absolutely consider writing a series of articles on this topic.</p>Jim HribarI never experienced World of Warcraft. I played other Blizzard games, both before and after its release, including the original Warcraft series and Diablo. I skipped over WoW for reasons I can’t completely recall. During the hype around “classic” I tried “retail” for a moment and abruptly stopped due to a move to a new home. I was tempted to play again after learning about “vanilla” servers but had already mentally committed myself to wait for the official servers. Classic finally launched on August 26, 2019 and I joined in as to not miss out on the twice in a lifetime experience.When a Filter Goes Wrong: Adventures in Async/Await2019-05-22T00:00:00-07:002019-05-22T00:00:00-07:00https://www.jimhribar.com/adventures-in-async-await<p>As happens regularly in software development, I had some partially completed code for a <a href="https://nodejs.org">Node.js</a> script handed to me containing an enhancement to existing logic that I was to pick up and complete. I went upon my way adding the rest of the required changes so that the code could be tested. That’s when things started to get fishy. It was a data processing job so I didn’t have any idea for how long it would take or how demanding it would be. I started it up, thought it might take a few seconds or minutes and began browsing the web. My MacBook started to sound like a commercial airliner. Websites weren’t opening. Applications were getting slower. Something was very wrong. How could this script be so demanding?</p>
<p>I stopped the job and began my investigation. Nothing seemed out of place. I barely added anything. I ran it again. Same thing. I tried altering the logic I added. No luck. So, I did what you would do, I removed the changes and tested the original. Seemed fine. What could this possibly be? What was wrong with the additional code I was given? There wasn’t much too it. Fetch some data from the database. Manipulate it with a standard filter. Call an async method and await it… Async/await. Is it not waiting? Why wouldn’t it wait? Oh, no… It’s one of <em>those</em> bugs.</p>
<p>The following is a generic recreation of the changes that transpired in the code and my eventual revelation.</p>
<h3 id="scenario-1-given-an-array-of-objects-filter-down-to-a-subset">Scenario #1: Given an array of objects, filter down to a subset:</h3>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">values</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">];</span>
<span class="kd">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="nx">values</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">value</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">value</span> <span class="o">==</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">});</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">results</span><span class="p">);</span>
</code></pre></div></div>
<h3 id="output">Output:</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[1, 1, 1, 1]
</code></pre></div></div>
<p>This works. Very common. Expected. If the value is 1, we keep it, otherwise it’s dropped.</p>
<h3 id="scenario-2-given-array-of-objects-filter-down-to-a-subset-using-a-function">Scenario #2: Given array of objects, filter down to a subset using a function:</h3>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">double</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">value</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>
<span class="p">};</span>
<span class="kd">const</span> <span class="nx">values</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">];</span>
<span class="kd">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="nx">values</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">value</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">x</span> <span class="o">=</span> <span class="nx">double</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">x</span> <span class="o">==</span> <span class="mi">4</span><span class="p">;</span>
<span class="p">});</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">results</span><span class="p">);</span>
</code></pre></div></div>
<h3 id="output-1">Output:</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[2, 2, 2, 2, 2, 2]
</code></pre></div></div>
<p>This still works as expected and should not be a surprise with a standard function call. If twice the value is 4, we keep it, otherwise it’s dropped.</p>
<h3 id="scenario-3-given-array-of-objects-filter-down-to-a-subset-using-an-asynchronous-function">Scenario #3: Given array of objects, filter down to a subset using an asynchronous function:</h3>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">double</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">(</span><span class="nx">resolve</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">setTimeout</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">resolve</span><span class="p">(</span><span class="nx">value</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span>
<span class="p">},</span> <span class="mi">5000</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">};</span>
<span class="kd">const</span> <span class="nx">values</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">];</span>
<span class="kd">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="nx">values</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">value</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">x</span> <span class="o">=</span> <span class="nx">double</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">x</span> <span class="o">==</span> <span class="mi">4</span><span class="p">;</span>
<span class="p">});</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">results</span><span class="p">);</span>
</code></pre></div></div>
<h3 id="output-2">Output:</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[]
</code></pre></div></div>
<p>This code shouldn’t work for filtering and didn’t work. If you are familiar with asynchronous code at all you knew that right away. The result of the function call is not going to return the value we want, it’s a Promise and its messing up our condition. The developer that was working on the code knew that as well. If you run this code yourself you will see the output is displayed almost immediately and then after some delay the program completes. This is because the asynchronous operations are still firing even thought the filter method has returned.</p>
<h3 id="scenario-4-given-array-of-objects-filter-down-to-a-subset-using-an-asynchronous-function-with-asyncawait-applied">Scenario #4: Given array of objects, filter down to a subset using an asynchronous function with async/await applied:</h3>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">double</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">(</span><span class="nx">resolve</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">setTimeout</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">resolve</span><span class="p">(</span><span class="nx">value</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span>
<span class="p">},</span> <span class="mi">5000</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">};</span>
<span class="kd">const</span> <span class="nx">values</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">];</span>
<span class="kd">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="nx">values</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="k">async</span> <span class="nx">value</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">double</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">x</span> <span class="o">==</span> <span class="mi">4</span><span class="p">;</span>
<span class="p">});</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">results</span><span class="p">);</span>
</code></pre></div></div>
<h3 id="output-3">Output:</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[1, 2, 1, 2, 1, 2, 1, 2, 2, 2];
</code></pre></div></div>
<p>Huh? That’s the original set of values. The delay is still present. The developer assumed, as I also willingly accepted at the time, that adding async/await was sufficient to wait on the Promise to resolve. It’s not as you can see from the output. It appeared to have a different impact altogether. Nothing I tried would get it to work. The code was being interpreted, but the calls were firing off as fast as they could not waiting for the completion of the last, crippling my machine. The async inside the filter seemed odd to me, but I wasn’t sure why. It <em>was</em> running after all. What followed was a considerable amount of random searching and exponentially more denial but I came to eventually accept that filter does not support async/await. It’s a synchronous function, so even my misguided attempt to add another await outside the filter just confirmed how naive I was. The interpreter finally generated an error.</p>
<p>Being a dead end, I regressed back to a less elegant solution that I describe next. By no means do I love it, but it is at least readable to us mere mortals. Happenstance, this was the last revision to the code as we changed our approach to the job and all this was all removed anyways. There’s likely a clean filter-like solution that uses async/await out there, somewhere, but that is left as an exercise for the reader.</p>
<h3 id="solution-given-array-of-objects-filter-down-to-a-subset-using-an-asynchronous-function-with-asyncawait-applied-inside-a-for-loop">Solution: Given array of objects, filter down to a subset using an asynchronous function with async/await applied inside a for loop:</h3>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">double</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">(</span><span class="nx">resolve</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">setTimeout</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">resolve</span><span class="p">(</span><span class="nx">value</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span>
<span class="p">},</span> <span class="mi">5000</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">};</span>
<span class="kd">const</span> <span class="nx">values</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">];</span>
<span class="kd">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="p">[];</span>
<span class="kd">const</span> <span class="nx">job</span> <span class="o">=</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">value</span> <span class="k">of</span> <span class="nx">values</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">((</span><span class="k">await</span> <span class="nx">double</span><span class="p">(</span><span class="nx">value</span><span class="p">))</span> <span class="o">==</span> <span class="mi">4</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">results</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="nx">job</span><span class="p">().</span><span class="nx">then</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">results</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>
<h3 id="output-4">Output:</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ 2, 2, 2, 2, 2, 2 ]
</code></pre></div></div>
<p>Great, it finally works. A traditional for loop that builds up a secondary array. It slow and it makes me sad.</p>
<p>Why filter can’t come out of the box able to await passed in async functions, is beyond me, but the limitations are real. The biggest shock to me throughout this whole learning process though was that there was not a single tool that detected the misusage. <a href="https://eslint.org">ESLint</a> didn’t identify it. <a href="https://code.visualstudio.com">Visual Studio Code</a> had no input for me. I even installed <a href="https://www.jetbrains.com/webstorm/">WebStorm</a>, the self-proclaimed “Smartest JavaScript IDE” while writing this and unfortunately no, nothing helpful there. So there’s only one thing I can proclaim with confidence to you now and forever: Test! Your! Code!</p>Jim HribarAs happens regularly in software development, I had some partially completed code for a Node.js script handed to me containing an enhancement to existing logic that I was to pick up and complete. I went upon my way adding the rest of the required changes so that the code could be tested. That’s when things started to get fishy. It was a data processing job so I didn’t have any idea for how long it would take or how demanding it would be. I started it up, thought it might take a few seconds or minutes and began browsing the web. My MacBook started to sound like a commercial airliner. Websites weren’t opening. Applications were getting slower. Something was very wrong. How could this script be so demanding?How to Implement Adsense Auto Ads on Jekyll Posts2019-05-19T00:00:00-07:002019-05-19T00:00:00-07:00https://www.jimhribar.com/jekyll-autoads<p>I’ve made a commitment to reboot my blog, but needed some additional motivation to actually start writing. Money tends to be a good motivator. I figured I’d give <a href="https://www.google.com/adsense/">Google Adsense</a> a chance. I don’t expect to make much, if anything, in the beginning, but over time I’m hopeful that this could begin to generate some passive income.</p>
<p>Like many people, I’m guilty of using ad blockers myself, so mileage may vary, but as a user, I get why they do this - too many advertisements. With this in mind I didn’t want to pollute my entire site with ads but instead only include them on my posts themselves. This way, I’m only monetizing my actual content, which, I think is a fair use case and might encourage a percentage of users to not block my ads as my site grows.</p>
<p>For the actual implementation I chose to go with the newer <a href="https://support.google.com/adsense/answer/7478040">Auto ads</a> feature. Traditionally you would be required to choose the type of advertisement and placement on your site, but it’s 2019. We have machines for that. Auto ads now can make all the choices for you thanks to machine learning. You can still opt out of different types of ads if you wish, but in theory, the best ad type and placement will be automatically chosen for each page.</p>
<p>When I rebooted the blog I went with <a href="https://jekyllrb.com">Jekyll</a>, so this approach applies specifically to that platform, but should be very similar for others as well.</p>
<p>The steps to implement Auto ads only on posts in Jekyll is as follows:</p>
<ol>
<li>
<p>Update the <code class="language-plaintext highlighter-rouge">_config.yml</code> file to include a new variable <code class="language-plaintext highlighter-rouge">autoads</code> that is <code class="language-plaintext highlighter-rouge">true</code> by default only for post pages. NOTE: depending on your theme, this section may not already exist.</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">defaults</span><span class="pi">:</span>
<span class="c1"># _posts</span>
<span class="pi">-</span> <span class="na">scope</span><span class="pi">:</span>
<span class="na">path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">posts</span>
<span class="na">values</span><span class="pi">:</span>
<span class="na">layout</span><span class="pi">:</span> <span class="s">single</span>
<span class="na">author_profile</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">read_time</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">comments</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">share</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">related</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">autoads</span><span class="pi">:</span> <span class="no">true</span> <span class="c1"># like this</span>
</code></pre></div> </div>
</li>
<li>
<p>Update the <code class="language-plaintext highlighter-rouge">_includes/head.html</code> file to include the standard snippet from Adsense but also wrap the block with an if statement using the <a href="https://shopify.github.io/liquid/">Liquid</a> syntax.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{% if page.autoads %}
<span class="nt"><script </span><span class="na">async</span> <span class="na">src=</span><span class="s">"//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"</span><span class="nt">></script></span>
<span class="nt"><script></span>
<span class="p">(</span><span class="nx">adsbygoogle</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">adsbygoogle</span> <span class="o">||</span> <span class="p">[]).</span><span class="nx">push</span><span class="p">({</span>
<span class="na">google_ad_client</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ca-pub-################</span><span class="dl">"</span><span class="p">,</span>
<span class="na">enable_page_level_ads</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">});</span>
<span class="nt"></script></span>
{% endif %}
</code></pre></div> </div>
</li>
</ol>
<p>That’s it. If instead you wanted ads on all pages, there’s no need for the if condition or step one. If you want ads on multiple page types, just default the <code class="language-plaintext highlighter-rouge">autoads</code> variable to <code class="language-plaintext highlighter-rouge">true</code> for those pages as well. If you have a similar script you want to use only on certain pages, the same steps hold true for that as well. The sky is the limit.</p>Jim HribarI’ve made a commitment to reboot my blog, but needed some additional motivation to actually start writing. Money tends to be a good motivator. I figured I’d give Google Adsense a chance. I don’t expect to make much, if anything, in the beginning, but over time I’m hopeful that this could begin to generate some passive income.