Jump to content

The Wildkins Guide to Contribution and Coding


Recommended Posts

Matt's guide to contribution and coding was a good first step for on-boarding would-be coders, mappers, and spriters, but alas it fell into the development black hole. Lacking a comprehensive guide to link new contributors to and finding myself repeating advice quite often, I decided to bite the bullet and write a New and Improved Guide to all things Aurorastation contribution.

This guide will assume you have no or minimal coding experience, as well as having never before used Git / Github. I will attempt to highlight / bold sections that are important for users of all skill levels, but if you come from a programming background you'll probably find some of this redundant. Sorry.

Part 0: Prerequisite Tools

Before we get started proper, we'll need to download a few things. We'll assume that you've installed BYOND already (you know, to play Aurora), so in order to get started we'll need to download just a few more things:

  • Git Source Control: Necessary in order to handle your local repository and connect to your forked remote repository. That won't make sense right now, but it basically is part of what gets your code from your computer to our server.
  • Git GUI Client (optional): Not strictly required, however many users prefer using a graphical client to interface with Git rather than using the command line. There are several options; Matt recommended GitKraken in his original post (and has since disavowed it for a number of reasons). Personally, I use the bare command line, however this guide will also cover everything in Fork and VSCode's in-built source control (see below).
  • Text Editor / IDE: You will need a text editing solution for coding in BYOND. Strictly speaking, BYOND's native Dream Maker application can handle editing .dm files appropriately, but it is frankly horribly insufficient and you will only be putting yourself through more trouble than it's worth doing so. Overwhelmingly recommended is Microsoft's Visual Studio Code, chiefly because of the extensions available for it (explained later); if you're not looking to set up a long term dev environment and/or not willing to download VSCode, then something like Notepad++ will work.
  • Mapping: StrongDMM. If you're going to do any mapping contributions, you absolutely should use this program. Again, Dream Maker can edit .dmm files, but you really, really shouldn't do it.
  • Spriting: I'm going to break my streak here and say Dream Maker does actually work for this; obviously if you have any sort of editing software capable of outputting .png files, that's preferable, but until the DMI editor extension for VSCode is properly finished, this is good enough for editing .dmis.

Once you've got VSCode installed (assuming you've made the correct decision to go ahead with installing it), you'll want to open it up and get a few extensions ready. You can find extensions by clicking on the left-hand bar, 'extensions' (the bottom-most icon), or hitting CTRL+SHIFT+X. From there, you can download extensions for almost anything you'd ever want. For this tutorial (and BYOND coding in general), however, you'll only need a few:

  • DreamMaker Language Client: This is perhaps the most crucial and by far the most quality of life extension you'll use. Allowing compilation, testing, debugging, and all sorts of look-up features, DMLC is half the reason VSC is so much better to work in than Dream Maker.
  • BYOND DM Language Support: Syntax highlighting for Dream Maker (.dm) code files.
  • Optional:
    • Trailing Spaces: Highlights whitespace that is erroneously created (tabs/spaces in empty new lines, spaces after the ends of code blocks, etc.)
    • GitLens: Not strictly necessary, but provides a bunch of extra features for tracking previous Git changes and the like; see at a glance when code was last changed, and by what.
    • GitHub Pull Requests: Very useful extension for checking out active PRs, mainly for testing and reviewing purposes. Not super useful for you as a contributor, but we can always use more people willing to test! Note that the extension can also create PRs, and while it has worked fine for me doing so, I've seen plenty of complaints about it not working, so YMMV.

With that, let's get on with the show!

Part 1: Let's Fork Ourselves

Our very first step here is going to be preparing our local environment. That's fancy talk for saying 'make a folder somewhere that'll store the code you work on'. For this example, I'll just be chucking mine in Documents. I'll name it 'aurorastation-test', but you can name it anything. Open that folder up and right-click anywhere; you should get a handy Git Bash Here prompt. If you don't, you likely didn't install Git-SCM correctly.

spacer.png

This will open up a command prompt! GUI users never fear, we'll just be doing the one thing here: setting up our new repository! But first, we've got to have a repository to grab, so let's head on over to GitHub. Specifically: https://github.com/Aurorastation/Aurora.3

This is the GitHub repository for Aurorastation. What we'll be doing here is creating something called a fork - essentially we'll split off our own copy of this repo that we can edit to our hearts content, and later we can pick and choose which parts to send off to the server's repository to be included as updates. Neat, huh? To do this, you'll need to sign up for a GitHub account if you haven't already - and once that's done, just head up to the top right of the Aurorastation repository page and click Fork.

spacer.png

Once forked, you'll have your own repository - the main server's is Aurorastation/Aurora.3, while yours will be <your username>/Aurora.3. GitHub should navigate you to your repository's main page, but if it doesn't, click your profile picture in the top right -> Your Repositories -> Aurora.3.

spacer.png

From here, there's just one step left - select the big green Code button near the top-center of the screen; you'll see a window giving you a few different options, but the one you want is simply to click the copy icon next to the URL, as seen below.

spacer.png

Now, returning to your command prompt - you left it open, right? - and paste it immediately following git clone. (Pasting into git bash is right-click, at least on my machine.)

Your full command should look like this before you hit ENTER: git clone https://github.com/YOURNAMEHERE/Aurora.3.git
If you did it right, you should see something like this:

spacer.png

We're ready to rumble! Now, with your tool of choice - be it command line, fork, smartgit, or VSCode - let's start to understand exactly what's going on here so we can get to work on our very first PR!

I've subdivided the next section into pieces depending on which tool you're using.

Git Bash / Git Command Line

Spoiler

If you plan on using the command line to do git stuff, welcome onboard! This is my own comfort zone and so it'll be pretty easy to guide you to where you need to go. With the same window you have open (if it's not open, just Git Bash Here in your Aurora.3 folder you cloned), go ahead and put in cd Aurora.3. You should see:

