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 WHAT DO THESE ERRORS MEAN? QUICKREF WHAT ARE GLOB, SS, and _ PREFIXES? QUICKREF WHAT ARE STATIC VARS? QUICKREF WHAT ARE BITFLAGS AND 0x1? 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 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/4 : 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 - 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 personal vars. 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 - 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/4: 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, filter for a specific type path in the list, which is useful for more specific internal procs or variables. 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 - 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. CLOSE OUT TOPIC OF SCOPE WITH VERBS, THEN GLOBAL VARS SYNTAX AND MACROS: MENTION TABS, IF-ELSE CHAINS no duplicates, if-else chains, as keyword preprocessor macros, deciseconds src, usr, client, text macros ternary operators TODO : [BYOND CODING BASICS] Explain New() and instances Scope and Access (explain references, lists, for loops, and verbs) Syntax 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 WHAT DO THESE ERRORS MEAN? Spoiler "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. 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. "cannot access non-static field" Not to be confused with the static keyword: 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; 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/ keyword if you ever have to declare one, but you probably won't need to most of the time. QUICKREF WHAT ARE BITFLAGS AND 0x1? 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 |= (variable is equal to itself or [bitvalue]) To remove bitvalues, you will probably use ~= (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. 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) 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, later calls nested proc show_message(). do_after() - proc; Called to make a progress bar where an output happens after the delay. 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. 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 "humanoid". ishuman_species() checks for the actual species. playsound() - proc; Called to play sound files. show_message() - proc; Called to display a message or its alternate based on its type (visible or hearable). 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. tgui_input_text - proc; to_chat() - proc; Sends text to the target. visible_message() - proc; Called for exclusively visible messages, later calls nested proc show_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/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. QUICKREF HOW TO ADD MOBS USING SYSTEMS OR BACKEND STRUCTURES 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 inbuilt 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 Saturday at 01:14 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