<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>The Patcave</title>
    <link href="https://patcave.dev/feed.xml" rel="self" />
    <link href="https://patcave.dev" />
    <updated>2023-07-21T06:09:05-06:00</updated>
    <author>
        <name>Patrick Hennessy</name>
    </author>
    <id>https://patcave.dev</id>

    <entry>
        <title>Sharkbay!</title>
        <author>
            <name>Patrick Hennessy</name>
        </author>
        <link href="https://patcave.dev/sharkbay/"/>
        <id>https://patcave.dev/sharkbay/</id>
            <category term="Level Design"/>

        <updated>2023-07-21T06:09:05-06:00</updated>
            <summary>
                <![CDATA[
                        <img src="https://patcave.dev/media/posts/17/Screenshot_2.png" alt="" />
                    <p>I got another map accepted by Valve for Team Fortress 2! I'd like to just showcase some of the work I did for the map here.</p>

                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                    <img src="https://patcave.dev/media/posts/17/Screenshot_2.png" alt="" />
                <p>I got another map accepted by Valve for Team Fortress 2! I'd like to just showcase some of the work I did for the map here.</p>

<p>The map is called <a href="https://steamcommunity.com/sharedfiles/filedetails/?id=2404226979">Sharkbay</a>, and the layout was done by <a href="https://tf2maps.net/members/billo.22663/">Billo</a>. This map has been well liked in the community for a long time, so I was happy to be able to be a part of it!</p>
<p>The map is a basic lobby-style <a href="https://wiki.teamfortress.com/wiki/King_of_the_Hill">King of the Hill</a> map that takes place in a New York harbor that has become infested with sharks.</p>
<hr>
<h2>Signs</h2>
<p>I've recently become sort of obsessed with signs and their ability to be created fairly easily and you can hide jokes and Easter eggs in them while giving your map an extra little polish. All of these were done 100% using <a href="https://www.adobe.com/products/substance3d-designer.html">Substance Designer</a>, which has quickly become my favorite program.</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-translation="Add images" data-columns="4">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/17//gallery/sign001.png" data-size="512x512"><img loading="lazy" src="https://patcave.dev/media/posts/17//gallery/sign001-thumbnail.png" alt="" width="512" height="512"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/17//gallery/sign002.png" data-size="512x512"><img loading="lazy" src="https://patcave.dev/media/posts/17//gallery/sign002-thumbnail.png" alt="" width="512" height="512"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/17//gallery/sign009.png" data-size="512x512"><img loading="lazy" src="https://patcave.dev/media/posts/17//gallery/sign009-thumbnail.png" alt="" width="512" height="512"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/17//gallery/sign010.png" data-size="512x512"><img loading="lazy" src="https://patcave.dev/media/posts/17//gallery/sign010-thumbnail.png" alt="" width="512" height="512"></a></figure>
</div></div>
<p>Another thing I have always tried to make a point to do is to add little calling cards for the mappers involved in the project. Here's the one's I made for myself and Billo:</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-translation="Add images" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/17/gallery/sign005-2.png" data-size="512x512"><img loading="lazy" src="https://patcave.dev/media/posts/17/gallery/sign005-2-thumbnail.png" alt="" width="512" height="512"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/17/gallery/sign003-2.png" data-size="512x512"><img loading="lazy" src="https://patcave.dev/media/posts/17/gallery/sign003-2-thumbnail.png" alt="" width="512" height="512"></a></figure>
</div></div>
<hr>
<h2>Models</h2>
<p>I was originally brought on to make a single model; the shark cage prop:</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-translation="Add images" data-columns="1">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/17/gallery/Screenshot_2.png" data-size="1919x1079"><img loading="lazy" src="https://patcave.dev/media/posts/17/gallery/Screenshot_2-thumbnail.png" alt="" width="720" height="405"></a></figure>
</div></div>
<p>This was one of my first bespoke models that I made 100% from scratch, and I was quite happy with how it turned out. Here is a video timeplase of me creating it!</p>
<figure class="post__video"><iframe loading="lazy" width="560" height="314" src="https://www.youtube.com/embed/aLeUEFKqQS8" allowfullscreen="allowfullscreen" data-mce-fragment="1"></iframe></figure>
<hr>
<p>This is incredibly nerdy, but thanks to the help another artist named Tianes, I learned how to do diffuse baking in Blender and unleash the power of my procedural ropes I had designed for TF2. I can basically make any shape rope I want using a spline system in Blender incredibly quickly.</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-translation="Add images" data-columns="1">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/17/gallery/Screenshot_4.png" data-size="1919x1079"><img loading="lazy" src="https://patcave.dev/media/posts/17/gallery/Screenshot_4-thumbnail.png" alt="" width="720" height="405"></a></figure>
</div></div>
<hr>
<p>A rather large prop I did that has probably gone unnoticed was the garage door at mid. The brushwork version of this door like really bad so I just had to make it a prop. It's pretty simple, just 2 sliding doors, sort of rusty. The fact that it's gone unnoticed I think is a good thing because it means it fits the space well and reads as a door. </p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-translation="Add images" data-columns="1">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/17/gallery/Screenshot_5.png" data-size="1919x1079"><img loading="lazy" src="https://patcave.dev/media/posts/17/gallery/Screenshot_5-thumbnail.png" alt="" width="720" height="405"></a></figure>
</div></div>
<hr>
<h2>Soundscape</h2>
<p>I have never done a soundscape design before so this was a fun challenge. Basically none of the existing soundscapes matched the environment particularly well. I need a mix of watery, industrial, boat fog horns and seagull sounds. Thankfully all these sounds already existed in TF2's ambient sound files so it was just about mixing them correctly.</p>
<p>I also learned you can specify targets in the soundscape so the sounds come from a particular location in the map. This worked very well for setting direction to the fog horns and seagulls, as well as the faint car honking.</p>
<figure class="post__audio"><audio loading="lazy" controls="controls" src="https://cdn.discordapp.com/attachments/1118150163266023434/1119245487606878208/sharkbay_outside.mp3" data-mce-fragment="1"></audio></figure>
<hr>
<h2>Logic</h2>
<p>A lot of people have noticed the shark kill counter, this was one of those, "you know what would be cool?" things we were just spit balling. </p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-translation="Add images" data-columns="1">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/17/gallery/Screenshot_3.png" data-size="1919x1079"><img loading="lazy" src="https://patcave.dev/media/posts/17/gallery/Screenshot_3-thumbnail.png" alt="" width="720" height="405"></a></figure>
</div></div>
<p>Basically any time someone falls in the water and is killed by a shark, this number increments. This is a bit complicated how it actually works using Source logic but I'll briefly try to explain.</p>
<p>The heavy lifting is done by a <a href="https://developer.valvesoftware.com/wiki/Material_proxies">material proxy</a> called "<a href="https://developer.valvesoftware.com/wiki/Env_texturetoggle">ToggleTexture</a>" where you can fire an input to the <a href="https://developer.valvesoftware.com/wiki/Info_overlay">info_overlay</a> entity and cause it to change to the next frame in the VTF. Shout out to <a href="https://tf2maps.net/members/adam2.33350/">Adam</a> for helping me figure that part out!</p>
<p>So I created 2 VTF's, one with digits 1-9, and a second with blank-9. Then all I need is a single <a href="https://developer.valvesoftware.com/wiki/Math_counter">math_counter</a> to bump the second digit every 10 kills, and second math counter to track total kills so we can clamp it to 99.</p>
<p>Next there is an issue of <a href="https://en.wikipedia.org/wiki/Z-fighting">z-fighting</a> between info_overlays. Basically the sign on the brush was fighting with the 2 digits, so my solution was incredibly stupid; I created a 1hu thick <a href="https://developer.valvesoftware.com/wiki/Func_detail">func_detail</a> that as a totally blank texture on it, as in 100% white with a 100% alpha channel and applied the 2 digit overlays to that.</p>
<hr>
<p>Another thing I did was the flying seagulls.</p>
<p>The map has 3 different seagulls that fly. The one sitting on the derrick takes off when the point is capped, as if to imply he is annoyed by the ruckus caused by the point cap that he flies off. Don't worry though, he returns to his perch!</p>
<p>These are actually just retextured Half-Life 2 seagulls, so we get the full benefit of the seagull animations which there is a surprising amount of. Then all I needed to do was set up a <a href="https://developer.valvesoftware.com/wiki/Func_tracktrain">tracktrain</a> and a path of them to follow and periodically have them change animations when they pass certain path nodes. I'll say though, aligning the seagull to the one perched on the derrick was really hard because the tracktrain sometimes moves the model in a way that isn't very precise, but it works well enough I suppose.</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-translation="Add images" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/17/gallery/Screenshot_6.png" data-size="1919x1079"><img loading="lazy" src="https://patcave.dev/media/posts/17/gallery/Screenshot_6-thumbnail.png" alt="" width="720" height="405"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/17/gallery/Screenshot_8.png" data-size="1919x1052"><img loading="lazy" src="https://patcave.dev/media/posts/17/gallery/Screenshot_8-thumbnail.png" alt="" width="720" height="395"></a></figure>
</div></div>
<hr>
<h2>Menu Photo Render</h2>
<p>I've always loved the skeuomorphic aesthetic of a lot of things in TF2; so I wanted to do something spicy for the menu photos for the map. This was done in Blender using a simple orthographic camera and a point light. The newspaper itself was created by Blaholtzen. This turned out great and it's really unique among menu photos in game.</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-translation="Add images" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/17/gallery/Screenshot_1.png" data-size="1919x1079"><img loading="lazy" src="https://patcave.dev/media/posts/17/gallery/Screenshot_1-thumbnail.png" alt="" width="720" height="405"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/17/gallery/Screenshot_7.png" data-size="1919x1056"><img loading="lazy" src="https://patcave.dev/media/posts/17/gallery/Screenshot_7-thumbnail.png" alt="" width="720" height="396"></a></figure>
</div></div>
            ]]>
        </content>
    </entry>
    <entry>
        <title>It&#x27;s 2022 and front-end is still a mess</title>
        <author>
            <name>Patrick Hennessy</name>
        </author>
        <link href="https://patcave.dev/its-2022-and-the-frontend-is-still-a-mess/"/>
        <id>https://patcave.dev/its-2022-and-the-frontend-is-still-a-mess/</id>
            <category term="Programming Stuff"/>

        <updated>2023-01-12T02:47:07-07:00</updated>
            <summary>
                <![CDATA[
                        <img src="https://patcave.dev/media/posts/16/frontend.png" alt="" />
                    <p>This is just a short rant I guess. Recently, I've wanted to work on some projects requiring me to write a web front-end and every time I revisit doing this, I'm left feeling very frustrated and defeated.</p>

                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                    <img src="https://patcave.dev/media/posts/16/frontend.png" alt="" />
                <p>This is just a short rant I guess. Recently, I've wanted to work on some projects requiring me to write a web front-end and every time I revisit doing this, I'm left feeling very frustrated and defeated.</p>
<hr>
<h2 id="i-actually-like-frontend">I actually like front-end</h2>
<p>Not something you normally hear from people that spend their time writing deploy pipelines and VPC network infrastructure. I've always liked the creative aspect of web design and working on visual mediums. At one point I thought I'd be doing front end dev, but quickly decided to not pursue it after working on front-end stuff at Carfax for about a year. </p>
<p> </p>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://patcave.dev/media/posts/16/jquery-logo-vertical_large_square.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/16/responsive/jquery-logo-vertical_large_square-xs.png 300w ,https://patcave.dev/media/posts/16/responsive/jquery-logo-vertical_large_square-sm.png 480w ,https://patcave.dev/media/posts/16/responsive/jquery-logo-vertical_large_square-md.png 768w ,https://patcave.dev/media/posts/16/responsive/jquery-logo-vertical_large_square-lg.png 1024w ,https://patcave.dev/media/posts/16/responsive/jquery-logo-vertical_large_square-xl.png 1360w ,https://patcave.dev/media/posts/16/responsive/jquery-logo-vertical_large_square-2xl.png 1600w"  alt="" width="130" height="130"></figure>At that time, front-end was mostly dominated by jQuery plugins and crazy over-engineered MVC frameworks. Around this time, Bootstrap was released, along with a bunch of other CSS / JS frameworks that were <em>supposed </em>to help make development faster. They kind of did, but also introduced a lot of issues with customizing things to your needs that you were probably better off writing your own library for you own needs. </p>
<p>I left front-end dev because of just how painful it was to do, and it has always felt so needlessly difficult. Like how many of us have had the experience of "how do I center this fuckin div!". <em>Why</em> is that so hard? Back then we only aligned stuff using floats or tables. I guess in short, the web was just too immature and clunky to my liking. Even today, I feel like you have to be something of a masochist to actually want to do front-end.</p>
<p>As a programmer, I thoroughly enjoy the fact that other people write high quality libraries so that I can just reuse their code and not have to think about those problems and just get going with my project. This is true for most things I do with DevOps and backend libraries in Python. Those libraries / tools work great most of the time. I really wish I could say the same for the frontend...</p>
<hr>
<h2 id="brhow-it-feels-to-be-a-frontend-dev-in-2022">How it feels to do front-end in 2022</h2>
<p>You have a new project and your colleague told you that React is good and an industry standard. So you spend the next hour just trying to choose one out of literally thousands of component libraries to use. </p>
<figure class="post__image post__image--center"><img loading="lazy"  src="https://img-9gag-fun.9cache.com/photo/aXn9nd9_460s.jpg" data-is-external-image="true"  alt="" width="460" height="460"></figure>
<p>After you spend an hour waiting for NPM to finish installing the <a href="https://medium.com/swlh/modern-react-development-but-without-200-mb-of-node-modules-69d8ca01eacf" target="_blank" rel="noopener noreferrer">200mb of needed NodeJS packages</a>, and you finished reading for another hour on Webpack docs so that you can have a constantly running build pipeline locally because this framework uses MochaScript; a way "better" version of JavaScript apparently that has to be compiled. Now you can finally start a local webserver with the convenient and never broken <code>npm start dev</code> command!</p>
<p>If by some miracle you get that working in less than a half day, now you get finally use these components! Wait, the docs don't have copy and passable code? It's no problem; you just have to dig through lots of stack overflow posts to find an example that you can tailor to your needs.<br><br>I for one am glad that the frontend community adheres to common conventions. After all; what really is the semantic difference between a Modal, Dialog, Popup and Popover.</p>
<p>So after a while of thoroughly reading the docs and StackOverflow, you finally figured out what to do. You start writing your app, and get pretty far. Ah but your compontent library is missing a component that you need.</p>
<p>Do you...</p>
<ul>
<li>Switch to a new component library and rewrite everything from scratch?</li>
<li>Try to import just the components you need and make a frakenframework of sorts?</li>
<li>Write your own component?</li>
</ul>
<p>Writing your own component is sooooo easy. Just use this code generator that will make 30 files for you, and be sure to be up to date on the latest JavaScript, React, JSX and CSS features otherwise you'll be completely lost.<br><br>Wait none of that matters now; a new thing came out this week!</p>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://blog.webix.com/wp-content/uploads/2021/07/13.jpg" data-is-external-image="true"  alt="" width="329" height="329"></figure>
<p>The library you using has decided to completely switch underlying tools and dependencies, now you have to decide if you will update to this standard; and they gave you 1 week notice of this change. Most of the code you wrote will have to be significantly changed and the old version of this framework becomes effectively abandoned so you never get updates or security patches. Foolish mortal; you thought MochaScript was the perfect JavaScript? Behold CoffeeScript! Its like MochaScript, but slower and with questionable syntactic sugar.</p>
<p> </p>
<pre><code class="language-javascript">exampleCode().
  showing(aThing(), thisLibrary.does())
  .justIgnore(
    () -&gt; theSyntax()
);
</code></pre>
<p>So readable.</p>
<p>Thank goodness they added these chained call backs, I don't know how people coded before this! I hope I don't have issues compiling back to ES6. Don't sweat it too much though, wait another few months and they'll be using MelonScript or SuperScript, or some new flavor that will solve all it's problems. </p>
<p>Now your app has been in production for a few months and you need to fix some bugs; requiring you to update your library version. Well now you can't even do that because the package maintainer <a href="https://techcrunch.com/2022/07/27/protestware-code-sabotage/" target="_blank" rel="noopener noreferrer">took the library off the internet and replaced it with bitcoin mining software to protest Climate Change</a>.</p>
<hr>
<h2 id="satire-aside">Roasting Javascript Aside...</h2>
<p>I admit that these issues are not exclusive to JavaScript / Frontend dev. Though it feels especially bad in frontend because of how quickly things are changing. And to what end? Sometimes I really wonder; have we not found the right abstractions to write engaging and pretty user interfaces? Why is it <em>this </em>difficult just to get a basic dynamic page working?</p>
<p>And for fuck sake, can we get a language other than JavaScript to use on the frontend? A lot of these "JavaScript but better" languages that require transpilers have sprung up to attempt to fix the issues with JavaScript itself, which is sort of an interesting way to test new language features and ideas; but good lord JavaScript has the highest density of footgun's of any language I've ever used. I'm sure the reason they don't just make JavaScript not full of foot guns is that they are trying to maintain reverse compatibility.</p>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/1f/WebAssembly_Logo.svg/1200px-WebAssembly_Logo.svg.png" data-is-external-image="true"  alt="" width="85" height="85"></figure>
<p>I know that <a href="https://webassembly.org/" target="_blank" rel="noopener noreferrer">WebASM</a> is supposed to be at some point allow us to solve this issue of a single language dominating the front-end, but we really haven't seen much practical things from that effort yet; outside of cool <a href="https://media.tojicode.com/q3bsp/" target="_blank" rel="noopener noreferrer">OpenGL ports</a>.</p>
<p>I loved the simplicity of the old web. You can super quickly prototype something on a local web page. You didn't need to worry about package managers, CDN's, or running a web server. </p>
<p>From a DevOps perspective; I will go very very very far out of my way to not have to run a production NodeJs server. I've had numerous horrendous experiences in managing and troubleshooting production NodeJs servers. I'd much rather stick with the devil I know, like NginX. Again I yearn for the simpler times when you just served an HTML page and didn't have so much complexity to deal with. Browser default components don't have any of these issues, why can't we have more of those?</p>
<p>I've been following the evolution of front-end for a long time. I don't think front-end developers realize just how complex they have made their ecosystem. And I'd say, quite needlessly so. For something as huge as Facebook, that level of complexity is probably warranted; but the level of complexity that Facebook uses is the only level. React is React. There is not really a lower level of complexity for smaller teams or hobby projects. One that doesn't require WebPack, NodeJS server, JestJs tests, code generation, tree shaking, CSS precompliation, etc etc. One where you can just get to the heart of what you're actually trying to do, which is make a fucking usable component.</p>
<hr>
<h2 id="web-components">Web Components?</h2>
<p>The thing that I think frameworks like React get right is the component encapsulation model and in principal I quite like the React way of doing this, it's just all the ecosystem surrounding it that is really painful and a huge turn off to me and the tools are individually so complex and never work out of the box for me.</p>
<p>When React rose to popularity in ~2015 (which is right around the time when I left Carfax and moved on to DevOps) the W3C had been working to develop and implement a way to do what React does, natively in the browser. </p>
<p>This is usually referred to as <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components" target="_blank" rel="noopener">Web Components</a>; but is really the combination of 3 features to the web standard:</p>
<ul>
<li>Custom Elements</li>
<li>Shadow DOM</li>
<li>HTML Templates and Slots</li>
</ul>
<p>I quite like the idea of having these things natively. It gives me the thing that I want which is ease of development. I don't need build frameworks, or Webpack or tree shakers or anything like that. All you need to do is include a JavaScript file on your page and you're done. This seems like the level of complexity I'm looking for.<br><br>As of 2018, all major browsers support all three of these standards natively which means that native custom components have arrived.</p>
<hr>
<h2 id="authoring-web-componentsbr">Authoring Web Components</h2>
<p>These native API's are a bit clunky to use, or should I say are low level. It makes sense, to a degree, that they would do it that way because it allows for libraries to sit on top that expose different abstractions tailored to certain needs. But I fear this gets us back to the issues from before where these libraries will bring in elaborate build systems and complex bundlers and such.</p>
<p>With that said, I've experimented with a few libraries, as well as </p>
<ul>
<li><a href="https://stenciljs.com/" target="_blank" rel="noopener">StencilJS</a></li>
<li><a href="https://www.fast.design/" target="_blank" rel="noopener">Fast</a></li>
<li><a href="https://lit.dev/">Lit</a> (Formerly Polymer)</li>
</ul>
<p>I quite like Stencil, but am a bit annoyed that it forces you to exclusively use Typescript. It is essentially a toolchain for "compiling" Typescript as well as the HTML and CSS into a native web component. The thing I like the most about it is that it includes the primitives for hydration and minifying bundles. I could use this tool right out of the box. Or put another way; you can experience success in 5 minutes with this tool; which is more than can be said about React. However, in general, I'm quite wary of complex build chains, and indeed if there was some kind of underlying problem with Stencil's use of WebPack, I'd be at a complete loss at how to fix it.</p>
<p>I also quite like Lit, though it doesn't come with all the same nice batteries that Stencil does. Normally this would be a trade off of bundle size but infact Stencil and Lit will produce bundles of basically the same size since they will both produce an artifact that is just the native component with a thin abstraction layer over it. Though all I really care about for small projects is just being able to author a component quickly. Lit is basically just a thin abstraction on top of the native API's. I won't really know which of these I prefer until I really dig into a project using them.</p>
<hr>
<h2>Native Component Libraries</h2>
<p>I've searched pretty thoroughly across the web for anyone authoring native web components and here's all the ones I've found:</p>
<ul>
<li><a href="https://shoelace.style/" target="_blank" rel="noopener noreferrer">Shoelace</a></li>
<li><a href="https://nordhealth.design/components/" target="_blank" rel="noopener noreferrer">Nord</a></li>
<li><a href="https://crayons.freshworks.com/" target="_blank" rel="noopener noreferrer">Crayons</a> by Freshworks</li>
<li><a href="https://kickstand-ui.com/" target="_blank" rel="noopener noreferrer">Kickstand</a></li>
<li><a href="https://emdgroup-liquid.github.io/liquid/" target="_blank" rel="noopener noreferrer">Liquid Oxygen</a></li>
<li><a href="https://component.kitchen/elix" target="_blank" rel="noopener noreferrer">Elix</a></li>
<li><a href="https://goatui.com/" target="_blank" rel="noopener noreferrer">GoatUI</a></li>
<li><a href="https://vaadin.com/docs/latest/components" target="_blank" rel="noopener noreferrer">Vaadin</a></li>
</ul>
<p>I've dabbled with all of these, and the one I like the most is Shoelace. It seems to be the most batteries included, but also customizable (via "design tokens") of the lot.</p>
<p>To me these are basically the next evolution of Bootstrap. Gives you a lot of stuff out of the box, but this much less boilerplate-y to use, and has the styles encapsulated. </p>
<p>For some upcoming projects; I think I will likely make use of Shoelace and then use Lit or Stencil to author custom components that suit my specific needs, and then glue component functionality together with plain old fashioned no frills JavaScript.</p>
<hr>
<h2>Things do seem to be getting better</h2>
<figure class="post__image post__image--right"><img loading="lazy"  src="https://media.makeameme.org/created/waiting-for-things-4ab94153bb.jpg" data-is-external-image="true"  alt="" width="216" height="284"></figure>
<p>With the advent of native web components alongside tons of fantastic new CSS features such as Grid, Flexbox, and variables, it seems like things are heading in a good direction for the web. </p>
<p>Right now I'm just stuck in a bit of a limbo waiting for Web Components and WebASM to really get mainstream adoption and start bearing fruit. React just has so much momentum and adoption that these new standards will take a while to get large scale adoption.</p>
<p> </p>
<p>Though one very nice feature of native components is that you can just use them in React, Vue, Angular, Svelte and basically every React-like framework; so there is a non destruction path to adoption at least.</p>
<p>At least with native web components, we can rely on them to work basically forever. One of my biggest gripes is that with all the frameworks and libraries out there; how many times has someone reinvented the "button" component? There may be times that this needs some specific customized button; but generally its just a box with text. My hope is that native web components will see a lot of reduction of this reinventing the wheel. Just a handful of libraries that give us the tools to customize parts of the element as we need and we're off to the races; or hell, give me high quality, accessible but otherwise unstyled components that I can customize to my brand / needs.</p>
<p>My hope is that one day, the web wont be so painful that I might be able to re-enter the world of the front-end as a professional. For now, I'm fine to watch from the sidelines.</p>
<p> </p>
<p> </p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Managing IAM more sanely</title>
        <author>
            <name>Patrick Hennessy</name>
        </author>
        <link href="https://patcave.dev/managing-iam-more-sanely/"/>
        <id>https://patcave.dev/managing-iam-more-sanely/</id>
            <category term="Programming Stuff"/>

        <updated>2023-01-11T22:36:06-07:00</updated>
            <summary>
                <![CDATA[
                        <img src="https://patcave.dev/media/posts/11/identity-access-management-iam-2.png" alt="" />
                    <p><a href="https://aws.amazon.com/iam/" target="_blank" rel="noopener noreferrer">AWS IAM</a> is the global permissions system that AWS uses. People are often mystified by how it works and often end up just giving everything <code>*.*</code> permissions. </p>
<p>In this post I'll discuss things I learned while managing the IAM configuration at <a href="https://en.wikipedia.org/wiki/Narrative_Science" target="_blank" rel="noopener noreferrer">Narrative Science</a> and hopefully allow you can learn from our failures so you can manage your IAM account in a much for sane and scalable way.</p>

                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                    <img src="https://patcave.dev/media/posts/11/identity-access-management-iam-2.png" alt="" />
                <p><a href="https://aws.amazon.com/iam/" target="_blank" rel="noopener noreferrer">AWS IAM</a> is the global permissions system that AWS uses. People are often mystified by how it works and often end up just giving everything <code>*.*</code> permissions. </p>
<p>In this post I'll discuss things I learned while managing the IAM configuration at <a href="https://en.wikipedia.org/wiki/Narrative_Science" target="_blank" rel="noopener noreferrer">Narrative Science</a> and hopefully allow you can learn from our failures so you can manage your IAM account in a much for sane and scalable way.</p>
<hr>
<h2>IAM Primer</h2>
<figure class="post__image post__image--right"><img loading="lazy"  src="https://cdn.worldvectorlogo.com/logos/aws-iam.svg" data-is-external-image="true"  alt="" width="81" height="155"></figure>IAM is composed of a few primitives:</p>
<ul>
<li>Roles</li>
<li>Policies</li>
<li>Groups</li>
<li>Users</li>
</ul>
<p>Roles, Groups and Users can all have policies attached to them. Groups are composed of multiple Users. For example, you could have a <code>db-admin</code> group that has the common permissions needed for that task, and users can be added or removed easily from it.</p>
<p>Users and Roles are the entities that <a href="https://www.okta.com/identity-101/authentication-vs-authorization/" target="_blank" rel="noopener noreferrer">authenticate</a> with AWS. There's a lot of ways this can be done but most commonly it's either an EC2 instance with a Role applied, or an Employee with an access key.</p>
<p>Policies are where the rubber hits the road. When you author a policy, you can say to allow or deny access to a resource (though <em>not</em> allowing something is an implicit deny).</p>
<p>Here is AWS's official documentation on the subject:<br><a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction_access-management.html">https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction_access-management.html</a></p>
<p>It will also be important to understand ARN's (Amazon Resource Identifiers) as they are used extensively in IAM policy documents:<br><a href="https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html">https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html</a></p>
<hr>
<h2>Security Objectives</h2>
<ul>
<li><a href="https://www.cyberark.com/what-is/least-privilege/" target="_blank" rel="noopener noreferrer">Least privilege</a> access:<br>In short, only granting permissions to the resources and actions an entity will need to perform. The idea here is that having credentials compromised doesn't allow an attacker to do anything they want.</li>
<li>Easy to administer in a fast changing environment.<br>Employee turn over, new projects coming online, etc</li>
<li>In a multiple account setup, have a single user account for each employee</li>
</ul>
<hr>
<h2>Naïve Solution #1</h2>
<p>One of the first solutions we used at Narrative Science, you just go through all the resources an entity needs access to and which actions they need and grant them. This is a "grant-only" solution, so never use a deny in these policies.</p>
<p>Seems pretty straight forward, why is this naïve?</p>
<p>Well AWS has some annoying limitations:</p>
<ul>
<li>Users cannot have more than 10 policies attached.</li>
<li>Users cannot be a member of more than 10 groups.</li>
<li>User policy size cannot exceed 2,048 characters.</li>
<li>Role policy size cannot exceed 10,240 characters.</li>
<li>Group policy size cannot exceed 5,120 characters.</li>
</ul>
<p>These might seem like a lot of wiggle room at first but you'll find very quickly that you start running out and have to be overly permissive to fit within these bounds, especially if your team has generalists that perform multiple job functions.</p>
<p>If a user is already maxed out on groups and policies, but needs permission to perform a certain function for a small period of time (privilege escalation), what do you do? Well I guess we just drop one they don't need at that time and revert it later. You'll need some way of tracking that escalation and reminder to deescalate when they're done, which will almost certainly be required for compliance anyway.</p>
<p>At best this is an annoying burden on the admin team managing the user permission granting. At worst this will just lead to that team getting fed up and granting way overly permissive permissions.</p>
<hr>
<h2>Naïve Solution #2</h2>
<p>Lets try the opposite; being overly permissive and having explicit deny's on certain resources. On the surface this seems reasonable since its probably easier to state what you <i>don't</i> want to grant access to vs what you do.</p>
<p>Say for example you want engineers to have free access of a lower environment, but want to deny all of production. Well if you have good resource tagging, you can mostly deny by anything with a production tag or having <code>prod</code> somewhere in the ARN.</p>
<p>This also very quickly becomes a mess and doesn't save that many characters in your policy documents after its all said and done. Some resources require you to be able to have read / list access, otherwise the UI is unusable. One example is CloudFormation having to have read / list permissions on all resources but also you have to deny any update access to production stacks specifically. But do you then deny CreateStack access in CloudFormation too? </p>
<p>There's also a huge wrinkle here; you cannot allow users to create IAM policies because they can just create a role that grants higher access and allow themselves to be able to <a href="https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html" target="_blank" rel="noopener noreferrer">assume that role</a>. But then how do you allow one to make IAM policies that are deployed with a CloudFormation stack?</p>
<hr>
<h2>A Better Solution</h2>
<p>First of all, one of the biggest issues we had was that we were running both our lower environment and production environments in the same AWS account. We thought that it was good to be diligent with tagging and resource naming but that didn't really help us here for the reasons listed in the last section; even though its still really great to be diligent with resource naming and tagging for billing and clarity of communication.</p>
<p>AWS allows an <a href="https://aws.amazon.com/organizations/" target="_blank" rel="noopener noreferrer">Organization</a> to have multiple accounts associated with it. IAM doesn't do well trying to isolate resources in the same AWS account, so first order of business; isolate your production resources in it's own AWS account, then you can focus on access control to that account specifically.<br><br>In our Organization, we setup a security account where all the IAM user accounts are, a non-production account where users were granted almost complete admin access, and a production account where access was extremely locked down.</p>
<p>Before we go further, we need to understand a 2 advanced AWS IAM features:</p>
<ul>
<li><a href="https://docs.aws.amazon.com/images/IAM/latest/UserGuide/images/permissions_boundary.png" target="_blank" rel="noopener noreferrer">Permission Boundary's</a><br>You can basically grant a wider array of access, then with a boundary policy, restrict the access to only the resources in that boundary. Think of it like a Venn diagram of permissions:<br><br><img loading="lazy" src="https://docs.aws.amazon.com/images/IAM/latest/UserGuide/images/EffectivePermissions-session-boundary-id.png" data-is-external-image="true"  width="292" height="243"><br><br></li>
<li><a href="https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html" target="_blank" rel="noopener noreferrer">Assume Role</a><br>An IAM user can inhabit the permissions of a Role, but only the permissions applied to that role itself. <br>Both the UI and CLI are able to do these role assumptions, and can be done in a way that requires MFA (big plus for compliance). This feature is what will enable us to have single IAM accounts per employee but allow them to have permissions in multiple AWS accounts. </li>
</ul>
<p>So the idea here is; you create roles with open permissions that you then restrict down with a permission boundary, creating much more strict effective permissions; and then users can inhabit those roles.<br><br>You can define these roles any way you want, it will vary greatly depending on your organization and this can obviously be a bit subjective. We decided to define our roles roughly along discrete job functions which allows for an employee to inhabit the role needed to complete a certain task without having to grant full admin (unless that is what is needed).<br><br>For our production account, we created the following roles:</p>
<ul>
<li><strong>Admin </strong>- Complete god permissions only granted to a few, mostly just myself.</li>
<li><strong>ProdAdmin </strong>- Almost the same as god permissions except for it cant change IAM roles, view billing or make account wide changes. Used for team leads / architects<br><strong>OnCall </strong>- Basically a copy of ProdAdmin except that we wanted to keep this distinct incase these 2 roles diverged. Also its more obvious what it is.</li>
<li><strong>ProdEngineer </strong>- Allows engineers to view the status of deployments, read log files, view alarm states. </li>
<li><strong>CustomerSuccess</strong> - Allow our customer success team to manage our Serverless SFTP and handle issues relating to customer data delivery. They basically only had access to a few S3 buckets.</li>
</ul>
<p>If a new job function should arise, then you can of course make new roles to suit those needs. We were working on adding a new one right before we were <a href="https://narrativescience.com/resource/blog/narrative-science-signs-agreement-to-be-acquired-by-salesforce" target="_blank" rel="noopener noreferrer">acquired by Salesforce</a> that would allow some of our engineers to train and maintain production ML models; just as an example.</p>
<p>The advantage of this setup is that not only do you not run into annoying AWS limitations, but also you can grant or remove access to individual users as needed. This can get out of hand to manage manually, so we codified all of our IAM using Git and Terraform. This had the side effect of also allowing engineers to request a role escalation via Git that we could gate with <a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners" target="_blank" rel="noopener noreferrer">CodeOwners</a>, and have an audit trail for compliance.</p>
<p class="msg msg--highlight  msg--info"><strong>Side note</strong>: CloudTrail does log when a user assumes a role, and will show that role's session id in any logs done while that user is in that role; so you can audit who did what and when. </p>
<h3>Example Code</h3>
<p>Enough of me describing the theory of this; here's some actual code. I'm not allowed to share the exact code (for obvious reasons), however here is an example to demonstrate how we used permission boundaries. Basically think of these 2 policy documents in a Venn diagram. </p>
<p>Role Permissions:</p>
<pre><code class="language-json">{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "cloudformation:Describe*",
                "cloudformation:EstimateTemplateCost",
                "cloudformation:Get*",
                "cloudformation:List*",
                "cloudformation:ValidateTemplate",
                "cloudformation:Detect*"
            ],
            "Resource": "*"
        }
    ]
}    
</code></pre>
<p>Role Boundary:</p>
<pre><code class="language-json">{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "cloudformation:*"
            ],
            "Resource": [
                "arn:aws:cloudformation:*:ACCOUNT_ID_REDACTED:stack/prod-app-*/*",
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "cloudformation:Describe*",
                "cloudformation:List*",
                "cloudformation:Get*",
                "cloudformation:GetTemplateSummary",
                "cloudformation:CreateChangeSet",
                "cloudformation:ExecuteChangeSet"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}  
</code></pre>
<p>Terraform Role Creation Module:</p>
<pre><code class="language-hcl">variable "app_admins" {
    type = list(string)
    default = [
        "user1",
        "user2",
        "user3",
        "user4",
        "user5"
    ]
}

resource "aws_iam_role" "app_admin" {
    name = "app_admin"
    path = "/humans/"
    max_session_duration = 3600

    assume_role_policy = data.aws_iam_policy_document.app_admin_trust_policy.json
    permissions_boundary  = aws_iam_policy.app_admin_boundry.arn
}

data "aws_iam_policy_document" "app_admin_trust_policy" {
    statement {
        principals {
            type = "AWS"
            identifiers = [
                for user in var.app_admins :
                "arn:aws:iam::ACCOUNT_ID_REDACTED:user/${user}"
            ]
        }

        actions = [
            "sts:AssumeRole",
            "sts:AssumeRoleWithWebIdentity"
        ]

        condition {
            test = "Bool"
            variable = "aws:MultiFactorAuthPresent"
            values = ["true"]
        }
    }
}

resource "aws_iam_policy" "app_admin" {
    name = "app_admin"
    policy = file("${path.module}/../policies/app_admin.json")
}

resource "aws_iam_policy" "app_admin_boundry" {
    name = "app_admin_boundry"
    policy = file("${path.module}/../permission_boundries/app_boundry.json")
}

resource "aws_iam_role_policy_attachment" "app_admin_attach" {
    role = aws_iam_role.app_admin.name
    policy_arn = aws_iam_policy.app_admin.arn
}        
</code></pre>
<hr>
<h2>IAM Scopedown for Service Roles</h2>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://www.openaccessgovernment.org/wp-content/uploads/2019/09/dreamstime_xxl_123552072.jpg" data-is-external-image="true"  width="351" height="233"></figure>A distinction that should be made is that so far we've focused this on<em> IAM for Humans</em>: permissions that apply to employee IAM accounts, but haven't really touched on<em> IAM for Services</em>: permissions granted to EC2 instances and the like.</p>
<p>We never ended up putting this into action at Narrative Science but I stand by the design and idea. </p>
<p>With services, it can be quite tedious to know <em>all</em> the permissions exhaustively that a service will need ahead of time. Even in earnest, most humans will miss a lot because AWS has just so many services and caveats. But we also need to reach least privilege access for services, is there a way to do this so it takes the burden off the developer <em>and </em>can allow you to achieve least privilege? </p>
<p>The solution here I think is quite elegant. I call it IAM Scopedown.</p>
<p>Basically, a new project that is not yet in production is allowed to run for a time, say 1 month (but the duration is somewhat arbitrary). Initially you grant a sane but knowingly over permissive set of permissions to the app. CloudTrail will log the actions that project did during that time. Then you can view those logs and see which permissions the app actually needed and edit your IAM policies based on that.</p>
<p>This is ripe for a script isnt it? Well good news, one exists for this very thing! Enter <a href="https://github.com/duo-labs/cloudtracker" target="_blank" rel="noopener noreferrer">Cloudtracker</a>.</p>
<p>To further on this idea; I would have automation run this script and send IAM scopedown suggestions to the team managing the project; ideally with the exact output needed for an IAM policy document. This is more of a "eventually least privilege" solution; which is far better than what usually happens where people give up and grant it way too much.</p>
<p>It is possible to have this tool automatically scope down service policies live in production, but I'm wary of not having a human involved in something that is likely to brick production in unexpected ways; though with time and developing on this idea further, I think that is feasible.<br><br>This scopedown procedure can probably also be applied to IAM for Humans but just because something goes unused doesn't necessarily mean it wont have need in the future. This is why I make the distinction between Services and Humans, since they tend to have big differences in their security needs.</p>
<hr>
<h2>Conclusion</h2>
<p>Using a few advanced IAM features and a little bit of clever design, you can make your IAM setup a LOT less tedious to deal with, and reasonably achieve least privilege. </p>
<p>I have to give some credit to Netflix engineers on their "security pizza" talk. I built on a lot of ideas from this talk:<br><br><div class="post__iframe"><iframe loading="lazy" width="560" height="314" src="https://www.youtube.com/embed/aVVX8hV_ywI" allowfullscreen="allowfullscreen" data-mce-fragment="1"></iframe></div>
<p> </p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Disaster Averted</title>
        <author>
            <name>Patrick Hennessy</name>
        </author>
        <link href="https://patcave.dev/disaster-averted/"/>
        <id>https://patcave.dev/disaster-averted/</id>
            <category term="Programming Stuff"/>
            <category term="Gaming"/>

        <updated>2023-01-11T19:01:18-07:00</updated>
            <summary>
                <![CDATA[
                        <img src="https://patcave.dev/media/posts/12/blueprints.png" alt="" />
                    <p>I have been Technical Staff at <a href="https://tf2maps.net">TF2Maps.net</a> for about a year as of writing this. One of the big things that bothered me when I first got onboard was how disorganized and chaotic the state of the infrastructure was. There wasn't a lot of it but it was old and fragile, and soon to be some serious hardware issues that needed to be addressed.</p>

                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                    <img src="https://patcave.dev/media/posts/12/blueprints.png" alt="" />
                <p>I have been Technical Staff at <a href="https://tf2maps.net">TF2Maps.net</a> for about a year as of writing this. One of the big things that bothered me when I first got onboard was how disorganized and chaotic the state of the infrastructure was. There wasn't a lot of it but it was old and fragile, and soon to be some serious hardware issues that needed to be addressed.</p>
<hr>
<h2>Unexpected hardware failures</h2>
<p> </p>
<figure class="post__image post__image--right"><img loading="lazy"  style="font-size: 18.4px; outline: 3px solid rgba(var(--primary-color-rgb), 0.55)  !important;" src="https://patcave.dev/media/posts/12/download-1.jpg" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/12/responsive/download-1-xs.jpg 300w ,https://patcave.dev/media/posts/12/responsive/download-1-sm.jpg 480w ,https://patcave.dev/media/posts/12/responsive/download-1-md.jpg 768w ,https://patcave.dev/media/posts/12/responsive/download-1-lg.jpg 1024w ,https://patcave.dev/media/posts/12/responsive/download-1-xl.jpg 1360w ,https://patcave.dev/media/posts/12/responsive/download-1-2xl.jpg 1600w"  alt="" width="266" height="116"></figure>
<p>In November of 2021, we discovered that our <span style="color: var(--text-editor-body-color); font-family: var(--font-base); font-size: inherit; font-weight: var(--font-weight-normal);">hardware was beginning to fail. We had been running a physical host out of a New York </span><a href="https://en.wikipedia.org/wiki/Colocation_centre" style="font-family: var(--font-base); font-size: inherit; font-weight: var(--font-weight-normal);">colo</a><span style="color: var(--text-editor-body-color); font-family: var(--font-base); font-size: inherit; font-weight: var(--font-weight-normal);"> for nearly 8 years.</span></p>
<p>Usually disks and power supplies have a life of ~2-4 years, so its impressive we made it was far as we did, though scary because I was afraid our hardware could fail at basically any time.</p>
<p>In trying to allocate more disk to one of our VM's, our hypervisor <a href="https://www.proxmox.com/en/">Proxmox</a> told us that it detected bad sectors. Super oh no. These disks we're <a href="https://en.wikipedia.org/wiki/Standard_RAID_levels" target="_blank" rel="noopener noreferrer">RAID 0</a>, meaning we had no actual redundancy.</p>
<p>Additionally, our colo contract expired in February of 2022, meaning we would need to move all of our systems over to something new with the quickness.</p>
<hr>
<h2>Lay of the land</h2>
<p>The very first thing I did was assess the problem at hand and determine the scope of what we will do about it.</p>
<h4>So the obvious question is, what needs to move?</h4>
<p>I went onto every VM we had and took a thorough look to get an accounting of everything we would need to move or replace.</p>
<p>There was a lot more than I expected:</p>
<ul>
<li>Bind9 DNS</li>
<li>Dovecot Email server</li>
<li>Apache with 22 distinct virtual hosts
<ul>
<li>Many of which were just static html sites</li>
<li>One was the forum software</li>
<li>One was our custom built map queue site</li>
</ul>
</li>
<li>The Discord Bot</li>
<li>MySQL database</li>
<li>Almost 1TB of static attachment data on disk</li>
</ul>
<h4>Bills Bills Bills</h4>
<p>I also spoke with the incumbent system administrator about hosting cost; which I had found we were consistently spending more on hosting than we received in donations, and the difference came out of his pocket. Not great, and he defiantly was not happy with it.</p>
<p>After some research, I determined that we would likely need to upgrade our version of the <a href="https://xenforo.com/">Xenforo </a>fo<span style="color: var(--text-editor-body-color); font-family: var(--font-base); font-size: inherit; font-weight: var(--font-weight-normal);">rum software in order to manifest significant cost savings. So I tasked one of my "interns" with going into Xenforo and evaluate every existing plugin we ran on the old site, and asses it's criticality (must have, nice to have, dont need), as </span><span style="color: var(--text-editor-body-color); font-family: var(--font-base); font-size: inherit; font-weight: var(--font-weight-normal);">well as if we have a migration path for that plugin to Xenforo 2. </span></p>
<p><span style="color: var(--text-editor-body-color); font-family: var(--font-base); font-size: inherit; font-weight: var(--font-weight-normal);">This research was critical to learning if this was even possible, or if we would need to look into other solutions.</span></p>
<p><span style="color: var(--text-editor-body-color); font-family: var(--font-base); font-size: inherit; font-weight: var(--font-weight-normal);">Side note: Google</span><span style="color: var(--text-editor-body-color); font-family: var(--font-base); font-size: inherit; font-weight: var(--font-weight-normal);"> Sprea</span><span style="color: var(--text-editor-body-color); font-family: var(--font-base); font-size: inherit; font-weight: var(--font-weight-normal);">dsheets are</span><span style="color: var(--text-editor-body-color); font-family: var(--font-base); font-size: inherit; font-weight: var(--font-weight-normal);"> great for this kind of ongoing collaboration:</span></p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="1">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/12/gallery/Screenshot_774-3.png" data-size="920x666"><img loading="lazy" src="https://patcave.dev/media/posts/12/gallery/Screenshot_774-3-thumbnail.png" alt="" width="720" height="521"></a></figure>
</div></div>
<hr>
<h2>Project Objectives</h2>
<ul>
<li>Have fully operational systems moved to new hardware before our colo contract expired</li>
<li>Reduce our hosting bill by at least 25%, but ideally as much as possible</li>
<li>Lower the maintenance overhead for all systems going forward</li>
<li>Bring us up to latest security best practices</li>
</ul>
<h3>Where to host it all?</h3>
<p>So the first order of business was to see what our hosting options could be. At a glance I figured we could leverage a cloud provider to replace a good amount of functionality for us, as well as possibly use S3 to host a lot of stuff for very cheap.</p>
<p>I put together a cost estimate for...</p>
<ul>
<li><a href="https://aws.amazon.com/">AWS</a></li>
<li><a href="https://www.digitalocean.com/">Digital Ocean</a></li>
<li><a href="https://www.vultr.com/">Vultr</a></li>
<li><a href="https://us.ovhcloud.com/">OVH</a></li>
</ul>
<figure class="post__image post__image--left"><img loading="lazy"  style="font-size: 18.4px; outline: 3px solid rgba(var(--primary-color-rgb), 0.55)  !important;" src="https://patcave.dev/media/posts/12/grmuy1fs409mjcg3xrqs-md.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/12/responsive/grmuy1fs409mjcg3xrqs-md-xs.png 300w ,https://patcave.dev/media/posts/12/responsive/grmuy1fs409mjcg3xrqs-md-sm.png 480w ,https://patcave.dev/media/posts/12/responsive/grmuy1fs409mjcg3xrqs-md-md.png 768w ,https://patcave.dev/media/posts/12/responsive/grmuy1fs409mjcg3xrqs-md-lg.png 1024w ,https://patcave.dev/media/posts/12/responsive/grmuy1fs409mjcg3xrqs-md-xl.png 1360w ,https://patcave.dev/media/posts/12/responsive/grmuy1fs409mjcg3xrqs-md-2xl.png 1600w"  alt="" width="237" height="181"></figure>
<p>AWS was cost prohibitive, though would have been the most feature rich one. OVH would mostly put us in a simliar situation as before.</p>
<p>So it was between DigitalOcean and Vultr. We ended up choosing Vultr because it had some slightly cheaper VM options and it has <em>way</em> more locations; which will be nice for ad-hoc game servers.</p>
<h3>Bill reduction strategy</h3>
<p>We had a lot of inefficiency in our hosting that we could fix. </p>
<p>First was all the static HTML sites we were serving. Vultr has an S3-type offering, so I assigned my "intern" the task of moving all of those sites into there. This will save us overhead on disk and also on just having less things to manage overall, since its setup and forget.</p>
<p>This was also a test for me to see how well their S3 setup works. The biggest hurdle was going to be figuring out if we can store our terabyte of attachment data out of S3 as well. A disk of that size for Vultr was going to be very expensive. </p>
<p>In researching this, I learned I could only do this if we upgraded our Xenforo software to 2+ (we were still running 1.4). Unfortunately there were no docs specifically for Vultr, but I was able to figure out how to get it to work, mostly by reading the source code of a few plugins. Xenforo 2 also allowed us to do a lot of things we had wanted to do for many years but were unable to because of XF 1 limitations.</p>
<p>Coupled with the cheap VM's that are offered by Vultr, and the use of S3 as a backend, as well as ad-hoc game servers; I believed we could significantly reduce our hosting cost this way.</p>
<hr>
<h2>The Prototypes</h2>
<p> </p>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://patcave.dev/media/posts/12/prototype-comic-2-en.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/12/responsive/prototype-comic-2-en-xs.png 300w ,https://patcave.dev/media/posts/12/responsive/prototype-comic-2-en-sm.png 480w ,https://patcave.dev/media/posts/12/responsive/prototype-comic-2-en-md.png 768w ,https://patcave.dev/media/posts/12/responsive/prototype-comic-2-en-lg.png 1024w ,https://patcave.dev/media/posts/12/responsive/prototype-comic-2-en-xl.png 1360w ,https://patcave.dev/media/posts/12/responsive/prototype-comic-2-en-2xl.png 1600w"  alt="" width="299" height="261"></figure>There was a lot of unknowns going into this. This site has existed since 2007, there was literally 13 years of data we <em>absolutely could not lose</em>. If that data was lost it would be gone forever. To be confident in what I was going to do; i stood up several prototype instances.</p>
<p>One was a clean install of Xenforo 2 on a fresh Vultr VM so we could see what we wanted to end state to be.</p>
<p>This prototype had no data in the database or attachments. It was done this way to make it faster to setup and reduce any conversion issues, and it was way cheaper. I wanted a clean reference setup. Using the research we did before; we installed all the plugins we believed would cover our needs, and then did a lot of testing to be certain.</p>
<p>We spent probably a month or so working on setting up the clean install and getting familiar with Xenforo 2. I also assigned my "intern" the task of setting up a UI theme to be reminiscent of our branding and old website, built off a more modern base. We quickly realized how many hacks the old site had in its CSS and theme so that was a fun adventure in CSS and HTML, something I haven't done much of in a long time.</p>
<p>The next prototype site I stood up was a clone of the existing site without attachments. It's purpose was to test the migration path from version 1.x to 2.x. Basically, what are we going to run into when we do this for real? The process was actually pretty smooth, though there were a bunch of non-critical things that didn't make it over cleanly, and we had to come back later and fix those up. </p>
<hr>
<h2>The Great Migration</h2>
<p> </p>
<figure class="post__image post__image--right"><img loading="lazy"  src="https://patcave.dev/media/posts/12/download.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/12/responsive/download-xs.png 300w ,https://patcave.dev/media/posts/12/responsive/download-sm.png 480w ,https://patcave.dev/media/posts/12/responsive/download-md.png 768w ,https://patcave.dev/media/posts/12/responsive/download-lg.png 1024w ,https://patcave.dev/media/posts/12/responsive/download-xl.png 1360w ,https://patcave.dev/media/posts/12/responsive/download-2xl.png 1600w"  alt="" width="263" height="167"></figure>For most of January, I was rather busy given that my startup had just been <a href="https://narrativescience.com/resource/blog/narrative-science-signs-agreement-to-be-acquired-by-salesforce">acquired by Salesforce</a>, so my head was not really in a good space to work on this for TF2Maps. At this point though; all the research was done to be confident in our move, so we just needed to execute on it.</p>
<p>We had started moving some items in January in preparation of the VM move:</p>
<ul>
<li>DNS served out of Vultr</li>
<li>Email from a free email provider</li>
<li>Static HTML sites</li>
<li>Discord Bot</li>
<li><a href="https://patcave.dev/game-server-monitoring/">Grafana</a></li>
</ul>
<p>There was absolutely no way to do this without taking the site offline while data moved. Thankfully, nothing bad happens if TF2Maps is down for a few days. I wish it worked this way with production systems at work...</p>
<p>The VM move was a tense 3 days. If something in this process failed we may have lost a lot of data, so we needed to be very precise and methodical. Here was the precise steps, in which I was in constant communication with my intern and other "stakeholders":</p>
<ol>
<li>Create new (final) VM for the Xenforo site inside Vultr</li>
<li>Put up a maintenance page to categorically prevent any DB writes from occurring</li>
<li>Move the SQL databases onto the new VM, and save a backup in S3</li>
<li>Attach a 1TB disk to the VM, and start the static file sync
<ul>
<li>This ended up taking 3 days to complete</li>
</ul>
</li>
<li>Clone the Xenforo 1 site</li>
<li>Perform the Xenforo upgrade</li>
<li>Setup all the new plugins and theme</li>
<li>Other Staff thoroughly inspect the site to knock out obvious issues first</li>
<li>Create a thread on the forums for end users to report bugs</li>
<li>Open it up to the public (Scary!)</li>
</ol>
<hr>
<h2>The Post Migration Chaos</h2>
<p>As usual with projects like this, we got dozens of bug reports and small issues.</p>
<p>To add to this, our first salvo was just to get us out of the colo expiration problem. We had not actually moved our attachments into S3, or created ad-hoc servers.</p>
<p>We would not be able to realize our cost savings until we had finally moved attachment data into S3. Again, this was easy because I already knew what to do because of the prototype phase we did. I had to once again take the site offline for a few days to do this. Once moved, I detached the disk, but kept it around as a hot backup incase something really went wrong with S3. After a week, I shutdown that disk, given it was quite expensive.</p>
<p>I assigned as many bugs to my intern as I felt he could reasonably handle while I tackled the more difficult ones. Together, we ended up fixing probably 50 issues over the course of a few weeks. These bugs were all over the place, and a lot of it was things we didn't purposely set or create. Needing to troubleshoot systems you're unfamiliar is uhhh, fun. </p>
<hr>
<h2>A major upgrade to security</h2>
<p>Now I don't want to knock on the people who set things up before I came along because they did a good job of just keeping the ship floating, but lets just say the state of security was a bit, uhhh, <em>nuanced</em>. </p>
<p>Thankfully, I've done server hardening many many times before. Over the next few weeks I worked on setting up:</p>
<ul>
<li>Least privilege database users</li>
<li>SSH key authentication only</li>
<li>No root user logins</li>
<li>SSH whitelisted to only administrators IP's</li>
<li>Stateful firewall (fail2ban)</li>
<li>Ulimits</li>
<li>Apache virtual hosts run in discrete sandboxes</li>
<li>Log rotate configs</li>
<li>Auto-renewing wildcard SSL cert from LetsEncrypt</li>
<li>Least privilege firewalls inside of Vultr</li>
<li>Forced HTTPS redirection</li>
</ul>
<hr>
<h2>Outcomes</h2>
<ul>
<li>Data move completed with 3 days to spare!</li>
<li><em>We did not lose any data whatsoever</em></li>
<li>The steady state hosting cost was reduced by over 65%!</li>
<li>Security is a lot better now</li>
<li>Automation now exists for all the mundane things
<ul>
<li>Automatic DB backups to S3 on a weekly cadance</li>
<li>Automatic SSL renewal</li>
<li>Xenforo -&gt; Discord rank syncing</li>
</ul>
</li>
<li>The site is much better protected from disasters given most of the data is in S3 and the rest is regularly backed up</li>
<li>Multiple people on Staff can now help me operate the site thanks to Xenforo 2 being a lot easier to administer.</li>
</ul>
<figure class="post__image post__image--center"><img loading="lazy"  src="https://patcave.dev/media/posts/12/great-success.jpg" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/12/responsive/great-success-xs.jpg 300w ,https://patcave.dev/media/posts/12/responsive/great-success-sm.jpg 480w ,https://patcave.dev/media/posts/12/responsive/great-success-md.jpg 768w ,https://patcave.dev/media/posts/12/responsive/great-success-lg.jpg 1024w ,https://patcave.dev/media/posts/12/responsive/great-success-xl.jpg 1360w ,https://patcave.dev/media/posts/12/responsive/great-success-2xl.jpg 1600w"  alt="" width="576" height="432"></figure>
<hr>
<h2>Learnings</h2>
<p>To be successful here, I had to wear a lot of different hats. I was basically the lead developer, project manager, ui designer, QA, security, and finance. I had a few people who were technical enough I could slice off some simple tasks and assign them to those people which did help a lot. Shout out to my intern!</p>
<p>The most difficult thing to do was cut through all the people wanting to shoot from the hip and get something out quick and also managing peoples expectations. Most of the people involved have not been a part of a project such as this before so I had to also wear the hat of the mentor. I had to guide everyone through a process which I had developed from doing migration projects like this at work. I have a systematic process to do this, but it was defiantly challenging getting folks onboard and to trust that I knew what I was doing. There was a great deal of skepticism that I would be able to actually pull this off, and to a degree, I myself was unsure if I would actually be able to deliver on some of the goals, mostly relating to cost optimization. The research step helped ease a lot of peoples concerns because I had proof that we would be able to actually pull this off.</p>
<p>The other thing to do was managing the expectations of our user base. We probably have about 1000 active users, so we got absolutely slammed with requests for a few weeks. My intern helped triage a lot of this stuff which was a nice burden off my shoulders, since I could focus on much bigger picture things. Of course every person feels the issue they are reporting is the most important one and you have to be patient and communicate that we acknowledge the issue and we are going to get to it when we can given its difficulty to fix and its value to the community as a whole while also reminding them that I am doing this without being paid at all. Some issues however, we probably just aren't going to fix because they're so difficult and low value. You have to accept that in projects like this, those things are going to happen.</p>
<p>Another thing I did here was to coach the rest of staff to act on my behalf and get in front of issues and snowballing discussions about the new site. I defiantly succeeded here in rallying dozens of people to assist me when I needed it. I think the key to this is being transparent and very clear about what you need from them and also modeling the behavior you want.</p>
<hr>
<h2>The Process in Bullet form</h2>
<ol>
<li>Assess the scope of the problem at hand, i.e. what do we need to solve for?</li>
<li>Research solutions
<ul>
<li>Create prototypes to learn about a solution so you can pivot your research if needed</li>
<li>Setup cost estimates</li>
<li>Estimate the scope of work involved, and determine the must-haves
<ul>
<li>Try to organize the work such that you can farm out to multiple people</li>
</ul>
</li>
</ul>
</li>
<li>Prioritize work based on the constraints of the problem (such as our colo contract expiration)</li>
<li>Action the minimum critical scope as your first salvo</li>
<li>Openly seek feedback</li>
<li>Iterate on improvements and bug fixes until completion</li>
</ol>
<p>Don't forget to openly communicate to stakeholders about where you are in this process and be open about your unknowns. Seek to resolve those unknowns in your research as much as possible.</p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>My choice tools for Cloud Engineering</title>
        <author>
            <name>Patrick Hennessy</name>
        </author>
        <link href="https://patcave.dev/my-choice-tools-for-cloud-engineering/"/>
        <id>https://patcave.dev/my-choice-tools-for-cloud-engineering/</id>
            <category term="Programming Stuff"/>

        <updated>2023-01-11T19:01:41-07:00</updated>
            <summary>
                <![CDATA[
                        <img src="https://patcave.dev/media/posts/10/cover-2.png" alt="" />
                    <p><a href="https://www.youtube.com/watch?v=SNgNBsCI4EA" target="_blank" rel="noopener noreferrer">I'm an engineer</a>, which means I love my tools and there's some tools I hate. I guess that comes with the territory. This is the collection of tools I try to use anytime I can for Cloud Engineering tasks.</p>

                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                    <img src="https://patcave.dev/media/posts/10/cover-2.png" alt="" />
                <p><a href="https://www.youtube.com/watch?v=SNgNBsCI4EA" target="_blank" rel="noopener noreferrer">I'm an engineer</a>, which means I love my tools and there's some tools I hate. I guess that comes with the territory. This is the collection of tools I try to use anytime I can for Cloud Engineering tasks.</p>

<p>Ill format this by category, and list out my likes and dislikes about the tool. Realize that each one of these is just my <em>opinion</em> and I'm not saying they're the only tool anyone should ever use, people have different preferences and needs afterall. Enjoy!</p>
<hr>
<h2>Infrastructure as Code: <em><a href="https://www.terraform.io/" target="_blank" rel="noopener noreferrer">Terraform</a></em></h2>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://patcave.dev/media/posts/10/terraform-logo.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/10/responsive/terraform-logo-xs.png 300w ,https://patcave.dev/media/posts/10/responsive/terraform-logo-sm.png 480w ,https://patcave.dev/media/posts/10/responsive/terraform-logo-md.png 768w ,https://patcave.dev/media/posts/10/responsive/terraform-logo-lg.png 1024w ,https://patcave.dev/media/posts/10/responsive/terraform-logo-xl.png 1360w ,https://patcave.dev/media/posts/10/responsive/terraform-logo-2xl.png 1600w"  alt="" width="192" height="192"></figure>
<p>Terraform is a tool that allows you to define your cloud infrastructure as code. It is intended to be declarative rather than programmatic, but it does a good job of balancing between the two. </p>
<p> </p>
<p> </p>
<table style="border-collapse: collapse; width: 100%; border-style: hidden;" border="0">
<tbody>
<tr>
<td style="width: 50%;"><strong>Things I like</strong></td>
<td style="width: 50%;"><strong>Things I don't like</strong></td>
</tr>
<tr>
<td style="width: 50%;">
<ul>
<li>Deployment is part of the tool automatically. No need to write custom deploy or wrapper code</li>
<li>Support for variables, loops, conditionals and reusability</li>
<li><a href="https://registry.terraform.io/browse/modules" target="_blank" rel="noopener noreferrer">Module repository</a></li>
<li>Backend agnostic. Well suited for multi-cloud situations</li>
<li>Backed by Hashicorp but completely FOSS</li>
<li>Very popular which means lots of good Stack Overflow posts on gotchas</li>
<li><a href="https://www.terraform.io/cloud-docs/cost-estimation" target="_blank" rel="noopener noreferrer">Cost Estimation</a></li>
</ul>
</td>
<td style="width: 50%;">
<ul>
<li>State management is literally a flat file that itself needs to be centrally managed</li>
<li>The looping syntax is quite confusing</li>
<li>Not always 100% up to date with it's AWS provider</li>
</ul>
</td>
</tr>
</tbody>
</table>
<p>Admittedly I am a bit biased because the only other tool in this space I've used is CloudFormation, which works well for small use cases but quickly becomes unmanageable when you have a fast changing dynamic environment like the cloud.</p>
<p>In my mind, the 3 big players for IaC are: <em><a href="https://aws.amazon.com/cloudformation/" target="_blank" rel="noopener noreferrer">CloudFormation</a></em>, <em>Terraform </em>and <em><a href="https://www.pulumi.com/" target="_blank" rel="noopener noreferrer">Pulumi</a></em>. The way I like to think about it is a spectrum where CloudFormation being the most "declarative" while Pulumi is almost literally just a library in a programming language. Terraform leans towards being declarative but has much better support for things like conditionals, loops, variables, passing data between modules and other programming language paradigms.</p>
<figure class="post__image post__image--center"><img loading="lazy"  src="https://patcave.dev/media/posts/10/spectrum.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/10/responsive/spectrum-xs.png 300w ,https://patcave.dev/media/posts/10/responsive/spectrum-sm.png 480w ,https://patcave.dev/media/posts/10/responsive/spectrum-md.png 768w ,https://patcave.dev/media/posts/10/responsive/spectrum-lg.png 1024w ,https://patcave.dev/media/posts/10/responsive/spectrum-xl.png 1360w ,https://patcave.dev/media/posts/10/responsive/spectrum-2xl.png 1600w"  alt="" width="1576" height="496"></figure>
<p> </p>
<hr>
<h2>Configuration Management: <em><a href="https://www.ansible.com/" target="_blank" rel="noopener noreferrer">Ansible</a></em></h2>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://patcave.dev/media/posts/10/Ansible_logo.svg.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/10/responsive/Ansible_logo.svg-xs.png 300w ,https://patcave.dev/media/posts/10/responsive/Ansible_logo.svg-sm.png 480w ,https://patcave.dev/media/posts/10/responsive/Ansible_logo.svg-md.png 768w ,https://patcave.dev/media/posts/10/responsive/Ansible_logo.svg-lg.png 1024w ,https://patcave.dev/media/posts/10/responsive/Ansible_logo.svg-xl.png 1360w ,https://patcave.dev/media/posts/10/responsive/Ansible_logo.svg-2xl.png 1600w"  alt="" width="156" height="192"></figure>
<p>Ansible allows one to codify the steps to provisioning a virtual machine or a Docker image, allowing one to reproduce their configuration whenever needed.</p>
<p> </p>
<p> </p>
<table style="border-collapse: collapse; width: 100%; border-style: hidden;" border="0">
<tbody>
<tr>
<td style="width: 50%;"><strong>Things I like</strong></td>
<td style="width: 50%;"><strong>Things I don't like</strong></td>
</tr>
<tr>
<td style="width: 50%;">
<ul>
<li>No weird metaphors for it's features like "Salt Pillar" or "Cookbook"</li>
<li>Executes serially but can be made to <a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_strategies.html" target="_blank" rel="noopener noreferrer">run in parallel</a></li>
<li>No agent to install and run</li>
<li>No additional infrastructure needed</li>
<li><a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html" target="_blank" rel="noopener noreferrer">Reusable roles</a> and <a href="https://galaxy.ansible.com/" target="_blank" rel="noopener noreferrer">pre-made roles</a></li>
<li>Allows for variables and looping</li>
<li><a href="https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html" target="_blank" rel="noopener noreferrer">Extendable with Python</a></li>
<li><a href="https://janikvonrotz.ch/2018/03/07/profiling-ansible-roles-and-tasks/" target="_blank" rel="noopener noreferrer">Task profiling</a></li>
<li>Maintained by RedHat</li>
<li>Great documentation</li>
</ul>
</td>
<td style="width: 50%;">
<ul>
<li>Doesn't "guarantee" what is written will be reality</li>
<li>With roles, sometimes it's hard to follow the flow of execution</li>
<li>Some built-in modules (e.g. Docker) require system packages to be installed before the task can run. This is also poorly documented in most cases.</li>
</ul>
</td>
</tr>
</tbody>
</table>
<p>Honestly I feel like I could keep going on and on with this list of pro's.</p>
<p>Personally I've used the task profiling to setup a playbook with parallelization which gave us a dramatic speed up in execution time. Using those 2 features we went from 3 hour runs down to ~25 minutes.</p>
<p>Competitors I've used are <a href="https://www.fabfile.org/" target="_blank" rel="noopener noreferrer">Fabric</a> (Python lib) and <a href="https://saltproject.io/" target="_blank" rel="noopener noreferrer">SaltStack</a>. I've dabbled a bit with <a href="https://www.chef.io/" target="_blank" rel="noopener noreferrer">Chef</a> but am very unfamiliar with it. Fabric is no longer maintained because of the Python 3 switch, but I do like the concept of managing state with the power of a programming language, but also fear that it can become very unruly very quick if you maintain poor abstractions. I like Ansible because it puts guard rails on those abstractions so things really can't get too complex or hard to understand.</p>
<p>For what Ansible is, it does a great job and is one of the more reliable tools I regularly use at work.</p>
<hr>
<h2>Version Control: <em><a href="https://github.com/" target="_blank" rel="noopener noreferrer">Github</a> / <a href="https://git-scm.com/" target="_blank" rel="noopener noreferrer">Git</a></em></h2>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://patcave.dev/media/posts/10/25231.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/10/responsive/25231-xs.png 300w ,https://patcave.dev/media/posts/10/responsive/25231-sm.png 480w ,https://patcave.dev/media/posts/10/responsive/25231-md.png 768w ,https://patcave.dev/media/posts/10/responsive/25231-lg.png 1024w ,https://patcave.dev/media/posts/10/responsive/25231-xl.png 1360w ,https://patcave.dev/media/posts/10/responsive/25231-2xl.png 1600w"  alt="" width="192" height="192"></figure>
<p>Github is a remote code repository and developer platform. It is obviously backed by the company Github itself. It is by far the most popular code repository of it's kind, built by coder for coders.</p>
<p> </p>
<p> </p>
<table style="border-collapse: collapse; width: 100%; border-style: hidden;" border="0">
<tbody>
<tr>
<td style="width: 50%;"><strong>Things I like</strong></td>
<td style="width: 50%;"><strong>Things I don't like</strong></td>
</tr>
<tr>
<td style="width: 50%;">
<ul>
<li>UI makes the complexity of Git feel much less so</li>
<li>Pull request comment and approval workflow is really nice</li>
<li>Merging a PR can be gated by tests passing or needing approvals</li>
<li>Code Owners</li>
<li>Github pages is nice for making static docs for your project</li>
<li>Github Actions!</li>
<li>Private repo's are now free!</li>
</ul>
</td>
<td style="width: 50%;">
<ul>
<li>Git itself is very confusing if you stray away from the branch, add, commit, push workflow</li>
<li>Git has a bit of a steep learning curve and lot of jargon</li>
<li>New Git users often get into crazy Git states and have to be destructive to get out of it</li>
<li>Github's permissions are not granular enough for a medium to large organization</li>
</ul>
</td>
</tr>
</tbody>
</table>
<p>I have only really used <a href="https://tortoisesvn.net/" target="_blank" rel="noopener noreferrer">TortiseSVN</a> as an alternative, and it's workflow always felt clunky to me. I was using it right when I started learning programming so perhaps it's not as bad as I remember.</p>
<p>Git is so popular and ingrained in my head that I honestly don't know how I would switch away unless I joined a company that ran another one.</p>
<p>I should also mention that <a href="gitlab.com" target="_blank" rel="noopener noreferrer">Gitlab</a> is also pretty nice in comparison to Github, though I still prefer Github probably just out of familiarity alone. Both do seem to offer the same things.</p>
<hr>
<h2>Programming Language: <em><a href="https://www.python.org/" target="_blank" rel="noopener noreferrer">Python</a></em></h2>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://patcave.dev/media/posts/10/1200px-Python-logo-notext.svg.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/10/responsive/1200px-Python-logo-notext.svg-xs.png 300w ,https://patcave.dev/media/posts/10/responsive/1200px-Python-logo-notext.svg-sm.png 480w ,https://patcave.dev/media/posts/10/responsive/1200px-Python-logo-notext.svg-md.png 768w ,https://patcave.dev/media/posts/10/responsive/1200px-Python-logo-notext.svg-lg.png 1024w ,https://patcave.dev/media/posts/10/responsive/1200px-Python-logo-notext.svg-xl.png 1360w ,https://patcave.dev/media/posts/10/responsive/1200px-Python-logo-notext.svg-2xl.png 1600w"  alt="" width="192" height="192"></figure>
<p>Okay before you bite my head off saying its "too slow", remember this post is about the role of a<em> Cloud Engineer</em>, not a HPC or ML engineer. Python has dozens of useful libraries and has a long history of being used as a systems language.</p>
<p> </p>
<p> </p>
<table style="border-collapse: collapse; width: 100%; border-style: hidden;" border="0">
<tbody>
<tr>
<td style="width: 50%;"><strong>Things I like</strong></td>
<td style="width: 50%;"><strong>Things I don't like</strong></td>
</tr>
<tr>
<td style="width: 50%;">
<ul>
<li><a href="https://boto3.amazonaws.com/v1/documentation/api/latest/index.html" target="_blank" rel="noopener noreferrer">Boto3</a> library is great for writing glue code for AWS or as part of a Lambda</li>
<li>All Linux distros ship with Python by default</li>
<li>Most OS tools use Python and can be extended using it (e.g. Systemd)</li>
<li>Very helpful traceback's</li>
<li><a href="https://docs.python.org/3/library/pdb.html" target="_blank" rel="noopener noreferrer">PDB</a> - Very easy to debug</li>
<li><a href="https://insights.stackoverflow.com/survey/2021#most-popular-technologies-language" target="_blank" rel="noopener noreferrer">3rd most popular language</a></li>
<li>Syntax often reads like pseudo-code (most "sys admins" can barely code, this is a big plus)</li>
<li>Language features work well together. Decorators, comprehensions, iterators, dataclasses, dicts, just to name a few.</li>
</ul>
</td>
<td style="width: 50%;">
<ul>
<li>It's package management ecosystem was designed by Satan himself</li>
<li>Subprocessing and multiprocessing are not as easy as something like Bash.</li>
<li>Python 2 vs 3 switch. I still find old Python 2 scripts laying around</li>
<li>Arguments about the 80 char lines</li>
</ul>
</td>
</tr>
</tbody>
</table>
<p>The obvious elephant in the room is Bash. I have a love - hate relationship with Bash, but also feel very inadequate because I've been using daily it for over 10 years and still have to Google basic syntax every time I use it.</p>
<p>The way I think about these 2 "languages" (if you can even call bash a language) is that Bash is great for simple things that you run on your command line, but the second you need more than 1 if statement, you should seriously consider using Python instead. Also bash sucks at handling JSON output and Python defiantly shines there.</p>
<p>I know that Hashicorp was trying to make Ruby the Cloud Engineer language of choice but just doesn't seem to have taken off the same way as Python has.</p>
<p>Just for fun, here's some of my favorite Python libraries:</p>
<ul>
<li><a href="https://boto3.amazonaws.com/v1/documentation/api/latest/index.html" target="_blank" rel="noopener noreferrer">boto3</a></li>
<li><a href="https://fastapi.tiangolo.com/" target="_blank" rel="noopener noreferrer">FastAPI</a></li>
<li><a href="https://pydantic-docs.helpmanual.io/" target="_blank" rel="noopener noreferrer">Pydantic</a></li>
<li><a href="https://jinja.palletsprojects.com/" target="_blank" rel="noopener noreferrer">Jinja2</a></li>
<li><a href="https://collerek.github.io/ormar/" target="_blank" rel="noopener noreferrer">ormar</a> or <a href="https://roman-right.github.io/beanie/">beanie</a><a href="#INTERNAL_LINK#/post/null" target="_blank" rel="noopener noreferrer"></a></li>
<li><a href="https://www.paramiko.org/" target="_blank" rel="noopener noreferrer">paramiko</a></li>
<li><a href="https://docs.python-requests.org/" target="_blank" rel="noopener noreferrer">requests</a> or <a href="https://www.python-httpx.org/" target="_blank" rel="noopener noreferrer">httpx</a> (async)</li>
<li><a href="https://rich.readthedocs.io/en/stable/introduction.html" target="_blank" rel="noopener noreferrer">rich</a></li>
<li><a href="https://python-prompt-toolkit.readthedocs.io/en/master/" target="_blank" rel="noopener noreferrer">PromptToolkit</a></li>
<li><a href="https://github.com/Delgan/loguru" target="_blank" rel="noopener noreferrer">loguru</a></li>
</ul>
<hr>
<h2>Monitoring: <em><a href="https://prometheus.io/" target="_blank" rel="noopener noreferrer">Prometheus</a> + <a href="https://grafana.com/" target="_blank" rel="noopener noreferrer">Grafana</a></em></h2>
<figure class="post__image post__image--center"><img loading="lazy"  src="https://patcave.dev/media/posts/10/grafaf.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/10/responsive/grafaf-xs.png 300w ,https://patcave.dev/media/posts/10/responsive/grafaf-sm.png 480w ,https://patcave.dev/media/posts/10/responsive/grafaf-md.png 768w ,https://patcave.dev/media/posts/10/responsive/grafaf-lg.png 1024w ,https://patcave.dev/media/posts/10/responsive/grafaf-xl.png 1360w ,https://patcave.dev/media/posts/10/responsive/grafaf-2xl.png 1600w"  alt="" width="420" height="192"></figure>
<p>I've already written about this subject when discussing <a href="https://patcave.dev/game-server-monitoring/" target="_blank" rel="noopener noreferrer">monitoring for my Team Fortress servers</a> but ill summarize here.</p>
<p> </p>
<table style="border-collapse: collapse; width: 100%; border-style: hidden;" border="0">
<tbody>
<tr>
<td style="width: 50%;"><strong>Things I like</strong></td>
<td style="width: 50%;"><strong>Things I don't like</strong></td>
</tr>
<tr>
<td style="width: 50%;">
<ul>
<li>Very easy to get going out of the box</li>
<li>PromQL and Grafana explorer are very easy to use</li>
<li>Grafana dashboards are very easy and intuitive to create</li>
<li>Grafana supports tables and text nodes</li>
</ul>
</td>
<td style="width: 50%;">
<ul>
<li>Alerting with AlertManager is not the greatest</li>
</ul>
</td>
</tr>
</tbody>
</table>
<hr>
<h2>Code Editor: <em><a href="https://code.visualstudio.com/" target="_blank" rel="noopener noreferrer">VS Code</a></em></h2>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://patcave.dev/media/posts/10/Visual_Studio_Code_1.35_icon.svg.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/10/responsive/Visual_Studio_Code_1.35_icon.svg-xs.png 300w ,https://patcave.dev/media/posts/10/responsive/Visual_Studio_Code_1.35_icon.svg-sm.png 480w ,https://patcave.dev/media/posts/10/responsive/Visual_Studio_Code_1.35_icon.svg-md.png 768w ,https://patcave.dev/media/posts/10/responsive/Visual_Studio_Code_1.35_icon.svg-lg.png 1024w ,https://patcave.dev/media/posts/10/responsive/Visual_Studio_Code_1.35_icon.svg-xl.png 1360w ,https://patcave.dev/media/posts/10/responsive/Visual_Studio_Code_1.35_icon.svg-2xl.png 1600w"  alt="" width="192" height="192"></figure>
<p>VS Code is a code editor created and maintained by Microsoft. It's relatively lightweight and can become very fully featured via their extensions.</p>
<p> </p>
<p> </p>
<p> </p>
<p>Normally I don't like throwing my hat in the ring for things as subjective as a code editor. <em>Everyone</em> has strong opinions about this, however VS Code has a few things I believe seperate it from other editors in it's class.</p>
<p> </p>
<table style="border-collapse: collapse; width: 100%; border-style: hidden;" border="0">
<tbody>
<tr>
<td style="width: 50%;"><strong>Things I like</strong></td>
<td style="width: 50%;"><strong>Things I don't like</strong></td>
</tr>
<tr>
<td style="width: 50%;">
<ul>
<li>Very modular and robust extension ecosystem</li>
<li><a href="https://code.visualstudio.com/docs/remote/remote-overview" target="_blank" rel="noopener noreferrer">Remote Developer Pack</a></li>
<li>Built-in Git features</li>
<li>Fast code searching</li>
<li>Built-in terminal tab's</li>
<li><code>Ctrl + P</code> fast search menu</li>
<li>Saves your entire setup to a config file that can be shared on new machines</li>
<li>Offers suggestions for extensions if you open a file that is an unrecognized format</li>
<li>Command line integration via the <code>code</code> cli (A bit of a pretentious name for their cli)</li>
</ul>
</td>
<td style="width: 50%;">
<ul>
<li>Intellisense is very dumb unless you do a lot of configuration</li>
<li>Terminal can only be split vertically, sometimes I want to split horizontally</li>
</ul>
</td>
</tr>
</tbody>
</table>
<p>I've used so many text editors over the years: Brackets, Atom, Notepad++, Sublime, Bluefish, GGTS, Eclipse, Visual Studio, and even just VIM. To be honest, until I started using VS Code, mostly I chose an editor based on if I could find a syntax color theme I liked. </p>
<p>I will say the #1 thing I love about VS Code is the <a href="https://code.visualstudio.com/docs/remote/remote-overview" target="_blank" rel="noopener noreferrer">Remote Developer Pack</a>. Instead of running an SFTP plugin or having just a terminal in another screen, this is a truely seamless experience. You get all the same code searching and features normally only available locally with it. I've been using it to connect to remote servers as well as with <a href="https://docs.microsoft.com/en-us/windows/wsl/install" target="_blank" rel="noopener noreferrer">Windows WSL</a>. </p>
<p>VS Code is not as oriented to a specific task as an IDE like Eclipse or Visual Studio. This is actually nice for me since I work on a lot of different tasks and like that I can tailor VS code to my needs, instead of trying to shoe horn stuff into other IDE's.</p>
<hr>
<h2>Cloud Provider: <em>Too difficult to say</em></h2>
<p>Haha didn't expect that one did you?</p>
<p>I feel like this is very dependent on the task. Obviously I like any provider that has integration with other tools I mentioned. So ill just talk briefly about the one's I've used.</p>
<h3><a href="https://aws.amazon.com/" target="_blank" rel="noopener noreferrer">AWS</a></h3>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://patcave.dev/media/posts/10/eeYUFCO.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/10/responsive/eeYUFCO-xs.png 300w ,https://patcave.dev/media/posts/10/responsive/eeYUFCO-sm.png 480w ,https://patcave.dev/media/posts/10/responsive/eeYUFCO-md.png 768w ,https://patcave.dev/media/posts/10/responsive/eeYUFCO-lg.png 1024w ,https://patcave.dev/media/posts/10/responsive/eeYUFCO-xl.png 1360w ,https://patcave.dev/media/posts/10/responsive/eeYUFCO-2xl.png 1600w"  alt="" width="128" height="128"></figure>The biggest player in this space and by far the most features and services, most of which I don't generally use. AWS's core things work very well, but one thing I don't like about AWS is how a lot of the newer tools feel kind of thrown together and often have a lot of problems actually using them. As with their UI, the experience of the developer feels like an afterthought a lot of the time. </p>
<p>AWS is the only one I've used that has a robust virtual networking offering which is huge for any company trying to do basic network security. And on the subject of security; IAM permissions are a horrendous pain to use, especially if you're not familiar with all the in's and outs of how it works; though no other provider does it any better.</p>
<p>AWS is also by far the most expensive one here. For hobby projects, it's a bit costly unless you really do a good job with cost management. AWS also makes it easy to spend way more than you expect and you need dedicated engineers to pay attention to this kind of thing.</p>
<p>For business uses, I think AWS is the clear choice.</p>
<h3><a href="https://cloud.google.com/" target="_blank" rel="noopener noreferrer">Google Cloud</a></h3>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://patcave.dev/media/posts/10/Google-Cloud-Platform-GCP-logo.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/10/responsive/Google-Cloud-Platform-GCP-logo-xs.png 300w ,https://patcave.dev/media/posts/10/responsive/Google-Cloud-Platform-GCP-logo-sm.png 480w ,https://patcave.dev/media/posts/10/responsive/Google-Cloud-Platform-GCP-logo-md.png 768w ,https://patcave.dev/media/posts/10/responsive/Google-Cloud-Platform-GCP-logo-lg.png 1024w ,https://patcave.dev/media/posts/10/responsive/Google-Cloud-Platform-GCP-logo-xl.png 1360w ,https://patcave.dev/media/posts/10/responsive/Google-Cloud-Platform-GCP-logo-2xl.png 1600w"  alt="" width="128" height="128"></figure>I think the biggest thing GCP has going for it is that it's UI feels "designed" and like it's not programmatically generated. </p>
<p>I had used GCP for a while with my <a href="https://patcave.dev/automate-all-the-things/" target="_blank" rel="noopener noreferrer">Team Fortress 2 servers</a>, but ultimately ended up moving to AWS because of the lack of features for VM management and their API wasn't quite as simple to integrate with as AWS (GCP has no equivalent to <a href="https://aws.amazon.com/sdk-for-python/" target="_blank" rel="noopener noreferrer">boto3</a>)</p>
<p>GCP's user permissions are also way more byzantine than I expected, and is somehow more confusing to me than AWS IAM. I can't believe I'm going to say this sentence: I'd rather use AWS IAM over GCP's permissions.</p>
<p>They offer a similar suite of features to that of AWS, and is a serious contender for business use cases.</p>
<p>On the subject of cost, GCP has way better cost estimation tools and they tell you the price before you stand something up in their UI; something AWS just simply does not do. Though that said, most of the time I'm standing up infrastructure programmatically so I don't really see this kind of thing often.</p>
<h3><a href="https://www.vultr.com/" target="_blank" rel="noopener noreferrer">Vultr</a></h3>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://patcave.dev/media/posts/10/grmuy1fs409mjcg3xrqs.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/10/responsive/grmuy1fs409mjcg3xrqs-xs.png 300w ,https://patcave.dev/media/posts/10/responsive/grmuy1fs409mjcg3xrqs-sm.png 480w ,https://patcave.dev/media/posts/10/responsive/grmuy1fs409mjcg3xrqs-md.png 768w ,https://patcave.dev/media/posts/10/responsive/grmuy1fs409mjcg3xrqs-lg.png 1024w ,https://patcave.dev/media/posts/10/responsive/grmuy1fs409mjcg3xrqs-xl.png 1360w ,https://patcave.dev/media/posts/10/responsive/grmuy1fs409mjcg3xrqs-2xl.png 1600w"  alt="" width="128" height="97"></figure>I like Vultr. It's simple. They have a lot of good locations in the US, which is great for me as someone who hosts game servers. </p>
<p>They obviously don't have nearly as many features as AWS or GCP, but they knock their VM stuff out of the park. Infact you can literally upload your own .iso to it and just run that; opening up the possibility of doing local builds and just sharing an .iso. They also have Windows VM's if you need that kind of thing.</p>
<p>They have recently added an S3-compatible "spaces" offering which I'm using to good effect for TF2maps static sites. It's not fully flushed out though, so I'll hold my judgement on it for now.</p>
<p>It has everything you need for small projects. Built-in DNS, VM's, Block Storage, Firewalls, Load Balancers. It's networking is lacking a bit in my opinion. </p>
<p>3 features I want from Vultr:</p>
<ul>
<li>An AWS Fargate type docker runtime. Basically I just give you a container and you run it</li>
<li>Lambda's</li>
<li>Managed databases</li>
</ul>
<p>A few years ago I had a lot of networking issues hosting game servers, which has turned me off from using them. It appears that this is no longer a problem given we're running almost entirely on Vultr at TF2Maps.</p>
<h3><a href="https://www.digitalocean.com/" target="_blank" rel="noopener noreferrer">DigitalOcean</a></h3>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://patcave.dev/media/posts/10/1200px-DigitalOcean_logo.svg.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/10/responsive/1200px-DigitalOcean_logo.svg-xs.png 300w ,https://patcave.dev/media/posts/10/responsive/1200px-DigitalOcean_logo.svg-sm.png 480w ,https://patcave.dev/media/posts/10/responsive/1200px-DigitalOcean_logo.svg-md.png 768w ,https://patcave.dev/media/posts/10/responsive/1200px-DigitalOcean_logo.svg-lg.png 1024w ,https://patcave.dev/media/posts/10/responsive/1200px-DigitalOcean_logo.svg-xl.png 1360w ,https://patcave.dev/media/posts/10/responsive/1200px-DigitalOcean_logo.svg-2xl.png 1600w"  alt="" width="128" height="128"></figure>Very similar to Vultr, and similarly I do like DigitalOcean for the same reasons. Their networking is more mature and they do have a database offering as well. They recently launched an Apps feature, which as far as I can tell is basically just <a href="https://www.heroku.com/" target="_blank" rel="noopener noreferrer">Heroku</a>.</p>
<p> </p>
<p>If DigitalOcean added the features i mentioned in the Vultr section, I'd happily switch to them. They have the managed DB part down.</p>
<hr>
<h2>Honorable Mentions</h2>
<ul>
<li><a href="https://www.packer.io/" target="_blank" rel="noopener noreferrer">Packer </a>- A simple program for doing Golden image builds. Very modular and simple</li>
<li><a href="https://cloud.redhat.com/learn/topics/rkt" target="_blank" rel="noopener noreferrer">RKT</a> - A Docker-compatible runtime that has a lot more security features vs Docker itself. </li>
<li><a href="https://www.pgcli.com/" target="_blank" rel="noopener noreferrer">pgcli</a>, <a href="https://www.mycli.net/" target="_blank" rel="noopener noreferrer">mycli</a>, <a href="https://github.com/dbcli/litecli" target="_blank" rel="noopener noreferrer">litecli</a> - Database CLI's that use <a href="https://python-prompt-toolkit.readthedocs.io/en/master/" target="_blank" rel="noopener noreferrer">Prompt-Toolkit</a>. Nice features like syntax highlighting, and context aware tab completion</li>
<li><a href="https://github.com/tmux/tmux/wiki" target="_blank" rel="noopener noreferrer">tmux</a> - Great for running some services in the background without losing access to the command line. Also great for doing multiple tasks on a VM at the same time</li>
<li><a href="https://stedolan.github.io/jq/" target="_blank" rel="noopener noreferrer">jq</a> - An attempt to make JSON parsing work in bash without crazy awk, sed and grep pipelines.</li>
<li><a href="https://www.figma.com/" target="_blank" rel="noopener noreferrer">Figma </a>- A free UI prototyping tool. Mostly I've used this as a way to show a more tangible face to some of the backend things I want to do at TF2Maps.</li>
<li><a href="https://www.lucidchart.com/pages/">LucidCharts</a> - Great for making infrastructure diagrams</li>
<li><a href="https://github.com/duo-labs/cloudtracker" target="_blank" rel="noopener noreferrer">CloudTracker </a>- A great tool for creating least priveledge IAM permissions</li>
</ul>
<hr>
<h2>These are <em>just </em>tools</h2>
<p>It's important to remember that just like a hammer is a tool, all of these are just tools for specific tasks. I'm not married to any one of these tools and will switch if another tool comes along that solves my problem better. </p>
<p>To me it's more important to follow principals of staying organized than to become obsessed with how a certain tool solves a problem. Almost all of these tools I like because they allow me to maintain that kind of simplicity and organization that I want.</p>
<p>One final thought; remember that everyone's use case is different and sometimes a tool is still a better choice even when it has less exciting features because of things like cost and whether engineers at your organization are already familiar with it. There is a cost to onboarding new tools and learning their paradigms.</p>
<p> </p>
<p> </p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>I&#x27;m officially a &quot;professional&quot; game designer!</title>
        <author>
            <name>Patrick Hennessy</name>
        </author>
        <link href="https://patcave.dev/i-am-officially-a-professional-game-designer/"/>
        <id>https://patcave.dev/i-am-officially-a-professional-game-designer/</id>
            <category term="Level Design"/>
            <category term="Gaming"/>

        <updated>2023-01-11T19:02:34-07:00</updated>
            <summary>
                <![CDATA[
                        <img src="https://patcave.dev/media/posts/5/Pd_farmageddon.png" alt="" />
                    <p>Okay okay, maybe that title is a bit of an overstatement, but I have very exciting news!</p>
<p>Among my many hobbies; one I've been doing since the beginning of the pandemic is level design, specifically for the game: Team Fortress 2.</p>
<p>I'm excited to share that today Valve purchased a level that I worked on (with a team of 8 others)!</p>

                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                    <img src="https://patcave.dev/media/posts/5/Pd_farmageddon.png" alt="" />
                <p>Okay okay, maybe that title is a bit of an overstatement, but I have very exciting news!</p>
<p>Among my many hobbies; one I've been doing since the beginning of the pandemic is level design, specifically for the game: Team Fortress 2.</p>
<p>I'm excited to share that today Valve purchased a level that I worked on (with a team of 8 others)!</p>

<p>Here is the Steam Workshop link: <a href="https://steamcommunity.com/sharedfiles/filedetails/?id=2237224308">https://steamcommunity.com/sharedfiles/filedetails/?id=2237224308</a></p>
<hr>
<h2>Farmageddon</h2>
<p>The concept of the map is that a farmer, in wanting to make the biggest pumpkin he could, gave his pumpkin radioactive sludge to make it as big as possible. It's possible that he was eaten by his creation...</p>
<p>Your job is to collect enough weed killer to kill this abomination!</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="3">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/5/gallery/download-3.jpg" data-size="1680x1050"><img loading="lazy" src="https://patcave.dev/media/posts/5/gallery/download-3-thumbnail.jpg" alt="" width="720" height="450"></a>
<figcaption>Gourdon, the pumpkin monster</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/5/gallery/1280px-Farmageddon_RED_Grain_Shed_Outside.jpeg" data-size="1280x800"><img loading="lazy" src="https://patcave.dev/media/posts/5/gallery/1280px-Farmageddon_RED_Grain_Shed_Outside-thumbnail.jpeg" alt="" width="720" height="450"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/5/gallery/download-1-2.jpg" data-size="1680x1050"><img loading="lazy" src="https://patcave.dev/media/posts/5/gallery/download-1-2-thumbnail.jpg" alt="" width="720" height="450"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/5/gallery/download-1.jpg" data-size="637x358"><img loading="lazy" src="https://patcave.dev/media/posts/5/gallery/download-1-thumbnail.jpg" alt="" width="637" height="358"></a>
<figcaption>Beware of the scarecrow monsters!</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/5/gallery/Farmageddon_Inside_Pumpkin.png" data-size="1920x1080"><img loading="lazy" src="https://patcave.dev/media/posts/5/gallery/Farmageddon_Inside_Pumpkin-thumbnail.png" alt="" width="720" height="405"></a>
<figcaption>Inside the pumpkin, you can find his heart</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/5/gallery/Farmageddon_overview.png" data-size="1366x768"><img loading="lazy" src="https://patcave.dev/media/posts/5/gallery/Farmageddon_overview-thumbnail.png" alt="" width="720" height="405"></a>
<figcaption>Level Overview</figcaption>
</figure>
</div></div>
<hr>
<h2>My contributions</h2>
<p>I mostly worked on some of the models, animations, particle effects and bug fixes. Below is just a small showcase of those items.</p>
<h3>Weed Killer Pickups</h3>
<p>These 2 models are the pickups you get when you kill a player:</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/5//gallery/Largeweedkiller.png" data-size="725x725"><img loading="lazy" src="https://patcave.dev/media/posts/5//gallery/Largeweedkiller-thumbnail.png" alt="" width="720" height="720"></a>
<figcaption>Large Weed Killer Pickup</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/5//gallery/Weedkillersmall.png" data-size="725x725"><img loading="lazy" src="https://patcave.dev/media/posts/5//gallery/Weedkillersmall-thumbnail.png" alt="" width="720" height="720"></a>
<figcaption>Small Weed Killer Pickup</figcaption>
</figure>
</div></div>
<p>Here is a video timelapse of the modeling and texturing process:<div class="post__iframe"><iframe loading="lazy" width="560" height="314" src="https://www.youtube.com/embed/Q1kpbwl7Lko" allowfullscreen="allowfullscreen" data-mce-fragment="1"></iframe></div>
<hr>
<h3 class="align-left">Pumpkin monster breathing animations</h3>
<p>Another thing I did was the pumpkin idle animations to make it feel more "alive". Before it just kinda sat there completely still, it didn't feel like it was really a monster.</p>
<p>Basically I gave the big areas some up and down movement, and added bones to each tumor to make them appear to be pulsing.</p>
<figure class="post__video"><video loading="lazy" width="300" height="150" controls="controls" data-mce-fragment="1">
<source src="https://cdn.discordapp.com/attachments/279748353343094784/886125731107700746/2021-09-10_23-46-30.mp4" type="video/mp4" /></video></figure>
<p>This was surprisingly difficult because Source engine requires the use of "bones" and each bone can rigidly deform a set of vertices near by it. Making a "rig" for basically a sphere is a bit unconventional. It works though! </p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="1">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/5/gallery/Screenshot_145.png" data-size="1919x1049"><img loading="lazy" src="https://patcave.dev/media/posts/5/gallery/Screenshot_145-thumbnail.png" alt="" width="720" height="394"></a>
<figcaption>Gourdon Rig</figcaption>
</figure>
</div></div>
<hr>
<h3>Pumpkin monster death effects</h3>
<p>This is the full "death" effect I did for the pumpkin monster (named "Gourdon")</p>
<figure class="post__video"><video loading="lazy" width="300" height="150" controls="controls" data-mce-fragment="1">
<source src="https://cdn.discordapp.com/attachments/823623265796751370/887521282147647528/2021-09-14_20-12-14.mp4" type="video/mp4" /></video></figure>
<p>This effect was created using a physics simulation inside of Blender along side a technique called "cell fracture" where you randomly split the model into smaller "cells". </p>
<p>Big shoutout to a Blender plugin called <a href="https://github.com/bonjorno7/SourceOps">SourceOps </a>for making it easy to import level geometry into Blender!</p>
<figure class="post__video"><video loading="lazy" width="300" height="150" controls="controls" data-mce-fragment="1">
<source src="https://cdn.discordapp.com/attachments/709803812881170607/886865559445205062/2021-09-13_00-45-54.mp4" type="video/mp4" /></video></figure>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/5/gallery/Screenshot_159.png" data-size="1919x1049"><img loading="lazy" src="https://patcave.dev/media/posts/5/gallery/Screenshot_159-thumbnail.png" alt="" width="720" height="394"></a>
<figcaption>Gibs</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/5/gallery/Screenshot_153.png" data-size="1919x1049"><img loading="lazy" src="https://patcave.dev/media/posts/5/gallery/Screenshot_153-thumbnail.png" alt="" width="720" height="394"></a>
<figcaption>Particle Effects</figcaption>
</figure>
</div></div>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Game Server Monitoring </title>
        <author>
            <name>Patrick Hennessy</name>
        </author>
        <link href="https://patcave.dev/game-server-monitoring/"/>
        <id>https://patcave.dev/game-server-monitoring/</id>
            <category term="Programming Stuff"/>
            <category term="Gaming"/>

        <updated>2023-01-11T19:01:50-07:00</updated>
            <summary>
                <![CDATA[
                        <img src="https://patcave.dev/media/posts/9/Screenshot_1.png" alt="" />
                    <p>I was brought on to the <a href="https://tf2maps.net/">TF2Maps.net</a> staff team to bring my technical expertise to help maintain and improve the state of our systems. </p>
<p>The first thing I did in order to get an idea of how the servers were used was to install some monitoring software. </p>

                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                    <img src="https://patcave.dev/media/posts/9/Screenshot_1.png" alt="" />
                <p>I was brought on to the <a href="https://tf2maps.net/">TF2Maps.net</a> staff team to bring my technical expertise to help maintain and improve the state of our systems. </p>
<p>The first thing I did in order to get an idea of how the servers were used was to install some monitoring software. </p>
<hr>
<h2>Tools that have previously failed me</h2>
<p>I've had previous experience with a bunch of different tools, but none of them I was really a big fan of, they all have annoying limitations. I've used all of these tools at one point in my career:</p>
<ul>
<li><a href="https://www.influxdata.com/time-series-platform/">TICK stack</a></li>
<li><a href="https://www.nagios.org/">Nagios</a></li>
<li><a href="https://aws.amazon.com/cloudwatch/">AWS Cloudwatch</a><a href="https://sensu.io/"></a></li>
<li><a href="https://sensu.io/">Sensu</a></li>
<li><a href="https://www.sumologic.com/">Sumologic</a></li>
<li><a href="https://www.elastic.co/what-is/elk-stack">ELK stack</a></li>
</ul>
<p>The TICK stack is nice except their dashboarding tool is really lacking in a lot of ways, and their query language is not as nice for more complex use cases. ELK stack has a huge amount of overhead to get it setup well, but a good ELK setup is defiantly really nice. Nagios is just... well... Nagios, not much needs to be said that has been said to death already. Cloudwatch I want to like, but its obviously AWS specific and it's dashboarding is really limited as well. Even pulling metrics from it into Grafana is still really hard to use meaningfully. Infact, as recently as 6 months ago, I tried to do just this but I couldn't find even a documentation page on their query syntax. </p>
<hr>
<h2>Enter Prometheus</h2>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://patcave.dev/media/posts/9/1200px-Prometheus_software_logo.svg.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/9/responsive/1200px-Prometheus_software_logo.svg-xs.png 300w ,https://patcave.dev/media/posts/9/responsive/1200px-Prometheus_software_logo.svg-sm.png 480w ,https://patcave.dev/media/posts/9/responsive/1200px-Prometheus_software_logo.svg-md.png 768w ,https://patcave.dev/media/posts/9/responsive/1200px-Prometheus_software_logo.svg-lg.png 1024w ,https://patcave.dev/media/posts/9/responsive/1200px-Prometheus_software_logo.svg-xl.png 1360w ,https://patcave.dev/media/posts/9/responsive/1200px-Prometheus_software_logo.svg-2xl.png 1600w"  alt="" width="183" height="181"></figure>
<p>For the longest time; I was confused at how <a href="https://prometheus.io/">Prometheus</a> worked, and they way they explain its "no long term storage" thing I think turned me off for a long time on it. </p>
<p>I thought I'd give it a try on the recommendation of a co-worker and it being probably the only tool I haven't yet tried for monitoring.</p>
<p><a href="https://grafana.com/pricing/">Grafana cloud</a> also has a free tier offering that was enough for me to test stuff out with! </p>
<p>The storage thing is basically just that they don't retain metrics forever and also don't offer any kind of highly available or redundant storage solution. This is fine for my use case, but may not be suitable for everyone.</p>
<hr>
<h2>What to track?</h2>
<p>So I will be monitoring our 4 <a href="https://wiki.teamfortress.com/wiki/Linux_dedicated_server">Team Fortress 2 game servers</a>. </p>
<p>Currently they run on <a href="https://en.wikipedia.org/wiki/Colocation_centre">co-located</a> hardware. 2 in New York and 2 in Frankfurt. Each server has its own distinct hostname (DNS resolvable), so I can use that as a "key" to filter by each instance. </p>
<p>Basically I just want to have some insight to what is going on the server when I'm not directly logged in and so we can analyze historical events like server crashes or moderation events.</p>
<h5>Instance metrics</h5>
<ul>
<li>CPU Metrics</li>
<li>Memory Use</li>
<li>Disk Space and IO</li>
<li>Network IO, packet loss, bandwidth usage</li>
</ul>
<h5>Process metrics</h5>
<ul>
<li>Is the srcds process even running?</li>
<li>How long has the srcds process been running</li>
</ul>
<h5>Game Server stats</h5>
<ul>
<li>Current player count</li>
<li>Current level server is set to</li>
<li>Histogram of player activity</li>
<li>Histogram of levels played</li>
</ul>
<hr>
<h2>How to gather metrics with Prometheus?</h2>
<p>To my surprise, this is actually quite easy to do, every other tool I've used is very cumbersome to setup. Each of the buckets of metrics I want can be grabbed basically out of the box using <a href="https://prometheus.io/docs/instrumenting/exporters/">Prometheus exporters</a>. Here are the exporters I used:</p>
<ul>
<li><a href="https://github.com/prometheus/node_exporter">Node Exporter</a> - Stats about the machine itself.</li>
<li><a href="https://github.com/ncabatoff/process-exporter">Process Exporter</a> - Stats about specific processes on the machine</li>
<li><a href="https://github.com/galexrt/srcds_exporter">Srcds Exporter</a> - Specific to srcds data like map name, player count, etc.</li>
</ul>
<p>You basically give each one a config file to read a few variables like telling it how to find the process it needs to watch or information about the hostname and most importantly, the Prometheus instance to publish them to. </p>
<p>Now this is the part that was confusing for me at first. You need to run a local Prometheus on the server in order to publish / replicate the metrics to a remote one that does the actual aggregation. In this case, my local Prometheus published to the one hosted by Grafana Cloud. </p>
<p>On a small aside, I'll also say the Grafana Cloud docs are fantastic for setting this up and I had absolutely no issues getting going with it.</p>
<p>Below are snippets from my config files (node_exporter needs no config):</p>
<pre><code class="language-yaml">process_names:
  - name: "srcds_linux"
    exe:
    - tmux
    cmdline:
    - new -d -s tf .*
</code></pre>
<pre><code class="language-yaml">options:
  rcontimeout: 60s
  cachetimeout: 15s
servers:
  us.tf2maps.net:
    address: 172.93.228.253:27015
    rconpassword: ***password***
</code></pre>
<hr>
<h2>Logging Admin Actions</h2>
<p>TF2 servers often run modding platforms that come with an ecosystem of plugins for doing administrative actions. One very popular platform is called <a href="https://www.sourcemod.net/">SourceMod</a>. Many of the built-in plugins will log important information like administrator actions to a log file that I can parse. Obviously logs are not like metrics that can be measured every minute, log files get new lines at random intervals, so it requires a different tool.</p>
<p>Thankfully, Prometheus has an answer to this! In order to scrape logs you have to run another service called <a href="https://grafana.com/oss/loki/">Loki</a>, which is advertised as "Prometheus but for logs". Grafana Cloud's free tier also comes with Loki access. Awesome!</p>
<p>To actually export to Loki, you need to run a Prometheus style exporter called <a href="https://grafana.com/docs/loki/latest/clients/promtail/">Promtail</a>. </p>
<p>Now, most people who know me know that I'm a wizard with <a href="https://en.wikipedia.org/wiki/Regular_expression">Regular Expressions</a>, though admittedly this one took me a while to get right. Whoever created this log format clearly did not intend for it to be parsed. (Aside: Lea Verou has a great <a href="http://projects.verou.me/regexplained/">RegEx testing tool</a> that I highly recommend for this kind of task.)</p>
<p>Below is the full config I used to parse the SourceMod log files. (I would have really preferred JSON, but SourceMod has been around since before JSON was a thing)</p>
<pre><code class="language-yaml">---
  pipeline_stages:
  - match:
      selector: '{job="sourcemod"}'
      stages:
      - regex:
          expression: 'L (?P\d{2}\/\d{2}\/\d{4} - \d{2}:\d{2}:\d{2}): \[(?P.*\.smx)\] \"(?P[A-Za-z0-9\ ]+)&lt;\d+&gt;&lt;(?P\[U:[0-9]:[0-9]+\])&gt;&lt;[\d\w]{0,}&gt;" (?P.*)'
      - labels:
          plugin:
          user_name:
          steam_id:
      - timestamp:
          format: '01/02/2006 - 15:04:05'
          source: timestamp
      - output:
          source: message
  - match:
      selector: '{job="sourcemod",plugin!~".*.smx"}'
      stages:
      - drop:
          expression: ".*"

  static_configs:
  - labels:
      job: sourcemod
      __path__: /home/tf/tf/addons/sourcemod/logs/L*.log
</code></pre>
<p>This basically pulls out the timestamp, plugin name, user name, their steam id, and the actual log message.</p>
<hr>
<h2>Configuring Grafana</h2>
<p>Now that we're collecting these stats, we need to display them! Prometheus does not have a tool for this themselves. The obvious choice here is Grafana, especially because they're very well integrated cloud free tier.</p>
<p>They have a really nice query builder / explorer to use. Very intuitive, and works well with both Prometheus and Loki out of the box. For most simple queries you can just click a few buttons and get what you want without having to even learn <a href="https://prometheus.io/docs/prometheus/latest/querying/basics/">PromQL</a>.</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="1">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/9/gallery/Screenshot_4.png" data-size="1544x931"><img loading="lazy" src="https://patcave.dev/media/posts/9/gallery/Screenshot_4-thumbnail.png" alt="" width="720" height="434"></a></figure>
</div></div>
<p>For the actual dashboard panels, I wanted to be able to filter by host. You basically just setup a dashboard variable and then you can use it as a variable in your queries:</p>
<pre><code class="language-yaml">srcds_playercount_humans{instance=~"$host"}
time() - namedprocess_namegroup_oldest_start_time_seconds{instance=~"$host"}
</code></pre>
<p>Most panels were very easy to setup. Infact most of them were just simple 1 liners just like the queries in the above code.</p>
<p>The Admin log required 3 transforms to display how I wanted. Filtering out unwanted messages, setting up the table fields, and a labels to fields transform.</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="3">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/9/gallery/Screenshot_6.png" data-size="1193x830"><img loading="lazy" src="https://patcave.dev/media/posts/9/gallery/Screenshot_6-thumbnail.png" alt="" width="720" height="501"></a></figure>
</div></div>
<p>The panel that gave me the most trouble was the "Map activity" panel.</p>
<p>It is intended to show the history of the maps played. To get this to work, I had to use the deprecated version of their Graph panel, and setup a transform that converts a Prometheus label to the legend of the graph. If that sounds confusing, that's because it is. I basically monte carlo'd it into working.</p>
<p> ¯\_(ツ)_/¯</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="3">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/9/gallery/Screenshot_5.png" data-size="451x197"><img loading="lazy" src="https://patcave.dev/media/posts/9/gallery/Screenshot_5-thumbnail.png" alt="" width="451" height="197"></a></figure>
</div></div>
<hr>
<h2>Full Dashboard</h2>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="1">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/9/gallery/Screenshot_1.png" data-size="1546x903"><img loading="lazy" src="https://patcave.dev/media/posts/9/gallery/Screenshot_1-thumbnail.png" alt="" width="720" height="421"></a></figure>
</div></div>
<p>One thing to mention is that I also have a panel that is tracking the "time in use" stat. This is calculated by time spent running with at least 1 person connected; expressed as a percent. As you can see, we only actually use the servers very little compared to how long they run, and that's a generous number because simply having 1 player on the server doesn't really mean its "in use". This is part of a larger case I'm making for <a href="https://patcave.dev/automate-all-the-things/">ad-hoc servers</a>.</p>
<hr>
<h2>Takeaways from the data</h2>
<p>Like I said above, I am tracking a number of stats so I can better determine our hardware needs in the future. Given our budget is extraordinarily low, we need to take as many optimizations as we can get. </p>
<p>The key takeaways I've gotton from this:</p>
<ul>
<li>The server is idle <em>way</em> too much. Ad-hoc servers would solve this problem</li>
<li>Our bandwidth is higher than I expected
<ul>
<li>Could be optimized by not having to keep a copy of a map on each server, but instead share a file system using something like EFS</li>
</ul>
</li>
<li>We're overprovisioned on CPU, RAM and Disk
<ul>
<li>We never go above 10% cpu use, and we have 2 cores available. A VM with a single core should be more than enough.</li>
<li>RAM use can get high at times, but that is a function of us running 2 srcds servers on the same VM. We could happily cut the provisioned RAM in half and be fine</li>
<li>We just don't need a huge disk. We can do a better job of clearing out <code>.dem</code> files (game recordings) and clean out maps after they're been played in our map tests. Disks are fairly cheap though I suppose</li>
</ul>
</li>
</ul>
<hr>
<h2>Alerting?</h2>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://patcave.dev/media/posts/9/Screenshot_7.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/9/responsive/Screenshot_7-xs.png 300w ,https://patcave.dev/media/posts/9/responsive/Screenshot_7-sm.png 480w ,https://patcave.dev/media/posts/9/responsive/Screenshot_7-md.png 768w ,https://patcave.dev/media/posts/9/responsive/Screenshot_7-lg.png 1024w ,https://patcave.dev/media/posts/9/responsive/Screenshot_7-xl.png 1360w ,https://patcave.dev/media/posts/9/responsive/Screenshot_7-2xl.png 1600w"  alt="" width="315" height="253"></figure>
<p>Now that I'm tracking this information, can I meaningfully alert off of it?</p>
<p>Prometheus does have a tool called <a href="https://prometheus.io/docs/alerting/latest/alertmanager/">AlertManager</a> for this task, but I must say this tool is very clunky to use, but even so I was able to get alerts working and publishing to our <a href="https://discord.com/">Discord</a> server.</p>
<p> </p>
<p>My big complaint with it is that you have to have a graph that is specific to a metric and setup as an "alarm graph" in order to alarm off of. In other words, I can't use my existing panels because alert manager doesn't like the <code>$host</code> filter. So I have to make a new dashboard just for alerts. </p>
<p>There is a big lack of customization for the data it shares with Discord and doesn't do a great job of keeping users informed when an alarm clears or is "acked". Also testing the whole alarming setup is really difficult and slow. It does feel like it was designed for a specific team and use case.</p>
<p>Honestly I'm probably better off using PagerDuty, but I'm not sure how to actually get the data out of Prometheus and into PagerDuty. </p>
<hr>
<h2>In summation</h2>
<p>I have to say that I am pleasantly surprised at how easy this all was to get setup with. Prometheus and Grafana both have done a great job with their documentation and designing their tools for engineers. However their alerting solution could use some work in my opinion. </p>
<p>I'm happy with this level of success in this small effort because it's a prototype for things I may possibly bring into my work one day. </p>
<p>There is a synergy in solving problems for TF2Maps. Often at work I don't get as many opportunities as I like to just experiment with new tools. It make sense why we don't just constantly prototype new tools, but in doing so we do sometimes miss valuable setup's like this. </p>
<p>I would absolutely recommend the Prometheus + Loki + Grafana stack for anyone running game servers, not just Source servers.</p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Procedural Texture creation</title>
        <author>
            <name>Patrick Hennessy</name>
        </author>
        <link href="https://patcave.dev/procedural-texture-creation/"/>
        <id>https://patcave.dev/procedural-texture-creation/</id>
            <category term="Level Design"/>
            <category term="Gaming"/>

        <updated>2023-01-11T19:02:00-07:00</updated>
            <summary>
                <![CDATA[
                        <img src="https://patcave.dev/media/posts/8/franklin-rell-materialvol22.jpg" alt="" />
                    <p>For a while now i've been learning to use a program called <a href="https://www.adobe.com/products/substance3d-designer.html">Substance Designer</a>. It allows you to procedurally and nondestructively create textures for 3D programs to use. For me i've been trying to use it to emulate the <a href="https://polycount.com/discussion/73559/texturing-for-team-fortress-2-a-short-guide">Team Fortress 2 Style</a>.</p>

                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                    <img src="https://patcave.dev/media/posts/8/franklin-rell-materialvol22.jpg" alt="" />
                <p>For a while now i've been learning to use a program called <a href="https://www.adobe.com/products/substance3d-designer.html">Substance Designer</a>. It allows you to procedurally and nondestructively create textures for 3D programs to use. For me i've been trying to use it to emulate the <a href="https://polycount.com/discussion/73559/texturing-for-team-fortress-2-a-short-guide">Team Fortress 2 Style</a>.</p>
<hr>
<h2>Node Workflow</h2>
<p>There's been a few programs i've used in the last few years that use a node based workflow. Blender, Davinci Resolve Fusion and now Substance; so this felt very natural to me. </p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="1">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8//gallery/tf_snow_graph.png" data-size="1167x515"><img loading="lazy" src="https://patcave.dev/media/posts/8//gallery/tf_snow_graph-thumbnail.png" alt="" width="720" height="318"></a></figure>
</div></div>
<p>Basiccaly you start on the leftmost part of the graph with something that produces a basic texture. These are usually <a href="https://en.wikipedia.org/wiki/Perlin_noise">procedrual noise</a> textures or shape nodes.</p>
<p>Just like with a painting or any kind of "art", you start with very rough and basic shapes, and slowly add in detail over time. </p>
<p>The set of nodes provided by Substance allow an artist to do mathematical transforms such as warps, blurs, blending with other sub graphs, color transformations, masking and a lot more. </p>
<p>The coolest thing about it being procedural is that you can change the inputs at the beginning of the graph and can produce a lot of variation. These textures all are produced using the same procedure, but just have different base shape inputs:</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="4">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/castle1-2.png" data-size="1024x1024"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/castle1-2-thumbnail.png" alt="" width="720" height="720"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/castle2-2.png" data-size="1024x1024"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/castle2-2-thumbnail.png" alt="" width="720" height="720"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/castle3-3.png" data-size="1024x1024"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/castle3-3-thumbnail.png" alt="" width="720" height="720"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/castle4-3.png" data-size="1024x1024"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/castle4-3-thumbnail.png" alt="" width="720" height="720"></a></figure>
</div></div>
<p>Also technically all the nodes produce <a href="https://en.wikipedia.org/wiki/Vector_graphics">vectorized graphics</a> under the hood, so you can scale your texture output to insane numbers like 16k x 16k. (For TF2 you generally work at 1024 or 512).</p>
<hr>
<h2>Maps, maps, maps</h2>
<p>For Team Fortress 2, you generally work with diffuse textures, which is essentially just color with <a href="https://en.wikipedia.org/wiki/Ambient_occlusion">ambient occlusion</a> baked onto it.</p>
<p>However substance is able to produce a wide variety of other maps useful for TF2.</p>
<h3>Specular</h3>
<p>Otherwise known as roughness; this is telling a renderer how shiney something should be. Or how much of the world to reflect on it's surface.</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/Screenshot_2.png" data-size="671x561"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/Screenshot_2-thumbnail.png" alt="" width="671" height="561"></a>
<figcaption>high specular</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/Screenshot_3.png" data-size="674x574"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/Screenshot_3-thumbnail.png" alt="" width="674" height="574"></a>
<figcaption>low specular</figcaption>
</figure>
</div></div>
<h3>Normal</h3>
<p>Normal maps are extremely useful and powerful and used almost everywhere.</p>
<p>Basically they allow an object to catch lighting without actually having any extra geometry. This is very powerful because calculating how light will hit a 3D object's face is relatively expensive (which is why game engines want low poly objects).</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/Screenshot_5.png" data-size="663x568"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/Screenshot_5-thumbnail.png" alt="" width="663" height="568"></a>
<figcaption>no normal map</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/Screenshot_6.png" data-size="666x571"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/Screenshot_6-thumbnail.png" alt="" width="666" height="571"></a>
<figcaption>with normal map</figcaption>
</figure>
</div></div>
<p>Normal maps do have their limitations though. If you view them really close up you can tell that it's actually just a flat plane instead of the geometery. They work best on adding small details like cracks, indentations or clothing folds but shouldn't be use to do larger details that need real geometry.</p>
<h3>Height</h3>
<p>One thing that I can't use because Source Engine simply doesn't support it is Height Maps. Substance Designer is very powerful with the addition of height maps. Basically they're black and white images that tell a renderer where to "push out" geometery. </p>
<p>Here is an example that demonstrates it this very powerful feature:</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/Screenshot_1.png" data-size="670x565"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/Screenshot_1-thumbnail.png" alt="" width="670" height="565"></a>
<figcaption>Rendered material</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/legoheight.png" data-size="2048x2048"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/legoheight-thumbnail.png" alt="" width="720" height="720"></a>
<figcaption>height map</figcaption>
</figure>
</div></div>
<h3>Other Maps</h3>
<ul>
<li><a href="https://developer.valvesoftware.com/wiki/$blendmodulatetexture">Blend Modulation</a> - Source Engine's way to blend between 2 different textures</li>
<li>Emissive - Source Engine allows a "self illumination" property so something looks like it glows while the object itself doesn't emit light</li>
</ul>
<hr>
<h2>My First Textures with Substance</h2>
<p>Okay, as usual my first attempts were horrendous, but its a learning process right?</p>
<table class="align-left" style="border-collapse: collapse; width: 100%; height: 157px; border-style: hidden;" border="0">
<tbody>
<tr style="height: 230px;">
<td style="width: 34.9373%; height: 10px;">
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="1">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/bark-2.png" data-size="2048x2048"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/bark-2-thumbnail.png" alt="" width="720" height="720"></a></figure>
</div></div>
</td>
<td style="width: 65.0627%; height: 10px;">
<p> </p>
<p>This was a wood bark texture I tried to make, quite unsuccessfully i admit. Though with this one I do think i got a rough gist of wood, perhaps adjusting the colors a bit would help sell it better. Though either way this def does not fit into TF2.</p>
</td>
</tr>
<tr style="height: 344px;">
<td style="width: 34.9373%; height: 86px;">
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="1">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/sand-2.png" data-size="2048x2048"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/sand-2-thumbnail.png" alt="" width="720" height="720"></a></figure>
</div></div>
</td>
<td style="width: 65.0627%; height: 86px;">
<p> </p>
<p>This was an attempt to make sand. I think I got the general wavy flow of sand but <span style="color: var(--text-editor-body-color); font-family: var(--font-base); font-size: inherit; font-weight: var(--font-weight-normal);">actually getting the "grainy-ness" of it it a lot harder, and defiantly not as easy as just </span><span style="color: var(--text-editor-body-color); font-family: var(--font-base); font-size: inherit; font-weight: var(--font-weight-normal);">adding a bunch of little dots everywhere haha.</span></p>
</td>
</tr>
<tr style="height: 344px;">
<td style="width: 34.9373%; height: 10px;">
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="1">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/dirt-2.png" data-size="1024x1024"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/dirt-2-thumbnail.png" alt="" width="720" height="720"></a></figure>
</div></div>
</td>
<td style="width: 65.0627%; height: 10px;">
<p> </p>
<p>I'm happy to call this one a complete failure. This is intended to be a dirt texture. I'm not even sure what I was thinking making it, but again; you gotta have these failures so you can get a feel for the tool and learn how to do things better over time.</p>
</td>
</tr>
<tr style="height: 344px;">
<td style="width: 34.9373%; height: 51px;">
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="1">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/floortile.png" data-size="1024x1024"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/floortile-thumbnail.png" alt="" width="720" height="720"></a></figure>
</div></div>
</td>
<td style="width: 65.0627%; height: 51px;">
<p> </p>
<p>This one is a tile floor texture I tried to replicate, its got the basic shapes but it's <span style="color: var(--text-editor-body-color); font-family: var(--font-base); font-size: inherit; font-weight: var(--font-weight-normal);">missing the dirty floor look like the in game one has.</span></p>
</td>
</tr>
</tbody>
</table>
<p>With all of these, its just baby steps. One thing I think i've done well in my life is embrace the learning process and accept that at the beginning of some new endeavor that I'm going to suck at it, but as long as I'm seeking to improve and being critical of my failures and understanding why I failed, then I improve quite quickly. </p>
<p>These textures were my first steps; attempts that I made within a few weeks of owning the software. The next section is textures I made after a few months of continued practice.</p>
<hr>
<h2>Showcasing my "better" textures</h2>
<p>Below is some textures and the graph for them that i've made after a few months of using Substance. These are much closer to the artstyle for TF2 and some of these I think could be used in game quite well.</p>
<table style="border-collapse: collapse; width: 100%; height: 595px; border-style: hidden;" border="0">
<tbody>
<tr style="height: 273px;">
<td style="width: 33.3333%; height: 117px;">
<p><strong>Wood</strong></p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8//gallery/tf_wood.png" data-size="1024x1024"><img loading="lazy" src="https://patcave.dev/media/posts/8//gallery/tf_wood-thumbnail.png" alt="" width="720" height="720"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8//gallery/tf_wood_graph.png" data-size="4063x1635"><img loading="lazy" src="https://patcave.dev/media/posts/8//gallery/tf_wood_graph-thumbnail.png" alt="" width="720" height="290"></a></figure>
</div></div>
<p> </p>
</td>
<td style="width: 33.3333%; height: 117px;">
<p><strong>Grass</strong></p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8//gallery/tf_grass.png" data-size="2048x2048"><img loading="lazy" src="https://patcave.dev/media/posts/8//gallery/tf_grass-thumbnail.png" alt="" width="720" height="720"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8//gallery/tf_grass_graph.png" data-size="1939x1059"><img loading="lazy" src="https://patcave.dev/media/posts/8//gallery/tf_grass_graph-thumbnail.png" alt="" width="720" height="393"></a></figure>
</div></div>
<p> </p>
</td>
<td style="width: 33.3333%; height: 117px;">
<p><strong><strong>Snow</strong></strong></p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8//gallery/tf_snow.png" data-size="1024x1024"><img loading="lazy" src="https://patcave.dev/media/posts/8//gallery/tf_snow-thumbnail.png" alt="" width="720" height="720"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8//gallery/tf_snow_graph-2.png" data-size="1167x515"><img loading="lazy" src="https://patcave.dev/media/posts/8//gallery/tf_snow_graph-2-thumbnail.png" alt="" width="720" height="318"></a></figure>
</div></div>
</td>
</tr>
<tr style="height: 219px;">
<td style="width: 33.3333%; height: 219px;"><strong><strong> Truss</strong></strong>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/tf_truss.png" data-size="1024x1024"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/tf_truss-thumbnail.png" alt="" width="720" height="720"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/tf_truss_graph.png" data-size="3091x1763"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/tf_truss_graph-thumbnail.png" alt="" width="720" height="411"></a></figure>
</div></div>
</td>
<td style="width: 33.3333%; height: 219px;"><span style="font-weight: var(--font-weight-bold); font-size: 18.4px;"><span style="font-weight: var(--font-weight-bold); font-size: 18.4px;">Cliff Wall</span></span>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/tf_cliff.png" data-size="1024x1024"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/tf_cliff-thumbnail.png" alt="" width="720" height="720"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/tf_cliff_graph.png" data-size="3571x1379"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/tf_cliff_graph-thumbnail.png" alt="" width="720" height="278"></a></figure>
</div></div>
</td>
<td style="width: 33.3333%; height: 219px;"><span style="font-weight: var(--font-weight-bold); font-size: 18.4px;"><span style="font-weight: var(--font-weight-bold); font-size: 18.4px;">Bamboo</span></span>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/bamboo.png" data-size="1024x1024"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/bamboo-thumbnail.png" alt="" width="720" height="720"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/bamboo_graph.png" data-size="2003x1027"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/bamboo_graph-thumbnail.png" alt="" width="720" height="369"></a></figure>
</div></div>
</td>
</tr>
<tr style="height: 219px;">
<td style="width: 33.3333%; height: 111px;"><span style="font-weight: var(--font-weight-bold); font-size: 18.4px;"><span style="font-weight: var(--font-weight-bold); font-size: 18.4px;">Herringbone Bamboo</span></span>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/herringbone.png" data-size="1024x1024"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/herringbone-thumbnail.png" alt="" width="720" height="720"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/herringbone_grpah.png" data-size="3539x1348"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/herringbone_grpah-thumbnail.png" alt="" width="720" height="274"></a></figure>
</div></div>
</td>
<td style="width: 33.3333%; height: 111px;"><span style="font-weight: var(--font-weight-bold); font-size: 18.4px;"><span style="font-weight: var(--font-weight-bold); font-size: 18.4px;">Chesterfield</span></span>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/chesterfield.png" data-size="1024x1024"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/chesterfield-thumbnail.png" alt="" width="720" height="720"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/chesterfield_graph-2.png" data-size="3538x1330"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/chesterfield_graph-2-thumbnail.png" alt="" width="720" height="271"></a></figure>
</div></div>
</td>
<td style="width: 33.3333%; height: 111px;"> <span style="font-weight: var(--font-weight-bold); font-size: 18.4px;"><span style="font-weight: var(--font-weight-bold); font-size: 18.4px;">Gold Coins</span></span>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/coins.png" data-size="1024x1024"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/coins-thumbnail.png" alt="" width="720" height="720"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/coin_graph.png" data-size="5048x2546"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/coin_graph-thumbnail.png" alt="" width="720" height="363"></a></figure>
</div></div>
</td>
</tr>
<tr style="height: 245px;">
<td style="width: 33.3333%; height: 148px;"><span style="font-weight: var(--font-weight-bold); font-size: 18.4px;"><span style="font-weight: var(--font-weight-bold); font-size: 18.4px;"><span style="font-weight: var(--font-weight-bold); font-size: 18.4px;"><span style="font-weight: var(--font-weight-bold); font-size: 18.4px;">Arrow Sign (recreation)</span></span></span></span>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/arrow_sign.png" data-size="1024x512"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/arrow_sign-thumbnail.png" alt="" width="720" height="360"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/arrow_sign_graph.png" data-size="2515x1315"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/arrow_sign_graph-thumbnail.png" alt="" width="720" height="376"></a></figure>
</div></div>
<span style="font-weight: var(--font-weight-bold); font-size: 18.4px;"><span style="font-weight: var(--font-weight-bold); font-size: 18.4px;"><br></span></span></td>
<td style="width: 33.3333%; height: 148px;">
<p><span style="font-weight: var(--font-weight-bold); font-size: 18.4px;"><span style="font-weight: var(--font-weight-bold); font-size: 18.4px;"> Stone Wall</span></span></p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/castle1.png" data-size="1024x1024"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/castle1-thumbnail.png" alt="" width="720" height="720"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/stonewall_graph.png" data-size="7892x2371"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/stonewall_graph-thumbnail.png" alt="" width="720" height="216"></a></figure>
</div></div>
</td>
<td style="width: 33.3333%; height: 148px;"> </td>
</tr>
</tbody>
</table>
<hr>
<h2>Crazy Stuff Expert Artists have Made</h2>
<p>These all blow my mind that it's possible to do this all procedurally but here we are. I feel very inadequate compared to these guys, but then again they're all professionals. I do think admiring art of people who are better than you at it is a great way to learn and inspire. I'll also say that all of these look way more impressive because they have the benefit of using height maps, which makes it look a lot better than the flat stuff I do, but as said before, I can't use height because TF2's Source Engine branch does not support it unfortunately. Nonetheless these are all very impressive.</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="4">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/daniel-thiger-spagetthi-sphere-b.jpg" data-size="1920x1920"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/daniel-thiger-spagetthi-sphere-b-thumbnail.jpg" alt="" width="720" height="720"></a>
<figcaption>https://www.artstation.com/artwork/gJV9bL</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/james-ritossa-padding-r-03.jpg" data-size="1920x1920"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/james-ritossa-padding-r-03-thumbnail.jpg" alt="" width="720" height="720"></a>
<figcaption>https://www.artstation.com/artwork/19P8L</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/javier-perez-jperez-tiki-01.jpg" data-size="1920x3240"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/javier-perez-jperez-tiki-01-thumbnail.jpg" alt="" width="720" height="1215"></a>
<figcaption>https://www.artstation.com/artwork/58RxrE</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/jonathan-benainous-sphere-01.jpg" data-size="1920x2054"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/jonathan-benainous-sphere-01-thumbnail.jpg" alt="" width="720" height="770"></a>
<figcaption>https://www.artstation.com/artwork/P9WN1</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/ke-liu-screenshot054.jpg" data-size="1920x1080"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/ke-liu-screenshot054-thumbnail.jpg" alt="" width="720" height="405"></a>
<figcaption>https://www.artstation.com/artwork/Z5QJ50</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/vladimir-fedorenko-preview-color.jpg" data-size="1441x1440"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/vladimir-fedorenko-preview-color-thumbnail.jpg" alt="" width="720" height="720"></a>
<figcaption>https://www.artstation.com/artwork/gJRmvP</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/3dextrude-tutorials-02.jpg" data-size="1920x731"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/3dextrude-tutorials-02-thumbnail.jpg" alt="" width="720" height="274"></a>
<figcaption>https://www.artstation.com/artwork/48e1E4</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/daniel-thiger-popsicle-render-a.jpg" data-size="1440x1440"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/daniel-thiger-popsicle-render-a-thumbnail.jpg" alt="" width="720" height="720"></a>
<figcaption>https://www.artstation.com/artwork/mq2bO8</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/james-ritossa-sphere.jpg" data-size="1920x1920"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/james-ritossa-sphere-thumbnail.jpg" alt="" width="720" height="720"></a>
<figcaption>https://www.artstation.com/artwork/1n18g2</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/ke-liu-screenshot000.jpg" data-size="1920x1920"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/ke-liu-screenshot000-thumbnail.jpg" alt="" width="720" height="720"></a>
<figcaption>https://www.artstation.com/artwork/1nJRRX</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/clark-coots-screenshot072.jpg" data-size="1080x1080"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/clark-coots-screenshot072-thumbnail.jpg" alt="" width="720" height="720"></a>
<figcaption>https://www.artstation.com/artwork/e0WJgw</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/8/gallery/imanol-delgado-port2222.jpg" data-size="1920x1079"><img loading="lazy" src="https://patcave.dev/media/posts/8/gallery/imanol-delgado-port2222-thumbnail.jpg" alt="" width="720" height="405"></a>
<figcaption>https://www.artstation.com/artwork/XoxBY</figcaption>
</figure>
</div></div>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Database-Linked Dataclasses</title>
        <author>
            <name>Patrick Hennessy</name>
        </author>
        <link href="https://patcave.dev/database-linked-dataclasses/"/>
        <id>https://patcave.dev/database-linked-dataclasses/</id>
            <category term="Programming Stuff"/>

        <updated>2023-01-11T19:03:09-07:00</updated>
            <summary>
                <![CDATA[
                        <img src="https://patcave.dev/media/posts/14/The-Ultimate-Guide-to-Data-Classes-in-Python-3-2.webp" alt="" />
                    <p>The most common use of a class in Python is acting as a data container. Throughout the development of our backend libraries at Narrative Science, we’ve seen several generations of code that attempts to make modeling this data easy.</p>
<p>Finding a simple to use abstraction for these type of classes allows us to focus less on writing mundane classes and more on our business logic.</p>
<p>In this post we will outline the evolution of this code and the pros and drawbacks of each approach.</p>

                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                    <img src="https://patcave.dev/media/posts/14/The-Ultimate-Guide-to-Data-Classes-in-Python-3-2.webp" alt="" />
                <p>The most common use of a class in Python is acting as a data container. Throughout the development of our backend libraries at Narrative Science, we’ve seen several generations of code that attempts to make modeling this data easy.</p>
<p>Finding a simple to use abstraction for these type of classes allows us to focus less on writing mundane classes and more on our business logic.</p>
<p>In this post we will outline the evolution of this code and the pros and drawbacks of each approach.</p>
<hr>
<h2><strong>V1: Basic classes</strong></h2>
<p>We have all been here; you write a class, and implement all the <a href="https://levelup.gitconnected.com/python-dunder-methods-ea98ceabad15">dunder</a> methods needed by that class. Simple and straight forward.</p>
<h5><strong>Example:</strong></h5>
<pre><code class="language-python">class User():
    def __init__(self, fname, lname, groups):
        if not isinstance(groups, list):
            raise ValueError("groups is not a list!")
        if not isinstance(fname, str):
            raise ValueError("fname is not a str!")
        if not isinstance(lname, str):
            raise ValueError("lname is not a str!")

        self.fname = fname
        self.lname = lname
        self.groups = groups
    
    def __repr__(self):
        return f"User({self.fname},{self.lname})"    </code></pre>
<h5><strong>Pros:</strong></h5>
<ul>
<li>This is very explicit, you can see everything this class does</li>
<li>Basic, most likely taught in beginner Python courses</li>
</ul>
<h5><strong>Cons:</strong></h5>
<ul>
<li>Type Checking<br>Notice all the defensive code needed to handle the cases when we receive arguments with incorrect types. Now imagine this example accepted 20 properties. More than likely the author of this code just wouldn’t even bother to write this kind of type checking since it’s tedious and time consuming. However not doing so can create complex error conditions in downstream code.<br><br></li>
<li>Database<br>Very commonly, these type of objects directly represent data from the database. <br>As it is currently written, you would need another translation layer outside the object to do your queries and create the objects as needed.<br><br></li>
<li>Testing<strong><br></strong>You will need to write tests to make sure this code correctly rejects invalid inputs.<br><br></li>
<li>Instantiating<strong><br></strong>In order to create an instance of this object, you need to pass <span data-token-index="1" data-reactroot="">every</span> value into the constructor:<br>
<pre><code class="language-python">new_user = User("frank", "smith", ["group1", "group2"])
</code></pre>
<br>It’s worth nothing that it is possible to do some fancy Python magic to create this object out of a dictionary:
<pre><code class="language-python">user_data = get_user_data()
new_user = User(**user_data)</code></pre>
</li>
</ul>
<p>But what if I want to dump the properties of this object <em>back</em> to JSON so we could send it over HTTP? You would have to write a method that takes every property and populates a dictionary.</p>
<hr>
<h2><strong>V2: Database linked classes</strong></h2>
<p>The idea here is: what if we created objects that were aware of the database?</p>
<h5><strong>Example:</strong></h5>
<pre><code class="language-python">from sqlalchemy import Table, Column, VARCHAR, UUID, JSONB
from ns_database import DatabaseObject

article_table = Table(
    "article",
    Column("id", UUID, primary_key=True),
    Column("name", VARCHAR(45), nullable=False),
    Column("slug", VARCHAR(45), nullable=False),
)

class Article(DatabaseObject):
    table = article_table
    primary_key = app_table.id

new_article = Article.create(id=UUID4(), name="Foo Bar", slug="foo-bar")
new_article.save()<br></code></pre>
<p>This essentially creates a class that is now able to push and pull its data from a database and functions like a normal object inside of Python.</p>
<p>The way this DatabaseObject class works is that the properties for the class are determined by the SQL Alchemy table.</p>
<p>DatabaseObject also implements a handful of convenient methods to help getting data in and out of the database such as: <code>create</code>, <code>get</code>, <code>save</code>, <code>update</code>, and <code>delete</code>. Each will perform the appropriate SQL query.</p>
<h5><strong>Pros:</strong></h5>
<ul>
<li>We have reduced the amount of tedium from boilerplate code.</li>
<li>Users have a database aware object without needing to write any SQL of their own.</li>
</ul>
<h5><strong>Cons:</strong></h5>
<ul>
<li>We still have the issue of having to make sure our types are correct when accepting them as parameters.</li>
<li>We are unable to set default values for properties, so we must always provide all properties, which is tedious. This is because we’re using SQL alchemy as the source of the property names and don’t have a mechanism for passing other values in.</li>
<li>What if someone inputs a string longer than 45 characters? We need to write code to check for that.</li>
<li>There is an assumption that we must know the type in Python that corresponds to the SQLAlchemy type.</li>
<li>Alongside the tests needed for the DatabaseObject class to ensure the database accessor methods work correctly, we will also need to still have tests that ensure that these classes reject invalid inputs and don’t just throw an exception from SQL alchemy.</li>
</ul>
<hr>
<h2><strong>V3: Along came dataclasses</strong></h2>
<p>Basically, a dataclass is a simple abstraction for classes that store data. It performs automatic type checking, serialization and deserialization, and implements things like <code>__eq__</code>, and gives a standard <code>__repr__</code>. This is basically what we wanted from Generation 1.</p>
<p>There have been many libraries before this to implement the data-class such as <a href="https://www.attrs.org/en/stable/">attrs</a>, <a href="https://marshmallow.readthedocs.io/en/stable/">marshmallow</a>, and <a href="https://schematics.readthedocs.io/en/latest/">schematics</a>. It is worth noting that Python 3.7 introduced an official the <a href="https://docs.python.org/3/library/dataclasses.html">Dataclass</a> to the core library as well.</p>
<p>The library we chose to use was <a href="https://pydantic-docs.helpmanual.io/">Pydantic</a> because it worked best with our implementation of <a href="https://fastapi.tiangolo.com/">FastAPI</a>.</p>
<div class="resource-block resource-block--content">
<div class="resource-block__rich-text">
<h5><strong>Example:</strong></h5>
<pre><code class="language-python">from enum import Enum
from typing import Optional
from pydantic import BaseModel, Field

class User(BaseModel):
    name: str = Field(max_length=45)
    department: Department = Field(default=Department.engineering)
    nickname: Optional[str]

class Department(str, Enum):
    engineering = "engineering"
    finance = "finanace"
</code></pre>
<h5><strong>Pros:</strong></h5>
<ul>
<li>Automatic type checking</li>
<li>Allow us to use other data structures like Enums or other models as types</li>
<li>Ability to set contraints such as string length</li>
<li>Ability to set default values</li>
<li>Greatly reduced boilerplate vs Generation 1</li>
<li>Easily readable</li>
<li>Integration with code editors intellisense</li>
<li>No need to write additional tests</li>
</ul>
<p>You can leverage other Python built-ins to do even more powerful things:</p>
<pre><code class="language-python">class User(BaseModel):
    first_name: str
    last_name: str

    @property
    def email(self):
        return f"{self.firstname[:1]}{self.last_name}@example.com"

    def send_message(self, msg):
        some_email_function(self.email, msg)

...

user = User("John", "Smith")
user.send_message("hey dude!")
</code></pre>
<p><strong style="color: var(--headings-color); font-family: var(--font-base); font-size: 1.125em; letter-spacing: -0.03rem;">Cons:</strong></p>
</div>
</div>
<ul>
<li>These models are not database aware like Generation 2.</li>
</ul>
<hr>
<h2><strong>V4: Adding a database to Pydantic</strong></h2>
<p>Building on our idea from before about turning an ordinary class into a database aware object; we combined the benefits of <a href="https://pydantic-docs.helpmanual.io/usage/validators/">Pydantic's input validation</a> and serialization with the <a href="https://www.sqlalchemy.org/">SQL Alchemy</a> based database class from Generation 2.</p>
<p>The result is that now we have functional ORM that works with <a href="https://fastapi.tiangolo.com/">FastAPI</a>!</p>
<h5>Example: </h5>
<pre><code class="language-python">from enum import Enum
from typing import Optional
from pydantic import BaseModel, Field

from ns_database import database_model, Databases

class UserIn(BaseModel):
    name: str = Field(max_length=45)
    department: Department = Field(default=Department.engineering)
    nickname: Optional[str]

class Department(str, Enum):
    engineering = "engineering"
    finance = "finanace"

@database_model(table_name="users", database_name=Databases.main)
class User(UserIn):
    id: Optional[UUID4] = Field(primary_key=True, fetch_on_create=True)
    created_at: Optional[datetime] = Field(fetch_on_create=True)
    updated_at: Optional[datetime] = Field(fetch_on_create=True
</code></pre>
<p>We add a class decorator that will use the properties of the model as fields in the database. This means that database models will be 1:1 representations of the database itself.</p>
<p>Using Pydantic, we are able to add additional keyword arguments to the Field function.</p>
<h5><strong>Fetch on create / update</strong></h5>
<p>We want our database to be the ultimate source of authority, so we rely on it to do the computation for certain fields.</p>
<p>This is done through setting <code>fetch_on_create</code> and <code>fetch_on_update</code> which will defer to the database’s generated value when a record is created or updated respectively.</p>
<p>All that needs to happen is your database is setup to have column defaults such as static values, subroutines, or sequences.</p>
<h5><strong>Primary key fields</strong></h5>
<p>Since all tables will have a primary key, each <code>database_model</code> object <em>must</em> have a primary key.</p>
<p>Currently we are not able to have compound keys meaning your key must be a single type. This may be a situation where we can create a special type for it to allow it to be more semantic.</p>
<p>This primary key field is also used by our data accessor method to know how to lookup a record.</p>
<p> </p>
<h5><strong>Pros:</strong></h5>
<ul>
<li>These database models get all the benefits from Generation 2 and 3!</li>
</ul>
<h5><strong>Cons:</strong></h5>
<ul>
<li>Lack of foreign key relationships may be misleading to developers. To many developers, this code is their interface to the table schema. Since we use <a href="https://alembic.sqlalchemy.org/en/latest/">Alembic</a> to manage our database schema and updates; there is actually no need to declare fields inside our database_model to be foreign keys. These are only used by SQL Alchemy in order to <em>create</em> your table schema. This does create a situation where our code and schema may not be in-sync. It is also worth noting that those foreign key constraints are still enforced by the database.</li>
<li>Database managed fields have to be “optional”. One limitation of Pydantic is that we have to make any field that the database will manage (such as the primary_key) as Optional type. The Pydantic validation code runs before we ever make our fetch from the database. This is syntactically a bit confusing.</li>
</ul>
<hr>
<h2><strong>Closing thoughts</strong></h2>
<p>These new database model classes have greatly improved the readability of our code and reduced the amount of boilerplate we need to write for new objects which ultimately means that we spend more time worrying about our own business logic vs writing all the low level stuff classes usually need.</p>
<p>As with most projects in software engineering, we kept making marginal improvements to the ideas until we got something awesome! I’m excited to see how this idea continues to grow to further help us achieve these same goals.</p>
<p>You can find the full code for this here: <a href="https://github.com/NarrativeScience/pynocular">https://github.com/NarrativeScience/pynocular</a></p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Learning 3D Modeling for TF2</title>
        <author>
            <name>Patrick Hennessy</name>
        </author>
        <link href="https://patcave.dev/learning-3d-modeling-for-video-games/"/>
        <id>https://patcave.dev/learning-3d-modeling-for-video-games/</id>
            <category term="Level Design"/>
            <category term="Gaming"/>

        <updated>2023-01-11T19:02:13-07:00</updated>
            <summary>
                <![CDATA[
                        <img src="https://patcave.dev/media/posts/7/20200515140446_1.jpg" alt="" />
                    <p>For a while now i've been working on level design projects at <a href="https://tf2maps.net/">TF2Maps.net</a>, and something that has been a huge need in the community is getting models for the game tailored to certain game play needs or to fit into a theme. </p>

                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                    <img src="https://patcave.dev/media/posts/7/20200515140446_1.jpg" alt="" />
                <p>For a while now i've been working on level design projects at <a href="https://tf2maps.net/">TF2Maps.net</a>, and something that has been a huge need in the community is getting models for the game tailored to certain game play needs or to fit into a theme. </p>

<p>Having run into this kind of problem myself I decided to take it on myself to learn modeling. I'm the kind of person who goes way out of his way to help fill a need for whatever organization I'm in.</p>
<hr>
<h2>Humble beginnings</h2>
<figure class="post__image post__image--left"><img loading="lazy"  src="https://patcave.dev/media/posts/7/blender_community_badge_orange.png" sizes="(max-width: 48em) 100vw, 768px" srcset="https://patcave.dev/media/posts/7/responsive/blender_community_badge_orange-xs.png 300w ,https://patcave.dev/media/posts/7/responsive/blender_community_badge_orange-sm.png 480w ,https://patcave.dev/media/posts/7/responsive/blender_community_badge_orange-md.png 768w ,https://patcave.dev/media/posts/7/responsive/blender_community_badge_orange-lg.png 1024w ,https://patcave.dev/media/posts/7/responsive/blender_community_badge_orange-xl.png 1360w ,https://patcave.dev/media/posts/7/responsive/blender_community_badge_orange-2xl.png 1600w"  alt="" width="214" height="279"></figure>
<p>First, I chose to use a program called <a href="https://www.blender.org/">Blender</a> to do my modeling, and that is because it's completely free and very well featured.</p>
<p>After following a few tutorials and getting to know the program, I volunteered to make a "<a href="https://collinsvisualmedia.com/hero-props-description">hero prop</a>" for the map <a href="https://tf2maps.net/downloads/kelly.9294/">ctf_kelly</a>. The map's author (MegapiemanPHD) published dev log videos and said he will at some point want a prop made for the center piece, so I just contacted him and offered to do it.</p>
<p>Basically the idea was it was a giant bronze globe as a centerpeice for a retro futuristic plaza. This would turn out to challenge my very new skills a lot, but as such I learned a great deal from this.</p>
<hr>
<h2>First Iteration</h2>
<p>Having no idea what I would actually make, (no concept art or anything), I just searched on ArtStation for "globes" and tried to emulate what I saw. The big common elements I saw were the grid like sphere with contients on the outside, so that was the rough plan.</p>
<p>This was the first attempt I made at making it:</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/7//gallery/ss.png" data-size="1919x1053"><img loading="lazy" src="https://patcave.dev/media/posts/7//gallery/ss-thumbnail.png" alt="" width="720" height="395"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/7//gallery/ss2.png" data-size="1919x1053"><img loading="lazy" src="https://patcave.dev/media/posts/7//gallery/ss2-thumbnail.png" alt="" width="720" height="395"></a></figure>
</div></div>
<p>The next part was getting it into the game engine, which was a lot harder than I expected. I used a Blender plugin called <a href="https://developer.valvesoftware.com/wiki/Blender_Source_Tools">Blender Source Tools</a> which allows you to export a mesh to the '.smd' format that Source engine requires. You also need to learn to write QC files and follow a process of 'compiling' the model data so the engine can actually use it. </p>
<p>After getting the mesh in engine and applying a very basic texture to it, this is what I had:</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/7//gallery/20200511125335_1.jpg" data-size="1920x1080"><img loading="lazy" src="https://patcave.dev/media/posts/7//gallery/20200511125335_1-thumbnail.jpg" alt="" width="720" height="405"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/7//gallery/20200511150244_1.jpg" data-size="1920x1080"><img loading="lazy" src="https://patcave.dev/media/posts/7//gallery/20200511150244_1-thumbnail.jpg" alt="" width="720" height="405"></a></figure>
</div></div>
<p>Yeah okay, so it looks not the best, a bit like chocolate, and is really <a href="https://en.wikipedia.org/wiki/Low_poly">low poly</a>, but hey I got something I made from scratch into the engine! This is a good but humble start.</p>
<p>So in order to make it not look so "chocolatey" I started learning about "shaders" in the engine. In this case, there were other inputs to the shader I wasn't using which would give it that <a href="https://en.wikipedia.org/wiki/Specular_reflection">specular sheen</a> I wanted. See the thing about 3D rendering is that you have different types of inputs. So mostly I was just using the color input, but to get it to actually reflect the world and have more of a gloss look to it, you needed to add a specular map. In my case I just uniformly applied it to the whole object.</p>
<p>This is the result with the specular map and cubemaps:</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="1">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/7//gallery/20200511165004_1.jpg" data-size="1920x1080"><img loading="lazy" src="https://patcave.dev/media/posts/7//gallery/20200511165004_1-thumbnail.jpg" alt="" width="720" height="405"></a></figure>
</div></div>
<p>Defiantly an improvement; i'd believe that was a bronze metallic material. </p>
<hr>
<h2>Redoing the Model</h2>
<p>Megapieman and myself both felt the current model I had made looked too "off balance" and also given this was in gameplay space, players will be able to jump on or around this, so smoothing out the base to be more gameplay friendly was a good idea. I would end up redo-ing the model for this prop probably 5 times in total by the end.</p>
<p>This is what I came up with, basically having remodeled it from scratch a few times:</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/7//gallery/Screenshot_37-2.png" data-size="1919x1052"><img loading="lazy" src="https://patcave.dev/media/posts/7//gallery/Screenshot_37-2-thumbnail.png" alt="" width="720" height="395"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/7//gallery/ss-2.png" data-size="1919x1053"><img loading="lazy" src="https://patcave.dev/media/posts/7//gallery/ss-2-thumbnail.png" alt="" width="720" height="395"></a></figure>
</div></div>
<hr>
<h2>Texturing </h2>
<p>I only really put a basic texture on this thing, which was mostly just the bronze we saw before, but I did try a few techniques I know about "stylized" art.</p>
<ul>
<li>Gentle gradient that leads the eye up to a focal point. The base of objects is generally more "dirty" too</li>
<li>Edge wear on the edges</li>
<li>Scratches or other imperfections</li>
</ul>
<p>For texturing I tried doing it only using Blender since I felt that using Gimp (another program i'm very comfortable with) was too hard since I couldn't get a good preview of the peice easily.</p>
<p>This is the only screenshot I took from the texturing process:</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="1">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/7//gallery/ss-3.png" data-size="1919x1053"><img loading="lazy" src="https://patcave.dev/media/posts/7//gallery/ss-3-thumbnail.png" alt="" width="720" height="395"></a></figure>
</div></div>
<hr>
<h2>Collision Meshes</h2>
<p>Collision meshes are very important for Source, especially since this is in gameplay space, meaning players will interact with it as if its a real object. </p>
<p>One caveat of the engine is that it can't and does not allow concave geometery for collision meshes. You basically have to create multiple discrete meshes that are strictly convex and stack them on each other. If you dont, then it just shrink wraps the collision mesh instead.</p>
<p>Here you can see my many attempts and frustations at getting this right:</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="4">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/7//gallery/globemesh2.jpg" data-size="891x652"><img loading="lazy" src="https://patcave.dev/media/posts/7//gallery/globemesh2-thumbnail.jpg" alt="" width="720" height="527"></a>
<figcaption>Shrink wrapped cuz I made them concave</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/7//gallery/globemesh.jpg" data-size="793x441"><img loading="lazy" src="https://patcave.dev/media/posts/7//gallery/globemesh-thumbnail.jpg" alt="" width="720" height="400"></a>
<figcaption>Collision mesh not aligned at all</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/7//gallery/Screenshot_17.png" data-size="923x700"><img loading="lazy" src="https://patcave.dev/media/posts/7//gallery/Screenshot_17-thumbnail.png" alt="" width="720" height="546"></a>
<figcaption>Collision mesh very not aligned at all</figcaption>
</figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/7//gallery/Screenshot_1.png" data-size="1919x1079"><img loading="lazy" src="https://patcave.dev/media/posts/7//gallery/Screenshot_1-thumbnail.png" alt="" width="720" height="405"></a>
<figcaption>Final "good" collision mesh</figcaption>
</figure>
</div></div>
<hr>
<h2>Animation</h2>
<p>Megapieman wanted the globe to spin. This seems easy, but is a lot more invovled than one might realize. You have to basically setup a bone rig in blender and have it animate based on key frames. Then you have to export your animation as a separate .smd and add more info to your QC file to tell it this has an animation and how to do it. This took me probably 2 weeks of troubleshooting and frustration to get working right.</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="2">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/7//gallery/Screenshot_7.png" data-size="1919x1051"><img loading="lazy" src="https://patcave.dev/media/posts/7//gallery/Screenshot_7-thumbnail.png" alt="" width="720" height="394"></a></figure>
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/7//gallery/Screenshot_14.png" data-size="1919x1048"><img loading="lazy" src="https://patcave.dev/media/posts/7//gallery/Screenshot_14-thumbnail.png" alt="" width="720" height="393"></a></figure>
</div></div>
<p>This is the result of all the animation work, just for a simple spin:</p>
<figure class="post__video"><video loading="lazy" width="300" height="150" controls="controls" data-mce-fragment="1">
<source src="https://cdn.discordapp.com/attachments/702085759477350460/711075301639061564/2020-05-15_23-31-55.mp4" type="video/mp4" /></video></figure>
<hr>
<h2>Final Result &amp; Learnings</h2>
<p>After having modeled and remodeled things on this several times I finally got it to what I call a finished state. I would say I learned quite a lot from doing this:</p>
<ul>
<li>How to get models into and out of TF2</li>
<li>Modeling using modifiers and procedural methods
<ul>
<li>Shrink wrap modifier on the continent</li>
<li>Array / mirror on the globe lattice</li>
</ul>
</li>
<li>UV unwrapping a complex object</li>
<li>Bone animation in Source</li>
<li>Basics of texturing</li>
<li>How to use the game shaders to apply texture to the model</li>
<li>Collision Meshes</li>
</ul>
<p>And probably dozens of other things I'm not even thinking about. As far as first projects go, I'd say this was a pretty good one, and now its used on the map!</p>
<div class="gallery-wrapper"><div class="gallery"  data-is-empty="false" data-columns="1">
<figure class="gallery__item"><a href="https://patcave.dev/media/posts/7//gallery/20200515140446_1-2.jpg" data-size="1680x1050"><img loading="lazy" src="https://patcave.dev/media/posts/7//gallery/20200515140446_1-2-thumbnail.jpg" alt="" width="720" height="450"></a></figure>
</div></div>
<p>Big shoutout to Megapieman for being such a good sport and allowing me (a complete noob) to work on such an important piece of his map! We are both very happy with the end result.</p>
            ]]>
        </content>
    </entry>
</feed>