spacer.png

Notice the addition of (master) at the end of the line, there. That's indicating what branch we're currently operating on - in this case, the master branch. This is essentially what your 'clean copy' is going to be - master should always be an (ideally working) copy of the files currently running on the server. We don't want to mess with that, and we want to be able to update it regularly (because other people are contributing too!), so instead we'll make a copy of that copy - a snapshot of the state of master as it currently is locally, to work on.

We can do that simply with the git branch command. Simply type git branch <name> and your new branch will be created! At first it will seem as though nothing has happened, as you're given a blank line with no feedback, but using the git branch command without any arguments will show that, indeed, we have our new branch! To get to it we need to simply use git checkout <branch name>, and voila, we're on our new branch as denoted by the blue name.

spacer.png

In the future, you can create a new branch and checkout in one easy step, by issuing the command git checkout -b <branchname>!

Note that making branches this way creates a branch off your local master branch, rather than Aurorastation's. It's the workflow I've gotten into but it isn't necessarily the correct one - you can, instead, create a branch based off the upstream master branch, which will always be what the live server is currently running off of. (The alternative is routinely updating your local master branch.) To do so, you'll need to do:

git fetch upstream (this figures out everything that's changed, but doesn't apply anything)

git checkout -b <branchname> upstream/master

And then, when you're ready to push it to origin to write up your PR:

git push -u origin <branchname>

VSCode

Spoiler

If you've already got VSCode open, now's the time to open up your repository - select File > Open Folder > navigate to your Aurora.3 folder that you cloned earlier. If it's not open, go ahead and navigate to that folder and right-click > Open with Code. You should be greeted with this screen:

spacer.png

Select the 'source control' button from the left, third from the top. You'll be greeted with a window with several tabs; everything other than Source Control, Remotes, and Branches can be minimized for now. Under branches, you'll see that we only have one branch right now - master. The master branch is, essentially, the default area that is more or less 'the codebase'. Everything on Aurorastation's master branch (that is to say, the upstream's master branch) is what is currently running on the server. As such, we'll be making our own branches off of that to code in. Think of branches as snapshots much the same way that our fork was, however these are more one-time use buckets that we can edit code in and discard later.

Under Remotes, you should see two entries - origin, linked to <Your Username>/Aurora.3, and upstream, linked to Aurorastation/Aurora.3. Essentially your local repository is a copy of your forked repository, aka origin; that, in turn, is a copy of the 'upstream' repository, which is Aurora proper. What we'll want to do is create our new branch - our snapshot of the master branch as-is to edit - off the upstream's master. The upstream master is always what the server is currently running (at the time that we make the branch, at least), so we know we're using current code!

For now, go ahead and expand upstream, and right-click master > Create Branch. You should be prompted to enter a branch name; anything works here, but I'll go with first-pr as an example. It should then ask if you want to simply create the branch, or create and switch to it - we generally want the latter, since we're going to work in it afterwards!

If everything worked, you should see the blue bar at the bottom change; master should now read first-pr; you'll also see first-pr appear under Branches, and Source Control's commit message editor will read CTRL+ENTER to commit on 'first-pr'. Congratulations, you're ready to begin coding!

 

Fork

Spoiler

Upon opening Fork and directing it to your created repository (.../Aurora.3), you should be greeted with this:

spacer.png

This is a whole bunch of nonsense, but don't worry about it! Essentially what you're looking at in the main panel there is a chronological list of commits - that is to say, bundles of code that have been uploaded to the main repository. It's essentially just a changelog.

What we're concerned with right now is on the left-hand side; specifically, Branches and Remotes. Remotes are the repositories on GitHub - they're 'remote' repositories in the sense that they aren't on your computer. Your origin remote is the one you forked from Aurora, which is linked to your GitHub account. It will hold all of the branches you create for pull requests, and any data you decide to push up to it from your local repository (i.e., your current coding environment on your computer). Upstream, on the other hand, refers to Aurorastation's repository - i.e. the one the server runs off of. As such, the master branch on upstream will be the code that's currently on the server - and so, the code that we want to work off of!

We're going to create a branch off of upstream's master branch. A branch is, somewhat like a fork, a 'snapshot' of whatever it's taken from at a particular moment; unlike forks, however, branches are meant to be used (at least in our use case) as a singular, disposable area to write up some code and then send it away. Once we're done with whatever particular feature we're intending to contribute, we won't need the branch anymore!

Go ahead and expand upstream so that it lists all of its branches. There's a few, but we're only interested in master. Right-click master, and you should get an option to create a new branch. Go ahead and name it whatever you'd like, and tick the box Check-out after create - that means that we'll swap to that branch off of our local master.

spacer.png

You should now see first-pr (or whatever you named it) appear under Branches, and you'll also notice that it appeared beside upstream/master in the changelog! That means we're good to go.

Once you've created your branch and checked it out, you're ready to begin coding. But what exactly will we code? How do you make a pull request? What even is BYOND coding, anyway?

Stay tuned for Part 2: Code and Bug Torture, coming soon to a Guides & Tutorials section near you!

Link to comment

Part 2: Code and Bug Torture

Now that our branch is set up and we have an environment in which to code, let's get down to business.

...how exactly do you code in BYOND? What language does it even use? Well, the short answer is "painfully", and DM, or dream maker.
As Matt put it in his guide, I can't teach you how to become a BYOND savant (if such a thing exists), the best method is simply to decide to do [something], and then figure out how something similar already in the codebase does that thing. It's also easy for one of us developers or your fellow contributors to help out if you have an idea for what you want to do.

Highly recommended regardless of your skill level that you keep the BYOND DM Reference handy. There's a lot of specific information about how specific things work that can come in handy if you're ever stuck: https://www.byond.com/docs/ref/

