GreenBoi Posted June 18 Posted June 18 (edited) Wildkins' guide is good at telling you to set up your first PR and even test servers, but it doesn't tell you much about actual coding and other ways to contribute are currently unfinished. I've been programming for exactly [53] days, that's under two months, and I've made seven successful PRs plus a few more yet to be uploaded. They weren't super-complicated, but I did add cool stuff, and the coolest PRs I learned to make within my first 10 days. Coding contributions are the ultimate lifeblood of the server, so I intend to comprehensively teach the foundations so anyone can learn within ten days. I've been told it's better to learn programming in another, bigger language and then return to apply fundamentals to DM, but that's an entire extra layer of friction for beginners to hear. For the record, programming languages share a lot of fundamental concepts so it's easier to swap to another than you think, but that still feels like huge overkill when you just want a simple PR done, so I'll do that part for you. The "lazy" part of this guide is the fact I don't want you to worry about more than necessary for functional DM code. You will eventually and naturally learn smart practices, it's literally the job of maintainers to tell you so the server doesn't break. There will be quick references you can Ctrl+F in this guide for understanding particular systems, they'll be focused on the logic so even if the code becomes out-of-date, the skill is still good. Just so it's in this post, here's a quick refresher on forking and setting up a local repository, and GitHub terminology. Spoiler GitHub Terminology Cheatsheet Repository: The stored codebase itself Master: The main, normal version of the codebase. Branch: An alternate offshoot of code within the same repository Fork: A separate copy of the entire repository Upstream: The parent repository you forked from Remote: The online, cloud version of your codebase visible on GitHub. Has to be manually updated. Local: The downloaded version of your codebase on a device. Must be compiled. Origin: Your fork Commit: A bundle of code changes packaged together Push: Moving commit changes from one place to another, usually from local to remote Pull Request: Requesting your commits get added elsewhere Merges: Syncing with and adding changes from elsewhere. 0. Create a GitHub account. Get any GitHub client, and Visual Studio Code (with the recommended DM extensions) 1. Go to the main GitHub page you want to fork, that's https://github.com/Aurorastation/Aurora.3 2. Go to the top right corner and click Fork. 3. Move to your new repository, click on your Profile -> Repositories -> Aurora.3 4. Click the green Code button, then copy the HTTPS link inside 5. Use your client to clone locally from the link, then make a new branch For actual private server testing: Select and copy all inside the config/example subfolder to the normal config folder. Add your ckey to admins.txt without the front hashtag, so it's not commented out. Rename the main folder with the BYOND .dme file to "aurorastation", then right-click and open Visual Studio Code, now using Run -> Start Debugging (or F5) should start compiling and boot Dreamseeker. If the main folder isn't named "aurorastation" and your DreamDaemon's security is on Safe (default), you will be annoyingly forced to accept over a hundred security prompts EACH time booting. Starting Dream Daemon and launching with security set to Trusted is another way around this. 6. Once done with testing, you can use your Git Client, or VSCode's Source Control, to make a commit. Then you can push these changes to remote, and make a PR there. VSCODE DOESN'T COMPILE OR BOOT DREAMSEEKER, WHAT'S WRONG If that method doesn't work for whatever reason, then you can manually type in the VSCode terminal C:\yourfilepathhere\aurorastation\tools\build\build.bat to compile, then click the aurorastation.dmb BYOND executable. You can also manually go to the folder and click the script, though if it gets errors, the logs will only temporarily be there. The root issue is probably that the folder to your Dreamseeker executable isn't set, use Run -> Open Configurations and an option to set the folder configuration should appear. If you don't know where your BYOND folder is, open Dreamseeker then use (Ctrl+F) to Open File and look where the folder is in the drop-down menu there. This is the Table of Contents for the sections of this post, note that the actual coding guide is pretty short because the fundamentals aren't that complex. Just give 30 minutes reading and you'll be able to break down and write most code. If you ever need to learn something more specific, check a Quickref for directions. Spoiler [BYOND CODING BASICS] Objects, Overriding, Procedures Scope and Access Syntax and Macros Deletion and garbage collection [PR DIRECTION & ADVICE] It's All Illusory Example Beginner PRs You're Not Alone [DEBUGGING] Visual Studio Code Overview: Tabs, Extensions, Breakpoints Common Error Messages [REFERENCE MATERIAL] Aurora's Type Structure Quick Reference Spoiler ANSWERS QUICKREF HANDY ADMIN VERBS QUICKREF WHAT DO THESE ERRORS MEAN? QUICKREF WHAT ARE GLOB, SS, and _ PREFIXES? QUICKREF WHAT ARE STATIC VARS? QUICKREF WHAT ARE BITFLAGS, 0x1, AND 1<<1? QUICKREF WHAT ARE SINGLETONS QUICKREF WHY DO THESE PROC DECLARATIONS ALL RETURN? BASE INTERACTIONS AND FEEDBACK QUICKREF COMMON PROCS AND MACROS QUICKREF OUTPUT OR DISPLAY FEEDBACK QUICKREF INPUTS OR INTERACTION, HYPERLINKS AND TOPICS QUICKREF CHECKING FOR CONDITIONS ADDING TO TYPES QUICKREF HOW TO CHANGE ICONS AND SPRITES QUICKREF HOW TO ADD TURFS AND FLOORING QUICKREF HOW TO ADD OBJS (ITEM/ORGANS/MACHINERY) QUICKREF HOW TO ADD MOBS USING SYSTEMS OR BACKEND STRUCTURES QUICKREF THE OVERMAP & AWAY SITES QUICKREF WIRES FOR HACKING QUICKREF USING POWER QUICKREF ADDING OR TRANSFERRING REAGENTS QUICKREF HOW TO ADD TO LOADOUT, WHY CAN'T I SEE XENO LOADOUTS? QUICKREF HOW TO ADD TO CHARACTER SETUP QUICKREF SIGNALS QUICKREF COMPONENTS Now, for BYOND CODING BASICS 1/3 : Objects, Overriding, Procedures The main language for SS13 is Dream Maker, its official documentation is here and a mostly-fine source for understanding in-built parts of the language. To understand DM and minimize structural problems or frustration, you NEED to understand Object-Oriented Programming. OOP relies on encapsulation and inheritance. An object is just a recognized bundle of data, variables are specific pieces of data, and inheritance is when related child objects share data from their parents. Children can override and change data from their parents, and freely declare entirely new data for themselves which will also be inherited by any children they have. In VSCode, you can right-click/Go -> Go to Definition (F12) and quickly find the variable's declaration. In BYOND, there are four main objects which will be familiar to you: Area, zones like what APCs grant power to; Turf, the flooring below you; Obj, physical items and structures; and Mob, short for mobile object, which covers players & creatures. Atom is a special object which is the ancestor to all mappable objects (areas, turfs, objs, mobs...hey, see what they did?), and Datum is the parent to effectively all types, it's mostly used to hold "pure information" like your character's DNA in-game, the powernet, or computer programs; datum is always the implied parent of types with seemingly no parent (ex: "plushie" alone is actually "/datum/plushie") Comprehension Checkpoint 1/2 - Can you figure out the relationship between the objs inside the spoiler, and why? Spoiler /obj/plushie is clearly the parent path of everything else, but what does that actually mean? /obj/plushie name = "relaxing plush" /obj/plushie/canine var/number_of_legs = 4 /obj/plushie/canine/wolf name = "cool wolf" /obj/plushie/dog name = "cute dog" And can you tell what's a mistake or poor type-pathing above? Spoiler 1. What it means: /obj/plushie as the parent type means any of its children have the name "relaxing plush" unless overridden and changed. 2. What is poor: /obj/plushie/dog isn't a child of canine, which means it'd lack the number_of_legs variable if that ever came up Wondering why number_of_legs is prefixed by "var"? That's because it's an entirely new variable being declared rather overriding an inherited one. "name" is an in-built variable for all objs in BYOND. It's a good habit to check the official docs for their list of in-built variables for the big 6 parent types. Objects alone are just bundles of data. For anything to actually happen like even changing a set value (without making a child), we use procs or procedures, known generally as functions in programming. Objects are like stations and procs are like trains, neither does anything alone, and must connect. A proc is predefined data attached to an object with "type/proc/procname()" and simply does stuff to data when called; that can be directly changing the data, calling other procs, or simply retrieving what the data is. Like objects, procs inherit from their parents and get inherited by all children, where they're formatted as just "type/procname()". They MUST be called to do anything, and any new variable declared inside a proc only exists until it ends. The entire game is legitimately just a bunch of procs triggering each other, with objects storing long-term data. Not kidding, VSCode allows you to pause all code running in a few ways, and you can clearly see stops usually pause the call stacks from the Master Controller's Loop() proc...it's truly just procedures triggering each other all the way down. The new() proc is in-built for all types, it's important because it constructs an actual instance of the object into the game world with its own vars at the location sent as the first argument. Without calling it, objects are just kind of floating data templates. new()'s effect on a particular type can be defined through New(), they are both case-sensitive. The custom troll() proc will be inherited by all objs, and changes their in-built name variable to "Trolled!" Calling procs is as simple as writing its name, so this could be called through troll() obj/New() object_id = total_objects total_objects += 1 /obj/proc/troll() name = "Trolled!" Procedures have args or arguments, also called parameters, that can go in their parentheses and allow data to be passed in and out as named variables. Input order corresponds to arg order, however an argument's value can be specified out-of-order. Arguments can have default values through simply being assigned one in the proc declaration, they can also have their type written out to accept only values under that. /obj/proc/vandalize(newname = name, newdesc = "Vandalized!") name = "[newname]" desc = newdesc With this proc definition, you can use vandalize("Cool Name", "Cool Desc") to set an obj's name and desc to the respective text strings. vandalize(newdesc = "Cool Desc") would skip the first argument and just set a new description. Because newname has a default assigned value, not inputting it while calling the proc just results in the name staying the same. If there weren't a default value, newname would be equal to null which naturally blanks out the name. The brackets in quotes are a DM-specific feature called "embeds", which lets you automatically text-stringify a variable or even proc result; that's actually very convenient and well-designed compared to other programming languages. It's not necessary for the example situation, but it's good to know about that as some stuff like name and desc SHOULD be text-only values on the actual server. Comprehension Checkpoint 2/2 - What is the mistake in this proc? Spoiler HINT: It's not related to newname itself. /obj/proc/change_name(newname) var/name_change_count name = newname if(name == newname) name_change_count += 1 As the normal = sign gets used to mean "assign the right value into the left variable", == is what's used for the actual "is equal to" operator in most programming languages. Spoiler REMEMBER: New variables declared within a proc don't exist outside it being called. Declaring a new variable inside a proc to count will literally never work, except for one case. Any form of permanence from a proc should be done using pre-existing variables declared outside it. The example in /obj/New() would be a working count because total_objects is a pre-existing variable. The += operator just means "total_changes = total_changes + 1", also common in most programming languages. += 1 can also be written as simply ++, it's preference, but ++ is good if it'll only be by 1. Like variables in objects, procs can also be overridden in children. Doing this gives it an entirely new function, which can be handy for specific interactions, but you will probably want to instead call the parent proc. The present return value of a proc is referenced with a period . character, and ..() calls the parent, so ". = ..()" means "assign the value of the parent proc to the current one." Extending the parent proc in this example makes canine plushies uniquely get their number_of_legs lowered by 1 while also getting a changed desc. /obj/plushie/canine/vandalize() . = ..() number_of_legs -= 1 You can keep extending forever to always add stuff, if you want to. This is how mechanics hints in examines work, they're a separate inherited proc that extends the parent to get added onto the examine. Arguments themselves can also be overridden, adding in new ones or even removing some; never remove args, it's confusing. The Takeaway 1. Objects cannot change values or interact without using procs 2. Procs cannot affect anything permanently without using pre-existing objects 3. Variables not declared with "var" and procs not using "/proc/procname()" are inherited from parents. 4. Go to Definition can be used to learn context and purpose, especially when the inherited proc is also calling its parent. Alternatively, search for "var/" or "/proc/" Procs can actually be used before they're defined, unlike objects, because the definition is just instruction for what to do when called. BYOND CODING BASICS 2/3: Scope and Access Prior examples assumed you're using a variable or proc from the same object it's attached it, but this is commonly not the case. Related to inheritance is the concept of scope, which affects what data you're allowed to modify and what they can be identified as. Scope flows downwards and only data from the current scope or higher can be modified. Variables, objects, and proc name declarations are unique for their scope and all beneath it. Using the vandalize() example: because that's attached to /obj/ itself, /obj/plushie/proc/vandalize() is invalid because it'd compete within the scope. To access data outside the current scope, you use the . operator after their name, which can be chained to go down several layers of variables if necessary. Knowing what and when you can access data is a a huge part of structuring code, do not overlook it. vandalize() is a purely internal proc right now that can only be used by the object itself, so for players to ever interact with it, you'd need to actually use another proc that either passes to the object's scope or calls object.vandalize() The most specific scope is with a reference, which is the exact computer memory address of an individual object- so, completely unique and constrained to that particular instance. Holding references is necessary to affect an instance of anything, they are the keys to extending access and different methods should be used when situations arise. Lists are the most obvious way besides single-value variables, you can hold however-many you need for later access and if that list is part of an object then having access to the object extends access to not only the list, not only access to references in it, but also any held references within those references. Lists are pretty great and make up a common method of storing important references at the high-level, it's even possible to have lists of lists or multi-dimensional lists where each item holds lists with several other lists; obviously, avoid excessive lists that hold so much extra stuff. "contents" is a convenient, in-built, atom-level variable that lists everything contained by an object. List items can be accessed through their index number in brackets if you know it, which count from 1 (ex: list_name[1]) in Dream Maker (most languages count from 0), or found with a for loop. For loops iterate over a fixed range, literally performing its instructions for each instance; they're the most consistent way to affect multiple objects or search for a particular one. A for loop can be declared a few different ways, but should probably look like these as they're most readable and straightforward: for(var/thing in list_of_stuff) // Form 1: Assign from list if(istype(thing, /obj/plushie)) thing.name = "Stuffed Plush" for(var/obj/plushie/plush in list) // Form 2: Filter in list for(var/num in 1 to 10) // Form 3: Increment over range Form 1 for loops assigns each item in the list to the lefthand variable "thing", which may then be used to affect the item's variables. Form 2 for loops, however, typecast the variable to filter for that type in the list, which is useful for more specific internal procs or variables. Typecasting is a general thing that can be done outside lists too. Note that in the syntax of Dream Maker, the latest type after the last slash is always the variable name, so "plush" is just the instance variable name for anything under the path /obj/plushie, this also naturally includes child types. The variables declared in a for loop only exists until its end, but you should probably still keep its name distinct from its parent type for the sake of clarity. Form 3 allows incrementing over numerical ranges, there can even be variables in place of the numbers for an adjustable loop. To filter for multiple objects, you can use Form 1 with an istype() check as shown above. If you're looking for just one object or the first instance, you can break out of the loop to end it early; alternatively, you may filter out objects you don't want using continue to skip it in the loop. There's also the associative list, sometimes generally known as dictionaries, which has each item as a unique key paired to an inner value. Associative lists are useful to easily retrieve data without a for loop, it's as simple as using the key to access the value: var/list_of_stuff[0] // Declare 0-item list list_of_stuff("Plush Dog" = /obj/plushie/dog) // parentheses format is a method of declaration list_of_stuff["Plush Wolf"] = /obj/plushie/canine/wolf // brackets format here is equal to .Add("keyname" = value) // Now, using either keys list_of_stuff["Plush Dog"] and list_of_stuff["Plush Wolf"] would automatically link to the associated value in any context In some cases, for really tedious and repetitive actions, a while loop can be desirable. Where for loops iterate over a finite range, while loops can iterate infinitely, so it's very important you have proper checks. Verbs are a part of Dream Maker declared with /verb/ you're definitely familiar with from play, they show up clickable on panels and are typable in the command bar working functionally identical to procs. Where procs are only used when called in code, declared verbs are usable live whenever a player has access to its source, that is their effective scope: this is handy for cases of range-limited obj verbs through set src, but there's no way to restrict having them for only specific instances of an object. In order to restrict verbs to certain states or specific instances, you'll want them declared as procs for the constrained access and then added to the specific atom's verbs list variable to make it display like a normal verb. COMPREHENSION CHECKPOINT 1/1 - Which verb is properly restricted? Spoiler "src" is a variable available for all procs which refers to the object containing it; useful for an object to target itself. /mob/user/reader /mob/user/writer /mob/user/verb/write() if(!istype(src, /mob/user/writer)) return return gives the specified value of a proc. When nothing is specified, it returns null and is an effective way to end procs early. /datum/user_setup/proc/add_innate_verb(mob/user/target) add_verb(target, innate_verb) /proc/add_verb(mob/target, verb_path) target.verbs += verb_path /datum/user_setup/writer innate_verb = /mob/user/writer/proc/write /mob/user/writer/proc/write() set category = "Writer" set name = "Write Post" The set option affects how and when verbs are visible. You should also be able to figure out where you'd place a for loop here if you turned the innate_verb variable into a list. Spoiler The first example is available for all users, but manually checks whenever called for if the user is actually a writer. This design can work if it's reliant on finding an obj or status, but mob type doesn't change, so the second example is clearly a better method of restriction. The second example itself can also be done in a few ways. You've already been told some different methods that can achieve this, so take time to think about how for a bit to practice code structuring. The broadest scope is the global scope, variables and objects there are accessible by everything else in code. You should avoid declaring new globals when unnecessary because they all take up memory, but if you must then make sure the names are specific and recognizable so it's hard to accidentally access them or cause conflicts. Global values shouldn't be casually modified and situations where you'd want to do so can always instead use a new local variable which will limit unintended side-effects. The Takeaway 1. Scope determines access to values and flows down like inheritance, so you can only access data from the current scope or higher. The . operator is used to access data outside the current scope 2. References are the exact instance of an object, necessary for specific targeting, and can extend access to further object references held inside. 3. Lists can store many values in one place, and associative lists can make it easy to retrieve them 4. For loops can iterate, search, or filter through any list of values. BYOND CODING BASICS 3/3: Syntax, Assignment, and Macros Now that you know what objects are, and where or when to access them, you just need to know how things are organized to start coding for real. In Dream Maker, all code gets written into .dm files, sprites or icons go into .dmi files, and maps are in .dmm files. Code in any .dm file can access and modify code in others, so these files are purely organizational and in the actual codebase, you'll find some objects are defined in entirely different .dm files from where they're used based on organization. Coding really is a language and just like any language, there's rules for sentence logic and multiple ways to accomplish the same thing. /obj/plushie // This is the most common path syntax you'll recognize, it's used for a good reason. name = "relaxing plush" /obj/plushie/canine/var/number_of_legs = 4 // This is valid path syntax in DM, but it's less readable for multiple variables obj // This is also technically-valid syntax, but it's even less readable and impossible to search for plushie dog name = "cute dog" The double forward slashes mark comments: nothing after them is executed as code. Real comments are left behind to explain complex or less-intuitive code blocks. Multi-line ones can be done with /* and */ DM allows both single forward slashes and tab indentations for defining new type paths, but it's better for readability and searchability to use the former over the latter. The only exception is with indenting variables, like shown /obj/plushie's name variable. Code is otherwise structured around operators, which modify values, and procs, which facilitate that. Trying to redefine a set value outside a proc, or without overriding it under a new type, will conflict the code compiler and count as a duplicate definition of the same thing. The = sign is the operator used to define stored values, it assigns whatever is the result of the right side to be stored in the left. A similar operator is ==, which is what's used for "is equal to" checks common for both mathematical (+, -, /, *, %, <, >, <=, >=) and logical operations (!, ||, &&, ?). Logical operators are conditional checks that allow further control over code. They're almost always paired with if-statements, which you've seen before! The logic of an if-statement is simple: if the argument within the parentheses is true, then the code indented under gets executed. The logical operators are straightforward, but have their little caveats, though these ones are a general programming thing than DM-specific. var/a // No defined value means it's equal to null var/b = 2 /proc/FlipValue() if(!a || a) // || is the or operator, it executes off the first true value from left to right. ! is the not operator, it inverts true/falseness. a = !a b += 1 /proc/HasValue() if((a && b) || a != null) // && is the and operator, it stops at the first false value. != means "is not equal to" b -= 1 /proc/GetValue() return a ? a : b // ? is the ternary operator, first value after sign is the result when condition is true, value after colon is for when it's false. All the logical operators have something to do with true-falseness because the root logic for all programming is binary 1s (truth) and 0s (falseness), also known as Booleans. Null and empty text strings also implicitly count as false, which makes checking values very convenient. There's also the bitwise operators (&, |, ^, ~) that get used specifically for bitflags, which are effectively multiple booleans stacked in one variable for memory optimization. You can learn more about them in the What Are Bitflags? Quickref. Compound operations like += and -= just save you time and are possible for all operators, being equal to "variable = variable + value" and so on for the other signs. ?. and ?[] are condensed ternary operators which can be used to conditionally access variables and indexes that aren't guaranteed to exist on the object. The indented code under if-statements work like a side branch, code execution eventually returns to the same indentation level as the if once done. Similar to the ternary operator, if statements can have fallback code executed for when their condition is false with the else-if statements, which allow another condition to be checked and executed only when the prior one fails, and the plain else-statements, which always get executed at the end when the previous ifs have failed. They all follow the same indentation rule, so same-level code statements should be lining up together. COMPREHENSION CHECKPOINT - Which if-else chain executes code only once? Spoiler CCCC var/a var/b = 2 /proc/MatchValue() VVVV Most code is controlled through a chain of various if-statements and operator use, and their effects is limited by the object's scope. How the previous sections connect should be clicking into place for you: coding is all about structure. And a feature of Dream Maker that's very convenient for structure is the #define macro, they work like substitutions for the compiler which gets them used like alternative procs or constant variables. #define macros are globally usable by default, unless #undef is also used to remove it, then they're restricted to the file they're defined in. Proper read-only constant variables do exist, but they're only really used under specific scopes where macros can't shine. Macros are preferred over "magic numbers" which are cases in code where a proc or variable uses a number value that communicates nothing about its purpose; most macros in the codebase are typed in what's called SCREAMING_SNAKE_CASE to stand out precisely for clear communication. TRUE and FALSE are macros actually in-built into DM that can substitute 1 and 0 for variables meant to be booleans, it's a preferred convention in the codebase for readability. The actual codebase also has custom-defined time macros for SECONDS/MINUTES/HOURS/DAYS because Dream Maker's base time is in deciseconds, this also means that displaying accurate time always requires some division. Dream Maker is, at least, convenient when it comes to in-built references for ease of use. There's a few you should know that are really helpful: /* var/client - The client object is tied your connection to the server. You won't access it often unless you need an in-built var. The client mob var is what you'll usually use to check for connected players src - src is a var specific to procs that just means the object the proc is attached to. It's required for an object to target itself in procs, or to embed itself in text strings. usr - usr is the mob of the player that triggered a proc, official documentation recommends to use this with only verbs where the effect is clearest as - a keyword that lets you cast a specific return type for a proc so it can be chained like example()?.ChainedProc() */ Similar convenience... /NEW() INSTANCING client, src, usr, as keyword TODO : [BYOND CODING BASICS] Syntax, Assignment, and Macros Deletion and garbage collection [DIRECTION] It's All Illusory Example Beginner PRs You're Not Alone The Aurora Style [DEBUGGING] Visual Studio Code Overview: Tabs, Extensions, Breakpoints Common Error Messages [REFERENCE MATERIAL] Aurora's Type Structure Quick Reference ANSWERS QUICKREF HANDY ADMIN VERBS Spoiler Aghost - Normal ghosting, but you can re-enter your body. Change-Mob-Appearance---Admin Change Species, Add Languages + Appearance options like mirrors. Obeys some defined stats like a species' max languages. Event-Manager-Panel Control and schedule random events. Game-Panel Has Create Object/Turf/Mob for spawning. Can also affect airflow and atmos if you want. Get-Key-to-Teleport Select a ckey to teleport their body to you. Get-Mob-to-Teleport is similar, but for any mob including NPCs. Jump-to-Coordinate Jump to exact XYZ coordinates. Jump-to-Sector Jump to a visitable overmap sector. Jump-to-Ship is for specific ships, Jump-to-Shuttle is for only landable shuttles (and elevators). Jump-to-Turf Jump to any turf in-game, or whichever one you have one the right-click menu. Rejuvenate Completely restores target, even from death or decapitation. Show-Game-Mode See roundtype, mainly useful for setting up autotraitor antags, which aren't limited to traitor. Show-Player-Panel Heal = Rejuvenate, VV is View-Variables, Traitor panel is for any antagonist role; can add any language regardless of normal restrictions, and set Psionics Rank (follows species restrictions) Start-Now Starts the round when possible; automatically queues to happen if not currently possible. Spawn Manually spawn in any atom. Sometimes works when Game-Panel 's Create doesn't. Spawn-Chemical-Dispenser-Cartridge is useful for reagent testing. Spawn-Bluespace-Technician An easy body double for testing: they know most languages, can toggle a personal godmode or phase through walls. Teleport-out to return to your original self. View-Variables Useful for debugging, you can see most variables and even edit them to an extent. Sometimes faster to notice stuff here than searching through code. QUICKREF HOW DO YOU DUPLICATE OBJECTS? Spoiler Fully duplicating the exact variables of an object, known in programming as deep cloning, is actually kind of involved and annoying, but that's probably not actually what you want. What you're actually after is probably just duplicating its name and desc to be passable, which is much easier. var/dupename = "[target.name]" var/dupedesc = "[target.desc]" var/obj/item/youritem/i = new(target.loc) i.name = dupename i.desc = dupedesc Where target is a reference to the object you're copying, of course. This syntax uses the lefthand variable as the implied type for new, creates the instance at target's location and then changes its values. Should things get janky somehow, specifying new varname.type may get things working as expected again. QUICKREF WHAT DO THESE ERRORS MEAN? Spoiler "expected a constant expression" A pre-existing variable outside a proc is linked to another variable, embedding something, or trying to perform operations. Keep it assigned to a simple value like the others. "inconsistent indentation" You don't have things tabbed correctly. Remember: if, else, for, and while statements should have code run under them tabbed FURTHER in. Given line in the error is usually off a bit. If it's from missing tabs elsewhere, you can probably fix it automatically in VSCode with View -> Command Palette ->Convert Indentation to Tabs. "missing lefthand..." Your bracket or parentheses grouping isn't properly enclosed. Given line in the error is usually very off, so expect to retrace steps. "cannot access non-static field" Not to be confused with the static modifier: you declared an instance with a type that doesn't inherit actual access to the var or proc, properly typecast it to ensure it can. (EX: internal_organs_by_name is a /mob/living/carbon-defined variable, so a /mob/living instance could never access that list.) "cannot define proc within a proc" You probably haven't realized if(), for(), and similar count as procs; ensure proper indentation and remember that the parentheses are there for a reason. "cannot read null.var" A variable used somewhere is null when it probably shouldn't be. Fixing this may just take a null check, but tracing the exact cause will take breakpoints and stepping with VSCode. QUICKREF WHAT ARE GLOB, SS, and _ PREFIXES? Spoiler GLOB means it's a global variable. Aurora has a system set up that prevents the casual creation of new globals and explicitly marks access to all existing ones with the unique syntax GLOB.varname Examples of properly declaring globals can be found at __globals.dm in the code/__DEFINES subfolder, but it's the general format of "GLOBAL_VAR(x)", "GLOBAL_DATUM(x)" or "GLOBAL_LIST_INIT(x)" Variables prefixed with SS (ex: SShallucinations) are subsystems, they're global variables defined through the SUBSYSTEM_DEF(x) macro and usually must initialize or fire at some point from the server booting, so they're marked for being generally important. Variables and procs prefixed with underscores are a general programming convention marking internal use only, meaning they exist for structural purposes and shouldn't be interacted with or accessed outside specific accepted methods. The underscore happens to also make it hard to accidentally use these from VSCode's autocomplete. QUICKREF WHAT ARE STATIC VARS? Spoiler Static variables are permanent and shared across a type path or proc, meaning all uses of them affect the same instance within that scope. They're the one way you can declare something in a proc that persists afterwards in future calls. Static variables are a normal, general concept in programming, but Dream Maker annoyingly refers to these as "globals" in most official documentation. Just use /static/ modifier if you ever have to declare one, but you probably won't need to most of the time. QUICKREF WHAT ARE BITFLAGS, 0x1, AND 1<<1? Spoiler A bit is the smallest unit in programming, a binary digit representing true (1) or false (0). There's eight bits in a byte and a byte is the smallest addressable unit, being equal to a single text character. Binary true-false values are also called Booleans, named after math using lots of 1s and 0s. Flags in programming are boolean variables to indicate a state or condition, like activated or processing. Bitflags are a method of squeezing more value out of a byte to optimize memory, tying multiple booleans and conditions to a single variable. The different conditions can be switched through like numeric thresholds using the bitwise operators: & (and)/ | (or)/ ^/ (exclusive-or/xor)/ ~ (not) To check for bitflag thresholds, you will probably use var & value (variable is true/non-zero and equal to [bitvalue]) To add bitvalues, you will probably use |= (variable is equal to itself or [bitvalue]) To remove bitvalues, you will probably use &= ~var (variable is itself WITHOUT [bitvalue]), ^= may be used for bitflags with only two values. If you ever see stuff like 0x1, that x means it's hexadecimal (literally 6+10, x is sort for hex, b would mean binary), which is base-16 and effectively just mega-condenses 4 binary digits to 1 hexadecimal digit. 0-9 represent themselves while the letters A-F get used to represent values 10-15 while the powers of sixteen increment the left place's value. So, 0x1 in hex just means 1, but 0xF means 15. 0x10 would equal 16, 0x20 for 32, and so on. Stuff like 1<<1 is something known as bit-shifting, which is just another condensed form of the binary format. <<1 means moving one place to the left, which is equal to multiplying by 2. <<2 would mean two places to the left, and therefore you multiply by 4...you get the pattern, it goes by the power of two and is easier notation. QUICKREF WHAT ARE SINGLETONS? Spoiler Singletons are a design pattern in programming where there is exactly one shared instance of an object accessible anywhere, globally. In Aurora, singletons are explicitly marked with the path /singleton/ and the design gets used for reagents, flooring, plant genes, emotes, origins, posters, and more. Singleton design means even if these types exist in different quantities at different places, their effects and interactions are completely uniform. The GET_SINGLETON(x) macro is used to check for an instance, defined in code/__DEFINES/singletons.dm QUICKREF WHY DO THESE PROC DECLARATIONS ALL RETURN? Spoiler If you see a full /proc/ declaration that immediately returns, it could possibly just be one designed to be overridden by its children if it has a generic name that could be used to interact with a variety of things like /obj/proc/interact(), however it could also be that the proc is only declared at that level to prevent errors when calling it elsewhere, with the "true proc" definition with an actual use being in a child type. This can be likely when the declaration is on an object that's a parent to many things and the proc name is much more specific. You could argue the difference is just a matter of perspective, but the former case CAN benefit from calling the parent procs in some situations while the latter cases NEVER need their parent declaration to actually have anything. BASE INTERACTIONS AND FEEDBACK QUICKREF COMMON PROCS AND MACROS Spoiler addtimer - macro; Used to set a timer to call a proc. Args follow format of addtimer(CALLBACK(target, PROC_REF, PROC_REF parameters...), waiting time) assembly_hints() - proc; ALWAYS += EXTEND PARENT PROC. Called on examine, meant to be overridden for assembly hints section of desc. antagonist_hints() - proc; ALWAYS += EXTEND PARENT PROC. Called on examine, meant to be overriden for antagonist hints shown only to antags and ghosts. attackby() - proc; Called on objects hit by items. attack_ai() - proc; Called when clicked by an AI mob. attack_hand() - proc; Called when clicked by an empty active hand. attack_ranged() - proc; Called with a click while not adjacent. Only actually used by Bishop frame IPCs, but it's handy to know this is an option. attack_self() - proc; Called when an item held in active hand is clicked, or with the "activate-held-object" verb (known as mode() internally). audible_message() - proc; Called for exclusively audible messages, a wrapper that later calls the nested proc show_message(). Destroy() - proc; Datum-level, used over Del() for defining how objects are deleted. ALWAYS RETURN PARENT PROC AT END. disassembly_hints() - proc; ALWAYS += EXTEND PARENT PROC. Called on examine, meant to be overridden for disassembly hints section of desc. do_after() - proc; Called to make a progress bar where an output happens after the delay. emp_act() - proc; ALWAYS EXTEND PARENT PROC. Called whenever an emp affects an atom, meant to be overriden for custom interactions. Severity arg's value generally goes from EMP_HEAVY (1) to EMP_LIGHT (2), but it used to be a magic number so some procs have it reversed from poor understanding. ex_act() - proc; Called whenever an explosion affects an atom, meant to be overridden for custom interactions. Severeity arg's value goes from 1 to 3, strongest to weakest. feedback_hints() - proc; ALWAYS += EXTEND PARENT PROC. Called on examine, meant to be overridden for general feedback hints on desc, like remaining ammo on a gun. forceMove() - proc; Called to forcibly move an atom in a direction or to specific coordinates for teleporting. forceMove(null) stores an atom nowhere, but doesn't delete them so they can still be retrieved. get_examine_text() - proc; ALWAYS += EXTEND PARENT PROC. Called on examine, meant to be overridden for direct changes to actual examine text. interact() - proc; Called commonly for matters of UI, whether it be inputs and/or outputs. Initialize() - proc; Atom-level, ALWAYS EXTEND PARENT PROC. Used over New() for setting up objects for roundstart. After the server is initialized, New() automatically calls Initialize() so you may use that. ishuman() - proc; Called to easily type-check something as /mob/living/carbon/human, importantly: all playable species are technically human subtypes, so think of it as ishumanoid; ishuman_species() checks for the actual exact species. istype() - proc; In-built, Checks if value is the same type or a subtype of the given path. If no path is specified, defaults to given value's type. mechanics_hints() - proc; ALWAYS += EXTEND PARENT PROC. Called on examine, meant to be overridden for mechanics hints section of desc. playsound() - proc; Called to play sound files. qdel() - proc; Datum-level, called over plain the plain del proc to delete object. The q stands for queue. show_message() - proc; Called to display a message or its alternate based on its type (visible or hearable). sleep() - proc; In-built, pauses and delays proc for amount set as argument, in deciseconds. Note there's some inherited procs custom-designed to reject sleeping for important structural reasons. SPAN_INFO() - macro; Shortcut for HTML span class tag, makes your message blue. Common for general feedback messages meant to stand out. SPAN_WARNING() - macro; similar HTML tag shortcut, makes your message red and italicized. Used for negative feedback or minor urgencies. SPAN_DANGER() - macro; similar HTML tag shortcut, makes your message red and boldened. Used for major urgencies. START_PROCESSING() - macro; begins processing and calling the specified datum. STOP_PROCESSING() to end this. tgui_input_text - proc; takes your input as a text string to_chat() - proc; Sends text to the target. upgrade_hints() - proc; ALWAYS += EXTEND PARENT PROC. Called on examine, meant to be overridden for listing available stock part component upgrades in advanced machinery. visible_message() - proc; Called for exclusively visible messages, a wrapper that later calls nested proc show_message(). If there's a second argument for a self message, the performer will only see that message. QUICKREF OUTPUT OR DISPLAY FEEDBACK QUICKREF INPUTS OR INTERACTION, HYPERLINKS AND TOPICS QUICKREF CHECKING FOR CONDITIONS ADDING TO TYPES QUICKREF HOW TO CHANGE ICONS AND SPRITES QUICKREF HOW TO ADD TURFS AND FLOORING QUICKREF HOW TO ADD OBJS (ITEMS/ORGANS/AUGMENTS/MACHINERY) Spoiler Defining new /obj/s is simple, but functionality varies massively depending on the path used: 1. /obj/item is the parent to anything that may be picked up or fit in your hand. Properly defined at code/game/objects/items.dm 2. /obj/item/organ is the parent of body-specific items. /obj/item/organ/external refers to limbs while /obj/item/organ/internal is for everything else inside them. Properly defined under the code/modules/organs folder. 3. /obj/structure is the parent of anything that can't be picked up, meaning walls, windows, tables, and such 4. /obj/structure/machinery is the parent of any structure with complex-ish player interactions and usually also power usage, like cameras, airlocks, computers (modular or circuit-based), alarms, and even weird stuff like the Supermatter (this is how it always gets announcements). Properly defined under code/game/objects/structures/_machinery.dm Organs are designed to process for injuries or life effects when relevant (check the base /proc/process()'s comments to know what should be adjusted to the speed of processing ticks), with bruised and broken as two damage thresholds that can alter behavior once reached; is_bruised() and is_broken() are helpful procs to check for that with. Internal organs normally take half damage from anything that manages to hit them. All organs have a unique tag variable that's used for targeting them and preventing duplicates within the same body, limbs have these defined in code/__DEFINES/mobs.dm while internal organs have it at their own .dm file. Species-specific organs of either type get defined together in one related file under the code/modules/organs/subtypes folder. The status variable is a bitflag which designates most conditions like bleeding or being robotic from macros defined in code/__DEFINES/damage_organs.dm, but augments specifically get designated with the is_augment boolean and are their own child type with dedicated do_bruised_act() and do_broken_act() procs for additional behaviors at those damage thresholds. Machinery objects can use power, be constructed through stock part components if wanted, interacted by tools, hold a signaler, and interfaced by the AI at the base level. You may override whatever is needed to make the object work for its purpose, the Supermatter is an extreme version of this that demonstrates the flexibility possible. use_power_oneoff() is useful for Related:How to Add to Loadout, Using Power QUICKREF HOW TO ADD MOBS USING SYSTEMS OR BACKEND STRUCTURES QUICKREF THE OVERMAP & AWAY SITES QUICKREF WIRES FOR HACKING QUICKREF USING POWER QUICKREF ADDING IN RECIPES QUICKREF ADDING OR TRANSFERRING REAGENTS QUICKREF HOW TO ADD TO LOADOUT, WHY CAN'T I SEE XENO LOADOUTS? Spoiler The different loadout files are in the code/modules/client/preference_setup/loadout subfolder, the system's design is pretty straight-forward: all loadout options are their own gear datums with variables for display name, description, and who is allowed to spawn with it. You can see the variable declarations in loadout.dm, appropriate flag macros in _defines.dm and the actual loadout options in the /items subfolder with a different .dm file for each category. /datum/gear/toothpaste display_name = "dental hygiene kit" path = /obj/item/storage/box/toothpaste /datum/gear/toothpaste/New() ..() var/list/toothpaste = list() toothpaste["dental hygiene kit, blue toothbrush"] = /obj/item/storage/box/toothpaste toothpaste["dental hygiene kit, green toothbrush"] = /obj/item/storage/box/toothpaste/green toothpaste["dental hygiene kit, red toothbrush"] = /obj/item/storage/box/toothpaste/red gear_tweaks += new /datum/gear_tweak/path(toothpaste) Adding an option is as simple as referencing the type path of an object defined elsewhere To have multiple selections for one item, you declare an associative list in its New() proc, key is the name you'll see and the value is the appropriate item. Renaming or changing description is done with setting the flag variable to GEAR_HAS_NAME_SELECTION | GEAR_HAS_DESC_SELECTION if its parent has name and description restricted by default, GEAR_NO_SELECTION if the parent otherwise can be renamed and changed by default. Augments just reference an organ type path and have the augment boolean as true Related: How to Add Objs, What Are Bitflags? WHY CAN'T I SEE XENO LOADOUTS? If you can't see xeno loadouts, remember to comment out USEALIENWHITELIST in the config file. Get in the habit of doing that pre-emptively, actually, since it doesn't even need recompiling as the config is a plain text file. QUICKREF HOW TO ADD TO CHARACTER SETUP Spoiler We can follow what's already there for 01_basic.dm in code/modules/client/preference_setup/general as an easy reference for the Character Setup system. It's most of what shows up on the first page. /datum/category_item/player_setup_item/general/basic name = "Basic" sort_order = 1 /datum/category_item/player_setup_item/general/basic/load_character(var/savefile/S) S["real_name"] >> pref.real_name S["gender"] >> pref.gender ... Going in order, this is a datum which loads savefiles through list keys. Savefiles are an in-built Dream Maker file type which does exactly what you think it does, the >> operator clearly means "load from file" and the S["variable_names"] are associated list keys being used to access the actual values held within. The save_character() is the same, but with the << operator that saves to the file. "pref" is clearly an inherited object because it has its own variables and also wasn't declared with basic. You can manually dig up to find its declaration, or just right-click Go to Definition (or Peek) in VSCode on specifically the pref part where you'll see it's declared in preference_setup.dm, inherited from specifically /datum/category_item/player_setup_item where pref itself is a child of /datum/preferences. Back to 01_basic.dm, the actual options and buttons for what you see in the character setup page are managed with the content() proc, it's exactly half of how the page functions. /datum/category_item/player_setup_item/general/basic/content(var/mob/user) var/list/dat = list("<b>Name:</b> ") if (pref.can_edit_name) dat += "<a href='byond://?src=[REF(src)];rename=1'><b>[pref.real_name]</b></a><br>" else dat += "<b>[pref.real_name]</b><br> (<a href='byond://?src=[REF(src)];namehelp=1'>?</a>)" if (pref.can_edit_name) dat += "(<a href='byond://?src=[REF(src)];random_name=1'>Random Name</A>)" dat += "<br>" dat += "<b>Sex:</b> <a href='byond://?src=[REF(src)];gender=1'><b>[capitalize(lowertext(pref.gender))]</b></a><br>" When you're in the lobby, you're technically a special newplayer mob, that's what user is for. Don't let the byond URLs frighten you, <a> is the HTML tag for anchors, which are hyperlinks. "href" is literally short for hyperlink reference, the "byond://?src=" is just saying to reference that EXACT data, and the word after the semicolon is a shortcut to the reference. This is just the written-out form of the buttons in Character Setup. The rest of the content() proc is just repeating this to add the relevant text and references with a few conditional if-checks. Really simple when it's broken down like that, right? Well, you're about to fully understand it because this the second half of how the page functions: /datum/category_item/player_setup_item/general/basic/OnTopic(var/href,var/list/href_list, var/mob/user) if(href_list["rename"]) if (!pref.can_edit_name) alert(user, "You can no longer edit the name of your character.<br><br>If there is a legitimate need, please contact an administrator regarding the matter.") return TOPIC_NOACTION var/current_character = pref.current_character var/raw_name = input(user, "Choose your character's name:", "Character Name") as text|null if(current_character != pref.current_character) //Without this, you can switch slots while the input menu is up to change your character's name past the grace period return ... "href" just means hyperlink reference, remember? And I said those words were shortcuts, this is how the buttons and hyperlinks actually function after they were declared and referenced. It's formally known as a Topic() proc in the BYOND docs, in general programming it's called a "command pattern." This is because all that's actually happening is you check which hyperlink was clicked using their keywords, and then you code the appropriate response contained in that check. It is genuinely that simple, just remember that they should all end in a return statement as it's technically one proc. To add a new option, you simple write a new href in content() then implement its actual effect in OnTopic(). Ensure it's properly saved by having the option represented in the different lists. Related: Inputs or Interaction, Checking for Conditions QUICKREF SIGNALS Spoiler A signal in programming is an indicator sent while or after an event has occurred. Yes, that's it. They're very handy and a native feature for some game engines, but are completely manually-built as a backend system for Aurora's codebase. Another name for signals in programming is the "observer pattern," this is because signals are only relevant for code that actively checks for them, which can save a lot of performance as stuff that doesn't use them completely ignores them. Signals are checked for with RegisterSignal(), a datum-level proc naturally inherited by everything. Registering also links the signal to particular proc, likely one also on the attached object for clear purpose. An easy example of registration in action is with the skills system. /datum/component/skill/pilot_mechs/Initialize() . = ..() if (!parent) return RegisterSignal(parent, COMSIG_MECH_MOVE_WASD, PROC_REF(handle_user_move), override = TRUE) RegisterSignal(parent, COMSIG_MECH_MOVE_STRAFE, PROC_REF(handle_user_strafe), override = TRUE) RegisterSignal(parent, COMSIG_MECH_TOGGLE_POWER, PROC_REF(handle_toggle_power), override = TRUE) ... And whenever those signals are sent, the relevant procs are run. The override argument lets you even register the same signal for however-many-different procs desired. It should be noted there's a few different forms of the PROC_REF() macro depending on scope, plain PROC_REF is for procs on the same attached object's type or those inherited, TYPE_PROC_REF is for completely unrelated types, and GLOBAL_PROC_REF is for exclusively global procs. The SIGNAL_HANDLER macro is also mandatory on the registered procs, not because they're totally unable to work without it, but because signals are a manually-built backend feature. Signals get sent with the SEND_SIGNAL() macro, target as the first arg and signal type as the second, then however-many extra args are needed for the procs. Note that the target is ALWAYS sent as the first argument to the procs no matter what. /mob/living/heavy_vehicle/relaymove(mob/living/user, direction, var/turn_only = FALSE) . = ..() var/delay_modifier = 0 SEND_SIGNAL(user, COMSIG_MECH_MOVE_WASD, &direction, &delay_modifier) ... The & sign designates pointers, which are something specific to skills that genuinely shouldn't be used elsewhere...ever because it makes tracking down changes awful. Obvious from the all-caps, all signals are macros and most are defined in code/__DEFINES/dcs/signals.dm, though the most generic ones are in the .../dcs/signals/signals_atom subfolder. For those generic atom-level signals, registration is actually already done for you and tied to a custom proc for whatever use is needed. /obj/item/soap/Initialize() . = ..() var/static/list/loc_connections = list( COMSIG_ATOM_ENTERED = PROC_REF(on_entered), ) ... Soap is registered to the atom_entered signal, which is sent whenever something enters its location. on_entered() is a custom proc that soap happens to use for slipping. That"static" designation means whenever you see the loc_connections list elsewhere, it is technically the same list persistent everywhere. Related: Components, What Are Static Vars? QUICKREF COMPONENTS Spoiler Components are completely modular objects, bundles of data and procs that can come and go for whatever. It's a backend system which is special because it fundamentally changes the logic structure from purely "is-a" type relationships to also "has-a" type relationships. To understand that, think of an object's type in a sentence. Using /obj/item/soap as an example, a bar of soap literally is a(n) item, which is also a(n) obj. Inheritance means children are always their parents in some form. Components are like labels or stickers, you attach them to an object and that object literally has a component, the object is basically a container now for external interactions and variables handled in a component datum. Because of this unique has-a relationship, components are primarily designed around not caring about their parents unless absolutely necessary because components are the very things that make up their parent's functionalities. The skill system is a great example of the component design structure as each skill is its own attached datum instance handling procs, being designed this way allows it to non-intrusively exist and even be easily ignored for shipbound, ghostroles, or anything playable not yet designed or intended for the system. In Aurora's code, components are heavily tied to signals, COMSIG being short for Component Signal. Signals are usually the trigger to add or remove components, it's a smooth operation where specific events passing adds necessary components, and those components can be easily removed if necessary. The bartending skill in particular uses the modularity of components to tie a moodlet to a specific glass. Without components, this would require all glasses to hold these variables just in case of an interaction. Components, and signals, makes it only happen when actually necessary. /datum/component/drink_moodlet_provider/Initialize(value = 5.0, overwrite = FALSE, drink_quality) . = ..() if (!parent) return moodlet_value = value overwrite_moodlet = overwrite RegisterSignal(parent, COMSIG_CONTAINER_DRANK, PROC_REF(handle_drank), override = TRUE) if (!isatom(parent)) return var/atom/owner = parent initial_name = owner.name switch (drink_quality) if (-INFINITY to 5) owner.name = "inferior " + initial_name ... A "moodlet" is another component-reliant system, it's tied to Morale. The only things that have Morale are things that actually use it, and the Morale component itself is just a datum instance with a special value that holds and handles the moodlet components when they become relevant. Traits, most obviously used by background Origins in Character Setup, are also component-reliant, but in a much simpler way as they are basically just floating flags that can be attached to a particular thing than being a inherited boolean in everything of its type. To access a component, you simply use the proc GetComponent(), inherited from the datum-level. The actual component as an argument should be with a macro defined in components.dm under code/__DEFINES. To load a component, you use the LoadComponent() macro with the datum as an argument and any extras desired. To add a component, you do similar using the AddComponent() macro. The main procs for all components are in _component.dm under the code/datums/components folder, the file already has good comments documenting their use and purpose. You shouldn't need to use most of the procs, because a lot of them are backend or structural. If one starts with underscores, it's definitely not meant to be directly used- that's common programming syntax for "private, don't touch when running" Related: Signals VVVV (THIS WAS NOT SUPPOSED TO BE POSTED SO EARLY, AAAAAGH) Edited Thursday at 16:00 by GreenBoi this was posted early and is clearly unfinished; updated coding guide 1
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now