Not to get too deep into CS101 here, but whenever you're engaging in a coding problem, you should always tackle the "algorithm" first - put simply, what problem you're trying to solve and how, exactly, you're going to solve it. You don't have to worry about which functions or variables - you don't even know what those will be yet - but simply, what steps from A to Z you're going to have to do to fix a problem, or implement something new. For today's guide we're going to go with the former, fixing a "problem" in the code. Many such problems can be found on the main repository by going to the issues tab, linked here: https://github.com/Aurorastation/Aurora.3/issues

We'll talk more about picking issues to solve (if you're willing to help us bugfix!) at the end of this guide, but for now, I'll take you through the step-by-step guide of coding a solution to this issue: https://github.com/Aurorastation/Aurora.3/issues/13629

"Flashlight in wristbound PDAs doesn't work." Specifically, the flashlight is reported to not work, and can't be reattached when removed with a screwdriver. This is a good report of a simple (at least in scope) bug, that clearly defines how to replicate the issue. So, with our branch selected and VSCode open, let's go ahead and try and figure out what's going on!

A Quick Primer

I said I can't teach you how to be a BYOND programmer in one post, but I can at least get you up to speed on the very basics. If you've got a technical background, I recommend digging into this article from the TGstation wiki: https://tgstation13.org/wiki/SS13_for_experienced_programmers

BYOND is a very weird language. It's not quite like any modern programming syntax you're probably used to; personally I'd put it close to a weird hybrid between JavaScript and Python, but it has flavorings of essentially everything tangential to the object-oriented space. In a word, it's a mess. I will now try to explain object-oriented programming in this mess of a language in a paragraph or two, so bear with me.

BYOND is pretty big on the idea of inheritance - in BYOND, you have a given object A that does a certain thing, and a child object of A named B that does a slightly different thing - but because it's a child of A, it can also do everything A can do, unless we say otherwise. Likewise, if unrelated object C comes along and asks for all the As, well, it'll get B too - because B is a child of A, after all. This makes a little more sense when you apply it to how BYOND declares... 'stuff':

/datum/A
    var/name = "Object A"
/datum/A/B
    name = "Object B"
    
/datum/A/proc/print_name()
    to_world(name)

To most of you I've just typed gibberish, but I'll try to explain this as succinctly as I can: this is BYOND code that will define two objects - specifically, datums - named A and B. More specifically, it will create two types of objects; as you can see, one of them is A, while the other is A/B - that means that it's the type /datum/A/B, which happens to be a subtype of /datum/A. What does that mean, exactly? Well, the implications become a little more clear once you understand what's underneath them.

Indented underneath A and B are definitions for a variable - "name", which is set to Object A and Object B, respectively. A variable is essentially a place we can store [something] to access or modify it later; it can be a string of letters, numbers, a list of objects, every tile in the game - anything we need. Note, though, how it's defined differently between A and A/B, right?

Because B is a subtype of A, it inherits all of A's definitions - that goes for variables as well as procedures (procs, as you can see one below - these are BYOND's equivalent of functions). If you were to put this into DM, but have both objects defining var/name, BYOND would turn around and complain about already-defined variables; because you already said you wanted all objects of type A to have a name variable, and B is an A because we said it was!

Assuming that wasn't either completely confusing or horribly condescending, we can now look at the proc beneath it. Procs are, like I said, BYOND's equivalent of most languages' "functions" - basically these are places where code is executed, where rocks are tricked into thinking and the magic happens. Underneath object lines like /datum/A, all we can do is define static data - but in procs, we can do a whole lot more. This proc is as simple as they come - all it does is call to_world, another proc (well technically it's a macro BUT that's beyond the scope of this) which sends a message to everything connected to the server about whatever we tell it to - in this case, the name of the datum. Because we defined this as a proc under /datum/A, it can be used by B as well - because, again, it's a subtype of A! In much the same way as variables, though, we can't redefine this proc - if we add another line starting /datum/A/B/proc/print_name(), BYOND will (rightly) complain. But we can, like with the name variable, override the logic! /datum/A/B/print_name() is totally fine, and in fact is how we'll be able to have subtype B objects do something different with the same proc!

That's all esoteric, though. Let's get a real-world example from what we're about to tackle - wristbound PDAs. The object line for wristbound PDAs is:
/obj/item/modular_computer/handheld/wristbound
So, that means wristbound computers are a subtype of /obj/item/modular_computer/handheld, which are a subtype of /obj/item/modular_computer...you get the gist. There's a lot of inherited behavior all the way down, from general stuff like picking up items (obj / item level) to very specific stuff (like slapping the wristbound computer on your wrist!). If you ever run into something that has a certain behavior, but you don't see any code for it tied to that object...odds are it's being inherited from one of its parent types!

NOTE: TO FOLLOW ALONG WITH THIS EXAMPLE, YOU'LL NEED TO SET UP A NEW BRANCH. I know, I lied in the last part, I cheated all of you. My Hugh Briss is astounding. You'll need to set up a new remote in your Git client - in VSCode you'll just hit the + button when you hover over Remotes under Source Control; in Fork you right-click Remotes on the left and hit Add New Remote, and in Git Bash the command is git remote add https://github.com/JohnWildkins/Aurora.3.git. The URL there is what you'll use as the remote URL, and the name can be anything - it's my origin remote, but for your purposes all you're interested in is the broken-light branch on that remote (there's a lot of branches, I know, I'm sorry.) Create a new branch off of the broken-light remote just as you did with upstream/master. This'll be off the codebase as I wrote this guide, so obviously you shouldn't PR this - by the time you're reading this I probably have already PR'd the fix to explain Pull Requests in the next part, but this way you can follow along with the changes I make as I make them.

Getting Around

There's a few different ways to find what we're looking for. If you're a damaged individual like me, you'll already know where you need to go: wristbound hardware is defined in preset_wristbound.dm under code/modules/modular_computers/computers/subtypes; but rarely will you know where to find something just off prior experience! We have a few options for finding what we need, though.

The simplest is the search function. It's deceptively powerful and one of the best parts of VSCode: simply click the magnifying glass on the left, or hit CTRL+SHIFT+F to bring up the Search pane. There's an arrow to expand for find and replace as well as a ... dropdown that allows you to filter for specific folders, but those are unnecessary right now. We're just going to look for "wristbound" (without the quotes), to try and figure out where wristbound computers are in the code.

spacer.png

Well, that could certainly be better. Looking through 312 results isn't exactly the most fun, but thankfully it's only 23 files. You'll notice each result is located under a collapsible header, which is the name of the file it's located in. File names aren't always the best, but they do also list out their directory beside them; clicking the collapse all button (top right of the Search window) will give us a nice, easy list of files that match what we're looking for.

S3fVpPF.png

The first file is the aurorastation.dme, also known as the environment file. This file basically tells BYOND all the other files that we want to load when we start the game. You'll need to edit this if you ever add a new file to the codebase; thankfully, the extensions we got you to download for VSCode handle this automatically, you just need to save the changes. Looking through the rest of this though, we can see that most of them refer to jobs or outfits - that makes sense, given that any loadout would need to define what wristbound computer it uses, if any. We know though that since this is dealing with the flashlight component in the wristbound PDAs, and that it's likely something to do with the code around the computer object itself, we should probably start by taking a look at the modular_computers files - that being dev_wristbound.dm and preset_wristbound.dm. Let's go ahead and open both up now.

You'll notice when clicking on the file names that they simply expand; you'll need to click on one of the entries for the file to show up. You'll also notice that clicking on another entry - to try and open both - will simply close the first window and open the second! What's up with that? Well, take a look at the tab of the file along the top - you see how it's italicized?

5ihiNTf.png

This means that it's "temporarily" open; any new file you open will replace this tab with it. To keep it open, you can do a number of things; I tend to simply save (CTRL+S); alternatively you can hit CTRL+K followed by ENTER. Instead of either of those, you can also open the file by hitting CTRL+P and typing the name you're looking for; this option is great for when you know what file you're looking for but don't really want to search through the whole file tree to get it. So now, you should have a window looking something like this; go ahead and close the search window for now, we'll open it back up when we need it.

12rEfWs.png

Since we know we're dealing with a flashlight issue, the easiest thing here is going to be to look for flashlight; instead of using VSC's main search (which looks through all the files in our directory), we'll just hit CTRL+F to bring up the Find menu, and look for flashlight. We'll be doing this in dev_wristbound.dm, by the way - as you might notice by looking at the code, preset_wristbound.dm is simply the definitions of the various preset loadouts for the wristbound computer. Odds are what we're dealing with here is a code issue, since the bug reported that the flashlight didn't work properly, not that it wasn't there at all.

t68xB17.png

Huh. Well, that's unfortunate. It seems like there isn't any code for flashlights here after all; it must be somewhere else. Odds are, if an object doesn't have the behavior, then one of the parent objects probably does! (See the primer!) Either that, or it's in another file -- but as we saw, the mentions for wristbounds are pretty limited in terms of actual procs.

The easiest way to "traverse" up the chain of objects is to right-click on the parent subtype - in this case, handheld - and click 'Go to Definition'.

uV4CjCT.png

This should bring up a new file, dev_handheld.dm. But this one's even shorter than the last! There's no mention of any flashlights anywhere! Well, let's go ahead and check out modular_computer, then - odds are that's where all the general modular computer stuff will be handled. Even though this is reported as a wristbound specific issue, the problem is more likely a general modular computer issue that is only appearing because of something wristbounds do differently. At least, that's my theory right now! If we Go to Definition again on modular_computer, we get...

n4l9fpb.png

Well, this looks a bit better - but only one result for flashlight, and it's defining a variable! Thankfully, though, this gives us a nice crossroads - we can either check the other files here for a proc that handles enabling and disabling the flashlight, or we can use the variable definition to hop over to the flashlight component itself. First, though, let's put the code down for a second and boot up the game to see if we can recreate what's going on here - but don't touch Dream Daemon! We can do this all from the comfort of VSCode.

Config and Debugging

First things first, make sure BYOND is open and you're signed in. Then, in vscode, open the file browser (CTRL+SHIFT+E, or the file icon at the top left), and scroll until you find the config folder. Inside you'll see some subfolders, one of which is example - open that, click on access levels.txt, then hold SHIFT and click on whitelist_jobconfig.json. This should select the whole folder - now CTRL+C (copy) and then right-click the config folder above - and hit Paste. You've now populated your config folder properly, which your debug server will read from! Let's set a couple things to make our lives easier.

First, open up admins.txt. This is pretty simple, just a list of who gets what permissions. Since we're testing things locally, we want everything - that's Head Admin/Dev. Go ahead and put your ckey in, followed by a dash, and then Head Admin/Dev (as the example puts it):

zjD6SNO.png

Go ahead and save that, and then close it and open config.txt. There's a lot of options here, but the only one that matters for you on a regular basis will be FORCE_MAP <mapname>. Go ahead and CTRL+F for FORCE_MAP to find it; you'll see that it currently says FORCE_MAP runtime, with a comment explaining your options. This allows you to force the server to run a specific map; by default it runs Runtime, which is a small station ideal for testing things that aren't map-related, or don't require the full functionality of the Horizon or Aurora. This is what you'll use probably 90% of the time, but just in case you need to load up Horizon specifically, you'd replace runtime with sccv_horizon as the comment explains. Go ahead and leave it as-is and close that too.

Now, how do we run BYOND from VSCode? Well, simple! Make sure you have a .dm file open, and hit the F5 key! You should see two things - one, a command prompt at the bottom that looks like this:

IuzhVP3.png

And a toolbar that pops up near the top-right, that looks like this:

e8buIfG.png

Let the command prompt do its thing, and don't touch the toolbar for now. If everything went right, you should eventually see a BYOND window pop up on your taskbar - open it up and bam, you're playing a SS13 server running through VSCode!

Okay, so thats convenient, but what does it let us do, exactly? Well, first, we can load in with a wristbound PDA and try to test out the flashlight to see how it works (or doesn't). They specified a wristbound PDA so we'll switch our PDA type to Wristbound, but just in case we'll spawn ourselves with a cheap generic wristbound as well.

Well, clicking the flashlight button on and off does appear to do precisely nothing. Looking at the configuration menu for the PDA, we see that the hardware is turning on and off with the button, so it's likely not a UI issue nor a generic computer issue - so let's go back to the code and look at the flashlight code. To do that, we'll open variables.dm and scroll down to flashlight, right-clicking on the flashlight that isn't the variable name (i.e. not the light blue one) and clicking Go To Definition. You should be taken to code/modules/modular_computers/hardware/misc.dm, which shows us the flashlight object in all its glory!

It's very simple - other than a few variables, all it has is a proc for enabling and disabling the light. Obviously something isn't going right here, though, or it wouldn't work. So let's look at the enable code specifically.

glSHoqd.png

So we can tell that this is an inherited proc, because it doesn't use the proc keyword in its type line - this is further supported by the completely esoteric . = ..() line that starts off the proc! Now's a good a time as any to explain what that means.

Procs, like functions in other languages, have two main purposes: one, to perform whatever task they're assigned, whether it's manipulating variables or calling other procs/functions, and two, to return a value. Every proc in BYOND will return a value - if you don't tell it to with the return keyword, it will return null. (Essentially, it returns "nothing", but a nothing that we can quantify as being...nothing.) This matters in cases where we want to have a some bit of code return a different value depending on how the code executed - like a function that handles power draw, which might return how much charge we used from our battery in a single operation.

That default returned value can be referenced by using the . character. Whenever you see . it refers to what the function will currently return - without any return keyword specifying something else, all procs return . - which is usually null.

So, we know what . is. And we can see that the equals sign means that we're assigning the default returned value to something...that something being ..().

Remember how we said that this is an inherited proc? That means there's a parent proc to this one, and ..() calls that parent - running it and handling whatever it says to do before going on with the rest of our code. This is pretty common in inherited procs, since usually we want to extend the behavior of a child proc, not completely overwrite it. You can even see the parent proc for yourself by right-clicking the ..() and clicking Go to Definition. It'll bring you to /obj/item/computer_hardware/proc/enable(). But for now, let's focus on this code.

. = ..() then simply means that we're going to call the parent proc, and whatever that proc returns, we're going to assign to our return value. It's the same thing as saying return ..() at the end of the proc, but we're running the parent first rather than later, while still returning whatever value the parent assigned.

Next, we have an if statement. If statements simply run the code underneath them (indented) so long as they are "true" - in this case, if(parent_computer) simply asks "is the parent_computer variable not null (or 0)?" We can be pretty certain that parent_computer will be true (or else a lot more would be broken), so let's move on.

Underneath that if statement, we see two similar lines - parent_computer.light_range = range and the same for power. parent_computer.light_range means that we're accessing the parent_computer object, and assigning its light_range variable to the range variable of this object (the flashlight). That's why we had the if-statement before, by the way - if we didn't, and we tried to access the variable light_range of parent_computer while parent_computer was null, we'd get an error!

Okay, so we're setting the computer light's range and power to the range and power of the flashlight, and then we have tweak_light(parent_computer). That's another proc call! The syntax highlighting isn't always perfect - yellow text always means a proc call, but if you call a proc tied to an object (like C.set_light(...) below), it won't be yellow. The giveaway is the parentheses - those show the parameters of the proc, and are a clear sign that we're dealing with procs rather than variables! Thankfully, this proc is at the bottom of the file, so we don't need to go far - and the code is simple too.

In the parenthesis we see var/obj/item/modular_computer/C. That's the only parameter that this proc needs, and it's defined specifically as a modular_computer named C. Whenever we call tweak_light, as you see, we call it with parent_computer inside the parentheses - because it's the computer! Fittingly, then, the tweak_light proc has an if statement checking for... !istype(C). If it is whatever !istype(C) means, we return nothing!

istype(C) will check that whatever is currently assigned to C matches the type we defined for C previously - that is to say, whatever our call passed along to be assigned to C is, in fact, a obj/item/modular_computer. The ! added to the front is simply a negation - istype(C) returns 1 (or TRUE) if C is a computer, so !istype(C) then, returns FALSE if C is a computer - so it returns TRUE if it isn't a computer! Thus, this is simply a check for whether or not C is what we expect it to be.

One other line in the function, then - a call to set_light on C, using the C's variables light_range, light_power, and our flashlight's flashlight_color. Well, if we Go to Definition on set_light, we'll see a pretty complex proc relating to the lighting system, defined all the way up at the /atom/ level. Odds are this isn't what's broken...but at the same time, it might explain why setting the light isn't working! If we look at the code, we can see that it starts off by setting the return value to FALSE.

0IQ5KcM.png

And then, there's a chain of if statements. Each one of them assigns something to the object's light vars, based on the parameters passed to set_light. If any of those statements are true, it sets . to TRUE. And then, at the bottom, we notice an if statement - calling update_light(), but only if . is TRUE. So, based on that, we can assume that . isn't being set to TRUE, or else the light would, y'know, update!

Note what the if-statements are looking for. Ignoring the first one, which only fires if the range is between 0 and 1.4 (our range is set to 2 by the flashlight), we can see that the first if statement checks if l_power is not null, and l_power is not equal to light_power. Well, light_power, since it isn't being tied to an object (like C.light_power was earlier), means it's the light_power variable of whatever object this proc is attached to - we know from our calling statement earlier that it's being run off of C, so that's C's light power var! And l_power is one of the parameters of the proc - the second one, as you might notice from the top - and if we look at our call statement from before, we'll see that we do in fact have l_power passed along in our proc call...

kopdHkX.png

...as C.light_power. Well, I do believe that C's light_power should be equal to C's light_power. This is why the light isn't updating - we're already setting the light before we set the light, by directly editing the light vars rather than using the proc! What idiot coded this?!

m6LzDKe.png

Right.

Anyway, the simple solution here is to get rid of the tweak_light proc entirely. All it does is call set_light on the computer anyway, and we can do that in enable/disable. Don't delete it all just yet, though - we want to more or less copy the set_light proc first!

Go ahead and, under enable and the if statement, rewrite the set_light call there - but instead of using the computer's light range or power, we'll use the flashlight's, since we're setting the computer's lights with this and we want it to change only when we call this set_light proc, not before. Remember that up here, the computer is called parent_computer, not C.

sPdouuH.png

Then, once you've written the set_light call, you can remove the line below calling tweak_light, and delete the /obj/item/computer_hardware/flashlight/proc/tweak_light(...) line and everything below it at the bottom. Now we'll do the same for disable() below - but don't mindlessly copy-paste! Notice that we're assigning it to different values here, specifically initial(parent_computer.light_range), and the same for light_power. initial() is an in-built proc that will essentially revert a variable to its initial definition. This is useful in our case, since if you right-click > Go to Definition on light_range, you'll see that both it and light_power are defaulted to 0. We could, in this case, simply set it to 0, but it's better to use initial() since someone might change the default range and power of the computers later, and then our flashlight will set the variables to zero rather than their initial values.

This proc call will look a bit longer and messier, since we're dealing with much longer names. You'll want to call set_light on parent_computer with the range initial(parent_computer.light_range), power set to initial(parent_computer.light_power); the color is still flashlight_color, since we're not changing the color of the light, we're just turning it off. When all's said and done, you should see:

2fWmvKF.png

Let's go ahead and hit F5 to run, then, and see if our fix worked! But, wait, why isn't Dream Daemon appearing? Well, you probably got a big fat error message like this: ZeBeNKC.png

This means that in the process of compiling, the DM compiler hit a snag somewhere - we screwed something up! If you click 'show errors', you can see the problem:

DPMHRcl.png

Click on the undefined proc line, and VSCode will jump to the problem and highlight it - we forgot to remove a call to the tweak_light proc after we deleted it! In this code we're tweaking the birghtness - the power - and setting it, so we'll delete both lines and replace them as before, using parent_computer.set_light() instead of changing light_power directly.

Hvkbtc0.png

So just like that, we're good, right?

Well...

Think for a second. This will always set the light to be 2 tiles in range, at whatever power we tell it to...even if it's not on. After all, you can change the flashlight's brightness even if it's off, right? So, let's only call the actual set_light proc if the light is on. How do we do that? Well, we can take a guess - let's look up at the variables we have available to us underneath the flashlight!

Enabled is a pretty clear stand-out - if we remember from Go-to-Def'ing enable() earlier, we know that gets set to TRUE when it's on. So, let's just put an if statement for enabled before that proc call! And while we're at it, we can make sure parent_computer is defined, too. (Note that, in this case, you could check for enabled == TRUE, but checking for simply if(enabled) is essentially identical, and quite a bit cleaner. The difference is the latter will be met if enabled is set to TRUE (which in BYOND is really just 1), or if it's set to "Hello!", or a list, or anything. In this case, we only ever set enabled to TRUE/FALSE (1/0), so if(enabled) is totally fine.

ScPhp8S.png

F5 again, and let's run it! (n.b.: you can use CTRL+SHIFT+B to compile your changes without running a server, in case you just want to test for any compilation errors without booting up a server to test.) If you've followed along well, then it should be working just fine! Brightness changes work and save even if the light is off, but changing the brightness while off doesn't magically turn the light on!

...Remember, though, that wasn't the only issue! It was also reported that screwdrivering the flashlight component out didn't allow you to put it back in. Well, I'll give you this one for free: going back to variables.dm, click on the variables.dm > ... text near the top (not the name of the file!) and you should see a dropdown of files in the same folder. Select 'hardware.dm', and you'll see one loooong proc that handles installing every component for modular computers! Notice that flashlights are not one of them.

ruKyH51.png

Finding this would be a bit trickier than normal, but I can walk you through how I did it - all interaction with objects is usually handled by attackby() or attack_hand(), depending on whether or not you're clicking on an object with something in hand, or an empty hand (in that order). Since we're 'attacking' with a hardware component, it's the former - so we can either search for modular_computer's attackby proc, or we can just go to interaction.dm (as listed above) and find the attackby proc there.

We can see that about half way down, if we attack with hardware it sends us to try_install_component - one right-click > Go to Definition later, and we're at the proc!

3TlSU1n.png

The solution here is essentially just copy-pasting one of the else-ifs and adding it to the end of the chain, before the if(found) at the end. Since the paicard has special handling, we'll copy one of the others that's similar - the tesla_link will do fine. Replace tesla_link with flashlight in the type check, if statement, to_chat lines and the variable assignment...and that's it! Go ahead and test it to make sure it works - use a screwdriver to remove the flashlight, and then try to add it back in!

3NSnBKE.png

If you've followed along correctly, you should see two changed files under Source Control, now.

dj4aBzZ.png

You can click on these to see your changes - on the left is the old file, on the right is the new (if you're using split view - inline view will instead show both at once, with lines removed in red and lines added in green. You can swap between them by clicking the ... in the top right.)

That's everything, then! You should now have the vaguest of foggy understandings how variables and procs work in BYOND, and how different objects work together to produce the hilarious mess we call a roleplay server. Next time? We discuss everything you need to do to get this into a submitted PR! (Don't worry, that's the easy part.)

Edited by Wildkins
Link to comment

Part 3: Pull Requests And You

In the last part, we identified an issue on GitHub, investigated it in the codebase, made some changes, and saved them. Now our VSCode's Source Control shows this:

aPk4tae.png

If you click it, you'll see your changes (as we described just above). So, how do we get these changes onto the server? With a pull request!

A pull request is initiated between one GitHub repository and another; it's a request initiated by a user for the repository in question (Aurorastation, in this case) to pull whatever changes we indicate. In short, we're asking the developers to pretty-please take our changes and put them on upstream's master branch.

How do we do that? Well, if you go to your forked Aurora repository on GitHub, you'll notice that we have no changes compared to the upstream branch! We can't make a PR if we have no changes - and if you look closer, you'll see that the changes we made locally aren't on our repository. To get them there, we'll need to push those changes up to our remote repository.

We can only push commits, though. Commits are essentially bundles of changes wrapped together and applied over every previous change - every PR accepted by the developers is turned into a singular commit of all changes that is applied against the codebase. It's essentially one long list of things we tell Git to change in the codebase. To make a new commit, we first have to stage our changes for committing. Doing this is pretty different depending on your choice of Git interface:

Git Bash

Spoiler

Files are staged to commit in Git Bash by using the git add <file> command. Doing this for each file can be tedious, however, since you'd need to type out (or at least tab-complete) every file directory, and as you can see ours are quite long paths. So, instead, we can use shorthand that essentially says "add everything we've changed to staging": git add .

Any changes we've made will now be staged for commit!

cBcCGMP.png

How do we know that they're staged, though? Well, we can either check VSCode, where you can see the files have moved under a new Staged Changes banner, or...

seEW6k9.png

...we can use the git status command.

hnlCpv8.png

Green means the files are staged and ready to be committed! Once we're ready, then we can make the commit properly. Commits are made with the git commit command; but every commit needs a message explaining what it is. If we just run git commit, it'll open up whatever text editor is default (usually Notepad) and ask you to save a commit message. This is a clunky way of doing it, since messages are usually short, so instead we'll use the -m modifier:

git commit -m <message> like so:

jLUvUXH.png

If you did it right, you'll see a line titling the branch, commit hash (basically a unique ID for the commit), and the commit message, followed by the number of files and lines changed. If you run git status now, you should see:

r1fbt7A.png

Finally, if you ever mess up and commit something before you wanted to, you can reset the state of your repository. Be very careful doing this, as improper use can result in losing data - and thus lots of work. For now, we're just going to run git reset HEAD~1, since we want to undo that commit we just made since we're not quite ready to commit yet. HEAD~1 is essentially a pointer for git to go to the previous "state" of the repository; HEAD points to the most recent commit, thus HEAD~1 is the commit prior, and reset will thankfully dump out all of the changes we made to get to that point, unstaged and ready for use once more:

mXWOipC.png

VSC

Spoiler

In VSCode, staging files for commit is as easy as mousing over them in the Source Control tab, and clicking the '+' icon. Clicking the '+' icon over the Changes tab will instead stage all changes, which is (usually) what you want to do.

wIEIE8M.png

OJbYy5n.png

To commit, then, you need to simply add a message for the commit (something that describes what changes you made succinctly - if your PR is only a single commit, GitHub will default to titling your PR the name of your commit, so it's easier if you write something good once!)

OU2BSHm.png

Once you have your message, just hit CTRL+ENTER to commit your changes. If the source control window goes blank, then boom, you've made your changes! At this point it will probably be replaced with a Publish Branch button; don't click that just yet.

If you ever commit something accidentally and need to revert it, simply go to the Commits tab, right-click the commit prior to the one you wish to revert and select Reset Current Branch to Commit. Be careful when resetting branches, as doing so incorrectly could cause loss of data (and thus work); doing this, however, should be perfectly safe.

agIta9r.png

If it worked properly, you should see your changes appear once more above!

Fz1nWGw.png

Fork

Spoiler

To stage in Fork, all you need to do is select Local Changes on the left-hand panel:

TGU5JyP.png

Then, in the center column, you'll notice both an unstaged and a staged box. By default, it should highlight one of the two .dm files you changed.

488iEyw.png

You can either click Stage up above for each one individually, or click the top level folder (Code, in this case) and click Stage to stage all changes. If you did it right, you should see this:

zAsW6XH.png

Then, on the right side - beneath your change preview, you should see a grayed-out Commit 2 Files button, as well as a line for Commit subject and description. Every commit needs a message (listed as a subject in Fork); PRs you make with a single commit will use the commit message as a PR title, so coming up with something good here will save you a little work later! For now, we'll go with:

0vHbqJ6.png

Once you're done with the message, go ahead and click Commit! If you then go to All Commits (on the left side, where you found Local Changes), you can see the branch we've made visually compared to our master!

9vSelDb.png

But let's say we messed up and don't want this commit just yet. Scroll down to the last commit in white (alternatively, the last PR on the left where you see an orange dot - that should be the Automatic Build commit. Right click it and select Reset 'first-pr' to here...

T7WRoHk.png

Resetting PRs can be dangerous, especially if you select Hard Reset (which will delete all changes you've made), but we'll make sure to keep the reset Mixed - this will get rid of any evidence of the commit, but keep our changes in the unstaged bucket for us to do what we'd like with them.

5LldE7V.png

If you did things right, you should see your local changes re-appear, unstaged and ready to commit once again!

So, we know how to stage our changes and commit them - but before we do that, we need to address the log in the room. The Changelog. Every PR needs a changelog, except a very scant few that only touch backend systems that players will never see the results of - we're talking, like, changing unit tests or updating VueUI dependencies here. Odds are, every PR you make will need a changelog. To make one, open back up VSCode and navigate to the html/changelogs folder, or use CTRL+P and search for example.yml.

This is the example changelog, and this'll be what you copy to make every changelog for every PR you make from now until the heat-death of the universe. Do not edit this file, however - open up the file explorer on the left and right-click example.yml -> Copy, and then right-click changelogs (the folder) and hit Paste. If you did things right, you should see:

zYGKAS9.png

(Notice, by the way, that your .yml file is highlighted green, while your other changes are yellow - a file that is updated will be colored yellow, while a new file will be green. Deleted files are marked in red.)

Rename this changelog now, by right-clicking it -> Rename, or hitting the F2 key with it selected. The common convention for changelog files is <ckey>-<branch>.yml; for example, I'll be naming this johnwildkins-flashlightfix.yml. It's not super important what the file is named, but keeping a vague consistency helps with things. The identifier after your ckey can be anything - note that it's not actually my branch name, but a very brief summary of what we're doing. We're fixing flashlights.

gfdDQza.png

Open it up and follow the instructions inside - for this, we're simply doing a bugfix, so one line with the bugfix tag is all that's necessary. It'll differ for all future PRs, obviously - just go with what seems fitting. You also don't need to leave any of the comments in the changelog, so if you feel comfortable getting rid of them without breaking the formatting, go ahead.

w99ZnJn.png

Once your file is renamed and edited properly and appearing in your unstaged changes, go ahead and stage it as well as the rest of your PR's changes, type the commit message again, and commit!

Now, there's only two steps left. Remember that once we have a commit, now we need to get that commit worth of changes to our remote repository - we need to push. To push our changes, though, we need to have a branch on the remote to send our changes to! (Remember, we only made our branch locally - and changes aren't mirrored across repositories unless we specifically tell them to be!)

Thankfully, we can kill two birds with one stone, depending entirely again on your method of Git client:

Git Bash will involve the use of the git push command, with the -u flag which will set the 'upstream' remote for our branch (not to be confused with upstream, which is the Aurorastation repository). git push -u origin first-pr will push our commit to the origin remote (our GitHub repository), creating the branch first-pr if it doesn't already exist on that repository, and linking it as the upstream remote branch for our local branch. Long story short, that command will create first-pr on the remote and link them together so that in the future, all you need to do to send further commits is git push with no added flags or parameters.

VSCode users can rejoice in the simple usage of the Publish Branch button, found in the Source Control area once you've sent your commit - or alternatively, under Commits you can find a Publish first-pr to GitHub button. Either one will push the commit(s) you currently have on your local repository to a new first-pr branch on the GitHub remote, and link the two branches together so that any future changes can be easily pushed.

ZHdc0s5.png

Fork users simply need to click Push across the top bar - a window will open asking which branch, remote, and remote branch you wish to push to - the defaults (first-pr, origin, and new (origin/first-pr)) are all correct for what we're doing here. Create tracking reference should be checked if it isn't already - that will ensure that we can send further changes to the branch without any further worries; don't worry about pushing tags or force pushing right now. Simply click push, and you should be good to go!

ZbB1t7s.png

How to actually make the PR

Congratulations, this is the simplest part of the whole thing. Once you've successfully pushed your bracnh up to your remote, you can go directly to the Aurora repo at https://github.com/Aurorastation/Aurora.3 . You should immediately see an orange banner along the top of the repo, stating that your first-pr branch has recent pushes:

nz5MPsM.png

Just hit the Compare & pull request button to get started! (If you don't see this bar for whatever reason, you can get to this window by going to Pull Requests -> New pull request)

pY55Hj0.png

As you can see, since we only had one commit, GitHub has helpfully renamed our PR automatically to our commit message - more than one commit and we'd simply have our branch name there instead. Note that this doesn't mean you shouldn't split your PR into several commits - make as many as you're comfortable with! - but simply that for simple changes, you should probably keep everything to one commit if you can, at least to start with. The beauty of this is that the PR tracks your remote branch; if you need to make changes to the PR after posting it, you can! Just make your changes locally, commit them, and push them - they'll automatically appear in the PR, and the relevant unit tests will re-run automatically!

Now, to get your PR off the ground, you need to do a couple things. Read through the instructions listed in the PR write window (ignore the bit about IC changelogs) - then, clear that window, and replace it with a brief description of what all you're changing. For this, I'll simply state that we've fixed the bugs with the flashlight not lighting up or being installable in slightly more words.

In addition - for bugfix PRs with an attached issue, you should always note which issue(s) you fixed with the syntax Fixes #XXXXX (where XXXXX is the ID number of the issue). For example, this PR fixes the issue 13629, so below my description, I'll add:

WtBv1Uw.png

Writing this in the PR description tells GitHub to automatically close the attached issues once this PR is merged.

Finally, you're ready to post your PR! Once you do, you should go ahead and tag it - but as a contributor, you can't access the GitHub labels function. Instead, go ahead and leave a comment on your PR simply stating !review. If it's a bugfix PR, leave another comment with !bugfix. This alerts our GitHub bot to add the labels to your PR, which is important for our internal tracking and review of them.

iJpcliA.png

Once your PR is posted with the Review Required label attached, you're done! At least for now -- we developers and your fellow contributors will be along to review your changes, and perhaps request changes of our own if things don't quite meet contributing standards, or if we think something might be done in a better or faster way! Don't take it personally - we've all screwed up, and screwed up big time. It's inevitable working with a beast of a codebase in a monolith of an engine like this - so just take it in stride, and get coding!

Edited by Wildkins
Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...