diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index c98d8a93f8b8..000000000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,904 +0,0 @@ -# CONTRIBUTING - -## Introduction - -This is the contribution guide for Paradise Station. These guidelines apply to -both new issues and new pull requests. If you are making a pull request, please refer to -the [Pull request](#pull-requests) section, and if you are making an issue report, please -refer to the [Issue Report](#issues) section, as well as the -[Issue Report Template](ISSUE_TEMPLATE.md). - -## Commenting - -If you comment on an active pull request or issue report, make sure your comment is -concise and to the point. Comments on issue reports or pull requests should be relevant -and friendly, not attacks on the author or adages about something minimally relevant. -If you believe an issue report is not a "bug", please point out specifically and concisely your reasoning in a comment on the issue itself. - -### Comment Guidelines - -- Comments on Pull Requests and Issues should remain relevant to the subject in question and not derail discussions. -- Under no circumstances are users to be attacked for their ideas or contributions. All participants on a given PR or issue are expected to be civil. Failure to do so will result in disciplinary action. -- For more details, see the [Code of Conduct](../CODE_OF_CONDUCT.md). - -## Issues - -The Issues section is not a place to request features, or ask for things to be changed -because you think they should be that way; The Issues section is specifically for -reporting bugs in the code. - -### Issue Guidelines - -- Issue reports should be as detailed as possible, and if applicable, should include instructions on how to reproduce the bug. - -## Pull requests - -Players are welcome to participate in the development of this fork and submit their own -pull requests. If the work you are submitting is a new feature, or affects balance, it is -strongly recommended you get approval/traction for it from our forums before starting the -actual development. - -### Pull Request Guidelines - -- Keep your pull requests atomic. Each pull request should strive to address one primary goal, and should not include fixes or changes that aren't related to the main purpose of the pull request. Unrelated changes should be applied in new pull requests. In case of mapping PRs that add features - consult a member of the development team on whether it would be appropriate to split up the PR to add the feature to multiple maps individually. - -- Document and explain your pull requests thoroughly. Failure to do so will delay a PR as we question why changes were made. This is especially important if you're porting a PR from another codebase (i.e. TG) and divert from the original. Explaining with single comment on why you've made changes will help us review the PR faster and understand your decision making process. - -- Any pull request must have a changelog, this is to allow us to know when a PR is deployed on the live server. Inline changelogs are supported through the format described [here](https://github.com/ParadiseSS13/Paradise/pull/3291#issuecomment-172950466) and should be used rather than manually edited .yml file changelogs. - -- Pull requests should not have any merge commits except in the case of fixing merge conflicts for an existing pull request. New pull requests should not have any merge commits. Use `git rebase` or `git reset` to update your branches, not `git pull`. - -- Please explain why you are submitting the pull request, and how you think your change will be beneficial to the game. Failure to do so will be grounds for rejecting the PR. - -- If your pull request is not finished make sure it is at least testable in a live environment. Pull requests that do not at least meet this requirement may be closed at maintainer discretion. You may request a maintainer reopen the pull request when you're ready, or make a new one. - -- While we have no issue helping contributors (and especially new contributors) bring reasonably sized contributions up to standards via the pull request review process, larger contributions are expected to pass a higher bar of completeness and code quality _before_ you open a pull request. Maintainers may close such pull requests that are deemed to be substantially flawed. You should take some time to discuss with maintainers or other contributors on how to improve the changes. - -- By ticking or leaving ticked the option "Allow edits and access to secrets by maintainers", either when making a PR or at any time thereafter, you give permission for repository maintainers to push changes to your branch without explicit permission. Repository maintainers will avoid doing this unless necessary, and generally should only use it to apply a merge upstream/master, rebuild TGUI, deconflict maps, or other minor changes required shortly before a PR is to be merged. More extensive changes such as force-pushes to your branch require explicit permission from the PR author each time such a change needs to be made. - -#### Using The Changelog - -- The tags able to be used in the changelog are: `add/soundadd/imageadd`, `del/sounddel/imagedel`, `tweak`, `fix`, `wip`, `spellcheck`, and `experiment`. -- Without specifying a name it will default to using your GitHub name. Some examples include: - -```txt - :cl: - add: The ability to change the color of wires - del: Deleted depreciated wire merging now handled in parent - fix: Moving wires now follows the user input instead of moving the stack - /:cl: -``` - -```txt - :cl: UsernameHere - spellcheck: Fixes some misspelled words under Using Changelog - /:cl: -``` - -## Modifying MILLA - -Our atmos engine, MILLA, is in the `milla/` directory. It's written in Rust for performance reasons, which means it's not compiled the same way as the rest of the code. If you're on Windows, you get a pre-built copy by default. If you're on Linux, you built one already to run the server. - -If you make changes to MILLA, you'll want to rebuild. This will be very similar to RUSTG: -https://github.com/ParadiseSS13/rust-g -The only difference is that you run `cargo` from the `milla/` directory, and don't need to speify `--all-features` (though it doesn't hurt). - -The server will automatically detect that you have a local build, and use that over the default Windows one. - -When you're ready to make a PR, please DO NOT modify `milla.dll` or `tools/ci/libmilla_ci.so`. Leave "Allow edits and access to secrets by maintainers" enabled, and post a comment on your PR saying `!build_milla`. A bot will automatically build them for you and update your branch. - -## Specifications - -As mentioned before, you are expected to follow these specifications in order to make everyone's lives easier. It'll save both your time and ours, by making -sure you don't have to make any changes and we don't have to ask you to. Thank you for reading this section! - -### Object Oriented Code - -As BYOND's Dream Maker (henceforth "DM") is an object-oriented language, code must be object-oriented when possible in order to be more flexible when adding -content to it. If you don't know what "object-oriented" means, we highly recommend you do some light research to grasp the basics. - -### All BYOND paths must contain the full path - -(i.e. absolute pathing) - -DM will allow you nest almost any type keyword into a block, such as: - -```dm -datum - datum1 - var - varname1 = 1 - varname2 - static - varname3 - varname4 - proc - proc1() - code - proc2() - code - - datum2 - varname1 = 0 - proc - proc3() - code - proc2() - ..() - code -``` - -The use of this format is **not** allowed in this project, as it makes finding definitions via full text searching next to impossible. The only exception is the variables of an object may be nested to the object, but must not nest further. - -The previous code made compliant: - -```dm -/datum/datum1 - var/varname1 = 1 - var/varname2 - var/static/varname3 - var/static/varname4 - -/datum/datum1/proc/proc1() - code - -/datum/datum1/proc/proc2() - code - -/datum/datum1/datum2 - varname1 = 0 - -/datum/datum1/datum2/proc/proc3() - code - -/datum/datum1/datum2/proc2() - ..() - code -``` - -### Do not compare boolean values to TRUE or FALSE - -Do not compare boolean values to TRUE or FALSE. For TRUE you should just check if there's a value in that address. For FALSE you should use the ! operator. An exception is made to this when working with JS or other external languages. If a function/variable can contain more values beyond null/0 or TRUE, use numbers and defines instead of true/false comparisons. - -```dm -// Bad -var/thing = pick(TRUE, FALSE) -if(thing == TRUE) - return "bleh" -var/other_thing = pick(TRUE, FALSE) -if(other_thing == FALSE) - return "meh" - -// Good -var/thing = pick(TRUE, FALSE) -if(thing) - return "bleh" -var/other_thing = pick(TRUE, FALSE) -if(!other_thing) - return "meh" -``` - -### Use `pick(x, y, z)`, not `pick(list(x, y, z))` - -`pick()` will happily take a fixed set of options. Wrapping them in a list is redundant and slightly less efficient. -'''dm -// Bad -var/text = pick(list("test_1", "test_2", "test_3")) -to_chat(world, text) - -// Good -var/text = pick("test_1", "test_2", "test_3") -to_chat(world, text) -''' - -### User Interfaces - -All new user interfaces in the game must be created using the TGUI framework. Documentation can be found inside the [`tgui/docs`](../tgui/docs) folder, and the [`README.md`](../tgui/README.md) file. This is to ensure all ingame UIs are snappy and respond well. An exception is made for user interfaces which are purely for OOC actions (Such as character creation, or anything admin related) - -### No overriding type safety checks - -The use of the `:` operator to override type safety checks is not allowed. You must cast the variable to the proper type. - -### Do not access return value vars directly from functions - -The use of the pointer operator, `.`, should not be used to access the return values of functions directly. This can cause unintended behavior and is difficult to read. - -```dm -//Bad -var/our_x = get_turf(thing).x - -//Good -var/turf/our_turf = get_turf(thing) -var/our_x = our_turf.x -``` - -### Type paths must begin with a / - -eg: `/datum/thing`, not `datum/thing` - -### Datum type paths must began with "datum" - -In DM, this is optional, but omitting it makes finding definitions harder. To be specific, you can declare the path `/arbitrary`, but it -will still be, in actuality, `/datum/arbitrary`. Write your code to reflect this. - -### Do not use list operators in strings - -The use of list operators to augment strings is not allowed. This is roughly 10 times slower than using a list with a Join() Function. - -```dm -//Bad -var/text = "text" -text += "More text" -to_chat(world, text) - -//Good -var/list/text = list("text") -text += "More text" -to_chat(world, text.Join("")) -``` - -### Do not use text/string based type paths - -It is rarely allowed to put type paths in a text format, as there are no compile errors if the type path no longer exists. Here is an example: - -```dm -//Bad -var/path_type = "/obj/item/baseball_bat" - -//Good -var/path_type = /obj/item/baseball_bat -``` - -### Do not use `\The` - -The `\The` macro doesn't actually do anything when used in the format `\The [atom reference]`. Directly referencing an atom in an embedded string -will automatically prefix `The` or `the` to it as appropriate. As an extension, when referencing an atom, don't use `[atom.name]`, use `[atom]`. -The only exception to this rule is when dealing with items "belonging" to a mob, in which case you should use `[mob]'s [atom.name]` to avoid `The` -ever forming. - -```dm -//Bad -var/atom/A -"\The [A]" - -//Good -var/atom/A -"[A]" -``` - -### Use the pronoun library instead of `\his` macros - -We have a system in [`code/__HELPERS/pronouns.dm`](../code/__HELPERS/pronouns.dm) for addressing all forms of pronouns. This is useful in a number of ways; - -- BYOND's `\his` macro can be unpredictable on what object it references. Take this example: `"[user] waves \his [user.weapon] around, hitting \his opponents!"`. This will end up referencing the user's gender in the first occurence, but what about the second? It'll actually print the gender set on the weapon he's carrying, which is unintended - and there's no way around this. -- It always prints the real `gender` variable of the atom it's referencing. This can lead to exposing a mob's gender even when their face is covered, which would normally prevent it's gender from being printed. - -The way to avoid these problems is to use the pronoun system. Instead of `"[user] waves \his arms."`, you can do `"[user] waves [user.p_their()] arms."` - -```dm -//Bad -"[H] waves \his hands!" -"[user] waves \his [user.weapon] around, hitting \his opponents!" - -//Good -"[H] waves [H.p_their()] hands!" -"[user] waves [H.p_their()] [user.weapon] around, hitting [H.p_their()] opponents!"` -``` - -### Use `[A.UID()]` over `\ref[A]` - -BYOND has a system to pass "soft references" to datums, using the format `"\ref[datum]"` inside a string. This allows you to find the object just based -off of a text string, which is especially useful when dealing with the bridge between BYOND code and HTML/JS in UIs. It's resolved back into an object -reference by using `locate("\ref[datum]")` when the code comes back to BYOND. The issue with this is that locate() can return a unexpected datum -if the original datum has been deleted - BYOND recycles the references. - -UID's are actually unique; they work off of a global counter and are not recycled. Each datum has one assigned to it when it's created, which can be -accessed by `[datum.UID()]`. You can use this as a snap-in replacement for `\ref` by changing any `locate(ref)` calls in your code to `locateUID(ref)`. -Usage of this system is mandatory for any `Topic()` calls, and will produce errors in Dream Daemon if it's not used. - -```dm -//Bad -"Link!" - -//Good -"Link!" -``` - -### Use `var/name` format when declaring variables - -While DM allows other ways of declaring variables, this one should be used for consistency. - -### Tabs, not spaces - -You must use tabs to indent your code, NOT SPACES. - -(You may use spaces to align something, but you should tab to the block level first, then add the remaining spaces.) - -### No hacky code - -Hacky code, such as adding specific checks (ex: `istype(src, /obj/whatever)`), is highly discouraged and only allowed when there is **_no_** other option. (Protip: 'I couldn't immediately think of a proper way so thus there must be no other option' is not gonna cut it here! If you can't think of anything else, say that outright and admit that you need help with it. Maintainers, PR Reviewers, and other contributors who can help you exist for exactly that reason.) - -You can avoid hacky code by using object-oriented methodologies, such as overriding a function (called "procs" in DM) or sectioning code into functions and -then overriding them as required. - -The same also applies to bugfixes - If an invalid value is being passed into a proc from something that shouldn't have that value, don't fix it on the proc itself, fix it at its origin! (Where feasible) - -### No duplicated code - -Copying code from one place to another may be suitable for small, short-time projects, but Paradise is a long-term project and highly discourages this. - -Instead you can use object orientation, or simply placing repeated code in a function, to obey this specification easily. - -### Startup/Runtime tradeoffs with lists and the "hidden" init proc - -First, read the comments in [this BYOND thread](http://www.byond.com/forum/?post=2086980&page=2#comment19776775), starting where the link takes you. - -There are two key points here: - -1. Defining a list in the variable's definition calls a hidden proc - init. If you have to define a list at startup, do so in `New()` (or preferably `Initialize()`) and avoid the overhead of a second call (`init()` and then `New()`) - -2. It also consumes more memory to the point where the list is actually required, even if the object in question may never use it! - -Remember: although this tradeoff makes sense in many cases, it doesn't cover them all. Think carefully about your addition before deciding if you need to use it. - -### Prefer `Initialize()` over `New()` for atoms - -Our game controller is pretty good at handling long operations and lag, but it can't control what happens when the map is loaded, which calls `New()` for all atoms on the map. If you're creating a new atom, use the `Initialize()` proc to do what you would normally do in `New()`. This cuts down on the number of proc calls needed when the world is loaded. - -While we normally encourage (and in some cases, even require) bringing out of date code up to date when you make unrelated changes near the out of date code, that is not the case for `New()` -> `Initialize()` conversions. These systems are generally more dependent on parent and children procs, so unrelated random conversions of existing things can cause bugs that take months to figure out. - -### No implicit `var/` - -When you declare a parameter in a proc, the `var/` is implicit. Do not include any implicit `var/` when declaring a variable. - -```dm -//Bad -/obj/item/proc1(var/mob/input1, var/input2) - code - -//Good -/obj/item/proc1(mob/input1, input2) - code -``` - -### No magic numbers or strings - -This means stuff like having a "mode" variable for an object set to "1" or "2" with no clear indicator of what that means. Make these #defines with a name that more clearly states what it's for. For instance: - -```dm -//Bad -/datum/proc/do_the_thing(thing_to_do) - switch(thing_to_do) - if(1) - do_stuff() - if(2) - do_other_stuff() -``` - -There's no indication of what "1" and "2" mean! Instead, you should do something like this: - -```dm -//Good -#define DO_THE_THING_REALLY_HARD 1 -#define DO_THE_THING_EFFICIENTLY 2 - -/datum/proc/do_the_thing(thing_to_do) - switch(thing_to_do) - if(DO_THE_THING_REALLY_HARD) - do_stuff() - if(DO_THE_THING_EFFICIENTLY) - do_other_stuff() -``` - -This is clearer and enhances readability of your code! Get used to doing it! - -### Control statements - -(if, while, for, etc) - -- All control statements comparing a variable to a number should use the formula of `thing` `operator` `number`, not the reverse - (eg: `if(count <= 10)` not `if(10 >= count)`) -- All control statements must be spaced as `if()`, with the brackets touching the keyword. -- All control statements must not contain code on the same line as the statement. - - ```DM - //Bad - if(x) return - - //Good - if(x) - return - ``` - -### Player Output - -Due to the use of "TGchat", Paradise requires a special syntax for outputting text messages to players. Instead of `mob << "message"`, you must use `to_chat(mob, "message")`. Failure to do so will lead to your code not working. - -### Use early returns - -Do not enclose a proc in an if-block when returning on a condition is more feasible. - -This is bad: - -```dm -/datum/datum1/proc/proc1() - if(thing1) - if(!thing2) - if(thing3 == 30) - do stuff -``` - -This is good: - -```dm -/datum/datum1/proc/proc1() - if(!thing1) - return - if(thing2) - return - if(thing3 != 30) - return - do stuff -``` - -This prevents nesting levels from getting deeper then they need to be. - -### Use `addtimer()` instead of `sleep()` or `spawn()` - -If you need to call a proc after a set amount of time, use `addtimer()` instead of `spawn()` / `sleep()` where feasible. -Though more complex, this method has greater performance. Additionally, unlike `spawn()` or `sleep()`, it can be cancelled. -For more details, see [https://github.com/tgstation/tgstation/pull/22933](https://github.com/tgstation/tgstation/pull/22933). - -Look for code examples on how to properly use it. - -```dm -//Bad -/datum/datum1/proc/proc1(target) - spawn(5 SECONDS) - target.dothing(arg1, arg2, arg3) - -//Good -/datum/datum1/proc/proc1(target) - addtimer(CALLBACK(target, PROC_REF(dothing), arg1, arg2, arg3), 5 SECONDS) -``` - -### Signals - -Signals are a slightly more advanced topic, but are often useful for attaching external behavior to objects that should be triggered when a specific event occurs. - -When defining procs that should be called by signals, you must include `SIGNAL_HANDLER` after the proc header. This ensures that no sleeping code can be called from within a signal handler, as that can cause problems with the signal system. - -Since callbacks can be connected to many signals with `RegisterSignal`, it can be difficult to pin down the source that a callback is invoked from. Any new `SIGNAL_HANDLER` should be followed by a comment listing the signals that the proc is expected to be invoked for. If there are multiple signals to be handled, separate them with a `+`. - -```dm -/atom/movable/proc/when_moved(atom/movable/A) - SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED - do_something() - -/datum/component/foo/proc/on_enter(datum/source, atom/enterer) - SIGNAL_HANDLER // COMSIG_ATOM_ENTERED + COMSIG_ATOM_INITIALIZED_ON - do_something_else() -``` - -If your proc does have something that needs to sleep (such as a `do_after()`), do not simply omit the `SIGNAL_HANDLER`. Instead, call the sleeping code with `INVOKE_ASYNC` from within the signal handling function. - -```dm -/atom/movable/proc/when_moved(atom/movable/A) - SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED - INVOKE_ASYNC(src, PROC_REF(thing_that_sleeps), arg1) -``` - -### Operators - -#### Spacing of operators - -- Operators that should be separated by spaces: - - Boolean and logic operators like `&&`, `||` `<`, `>`, `==`, etc. (But not `!`) - - Bitwise AND `&` and OR `|`. - - Argument separator operators like `,`. (and `;` when used in a forloop) - - Assignment operators like `=` or `+=` or the like. - - Math operators like `+`, `-`, `/`, or `*`. -- Operators that should NOT be separated by spaces: - - Access operators like `.` and `:`. - - Parentheses `()`. - - Logical not `!`. - -#### Use of operators - -- Bitwise AND `&` - - Should be written as `bitfield & bitflag` NEVER `bitflag & bitfield`, both are valid, but the latter is confusing and nonstandard. -- Associated lists declarations must have their key value quoted if it's a string - -```DM - //Bad - list(a = "b") - - //Good - list("a" = "b") -``` - -#### Bitflags - -- Bitshift operators are mandatory, opposed to directly typing out the value. I.E: - -```dm - #define MACRO_ONE (1<<0) - #define MACRO_TWO (1<<1) - #define MACRO_THREE (1<<2) -``` - -Is accepted, whereas the following is not: - -```dm - #define MACRO_ONE 1 - #define MACRO_TWO 2 - #define MACRO_THREE 4 -``` - -While it may initially look intimidating, `(1<Arbitrary text") - - //Good - user.visible_message("Arbitrary text") -``` - -- You should not use color macros (`\red, \blue, \green, \black`) to color text, instead, you should use span classes. `Red text`, `Blue text`. - -```dm - //Bad - to_chat(user, "\red Red text \black Black text") - - //Good - to_chat(user, "Red textBlack text") -``` - -- To use variables in strings, you should **never** use the `text()` operator, use embedded expressions directly in the string. - -```dm - //Bad - to_chat(user, text("[] is leaking []!", name, liquid_type)) - - //Good - to_chat(user, "[name] is leaking [liquid_type]!") -``` - -- To reference a variable/proc on the src object, you should **not** use `src.var`/`src.proc()`. The `src.` in these cases is implied, so you should just use `var`/`proc()`. - -```dm - //Bad - var/user = src.interactor - src.fill_reserves(user) - - //Good - var/user = interactor - fill_reserves(user) -``` - -### Develop Secure Code - -- Player input must always be escaped safely, we recommend you use `stripped_input()` in all cases where you would use input. Essentially, just always treat input from players as inherently malicious and design with that use case in mind. - -- Calls to the database must be escaped properly - use proper parameters (values starting with a :). You can then replace these with a list of parameters, and these will be properly escaped during the query, and prevent any SQL injection. - -```dm - //Bad - var/datum/db_query/query_watch = SSdbcore.NewQuery("SELECT reason FROM [format_table_name("watch")] WHERE ckey='[target_ckey]'") - - //Good - var/datum/db_query/query_watch = SSdbcore.NewQuery("SELECT reason FROM [format_table_name("watch")] WHERE ckey=:target_ckey", list( - "target_ckey" = target_ckey - )) // Note the use of parameters on the above line and :target_ckey in the query. -``` - -- All calls to topics must be checked for correctness. Topic href calls can be easily faked by clients, so you should ensure that the call is valid for the state the item is in. Do not rely on the UI code to provide only valid topic calls, because it won't. - -- Information that players could use to metagame (that is, to identify round information and/or antagonist type via information that would not be available to them in character) should be kept as administrator only. - -- Where you have code that can cause large-scale modification and _FUN_, make sure you start it out locked behind one of the default admin roles - use common sense to determine which role fits the level of damage a function could do. - -### Files - -- Because runtime errors do not give the full path, try to avoid having files with the same name across folders. - -- File names should not be mixed case, or contain spaces or any character that would require escaping in a uri. - -- Files and path accessed and referenced by code above simply being #included should be strictly lowercase to avoid issues on filesystems where case matters. - -#### Modular Code in a File - -Code should be modular where possible; if you are working on a new addition, then strongly consider putting it in its own file unless it makes sense to put it with similar ones (i.e. a new tool would go in the `tools.dm` file) - -Our codebase also has support for checking files so that they only contain one specific typepath, including none of its subtypes. This can be done by adding a specific header at the beginning of the file, which the CI will look for when running. An example can be seen below. You can also run this test locally using `/tools/ci/restrict_file_types.py` - -```dm -RESTRICT_TYPE(/datum/foo) - -/datum/proc/do_thing() // Error: '/datum' proc found in a file restricted to '/datum/foo' - -/datum/foo - -/datum/foo/do_thing() - -/datum/foo/bar // Error: '/datum/foo/bar' type definition found in a file restricted to '/datum/foo' - -/datum/foo/bar/do_thing() // Error: '/datum/foo/bar' proc found in a file restricted to '/datum/foo' -``` - -### SQL - -- Do not use the shorthand sql insert format (where no column names are specified) because it unnecessarily breaks all queries on minor column changes and prevents using these tables for tracking outside related info such as in a connected site/forum. - -- Use parameters for queries, as mentioned above in [Develop Secure Code](#develop-secure-code). - -- Always check your queries for success with `if(!query.warn_execute())`. By using this standard format, you can ensure the correct log messages are used. - -- Always `qdel()` your queries after you are done with them, this cleans up the results and helps things run smoother. - -- All changes to the database's layout (schema) must be specified in the database changelog in SQL, as well as reflected in the schema file. - -- Any time the schema is changed the `SQL_VERSION` defines must be incremented, as well as the example config, with an appropriate conversion kit placed - in the SQL/updates folder. - -- Queries must never specify the database, be it in code, or in text files in the repo. - -### Mapping Standards - -- For map edit PRs, we do not accept 'change for the sake of change' remaps, unless you have very good reasoning to do so. Maintainers reserve the right to close your PR if we disagree with your reasoning. - -- Map Merge - - - The following guideline for map merging applies to **ALL** mapping contributers. - - Before committing a map change, you **MUST** run mapmerge2 to normalise your changes. You can do this manually before every commit with `"\tools\mapmerge2\Run Before Committing.bat"` or automatically by installing the hooks at `"\tools\hooks\Install.bat"`. - - Failure to run Map Merge on a map after editing greatly increases the risk of the map's key dictionary becoming corrupted by future edits after running map merge. Resolving the corruption issue involves rebuilding the map's key dictionary; - -- StrongDMM - - - [We strongly encourage use of StrongDMM version 2 or greater, available here.](https://github.com/SpaiR/StrongDMM/releases) - - When using StrongDMM, the following options must be enabled. They can be found under `File > Preferences`. - - Sanitize Variables - Removes variables that are declared on the map, but are the same as initial. (For example: A standard floor turf that has `dir = 2` declared on the map will have that variable deleted as it is redundant.) - - Save format - `TGM`. - - Nudge mode - pixel_x/pixel_y - -- Variable Editing (Var-edits) - - - While var-editing an item within the editor is fine, it is preferred that when you are changing the base behavior of an item (how it functions) that you make a new subtype of that item within the code, especially if you plan to use the item in multiple locations on the same map, or across multiple maps. This makes it easier to make corrections as needed to all instances of the item at one time, as opposed to having to find each instance of it and change them all individually. - - Subtypes only intended to be used on ruin maps should be contained within an .dm file with a name corresponding to that map within `code\modules\ruins`. This is so in the event that the map is removed, that subtype will be removed at the same time as well to minimize leftover/unused data within the repo. - - When not using StrongDMM (which handles the following automatically) please attempt to clean out any dirty variables that may be contained within items you alter through var-editing. For example changing the `pixel_x` variable from 23 to 0 will leave a dirty record in the map's code of `pixel_x = 0`. - - Areas should **never** be var-edited on a map. All areas of a single type, altered instance or not, are considered the same area within the code, and editing their variables on a map can lead to issues with powernets and event subsystems which are difficult to debug. - - Unless they require custom placement, when placing the following items use the relevant "[direction] bump" instance, as it has predefined pixel offsets and directions that are standardised: APC, Air alarm, Fire alarm, station intercom, newscaster, extinguisher cabient, light switches. - -- If you are making non-minor edits to an area or room, (non-minor being anything more than moving a few objects or fixing small bugs) then you should ensure the entire area/room is updated to meet these standards. - -- When making a change to an area or room, follow these guidelines: - - - Unless absolutely necessary, do not run pipes (including disposals) under wall turfs. - - **NEVER** run cables under wall turfs. - - Keep floor turf variations to a minimum. Generally, more than 3 floor turf types in one room is bad design. - - Run air pipes together where possible. The first example below is to be avoided, the second is optimal: - - ![image](https://user-images.githubusercontent.com/12197162/120011088-d22c7400-bfd5-11eb-867f-7b137ac5b1b2.png) ![image](https://user-images.githubusercontent.com/12197162/120011126-dfe1f980-bfd5-11eb-96b2-c83238a9cdcf.png) - - - Pipe layouts should be logical and predictable, easy to understand at a glance. Always avoid complex layouts like in this example: - - ![image](https://user-images.githubusercontent.com/12197162/120619480-ecda6f00-c453-11eb-9d9f-abf0d1a99c34.png) - - - Decals are to be used sparingly. Good map design does not require warning tape around everything. Decal overuse contributes to maptick slowdown. - - Every **area** should contain only one APC and air alarm. - - Critical infrastructure rooms (such as the engine, arrivals, and medbay areas) should be given an APC with a larger power cell. - - Every **room** should contain at least one fire alarm, air vent and scrubber, light switch, station intercom, and security camera. - - Intercoms should be set to frequency 145.9, and be speaker ON Microphone OFF. This is so radio signals can reach people even without headsets on. Larger room will require more than one at a time. - - Exceptions can be made to security camera placement for certain rooms, such as the execution room. Larger rooms may require more than one security camera. All security cameras should have a descriptive name that makes it easy to find on a camera console. - - A good example would be the template [Department name] - [Area], so Brig - Cell 1, or Medbay - Treatment Center. Consistency is key to good camera naming. - - Fire alarms should not be placed next to expected heat sources. - - Use the following "on" subtype of vents and scrubbers as opposed to var-editing: `/obj/machinery/atmospherics/unary/vent_scrubber/on` and `/obj/machinery/atmospherics/unary/vent_pump/on` - - Head of staff offices should contain a requests console. - - Electrochromic windows (`/obj/structure/window/reinforced/polarized`) and doors/windoors (using the `/obj/effect/mapping_helpers/airlock/polarized` helper) are preferred over shutters as the method of restricting view to a room through windows. Shutters are sill appropriate in industrial/hazardous areas of the station (engine rooms, HoP line, science test chamber, etc.). - - Electrochromic window/windoor/door sets require a unique ID var, and a window tint button (`/obj/machinery/button/windowtint`) with a matching ID var. The default `range` of the button is 7 tiles but can be amended with a var edit. - - Tiny fans (`/obj/structure/fans/tiny`) can be used to block airflow into problematic areas, but are not a substitute for proper door and firelock combinations. They are useful under blast doors that lead to space when opened. - - Firelocks should be used at area boundaries over doors and windoors, but not windows. Firelocks can also be used to break up hallways at reasonable intervals. - - Double firelocks are not permitted. - - Maintenance access doors should never have firelocks placed over them. - - Windows to secure areas or external areas should be reinforced. Windows in engine areas should be reinforced plasma glass. - - Windows in high security areas, such as the brig, bridge, and head of staff offices, should be electrified by placing a wire node under the window. - - Lights are to be used sparingly, they draw a significant amount of power. - - Ensure door and windoor access is correctly set, this is now done by using access helpers. - - - Multiple accesses can be added to a door by placing multiple access helpers on the same tile. Be sure to pay attention so as to avoid mixing up `all` and `any` subtypes. - - Old doors that use var edited access should be updated to use the correct access helper, and the var edit on the door should be cleaned. - - See [`code\modules\mapping\access_helpers.dm`](../code/modules/mapping/access_helpers.dm) for a list of all access helpers. - - Subtypes of `/obj/effect/mapping_helpers/airlock/access/any` lets anyone with ONE OF THE LISTED ACCESSES open the door. - - Subtypes of `/obj/effect/mapping_helpers/airlock/access/all` requires ALL ACCESSES present to open the door. - - - Departments should be connected to maintenance through a back or side door. This lets players escape and allows antags to break in. - - If this is not possible, departments should have extra entry and exit points. - - Engine areas, or areas with a high probability of receiving explosions, should use reinforced flooring if appropriate. - - External areas, or areas where depressurisation is expected and normal, should use airless turf variants to prevent additional atmospherics load. - - Edits in mapping tools should almost always be possible to replicate in-game. For this reason, avoid stacking multiple structures on the same tile (i.e. placing a light and an APC on the same wall.) - -### Other Notes - -- Bloated code may be necessary to add a certain feature, which means there has to be a judgement over whether the feature is worth having or not. You can help make this decision easier by making sure your code is modular. - -- You are expected to help maintain the code that you add, meaning that if there is a problem then you are likely to be approached in order to fix any issues, runtimes, or bugs. - -- If you used regex to replace code during development of your code, post the regex in your PR for the benefit of future developers and downstream users. - -- All new var/proc names should use the American English spelling of words. This is for consistency with BYOND. - -- All mentions of the company "Nanotrasen" should be written as such - 'Nanotrasen'. Use of CamelCase (NanoTrasen) is no longer proper. - -- If you are making a PR that adds a config option to change existing behaviour, said config option must default to as close to as current behaviour as possible. - -### Dream Maker Quirks/Tricks - -Like all languages, Dream Maker has its quirks, some of them are beneficial to us, like these: - -#### In-To for-loops - -`for(var/i = 1, i <= some_value, i++)` is a fairly standard way to write an incremental for loop in most languages (especially those in the C family), but DM's `for(var/i in 1 to some_value)` syntax is oddly faster than its implementation of the former syntax; where possible, it's advised to use DM's syntax. (Note, the `to` keyword is inclusive, so it automatically defaults to replacing `<=`; if you want `<` then you should write it as `1 to some_value-1`). - -HOWEVER, if either `some_value` or `i` changes within the body of the for (underneath the `for(...)` header) or if you are looping over a list AND changing the length of the list then you can NOT use this type of for-loop! - -### `for(var/A in list)` VS `for(var/i in 1 to length(list))` - -The former is faster than the latter, as shown by the following profile results: [https://file.house/zy7H.png](https://file.house/zy7H.png) - -Code used for the test in a readable format: [https://pastebin.com/w50uERkG](https://pastebin.com/w50uERkG) - -#### Istypeless for loops - -A name for a differing syntax for writing for-each style loops in DM. It's NOT DM's standard syntax, hence why this is considered a quirk. Take a look at this: - -```dm -var/list/bag_of_items = list(sword1, apple, coinpouch, sword2, sword3) -var/obj/item/sword/best_sword -for(var/obj/item/sword/S in bag_of_items) - if(!best_sword || S.damage > best_sword.damage) - best_sword = S -``` - -The above is a simple proc for checking all swords in a container and returning the one with the highest damage, and it uses DM's standard syntax for a for-loop by specifying a type in the variable of the for's header that DM interprets as a type to filter by. It performs this filter using `istype()` (or some internal-magic similar to `istype()` - this is BYOND, after all). This is fine in its current state for `bag_of_items`, but if `bag_of_items` contained ONLY swords, or only SUBTYPES of swords, then the above is inefficient. For example: - -```dm -var/list/bag_of_swords = list(sword1, sword2, sword3, sword4) -var/obj/item/sword/best_sword -for(var/obj/item/sword/S in bag_of_swords) - if(!best_sword || S.damage > best_sword.damage) - best_sword = S -``` - -The above code specifies a type for DM to filter by. - -With the previous example that's perfectly fine, we only want swords, but if the bag only contains swords? Is DM still going to try to filter because we gave it a type to filter by? YES, and here comes the inefficiency. Wherever a list (or other container, such as an atom (in which case you're technically accessing their special contents list, but that's irrelevant)) contains datums of the same datatype or subtypes of the datatype you require for your loop's body, you can circumvent DM's filtering and automatic `istype()` checks by writing the loop as such: - -```dm -var/list/bag_of_swords = list(sword, sword, sword, sword) -var/obj/item/sword/best_sword -for(var/s in bag_of_swords) - var/obj/item/sword/S = s - if(!best_sword || S.damage > best_sword.damage) - best_sword = S -``` - -Of course, if the list contains data of a mixed type then the above optimisation is DANGEROUS, as it will blindly typecast all data in the list as the -specified type, even if it isn't really that type, causing runtime errors (AKA your shit won't work if this happens). - -#### Dot variable - -Like other languages in the C family, DM has a `.` or "Dot" operator, used for accessing variables/members/functions of an object instance. eg: - -```dm -var/mob/living/carbon/human/H = YOU_THE_READER -H.gib() -``` - -However, DM also has a dot _variable_, accessed just as `.` on its own, defaulting to a value of null. Now, what's special about the dot operator is that it is automatically returned (as in the `return` statement) at the end of a proc, provided the proc does not already manually return (`return count` for example.) Why is this special? - -With `.` being everpresent in every proc, can we use it as a temporary variable? Of course we can! However, the `.` operator cannot replace a typecasted variable - it can hold data any other var in DM can, it just can't be accessed as one, although the `.` operator is compatible with a few operators that look weird but work perfectly fine, such as: `.++` for incrementing `.'s` value, or `.[1]` for accessing the first element of `.`, provided that it's a list. - -## Globals versus static - -DM has a var keyword, called global. This var keyword is for vars inside of types. For instance: - -```dm -/mob - var/global/thing = TRUE -``` - -This does NOT mean that you can access it everywhere like a global var. Instead, it means that that var will only exist once for all instances of its type, in this case that var will only exist once for all mobs - it's shared across everything in its type. (Much more like the keyword `static` in other languages like PHP/C++/C#/Java) - -Isn't that confusing? - -There is also an undocumented keyword called `static` that has the same behaviour as global but more correctly describes BYOND's behaviour. Therefore, we always use static instead of global where we need it, as it reduces suprise when reading BYOND code. - -### Global Vars - -All new global vars must use the defines in [`code/__DEFINES/_globals.dm`](../code/__DEFINES/_globals.dm). Basic usage is as follows: - -To declare a global var: - -```dm -GLOBAL_VAR(my_global_here) -``` - -To access it: - -```dm -GLOB.my_global_here = X -``` - -There are a few other defines that do other things. `GLOBAL_REAL` shouldn't be used unless you know exactly what you're doing. -`GLOBAL_VAR_INIT` allows you to set an initial value on the var, like `GLOBAL_VAR_INIT(number_one, 1)`. -`GLOBAL_LIST_INIT` allows you to define a list global var with an initial value. Etc. - -### GitHub Staff - -There are 3 roles on the GitHub, these are: - -- Headcoder -- Commit Access -- Review Team - -Each role inherits the lower role's responsibilities (IE: Headcoders also have commit access, and members of commit access are also part of the review team) - -`Headcoders` are the overarching "administrators" of the repository. People included in this role are: - -- [farie82](https://github.com/farie82) -- [S34N](https://github.com/S34NW) -- [SteelSlayer](https://github.com/SteelSlayer) - ---- - -`Commit Access` members have write access to the repository and can merge your PRs. People included in this role are: - -- [AffectedArc07](https://github.com/AffectedArc07) -- [Burzah](https://github.com/Burzah) -- [Charliminator](https://github.com/hal9000PR) -- [Contrabang](https://github.com/Contrabang) -- [DGamerL](https://github.com/DGamerL) -- [lewcc](https://github.com/lewcc) - ---- - -`Review Team` members are people who are denoted as having reviews which can affect mergeability status. People included in this role are: - -- [Burzah](https://github.com/Burzah) -- [Charliminator](https://github.com/hal9000PR) -- [Contrabang](https://github.com/Contrabang) -- [DGamerL](https://github.com/DGamerL) -- [FunnyMan3595](https://github.com/FunnyMan3595) -- [Henri215](https://github.com/Henri215) -- [lewcc](https://github.com/lewcc) -- [Sirryan2002](https://github.com/Sirryan2002) -- [Warriorstar](https://github.com/warriorstar-orion) - ---- - -Full information on the GitHub contribution workflow & policy can be found at [https://www.paradisestation.org/dev/policy/](https://www.paradisestation.org/dev/policy/) - -### PR Status - -Status of your pull request will be communicated via PR labels. This includes: - -- `Status: Awaiting type assignment` - This will be displayed when your PR is awaiting an internal type assignment (for Fix, Balance, Tweak, etc) -- `Status: Awaiting approval` - This will be displayed if your PR is waiting for approval from the specific party, be it Balance or Design. Fixes & Refactors should never have this label -- `Status: Awaiting review` - This will be displayed when your PR has passed the design vote and is now waiting for someone in the review team to approve it -- `Status: Awaiting merge` - Your PR is done and is waiting for someone with commit access to merge it. **Note: Your PR may be delayed if it is pending testmerge or in the mapping queue** diff --git a/.github/DOWNLOADING.md b/.github/DOWNLOADING.md deleted file mode 100644 index 5787b807775c..000000000000 --- a/.github/DOWNLOADING.md +++ /dev/null @@ -1,105 +0,0 @@ -# Info - -This document contains all the relevant information for downloading and running your own ParaCode server. - -## GETTING THE CODE - -The simplest way to obtain the code is using the github .zip feature. - -Click [here](https://github.com/ParadiseSS13/Paradise/archive/master.zip) to get the latest code as a .zip file, then unzip it to wherever you want. - -The more complicated and easier to update method is using git. -You'll need to download git or some client from [here](http://git-scm.com/). -When that's installed, right click in any folder and click on "Git Bash". -When that opens, type in: - -```sh - git clone https://github.com/ParadiseSS13/Paradise.git --depth 1 -``` - -(hint: hold down ctrl and press insert to paste into git bash) - -This will take a while to download (it is the entire repo + history, not just a snapshot), but it provides an easier method for updating. - -## INSTALLATION - -First-time installation should be fairly straightforward. -First, you'll need BYOND installed. We're going to assume you already did this - -This is a sourcecode-only release, so the next step is to compile the server files. -Open paradise.dme by double-clicking it, open the Build menu, and click compile. -This'll take a little while, and if everything's done right, -you'll get a message like this: - -```sh - saving paradise.dmb (DEBUG mode) - paradise.dmb - 0 errors, 0 warnings -``` - -If you see any errors or warnings, -something has gone wrong - possibly a corrupt download or the files extracted wrong, -or a code issue on the main repo. Feel free to ask on Discord. - -Once that's done, open up the config folder. -Firstly, you will want to copy `config.toml` from the example folder into the regular config folder. -You'll want to edit the `url_configuration` section of `config.toml` to set `reboot_url` to your server location, -so that all your players don't get disconnected at the end of each round. -It's recommended you don't turn on the gamemodes with probability 0, -as they have various issues and aren't currently being tested, -so they may have unknown and bizarre bugs. - -You'll also want to edit the `admin_configuration` section of `config.toml` to remove the default admins and add your own. -If you are connecting from localhost to your own test server, you should automatically be admin. -"Head of Staff" is the highest level of access, and the other recommended admin levels for now are -"Game Admin". The format is: - -```toml -# Note that your ranks must be cased properly, usernames can be normal keys or ckey -admin_assignments = [ - {ckey = "Admin1", rank = "Hosting Provider"}, - {ckey = "Admin2", rank = "Game Admin"}, -] -``` - -You can define your own ranks in the admin section of `config.toml`. - -If you want to run a production scale server, we highly recommend using database administrators. - -Finally, to start the server, -run Dream Daemon and enter the path to your compiled paradise.dmb file. -Make sure to set the port to the one you specified in the config.txt, -and set the Security box to 'Trusted'. -Then press GO and the server should start up and be ready to join. - -## Installation (Linux) - -The code is fully able to run on linux, however windows is still the recommended platform. The libraries we use for external functions (RUSTG and MILLA) require some extra dependencies. - -For debian, please download the latest RUSTG release from [https://github.com/ParadiseSS13/rust-g](https://github.com/ParadiseSS13/rust-g), run the following: `apt-get install libssl-dev:i386 pkg-config:i386 zlib1g-dev:i386`. - -After installing these packages, RUSTG should be able to build and function as intended. Build instructions are on the RUSTG page. We assume that if you are hosting on linux, you know what you are doing. - -Once you've built RUSTG, you can build MILLA similarly, just go into the `milla/` directory and run `cargo build --release --target=i686-unknown-linux-gnu`. - -## UPDATING - -If you used the zip method, -you'll need to download the zip file again and unzip it somewhere else, -and then copy the `config` and `data` folders over. - -If you used the git method, you simply need to type this in to git bash: - -```sh - git pull --depth 1 -``` - -When you have done this, you'll need to recompile the code, but then it should work fine and be up to date with the live server. - -## SQL Setup - -The SQL backend is required for storing character saves, preferences, administrative data, and many other things. -We recommend running a database if your server is going to be used as more than just a local test server. -Your SQL server details go in the `database_configuration` section of `config.toml`, -and the SQL schema can be found in `SQL/paradise_schema.sql`. -More detailed setup instructions are located on our wiki: -https://www.paradisestation.org/wiki/index.php/Setting_up_the_Database diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f4dbad5742aa..5a0d21b14aac 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -25,7 +25,7 @@ ### Declaration -- [ ] I confirm that I either do not require [pre-approval](https://github.com/ParadiseSS13/Paradise/blob/master/CODE_OF_CONDUCT.md#types-of-changes-that-need-approval) for this PR, or I have obtained such approval and have included a screenshot to demonstrate this below. +- [ ] I confirm that I either do not require [pre-approval](https://github.com/ParadiseSS13/Paradise/blob/master/docs/CODE_OF_CONDUCT.md#types-of-changes-that-need-approval) for this PR, or I have obtained such approval and have included a screenshot to demonstrate this below.
diff --git a/.github/SETTING_UP_GIT.md b/.github/SETTING_UP_GIT.md deleted file mode 100644 index 4eca3d017cb2..000000000000 --- a/.github/SETTING_UP_GIT.md +++ /dev/null @@ -1,178 +0,0 @@ -# Setting Up Git (Windows/Mac Only) - -## Linux users - -You will want to install the `git` package from your distribution's respective -package manager, whatever that may be. - ---- - -## Windows user -## Git-SCM -So you want to start contributing to Paradise? Where, well do you start? -First off, you will need some tools to work with Git, the -[Version Control System](https://en.wikipedia.org/wiki/Version_control) -that we operate off. There are many choices out there for dealing with Git, -including GitHub for Desktop- However, all of them are based off of the same -command-line tools. As such, we advise, rather than using a GUI Program, you -learn how to use the command line. - -An important note here is that the Git-SCM package for Windows includes -some GUI-based software in it. This can be used for easily monitoring the -state of your current branch, without downloading multiple different tools. - -## Installing -The version of Git that we will be using is available at https://git-scm.com/. -There should be four big orange buttons on the front page of the site when you -go there. You will want to click on the one labeled "Downloads". -![https://i.imgur.com/a6tX7IV.png](https://i.imgur.com/a6tX7IV.png) - -From here, you will want to select your operating system in this box. -![https://i.imgur.com/Ee4wVsF.png](https://i.imgur.com/Ee4wVsF.png) - -Download the `setup` version, which should automatically start downloading when -you select your operating system. Place it wherever you prefer to store your -downloaded files. You should end up with a file that looks like -`Git-version.number.here-32/64-bit.exe`. You should run this executable file. -![https://i.imgur.com/jnbodzV.png](https://i.imgur.com/jnbodzV.png) - -Click Next, after reading the GNU-GPL license if you wish to do so, which will -bring you to this screen. -![https://i.imgur.com/cl9RodU.png](https://i.imgur.com/cl9RodU.png) - -Your default options may be different than this- You'll want to amend them to -match this screenshot. (Future proofing: `Windows Explorer integration` partially -selected, just for `Git Bash Here`, `Git LFS (Large File Support)` checked, -and `Associate .git* configuration files with the default text editor` checked. -All other boxes should be left unchecked). Click next. The next screen is very -important. -![https://i.imgur.com/6ii7aRO.png](https://i.imgur.com/6ii7aRO.png) - -The screen should say `Adjusting your PATH environment`. You will definitely want -to select `Use Git from Git Bash only`- This is the safest option, and will not -change your PATH variables at all. The disadvantage of this is that any future -terminal emulators will be unable to use Git, as will the windows command prompt. - -Select `Use the OpenSSL library` for `Choosing HTTPS transport backend`. - -For Windows, you will also get the following screen: -![https://i.imgur.com/jOZJWvO.png](https://i.imgur.com/jOZJWvO.png) - -You will want to select "Checkout Windows-style, commit Unix-style line endings" -for working with our repository. - -If you get the choice between MinTTY and Windows' default console window, select -MinTTY. -![https://i.imgur.com/ZdZU0NB.png](https://i.imgur.com/ZdZU0NB.png) - -For `configuring extra options`, select `Enable file system caching` and -`Enable Git Credential Manager`, leaving `Enable symbolic links` disabled. -![https://i.imgur.com/6gspQAL.png](https://i.imgur.com/6gspQAL.png) - -From there, just hit `Install`. - -## Configuring - -We are going to configure Git for password-less SSH authentication for GitHub. -There is a simple way to make this require that you instead just enter your -password once upon opening a terminal for the first time running the program -after a restart, which we will cover when it is applicable. - -This guide is mostly copy-pasted from -`https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/`, - So you can view that for further instruction. - -As we are working with a completely fresh install of Git, we can skip over some -steps. You will want to open Git Bash, then use the text below, changing the email -to the one you use for GitHub. -```bash -ssh-keygen -t rsa -b 4096 -C "your_email@example.com" -``` -This will create a new SSH private/public key, using the email as a label. -```bash -Generating public/private rsa key pair. -``` - -When you're prompted to `Enter a file in which to save the key`, you will want -to just press enter, to accept the default file location. We will rename the key -later. - -Remember that I mentioned you could either do password-less SSH authentication, -or require a password one time per session? This is the point at which you -choose this, with the `Enter passphrase (empty for no passphrase):` prompt. If -you do not care about having a password on the private key (which you should -never ever distribute beyond your own computers on secure mediums), you can just -hit enter twice to completely skip setting a password on the key. Otherwise, enter -your desired password. - -You will now want to go to your home directory `C:/Users/YourName` and open the -`.ssh` folder. You should see two files in here, `id_rsa` and `id_rsa.pub`. Rename -them both to `your_github_username_rsa` and `your_github_username_rsa.pub`. -You'll then want to create two new folders, `public` and `private`. Put the `.pub` -file into `public`, then put the other one into `private`. - -As we are not using GitHub for Desktop, we will need to now setup SSH-agent. -Start by typing the command `vim ~/.bashrc`. This will open the Vi IMproved text -editor, which is one of the ones that comes with Git by default. It is entirely -built into the shell, and the interface will take a bit of getting used to. -You will want to hit `i`, to go into `Insert Mode`. This will produce a blinking -cursor on the top section, what you would expect for a text editor. You may now -type what you like, or right click and paste to paste from your clipboard. -Paste the following into the file: -```bash -#!/bin/bash - -SSH_ENV=$HOME/.ssh/environment - -function add_keys { - ssh-add ~/.ssh/private/* -} - -function start_agent { - echo "Initializing new SSH agent..." - /usr/bin/ssh-agent | sed 's/^echo/#echo' > ${SSH_ENV} - echo Suceeded - chmod 600 ${SSH_ENV} - . ${SSH_ENV} > /dev/null - add_keys; -} - -# Source SSH settings, if applicable - -if [ -f "${SSH_ENV}" ]; then - . ${SSH_ENV} > /dev/null - ps -ef | grep ${SSH_AGENT_PID} | grep ssh-agent$ > /dev/null || { - start_agent; - } -else - start_agent; -fi -``` - -Hit escape, then hit `:` followed by `wq` and enter. This is, to Vi, Insert Mode -to command mode (`ESC`), Command start `:`, and `w`rite `q`uit `ENTER` submit. - -Restart Git Bash- If you set a password on your SSH key, it will ask it for you -when it starts. Otherwise, it should just say `Identity added: ...`. You can see -all of the identities you have by running `ssh-add -l` in the shell at any time. - -You will now want to follow the instructions in this article to add the SSH key -to your GitHub profile: -https://help.github.com/articles/adding-a-new-ssh-key-to-your-github-account/ - -Obviously, for the first step, use the key we renamed and put into the `public` -folder, not the one in the `private` folder. - -To test that this all worked, you should run `ssh -T git@github.com`. If there -are any warning messages about authenticity, type `yes` followed by enter. You -should then see: -``` -Hi username! You've successfully authenticated, but GitHub does not -provide shell access. -``` - -If not, follow the troubleshooting directions in this article: -https://help.github.com/articles/error-permission-denied-publickey/ - - -Congratulations, you have now setup Git for Windows. \ No newline at end of file diff --git a/.github/TICK_ORDER.md b/.github/TICK_ORDER.md deleted file mode 100644 index c6e27741e2c8..000000000000 --- a/.github/TICK_ORDER.md +++ /dev/null @@ -1,21 +0,0 @@ -The byond tick proceeds as follows: -1. procs sleeping via walk() are resumed (I don't know why these are first) - -2. normal sleeping procs are resumed, in the order they went to sleep in the first place, this is where the MC wakes up and processes subsystems. a consequence of this is that the MC almost never resumes before other sleeping procs, because it only goes to sleep for 1 tick 99% of the time, and 99% of procs either go to sleep for less time than the MC (which guarantees that they entered the sleep queue earlier when its time to wake up) and/or were called synchronously from the MC's execution, almost all of the time the MC is the last sleeping proc to resume in any given tick. This is good because it means the MC can account for the cost of previous resuming procs in the tick, and minimizes overtime. - -3. control is passed to byond after all of our code's procs stop execution for this tick - -4. a few small things happen in byond internals - -5. SendMaps is called for this tick, which processes the game state for all clients connected to the game and handles sending them changes -in appearances within their view range. This is expensive and takes up a significant portion of our tick, about 0.45% per connected player -as of 3/20/2022. meaning that with 50 players, 22.5% of our tick is being used up by just SendMaps, after all of our code has stopped executing. That's only the average across all rounds, for most high-pop rounds it can look like 0.6% of the tick per player, which is 30% for 50 players. - -6. After SendMaps ends, client verbs sent to the server are executed, and its the last major step before the next tick begins. -During the course of the tick, a client can send a command to the server saying that they have executed any verb. The actual code defined -for that /verb/name() proc isnt executed until this point, and the way the MC is designed makes this especially likely to make verbs -"overrun" the bounds of the tick they executed in, stopping the other tick from starting and thus delaying the MC firing in that tick. - -The master controller can derive how much of the tick was used in: procs executing before it woke up (because of world.tick_usage), and SendMaps (because of world.map_cpu, since this is a running average you cant derive the tick spent on maptick on any particular tick). It cannot derive how much of the tick was used for sleeping procs resuming after the MC ran, or for verbs executing after SendMaps. - -It is for these reasons why you should heavily limit processing done in verbs, while procs resuming after the MC are rare, verbs are not, and are much more likely to cause overtime since they're literally at the end of the tick. If you make a verb, try to offload any expensive work to the beginning of the next tick via a verb management subsystem. diff --git a/.gitignore b/.gitignore index 6f6c2dd9c369..e1a2a1e9325b 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,6 @@ $RECYCLE.BIN # Rust output. /milla/target/* + +# mkdocs output. +site diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 43c12ed66f8a..000000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,55 +0,0 @@ -# Important - -The Paradise GitHub is not exempt from Paradise Station community and server rules, especially rules 0, 1, and 4. An inability to abide by the rules on the GitHub will result in disciplinary action up to, or including, a repository ban. - -## General Expectations - -### PR Discussion - -Comments on Pull Requests should remain relevant to the PR in question and not derail discussions. - -Under no circumstances are users to be attacked for their ideas or contributions. While constructive criticism is encouraged, toxicity or general mean-spirited behaviour will not be tolerated. All participants on a given PR or issue are expected to be civil. Failure to do so will result in disciplinary action. - -"Merge-nagging" or similar behaviour is not acceptable. Comments of this nature will result in warnings or an outright ban from the repository depending on general behaviour. - -If you exhibit behaviour that's considered to be a net-negative to the community (offensive commentary, repeat violations, constant nagging, personal attacks, etc.), you may be banned from other Paradise services (Discord, forums, server, wiki, etc.) - -Headcoders reserve the right to permanently revoke access from the repository if your behaviour is considered to be a net negative. - -### PR Approval/Objection Info - -Headcoders (who will take into account the votes from the relevant teams) have the final say on Pull Requests. While thumbsup/thumbsdown reaction ratios are generally taken into account, they do not dictate whether or not a PR will be merged. - -After a twenty four hour minimum waiting period, Pull Requests can be merged once they receive approval from the relevant team. An exception is made for refactors and fixes, which may be merged by any member with commit access' discretion with no waiting period. - -While normally provided, voting team members are not obligated to publicly state their objections to a Pull Request. Attacking or berating a voting team member over an objection will not be tolerated. Additionally, whining over the closure of a PR, the existence of an objection, or similar behaviour, will not be tolerated. - -Headcoders may close your PR at their discretion if your PR history has little focus on improving repo maintainability (ie: making nothing but 20 balance or feature PRs). Likewise, balance PRs may be closed if the PR author has little-to-no time played on the server. This is to ensure balance changes are made by people actually in-touch with the server atmosphere. - -### PR Expectations - -Contributors may only have a maximum of **2** feature or balance Pull Requests open at any given time. Any additional Pull Requests beyond this limit will be closed at the discretion of the Headcoders. The Headcoders may grant an exemption to this limit on a case-by-case basis, as the need arises. - -All Pull Requests are expected to be tested prior to submission. If a submitted Pull Request fails to pass CI checks, the likelihood of it being merged will be significantly lower. If you can't take the time to compile/test your Pull Request, do not expect a warm reception. - -Barring highly specific circumstances (such as single line changes, submissions from advanced users, or changes to repo documentation), we will not accept Pull Requests utilising the web editor. - -Pull Requests regarding heavy-handed nerfs, particularly immediately after said mechanic was used, will be tagged with `I ded pls nerf`. A bad experience with a particular mechanic is not a justification for nerfing it. - -Reactionary revert PRs are not tolerated under any circumstances. Posting a revert immediately after a Pull Request is merged will result in a repoban. - -It is expected that contributors discuss larger changes on the [Paradise Station forums](https://www.paradisestation.org/forum/91-code-discussion/), [GitHub discussions tab](https://github.com/ParadiseSS13/Paradise/discussions), or the [Discord project-discussion forum](https://discord.com/channels/145533722026967040/1110966752898207824) prior to starting work on a Pull Request. The amount of time spent on any given Pull Request is not relevant. Repo staff are not responsible for contributors wasting their time creating features nobody asked for. Be sure to inform the corresponding teams about the forum post or discussion. - -For changes to content listed below, contributors **must** obtain approval from a headcoder or a member of either the balance, design, mapping, or sprite team (depending on which teams are relevant to the changes) before opening their Pull Request. This approval must be displayed in the Pull Request description body in the form of a screenshot. The Headcoders may grant an exemption to this requirement on a case-by-case basis, as the need arises. - - - - -> [!IMPORTANT] -> ### Currently, changes to the following types of content requires pre-approval: -> - **Security content (excluding fixes, code improvement, refactors, sprites, and mapping changes)** -> - **Antagonist content (excluding fixes, code improvement, refactors, sprites, and mapping changes)** -> - **Species content (excluding fixes, code improvement, and refactors)** -> - **Large changes (for example PRs that touch multiple systems, many files, many lines of code)** -> - **Changes that might be controversial** -> - **Changes with wide-ranging balance or design implications** diff --git a/README.md b/README.md index 4976414fc67e..666b84e5f44f 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ > [!TIP] > Want to contribute for the first time but unsure where to start?
> Join our Discord and check out the [#coding_chat](https://discord.com/channels/145533722026967040/145700319819464704) channel for helpful links and advice!
-> Alternatively, have a look at our community maintained [Guide to Contributing](https://paradisestation.org/wiki/index.php?title=Guide_to_Contributing) +> Alternatively, have a look at our community maintained [Getting Started Guide](./docs/contributing/getting_started.md) # Useful Documents and Links @@ -64,15 +64,15 @@ This reference site by the creators of BYOND details information on the DM language, the syntax used, functionality of native procs, and a lot more. This is always useful to have on hand when contributing. -- ### [Autodocumentation Guide](.github/AUTODOC_GUIDE.md) +- ### [Autodocumentation Guide](./docs/references/autodoc.md) This guide shows you how to leave code comments that comply with "autodocumentation", a system designed to make everyone's lives easier when reading or reviewing code! -- ### [Code of Conduct](./CODE_OF_CONDUCT.md) +- ### [Code of Conduct](./docs/CODE_OF_CONDUCT.md) All contributors are expected to read our Code of Conduct before they take part in our community. -- ### [Contribution Guide](.github/CONTRIBUTING.md) +- ### [Contribution Guide](./docs/CONTRIBUTING.md) Not sure how to take part and contribute? This guide gives an overview of how to make comments, pull requests, and open issues. diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md new file mode 100644 index 000000000000..4084cf286e70 --- /dev/null +++ b/docs/CODE_OF_CONDUCT.md @@ -0,0 +1,112 @@ +# Contributor Code of Conduct + +## Important + +The Paradise GitHub is not exempt from [Paradise Station community and server rules][rules], +especially rules 0, 1, and 4. An inability to abide by the rules on the +GitHub will result in disciplinary action up to, or including, a repository ban. + +[rules]: https://www.paradisestation.org/rules/ + +## General Expectations + +### PR Discussion + +Comments on Pull Requests should remain relevant to the PR in question and not +derail discussions. + +Under no circumstances are users to be attacked for their ideas or +contributions. While constructive criticism is encouraged, toxicity or general +mean-spirited behaviour will not be tolerated. All participants on a given PR or +issue are expected to be civil. Failure to do so will result in disciplinary +action. + +"Merge-nagging" or similar behaviour is not acceptable. Comments of this nature +will result in warnings or an outright ban from the repository depending on +general behaviour. + +If you exhibit behaviour that's considered to be a net-negative to the community +(offensive commentary, repeat violations, constant nagging, personal attacks, +etc.), you may be banned from other Paradise services (Discord, forums, server, +wiki, etc.) + +Headcoders reserve the right to permanently revoke access from the repository if +your behaviour is considered to be a net negative. + +### PR Approval/Objection Info + +Headcoders (who will take into account the votes from the relevant teams) have +the final say on Pull Requests. While thumbs-up/thumbs-down reaction ratios are +generally taken into account, they do not dictate whether or not a PR will be +merged. + +After a twenty four hour minimum waiting period, Pull Requests can be merged +once they receive approval from the relevant team. An exception is made for +refactors and fixes, which may be merged by any member with commit access' +discretion with no waiting period. + +While normally provided, voting team members are not obligated to publicly state +their objections to a Pull Request. Attacking or berating a voting team member +over an objection will not be tolerated. Additionally, whining over the closure +of a PR, the existence of an objection, or similar behaviour, will not be +tolerated. + +Headcoders may close your PR at their discretion if your PR history has little +focus on improving repo maintainability (ie: making nothing but 20 balance or +feature PRs). Likewise, balance PRs may be closed if the PR author has +little-to-no time played on the server. This is to ensure balance changes are +made by people actually in-touch with the server atmosphere. + +### PR Expectations + +Contributors may only have a maximum of **2** feature or balance Pull Requests +open at any given time. Any additional Pull Requests beyond this limit will be +closed at the discretion of the Headcoders. The Headcoders may grant an +exemption to this limit on a case-by-case basis, as the need arises. + +All Pull Requests are expected to be tested prior to submission. If a submitted +Pull Request fails to pass CI checks, the likelihood of it being merged will be +significantly lower. If you can't take the time to compile/test your Pull +Request, do not expect a warm reception. + +Barring highly specific circumstances (such as single line changes, submissions +from advanced users, or changes to repo documentation), we will not accept Pull +Requests utilising the web editor. + +Pull Requests regarding heavy-handed nerfs, particularly immediately after said +mechanic was used, will be tagged with `I ded pls nerf`. A bad experience with a +particular mechanic is not a justification for nerfing it. + +Reactionary revert PRs are not tolerated under any circumstances. Posting a +revert immediately after a Pull Request is merged will result in a repo-ban. + +It is expected that contributors discuss larger changes on the +[Paradise Station forums](https://www.paradisestation.org/forum/91-code-discussion/), +[GitHub discussions tab](https://github.com/ParadiseSS13/Paradise/discussions), +or the [Discord project-discussion forum](https://discord.com/channels/145533722026967040/1110966752898207824) +prior to starting work on a Pull Request. The amount of time spent on any given +Pull Request is not relevant. Repo staff are not responsible for contributors +wasting their time creating features nobody asked for. Be sure to inform the +corresponding teams about the forum post or discussion. + +For changes to content listed below, contributors **must** obtain approval from +a headcoder or a member of either the balance, design, mapping, or sprite team +(depending on which teams are relevant to the changes) before opening their Pull +Request. This approval must be displayed in the Pull Request description body in +the form of a screenshot. The Headcoders may grant an exemption to this +requirement on a case-by-case basis, as the need arises. + + + + + +> [!IMPORTANT] +> +>

Currently, changes to the following types of content requires pre-approval:

+> +> - **Security content (excluding fixes, code improvement, refactors, sprites, and mapping changes)** +> - **Antagonist content (excluding fixes, code improvement, refactors, sprites, and mapping changes)** +> - **Species content (excluding fixes, code improvement, and refactors)** +> - **Large changes (for example PRs that touch multiple systems, many files, many lines of code)** +> - **Changes that might be controversial** +> - **Changes with wide-ranging balance or design implications** diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 000000000000..b3b15a1ad335 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,226 @@ +# Contributing Guidelines + +This is the contribution guide for Paradise Station. These guidelines apply to +both new issues and new pull requests. If you are making a pull request, please +refer to the [Pull request](#pull-requests) section, and if you are making an +issue report, please refer to the [Issue Report](#issues) section. + +## Commenting + +If you comment on an active pull request or issue report, make sure your comment +is concise and to the point. Comments on issue reports or pull requests should +be relevant and friendly, not attacks on the author or adages about something +minimally relevant. If you believe an issue report is not a "bug", please point +out specifically and concisely your reasoning in a comment on the issue itself. + +Comments on Pull Requests and Issues should remain relevant to the subject in +question and not derail discussions. + +Under no circumstances are users to be attacked for their ideas or +contributions. All participants on a given PR or issue are expected to be civil. +Failure to do so will result in disciplinary action. + +For more details, see the [Code of Conduct](./CODE_OF_CONDUCT.md). + +## Issues + +The Issues section is not a place to request features, or ask for things to be +changed because you think they should be that way. The Issues section is +specifically for reporting bugs in the code. + +Issue reports should be as detailed as possible, and if applicable, should +include instructions on how to reproduce the bug. + +## Pull Requests + +Players are welcome to participate in the development of this fork and submit +their own pull requests. If the work you are submitting is a new feature, or +affects balance, it is strongly recommended you get approval/traction for it +from our forums before starting the actual development. + +It is expected that all code contributors read and understand the +[Guide to Quality PRs](./contributing/quality_prs.md). + +Keep your pull requests atomic. Each pull request should strive to address one +primary goal, and should not include fixes or changes that aren't related to the +main purpose of the pull request. Unrelated changes should be applied in new +pull requests. In case of mapping PRs that add features - consult a member of +the development team on whether it would be appropriate to split up the PR to +add the feature to multiple maps individually. + +Document and explain your pull requests thoroughly. Failure to do so will delay +a PR as we question why changes were made. This is especially important if +you're porting a PR from another codebase (i.e. /tg/station) and divert from the +original. Explaining with single comment on why you've made changes will help us +review the PR faster and understand your decision making process. + +Any pull request must have a changelog. This is to allow us to know when a PR is +deployed on the live server. Inline changelogs are supported through the format +described [here](#using-the-changelog). + +Pull requests should not have any merge commits except in the case of fixing +merge conflicts for an existing pull request. New pull requests should not have +any merge commits. Use `git rebase` or `git reset` to update your branches, not +`git pull`. + +Please explain why you are submitting the pull request, and how you think your +change will be beneficial to the game. Failure to do so will be grounds for +rejecting the PR. + +If your pull request is not finished, make sure it is at least testable in a +live environment. Pull requests that do not at least meet this requirement may +be closed at maintainer discretion. You may request a maintainer reopen the pull +request when you're ready, or make a new one. + +While we have no issue helping contributors (and especially new contributors) +bring reasonably sized contributions up to standards via the pull request review +process, larger contributions are expected to pass a higher bar of completeness +and code quality _before_ you open a pull request. Maintainers may close such +pull requests that are deemed to be substantially flawed. You should take some +time to discuss with maintainers or other contributors on how to improve the +changes. + +By ticking or leaving ticked the option "Allow edits and access to secrets by +maintainers", either when making a PR or at any time thereafter, you give +permission for repository maintainers to push changes to your branch without +explicit permission. Repository maintainers will avoid doing this unless +necessary, and generally should only use it to apply a merge upstream/master, +rebuild TGUI, deconflict maps, or other minor changes required shortly before a +PR is to be merged. More extensive changes such as force-pushes to your branch +require explicit permission from the PR author each time such a change needs to +be made. + +### Using The Changelog + +- The tags able to be used in the changelog are: `add/soundadd/imageadd`, + `del/sounddel/imagedel`, `tweak`, `fix`, `wip`, `spellcheck`, and + `experiment`. +- Without specifying a name it will default to using your GitHub name. Some + examples include: + +```txt + :cl: + add: The ability to change the color of wires + del: Deleted depreciated wire merging now handled in parent + fix: Moving wires now follows the user input instead of moving the stack + /:cl: +``` + +```txt + :cl: UsernameHere + spellcheck: Fixes some misspelled words under Using Changelog + /:cl: +``` + +### PR Status + +Status of your pull request will be communicated via PR labels. This includes: + +- `Status: Awaiting type assignment` - This will be displayed when your PR is + awaiting an internal type assignment (for Fix, Balance, Tweak, etc) +- `Status: Awaiting approval` - This will be displayed if your PR is waiting for + approval from the specific party, be it Balance or Design. Fixes & Refactors + should never have this label +- `Status: Awaiting review` - This will be displayed when your PR has passed the + design vote and is now waiting for someone in the review team to approve it +- `Status: Awaiting merge` - Your PR is done and is waiting for someone with + commit access to merge it. **Note: Your PR may be delayed if it is pending + testmerge or in the mapping queue** + +### Mapping Standards + +All PRs which modify maps are expected to follow all of our +[mapping requirements](./mapping/requirements.md). + +## Modifying MILLA + +Our atmos engine, MILLA, is in the `milla/` directory. It's written in Rust for +performance reasons, which means it's not compiled the same way as the rest of +the code. If you're on Windows, you get a pre-built copy by default. If you're +on Linux, you built one already to run the server. + +If you make changes to MILLA, you'll want to rebuild. This will be very similar +to RUSTG: https://github.com/ParadiseSS13/rust-g The only difference is that you +run `cargo` from the `milla/` directory, and don't need to specify +`--all-features` (though it doesn't hurt). + +The server will automatically detect that you have a local build, and use that +over the default Windows one. + +When you're ready to make a PR, please DO NOT modify `milla.dll` or +`tools/ci/libmilla_ci.so`. Leave "Allow edits and access to secrets by +maintainers" enabled, and post a comment on your PR saying `!build_milla`. A bot +will automatically build them for you and update your branch. + +## Other Notes + +- Bloated code may be necessary to add a certain feature, which means there has + to be a judgement over whether the feature is worth having or not. You can + help make this decision easier by making sure your code is modular. + +- You are expected to help maintain the code that you add, meaning that if there + is a problem then you are likely to be approached in order to fix any issues, + runtimes, or bugs. + +- If you used regex to replace code during development of your code, post the + regex in your PR for the benefit of future developers and downstream users. + +- All new var/proc names should use the American English spelling of words. This + is for consistency with BYOND. + +- All mentions of the company "Nanotrasen" should be written as such - + 'Nanotrasen'. Use of CamelCase (NanoTrasen) is no longer proper. + +- If you are making a PR that adds a config option to change existing behaviour, + said config option must default to as close to as current behaviour as + possible. + +## GitHub Staff + +There are three roles on the GitHub: + +- Headcoder +- Commit Access +- Review Team + +Each role inherits the lower role's responsibilities (IE: Headcoders also have +commit access, and members of commit access are also part of the review team) + +`Headcoders` are the overarching "administrators" of the repository. People +included in this role are: + +- [farie82](https://github.com/farie82) +- [S34N](https://github.com/S34NW) +- [SteelSlayer](https://github.com/SteelSlayer) + +--- + +`Commit Access` members have write access to the repository and can merge your +PRs. People included in this role are: + +- [AffectedArc07](https://github.com/AffectedArc07) +- [Burzah](https://github.com/Burzah) +- [Charliminator](https://github.com/hal9000PR) +- [Contrabang](https://github.com/Contrabang) +- [DGamerL](https://github.com/DGamerL) +- [lewcc](https://github.com/lewcc) + +--- + +`Review Team` members are people who are denoted as having reviews which can +affect mergeability status. People included in this role are: + +- [Burzah](https://github.com/Burzah) +- [Charliminator](https://github.com/hal9000PR) +- [Contrabang](https://github.com/Contrabang) +- [DGamerL](https://github.com/DGamerL) +- [FunnyMan3595](https://github.com/FunnyMan3595) +- [Henri215](https://github.com/Henri215) +- [lewcc](https://github.com/lewcc) +- [Sirryan2002](https://github.com/Sirryan2002) +- [Warriorstar](https://github.com/warriorstar-orion) + +--- + +Full information on the GitHub contribution workflow & policy can be found at +. diff --git a/docs/coding/coding_requirements.md b/docs/coding/coding_requirements.md new file mode 100644 index 000000000000..151f26bc0d38 --- /dev/null +++ b/docs/coding/coding_requirements.md @@ -0,0 +1,867 @@ +# Coding Requirements + +Coders are expected to follow these specifications in order to make everyone's +lives easier. It'll save both your time and ours, by making sure you don't have +to make any changes and we don't have to ask you to. + +## Object Oriented Code + +As BYOND's Dream Maker (henceforth "DM") is an object-oriented language, code +must be object-oriented when possible in order to be more flexible when adding +content to it. If you don't know what "object-oriented" means, we highly +recommend you do some light research to grasp the basics. + +## Use absolute pathing + +DM will allow you nest almost any type keyword into a block, as in the following: + +```dm +datum + datum1 + var + varname1 = 1 + varname2 + static + varname3 + varname4 + proc + proc1() + code + proc2() + code + + datum2 + varname1 = 0 + proc + proc3() + code + proc2() + ..() + code +``` + +The use of this format is **not** allowed in this project, as it makes finding +definitions via full text searching next to impossible. The only exception is +the variables of an object may be nested to the object, but must not nest +further. + +The previous code made compliant: + +```dm +/datum/datum1 + var/varname1 = 1 + var/varname2 + var/static/varname3 + var/static/varname4 + +/datum/datum1/proc/proc1() + code + +/datum/datum1/proc/proc2() + code + +/datum/datum1/datum2 + varname1 = 0 + +/datum/datum1/datum2/proc/proc3() + code + +/datum/datum1/datum2/proc2() + ..() + code +``` + +## Do not compare boolean values to `TRUE` or `FALSE` + +Do not compare boolean values to `TRUE` or `FALSE`. For `TRUE` you should just +check if there's a value in that address. For `FALSE` you should use the `!` +operator. An exception is made to this when working with JavaScript or other +external languages. If a function/variable can contain more values beyond `null` +or 0 or `TRUE`, use numbers and defines instead of true/false comparisons. + +```dm +// Bad +var/thing = pick(TRUE, FALSE) +if(thing == TRUE) + return "bleh" +var/other_thing = pick(TRUE, FALSE) +if(other_thing == FALSE) + return "meh" + +// Good +var/thing = pick(TRUE, FALSE) +if(thing) + return "bleh" +var/other_thing = pick(TRUE, FALSE) +if(!other_thing) + return "meh" +``` + +## Use `pick(x, y, z)`, not `pick(list(x, y, z))` + +`pick()` takes a fixed set of options. Wrapping them in a list is +redundant and slightly less efficient. + +```dm +// Bad +var/text = pick(list("test_1", "test_2", "test_3")) +to_chat(world, text) + +// Good +var/text = pick("test_1", "test_2", "test_3") +to_chat(world, text) +``` + +## User Interfaces + +All new user interfaces in the game must be created using the TGUI framework. +Documentation can be found inside the [`tgui/docs`][tgui_docs] folder, and the +[`README.md`][tgui_readme] file. This is to ensure all ingame UIs are +snappy and responsive. An exception is made for user interfaces which are +purely for OOC actions (Such as character creation, or anything admin related) + +[tgui_docs]: https://github.com/ParadiseSS13/Paradise/tree/master/tgui/docs +[tgui_readme]: https://github.com/ParadiseSS13/Paradise/blob/master/tgui/README.md + +## No overriding type safety checks + +The use of the [`:`][colon] "runtime search" operator to override type safety +checks is not allowed. Variables must be casted to the proper type. + +[colon]: http://www.byond.com/docs/ref/#/operator/: + +## Do not chain proc calls and variable access + +The use of the pointer operator, `.`, should not be used to access the return +values of functions directly. This can cause unintended behavior and is +difficult to read. + +```dm +//Bad +var/our_x = get_turf(thing).x + +//Good +var/turf/our_turf = get_turf(thing) +var/our_x = our_turf.x +``` + +## Type paths must begin with a / + +e.g.: `/datum/thing`, not `datum/thing` + +## Datum type paths must began with "datum" + +In DM, this is optional, but omitting it makes finding definitions harder. To be +specific, you can declare the path `/arbitrary`, but it will still be, in +actuality, `/datum/arbitrary`. Write your code to reflect this. + +## Do not use list operators in strings + +The use of list operators to augment strings is not allowed. This is roughly 10 +times slower than using a list with a Join() Function. + +```dm +//Bad +var/text = "text" +text += "More text" +to_chat(world, text) + +//Good +var/list/text = list("text") +text += "More text" +to_chat(world, text.Join("")) +``` + +## Do not use text/string based type paths + +It is rarely allowed to put type paths in a text format, as there are no compile +errors if the type path no longer exists. Here is an example: + +```dm +//Bad +var/path_type = "/obj/item/baseball_bat" + +//Good +var/path_type = /obj/item/baseball_bat +``` + +## Do not use `\The` + +The `\The` macro doesn't actually do anything when used in the format `\The +[atom reference]`. Directly referencing an atom in an embedded string will +automatically prefix `The` or `the` to it as appropriate. As an extension, when +referencing an atom, don't use `[atom.name]`, use `[atom]`. The only exception +to this rule is when dealing with items "belonging" to a mob, in which case you +should use `[mob]'s [atom.name]` to avoid `The` ever forming. + +```dm +//Bad +var/atom/A +"\The [A]" + +//Good +var/atom/A +"[A]" +``` + +## Use the pronoun library instead of `\his` macros + +We have a system in [`code/__HELPERS/pronouns.dm`][pronouns] +for addressing all forms of pronouns. This is useful in a number of ways; + +- BYOND's `\his` macro can be unpredictable on what object it references. Take + this example: `"[user] waves \his [user.weapon] around, hitting \his + opponents!"`. This will end up referencing the user's gender in the first + occurrence, but what about the second? It'll actually print the gender set on + the weapon he's carrying, which is unintended - and there's no way around + this. +- It always prints the real `gender` variable of the atom it's referencing. This + can lead to exposing a mob's gender even when their face is covered, which + would normally prevent it's gender from being printed. + +The way to avoid these problems is to use the pronoun system. Instead of +`"[user] waves \his arms."`, you can do `"[user] waves [user.p_their()] arms."` + +```dm +//Bad +"[H] waves \his hands!" +"[user] waves \his [user.weapon] around, hitting \his opponents!" + +//Good +"[H] waves [H.p_their()] hands!" +"[user] waves [H.p_their()] [user.weapon] around, hitting [H.p_their()] opponents!"` +``` + +[pronouns]: https://github.com/ParadiseSS13/Paradise/blob/master/code/__HELPERS/pronouns.dm + +## Use `[A.UID()]` over `\ref[A]` + +BYOND has a system to pass "soft references" to datums, using the format +`"\ref[datum]"` inside a string. This allows you to find the object just based +off of a text string, which is especially useful when dealing with the bridge +between BYOND code and HTML/JS in UIs. It's resolved back into an object +reference by using `locate("\ref[datum]")` when the code comes back to BYOND. +The issue with this is that `locate()` can return a unexpected datum if the +original datum has been deleted - BYOND recycles the references. + +UID's are actually unique; they work off of a global counter and are not +recycled. Each datum has one assigned to it when it's created, which can be +accessed by [`[datum.UID()]`][duid]. You can use this as a snap-in replacement for +`\ref` by changing any `locate(ref)` calls in your code to `locateUID(ref)`. +Usage of this system is mandatory for any `Topic()` calls, and will produce +errors in Dream Daemon if it's not used. + +```dm +//Bad +"Link!" + +//Good +"Link!" +``` + +[duid]: https://codedocs.paradisestation.org/datum.html#proc/UID + +## Use `var/name` format when declaring variables + +While DM allows other ways of declaring variables, this one should be used for +consistency. + +## Tabs, not spaces + +You must use tabs to indent your code, **not spaces**. You may use spaces to align +text, but you should tab to the block level first, then add the remaining +spaces. + +## No hacky code + +Hacky code, such as adding specific checks (ex: `istype(src, /obj/whatever)`), +is highly discouraged and only allowed when there is **_no_** other option. +(Pro-tip: 'I couldn't immediately think of a proper way so thus there must be no +other option' is not gonna cut it here! If you can't think of anything else, say +that outright and admit that you need help with it. Maintainers, PR Reviewers, +and other contributors who can help you exist for exactly that reason.) + +You can avoid hacky code by using object-oriented methodologies, such as +overriding a function (called "procs" in DM) or sectioning code into functions +and then overriding them as required. + +The same also applies to bugfixes - If an invalid value is being passed into a +proc from something that shouldn't have that value, don't fix it on the proc +itself, fix it at its origin! (Where feasible) + +## No duplicated code + +Copying code from one place to another may be suitable for small, short-time +projects, but Paradise is a long-term project and highly discourages this. + +Instead you can use object orientation, or simply placing repeated code in a +function, to obey this specification easily. + +## Startup/Runtime tradeoffs with lists and the "hidden" init proc + +First, read the comments in [this BYOND thread](http://www.byond.com/forum/?post=2086980&page=2#comment19776775), starting where the link takes you. + +There are two key points here: + +1. Defining a list in the variable's definition calls a hidden proc - init. If + you have to define a list at startup, do so in `New()` (or preferably + `Initialize()`) and avoid the overhead of a second call (`init()` and then + `New()`) + +2. It also consumes more memory to the point where the list is actually + required, even if the object in question may never use it! + +Remember: although this tradeoff makes sense in many cases, it doesn't cover +them all. Think carefully about your addition before deciding if you need to use +it. + +## Prefer `Initialize()` over `New()` for atoms + +Our game controller is pretty good at handling long operations and lag, but it +can't control what happens when the map is loaded, which calls `New()` for all +atoms on the map. If you're creating a new atom, use the `Initialize()` proc to +do what you would normally do in `New()`. This cuts down on the number of proc +calls needed when the world is loaded. + +While we normally encourage (and in some cases, even require) bringing out of +date code up to date when you make unrelated changes near the out of date code, +that is not the case for `New()` -> `Initialize()` conversions. These systems +are generally more dependent on parent and children procs, so unrelated random +conversions of existing things can cause bugs that take months to figure out. + +## No implicit `var/` + +When you declare a parameter in a proc, the `var/` is implicit. Do not include +any implicit `var/` when declaring a variable. + +```dm +//Bad +/obj/item/proc1(var/mob/input1, var/input2) + code + +//Good +/obj/item/proc1(mob/input1, input2) + code +``` + +## No magic numbers or strings + +This means stuff like having a "mode" variable for an object set to "1" or "2" +with no clear indicator of what that means. Make these #defines with a name that +more clearly states what it's for. For instance: + +```dm +//Bad +/datum/proc/do_the_thing(thing_to_do) + switch(thing_to_do) + if(1) + do_stuff() + if(2) + do_other_stuff() +``` + +There's no indication of what "1" and "2" mean! Instead, you should do something +like this: + +```dm +//Good +#define DO_THE_THING_REALLY_HARD 1 +#define DO_THE_THING_EFFICIENTLY 2 + +/datum/proc/do_the_thing(thing_to_do) + switch(thing_to_do) + if(DO_THE_THING_REALLY_HARD) + do_stuff() + if(DO_THE_THING_EFFICIENTLY) + do_other_stuff() +``` + +This is clearer and enhances readability of your code! Get used to doing it! + +## Control statements + +- All control statements comparing a variable to a number should use the formula + of `thing` `operator` `number`, not the reverse (e.g. `if(count <= 10)` not + `if(10 >= count)`) +- All control statements must be spaced as `if()`, with the brackets touching + the keyword. +- All control statements must not contain code on the same line as the + statement. + +```dm +//Bad +if(x) return + +//Good +if(x) + return +``` + +## Player Output + +Due to the use of "TGchat", Paradise requires a special syntax for outputting +text messages to players. Instead of `mob << "message"`, you must use +`to_chat(mob, "message")`. Failure to do so will lead to your code not working. + +## Use guard clauses + +_Guard clauses_ are early returns in a proc for specific conditions. This +is preferred wrapping most of a proc's behavior in an in-block, as procs +will often check a handful of early conditions to bail out on. + +This is bad: + +```dm +/datum/datum1/proc/proc1() + if(thing1) + if(!thing2) + if(thing3 == 30) + do stuff +``` + +This is good: + +```dm +/datum/datum1/proc/proc1() + if(!thing1) + return + if(thing2) + return + if(thing3 != 30) + return + do stuff +``` + +This prevents nesting levels from getting deeper then they need to be. + +## Use `addtimer()` instead of `sleep()` or `spawn()` + +If you need to call a proc after a set amount of time, use `addtimer()` instead +of `spawn()` / `sleep()` where feasible. Though more complex, this method has +greater performance. Additionally, unlike `spawn()` or `sleep()`, it can be +cancelled. For more details, see +[https://github.com/tgstation/tgstation/pull/22933](https://github.com/tgstation/tgstation/pull/22933). + +Look for code examples on how to properly use it. + +```dm +//Bad +/datum/datum1/proc/proc1(target) + spawn(5 SECONDS) + target.dothing(arg1, arg2, arg3) + +//Good +/datum/datum1/proc/proc1(target) + addtimer(CALLBACK(target, PROC_REF(dothing), arg1, arg2, arg3), 5 SECONDS) +``` + +## Signals + +Signals are a slightly more advanced topic, but are often useful for attaching +external behavior to objects that should be triggered when a specific event +occurs. + +When defining procs that should be called by signals, you must include +`SIGNAL_HANDLER` after the proc header. This ensures that no sleeping code can +be called from within a signal handler, as that can cause problems with the +signal system. + +Since callbacks can be connected to many signals with `RegisterSignal`, it can +be difficult to pin down the source that a callback is invoked from. Any new +`SIGNAL_HANDLER` should be followed by a comment listing the signals that the +proc is expected to be invoked for. If there are multiple signals to be handled, +separate them with a `+`. + +```dm +/atom/movable/proc/when_moved(atom/movable/A) + SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED + do_something() + +/datum/component/foo/proc/on_enter(datum/source, atom/enterer) + SIGNAL_HANDLER // COMSIG_ATOM_ENTERED + COMSIG_ATOM_INITIALIZED_ON + do_something_else() +``` + +If your proc does have something that needs to sleep (such as a `do_after()`), +do not simply omit the `SIGNAL_HANDLER`. Instead, call the sleeping code with +`INVOKE_ASYNC` from within the signal handling function. + +```dm +/atom/movable/proc/when_moved(atom/movable/A) + SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED + INVOKE_ASYNC(src, PROC_REF(thing_that_sleeps), arg1) +``` + +## Operators + +### Spacing of operators + +- Operators that should be separated by spaces: + - Boolean and logic operators like `&&`, `||` `<`, `>`, `==`, etc. (But not `!`) + - Bitwise AND `&` and OR `|`. + - Argument separator operators like `,`. (and `;` when used in a forloop) + - Assignment operators like `=` or `+=` or the like. + - Math operators like `+`, `-`, `/`, or `*`. +- Operators that should NOT be separated by spaces: + - Access operators like `.` and `:`. + - Parentheses `()`. + - Logical not `!`. + +### Use of operators + +- Bitwise ANDs (`&`) should be written as `bitfield & bitflag` NEVER `bitflag & + bitfield`. Both are valid, but the latter is confusing and nonstandard. +- Associated lists declarations must have their key value quoted if it's a string. + +```dm +//Bad +list(a = "b") + +//Good +list("a" = "b") +``` + +### Bitflags + +Bitshift operators are mandatory, opposed to directly typing out the value: + +```dm +#define MACRO_ONE (1<<0) +#define MACRO_TWO (1<<1) +#define MACRO_THREE (1<<2) +``` + +Is accepted, whereas the following is not: + +```dm +#define MACRO_ONE 1 +#define MACRO_TWO 2 +#define MACRO_THREE 4 +``` + +While it may initially look intimidating, `(1<Arbitrary text") + +//Good +user.visible_message("Arbitrary text") +``` + +- You should not use color macros (`\red, \blue, \green, \black`) to color text, + instead, you should use span classes. `Red text`, + `Blue text`. + +```dm +//Bad +to_chat(user, "\red Red text \black Black text") + +//Good +to_chat(user, "Red textBlack text") +``` + +- To use variables in strings, you should **never** use the `text()` operator, + use embedded expressions directly in the string. + +```dm +//Bad +to_chat(user, text("[] is leaking []!", name, liquid_type)) + +//Good +to_chat(user, "[name] is leaking [liquid_type]!") +``` + +- To reference a variable/proc on the src object, you should **not** use + `src.var`/`src.proc()`. The `src.` in these cases is implied, so you should + just use `var`/`proc()`. + +```dm +//Bad +var/user = src.interactor +src.fill_reserves(user) + +//Good +var/user = interactor +fill_reserves(user) +``` + +## Develop Secure Code + +- Player input must always be escaped safely. We recommend you use + `stripped_input()` in all cases where you would use input. Essentially, just + always treat input from players as inherently malicious and design with that + use case in mind. + +- Calls to the database must be escaped properly; use proper parameters (values + starting with a `:`). You can then replace these with a list of parameters, and + these will be properly escaped during the query, and prevent any SQL + injection. + +```dm +//Bad +var/datum/db_query/query_watch = SSdbcore.NewQuery("SELECT reason FROM [format_table_name("watch")] WHERE ckey='[target_ckey]'") + +//Good +var/datum/db_query/query_watch = SSdbcore.NewQuery("SELECT reason FROM [format_table_name("watch")] WHERE ckey=:target_ckey", list( + "target_ckey" = target_ckey +)) // Note the use of parameters on the above line and :target_ckey in the query. +``` + +- All calls to topics must be checked for correctness. Topic href calls can be + easily faked by clients, so you should ensure that the call is valid for the + state the item is in. Do not rely on the UI code to provide only valid topic + calls, because it won't. + +- Information that players could use to metagame (that is, to identify round + information and/or antagonist type via information that would not be available + to them in character) should be kept as administrator-only. + +- Where you have code that can cause large-scale modification and _FUN_, make + sure you start it out locked behind one of the default admin roles - use + common sense to determine which role fits the level of damage a function could + do. + +## Files + +- Because runtime errors do not give the full path, try to avoid having files + with the same name across folders. + +- File names should not be mixed case, or contain spaces or any character that + would require escaping in a URI. + +- Files and path accessed and referenced by code above simply being `#include`d + should be strictly lowercase to avoid issues on filesystems where case + matters. + +### Modular Code in a File + +- Code should be modular where possible; if you are working on a new addition, + then strongly consider putting it in its own file unless it makes sense to put + it with similar ones (e.g. a new tool would go in the `tools.dm` file). + +- Our codebase also has support for checking files so that they only contain one + specific typepath, including none of its subtypes. This can be done by adding + a specific header at the beginning of the file, which the CI will look for + when running. An example can be seen below. You can also run this test locally + using `/tools/ci/restrict_file_types.py` + +```dm +RESTRICT_TYPE(/datum/foo) + +/datum/proc/do_thing() // Error: '/datum' proc found in a file restricted to '/datum/foo' + +/datum/foo + +/datum/foo/do_thing() + +/datum/foo/bar // Error: '/datum/foo/bar' type definition found in a file restricted to '/datum/foo' + +/datum/foo/bar/do_thing() // Error: '/datum/foo/bar' proc found in a file restricted to '/datum/foo' +``` + +## SQL + +- Do not use the shorthand SQL insert format (where no column names are + specified) because it unnecessarily breaks all queries on minor column changes + and prevents using these tables for tracking outside related info such as in a + connected site/forum. + +- Use parameters for queries, as mentioned above in [Develop Secure Code](#develop-secure-code). + +- Always check your queries for success with `if(!query.warn_execute())`. By + using this standard format, you can ensure the correct log messages are used. + +- Always `qdel()` your queries after you are done with them. This cleans up the + results and helps things run smoother. + +- All changes to the database's layout (schema) must be specified in the + database changelog in SQL, as well as reflected in the schema file. + +- Any time the schema is changed, the `SQL_VERSION` defines must be incremented, + as well as the example config, with an appropriate conversion kit placed in + the `SQL/updates` folder. + +- Queries must never specify the database, be it in code, or in text files in + the repo. + +## Dream Maker Quirks/Tricks + +Like all languages, Dream Maker has its quirks and some of them are beneficial +to us. + +### In-To for-loops + +`for(var/i = 1, i <= some_value, i++)` is a fairly standard way to write an +incremental for loop in most languages (especially those in the C family), but +DM's `for(var/i in 1 to some_value)` syntax is oddly faster than its +implementation of the former syntax; where possible, it's advised to use DM's +syntax. (Note, the `to` keyword is inclusive, so it automatically defaults to +replacing `<=`; if you want `<` then you should write it as `1 to +some_value-1`). + +**However**, if either `some_value` or `i` changes within the body of the for +(underneath the `for(...)` header) or if you are looping over a list **and** +changing the length of the list, then you can **not** use this type of for-loop! + +### `for(var/A in list)` VS `for(var/i in 1 to length(list))` + +The former is faster than the latter, as shown by the following profile results: + +![](./images/for_loop_timing.png) + +Code used for the test: + +```dm +var/list/numbers_to_use = list() +proc/initialize_shit() + for(var/i in 1 to 1000000) + numbers_to_use += rand(1,100000) + +proc/old_loop_method() + for(var/i in numbers_to_use) + var/numvar = i + +proc/new_loop_method() + for(var/i in 1 to numbers_to_use.len) + var/numvar = numbers_to_use[i] +``` + +### `istype()`-less `for` loops + +A name for a differing syntax for writing for-each style loops in DM. It's **not** +DM's standard syntax, hence why this is considered a quirk. Take a look at this: + +```dm +var/list/bag_of_items = list(sword1, apple, coinpouch, sword2, sword3) +var/obj/item/sword/best_sword +for(var/obj/item/sword/S in bag_of_items) + if(!best_sword || S.damage > best_sword.damage) + best_sword = S +``` + +The above is a simple proc for checking all swords in a container and returning +the one with the highest damage, and it uses DM's standard syntax for a for-loop +by specifying a type in the variable of the for's header that DM interprets as a +type to filter by. It performs this filter using `istype()` (or some +internal-magic similar to `istype()` - this is BYOND, after all). This is fine +in its current state for `bag_of_items`, but if `bag_of_items` contained ONLY +swords, or only SUBTYPES of swords, then the above is inefficient. For example: + +```dm +var/list/bag_of_swords = list(sword1, sword2, sword3, sword4) +var/obj/item/sword/best_sword +for(var/obj/item/sword/S in bag_of_swords) + if(!best_sword || S.damage > best_sword.damage) + best_sword = S +``` + +The above code specifies a type for DM to filter by. + +With the previous example that's perfectly fine, we only want swords, but if the +bag only contains swords? Is DM still going to try to filter because we gave it +a type to filter by? YES, and here comes the inefficiency. Wherever a list (or +other container, such as an atom (in which case you're technically accessing +their special contents list, but that's irrelevant)) contains datums of the same +datatype or subtypes of the datatype you require for your loop's body, you can +circumvent DM's filtering and automatic `istype()` checks by writing the loop as +such: + +```dm +var/list/bag_of_swords = list(sword, sword, sword, sword) +var/obj/item/sword/best_sword +for(var/s in bag_of_swords) + var/obj/item/sword/S = s + if(!best_sword || S.damage > best_sword.damage) + best_sword = S +``` + +Of course, if the list contains data of a mixed type, then the above +optimisation is **dangerous**, as it will blindly typecast all data in the list +as the specified type, even if it isn't really that type, causing runtime errors +(aka your shit won't work if this happens). + +### Dot variable + +Like other languages in the C family, DM has a `.` or "Dot" operator, used for +accessing variables/members/functions of an object instance. eg: + +```dm +var/mob/living/carbon/human/H = YOU_THE_READER +H.gib() +``` + +However, DM also has a dot _variable_, accessed just as `.` on its own, +defaulting to a value of null. Now, what's special about the dot operator is +that it is automatically returned (as in the `return` statement) at the end of a +proc, provided the proc does not already manually return (`return count` for +example.) Why is this special? + +With `.` being everpresent in every proc, can we use it as a temporary variable? +Of course we can! However, the `.` operator cannot replace a typecasted variable +- it can hold data any other var in DM can, it just can't be accessed as one, +although the `.` operator is compatible with a few operators that look weird but +work perfectly fine, such as: `.++` for incrementing `.'s` value, or `.[1]` for +accessing the first element of `.`, provided that it's a list. + +### Globals versus static + +DM has a var keyword, called `global`. This var keyword is for vars inside of +types. For instance: + +```dm +/mob + var/global/thing = TRUE +``` + +This does **not** mean that you can access it everywhere like a global var. Instead, it means that that var will only exist once for all instances of its type, in this case that var will only exist once for all mobs - it's shared across everything in its type. (Much more like the keyword `static` in other languages like PHP/C++/C#/Java) + +Isn't that confusing? + +There is also an undocumented keyword called `static` that has the same +behaviour as global but more correctly describes BYOND's behaviour. Therefore, +we always use static instead of global where we need it, as it reduces suprise +when reading BYOND code. + +### Global Vars + +All new global vars must use the defines in +[`code/__DEFINES/_globals.dm`][globals]. Basic usage is as follows: + +To declare a global var: + +```dm +GLOBAL_VAR(my_global_here) +``` + +To access it: + +```dm +GLOB.my_global_here = X +``` + +There are a few other defines that do other things. `GLOBAL_REAL` shouldn't be +used unless you know exactly what you're doing. `GLOBAL_VAR_INIT` allows you to +set an initial value on the var, like `GLOBAL_VAR_INIT(number_one, 1)`. +`GLOBAL_LIST_INIT` allows you to define a list global var with an initial value, +etc. + +[globals]: https://github.com/ParadiseSS13/Paradise/blob/master/code/__DEFINES/_globals.dm diff --git a/docs/coding/debugging.md b/docs/coding/debugging.md new file mode 100644 index 000000000000..6414d2df9727 --- /dev/null +++ b/docs/coding/debugging.md @@ -0,0 +1,304 @@ +# Guide to Debugging + +## Intro +Got a bug and you're unable to find it by just looking at your code? Try +debugging! This guide will teach you the basics of debugging, how to read the +values and some tips and tricks. It will be written as a chronological story. +Where the chapters explain the next part of the debugging process. + +Be sure to look at [Getting Started](../contributing/getting_started.md) if +you're new and need help with setting up your repo. Do also remember that all +below here is how I do it. There are many ways but I find that this works for +me. + +### What Is Debugging +> "Debugging is the process of detecting and removing of existing and potential +errors (also called as "bugs") in a software code that can cause it to behave +unexpectedly or crash." +[(source)](https://economictimes.indiatimes.com/definition/debugging) + +As you can see from this quote. It is a very broad term. This guide will use a +code debugger to step through your code and look at what is happening. + +## How To Debug +We will be using [#15958](https://github.com/ParadiseSS13/Paradise/issues/15958) as an example issue. + +### Finding The Issue +First of all, you need to understand what is happening functionally. This +usually gives you hints as to where it goes wrong. + +Looking at the GitHub issue we can see that the author (luckily) wrote a clear +reproduction. Without one we'd only be guessing as to where it goes wrong +exactly. Here a tripwire mine activates when it is in a container such as a +closed locker or a bag. + +This gives us the hint that the trigger mechanism does not check if the object +is directly on a turf. Using this hint we go look for the proc which causes the +trigger to happen. Using [my previous guide's advice][contrib] +we quickly find `/obj/item/assembly/infra`. + +![image](./images/debug_infra_search_results.png) + +In the current file of `/obj/item/assembly/infra`, "infrared.dm", we can see a +lot of different procs. We're only really interested in the proc which triggers +the bomb. + +![image](./images/debug_trigger_proc.png) + +We see that the proc `toggle_secure` starts and stops processing of the object. +This gives us a hint as to where the triggering happens. Looking at +`/obj/item/assembly/infra/process` we see that it creates beams when it is +active. Those beams are functionally used to trigger the bomb itself. + +![image](./images/debug_infra_process.png) + +Looking at the `/obj/effect/beam/i_beam` object we see that this is indeed the +case. + +![image](./images/debug_ibeam_hit.png) + +`/obj/effect/beam/i_beam/proc/hit()` is defined here which calls `trigger_beam` +on master. `hit()` in term is called whenever something `Bumped` or `Crossed` +the beam. I found this by right clicking on the `hit` proc and choosing `Find +All References` + +![image](./images/debug_ibeam_findreferences.png) + +![image](./images/debug_ibeam_findresults.png) + +So now we know what is causing the triggering of the bomb. We know that beams +are sent when the bomb is active. And we functionally know that these beams are +also sent when the bomb is hidden in a locker or bag. + +[contrib]: ./quickstart.md + +### Breakpoints +Now we know what is happening we can start debugging. I have a suspicion already +of what is the cause of the issue. Namely that the `infrared emitter` does not +check the `loc` of the actual bomb in the `/obj/item/assembly/infra/process()` +proc. + +To confirm this I will place a breakpoint just when the `process` proc begins. +You do this by clicking just left of the line number where you want to put the +breakpoint. + +![image](./images/debug_set_breakpoint.png) + +The red dot there is a breakpoint that is set. Clicking it again removes it. + +After doing this we will follow the reproduction steps. Once the game hits your +breakpoint it will freeze your game and your VS Code instance should pop up to +the front. If not just open VS Code. + +When testing this I noticed that the breakpoint got hit multiple times before I +could do my reproduction. If that is the case then you have multiple options for +combating this annoyance. Either you disable the breakpoint and re-enable it +when you are ready to test. This is not a great way of doing it for this case +but in some cases, this is enough. Or you move the breakpoint to a better +location. I choose this one and I've moved it a bit lower. + +![image](./images/debug_lower_breakpoint.png) + +I moved it past the `if(!on)` check which eliminates all trip wires which are +not turned on. (Quick note. This is really bad code. They should not be +processing when they are not turned on) + +I spawned a premade grenade instead of making my own grenade here. Saves me +quite some time and annoyance in trying to look up how to do this again. +Remember the search results when looking for the trip mine? Yeah, use one of +those. + +### In Debug Mode +Once I turn on the bomb I notice that VS Code indeed pops up to the front. The +game is now paused till you say it can continue. + +![image](./images/debug_game_paused.png) + +The breakpoint line is now highlighted. The highlight shows where the code is +currently. It is about to do the `if(!secured)` check. Let's make the code +execute that one step by stepping over the line. F10 as a shortcut or you can +press the "Step Over" button in your active debugger window. + +![image](./images/debug_stepthrough.png) + +Now the code is executed and the highlight moved to the next line. Step over is +handy to quickly move over your code. It will jump over any proc call. Step Into +(F11) is for when you want to actually step into the proc call. This will be +explained later when it is needed. + +If you step over a bunch of times you will see that it will go and create the +`i_beam`. + +![image](./images/debug_stepthrough_create.png) + +Even though I am currently holding the bomb in my hands (same situation as when +it is in a bag/locker code-wise). Why is this the case? In the code, you can see +that a beam is created when the bomb is `on`, `secured` and `first` and `last` +are both null. These all have nothing to do with our issue. But the last check +checks if `T` is not null. `T` is defined earlier in the proc as +`get_turf(src)`. + +![image](./images/debug_stepthrough_getturf.png) + +In other words. `T` is the turf below my characters feet. And of course, this +one does exist in this case. What is missing here is a check to see if the +actual bomb is on a `turf`. How do we even check this? + +Time to go look for a reference to the actual bomb as `/obj/item/assembly/infra` +is just the mechanism used by the bomb. `/obj/item/assembly/infra` itself is a +subtype of `/obj/item/assembly` which is the base type that is used for bomb +mechanisms. So we best look at the definition of `/obj/item/assembly`. We do +this by `Ctrl`-clicking on the `assembly` part of `/obj/item/assembly/infra`. + +![image](./images/debug_assembly_def.png) + +Here we see the definition. We can see that `/obj/item/assembly` has a variable +called `holder` which is of type `/obj/item/assembly_holder`. This is most +likely the thing we want. + +Now to check if this is correct. Go to your debug window and open `Arguments` +and then `src`. `src` is of course the object we currently are. Which is the +`/obj/item/assembly/infra`. Once it is open you can see a **LOT** of variables. + +![image](./images/debug_arg_vars.png) + +We are not interested in most of them and instead, we want to find `holder` + +![image](./images/debug_holder_var.png) + +Yep, this is the one we want. Now how do we check if this object is directly on +a turf? How do we even check its location? The answer is `loc`. `loc` contains +the location of the object. Here I found out that the `loc` of the +`assembly_holder` wasn't actually my character but instead it was the grenade. +Good thing we checked further before we started coding right? + +![image](./images/debug_mine_location.png) + +### Finding/Making The Right Variable To Use +Now it becomes a bit odd. Chemistry bomb code is fairly ... bad. But we can make +this work. First, we go look a bit more into the code to find the proper +variable to use. We *can* use `holder.loc.loc` but that says very little about +what it actually means. It would cause even more headaches for people working +with the code in the future. Instead, we will help our future co-developers a +bit and look into improving the existing code. Later on, we also see that this +is not the correct way of fixing it fully. + +Let's take a look at the `assembly` code in `assembly.dm`. Let's see how the +assembly determines what its grenade is. When looking around the file I found +the proc `/obj/item/assembly/proc/pulse(radio = FALSE)`. This one seems +promising. + +![image](./images/debug_radio_pulse.png) + +Here we can see that either the holder is used or if `loc` is a grenade it will +be primed using `prime`. As you can see a previous coder even stated that this +is a hack. This however does give me an idea of how to handle this and the edge +cases that exist. Namely the case where a grenade owns the trip laser as a +mechanism. + +The idea I had in mind is to create a proc which returns the physical outer +object. The payload, grenade or such. Where does this proc go? Well in the +assembly file since it will be usable for other code as well. I just put it at +the bottom of the file since nowhere else seemed to fit better. I quickly found +that I needed some more info for this. What if the `holder` was not attached +yet? Or it is remotely attached to a bomb? For this I made a proc for the +`/obj/item/assembly_holder` object which will return the actual outermost +object. + +![image](./images/debug_get_outer_object.png) + +I found out that `master` is the bomb linked by the line at the top of the +image. This all allows me to complete my other proc. + +![image](./images/debug_complete_other_proc.png) + +Now we have a proper method to use to find the outermost object. + +### Applying The Fix And Testing It +Now we can go and fix the issue at hand. We want to check if the outermost +object its `loc` is a turf. If not it should not fire new lasers and kill the +old ones. + +We already have the turf that the `/obj/item/assembly/infra` is located on. Now +we just have to check if that turf is the same turf as our outermost object. + +![image](./images/debug_check_turf.png) + +Now, this should work. But you of course have to test it! Build it and run the +game. Run it without a breakpoint set first to see if it works functionally. + +And it seems to work! Now let's trip it and see if it actually keeps working. + +### Runtimes And Stacktrace Traveling +And the game froze. VS Code began blinking. What is happening? The game ran into +a runtime exception + +![image](./images/debug_runtime.png) + +Now let's see if this is actually our fault or not. Since we did not touch any +timers. To check this go to the debug window and open the call stack. + +![image](./images/debug_call_stack.png) + +Here you can see **ALL** the current "threads" of the game. DM itself is not a +multithreaded language but it can have multiple threads. I won't go into detail +on that here for simplicity sake. The top item is the one you are currently on. +Just click on it to open it. + +![image](./images/debug_top_call_stack.png) + +Here we can see the entire stack trace of our current thread. The stack trace is +the entire path the code took thus far. At the top is the last called proc and +at the bottom is the origin of the call. Clicking on each stack will jump you to +the location in the code. The message said that `addtimer` was called on a +`QDELETED` item. This means that the item it is made for is already deleted. +Lets click on `/obj/item/assembly/infra/trigger_beam()` in the stack trace to go +to that call location. + +![image](./images/debug_addtimer.png) + +Here we see where it went wrong. It seems that `addtimer(CALLBACK(src, +.proc/process_cooldown), 10)` is called even though the `src` is already +deleted. How can this happen? To save us all some time. `pulse` is the proc +which triggers the bomb. In our case our bomb exploded, destroying itself. +Now... is this our fault? No. But we can fix it nonetheless. + +![image](./images/debug_pulse_false.png) + +This code change will ensure the message is sent and that the runtime stops from +happening. + +### Stepping Into VS Stepping Over +Now back to the testing. Once build again start up the game again and try it +this time using a raw `/obj/item/assembly/infra`. As you can see it works only +when you hold it. But not when it is on the floor itself. Seems we made a +mistake! Place a breakpoint again in the `/obj/item/assembly/infra/process()` +proc since there something goes wrong. + +![image](./images/debug_outer_object_breakpoint.png) + +Now we want to step into the current line. This means that we will go into +`get_outer_object`. Press either F11 or the "Step Into" button. + +![image](./images/debug_step_into_button.png) + +This will get us to the proc itself. Now step over till you see it return `loc`. +`loc` here is the turf. Which is not the outermost object. Seems we need to do +another check there. This almost certainly also goes for +`/obj/item/assembly_holder/proc/get_outer_object()` as we used mostly the same +logic there. + +![image](./images/debug_common_logic.png) + +## Outro +Now let's test again. And it seems that there are more issues! The +assembly_holder still shoots a laser if you hold it or put it in a bag. Same for +the infra itself. Now I know the fix already but where is the fun in that. + +I'm leaving the last solution (and honour of making the PR that solves the +issue) open for you! If you feel like testing yourself then please pick up this +issue and fix it the way you think would work. I'd love to see your method! + +Please also let me know what you think of this format of doing a guide. This one +was more a look into how I do it step by step compared to a more structured +guide. diff --git a/docs/coding/images/Game_Panel.png b/docs/coding/images/Game_Panel.png new file mode 100644 index 000000000000..1bdf2569941c Binary files /dev/null and b/docs/coding/images/Game_Panel.png differ diff --git a/docs/coding/images/Game_Panel_Create.png b/docs/coding/images/Game_Panel_Create.png new file mode 100644 index 000000000000..90dc29764cf3 Binary files /dev/null and b/docs/coding/images/Game_Panel_Create.png differ diff --git a/docs/coding/images/Runtime_Viewer.png b/docs/coding/images/Runtime_Viewer.png new file mode 100644 index 000000000000..d5df04bde7c3 Binary files /dev/null and b/docs/coding/images/Runtime_Viewer.png differ diff --git a/docs/coding/images/Runtime_Viewer_View_Runtime.png b/docs/coding/images/Runtime_Viewer_View_Runtime.png new file mode 100644 index 000000000000..6ff90e8cc0f0 Binary files /dev/null and b/docs/coding/images/Runtime_Viewer_View_Runtime.png differ diff --git a/docs/coding/images/debug_addtimer.png b/docs/coding/images/debug_addtimer.png new file mode 100644 index 000000000000..4d7e29e93f61 Binary files /dev/null and b/docs/coding/images/debug_addtimer.png differ diff --git a/docs/coding/images/debug_arg_vars.png b/docs/coding/images/debug_arg_vars.png new file mode 100644 index 000000000000..cc881635d3df Binary files /dev/null and b/docs/coding/images/debug_arg_vars.png differ diff --git a/docs/coding/images/debug_assembly_def.png b/docs/coding/images/debug_assembly_def.png new file mode 100644 index 000000000000..1017b0257229 Binary files /dev/null and b/docs/coding/images/debug_assembly_def.png differ diff --git a/docs/coding/images/debug_call_stack.png b/docs/coding/images/debug_call_stack.png new file mode 100644 index 000000000000..6a904edb9198 Binary files /dev/null and b/docs/coding/images/debug_call_stack.png differ diff --git a/docs/coding/images/debug_check_turf.png b/docs/coding/images/debug_check_turf.png new file mode 100644 index 000000000000..f7c21e607fa8 Binary files /dev/null and b/docs/coding/images/debug_check_turf.png differ diff --git a/docs/coding/images/debug_common_logic.png b/docs/coding/images/debug_common_logic.png new file mode 100644 index 000000000000..dfed2e73fed4 Binary files /dev/null and b/docs/coding/images/debug_common_logic.png differ diff --git a/docs/coding/images/debug_complete_other_proc.png b/docs/coding/images/debug_complete_other_proc.png new file mode 100644 index 000000000000..125985532777 Binary files /dev/null and b/docs/coding/images/debug_complete_other_proc.png differ diff --git a/docs/coding/images/debug_game_paused.png b/docs/coding/images/debug_game_paused.png new file mode 100644 index 000000000000..0aee2157eeea Binary files /dev/null and b/docs/coding/images/debug_game_paused.png differ diff --git a/docs/coding/images/debug_get_outer_object.png b/docs/coding/images/debug_get_outer_object.png new file mode 100644 index 000000000000..51ac102787bf Binary files /dev/null and b/docs/coding/images/debug_get_outer_object.png differ diff --git a/docs/coding/images/debug_holder_var.png b/docs/coding/images/debug_holder_var.png new file mode 100644 index 000000000000..1afbef7c6ebd Binary files /dev/null and b/docs/coding/images/debug_holder_var.png differ diff --git a/docs/coding/images/debug_ibeam_findreferences.png b/docs/coding/images/debug_ibeam_findreferences.png new file mode 100644 index 000000000000..986be7054e1e Binary files /dev/null and b/docs/coding/images/debug_ibeam_findreferences.png differ diff --git a/docs/coding/images/debug_ibeam_findresults.png b/docs/coding/images/debug_ibeam_findresults.png new file mode 100644 index 000000000000..164cce28581a Binary files /dev/null and b/docs/coding/images/debug_ibeam_findresults.png differ diff --git a/docs/coding/images/debug_ibeam_hit.png b/docs/coding/images/debug_ibeam_hit.png new file mode 100644 index 000000000000..c848f6af8485 Binary files /dev/null and b/docs/coding/images/debug_ibeam_hit.png differ diff --git a/docs/coding/images/debug_infra_process.png b/docs/coding/images/debug_infra_process.png new file mode 100644 index 000000000000..7ee9f9d42bc5 Binary files /dev/null and b/docs/coding/images/debug_infra_process.png differ diff --git a/docs/coding/images/debug_infra_search_results.png b/docs/coding/images/debug_infra_search_results.png new file mode 100644 index 000000000000..f0fa44b233c1 Binary files /dev/null and b/docs/coding/images/debug_infra_search_results.png differ diff --git a/docs/coding/images/debug_lower_breakpoint.png b/docs/coding/images/debug_lower_breakpoint.png new file mode 100644 index 000000000000..fd938b501d31 Binary files /dev/null and b/docs/coding/images/debug_lower_breakpoint.png differ diff --git a/docs/coding/images/debug_mine_location.png b/docs/coding/images/debug_mine_location.png new file mode 100644 index 000000000000..4068468baa22 Binary files /dev/null and b/docs/coding/images/debug_mine_location.png differ diff --git a/docs/coding/images/debug_outer_object_breakpoint.png b/docs/coding/images/debug_outer_object_breakpoint.png new file mode 100644 index 000000000000..425f2ce93c59 Binary files /dev/null and b/docs/coding/images/debug_outer_object_breakpoint.png differ diff --git a/docs/coding/images/debug_pulse_false.png b/docs/coding/images/debug_pulse_false.png new file mode 100644 index 000000000000..d20fdd03ad5a Binary files /dev/null and b/docs/coding/images/debug_pulse_false.png differ diff --git a/docs/coding/images/debug_radio_pulse.png b/docs/coding/images/debug_radio_pulse.png new file mode 100644 index 000000000000..390cf7f445fc Binary files /dev/null and b/docs/coding/images/debug_radio_pulse.png differ diff --git a/docs/coding/images/debug_runtime.png b/docs/coding/images/debug_runtime.png new file mode 100644 index 000000000000..ade35c432d63 Binary files /dev/null and b/docs/coding/images/debug_runtime.png differ diff --git a/docs/coding/images/debug_set_breakpoint.png b/docs/coding/images/debug_set_breakpoint.png new file mode 100644 index 000000000000..d2c183ca6ca0 Binary files /dev/null and b/docs/coding/images/debug_set_breakpoint.png differ diff --git a/docs/coding/images/debug_step_into_button.png b/docs/coding/images/debug_step_into_button.png new file mode 100644 index 000000000000..a7f398ef8ec1 Binary files /dev/null and b/docs/coding/images/debug_step_into_button.png differ diff --git a/docs/coding/images/debug_stepthrough.png b/docs/coding/images/debug_stepthrough.png new file mode 100644 index 000000000000..8f38dfb263d9 Binary files /dev/null and b/docs/coding/images/debug_stepthrough.png differ diff --git a/docs/coding/images/debug_stepthrough_create.png b/docs/coding/images/debug_stepthrough_create.png new file mode 100644 index 000000000000..ba8fb98a3269 Binary files /dev/null and b/docs/coding/images/debug_stepthrough_create.png differ diff --git a/docs/coding/images/debug_stepthrough_getturf.png b/docs/coding/images/debug_stepthrough_getturf.png new file mode 100644 index 000000000000..33c8655a4f92 Binary files /dev/null and b/docs/coding/images/debug_stepthrough_getturf.png differ diff --git a/docs/coding/images/debug_top_call_stack.png b/docs/coding/images/debug_top_call_stack.png new file mode 100644 index 000000000000..669c9e89d2cd Binary files /dev/null and b/docs/coding/images/debug_top_call_stack.png differ diff --git a/docs/coding/images/debug_trigger_proc.png b/docs/coding/images/debug_trigger_proc.png new file mode 100644 index 000000000000..990c7d0282c6 Binary files /dev/null and b/docs/coding/images/debug_trigger_proc.png differ diff --git a/docs/coding/images/for_loop_timing.png b/docs/coding/images/for_loop_timing.png new file mode 100644 index 000000000000..02b755d4772c Binary files /dev/null and b/docs/coding/images/for_loop_timing.png differ diff --git a/docs/coding/images/quickstart_animal_gib.png b/docs/coding/images/quickstart_animal_gib.png new file mode 100644 index 000000000000..081900f6e508 Binary files /dev/null and b/docs/coding/images/quickstart_animal_gib.png differ diff --git a/docs/coding/images/quickstart_build_errors.png b/docs/coding/images/quickstart_build_errors.png new file mode 100644 index 000000000000..a3acee5091a7 Binary files /dev/null and b/docs/coding/images/quickstart_build_errors.png differ diff --git a/docs/coding/images/quickstart_checkout_to.png b/docs/coding/images/quickstart_checkout_to.png new file mode 100644 index 000000000000..8b7a2e70199e Binary files /dev/null and b/docs/coding/images/quickstart_checkout_to.png differ diff --git a/docs/coding/images/quickstart_commit_message.png b/docs/coding/images/quickstart_commit_message.png new file mode 100644 index 000000000000..0c720d2cfc0e Binary files /dev/null and b/docs/coding/images/quickstart_commit_message.png differ diff --git a/docs/coding/images/quickstart_ctrl_key.png b/docs/coding/images/quickstart_ctrl_key.png new file mode 100644 index 000000000000..d606cd9d535f Binary files /dev/null and b/docs/coding/images/quickstart_ctrl_key.png differ diff --git a/docs/coding/images/quickstart_folder_navigate.png b/docs/coding/images/quickstart_folder_navigate.png new file mode 100644 index 000000000000..fb0c5d1e3b3b Binary files /dev/null and b/docs/coding/images/quickstart_folder_navigate.png differ diff --git a/docs/coding/images/quickstart_gib_search.png b/docs/coding/images/quickstart_gib_search.png new file mode 100644 index 000000000000..3780f2b9d9ac Binary files /dev/null and b/docs/coding/images/quickstart_gib_search.png differ diff --git a/docs/coding/images/quickstart_good_first_issue.png b/docs/coding/images/quickstart_good_first_issue.png new file mode 100644 index 000000000000..2680e5076130 Binary files /dev/null and b/docs/coding/images/quickstart_good_first_issue.png differ diff --git a/docs/coding/images/quickstart_mailman_search.png b/docs/coding/images/quickstart_mailman_search.png new file mode 100644 index 000000000000..bdfd955998b8 Binary files /dev/null and b/docs/coding/images/quickstart_mailman_search.png differ diff --git a/docs/coding/images/quickstart_new_branch_name.png b/docs/coding/images/quickstart_new_branch_name.png new file mode 100644 index 000000000000..da9fe0adeb04 Binary files /dev/null and b/docs/coding/images/quickstart_new_branch_name.png differ diff --git a/docs/coding/images/quickstart_problems_tab.png b/docs/coding/images/quickstart_problems_tab.png new file mode 100644 index 000000000000..3a55f4e5397c Binary files /dev/null and b/docs/coding/images/quickstart_problems_tab.png differ diff --git a/docs/coding/images/quickstart_push_branch.png b/docs/coding/images/quickstart_push_branch.png new file mode 100644 index 000000000000..d537f622f598 Binary files /dev/null and b/docs/coding/images/quickstart_push_branch.png differ diff --git a/docs/coding/images/quickstart_recent_pushes.png b/docs/coding/images/quickstart_recent_pushes.png new file mode 100644 index 000000000000..a6ac16a4f552 Binary files /dev/null and b/docs/coding/images/quickstart_recent_pushes.png differ diff --git a/docs/coding/images/quickstart_search_dm.png b/docs/coding/images/quickstart_search_dm.png new file mode 100644 index 000000000000..1237d58113c4 Binary files /dev/null and b/docs/coding/images/quickstart_search_dm.png differ diff --git a/docs/coding/images/quickstart_search_multitool.png b/docs/coding/images/quickstart_search_multitool.png new file mode 100644 index 000000000000..8561af196aa9 Binary files /dev/null and b/docs/coding/images/quickstart_search_multitool.png differ diff --git a/docs/coding/images/quickstart_search_multitool_quotes.png b/docs/coding/images/quickstart_search_multitool_quotes.png new file mode 100644 index 000000000000..70bbf48726f7 Binary files /dev/null and b/docs/coding/images/quickstart_search_multitool_quotes.png differ diff --git a/docs/coding/images/quickstart_search_toolhelpers.png b/docs/coding/images/quickstart_search_toolhelpers.png new file mode 100644 index 000000000000..1c49f804b6c3 Binary files /dev/null and b/docs/coding/images/quickstart_search_toolhelpers.png differ diff --git a/docs/coding/images/quickstart_vsc_tasks_config.png b/docs/coding/images/quickstart_vsc_tasks_config.png new file mode 100644 index 000000000000..cf391d2e5b0f Binary files /dev/null and b/docs/coding/images/quickstart_vsc_tasks_config.png differ diff --git a/docs/coding/images/quickstart_vsc_whitespace_setting.png b/docs/coding/images/quickstart_vsc_whitespace_setting.png new file mode 100644 index 000000000000..7057ab1afc69 Binary files /dev/null and b/docs/coding/images/quickstart_vsc_whitespace_setting.png differ diff --git a/docs/coding/images/quickstart_yellow_underline.png b/docs/coding/images/quickstart_yellow_underline.png new file mode 100644 index 000000000000..eff179d5c749 Binary files /dev/null and b/docs/coding/images/quickstart_yellow_underline.png differ diff --git a/docs/coding/quickstart.md b/docs/coding/quickstart.md new file mode 100644 index 000000000000..b22fd6c9187c --- /dev/null +++ b/docs/coding/quickstart.md @@ -0,0 +1,519 @@ +# Code Contribution Quickstart + +by *Farie82* + +## Intro +Glad you're reading this and hopefully this guide will help you start +contributing to this codebase! First a word of wisdom. Start small with your +first set of PRs even if you are already experienced with developing for other +languages or codebases. Every codebase has its own quirks and standards which +you will discover by doing and receiving feedback on your work. This guide will +help you set up your git and make your first PR. It will also include some tips +on how to (in my opinion) best handle the codebase. This guide will assume that +you have at least (very) minor knowledge of how programming works. Knowing what +a `string` is and how `if` statements work for example. The guide will also +assume that you will use VS Code which the [Getting +Started](../contributing/getting_started.md) guide helps you set up. + +Be sure to also take a look at the [contributing page](../CONTRIBUTING.md) so +you know what the coding standards are here. + +I've also made a [debugging tutorial](./debugging.md) which will help you find +the cause of bugs and how to fix them. + +## Quick DM tutorial +For your first PR you won't need an in-depth knowledge of how to code in DM, but +here are some of the basics. Feel free to skip these and come back to this once +you feel like you are missing some info. + +The [DM reference guide](http://www.byond.com/docs/ref/) is also great when you +want to look up how a proc or such works. VS Code does have a build-in reference +guide for you to use as well. Just `Ctrl`-click on any BYOND proc or variable to +see the reference on it. + +### Objects and Inheritance +An object is defined the following way: +```dm +/obj/item/multitool +``` + +Here we can see a `multitool` being defined. A `multitool` is an `item` which is +an `obj`. This is how the class inheritance works for DM. A real-life example is +that a dog is an animal and a cat is an animal. But a dog is not a cat. In DM it +could look something like this: + +```dm +/mob/animal/cat + name = "Cat" + +/mob/animal/dog + name = "Dog" +``` + +Where `mob` is a being in DM. Thus something that "lives" and can do things. + +### Procs +The way DM groups a set of instructions is as follows. It uses a *proc* or in +other languages also called a method or function. + +```dm +/obj/item/pen/multi/proc/select_colour(mob/user) + var/newcolour = input(user, "Which colour would you like to use?", name, colour) as null|anything in colour_choices + if(newcolour) + colour = newcolour + playsound(loc, 'sound/effects/pop.ogg', 50, 1) + update_icon() +``` + +`/obj/item/pen/multi/proc/select_colour` here is the proc definition. Meaning +this is the first instance of this proc. For this, you need to add `proc/` in +front of the method name (`select_colour` in this case). `mob/user` is here a +parameter given to the proc. The name of the parameter is `user` and its type is +`mob`. + +As with other languages you can also override the behaviour of a proc. + +```dm +/obj/item/pen/multi/attack_self(mob/living/user) + select_colour(user) +``` + +Here the proc `attack_self` is overridden with new behaviour. It will call +`select_colour` with as a parameter the given `user`. + +#### Overriding Procs + +When overriding a proc you can also call the parent's implementation. This is +especially handy when you want to extend the existing behaviour with new +behaviour. + +```dm +/obj/item/pen/multi/Initialize(mapload) + . = ..() + update_icon() +``` + +Here `Initialize` is overridden with `mapload` as a parameter. `..()` means call +the parent implementation of this proc with the parameters given to this +version. So `mapload` will be passed through. `. = ..()` means assign the value +that the parent's version returns as our default return value. `.` is the +default return value in DM. So if you don't return an explicit value at the end +of the proc then `.` will be returned. + +```dm +/proc/test() + . = "Yes" + return "No" +``` + +This will return `"No"` since you explicitly state to return `"No"`. + +Small tip. You can also `Ctrl`-click on `..()` to go to the parent's definition. + +### Putting values easily in strings + +Other languages use something like `string.format("{0} says {1}", mob_name, +say_text)`. But DM has something nifty for that. The same result can be achieved +in DM using the following: + +```dm +"[mob_name] says [say_text]" +``` + +`[...]` will run the code and return the outcome inside the `[]`. In the case +above it will just return the value of the variables but you can also use logic +here. + +```dm +var/val = 1 +world.log << "val is [val]. val plus 10 is: [val + 10]" +``` + +Which will produce `"val is 1. val plus 10 is: 11"` + +### Scoping +If you come from another language then you might think. "Hey, where are the +{}'s?!". Well, we do not use those (usually). Instead scoping is done by +whitespace. Tabs in our case. One tab means one scope deeper. + +```dm +/mob + name = "Thing" + +/mob/proc/test() + world.log << name // We can access name here since we are in the mob + if(name == "Thing") + var/value = 10 + world.log << "[value]" // We can also access value here since it is in the same scope or higher as us. + world.log << "Will only happen if name is Thing" + else + world.log << "Will only happen if name is not Thing" + world.log << "Will always happen even if name is not Thing" + world.log << "[value]" // This will produce an error since value is not defined in our current scope or higher +``` + +In VS Code you can make your life easier by turning on the rendering of +whitespace. Go to the settings and search for whitespace. + +![image](./images/quickstart_vsc_whitespace_setting.png) + +I have set it up so that I can only see the boundary whitespace. Meaning that I +visually see spaces and tabs on the outmost left and right side of a line. Very +handy in spotting indentation errors. + +### Deleting stuff + +DM has a build-in proc called `del`. **DO NOT USE THIS**. `del` is very slow and +gives us no control over properly destroying the object. Instead, most/all SS13 +codebases have made their own version for this. `qdel` which will queue a delete +for a given item. You should always call `qdel` when deleting an object. This +will not only be better performance-wise but it will also ensure that other +objects get notified about its deletion if needed. + +### Coding Standards + +**Before you start coding it is best to read our** [contributing page](../CONTRIBUTING.md). +It contains all of the coding standards and some tips and tricks on how to write +good and safe code. + +### Terminology +We will be using some terminology moving forward you should be comfortable with: + +- [PR](../references/glossary.md#pull-request), an abbreviation for pull + request. This is the thing that will get your changes into the actual game. In + short, it will say you request certain changes to be approved and merged into + the master branch. Which is then used to run the actual game. + +- [VS Code](../references/glossary.md#vsc), short for Visual Studio Code. The + place where you do all your magic. It is both a text editor with a lot of + helpful tools and a place where you can run and debug your code. + +- Scoping; defining what code belongs to what. You don't want to make everything + public to the whole codebase so you use scoping. See the explanation above for + more info. + +- Feature branch; the branch where your new feature or fix is located on. Git + works with branches. Each branch containing a different version of the + codebase. When making a PR git will look at the differences between your + branch and the master branch. + +## Setup +Code contributions require setting up a development environment. If you haven't +done that already, follow the guide at [Getting Started](../contributing/getting_started.md) +first. + +## Your First PR +Once you've completed the setup you can continue with making an actual PR. + +I'd suggest keeping it small since setting up all of the git stuff was already a +task of its own. My suggestion would be to look at issues with the [Good First +Issue][gfi] label. These usually are considered to be easy to solve. Usually, +they will also contain some comments containing hints on how to solve them. When +picking one be sure that you do not pick an issue that already has an open PR +attached to it like in the picture below. + +![image](./images/quickstart_good_first_issue.png) + +You *can* make a PR that solves that issue. But it would be a waste of time +since somebody else already solved it before you but their PR is still awaiting +approval. + +If there are no suitable good first issues then you can look through the issue +list yourself to find some. Good ones include typos or small logic errors. If +you know of any issues that are not yet listed in the issues list then those are +also fine candidates. + +Alternatively, you can implement a small new feature or change. Good examples +include: + +- More or changed flavour text to an item/ability etc. +- Adding (existing) sound effects to abilities/actions. +- Adding administrative logging where it is missing. For example, a martial arts + combo not being logged. +- A new set of clothing or a simple item. + +There of course are many more options that are not included in this list. + +[gfi]: https://github.com/ParadiseSS13/Paradise/labels/Good%20First%20Issue + +### Finding The Relevant Code +The first thing you will need to do is to find the relevant code once you +figured out what you want to add/change. This is no exact science and requires +some creative thinking but I will list a few methods I use myself when finding +code. + +For all of these, you will need to have VS code open and use the search +functionality. I tend to only look for things in dm files. Which are the code +files. + +![image](./images/quickstart_search_dm.png) + +#### Finding Existing Items +If you're looking for an existing item then it might be easiest to look for the +name of the item. Let's take a multitool as an example here. + +When looking for the term `multitool` you will tend to find a lot of results. +305 results on my current version of the game in fact. + +![image](./images/quickstart_search_multitool.png) + +Alternatively, you can search for `"multitool"` (the string with the value +`multitool`) and find a lot fewer results. For demonstration purposes, I will +exclude the `""` here. This will give you the following match: + +![image](./images/quickstart_search_multitool_quotes.png) + +This might seem like a lot (and it is) but you don't have to go through them all +to find the item itself. Using some deduction we can find the result we need. Or +find it via another result we found. Here we can see that there are some matches +with `obj/item/multitool` for example: + +![image](./images/quickstart_search_toolhelpers.png) + +We know that we are looking for a multitool and that a multitool is an item so +it looks like this is what we want to find. When hovering over the `multitool` +part of `obj/item/multitool` and holding the `Ctrl` key you will see the +definition of the object. + +![image](./images/quickstart_ctrl_key.png) + +Perfect! This is the one we need. How how do we get to that definition? Simple +you click on `multitool` when holding `Ctrl`. This will send you to the definition +of the object. + +Most of the times the file containing the definition will also include the +looked for proc or value you want to change. + +#### Finding Behaviour +This is a very wide concept and thus hard to exactly find. + +For this, we will use the above method and use keywords explaining the behaviour +you want to search. For example a mob gibbing. Simply looking for `gib` here +will find us too many results. About 1073 in my case. Instead, we will try to +look for a proc named gib. `/gib(` will be used as our search criteria here. + +![image](./images/quickstart_gib_search.png) + +Et voila, just 15 results. + +Say we want to delete the pet collar of animals such as Ian when he is gibbed. +Here we need to find something stating that the `gib` belongs to an animal. + +![image](./images/quickstart_animal_gib.png) + +`/mob/living/simple_animal/gib()` is what we are looking for here. Ian is an +animal. `simple_animal` in code. + +This will find us the following code (on my current branch): + +```dm +/mob/living/simple_animal/gib() + if(icon_gib) + flick(icon_gib, src) + if(butcher_results) + var/atom/Tsec = drop_location() + for(var/path in butcher_results) + for(var/i in 1 to butcher_results[path]) + new path(Tsec) + if(pcollar) + pcollar.forceMove(drop_location()) + pcollar = null + ..() +``` + +The behaviour we're looking for here has to do with the `pcollar` code there. It +will currently move the attached pet collar (if any) to the drop location of the +animal when they are gibbed. + +#### Finding A Suitable Place To Add A New Item +When adding a new item you want to ensure that it is placed in a logical file or +that you make a new file in a logical directory. I find that it is best to find +other similar items and see how they are defined. For example a special jumpsuit +without armour values. Here we first go look for the existing non-job-related +jumpsuits such as the `"mailman's jumpsuit"`. Say we don't know the exact name +of that jumpsuit but we do know that it is for a mailman. + +Our best bet will be to look for the term `mailman` and see what pops up. This +is a rather uncommon term so it should give only a few results. + +![image](./images/quickstart_mailman_search.png) + +Perfect. Even the item definition has the name mailman in it. + +We already see from the search results that the item is defined in the +`miscellaneous.dm` file. Navigating to it will show us the directory it is in. + +![image](./images/quickstart_folder_navigate.png) + +As you can see most clothing items are defined in this `clothing` directory. +Depending on your to add the item you can pick one of those files and see if it +would fit in there. Feel free to ask for advice from others if you are unsure. + +### Solving The Actual Issue +Now comes the **Fun** part. How to achieve what you want to achieve? The answer +is. "That depends" Fun, isn't it? Every problem has its own way of solving it. I +will list some of the more common solutions to a problem down here. This list +will of course not be complete. + +A great hotkey for building your code quick is `Ctrl` + `Shift` + `B`. Then press enter to +select to build via Byond. This will start building your code in the console at +the bottom of your screen (by default). It will also show any errors in the +build process. + +![image](./images/quickstart_build_errors.png) + +Here I have "accidentally" placed some text where it should not belong. Going to +the "Problems" tab and clicking on the error will bring you to where it goes +wrong. + +![image](./images/quickstart_problems_tab.png) + +This error does not tell much on its own (Byond is not great at telling you what +goes wrong sometimes) but going to the location shows the problem quite easily. + +![image](./images/quickstart_yellow_underline.png) + +More cases might be added later. + +#### Typo Or Grammar +The easiest of them all if you can properly speak English. Say the multitool +description text is: `"Used for pusling wires to test which to cut. Not +recommended by doctors."` Then you can easily fix the typo ("pusling" to +"pulsing") by just changing the value of the string to the correct spelling. + +#### Wrong Logic +This one really depends on the context. But let us take the following example. +You cannot link machinery using a multitool. Something it should do. + +```dm +/obj/item/multitool/proc/set_multitool_buffer(mob/user, obj/machinery/M) + if(ismachinery(M)) + to_chat(user, "That's not a machine!") + return +``` + +Here a simple mistake is made. A `!` is forgotten. `!` will negate the outcome +of any given value. In this case, a check to see if `M` is indeed a piece of +machinery. This seems dumb to forget or do wrong but it can happen when a large +PR gets made and is changed often. Testing every case is difficult and cases can +slip under the radar. + +#### Adding A New Item + +You can start defining the new item once you found the proper file the item +should belong to. Depending on the item you will have to write different code +(duh). We will take the new jumpsuit as an example again and will put it in the +`miscellaneous` file. + +When defining a new jumpsuit you can easily copy an existing one and change the +definition values. We will take the mailman outfit as a template. + +```dm +/obj/item/clothing/under/rank/mailman + name = "mailman's jumpsuit" + desc = "'Special delivery!'" + icon_state = "mailman" + item_state = "b_suit" + item_color = "mailman" +``` + +As seen here a jumpsuit has multiple values you can define. The `name` is pretty +straight forward. `desc` is the description of the item. `icon_state` is the +name the sprite has in the DMI file. `item_state` is the name of the sprite of +the suit while held in your hands has in the DMI file. `item_color` is the name +of the sprite in the `icons/mob/uniform.dmi` file. This value will indicate what +sprite will be used when a person wears this jumpsuit. (I know `item_color` is a +weird name for this) + +We of course also have to change the path of the newly created object. We'll +name it `/obj/item/clothing/under/rank/tutorial`. This alone will make it so +that you can spawn the item using admin powers. It will not automagically appear +in vendors or such. + +### Testing Your Code +Once you are done coding you can start testing your code, assuming your code compiles of course. + +To do this simply press `F5` on your keyboard. This will by default build your +code and start the game with a debugger attached. This allows you to debug the +code in more detail. + +Later I will include a more detailed testing plan in this guide. + +### Making The PR +Once you are done with your changes you can make a new PR. + +New PRs must be created on _branches_. Branches are copies of the `master` +branch that constitutes the server codebase. Making a separate branch for each +PR ensures your `master` branch remains clean and can pull in changes from +upstream easily. + +![image](./images/quickstart_checkout_to.png) + +Select "Create new branch" and then give your new branch a name in the top text +bar in VS code. Press enter once you are done and you will have created a new +branch. Your saved changes will be carried over to the new branch. + +![image](./images/quickstart_new_branch_name.png) + +You are ready to commit the changes once you are on your feature branch and when +the code is done and tested. Simply write a (short) useful commit message +explaining what you've done. For example when implementing the pet collar +gibbing example + +![image](./images/quickstart_commit_message.png) + +Here you can also see the changes you have made. Clicking on a file will show +you the difference between how it was before and how it is now. When you are +happy with the existing changes you can press the commit button. The checkmark. +By default it will commit all the changes there are unless you staged changes. +Then it will only commit the staged changes. Handy when you want to commit some +code while you have some experimental code still there. + +Once it is committed you will have to also publish the branch. + +![image](./images/quickstart_push_branch.png) + +When pressing push it will ask you if you want to publish the branch since there +is no known remote version of this yet (not on Github). Say yes to that and +select "origin" from the list of available remotes. Now your code is safely +pushed to Github. + +Now go to the Github page of this codebase and you will see the following: + +![image](./images/quickstart_recent_pushes.png) + +Click on the green button and Github will auto-create a PR template for you for +the branch you just pushed. Be sure to fill in the template shown to you. It is +quite straight forward but it is important to note down the thing you changed in +enough detail. If you fixed an issue then you can write the following `fixes #12345` +where 12345 is the number of the issue. Put this line of text in the +*What Does This PR Do* part of the PR. + +You can submit it once you are happy with how the PR looks. After this, your PR +will be made public and visible to others to review. In due time a maintainer +will look at your PR and will merge it if it is deemed an addition to the +codebase. + +## Tips And Tricks +Here I will list some of my tips and tricks that can be useful for you when +developing our codebase. + +- Sounds like a simple and logical one. But always feel free to ask others for + help/advice. This project is an open-source project run by a lot of passionate + people who want to improve the codebase together. Asking for help/advice will + not only help you get your code running but will also show you are interested + and will help you improve your skills. + +- Learn the VS Code shortcuts. This really saves you a lot of time. `Ctrl` + `Shift` + `B` + will build your project. `F5` will run it in debug mode. `Ctrl` + `Shift` + `F` + will global search. `Ctrl` + `P` will find definitions of things (super handy). + +- Use find references when right-clicking on a variable or proc. This will list + all the uses of this var/proc so you can see what it is actually used for or + where you need to change things as well etc. + +- You can remove the build command from F5. This saves you some time when you + develop. Be sure to manually build your code though! This can be done by + removing the `preLaunchTask` line in the debug config: + +![image](./images/quickstart_vsc_tasks_config.png) diff --git a/docs/coding/style_guidelines.md b/docs/coding/style_guidelines.md new file mode 100644 index 000000000000..f95cf196def6 --- /dev/null +++ b/docs/coding/style_guidelines.md @@ -0,0 +1,345 @@ +# Style Guidelines + +These guidelines are designed to maintain readability and establish a standard +for future contributions. By following these guidelines, we can reduce the +overhead during the review process and pave the way for future content, fixes, +and more. + +## Variables + +Variable conventions and naming are an important part of the development +process. We have a few rules for variable naming, some dictated by BYOND itself. +While naming variables can be tough, we ask that variable names are descriptive. +This helps contributors of all different levels understand the code better. +These guidelines only apply to DM, as TGUI uses a different convention. Avoid +using single-letter variables. Variable naming is to follow American English +spelling of words. This means that variables using British English will be +rejected. This is to maintain consistency with BYOND. + +Variables written in DM require the use of snake_case, which means words will be +spaced by an underscore while remaining lowercase. + +```dm +// An example of a variable written in snake_case +var/example_variable +``` + +## Strings and Messages + +### Strings + +When it comes to strings, they should be enclosed in double quotations. Like the +naming convention for variables, American English spelling is to be used. + +```dm +var/example_string = "An example of a properly formatted string!" +``` + +If a string is too long, break it into smaller parts for better readability. +This is especially useful for long descriptions or sentences, making the text +easier to read and understand. + +```dm +var/example_long_string = "This is a longer than average string \ + and how it should be formatted. This \ + is the method we prefer!" +``` + +Variables may be incorporated within strings to dynamically convey their values. +This practice is beneficial when the variable's value may change, enhancing +flexibility and maintainability. Avoid hardcoding values directly into strings, +as it is considered poor practice and can lead to less adaptable code. + +```dm +// Bad +var/bad_example_string = "There are 20 items in the box." + +// Good +var/item_count = 20 +var/good_example_string = "There are [item_count] items in the box." +``` + +### Messages + +Messages are anything that is sent to the chat window. This can include system +messages, messages to the user, as well as messages between users. + +#### Sending to chat + +Though there are multiple ways to send a message to the chat window, only +certain methods will be accepted. Avoid using `<<` when sending information to +the chat window. You can check out other examples throughout the codebase to see +how messages are typically handled. + +```dm +// Bad + world << "Hello World!" + + // Good + to_chat(world, "Hello World!") + +// Also Good +user.visible_message( + "[user] writes Hello World!", + "You write Hello World!.", + "You hear someone writing Hello World!." +) +``` + +#### Common Classes + +- ``'notice'``: used to convey anything the player should be aware of, including + actions, successes, and other pertinent information that is non-threatening. + This also includes information directly unrelated to gameplay. +- ``'warning'``: used for failures, errors, and warnings +- ``'danger'``: danger occurring around the player or to other players, damage + to things around the player +- ``'userdanger'``: used to convey to the player that they are being attacked or + damaged + +These can be set using in-line span tags. + +```dm +proc/my_example_proc + to_chat(user, "Message with the notice style.") +``` + +You are not limited to the styles listed above. It is important, however, to +evaluate and choose the right style accordingly. You can find additional styles +located within the chat style sheets. + +## Comments + +Comments are essential for documenting your code. They help others understand +what the code does by explaining its behavior and providing useful details. Use +comments where needed, even if the code seems clear. Proper commenting keeps the +codebase organized and provides valuable context for development. + +### Single-Line Comments + +Single-line comments are used for brief explanations or notes about the code. +They provide quick, straightforward context to help clarify the code’s purpose +or functionality. + +```dm +// This is a single-line comment +``` + +### Multi-Line Comments + +Used for longer explanations or comments spanning multiple lines. Good for +documenting parameters of procs. + +```dm +/* + * This is a multi-line comment. + * It spans multiple lines and provides detailed explanations. + */ +``` + +### Autodoc Comments + +[Autodoc][] is used for documenting variables, procs, and other elements that +require additional clarification. This is a useful tool, as it allows a coder to +see additional information about a variable or proc without having to navigate +to its declaration. To apply properly, an Autodoc comment should be used +**BEFORE** the actual declaration of a variable or proc. + +```dm +/// This is an Autodoc example +var/example_variable = TRUE +``` + +[Autodoc]: ../references/autodoc.md + +### Define Comments + +When documenting single-line macros such as constants, use the "enclosing +comment format", `//!`. This prevents issues with macro expansion: + +```dm +#define BLUE_KEY 90 //! The access code for the blue key. +``` + +These constant names can then be referred to in other Autodoc comments by +enclosing their names in brackets: + +```dm +/// This door only opens if your access is [BLUE_KEY]. +/obj/door/blue + ... +``` + +### Mark Comments + +Used to delineate distinct sections within a file when necessary. It should only +be used for that purpose. Avoid using it for items, procs, or datums. + +```dm +// MARK: [Section Name] +``` + +### Commented Out Code + +Commented out code is generally not permitted within the code base unless it is +used for the purpose of debugging. Code that is commented out during a +contribution should be removed prior to creating a pull request. If you are +unsure whether or not something should be left commented out, please contact a +development team member. + +## Multi-Line Procs and List Formatting + +When calling procs with very long arguments (such as list or proc definitions +with multiple or especially dense arguments), it may sometimes be preferable to +spread them out across multiple lines for clarity. For some more text-heavy +procs where readability of the arguments is especially important (such as +visible_message), you're asked to always multi-line them if you're providing +multiple arguments. + +For lists that may be subject to frequent code churn, we suggest adding a +trailing comma as well, as it prevents the line from needing to be changed +unnecessarily down the line. + +```dm +// Bad +var/list/numbers = list( + 1, 2, 3) + +user.visible_message("[user] writes the style guide.", +"You wonder if you're following the guide correctly.") + +// Good +var/list/numbers = list( + 1, + 2, + 3, +) + +user.visible_message( + "[user] writes the style guide.", + "You write the style guide.", + "You hear typing." +) + +// Also good +var/list/letters = list("a", "b", "c") +``` + +## Indentation + +Indentation in DM is used to define code blocks and scopes. Our code base +requiress tab spacing. Singular spacing to the length of four spaces will not be +accepted. + +```dm +// Good +for(var/example in 1 to 10) + if(example > 5) + to_chat(world, "Higher than five") + else + to_chat(world, "Lower than five") +``` + +Only when it comes to defines, curly braces can be used. This is allowed in some +instances to keep code neat and readable, and to ensure that macros expand +properly regardless of their indentation level in code + +```dm +// Good +#define FOO /datum/foo {\ + var/name = "my_foo"} +``` + +Not only is it easier to write, but the DM compiler also optimizes the preferred +method to run faster than the bad example. Using the DM style loop enhances +readability and aligns with the language’s conventions. + +## Operators + +### Spacing + +Code readability is an important aspect of developing on a large-scale project, +especially when it comes to open-source. As emphasized by other places in this +document, it is important to keep the code as readable as possible. One way we +do that is through spacing. Maintain a single space between all operators and +operands, including during variable declarations and value assignments. + +```dm +// Bad +var/example_variable=5 + +// Also Bad +example_variable=example_variable*2 + +// Good +var/example_variable = 5 + +// Also Good +example_variable = example_variable * 2 +``` + +## Boolean Defines + +Use `TRUE` and `FALSE` instead of 1 and 0 for booleans. This improves +readability and clarity, making it clear that values represent true or false +conditions. + +```dm +// Bad +var/example = 1 + if(example) + example_proc() + +// Good +var/example = TRUE + if(example) + example_proc() +``` + +## File Naming and References + +### Naming + +When naming files, it is important to keep readability in mind. Use these +guidelines when creating a new file. + +- Keep names short (≤ 25 characters). +- Do not add spaces. Use underscores to separate words. +- Do not include special characters such as: " / \ [ ] : ; | = , < ? > & $ # ! ' { } *. + +### References + +When referencing files, use single quotes (') around the file name instead of +double quotes. + +```dm +// Bad +var/sound_effect = "sounds/machines/wooden_closet_open.ogg" + +// Good +var/sound_effect = 'sounds/machines/wooden_closet_open.ogg' +``` + +## HTML Tag Format + +Though uppercase or mixing cases will work, we prefer to follow the W3 standard +for writing HTML tags. This means tags should be written in lowercase. This +makes code more readable and it just looks better. + +```dm +// Bad +This is an example of how not to do it. + +// Good +This is an example of how it should be. +``` + +## A Final Note + +These guidelines are subject to change, and this document may be expanded on in +the future. Contributors and reviewers should take note of it and reference it +when needed. By following these guidelines, we can promote consistency across +the codebase and improve the quality of our code. Not only that, by doing so, +you help reduce the workload for those responsible for reviewing and managing +your intended changes. Thank you for taking the time to review this document. +Happy contributing! diff --git a/docs/coding/testing_guide.md b/docs/coding/testing_guide.md new file mode 100644 index 000000000000..a87d8bb10b75 --- /dev/null +++ b/docs/coding/testing_guide.md @@ -0,0 +1,225 @@ +# Guide to Testing + +Code by nature works *as coded* and not always *as intended*, while you +know that your code compiles and passes tests on your Pull Request you +may not know if it breaks in edge cases or works fully in-game. In order +to ensure your changes actually work, you will need to know how to +**Test Your Code**. As part of this process, you will learn how to use +various in-game debugging tools to fully utilize your changes in a test +server, analyse variables at run-time, test for edge cases, and stress +test features. This guide will also explain more advanced concepts and +testing such as advanced proc calls, garbage collection testing, and +breakpoints. + +## Prerequisites + +- You will need to first [set up your development environment][setup] and + successfully launch a local server. +- Give yourself host-level permissions on your local server (You should have + access to every verb and tab available in-game). +- Have an open and patient mindset. +- Approach the QA process as if you're asking yourself questions and + answering them by performing successful (or unsuccessful) tests. + +[setup]: ../contributing/getting_started.md + +### Prep Work + +In order to speed this up, especially for experienced devs, know what +you're looking for and write down a list (mental lists work as well) of +what you want to test. If you're only changing an attribute, list the +interactions that attribute has with other functions so you remember to +test each one. If you're adding a new atom, write down possible +interactions that atom may have with other relevant atoms (think parent +objects, tools, materials, machinery such as autholathes, antagonists). + +Make your tests atomic. i.e. don't try and test everything at once, +pick one specific thing (or closely related groups of things) to test +on. If your item affects other items you want to test, consider +restarting and using a clean round or properly cleaning up the test +area. + +### Basic In-Game Tools + +While mastery of these tools is not required, basic familiarity with +them will be paramount to proper testing. + +#### Game Panel + +The Game Panel is a small menu that allows the user to set the game mode +for the round or spawn in atoms (turfs, objects, mobs, etc). In order to +access the Game Panel, you will need to click the *Game Panel* verb +under the admin tab. + +![](./images/Game_Panel.png) + +The Game panel has five buttons: + +1. **Change Gamemode** will allow the user to set the round game mode. This is + only binding if the round has not started yet. + +2. **Create Object** allows the user to spawn in any object. The search bar will + return all type paths relevant to the search given. + +3. **Quick Create Object** allows the user to search for objects in a more + specific scope (only guns, only mechs, etc). + +4. **Create Turf** allows the user to change the turf they are directly over. + +5. **Create Mob** allows the user to spawn in a mob. + +The most important buttons are the four create buttons. By clicking on them you +can open up the game panel create menu. For beginners, there are five important +aspects of the game panel that you will need to know (the other inputs and +buttons are very sparsely used, and likely not needed in your case). + +1. The type path to search for, this input will tell the panel to query + for any typepath that contains the given string, so if you searched + for "book" it would return type paths such as + "machinery/bookBinder", "spellbook/mime/oneuse", or + "book/codex_gigas". Keep in mind this will return *all* type paths + with the given string, since the game panel is tied to your client + CPU usage, trying to search type paths with a query such as "item" + or "mob" will return thousands of results and likely freeze your + client for some time or crash it. +2. The number or amount of the element you want to spawn, if you were + spawning a book and typed in three, it would spawn three books. +3. Where this object will spawn, generally you will want the default + "On the floor below mob" or if you're a human, "in own mob's + hands". If you're specifically trying to spawn the element inside + another object, you can mark the object and use that option. +4. The list of type paths to select, you will need to click the + typepath to select it. Alternatively, if you want to spawn in + multiple types at once, you can click-drag up to 5 type paths and + spawn them all at once. +5. The button that spawns stuff with the parameters you gave the panel. + +![](./images/Game_Panel_Create.png) + +#### Runtime Viewer + +The runtime viewer interface is a menu that displays every runtime that +occurred during the current round. It is available by clicking the *View +Runtimes* verb under the "Debug" tab. + +![](./images/Runtime_Viewer.png) + +The runtime viewer displays a list of *almost* every runtime in a round, a few +unimportant or repeated runtimes are skipped. Essentially, runtimes are errors +that occur when the server is running (as compared to a build error that occurs +when attempting to compile). Clicking on a runtime will open up more details +about it. + +1. The runtime error. This will generally include information about the + type of error (null reference, bad proc calls, etc), what file it + occurred in, what line it occurred at, and information about the + proc it occurred in. Some errors will also include "call stacks" + or the procs called leading up to the error. +2. user VV button, will open the view variables panel on the mob that + caused the runtime +3. user PP button, will open the player panel on the mob that caused + the runtime +4. user follow button, will force the user to follow/orbit the mob that + caused the runtime +5. loc VV button, will open the view variables panel on the loc (turf + or thing that contains the object) of the object that caused the + runtime +6. loc jump button, will force the user to jump to the loc where the + runtime occurred. + +![](./images/Runtime_Viewer_View_Runtime.png) + +## Does it Even Work? + +The first step in testing is to see if your change spawns in/displays +*at all*. This part of testing focuses solely on finding out when and +where your changes break, not particularly how or why it breaks. + +If your change is creation/removal of an atom. open up the [Game Panel](#game-panel) +and see if the atom has been added/removed as a typepath. If it's not, make sure +your code was actually compiled and check to either see if you: + +- actually defined a new typepath properly and have the file ticked in the DME + file, and +- you removed ALL instances where the type path is used (even proc + definitions!). + +If your change is a map change, please see the [Mapping Requirements](../mapping/requirements.md). + +Use the game panel to spawn your atom with the given type path. Ensure the following: + +- Does it appear? +- Is the sprite correct? +- Is the name/appearance/description of the atom correct? + +_Note:_ An Atom in DM refers to all elements of type "area", "turf", "object", +or "mob." Each has different behaviors for spawning, deletion, and interaction, +so keep that in mind. Additionally, there will not be much reference/relevance +in this section to "Area" type atoms. + +### Does it Work the Way You Want it to? + +Test the attributes of your atom: + +- If it has health, can you kill or break it? +- If it has a menu, can you open up and interact with the UI correctly, can you + press buttons? +- If you added a special feature, can you activate it correctly? +- Does your new turf have proper atmospherics? + +You may not have touched a certain section of code, but it's entirely +possible that you broke it with a nearby change, check to make sure it +still works the way it's supposed to (or even at all). For example, if +you modified a variable inside a book object, can the barcode scanner +still scan it into the library system? If you changed the way xenomorphs +handle combat, will disablers, lasers, batons, etc still work the same +way or function at all? + +### Be Concise and Specific + +Your goal here is to break your change in every (relevant) way possible, +use your change in every way you intended it to be used and then use it +every way it wasn't intended to be used. However, this isn't to say +you need to test every use case or test every single object that may be +affected. As a contributor, you have limited time in your day to spend +on coding, don't waste all of it trying out every different testing +scenario. Here are a few tips to be efficient: + +- Know the code so that you know what other objects or parts of a feature will + be affected, then you have a mental list of things that need to be tested. +- Focus on testing one feature at a time, especially ones that you're focused on + coding at the moment; This keeps your attention scoped to that feature so you + can quickly make the needed changes and move on (this will help avoid + "*scope-creep*.") +- If your feature is built on another feature working (i.e. your feature working + *depends* completely on another feature working properly), test the dependency + if your feature is breaking to ensure the point of failure isn't just a + dependency breaking. + +## Why Doesn't it Work? + +The previous question of "does it work" often answers itself just by +spooling up a test environment, however, figuring out why things break +is a much more difficult and in-depth task. This section will avoid +getting into technical discussion and will instead explore conceptually +how to begin understanding why your change is not working. + +### Does it Produce Errors? + +Changes that clearly break often come saddled with a few *runtimes* which are +errors that occur while the server is actively running. In your preferences tab, +you can click the *Toggle Debug Log Messages* verb to toggle on debug message +which will allow you to see runtimes pop up exactly when they happen in the chat +box. You will need to do this every round unless you have a database properly +set up. Additionally, you can view all runtimes in a round by clicking the +[*View Runtimes*](#runtime-viewer) verb in the debug tab to open up the runtime +viewer. + +This will allow you to identify the errors your changes are producing +and possibly even identify where, how, and what is breaking in your +code. Do note that this will often not reveal larger issues with your +code that is sourced from bad design decisions or unintentional effects. + +## TODOs + +Further sections are forthcoming, including assertion checking. diff --git a/docs/coding/testing_requirements.md b/docs/coding/testing_requirements.md new file mode 100644 index 000000000000..9aa22633c354 --- /dev/null +++ b/docs/coding/testing_requirements.md @@ -0,0 +1,77 @@ +# Pull Request Testing Requirements + +Testing is a critical aspect of the pull request process for the development +here at Paradise. Bugs often arise due to insufficient testing, which +can compromise the hard work of our contributors and development team members. +It is mandatory that all pull requests undergo thorough testing before merging. +Failure to comply may result in closure of the pull request and possible +disciplinary action. + +## Testing Procedures + +### Local Testing + +1. Compile and Run: Ensure the code compiles without errors. + +2. Game Loading: Use your preferred debugging method to load the game and verify + changes are applied correctly. + +3. Functional Testing: Test new features or changes to ensure they integrate + smoothly with existing functionality. + +### Validation + +1. Feature Integrity: Confirm that new additions do not break existing game + features. + +2. Performance Testing: Assess the performance impact of changes to ensure they + meet acceptable standards. + +### Comprehensive Review + +1. Edge Cases: Test edge cases to ensure robustness of the code. + +2. Error Handling: Verify error handling mechanisms are effective and + informative. + +### Documentation and Reporting + +1. Update Documentation: If changes impact user-facing features or developer + documentation, update them accordingly. + +2. Reporting: Provide clear and concise feedback in the pull request regarding + testing outcomes and any discovered issues. + +### Test Merging Into Production + +1. Additional Testing: If further validation is necessary, a test merge into + production may be scheduled to assess the code in a live environment. It is + imperative that the code functions correctly before proceeding with a test + merge. + +2. Requesting a Test Merge: Authors of pull requests may request a test merge + during the review phase. Additionally, development team members may initiate + a test merge if deemed necessary. + +3. Responsibilities During Test Merge: If your pull request is selected for a + test merge, it is your responsibility to actively manage and update it as + needed for integration into the codebase. This includes promptly addressing + reported bugs and related issues. + +4. Consequences of Non-compliance: Failure to address requested changes during + the test merge process will result in the pull request being reverted from + production and potentially closed. + +Testing plays a vital role in our process. As an open-source project with a +diverse community of contributors, it's essential to safeguard everyone's +contributions from potential issues. Thorough testing not only helps maintain +the quality of our code but also eases the workload for our development team, +who ensure that each pull request meets our agreed-upon standards. By following +these steps, we can ensure a streamlined process when implementing changes. + +Do you need some help with testing your Pull Request? You can ask questions to +the development team on the [Paradise Station Discord][discord]. We also have a +[guide about testing pull requests here!][testing-guide] + +[discord]: https://discord.gg/YJDsXFE +[testing-guide]: testing_guide.md diff --git a/docs/contributing/getting_started.md b/docs/contributing/getting_started.md new file mode 100644 index 000000000000..dae74269ff74 --- /dev/null +++ b/docs/contributing/getting_started.md @@ -0,0 +1,478 @@ +# Getting Started + +Whether you're looking to contribute code, mapping changes, or sprites to +Paradise Station, you will need to set up a development environment. This guide +provides all the necessary steps to do so. + +![](./images/flowchart.png){: style="width:50%"} + +## Development Environment Setup + +This guide will walk you through the basic steps of installing Git, the program +that tracks changes to the Paradise codebase, as well as Visual Studio Code, the +recommended editor for working with Paradise. + +### Setting Up Visual Studio Code + +Visual Studio Code is the recommended editor for working with Paradise and other +SS13 codebases. + +1. Go to VS Code's website: +2. Download the appropriate build for your system and install it. + +### Setting Up Git + +Git is the program that allows you to share your code changes with the Paradise +codebase. Git can be daunting to contributors unfamiliar with source control, +but is an incredibly powerful tool, and there are many resources online to learn +how to use it and understand its concepts. + +To install git: + +1. Go to the [Git][] website and download the installer for your operating system. +2. Run the installer, leaving all the default installation settings. + +For a straightforward beginner's guide to git, see GitHub's [Git Guides][]. + +For more guidance on installing git, see the Git Guide for [Installing Git][]. + +GitHub also provides a graphical environment for working with git called [GitHub +Desktop][]. This is not necessary to contribute to Paradise Station, as Visual +Studio Code also has powerful git integration. + +[Git]: https://git-scm.com/downloads +[Installing Git]: https://github.com/git-guides/install-git +[Git Guides]: https://github.com/git-guides +[GitHub Desktop]: https://github.com/apps/desktop + +#### Additional Help + +For instructional videos on Visual Studio Code's GitHub integration, see . + +For a straightforward beginner's guide to git, see GitHub's [Git Guides][]. + +For more guidance on installing git, see the Git Guide for [Installing Git][]. + +For introductory videos on Git, see: + +- [Introduction to Git with Scott Chacon of GitHub](https://www.youtube.com/watch?v=ZDR433b0HJY) +- [Git From Bits Up](https://www.youtube.com/watch?v=MYP56QJpDr4) +- [Linus Torvalds (inventor of Linux and Git) on Git](https://www.youtube.com/watch?v=4XpnKHJAok8) + +### Registering a GitHub Account + +GitHub is the service where the Paradise codebase is stored. You'll need a +GitHub account to contribute to Paradise. Go to the [GitHub signup page][] and +register with a username and e-mail account. + +[GitHub signup page]: https://github.com/signup + +### Hiding Your Email Address + +Changes to Git repositories include the e-mail address of the person who made +the change. If you don't wish your email address to be associated with your +development on Paradise, you can choose to hide your email when interacting with +repositories on GitHub: + +1. Log into your GitHub account. +2. Go to . +3. Select the _Keep my email addresses private_ checkbox. + +This means that while your e-mail address is associated with your GitHub +account, any changes you make will only be keyed to a generic e-mail address +with your username. + +## Installation for Linux Users + +The code is fully able to run on Linux, however Windows is still the recommended +platform. The libraries we use for external functions (rust-g and MILLA) require +some extra dependencies. + +### Building rust-g for Debian-based Distributions + +1. Download the latest release from +2. Run the following command: + + ```sh + apt-get install libssl-dev:i386 pkg-config:i386 zlib1g-dev:i386 + ``` + +3. After installing these packages, rust-g should be able to build and function + as intended. Build instructions are on the rust-g GitHub. We assume that if + you are hosting on Linux, you know what you are doing. +4. Once you've built rust-g, you can build MILLA similarly. Change into the + `milla/` directory and run: + + ```sh + cargo build --release --target=i686-unknown-linux-gnu + ``` + +## Cloning the Repository + +Cloning the Paradise repository only has to be done once. + +1. Visit the [repository][] and press the _Fork_ button in the upper right corner. + + ![](./images/fork_repository.png) + +[repository]: https://github.com/ParadiseSS13/Paradise + +2. Launch Visual Studio Code. Select the Source Control panel on the sidebar, + and click _Clone Repository_. + + ![](./images/vsc_clone_repository.png) + + If that’s not there, you can press `Ctrl`+`Shift`+`P` to open the command + palette, then type `Git: Clone` and then press `Enter`. + +3. Paste the URL of the repository you created in the last step. It should look + like this: `https://github.com/YOURNAME/Paradise`. Then, select a folder to + keep your local repository. The process of downloading might take a while. + Once it’s downloaded, open the folder in Visual Studio Code. + +## Installing Recommended Visual Studio Code Extensions + +When you first open the Paradise repository in Visual Studio Code, you will also +get a notification to install some recommended extensions. These plugins are +extremely useful for programming with BYOND and should be considered essential. +If you don't see the prompt to install the recommended extensions, they can be +found by searching for `@recommended` in the Extensions panel, or installed from +the list below. + +- [DreamMaker Syntax Highlighting](https://marketplace.visualstudio.com/items?itemName=gbasood.byond-dm-language-support) +- [BYOND Language Support](https://marketplace.visualstudio.com/items?itemName=platymuus.dm-langclient) +- [EditorConfig](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) +- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) +- [GitLens](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens) +- [ErrorLens](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens) +- [DreamMaker Icon Editor](https://marketplace.visualstudio.com/items?itemName=anturk.dmi-editor) +- [Prettier Code Formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) +- [ZipFS](https://marketplace.visualstudio.com/items?itemName=arcanis.vscode-zipfs) + +## Adding Paracode as an Upstream Repository + +We need to add the main Paradise repository as a remote now. + +1. Open the command palette (`Ctrl`+`Shift`+`P`), type `Git: Add Remote`, and + press `Enter`. You'll be prompted for the URL of the remote, and then the name + of the remote. + +2. Enter `https://github.com/ParadiseSS13/Paradise` for the URL, and `upstream` + for the name. After you've done that, you’ll have the main Paradise + repository as a remote named `upstream`. This will let you easily send your + pull requests there later. + +## Configuring a Local Database + +While configuring a local database is not required, it is recommended because it +tracks local round IDs, stores local player characters, and allows you to verify +the output of blackbox entries. + +### Initial setup and Installation + +1. Download and install [MariaDB](https://mariadb.com/downloads/mariadb-tx) for + your operating system. The default installation settings should work. You + need TCP enabled and to set a root password. If it offers, do _not_ set it up + to use Windows authentication. If you've ticked Install as a Windows Service + (should be ticked by default), it will run whenever you boot up your + computer, so there's no need to worry about starting it manually. + +2. Open HeidiSQL (comes with MariaDB) and connect it to the database. Click on + *New* to create a new session, check *prompt for credentials* and leave the + rest as default. + +3. Click *Save*, then click open and enter in `root` for the username and the + password you set up during the installation. + +4. Select the database you just created and then select *File -> Load SQL File*, + and open the `paradise_schema.sql` file found in the `SQL/` directory of the + game. + +5. Press the blue "play" icon in the topic bar of icons. If the schema imported + correctly you should have no errors in the message box on the bottom. + +6. Refresh the panel on the left by right clicking it and ensure there's a new + database called `paradise_gamedb` created. + +7. Create a new user account for the server by going to *Tools -> User Manager*. + 'From Host' should be `127.0.0.1`, not `localhost` if hosted locally. + Otherwise, use the IP of the game server. For permissions, do not give it any + global permissions. Instead click *Add Object*, select the database you + created for the server, click *OK*, then give it `SELECT`, `DELETE`, + `INSERT`, and `UPDATE` permissions on that database. + +8. You can click the arrow on the password field to get a randomly generated + password of certain lengths. copy the password before saving as it will be + cleared the moment you hit *Save*. + +9. Open the file `config/config.toml` in your text editor (such as VS Code) + scroll down to the `[database_configuration]` section. You should've copied + the file over from the `config/example` folder beforehand. + +10. Make sure that these settings are changed: + + - `sql_enabled` is set to `true`. + - `sql_version` to the correct version. By starting the server with a + mismatched version here and all the other settings set up, the chat box + will tell you the current version in red text, between the messages for + all the subsystems initializing. Set this to the current version. + - `sql_address` is set to `"127.0.0.1"`. (Replace with the database server's + IP if not hosted locally) + - `sql_port` is set to whatever port was selected during the MariaDB + install, usually `3306`. + - `sql_database` is set to the name of your database, usually + `"paradise_gamedb"`. + - `sql_username` is set to the 'User name' of the user you created above. + - `sql_password` is set to the randomly generated 'Password' of the user you + created above. + +The database is now set up for death logging, population logging, polls, +library, privacy poll, connection logging and player logging. There are two more +features which you should consider. And it's best to do so now, since adopting +them later can be a pain. + +### Database based administration + +Offers a changelog for changes done to admins, which increases +accountability (adding/removing admins, adding/removing permissions, +changing ranks); allows admins with `+PERMISSIONS` to edit other admins' +permissions ingame, meaning they don't need remote desktop access to +edit admins; Allows for custom ranks, with permissions not being tied to +ranks, offering a better ability for the removal or addition of +permissions to certain admins, if they need to be punished, or need +extra permissions. Enabling this can be done any time, it's just a bit +tedious the first time you do it, if you don't have direct access to +the database. + +To enable database based administration: + +- Open `\config\config.toml` and scroll to the `[admin_configuration]` + section. +- Set `use_database_admins` to `true`. +- Add a database entry for the first administrator (likely yourself). +- Done! Note that anyone set in the `admin_assignments` list will no + longer be counted. +- If your database ever dies, your server will revert to the old admin + system, so it is a good idea to have `admin_assignments` and + `admin_ranks` set up with some admins too, just so that the loss of + the database doesn't completely destroy everything. + +## Working with `config.toml` + +Paradise relies on a configuration file for many key aspects of its operation. +That file is `config\config.toml`, and it contains many setting that are useful +when developing locally. When you first clone the Paradise repository, +that file will not exist; that is because it is ignored by git so that people's +individual configurations do not get uploaded to the master repository. + +In order to modify the settings in it, you must copy the file +`config\example\config.toml` to its parent directory. + +Some helpful uses of `config.toml` follow. + +### Overriding the next map + +If you are testing a specific map, the `override_map` setting under the +`system_configuration` setting can be set to any map datum listed in +[`code/modules/mapping/station_datums.dm`][datums]. If you are testing something +that doesn't require a station map, such as ruins, you can avoid loading a large +map altogether and save yourself some time starting the server by setting +`override_map` to `"/datum/map/test_tiny"`. + +[datums]: https://github.com/ParadiseSS13/Paradise/blob/master/code/modules/mapping/station_datums.dm + +### Enabling or disabling Lavaland and Space levels + +If you do not need to load Lavaland, any of its ruins, or any space ruins, you +can change these settings under `ruin_configuration`: + +- `enable_lavaland`: whether to load the Lavaland z-level. +- `enable_space_ruins`: despite its name, this controls whether ruins are + spawned for both space _and_ Lavaland. +- `minimum_zlevels` and `maximum_zlevels` control the number of space z-levels + generated. If you don't need any, set both to `0`. + +### Enabling or disabling station traits + +You can control whether roundstart station traits roll with the +`add_random_station_traits` setting under the `gamemode_configuration` section. + +## Publishing Changes + +First, let's talk about **branches**. First thing to do is to make a new +branch on your fork. This is important because you should never make +changes to the default(master) branch of your fork. It should remain as +a clean copy of the main Paradise repository. + +**For every PR you make, make a new branch.** This way, each of your +individual projects have their own branch. A commit you make to one +branch will not affect the other branches, so you can work on multiple +projects at once. + +### Branching + +To make a new branch, open up the source control sidebar. Navigate to +the Branches section and open it. You should only have the master +branch. You can create a new branch by going and clicking on the Create +Branch button. + +![](./images/VSCodeBranching.png) + +It will then prompt you at the top of your screen to name your new +branch, then select Create Branch and Switch. For this guide, I'll be +creating a new hat, so I'll name my branch `hat-landia`. If you look at +the bottom left hand corner, you'll see that VS Code has automatically +checked out our +branch: + +![](./images/VSCodeBranchExample.png) + +Remember, **never commit changes to your master branch!** You can work +on any branch as much as you want, as long as you commit the changes to +the proper branch. + +Go wild! Make your code changes! This is a guide on how to contribute, +not what to contribute. So, I won't tell you how to code, make sprites, +or map changes. If you need help, try asking in the `#spriting` or the +`#coding_chat` Discord channels. + +### Changing Code + +You'll find your code to edit in the Explorer sidebar of VS Code; if you need to +find something, the Search sidebar is just below that. + +If you want to use DreamMaker instead, go ahead and edit your files there - once +you save them, VS Code will detect what you’ve done and you’ll be able to follow +the guide from there. + +If you do anything mapping related, it is highly recommended you use +StrongDMM and check out the [Mapping Quickstart](../mapping/quickstart.md). + +Now, save your changes. If we look at the Source Control tab, we'll see +that we have some new changes. Git has found every change you made to +your fork's repo on your computer! Even if you change a single space in +a single line of code, Git will find that change. Just make sure you +save your files. + +### Testing Your Code + +The easiest way to test your changes is to press `F5`. This compiles your code, +runs the server and connects you to it, as well as automatically giving you +admin permissions. It also starts a debugger that will let you examine what went +wrong when a runtime error happens. If you want to avoid the debugger press +`Ctrl` + `F5` instead. + +If `F5` does not automatically start a local server, you might have installed +BYOND on a custom path and VSC did not find it. In this case, try the following: + +1. Press `Ctrl` + `,` to open VSC settings. +2. Type "DreamMaker", select "DreamMaker language client + configuration". +3. Under "DreamMaker: Byond Path", add your path to BYOND (for + example, `D:\Program Files (x86)\BYOND`). +4. Press OK and close the tab. +5. Press `F5` to run the server. + +If that does not work, you can compile it into a dmb file and run it in +Dream Daemon. To do so, select the dmb file, set security to Trusted and +hit GO to run the server. After the server starts you can press the +button above the GO / STOP button (now red) to connect. + +Do note that if you compile the game this way, you need to manually make +yourself an admin. For this, you will need to copy everything from +`/config/example` into `/config`. Then you will need to edit the +`/config/config.toml` file by adding a +`{ckey = "Your Name Here", rank = "Hosting Provider"}` line to the +`admin_assignments` list. + +![](./images/DreamDaemon.png) + +Be sure to always test not only if your changes work, but also if you +didn't actually break something else that might be related. + +### Committing to Your Branch + +Hover over the word **Changes** and press the plus sign to stage all +modified files. It should look like this: + +![](./images/VSCodeStageChanges.png) + +Or, pick each file you want to change individually. Staged files are the +changes you are going to be submitting in commit, and then in your pull +request. Once you've done that, they'll appear in a new tab called +Staged Changes. + +![](./images/VSCodeStagedChanges.png) + +Click on one of the code files you've changed now! You'll see a compare +of the original file versus your new file pop up. Here you can see, line +by line, every change that you made. Red lines are lines you removed or +changed, and green lines are the lines you added or updated. You can +even stage or unstage individual lines, by using the More Actions +`(...)` menu in the top right. + +Now that you've staged your changes, you're ready to make a commit. At +the top of the panel, you'll see the Message section. Type a descriptive +name for you commit, and a description if necessary. Be concise! + +Make sure you're checked out on the new branch you created earlier, and +click the checkmark! This will make your commit and add it to your +branch. It should look like this: + +![](./images/VSCodeCommit.png) + +There you go! You have successfully made a commit to your branch. This +is still 'unpublished', and only on your local computer, as indicated by +the little cloud and arrow icon in the bottom left corner. + +![](./images/VSCodePublishBranch.png) + +Once you have it committed, you'll need to push/publish to your GitHub. +You can do that by pressing the small cloud icon called "publish +branch". + +### Publishing to GitHub + +Go to the [Main repository](https://github.com/ParadiseSS13/Paradise/) +once your branch is published, GitHub should then prompt you to create a +pull request. This should automatically select the branch you just +published and should look something like this. + +![](./images/GithubCreatePR.png) + +If not, you'll need to open a Pull Request manually. You'll need to select +_Compare across forks_, then select the upstream repo and target the master +branch. + +Then, you'll be able to select the title of your PR. The extension will make +your PR with your selected title and a default description. **Before +submitting**, ensure that you have properly created your PR summary and followed +the description template. + +#### Changelogs + +Changelogs should be player focused, meaning they should be understandable and +applicable to your general player. Keep it simple: + + fix: fixed a bug with X when you Y + tweak: buffed X to do Y more damage. + +Avoid coding-lingo heavy changelogs and internal code changes that don't visibly +affect player gameplay. These are all examples of what you shouldn't add: + + tweak: added the NO_DROP flag to X item. + tweak: refactored DNA to be more CPU friendly + +If the only changes you're making are internal, and will not have any effect on +players, you can replace the changelog section with _NPFC_, meaning "No +Player-Facing Changes". + +ShareX is a super useful tool for contributing as it allows you to make GIFs to +display your changes. you can download it [here](https://getsharex.com/). + +If all goes well, your PR should look like this: + +![](./images/ExamplePR.png) + +If you want to add more commits to your PR, all you need to do is just push +those commits to the branch. diff --git a/docs/contributing/images/DreamDaemon.png b/docs/contributing/images/DreamDaemon.png new file mode 100644 index 000000000000..45c79a93cab3 Binary files /dev/null and b/docs/contributing/images/DreamDaemon.png differ diff --git a/docs/contributing/images/ExamplePR.png b/docs/contributing/images/ExamplePR.png new file mode 100644 index 000000000000..7d04111e435c Binary files /dev/null and b/docs/contributing/images/ExamplePR.png differ diff --git a/docs/contributing/images/GithubCreatePR.png b/docs/contributing/images/GithubCreatePR.png new file mode 100644 index 000000000000..1133a4000d1b Binary files /dev/null and b/docs/contributing/images/GithubCreatePR.png differ diff --git a/docs/contributing/images/VSCodeBranchExample.png b/docs/contributing/images/VSCodeBranchExample.png new file mode 100644 index 000000000000..e3892dd4f374 Binary files /dev/null and b/docs/contributing/images/VSCodeBranchExample.png differ diff --git a/docs/contributing/images/VSCodeBranching.png b/docs/contributing/images/VSCodeBranching.png new file mode 100644 index 000000000000..f6fca9285b31 Binary files /dev/null and b/docs/contributing/images/VSCodeBranching.png differ diff --git a/docs/contributing/images/VSCodeCommit.png b/docs/contributing/images/VSCodeCommit.png new file mode 100644 index 000000000000..52c00fdfd908 Binary files /dev/null and b/docs/contributing/images/VSCodeCommit.png differ diff --git a/docs/contributing/images/VSCodePublishBranch.png b/docs/contributing/images/VSCodePublishBranch.png new file mode 100644 index 000000000000..8ceea31cd55c Binary files /dev/null and b/docs/contributing/images/VSCodePublishBranch.png differ diff --git a/docs/contributing/images/VSCodeStageChanges.png b/docs/contributing/images/VSCodeStageChanges.png new file mode 100644 index 000000000000..435416d9e021 Binary files /dev/null and b/docs/contributing/images/VSCodeStageChanges.png differ diff --git a/docs/contributing/images/VSCodeStagedChanges.png b/docs/contributing/images/VSCodeStagedChanges.png new file mode 100644 index 000000000000..caf2ce3218ce Binary files /dev/null and b/docs/contributing/images/VSCodeStagedChanges.png differ diff --git a/docs/contributing/images/flowchart.png b/docs/contributing/images/flowchart.png new file mode 100644 index 000000000000..4fb4ebffffab Binary files /dev/null and b/docs/contributing/images/flowchart.png differ diff --git a/docs/contributing/images/fork_repository.png b/docs/contributing/images/fork_repository.png new file mode 100644 index 000000000000..a4381c823e3a Binary files /dev/null and b/docs/contributing/images/fork_repository.png differ diff --git a/docs/contributing/images/reviewer_add_pr_suggestion.png b/docs/contributing/images/reviewer_add_pr_suggestion.png new file mode 100644 index 000000000000..7b3d0cc87f0c Binary files /dev/null and b/docs/contributing/images/reviewer_add_pr_suggestion.png differ diff --git a/docs/contributing/images/reviewer_pr_comment.png b/docs/contributing/images/reviewer_pr_comment.png new file mode 100644 index 000000000000..7ea843be4629 Binary files /dev/null and b/docs/contributing/images/reviewer_pr_comment.png differ diff --git a/docs/contributing/images/reviewer_pr_conversation.png b/docs/contributing/images/reviewer_pr_conversation.png new file mode 100644 index 000000000000..9c095126faa8 Binary files /dev/null and b/docs/contributing/images/reviewer_pr_conversation.png differ diff --git a/docs/contributing/images/reviewer_pr_suggestion_text.png b/docs/contributing/images/reviewer_pr_suggestion_text.png new file mode 100644 index 000000000000..7b9783a9677a Binary files /dev/null and b/docs/contributing/images/reviewer_pr_suggestion_text.png differ diff --git a/docs/contributing/images/reviewer_pr_suggestions.png b/docs/contributing/images/reviewer_pr_suggestions.png new file mode 100644 index 000000000000..30d818335f97 Binary files /dev/null and b/docs/contributing/images/reviewer_pr_suggestions.png differ diff --git a/docs/contributing/images/vsc_clone_repository.png b/docs/contributing/images/vsc_clone_repository.png new file mode 100644 index 000000000000..0f48d8004d28 Binary files /dev/null and b/docs/contributing/images/vsc_clone_repository.png differ diff --git a/docs/contributing/quality_prs.md b/docs/contributing/quality_prs.md new file mode 100644 index 000000000000..fa2c36e5c4fa --- /dev/null +++ b/docs/contributing/quality_prs.md @@ -0,0 +1,272 @@ +# A Guide to Good Quality Pull Requests + +by *Sirryan* + +Hello community members! This guide will help you improve your contributions to +our awesome codebase. To do this, I hope to help you understand how to make PRs +that are atomic, easy to review, and well-documented. + +## Introduction + +Like most contributors on Paradise, my interest in contributing to our codebase +sparked from my love for the game and a desire to improve it. Learning how to +even run a local server and set up git is the first hurdle, followed by learning +DM, and eventually crawling your way to making your first change to a local +branch. At this point one's love for contributing can start to bloom because +you're capable of molding the game into something more pleasing for yourself. + +However, your newly-developed skills and motivation must also be accompanied +with certain responsibilities and adherence to our community guidelines. Instead +of editing code in a bubble, you are now submitting changes to an actual game, +played by hundreds of players, and supported by 100s of community members. You +must not only sell your change to our development team but also make sure that +we can understand what's being changed in the first place and that your change +is beneficial for the server. + +## Making "Good" Pull Requests + +Pulling back from the coding world for a moment, I want to talk about another +community based platform, Wikipedia. One of my favorite things about Wikipedia +is the thorough, battle-tested article quality review system they have. The +crown jewel of this rating system is called a "good article" and it embodies +everything desirable you could ever want in a wikipedia article whether you're a +writer or a reader. While "good articles" are a restricted rating (your topic +has to be important enough), there are lower ratings any article can attain +which sets the best standards for all articles! I feel as though Github Pull +Requests can be similarly scrutinized. + +Much like a "Good Article," on our GitHub a "Good PR" has the following +characteristics: + +- **Your PR is limited in scope.** The PR only has one intended purpose or + change. the PR _only_ changes code that is needed for the purpose of that + change. +- **Your PR is designed to be reviewed.** You leverage code comments and design + logic in a way that a relatively experienced reviewer can understand your + change quickly. +- **Your PR is properly documented.** The entire PR template is filled out, all + changes are documented, and you provide ample justification for your changes. +- **Your PR is tested.** You loaded your changes on a local test server and + systematically checked all changes. This testing is not only documented but + very thorough. + +All of these things will ensure ease of review and expedient flow of your Pull +Request through our GitHub pipeline. To best understand how to reach a level of +quality such as a "Good PR," I'm going to break down each of those points in +detail. + +## Limit your Pull Request's Scope + +One common issue with PRs is something called **"scope creep"** where the scope +of your pull request--the full window of files/lines you are changing--expands +beyond the original intended changes of the PR. Expanding your pull-request +extends voting, review, and testing in such a way that your PR is at a higher +risk of becoming stale, getting conflicted, and potentially being closed. So it +is in your best interest to constrain the size of your PR as much as possible. + +But where's a good place to start? Well, when you're writing your intended +change, try not get side-tracked adding that miscellaneous feature, tweaking +that related system, and/or thinking about altering a few `to_chat`'s here and +there along the way. You might feel like you're improving the game (you probably +are) but this distracts from the more important aspect of your Pull Request, +which is the original intent of your PR. This is a great time to write those +off-topic ideas/changes down somewhere else where you can maybe think about +picking them up later in another PR. + +By limiting your pull request to your originally planned (singular) change you +are keeping your Pull Request **atomic.** Specifically you should: + +- **Focus on One Issue:** If you're fixing a bug, limit your PR to that bug fix. + If you're adding a feature, don't mix it with unrelated refactoring. Pick one + thing and do it really well! +- **Keep it Small and Manageable:** Smaller PRs are easier to review and less + likely to introduce new bugs. It's a lot easier to deconflict and debug 10 PRs + that each change 5 files than to deconflict and debug 1 PR that changes 50. +- **Incremental Changes:** Break down large changes into smaller, logical parts. + Submit these parts as separate PRs. If your feature requires a refactor of a + system or several bug fixes to an object, do those first separately in a + different PR before expanding content. + +While I'm mostly writing this section to make my life easier when I inevitably +review your Pull Request down the road… keeping your PRs small and atomic go a +long way to preserving your time & energy. + +## Make Your Code Easier to Review + +**Fact:** A well-structured & documented PR is easier and faster to review. + +When someone opens a Pull Request, all of the files changed is brand new code to +me for the most part. Especially if you are refactoring or adding new content. +So I need a bit of help or I'm gonna be banging my head against my monitor for a +while. After 30 minutes of confused reading I'm going to abandon my review and +tell you to document your code better before I start again. + +To help you visualize, I would like to take you on a minor code adventure. +Either through GitHub search or your local code editor find the definition for +`/datum/controller/subsystem`, it should be in `subsystem.dm`. Take a peak +around that file, you'll end up seeing a whole lot of code comments. Pick a +random variable or proc and try to explain what it does (rubber ducky debugging +style) to an imaginary friend. Now imagine trying to do that without any of the +comment documentation in the file. You likely can't, not without digging for a +long while and following all of the code logic yourself. Do you even know where +to start? + +I'm not going to write an essay on this section since it really boils down to a +lot of best practices you will learn along the way, so here's some bullets to +brush over: + +**Code Comment:** + +- **Function Headers:** Add comments at the beginning of each function to + explain its purpose, input parameters, and return values. +- **Complex Logic:** Place comments above or alongside complex or non-obvious + code sections to clarify the logic and intent. +- **Avoid Obvious Comments:** Do not comment on code that is self-explanatory, + as it can clutter the codebase. + +**Naming Conventions:** + +- **Descriptive Names:** Use clear and descriptive names for functions and + variables that convey their purpose and use. Name it how it is please! +- **Consistency:** Follow the Paradises naming conventions (snake_case for most + things). +- **Avoid Abbreviations:** Use full words instead of abbreviations unless they + are widely understood and standard within the codebase. +- **Action-Oriented:** Name functions based on what they do (e.g., + calculate_score(), visible_message(), update_appearance()). +- **Meaningful and Contextual:** Choose names that reflect the variable's role + or content (e.g., telecrystal_count, account_pin, summoner). + +By adhering to these practices, you help ensure that your code is understandable +and maintainable, making it easier for reviewers and other contributors to work +with. + +## Fill Out the PR Template + +When creating a pull request, it's crucial to provide clear, detailed +information. Your title and description are the first things anyone sees and are +essential for communicating the intent and scope of your changes to the Design, +Balance, and Review team. + +**Be #26094 and Not #7003** + +This means your PR should be sufficiently presented, like #26094, which is +detailed and precise, rather than vague and insufficient, like #7003. + +**Use Proper PR Titles and Descriptions** + +- **Titles:** Use clear and concise titles that summarize the change. Avoid + vague or ambiguous titles. For example, instead of "Fix bug," use "Fix crash + caused by null reference in inventory system." +- **Descriptions:** Provide a detailed description of your changes. Explain what + the PR does, why it's needed, and how it affects the game. This should be a + comprehensive overview that leaves little to interpretation. + +**Communicating and Visualizing Your Changes** + +When modifying game features, include: + +- **Proper Descriptions:** Clearly describe the changes made to features. + Explain how these changes alter the game's functionality or balance. +- **Screenshots and Videos:** Provide visual evidence of changes, such as + screenshots or videos. This is especially important for UI changes, sprite + updates, or any feature that affects the visual aspect of the game. Include + before-and-after images if applicable. +- **Proof of Functionality:** Demonstrate that the feature works as intended in + the game. This could include video clips of gameplay or detailed descriptions + of test cases and results. + +**Provide Justifications for Changes** + +It's important to justify why your changes are beneficial: + +- **Rationale:** Explain why the change is necessary. Avoid stating personal + opinions without support. Instead, provide a well-reasoned explanation of the + issue being addressed or the improvement being made. +- **Community Consensus:** Mention if there has been discussion among credible + community members. Link to relevant forum threads, Discord conversations, or + other community discussions that show a consensus or strong support for the + change. +- **Data and Evidence:** If applicable, provide data or consistent anecdotal + evidence to support the need for the change. This could include bug reports, + player feedback, or metrics showing a problem with the current state of the + game. + +You must ensure that your PR provides all necessary information for a thorough +review. This not only helps maintain the quality and balance of the game but +also speeds up the review process by reducing back-and-forth questions and +clarifications. Remember, a well-prepared PR reflects well on you as a +contributor and helps maintain a high standard for the codebase. + +## Test Your Code + +Testing your code is a crucial part of our development process, especially when +contributing to a multiplayer game like SS13. While we're working on a +specialized article that will delve deeper into best practices for code testing, +this section will cover the essential aspects and front-facing impacts of +testing, as well as how to effectively communicate your testing efforts in your +pull request. + +**The Basics of Code Testing** + +At a bare minimum, your PR should indicate that you've successfully compiled +your code and tested it on a local test server. This basic step assures +reviewers and other team members that your code runs without immediate issues +and doesn't cause server crashes. However, thorough testing goes beyond just +ensuring the game compiles. + +**Document Your Testing Process** + +When documenting your testing, include detailed steps that you took to verify +your changes. This helps reviewers understand how you tested the functionality +and provides a reference for anyone else looking to verify your work. Consider +the following: + +- **Feature Verification:** Describe how you verified that new features work as + intended. For example, if you added a new item, detail how you tested its + creation, usage, and any unique interactions it might have. +- **Edge Case Testing:** Test and document scenarios where players might use + your feature in uncommon but predictable ways. This helps catch issues that + may not be immediately obvious but could arise in actual gameplay. +- **Performance Considerations:** If your changes could impact game performance, + mention how you tested for this. For example, if you introduced a new loop or + complex logic, describe any stress tests or performance profiling you + conducted. + +**Communicate Testing Results** + +In your PR description, clearly communicate the results of your testing. Did +everything work as expected? Were there any unexpected issues? If you +encountered bugs that you couldn't resolve, note them and explain why they are +there, how they affect the game, and if there are plans to address them later. + +**The Importance of Thorough Testing** + +Thorough testing is vital for maintaining the quality and stability of the game. +It helps prevent embarrassing situations where a new feature is eagerly +anticipated by players, only to be released in a broken or incomplete state. +Reverting a PR or dealing with numerous bug reports due to avoidable issues can +be frustrating for both the development team and players. + +**Additional Resources** + +For a more comprehensive guide on testing your PR, refer to the [Guide to +Testing](../coding/testing_guide.md). This resource will provide detailed +instructions and best practices for ensuring your changes are robust and +reliable. Testing your code is not just a technical necessity; it's a +professional courtesy to your fellow developers and the player community. By +taking the time to thoroughly test and document your changes, you contribute to +a more enjoyable and stable game experience for everyone. + +## Conclusion + +As someone who's been developing at Paradise for several years and has briefly +served as a head of staff, the quality of our community is of high importance to +me. This article is a call to our contributing community to elevate their pull +requests to a quality they can truly be proud of. While Paradise may not always +lead in content expansion or player counts, it consistently sets the standard +for professionalism and server quality. By adhering to the guidelines outlined +in this article, you can continue to help us maintain and enhance our reputation +as a top-tier server, known for its stability, thoughtful design, and vibrant +community. Let's work together to make our GitHub repository a better experience +for everyone who enjoys Paradise. diff --git a/docs/contributing/reviewer.md b/docs/contributing/reviewer.md new file mode 100644 index 000000000000..96dede831cd0 --- /dev/null +++ b/docs/contributing/reviewer.md @@ -0,0 +1,317 @@ +# Reviewer Crash Course + +by *Sirryan* + +Hey everyone, I noticed some people were not sure how to approach reviewing PRs +so I figured I would write up a small guide on PR reviewing and how people like +our Headcoders, commit access, Lewcc, S34N, and I all do our jobs. In addition +to some guidance and pointers on PR reviewing, I will also go over a few code +examples and point out code standard corrections and basic errors that +prospective reviewers can begin to start on. + +## What is code review? + +> Code reviews act as quality assurance of the code base.... *and* can also act +> as a second step in identifying bugs, logic problems, or uncovered edge cases. +> [(source)](https://about.gitlab.com/topics/version-control/what-is-code-review/) + +This is a quote from a gitlab article on the importance of code reviews and code +reviewer teams. It refers to code reviews as a process in which code is +scrutinized (often by more experienced developers); In this process bugs, bad +logic, and unforeseen consequences of changes are uncovered and identified. This +implies that some code will not be high quality, it will have bugs, the logic +used may be illogical, and the actual execution of the code may produce results +not originally intended by the author. By addressing these quality issues in +review, we can eliminate security issues, increase collaboration, discover bugs +earlier, share knowledge, enforce code standards, and ultimately improve our +game. + +Understanding code review first comes from understanding what a pull request is +and the process contributors go through to add their code to the Paradise +Station codebase. When a community member wants to alter the game in some +format, be it a feature, backend processes, or artistic style, they must modify +our game code in some fashion. They will often do this through services such as +visual studio code, GitHub desktop, gitkraken, tortoisegit, etc. Eventually they +will be ready to request that our repository owners merge their new changes into +the codebase. This is a pull request. + +This is the point where code review comes in. The author's code is now publicly +visible and therefor available for anyone with an account to review. If you have +closely followed any PR that changed code in a significant way, you will see +that many people will chip in with their opinion or "comment" on code snippets. + +![image](./images/reviewer_pr_comment.png) + +This is the most basic form of review. Most people may understand "code review" +as developers suggesting changes to the authors proposed changes or providing +critical review of a code structure; Ultimately, review is just a conversation +between two developers. Feedback, questions, and advice are all **valid and +necessary** parts of the code review as much as a code suggestion or comment may +be. In fact, it may be even more important than suggesting the author change a +`src.loc` to a `loc`. Questions such as "What does X do?" or "I know that Y has +done this before, what happens when Z?" ask the author to take a closer look at +their own code and help you understand their intention and goals. Please note: +ITS IMPORTANT TO READ PR DESCRIPTIONS, you should not be reviewing a PR until +you know what it's actually attempting to do. + +**But Sirryan, that's not the *kind* of code review I'm interested in learning +about**. Yes, yes, I know, I'm getting there. While its important to understand +the conversation (and relationship-building) parts of code review, there's also +important technical parts to review that keep our codebase moving. Before +getting into HOW to code review, we will take a look at the two types of +technical code reviews. + +## Comments +Basic comments are when a reviewer leaves a message, question, or directive for +the PR author at a certain code line or chunk. For example, SteelSlayer has left +a comment on Line 12 of `objective.dm` inquiring about a certain variable. The +focus of this conversation here is on this one comment and there is room for the +author (and possibly other reviewers) to enter the discussion. Commenting on +specific places on code helps keep the conversation focused and allows minor +issues to be addressed quickly and efficiently. + +![image](./images/reviewer_pr_conversation.png) + +## Suggestions +Suggestions are when a reviewer suggests/requests a change to a certain line or +chunk of code. This leaves less agency for the PR author (especially when +suggested by a development team member or experienced reviewer) but allows for +the issue to be cleared up much more quickly and with only the click of a couple +buttons. In this case, I have physically wrote out the code I would like to +change this line to, in this case I want `if(contents.len>=max_n_of_items)` +changed to `if(length(contents) >= max_n_of_items)`. These types of reviews are +most critical for enforcing code standards and making 1-5 line corrections. + +![image](./images/reviewer_pr_suggestions.png) + +## Leaving PR Reviews +The way you leave any form of comment or suggestion directly on a line or chunk +of code is under the "Files Changed" tab of the pull request. All you need to do +now is scroll down to a line of code that you want to comment on and hover over +it, a blue plus button will appear and you can click on it to open a comment +menu. + +![image](./images/reviewer_add_pr_suggestion.png) + +You can leave feedback, ask questions, whatever. If you want to suggest changes +to the code, you will need to click the code review icon on the text tool bar. +This will automatically populate a suggestion template in your comment, +everything inside the triple tildes will be part of the suggested code. The code +inside initially will be identical to the PR authors code but you can do +whatever to it, including changes values, editing indentation, or even +adding/removing entire lines. + +![image](./images/reviewer_pr_suggestion_text.png) + +Finally, once you wish to submit this comment or suggestion you have two +options. You can just submit it as is, or you can add it to a batched review +(referred to as "start a review"). If you are doing many +comments/suggestion(2+), you should batch your reviews. If you do batch your +reviews, you can submit them together in the top right of the files changes tab +once done. + +## What can I start reviewing? +So you know what reviewing is, you know how to review, and you're ready to +review.... but what do you review? Knowledge of code and willingness to +understand our currently implemented systems is critically important to being +able to review pull requests. However, there are a few "code standards" you can +look out for on PR's to get familiarized with the code review process and get a +few reviews under your belt. + +### Problematic Code Examples +Lets say a contributor has opened a pull request adding a brand-new item to the +game. This item has a few special functions and procs that you need to look +over. I will go through each part of this code that I would leave comments or +suggestions on, for the most part this covers all the basic things you will look +out for as a beginner code reviewer. + +```dm +/obj/item/omega + name = "Omega Item" + desc = "This object is super duper cool!!!" + icon = 'icons/obj/food/food.dmi' + icon_state = "omega" + w_class = WEIGHT_CLASS_SMALL + throw_speed = 3 + //determines whether the person using this is "cool" or not + var/is_cool = null + var/list/announce_verbs = list(cool,epic,sick,spectacular) + +/obj/item/omega/attack_self(mob/user) + if(user) + if(is_user_cool(user) && istype(usr, /mob/living/carbon/human)) + to_chat(usr, "[usr] is very [pick(announce_verbs)]") + is_cool = TRUE + return + to_chat(usr, "[usr] is not very [pick(announce_verbs)]") + is_cool = FALSE + return + +/obj/item/omega/proc/is_user_cool(mob/user) + if(istype(usr, /mob/living/carbon/human)) + return 1 + return 0 +``` + +First `var/is_cool = null` needs to be corrected to `var/is_cool`. Any time you +establish a variable in its definition, it will initialize as 'null' if you do +not provide a default value. Therefor, we don't need to assign it a default +value of null because it's redundant. + +Second I immediately see a spacing problem with the list variable, there's not +spacing between comma separators `list(cool,epic,sick,spectacular)`, you should +correct this to be `list(cool, epic, sick, spectacular)` + +```dm +/obj/item/omega + name = "Omega Item" + desc = "This object is super duper cool!!!" + icon = 'icons/obj/food/food.dmi' + icon_state = "omega" + w_class = WEIGHT_CLASS_SMALL + throw_speed = 3 + //determines whether the person using this is "cool" or not + var/is_cool + var/list/announce_verbs = list(cool, epic, sick, spectacular) +``` + +Lets move onto the `attack_self` proc now. + +We can see that it takes one parameter: `mob/user`. The first thing that +immediately catches my eye is the liberal use of `usr`. Now, `usr` is a native +variable of dm that refers to the mob or user that initiated the calling of the +proc, however this doesn't always mean that `usr` is the same thing as +`mob/user` and may even change depending on the context in which the proc is +called. However, we know that `mob/user` will always be the user we need here +(unless someone screwed up elsewhere) and will use that instead of `usr. + +Next, our first `to_chat` call is miss a span class definition. We will add that +in `` and close it with `` within the parentheses. +With that in mind, we also notice that the second `to_chat` forgot to close +their span, so we will do that as well. + +```dm +/obj/item/omega/attack_self(mob/user) + if(user) + if(is_user_cool(user) && istype(user, /mob/living/carbon/human)) + to_chat(user, "[user] is very [pick(announce_verbs)]") + is_cool = TRUE + return + to_chat(user, "[user] is not very [pick(announce_verbs)]") + is_cool = FALSE + return +``` + +Now lets take a look at the logic here. What does `if(is_user_cool(user) && +istype(user, /mob/living/carbon/human))` do? It's performing an istype check, +and also checking for the return of `is_user_cool()` + +```dm +/obj/item/omega/proc/is_user_cool(mob/user) + if(istype(usr, /mob/living/carbon/human)) + return 1 + return 0 +``` + +More issues! First and foremost, we know that this proc is supposed to return +`TRUE` or `FALSE` so we want to make sure to correct those `0`s and `1`s to +their respective `FALSE` and `TRUE` defines. We should also nip that `usr`. One +final thing with this proc in particular when using istype's, we sometimes +already have defines for specific types. In this case, we already have an +`ishuman()` define + +```dm +#define ishuman(A) (istype(A, /mob/living/carbon/human)) +``` + +Lets make those corrections + +```dm +/obj/item/omega/proc/is_user_cool(mob/user) + if(ishuman(user)) + return TRUE + return FALSE +``` + +Now lets looks at the big picture, you may have noticed that we perform the same +`istype` check **twice**. The author appears to have accidentally added redundant +code in their if check. Let's fix that for them: + +```dm +if(is_user_cool(user)) +``` + +Lets put all of our suggested changes together! + +```dm +/obj/item/omega + name = "Omega Item" + desc = "This object is super duper cool!!!" + icon = 'icons/obj/food/food.dmi' + icon_state = "omega" + w_class = WEIGHT_CLASS_SMALL + throw_speed = 3 + //determines whether the person using this is "cool" or not + var/is_cool + var/list/announce_verbs = list(cool, epic, sick, spectacular) + +/obj/item/omega/attack_self(mob/user) + if(user) + if(is_user_cool(user)) + to_chat(user, "[user] is very [pick(announce_verbs)]") + is_cool = TRUE + return + to_chat(user, "[user] is not very [pick(announce_verbs)]") + is_cool = FALSE + return + +/obj/item/omega/proc/is_user_cool(mob/user) + if(ishuman(user)) + return TRUE + return FALSE +``` + +That code looks a lot better, it's not perfect and it may not be "balanced" but +the code is much cleaner and even less prone to failure. There is still 7 minor +issues or possibly problematic code in this pull request that can be fixed (and +one that will cause compile errors or runtimes!); **I invite you to look for +them and share in the replies to this post what they are and how you would +suggest to fix them as a PR reviewer.** + +## The Art of Code + +> ... I like it because I could make the computer do what I wanted and every +> time I did that, I got this little thrill and this rush and throughout *my +> entire career*. That thrill for me has never gone away [[The Art of Code - Dylan Beattie](https://www.youtube.com/watch?v=6avJHaC3C2U)] + +This segment might be a bit corny but I figured it would be important to include +because I felt like it was an important aspect of reviewing that I've always had +to keep in mind (and constantly struggle with). Code is not just code, it's the +work or "artwork" of someone else who may have spent a significant amount of +time writing it. Like any language, dreammaker is not particularly easy to learn +for the average player, many of us didn't learn a coding language before trying +our hand at contributing to the codebase. + +## Not All Code Is Equal + +What I mean by this is not that there is some amount of "worth" conferred +between two different works of code; For you and me, we likely have differing +levels of skill, so you writing code for a new custom mob may be extremely easy +but for me, it may be extremely difficult and one of the more difficult tasks I +have attempted. At the same time, I may be much better at working with complex +datums whereas you don't know where to start to build those into a larger +system. We all enter into our dev community with differing levels of skills and +talents, reviewers need to recognize this. + +This appreciation of diverse abilities is important in the sense that we should +not impose judgment on other people's code immediately. Do your absolute best to +avoid coming across as hostile, demanding, or rude in your review comments. +Positive and constructive feedback is important. Most of the time, bad code is +just a consequence of the coder not knowing how to properly do something and +should be treated as a learning experience. + +## Conclusion + +**This is the end of the guide**, I do hope to write an intermediate guide in +the future but I hope this serves well as an entry into reviewing. As always, +questions are always welcome (and criticism/recommendations to this guide). diff --git a/docs/css/atom-one-dark.css b/docs/css/atom-one-dark.css new file mode 100644 index 000000000000..3c51ce688a86 --- /dev/null +++ b/docs/css/atom-one-dark.css @@ -0,0 +1,90 @@ +pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em +} +code.hljs { + padding: 3px 5px +} +/* + +Atom One Dark by Daniel Gamage +Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax + +base: #282c34 +mono-1: #abb2bf +mono-2: #818896 +mono-3: #5c6370 +hue-1: #56b6c2 +hue-2: #61aeee +hue-3: #c678dd +hue-4: #98c379 +hue-5: #e06c75 +hue-5-2: #be5046 +hue-6: #d19a66 +hue-6-2: #e6c07b + +*/ +.hljs { + color: #abb2bf; + background: #282c34 +} +.hljs-comment, +.hljs-quote { + color: #5c6370; + font-style: italic +} +.hljs-doctag, +.hljs-keyword, +.hljs-formula { + color: #c678dd +} +.hljs-section, +.hljs-name, +.hljs-selector-tag, +.hljs-deletion, +.hljs-subst { + color: #e06c75 +} +.hljs-literal { + color: #56b6c2 +} +.hljs-string, +.hljs-regexp, +.hljs-addition, +.hljs-attribute, +.hljs-meta .hljs-string { + color: #98c379 +} +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-type, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-number { + color: #d19a66 +} +.hljs-symbol, +.hljs-bullet, +.hljs-link, +.hljs-meta, +.hljs-selector-id, +.hljs-title { + color: #61aeee +} +.hljs-built_in, +.hljs-title.class_, +.hljs-class .hljs-title { + color: #e6c07b +} +.hljs-emphasis { + font-style: italic +} +.hljs-strong { + font-weight: bold +} +.hljs-link { + text-decoration: underline +} \ No newline at end of file diff --git a/docs/css/para.css b/docs/css/para.css new file mode 100644 index 000000000000..f0c5021b48d9 --- /dev/null +++ b/docs/css/para.css @@ -0,0 +1,28 @@ +header { + font-family: ui-rounded, 'Hiragino Maru Gothic ProN', Quicksand, Comfortaa, Manjari, 'Arial Rounded MT', 'Arial Rounded MT Bold', Calibri, source-sans-pro, sans-serif; +} + +:root,[data-md-color-scheme=default] { + + /* Primary color shades */ + --md-primary-fg-color: #861f41; + --md-primary-fg-color--light: #861f4194; + --md-primary-fg-color--dark: #ac325a; + --md-default-fg-color--light: #e6e6e6; + --md-default-fg-color--lighter: #ad2e7352; + --md-default-fg-color: #e6e6e6; + --md-default-bg-color: #22030d; + --md-primary-bg-color--light: #ff266b; + --md-text-link-color: hsla(231, 48%, 48%, 1); + + /* Accent color shades */ + --md-accent-fg-color: rgb(183, 120, 255); + --md-accent-fg-color--transparent: hsla(189, 100%, 37%, 0.1); + --md-accent-bg-color: hsla(0, 0%, 100%, 1); + --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7); + + --md-code-bg-color: #281828; + --md-code-fg-color: #d4d4d4; + + --md-typeset-a-color: #ff558d; + } diff --git a/docs/hooks/contributing_path.py b/docs/hooks/contributing_path.py new file mode 100644 index 000000000000..b4b23e48cefc --- /dev/null +++ b/docs/hooks/contributing_path.py @@ -0,0 +1,36 @@ +"""Upper-case path rewriter. + +While it is not mandatory for the files "CONTRIBUTING" and "CODE_OF_CONDUCT" to +be in uppercase, this promotes visibility within the repo. + +However, if mkdocs has a root file named CONTRIBUTING.md, and a nav section +named "contributing", and the site is built on a case-insensitive filesystem, +these will resolve to the same path, i.e.: + +- CONTRIBUTING.md -> "/contributing/index.html" +- contributing/getting_started.md -> "/contributing/getting_started/index.html" + +When URL links are generated, the original filename is used, so any links to +CONTRIBUTING.md will resolve to "/CONTRIBUTING/index.html". This will break when +published to a webserver, as that directory won't exist. + +This tiny script replaces references to the upper-case file path to a lower-case +file path, to resolve this issue. + +For consistency's sake, we do this for CODE_OF_CONDUCT, despite no worry about +collisions in this case, because the URL looks nicer. +""" + +transforms = { + "CONTRIBUTING": "contributing", + "CODE_OF_CONDUCT": "code_of_conduct", +} + + +def on_page_markdown(markdown, *, page, config, files): + for old, new in transforms.items(): + if page.file.name == old: + page.file.name = new + page.file.url = page.file.url.replace(old, new) + page.file.dest_uri = page.file.dest_uri.replace(old, new) + page.file.abs_dest_path = page.file.abs_dest_path.replace(old, new) diff --git a/docs/images/favicon.png b/docs/images/favicon.png new file mode 100644 index 000000000000..2f923f9b1cac Binary files /dev/null and b/docs/images/favicon.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000000..54b6c1aadc0d --- /dev/null +++ b/docs/index.md @@ -0,0 +1,15 @@ +# Paradise Contributor Documentation + +This site is a comprehensive collection of all documents, guides, and references +necessary to get started making contributions to Paradise Station. + +## Required Reading + +All contributors are expected to read and abide by the Paradise Station [Code of +Conduct](./CODE_OF_CONDUCT.md) and [Contribution Guidelines](./CONTRIBUTING.md). + +## New Contributors + +If you are new to writing code and contributing, start at [Getting Started][]. + +[Getting Started]: ./contributing/getting_started.md diff --git a/docs/javascripts/highlight-dm.js b/docs/javascripts/highlight-dm.js new file mode 100644 index 000000000000..19075ffb21ac --- /dev/null +++ b/docs/javascripts/highlight-dm.js @@ -0,0 +1,113 @@ +// This is a grammar for highlight.js used by mdbook. +// +// - Guide on writing highlighters: https://highlightjs.readthedocs.io/en/latest/language-guide.html +// - possible values for `scope`/`className`: https://highlightjs.readthedocs.io/en/latest/css-classes-reference.html + +/* +Language: Dream Maker +Author: Pieter-Jan Briers +Various bits taken from Javascript, Rust and C++'s file. +Description: Dream Maker language used by the BYOND game engine. +Category: common +*/ + +var BLOCK_COMMENT = hljs.COMMENT('/\\*', '\\*/', {contains: ['self']}); +var KEYWORDS = + 'var proc verb global tmp static const set as ' + + 'new del ' + + 'sleep spawn break continue do else for in step goto if return switch while try catch throw'; +var BUILTINS = + 'usr src world args ' + + 'list datum area turf obj mob atom movable client database exception ' + + 'icon image matrix mutable_appearance savefile sound regex operator'; +var LITERAL = 'null'; +var SUBST = { + className: 'subst', + begin: '\\[', end: '\\]', + keywords: { + built_in: BUILTINS, + literal: LITERAL + }, + contains: [] // defined later +}; +var STRING = { + className: 'string', + begin: '"', end: '"', + contains: [ + hljs.BACKSLASH_ESCAPE, + SUBST + ] +}; +var STRING_MULTILINE = { + className: 'string', + begin: '\\{"', end: '"\\}', + contains: [ + hljs.BACKSLASH_ESCAPE, + SUBST + ] +}; +var FILE_STRING = { + className: 'string', + begin: "'", end: "'" +}; +var NUMBER = { + className: 'number', + variants: [ + { begin: '1\\.\\#IND' }, + { begin: '1\\.\\#INF' }, + { begin: hljs.C_NUMBER_RE }, + ], + relevance: 0 +}; +var CONSTANT = { + className: 'literal', + begin: /\b[A-Z_][A-Z_0-9]*\b/ +}; +var PREPROCESSOR = { + className: 'meta', + begin: /#\s*[a-z]+\b/, end: /$/, + keywords: { + 'meta-keyword': + 'if else elif endif define undef warn error ' + + 'ifdef ifndef include' + }, + contains: [ + { + begin: /\\\n/, relevance: 0 + }, + STRING, + FILE_STRING, + NUMBER, + CONSTANT, + hljs.C_LINE_COMMENT_MODE, + BLOCK_COMMENT, + ] +}; +SUBST.contains = [ + STRING, + FILE_STRING, + NUMBER, + CONSTANT +]; + +hljs.registerLanguage('dm', (hljs) => ({ + aliases: ['byond', 'dreammaker'], + keywords: { + keyword: KEYWORDS, + literal: LITERAL, + built_in: BUILTINS + }, + contains: [ + hljs.C_LINE_COMMENT_MODE, + BLOCK_COMMENT, + PREPROCESSOR, + STRING, + FILE_STRING, + STRING_MULTILINE, + NUMBER, + CONSTANT + ] + })); + +hljs.initHighlightingOnLoad(); + diff --git a/docs/javascripts/highlight.min.js b/docs/javascripts/highlight.min.js new file mode 100644 index 000000000000..9e23dd54ff70 --- /dev/null +++ b/docs/javascripts/highlight.min.js @@ -0,0 +1,618 @@ +/*! + Highlight.js v11.10.0 (git: 366a8bd012) + (c) 2006-2024 Josh Goebel and other contributors + License: BSD-3-Clause + */ +var hljs=function(){"use strict";function e(t){ +return t instanceof Map?t.clear=t.delete=t.set=()=>{ +throw Error("map is read-only")}:t instanceof Set&&(t.add=t.clear=t.delete=()=>{ +throw Error("set is read-only") +}),Object.freeze(t),Object.getOwnPropertyNames(t).forEach((n=>{ +const i=t[n],s=typeof i;"object"!==s&&"function"!==s||Object.isFrozen(i)||e(i) +})),t}class t{constructor(e){ +void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1} +ignoreMatch(){this.isMatchIgnored=!0}}function n(e){ +return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'") +}function i(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t] +;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n}const s=e=>!!e.scope +;class o{constructor(e,t){ +this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){ +this.buffer+=n(e)}openNode(e){if(!s(e))return;const t=((e,{prefix:t})=>{ +if(e.startsWith("language:"))return e.replace("language:","language-") +;if(e.includes(".")){const n=e.split(".") +;return[`${t}${n.shift()}`,...n.map(((e,t)=>`${e}${"_".repeat(t+1)}`))].join(" ") +}return`${t}${e}`})(e.scope,{prefix:this.classPrefix});this.span(t)} +closeNode(e){s(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ +this.buffer+=``}}const r=(e={})=>{const t={children:[]} +;return Object.assign(t,e),t};class a{constructor(){ +this.rootNode=r(),this.stack=[this.rootNode]}get top(){ +return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ +this.top.children.push(e)}openNode(e){const t=r({scope:e}) +;this.add(t),this.stack.push(t)}closeNode(){ +if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ +for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} +walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){ +return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t), +t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){ +"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ +a._collapse(e)})))}}class c extends a{constructor(e){super(),this.options=e} +addText(e){""!==e&&this.add(e)}startScope(e){this.openNode(e)}endScope(){ +this.closeNode()}__addSublanguage(e,t){const n=e.root +;t&&(n.scope="language:"+t),this.add(n)}toHTML(){ +return new o(this,this.options).value()}finalize(){ +return this.closeAllNodes(),!0}}function l(e){ +return e?"string"==typeof e?e:e.source:null}function g(e){return h("(?=",e,")")} +function u(e){return h("(?:",e,")*")}function d(e){return h("(?:",e,")?")} +function h(...e){return e.map((e=>l(e))).join("")}function f(...e){const t=(e=>{ +const t=e[e.length-1] +;return"object"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{} +})(e);return"("+(t.capture?"":"?:")+e.map((e=>l(e))).join("|")+")"} +function p(e){return RegExp(e.toString()+"|").exec("").length-1} +const b=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./ +;function m(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n +;let i=l(e),s="";for(;i.length>0;){const e=b.exec(i);if(!e){s+=i;break} +s+=i.substring(0,e.index), +i=i.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?s+="\\"+(Number(e[1])+t):(s+=e[0], +"("===e[0]&&n++)}return s})).map((e=>`(${e})`)).join(t)} +const E="[a-zA-Z]\\w*",x="[a-zA-Z_]\\w*",w="\\b\\d+(\\.\\d+)?",y="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",_="\\b(0b[01]+)",O={ +begin:"\\\\[\\s\\S]",relevance:0},v={scope:"string",begin:"'",end:"'", +illegal:"\\n",contains:[O]},k={scope:"string",begin:'"',end:'"',illegal:"\\n", +contains:[O]},N=(e,t,n={})=>{const s=i({scope:"comment",begin:e,end:t, +contains:[]},n);s.contains.push({scope:"doctag", +begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)", +end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0}) +;const o=f("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/) +;return s.contains.push({begin:h(/[ ]+/,"(",o,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),s +},S=N("//","$"),M=N("/\\*","\\*/"),R=N("#","$");var j=Object.freeze({ +__proto__:null,APOS_STRING_MODE:v,BACKSLASH_ESCAPE:O,BINARY_NUMBER_MODE:{ +scope:"number",begin:_,relevance:0},BINARY_NUMBER_RE:_,COMMENT:N, +C_BLOCK_COMMENT_MODE:M,C_LINE_COMMENT_MODE:S,C_NUMBER_MODE:{scope:"number", +begin:y,relevance:0},C_NUMBER_RE:y,END_SAME_AS_BEGIN:e=>Object.assign(e,{ +"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{ +t.data._beginMatch!==e[1]&&t.ignoreMatch()}}),HASH_COMMENT_MODE:R,IDENT_RE:E, +MATCH_NOTHING_RE:/\b\B/,METHOD_GUARD:{begin:"\\.\\s*"+x,relevance:0}, +NUMBER_MODE:{scope:"number",begin:w,relevance:0},NUMBER_RE:w, +PHRASAL_WORDS_MODE:{ +begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ +},QUOTE_STRING_MODE:k,REGEXP_MODE:{scope:"regexp",begin:/\/(?=[^/\n]*\/)/, +end:/\/[gimuy]*/,contains:[O,{begin:/\[/,end:/\]/,relevance:0,contains:[O]}]}, +RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", +SHEBANG:(e={})=>{const t=/^#![ ]*\// +;return e.binary&&(e.begin=h(t,/.*\b/,e.binary,/\b.*/)),i({scope:"meta",begin:t, +end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)}, +TITLE_MODE:{scope:"title",begin:E,relevance:0},UNDERSCORE_IDENT_RE:x, +UNDERSCORE_TITLE_MODE:{scope:"title",begin:x,relevance:0}});function A(e,t){ +"."===e.input[e.index-1]&&t.ignoreMatch()}function I(e,t){ +void 0!==e.className&&(e.scope=e.className,delete e.className)}function T(e,t){ +t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", +e.__beforeBegin=A,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, +void 0===e.relevance&&(e.relevance=0))}function L(e,t){ +Array.isArray(e.illegal)&&(e.illegal=f(...e.illegal))}function B(e,t){ +if(e.match){ +if(e.begin||e.end)throw Error("begin & end are not supported with match") +;e.begin=e.match,delete e.match}}function P(e,t){ +void 0===e.relevance&&(e.relevance=1)}const D=(e,t)=>{if(!e.beforeMatch)return +;if(e.starts)throw Error("beforeMatch cannot be used with starts") +;const n=Object.assign({},e);Object.keys(e).forEach((t=>{delete e[t] +})),e.keywords=n.keywords,e.begin=h(n.beforeMatch,g(n.begin)),e.starts={ +relevance:0,contains:[Object.assign(n,{endsParent:!0})] +},e.relevance=0,delete n.beforeMatch +},H=["of","and","for","in","not","or","if","then","parent","list","value"],C="keyword" +;function $(e,t,n=C){const i=Object.create(null) +;return"string"==typeof e?s(n,e.split(" ")):Array.isArray(e)?s(n,e):Object.keys(e).forEach((n=>{ +Object.assign(i,$(e[n],t,n))})),i;function s(e,n){ +t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|") +;i[n[0]]=[e,U(n[0],n[1])]}))}}function U(e,t){ +return t?Number(t):(e=>H.includes(e.toLowerCase()))(e)?0:1}const z={},W=e=>{ +console.error(e)},X=(e,...t)=>{console.log("WARN: "+e,...t)},G=(e,t)=>{ +z[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),z[`${e}/${t}`]=!0) +},K=Error();function F(e,t,{key:n}){let i=0;const s=e[n],o={},r={} +;for(let e=1;e<=t.length;e++)r[e+i]=s[e],o[e+i]=!0,i+=p(t[e-1]) +;e[n]=r,e[n]._emit=o,e[n]._multi=!0}function Z(e){(e=>{ +e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope, +delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={ +_wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope +}),(e=>{if(Array.isArray(e.begin)){ +if(e.skip||e.excludeBegin||e.returnBegin)throw W("skip, excludeBegin, returnBegin not compatible with beginScope: {}"), +K +;if("object"!=typeof e.beginScope||null===e.beginScope)throw W("beginScope must be object"), +K;F(e,e.begin,{key:"beginScope"}),e.begin=m(e.begin,{joinWith:""})}})(e),(e=>{ +if(Array.isArray(e.end)){ +if(e.skip||e.excludeEnd||e.returnEnd)throw W("skip, excludeEnd, returnEnd not compatible with endScope: {}"), +K +;if("object"!=typeof e.endScope||null===e.endScope)throw W("endScope must be object"), +K;F(e,e.end,{key:"endScope"}),e.end=m(e.end,{joinWith:""})}})(e)}function V(e){ +function t(t,n){ +return RegExp(l(t),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(n?"g":"")) +}class n{constructor(){ +this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} +addRule(e,t){ +t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]), +this.matchAt+=p(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) +;const e=this.regexes.map((e=>e[1]));this.matcherRe=t(m(e,{joinWith:"|" +}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex +;const t=this.matcherRe.exec(e);if(!t)return null +;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n] +;return t.splice(0,n),Object.assign(t,i)}}class s{constructor(){ +this.rules=[],this.multiRegexes=[], +this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ +if(this.multiRegexes[e])return this.multiRegexes[e];const t=new n +;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))), +t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){ +return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){ +this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){ +const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex +;let n=t.exec(e) +;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{ +const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)} +return n&&(this.regexIndex+=n.position+1, +this.regexIndex===this.count&&this.considerAll()),n}} +if(e.compilerExtensions||(e.compilerExtensions=[]), +e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") +;return e.classNameAliases=i(e.classNameAliases||{}),function n(o,r){const a=o +;if(o.isCompiled)return a +;[I,B,Z,D].forEach((e=>e(o,r))),e.compilerExtensions.forEach((e=>e(o,r))), +o.__beforeBegin=null,[T,L,P].forEach((e=>e(o,r))),o.isCompiled=!0;let c=null +;return"object"==typeof o.keywords&&o.keywords.$pattern&&(o.keywords=Object.assign({},o.keywords), +c=o.keywords.$pattern, +delete o.keywords.$pattern),c=c||/\w+/,o.keywords&&(o.keywords=$(o.keywords,e.case_insensitive)), +a.keywordPatternRe=t(c,!0), +r&&(o.begin||(o.begin=/\B|\b/),a.beginRe=t(a.begin),o.end||o.endsWithParent||(o.end=/\B|\b/), +o.end&&(a.endRe=t(a.end)), +a.terminatorEnd=l(a.end)||"",o.endsWithParent&&r.terminatorEnd&&(a.terminatorEnd+=(o.end?"|":"")+r.terminatorEnd)), +o.illegal&&(a.illegalRe=t(o.illegal)), +o.contains||(o.contains=[]),o.contains=[].concat(...o.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>i(e,{ +variants:null},t)))),e.cachedVariants?e.cachedVariants:q(e)?i(e,{ +starts:e.starts?i(e.starts):null +}):Object.isFrozen(e)?i(e):e))("self"===e?o:e)))),o.contains.forEach((e=>{n(e,a) +})),o.starts&&n(o.starts,r),a.matcher=(e=>{const t=new s +;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin" +}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end" +}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(a),a}(e)}function q(e){ +return!!e&&(e.endsWithParent||q(e.starts))}class J extends Error{ +constructor(e,t){super(e),this.name="HTMLInjectionError",this.html=t}} +const Y=n,Q=i,ee=Symbol("nomatch"),te=n=>{ +const i=Object.create(null),s=Object.create(null),o=[];let r=!0 +;const a="Could not find the language '{}', did you forget to load/include a language module?",l={ +disableAutodetect:!0,name:"Plain text",contains:[]};let p={ +ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i, +languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", +cssSelector:"pre code",languages:null,__emitter:c};function b(e){ +return p.noHighlightRe.test(e)}function m(e,t,n){let i="",s="" +;"object"==typeof t?(i=e, +n=t.ignoreIllegals,s=t.language):(G("10.7.0","highlight(lang, code, ...args) has been deprecated."), +G("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), +s=e,i=t),void 0===n&&(n=!0);const o={code:i,language:s};N("before:highlight",o) +;const r=o.result?o.result:E(o.language,o.code,n) +;return r.code=o.code,N("after:highlight",r),r}function E(e,n,s,o){ +const c=Object.create(null);function l(){if(!N.keywords)return void M.addText(R) +;let e=0;N.keywordPatternRe.lastIndex=0;let t=N.keywordPatternRe.exec(R),n="" +;for(;t;){n+=R.substring(e,t.index) +;const s=_.case_insensitive?t[0].toLowerCase():t[0],o=(i=s,N.keywords[i]);if(o){ +const[e,i]=o +;if(M.addText(n),n="",c[s]=(c[s]||0)+1,c[s]<=7&&(j+=i),e.startsWith("_"))n+=t[0];else{ +const n=_.classNameAliases[e]||e;u(t[0],n)}}else n+=t[0] +;e=N.keywordPatternRe.lastIndex,t=N.keywordPatternRe.exec(R)}var i +;n+=R.substring(e),M.addText(n)}function g(){null!=N.subLanguage?(()=>{ +if(""===R)return;let e=null;if("string"==typeof N.subLanguage){ +if(!i[N.subLanguage])return void M.addText(R) +;e=E(N.subLanguage,R,!0,S[N.subLanguage]),S[N.subLanguage]=e._top +}else e=x(R,N.subLanguage.length?N.subLanguage:null) +;N.relevance>0&&(j+=e.relevance),M.__addSublanguage(e._emitter,e.language) +})():l(),R=""}function u(e,t){ +""!==e&&(M.startScope(t),M.addText(e),M.endScope())}function d(e,t){let n=1 +;const i=t.length-1;for(;n<=i;){if(!e._emit[n]){n++;continue} +const i=_.classNameAliases[e[n]]||e[n],s=t[n];i?u(s,i):(R=s,l(),R=""),n++}} +function h(e,t){ +return e.scope&&"string"==typeof e.scope&&M.openNode(_.classNameAliases[e.scope]||e.scope), +e.beginScope&&(e.beginScope._wrap?(u(R,_.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap), +R=""):e.beginScope._multi&&(d(e.beginScope,t),R="")),N=Object.create(e,{parent:{ +value:N}}),N}function f(e,n,i){let s=((e,t)=>{const n=e&&e.exec(t) +;return n&&0===n.index})(e.endRe,i);if(s){if(e["on:end"]){const i=new t(e) +;e["on:end"](n,i),i.isMatchIgnored&&(s=!1)}if(s){ +for(;e.endsParent&&e.parent;)e=e.parent;return e}} +if(e.endsWithParent)return f(e.parent,n,i)}function b(e){ +return 0===N.matcher.regexIndex?(R+=e[0],1):(T=!0,0)}function m(e){ +const t=e[0],i=n.substring(e.index),s=f(N,e,i);if(!s)return ee;const o=N +;N.endScope&&N.endScope._wrap?(g(), +u(t,N.endScope._wrap)):N.endScope&&N.endScope._multi?(g(), +d(N.endScope,e)):o.skip?R+=t:(o.returnEnd||o.excludeEnd||(R+=t), +g(),o.excludeEnd&&(R=t));do{ +N.scope&&M.closeNode(),N.skip||N.subLanguage||(j+=N.relevance),N=N.parent +}while(N!==s.parent);return s.starts&&h(s.starts,e),o.returnEnd?0:t.length} +let w={};function y(i,o){const a=o&&o[0];if(R+=i,null==a)return g(),0 +;if("begin"===w.type&&"end"===o.type&&w.index===o.index&&""===a){ +if(R+=n.slice(o.index,o.index+1),!r){const t=Error(`0 width match regex (${e})`) +;throw t.languageName=e,t.badRule=w.rule,t}return 1} +if(w=o,"begin"===o.type)return(e=>{ +const n=e[0],i=e.rule,s=new t(i),o=[i.__beforeBegin,i["on:begin"]] +;for(const t of o)if(t&&(t(e,s),s.isMatchIgnored))return b(n) +;return i.skip?R+=n:(i.excludeBegin&&(R+=n), +g(),i.returnBegin||i.excludeBegin||(R=n)),h(i,e),i.returnBegin?0:n.length})(o) +;if("illegal"===o.type&&!s){ +const e=Error('Illegal lexeme "'+a+'" for mode "'+(N.scope||"")+'"') +;throw e.mode=N,e}if("end"===o.type){const e=m(o);if(e!==ee)return e} +if("illegal"===o.type&&""===a)return 1 +;if(I>1e5&&I>3*o.index)throw Error("potential infinite loop, way more iterations than matches") +;return R+=a,a.length}const _=O(e) +;if(!_)throw W(a.replace("{}",e)),Error('Unknown language: "'+e+'"') +;const v=V(_);let k="",N=o||v;const S={},M=new p.__emitter(p);(()=>{const e=[] +;for(let t=N;t!==_;t=t.parent)t.scope&&e.unshift(t.scope) +;e.forEach((e=>M.openNode(e)))})();let R="",j=0,A=0,I=0,T=!1;try{ +if(_.__emitTokens)_.__emitTokens(n,M);else{for(N.matcher.considerAll();;){ +I++,T?T=!1:N.matcher.considerAll(),N.matcher.lastIndex=A +;const e=N.matcher.exec(n);if(!e)break;const t=y(n.substring(A,e.index),e) +;A=e.index+t}y(n.substring(A))}return M.finalize(),k=M.toHTML(),{language:e, +value:k,relevance:j,illegal:!1,_emitter:M,_top:N}}catch(t){ +if(t.message&&t.message.includes("Illegal"))return{language:e,value:Y(n), +illegal:!0,relevance:0,_illegalBy:{message:t.message,index:A, +context:n.slice(A-100,A+100),mode:t.mode,resultSoFar:k},_emitter:M};if(r)return{ +language:e,value:Y(n),illegal:!1,relevance:0,errorRaised:t,_emitter:M,_top:N} +;throw t}}function x(e,t){t=t||p.languages||Object.keys(i);const n=(e=>{ +const t={value:Y(e),illegal:!1,relevance:0,_top:l,_emitter:new p.__emitter(p)} +;return t._emitter.addText(e),t})(e),s=t.filter(O).filter(k).map((t=>E(t,e,!1))) +;s.unshift(n);const o=s.sort(((e,t)=>{ +if(e.relevance!==t.relevance)return t.relevance-e.relevance +;if(e.language&&t.language){if(O(e.language).supersetOf===t.language)return 1 +;if(O(t.language).supersetOf===e.language)return-1}return 0})),[r,a]=o,c=r +;return c.secondBest=a,c}function w(e){let t=null;const n=(e=>{ +let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"" +;const n=p.languageDetectRe.exec(t);if(n){const t=O(n[1]) +;return t||(X(a.replace("{}",n[1])), +X("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"} +return t.split(/\s+/).find((e=>b(e)||O(e)))})(e);if(b(n))return +;if(N("before:highlightElement",{el:e,language:n +}),e.dataset.highlighted)return void console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",e) +;if(e.children.length>0&&(p.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."), +console.warn("https://github.com/highlightjs/highlight.js/wiki/security"), +console.warn("The element with unescaped HTML:"), +console.warn(e)),p.throwUnescapedHTML))throw new J("One of your code blocks includes unescaped HTML.",e.innerHTML) +;t=e;const i=t.textContent,o=n?m(i,{language:n,ignoreIllegals:!0}):x(i) +;e.innerHTML=o.value,e.dataset.highlighted="yes",((e,t,n)=>{const i=t&&s[t]||n +;e.classList.add("hljs"),e.classList.add("language-"+i) +})(e,n,o.language),e.result={language:o.language,re:o.relevance, +relevance:o.relevance},o.secondBest&&(e.secondBest={ +language:o.secondBest.language,relevance:o.secondBest.relevance +}),N("after:highlightElement",{el:e,result:o,text:i})}let y=!1;function _(){ +"loading"!==document.readyState?document.querySelectorAll(p.cssSelector).forEach(w):y=!0 +}function O(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]} +function v(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ +s[e.toLowerCase()]=t}))}function k(e){const t=O(e) +;return t&&!t.disableAutodetect}function N(e,t){const n=e;o.forEach((e=>{ +e[n]&&e[n](t)}))} +"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{ +y&&_()}),!1),Object.assign(n,{highlight:m,highlightAuto:x,highlightAll:_, +highlightElement:w, +highlightBlock:e=>(G("10.7.0","highlightBlock will be removed entirely in v12.0"), +G("10.7.0","Please use highlightElement now."),w(e)),configure:e=>{p=Q(p,e)}, +initHighlighting:()=>{ +_(),G("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")}, +initHighlightingOnLoad:()=>{ +_(),G("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.") +},registerLanguage:(e,t)=>{let s=null;try{s=t(n)}catch(t){ +if(W("Language definition for '{}' could not be registered.".replace("{}",e)), +!r)throw t;W(t),s=l} +s.name||(s.name=e),i[e]=s,s.rawDefinition=t.bind(null,n),s.aliases&&v(s.aliases,{ +languageName:e})},unregisterLanguage:e=>{delete i[e] +;for(const t of Object.keys(s))s[t]===e&&delete s[t]}, +listLanguages:()=>Object.keys(i),getLanguage:O,registerAliases:v, +autoDetection:k,inherit:Q,addPlugin:e=>{(e=>{ +e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{ +e["before:highlightBlock"](Object.assign({block:t.el},t)) +}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{ +e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),o.push(e)}, +removePlugin:e=>{const t=o.indexOf(e);-1!==t&&o.splice(t,1)}}),n.debugMode=()=>{ +r=!1},n.safeMode=()=>{r=!0},n.versionString="11.10.0",n.regex={concat:h, +lookahead:g,either:f,optional:d,anyNumberOfTimes:u} +;for(const t in j)"object"==typeof j[t]&&e(j[t]);return Object.assign(n,j),n +},ne=te({});return ne.newInstance=()=>te({}),ne}() +;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);/*! `c` grammar compiled for Highlight.js 11.10.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const n=e.regex,t=e.COMMENT("//","$",{ +contains:[{begin:/\\\n/}] +}),a="decltype\\(auto\\)",s="[a-zA-Z_]\\w*::",i="("+a+"|"+n.optional(s)+"[a-zA-Z_]\\w*"+n.optional("<[^<>]+>")+")",r={ +className:"type",variants:[{begin:"\\b[a-z\\d_]*_t\\b"},{ +match:/\batomic_[a-z]{3,6}\b/}]},l={className:"string",variants:[{ +begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{ +begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)", +end:"'",illegal:"."},e.END_SAME_AS_BEGIN({ +begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},o={ +className:"number",variants:[{begin:"\\b(0b[01']+)"},{ +begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)" +},{ +begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" +}],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{ +keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef elifdef elifndef include" +},contains:[{begin:/\\\n/,relevance:0},e.inherit(l,{className:"string"}),{ +className:"string",begin:/<.*?>/},t,e.C_BLOCK_COMMENT_MODE]},d={ +className:"title",begin:n.optional(s)+e.IDENT_RE,relevance:0 +},_=n.optional(s)+e.IDENT_RE+"\\s*\\(",u={ +keyword:["asm","auto","break","case","continue","default","do","else","enum","extern","for","fortran","goto","if","inline","register","restrict","return","sizeof","typeof","typeof_unqual","struct","switch","typedef","union","volatile","while","_Alignas","_Alignof","_Atomic","_Generic","_Noreturn","_Static_assert","_Thread_local","alignas","alignof","noreturn","static_assert","thread_local","_Pragma"], +type:["float","double","signed","unsigned","int","short","long","char","void","_Bool","_BitInt","_Complex","_Imaginary","_Decimal32","_Decimal64","_Decimal96","_Decimal128","_Decimal64x","_Decimal128x","_Float16","_Float32","_Float64","_Float128","_Float32x","_Float64x","_Float128x","const","static","constexpr","complex","bool","imaginary"], +literal:"true false NULL", +built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr" +},g=[c,r,t,e.C_BLOCK_COMMENT_MODE,o,l],m={variants:[{begin:/=/,end:/;/},{ +begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}], +keywords:u,contains:g.concat([{begin:/\(/,end:/\)/,keywords:u, +contains:g.concat(["self"]),relevance:0}]),relevance:0},p={ +begin:"("+i+"[\\*&\\s]+)+"+_,returnBegin:!0,end:/[{;=]/,excludeEnd:!0, +keywords:u,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:a,keywords:u,relevance:0},{ +begin:_,returnBegin:!0,contains:[e.inherit(d,{className:"title.function"})], +relevance:0},{relevance:0,match:/,/},{className:"params",begin:/\(/,end:/\)/, +keywords:u,relevance:0,contains:[t,e.C_BLOCK_COMMENT_MODE,l,o,r,{begin:/\(/, +end:/\)/,keywords:u,relevance:0,contains:["self",t,e.C_BLOCK_COMMENT_MODE,l,o,r] +}]},r,t,e.C_BLOCK_COMMENT_MODE,c]};return{name:"C",aliases:["h"],keywords:u, +disableAutodetect:!0,illegal:"=]/,contains:[{ +beginKeywords:"final class struct"},e.TITLE_MODE]}]),exports:{preprocessor:c, +strings:l,keywords:u}}}})();hljs.registerLanguage("c",e)})();/*! `diff` grammar compiled for Highlight.js 11.10.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const a=e.regex;return{name:"Diff", +aliases:["patch"],contains:[{className:"meta",relevance:10, +match:a.either(/^@@ +-\d+,\d+ +\+\d+,\d+ +@@/,/^\*\*\* +\d+,\d+ +\*\*\*\*$/,/^--- +\d+,\d+ +----$/) +},{className:"comment",variants:[{ +begin:a.either(/Index: /,/^index/,/={3,}/,/^-{3}/,/^\*{3} /,/^\+{3}/,/^diff --git/), +end:/$/},{match:/^\*{15}$/}]},{className:"addition",begin:/^\+/,end:/$/},{ +className:"deletion",begin:/^-/,end:/$/},{className:"addition",begin:/^!/, +end:/$/}]}}})();hljs.registerLanguage("diff",e)})();/*! `javascript` grammar compiled for Highlight.js 11.10.0 */ +(()=>{var e=(()=>{"use strict" +;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],t=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],s=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],r=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],c=["arguments","this","super","console","window","document","localStorage","sessionStorage","module","global"],i=[].concat(r,t,s) +;return o=>{const l=o.regex,b=e,d={begin:/<[A-Za-z0-9\\._:-]+/, +end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ +const a=e[0].length+e.index,t=e.input[a] +;if("<"===t||","===t)return void n.ignoreMatch();let s +;">"===t&&(((e,{after:n})=>{const a="e+"\\s*\\(")), +l.concat("(?!",T.join("|"),")")),b,l.lookahead(/\s*\(/)), +className:"title.function",relevance:0};var T;const C={ +begin:l.concat(/\./,l.lookahead(l.concat(b,/(?![0-9A-Za-z$_(])/))),end:b, +excludeBegin:!0,keywords:"prototype",className:"property",relevance:0},M={ +match:[/get|set/,/\s+/,b,/(?=\()/],className:{1:"keyword",3:"title.function"}, +contains:[{begin:/\(\)/},R] +},B="(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+o.UNDERSCORE_IDENT_RE+")\\s*=>",$={ +match:[/const|var|let/,/\s+/,b,/\s*/,/=\s*/,/(async\s*)?/,l.lookahead(B)], +keywords:"async",className:{1:"keyword",3:"title.function"},contains:[R]} +;return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{ +PARAMS_CONTAINS:w,CLASS_REFERENCE:k},illegal:/#(?![$_A-z])/, +contains:[o.SHEBANG({label:"shebang",binary:"node",relevance:5}),{ +label:"use_strict",className:"meta",relevance:10, +begin:/^\s*['"]use (strict|asm)['"]/ +},o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,h,N,_,f,p,{match:/\$\d+/},A,k,{ +className:"attr",begin:b+l.lookahead(":"),relevance:0},$,{ +begin:"("+o.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", +keywords:"return throw case",relevance:0,contains:[p,o.REGEXP_MODE,{ +className:"function",begin:B,returnBegin:!0,end:"\\s*=>",contains:[{ +className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{ +className:null,begin:/\(\s*\)/,skip:!0},{begin:/(\s*)\(/,end:/\)/, +excludeBegin:!0,excludeEnd:!0,keywords:g,contains:w}]}]},{begin:/,/,relevance:0 +},{match:/\s+/,relevance:0},{variants:[{begin:"<>",end:""},{ +match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:d.begin, +"on:begin":d.isTrulyOpeningTag,end:d.end}],subLanguage:"xml",contains:[{ +begin:d.begin,end:d.end,skip:!0,contains:["self"]}]}]},I,{ +beginKeywords:"while if switch catch for"},{ +begin:"\\b(?!function)"+o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", +returnBegin:!0,label:"func.def",contains:[R,o.inherit(o.TITLE_MODE,{begin:b, +className:"title.function"})]},{match:/\.\.\./,relevance:0},C,{match:"\\$"+b, +relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"}, +contains:[R]},x,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},O,M,{match:/\$[(.]/}]}}})() +;hljs.registerLanguage("javascript",e)})();/*! `json` grammar compiled for Highlight.js 11.10.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const a=["true","false","null"],s={ +scope:"literal",beginKeywords:a.join(" ")};return{name:"JSON",aliases:["jsonc"], +keywords:{literal:a},contains:[{className:"attr", +begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/,relevance:1.01},{match:/[{}[\],:]/, +className:"punctuation",relevance:0 +},e.QUOTE_STRING_MODE,s,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE], +illegal:"\\S"}}})();hljs.registerLanguage("json",e)})();/*! `markdown` grammar compiled for Highlight.js 11.10.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const n={begin:/<\/?[A-Za-z_]/, +end:">",subLanguage:"xml",relevance:0},a={variants:[{begin:/\[.+?\]\[.*?\]/, +relevance:0},{ +begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/, +relevance:2},{ +begin:e.regex.concat(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/), +relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{ +begin:/\[.*?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{match:/\[(?=\])/ +},{className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0, +returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)", +excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[", +end:"\\]",excludeBegin:!0,excludeEnd:!0}]},i={className:"strong",contains:[], +variants:[{begin:/_{2}(?!\s)/,end:/_{2}/},{begin:/\*{2}(?!\s)/,end:/\*{2}/}] +},s={className:"emphasis",contains:[],variants:[{begin:/\*(?![*\s])/,end:/\*/},{ +begin:/_(?![_\s])/,end:/_/,relevance:0}]},c=e.inherit(i,{contains:[] +}),t=e.inherit(s,{contains:[]});i.contains.push(t),s.contains.push(c) +;let g=[n,a];return[i,s,c,t].forEach((e=>{e.contains=e.contains.concat(g) +})),g=g.concat(i,s),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{ +className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:g},{ +begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n", +contains:g}]}]},n,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)", +end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:g, +end:"$"},{className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{ +begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{ +begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))", +contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{ +begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{ +className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{ +className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]},{scope:"literal", +match:/&([a-zA-Z0-9]+|#[0-9]{1,7}|#[Xx][0-9a-fA-F]{1,6});/}]}}})() +;hljs.registerLanguage("markdown",e)})();/*! `plaintext` grammar compiled for Highlight.js 11.10.0 */ +(()=>{var t=(()=>{"use strict";return t=>({name:"Plain text", +aliases:["text","txt"],disableAutodetect:!0})})() +;hljs.registerLanguage("plaintext",t)})();/*! `shell` grammar compiled for Highlight.js 11.10.0 */ +(()=>{var s=(()=>{"use strict";return s=>({name:"Shell Session", +aliases:["console","shellsession"],contains:[{className:"meta.prompt", +begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#][ ]?/,starts:{end:/[^\\](?=\s*$)/, +subLanguage:"bash"}}]})})();hljs.registerLanguage("shell",s)})();/*! `sql` grammar compiled for Highlight.js 11.10.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const r=e.regex,t=e.COMMENT("--","$"),n=["true","false","unknown"],a=["bigint","binary","blob","boolean","char","character","clob","date","dec","decfloat","decimal","float","int","integer","interval","nchar","nclob","national","numeric","real","row","smallint","time","timestamp","varchar","varying","varbinary"],i=["abs","acos","array_agg","asin","atan","avg","cast","ceil","ceiling","coalesce","corr","cos","cosh","count","covar_pop","covar_samp","cume_dist","dense_rank","deref","element","exp","extract","first_value","floor","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","last_value","lead","listagg","ln","log","log10","lower","max","min","mod","nth_value","ntile","nullif","percent_rank","percentile_cont","percentile_disc","position","position_regex","power","rank","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","row_number","sin","sinh","sqrt","stddev_pop","stddev_samp","substring","substring_regex","sum","tan","tanh","translate","translate_regex","treat","trim","trim_array","unnest","upper","value_of","var_pop","var_samp","width_bucket"],s=["create table","insert into","primary key","foreign key","not null","alter table","add constraint","grouping sets","on overflow","character set","respect nulls","ignore nulls","nulls first","nulls last","depth first","breadth first"],o=i,c=["abs","acos","all","allocate","alter","and","any","are","array","array_agg","array_max_cardinality","as","asensitive","asin","asymmetric","at","atan","atomic","authorization","avg","begin","begin_frame","begin_partition","between","bigint","binary","blob","boolean","both","by","call","called","cardinality","cascaded","case","cast","ceil","ceiling","char","char_length","character","character_length","check","classifier","clob","close","coalesce","collate","collect","column","commit","condition","connect","constraint","contains","convert","copy","corr","corresponding","cos","cosh","count","covar_pop","covar_samp","create","cross","cube","cume_dist","current","current_catalog","current_date","current_default_transform_group","current_path","current_role","current_row","current_schema","current_time","current_timestamp","current_path","current_role","current_transform_group_for_type","current_user","cursor","cycle","date","day","deallocate","dec","decimal","decfloat","declare","default","define","delete","dense_rank","deref","describe","deterministic","disconnect","distinct","double","drop","dynamic","each","element","else","empty","end","end_frame","end_partition","end-exec","equals","escape","every","except","exec","execute","exists","exp","external","extract","false","fetch","filter","first_value","float","floor","for","foreign","frame_row","free","from","full","function","fusion","get","global","grant","group","grouping","groups","having","hold","hour","identity","in","indicator","initial","inner","inout","insensitive","insert","int","integer","intersect","intersection","interval","into","is","join","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","language","large","last_value","lateral","lead","leading","left","like","like_regex","listagg","ln","local","localtime","localtimestamp","log","log10","lower","match","match_number","match_recognize","matches","max","member","merge","method","min","minute","mod","modifies","module","month","multiset","national","natural","nchar","nclob","new","no","none","normalize","not","nth_value","ntile","null","nullif","numeric","octet_length","occurrences_regex","of","offset","old","omit","on","one","only","open","or","order","out","outer","over","overlaps","overlay","parameter","partition","pattern","per","percent","percent_rank","percentile_cont","percentile_disc","period","portion","position","position_regex","power","precedes","precision","prepare","primary","procedure","ptf","range","rank","reads","real","recursive","ref","references","referencing","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","release","result","return","returns","revoke","right","rollback","rollup","row","row_number","rows","running","savepoint","scope","scroll","search","second","seek","select","sensitive","session_user","set","show","similar","sin","sinh","skip","smallint","some","specific","specifictype","sql","sqlexception","sqlstate","sqlwarning","sqrt","start","static","stddev_pop","stddev_samp","submultiset","subset","substring","substring_regex","succeeds","sum","symmetric","system","system_time","system_user","table","tablesample","tan","tanh","then","time","timestamp","timezone_hour","timezone_minute","to","trailing","translate","translate_regex","translation","treat","trigger","trim","trim_array","true","truncate","uescape","union","unique","unknown","unnest","update","upper","user","using","value","values","value_of","var_pop","var_samp","varbinary","varchar","varying","versioning","when","whenever","where","width_bucket","window","with","within","without","year","add","asc","collation","desc","final","first","last","view"].filter((e=>!i.includes(e))),l={ +begin:r.concat(/\b/,r.either(...o),/\s*\(/),relevance:0,keywords:{built_in:o}} +;return{name:"SQL",case_insensitive:!0,illegal:/[{}]|<\//,keywords:{ +$pattern:/\b[\w\.]+/,keyword:((e,{exceptions:r,when:t}={})=>{const n=t +;return r=r||[],e.map((e=>e.match(/\|\d+$/)||r.includes(e)?e:n(e)?e+"|0":e)) +})(c,{when:e=>e.length<3}),literal:n,type:a, +built_in:["current_catalog","current_date","current_default_transform_group","current_path","current_role","current_schema","current_transform_group_for_type","current_user","session_user","system_time","system_user","current_time","localtime","current_timestamp","localtimestamp"] +},contains:[{begin:r.either(...s),relevance:0,keywords:{$pattern:/[\w\.]+/, +keyword:c.concat(s),literal:n,type:a}},{className:"type", +begin:r.either("double precision","large object","with timezone","without timezone") +},l,{className:"variable",begin:/@[a-z0-9][a-z0-9_]*/},{className:"string", +variants:[{begin:/'/,end:/'/,contains:[{begin:/''/}]}]},{begin:/"/,end:/"/, +contains:[{begin:/""/}]},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,{ +className:"operator",begin:/[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/, +relevance:0}]}}})();hljs.registerLanguage("sql",e)})();/*! `typescript` grammar compiled for Highlight.js 11.10.0 */ +(()=>{var e=(()=>{"use strict" +;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],t=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],s=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],r=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],c=["arguments","this","super","console","window","document","localStorage","sessionStorage","module","global"],i=[].concat(r,t,s) +;function o(o){const l=o.regex,d=e,b={begin:/<[A-Za-z0-9\\._:-]+/, +end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ +const a=e[0].length+e.index,t=e.input[a] +;if("<"===t||","===t)return void n.ignoreMatch();let s +;">"===t&&(((e,{after:n})=>{const a="e+"\\s*\\(")), +l.concat("(?!",C.join("|"),")")),d,l.lookahead(/\s*\(/)), +className:"title.function",relevance:0};var C;const T={ +begin:l.concat(/\./,l.lookahead(l.concat(d,/(?![0-9A-Za-z$_(])/))),end:d, +excludeBegin:!0,keywords:"prototype",className:"property",relevance:0},M={ +match:[/get|set/,/\s+/,d,/(?=\()/],className:{1:"keyword",3:"title.function"}, +contains:[{begin:/\(\)/},R] +},B="(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+o.UNDERSCORE_IDENT_RE+")\\s*=>",$={ +match:[/const|var|let/,/\s+/,d,/\s*/,/=\s*/,/(async\s*)?/,l.lookahead(B)], +keywords:"async",className:{1:"keyword",3:"title.function"},contains:[R]} +;return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{ +PARAMS_CONTAINS:w,CLASS_REFERENCE:x},illegal:/#(?![$_A-z])/, +contains:[o.SHEBANG({label:"shebang",binary:"node",relevance:5}),{ +label:"use_strict",className:"meta",relevance:10, +begin:/^\s*['"]use (strict|asm)['"]/ +},o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,p,N,f,_,h,{match:/\$\d+/},A,x,{ +className:"attr",begin:d+l.lookahead(":"),relevance:0},$,{ +begin:"("+o.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", +keywords:"return throw case",relevance:0,contains:[h,o.REGEXP_MODE,{ +className:"function",begin:B,returnBegin:!0,end:"\\s*=>",contains:[{ +className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{ +className:null,begin:/\(\s*\)/,skip:!0},{begin:/(\s*)\(/,end:/\)/, +excludeBegin:!0,excludeEnd:!0,keywords:g,contains:w}]}]},{begin:/,/,relevance:0 +},{match:/\s+/,relevance:0},{variants:[{begin:"<>",end:""},{ +match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:b.begin, +"on:begin":b.isTrulyOpeningTag,end:b.end}],subLanguage:"xml",contains:[{ +begin:b.begin,end:b.end,skip:!0,contains:["self"]}]}]},O,{ +beginKeywords:"while if switch catch for"},{ +begin:"\\b(?!function)"+o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", +returnBegin:!0,label:"func.def",contains:[R,o.inherit(o.TITLE_MODE,{begin:d, +className:"title.function"})]},{match:/\.\.\./,relevance:0},T,{match:"\\$"+d, +relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"}, +contains:[R]},I,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},k,M,{match:/\$[(.]/}]}}return t=>{ +const s=o(t),r=e,l=["any","void","number","boolean","string","object","never","symbol","bigint","unknown"],d={ +begin:[/namespace/,/\s+/,t.IDENT_RE],beginScope:{1:"keyword",3:"title.class"} +},b={beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:{ +keyword:"interface extends",built_in:l},contains:[s.exports.CLASS_REFERENCE] +},g={$pattern:e, +keyword:n.concat(["type","interface","public","private","protected","implements","declare","abstract","readonly","enum","override","satisfies"]), +literal:a,built_in:i.concat(l),"variable.language":c},u={className:"meta", +begin:"@"+r},m=(e,n,a)=>{const t=e.contains.findIndex((e=>e.label===n)) +;if(-1===t)throw Error("can not find mode to replace");e.contains.splice(t,1,a)} +;Object.assign(s.keywords,g),s.exports.PARAMS_CONTAINS.push(u) +;const E=s.contains.find((e=>"attr"===e.className)) +;return s.exports.PARAMS_CONTAINS.push([s.exports.CLASS_REFERENCE,E]), +s.contains=s.contains.concat([u,d,b]), +m(s,"shebang",t.SHEBANG()),m(s,"use_strict",{className:"meta",relevance:10, +begin:/^\s*['"]use strict['"]/ +}),s.contains.find((e=>"func.def"===e.label)).relevance=0,Object.assign(s,{ +name:"TypeScript",aliases:["ts","tsx","mts","cts"]}),s}})() +;hljs.registerLanguage("typescript",e)})();/*! `yaml` grammar compiled for Highlight.js 11.10.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const n="true false yes no null",a="[\\w#;/?:@&=+$,.~*'()[\\]]+",s={ +className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/ +},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable", +variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},i=e.inherit(s,{ +variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={ +end:",",endsWithParent:!0,excludeEnd:!0,keywords:n,relevance:0},t={begin:/\{/, +end:/\}/,contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]", +contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{ +begin:/\w[\w :()\./-]*:(?=[ \t]|$)/},{begin:/"\w[\w :()\./-]*":(?=[ \t]|$)/},{ +begin:/'\w[\w :()\./-]*':(?=[ \t]|$)/}]},{className:"meta",begin:"^---\\s*$", +relevance:10},{className:"string", +begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{ +begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0, +relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type", +begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a +},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta", +begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)", +relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{ +className:"number", +begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b" +},{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},t,g,s],r=[...b] +;return r.pop(),r.push(i),l.contains=r,{name:"YAML",case_insensitive:!0, +aliases:["yml"],contains:b}}})();hljs.registerLanguage("yaml",e)})(); \ No newline at end of file diff --git a/docs/javascripts/init.js b/docs/javascripts/init.js new file mode 100644 index 000000000000..66ce4773c3d9 --- /dev/null +++ b/docs/javascripts/init.js @@ -0,0 +1 @@ +hljs.highlightAll(); diff --git a/docs/mapping/design.md b/docs/mapping/design.md new file mode 100644 index 000000000000..0eb78cf21fc8 --- /dev/null +++ b/docs/mapping/design.md @@ -0,0 +1,197 @@ +# Mapping Design Guide + +The purpose of this guide is to instruct how to design maps for Paradise. While +there are many resources on the technical aspects of mapping, this guide instead +focuses on considering mapping from a thematic, functional, and balance +perspective. + +## Design Guidelines + +Maps are one of the most visible ways of conveying the world and setting of the +server to players. Maps should work to preserve that setting. Paradise Station +takes place in a 26th century universe where multiple space-faring species work +on stations owned by a pangalactic corporate conglomerate. New maps, ruins, and +remaps should make sense within that world. + +1. ***Use the appropriate decorative elements and turf types.*** Department + flooring should use their associated colors: red for Security, brown for + Cargo, blue for Medbay, and so on. Stations should always use standard walls + and reinforced walls, and not e.g. plastitanium walls. Stations should always + use standard airlocks, and not e.g. syndicate hatches or Centcomm airlocks. + +2. ***Avoid excessive use of decals or floor tile variants.*** Using too many + decals or floor tile variants causes unnecessary visual noise. Only use + decals such as warning tape where it is sensible, e.g. around airlocks that + lead to space. Decal overuse contributes to maptick slowdown. + +2. ***Avoid "Big Room Disease".*** "Big Room Disease" refers to areas on a map + that are unnecessarily large, empty, and/or square. Rooms should be large + enough to handle crew foot traffic and facilitate their use, but no larger. + Furniture should be placed appropriately inside rooms, not just lined along + their walls. Large rooms should rarely be perfectly square or rectangular. + +3. ***Public areas should be interesting.*** "Interesting" is subjective, but + generally, areas such as public hallways should include space for crew + interaction; decorations such as posters, flags, and other decorative + structures; windows that look out onto space, and floor tiles and decals that + delineate the space. + +4. ***Use appropriate hall sizes.*** Primary hallways should be three tiles + wide. Arterial hallways off the primary halls should be two tiles wide. + Intradepartmental corridors should be one or two tiles wide. Exceptions to + this include the Brig, Medbay, and Science, whose main halls can be three + tiles wide due to the amount of foot traffic and number of sub departments + within their space. + +5. ***Properly signpost maintenance areas.*** "Signposting" refers to + environmental factors that make it clear what part of maintenance the player + is in. For example, while "med maints" on Cyberiad is the area around medbay, + it is also distinguishable from its abandoned cryo tube, medical dufflebag, + operating table, and so on. Note however that this signposting does not need + to be directly related to nearby departments: for example, mining maints on + Cyberiad has a small abandoned gambling room, a laundry room, and several + abandoned shower/bathroom areas. This distinguishes it from other maintenance + areas despite not directly referencing mining, as players will eventually + associate these distinct features with that area of maintenance. + +6. ***Ensure continuity of scale.*** The size of rooms should make sense + relative to one another. The chef’s freezer should not be larger than their + kitchen. The dorm’s bathroom should not be larger than the Captain’s office. + The scale of rooms should make sense for their expected occupancy and + purpose. For example, the Heads of Staff Meeting Room should be large enough + to seat all staff comfortably around a table, with extra space for navigating + foot traffic around the table. + + +## Balance Guidelines + +Maps should be an unbiased playing field for players, whether ordinary crew, +silicon, antagonists, or midrounds. Players should not be able to rely on a +specific station layout to gain unique advantages over other players. + +1. ***Maintain consistent loot counts and opportunities.*** The amount of + maintenance loot drops should remain consistent, with a slight scaling factor + based on expected station population. There should be no "treasure troves" or + hoards of loot hidden that can only be found with specific map knowledge. + Department supplies should be consistent across maps. Do not place any + syndicate items/traitor tools on station; always use the provided maintenance + loot spawners to maintain proper statistical likelihood of rare loot spawns. + +2. ***Use appropriate reinforcement.*** Most of the station should be delineated + with ordinary walls and reinforced windows. Only secure areas should use + reinforced walls and grilled spawners, and electrified windows should only be + used in rare cases: department head offices, technical storage, brig, + xenobio, and AI satellite. + +3. ***AI cameras should not have full coverage.*** The AI should not be + permitted to see into every single room. This makes it challenging for + antagonists to accomplish their objectives _in situ_. It is not enough to + have cameras that antagonists can disable, since an AI will notice that the + camera is out, when it is not usually. Areas appropriate for lacking cameras + include Operating Rooms, the Therapist’s office, the Execution Chamber, and + Dormitory bathrooms and shower rooms. Similarly: + +4. ***AI cameras should never be placed in maints.*** This is prohibited + completely. It provides a disproportionately competitive advantage for sec + against antagonists. Currently there is one exception to this, and that is + the cameras immediately outside the solar array maintenance areas on + Cyberiad. These give AI only a vague hint of what is happening nearby, and + very limited visibility into events in maints. + +5. ***Weak points are expected.*** The station is not a battle fortress, and it + is not fun for antagonists to attempt to ingress/egress deliberately + impenetrable areas. For example, Permabrig areas will typically have one or + two tools just out of reach for prisoners to attempt escape. There is a + toolbox in the far end of the gulag island to give gulag prisoners a chance + to escape. The Head of Security’s Office is bordered by outer space on two + station maps. Attempting to break into and out of sensitive areas should be + challenging, but not impossible. + +6. ***Occasionally place vents and scrubbers under furniture.*** Having all + vents and scrubbers prominently visible hinders ventcrawling antagonists. It + is easy for crew to forget to weld vents they cannot see. + +7. ***Ensure security/antag balance for maintenance tunnels.*** This includes + but is not limited to: having a primary path that allows navigability from + all entrances; providing ways for security to flank antagonists and + coordinate ambushes at maintenance entrances; ensuring that the majority of + the primary corridors are 2 tiles wide to allow for serpentine movement and + avoiding projectiles; ensuring that dead ends are rare; and providing places + for antagonists to hide using hidden walls or similarly difficult to find + places. + +8. ***Allow for escape routes and antag break-in routes.*** Departments should + be connected to maintenance through a back or side door. This lets players + escape and allows antags to break in. If this is not possible, departments + should have extra entry and exit points. + +## Functional Guidelines + +Stations are malleable. Players can build, rebuild, decorate, upholster, and +equip the station in many ways. Mappers should take this into account when +designing areas and departments. This goes doubly so for ruins: players will +always find a way to work around the restrictions and intended flow of your +ruin. Attempting to enforce a "correct" way of interacting with a map without +deviation is impossible. + +1. ***Rooms should have specific and clear functions.*** Public rooms should + have a clear purpose. Large maintenance areas should appear to have had a + clear purpose—an abandoned robotics department, for example, or a disused + monkey-fighting ring. The `/area/station` subtypes enumerate most of what + rooms are expected within a station and its departments. Even if a room is + largely meant for player expansion, it should use an appropriate type and + name, such as the Vacant Office. + +2. ***Do not create "perfect" departments.*** The stations are not ideal + workplaces, not state-of-the-art, and not diligently maintained by + Nanotrasen. There should always be a gap between the ideal station and how + the maps are designed. Departments should not come fully featured and + configured, and should require crew interaction to set up and use + effectively. Examples of this include Medbay preparing Operating Rooms, Cargo + arranging the office to make access to the autolathe more convenient, and + Engineering reconfiguring the supermatter’s pipenet. This scarcity is also + critical to crew interactions: the Kitchen should have to rely on botany to + make the full range of recipes, etc. + +3. ***Provide surfaces.*** All jobs require managing many different objects, + items, and pieces of equipment. There should be an adequate number of tables + and racks available for department members to place things down and drop + things off. + +4. ***Place emergency lockers at appropriate intervals.*** Emergency closets and + fire-safety closets should be accessible to crew at regular intervals in + primary hallways, or just off primary hallways in adjacent maintenance + tunnels. + +## Ruin-Specific Guidance + +1. ***Balance the risk/reward ratio of a ruin appropriately.*** If a player + decides that the risk of running a ruin is not worth the reward, they will + stop running it. + +2. ***Not all ruins should provide rare loot.*** "Low-reward" ruins should exist + to balance out random generation, so that every ruin in a round is not a loot + resource. These ruins can be purely decorative, provide a place for + role-play, or provide diegetic/environmental storytelling. + +3. ***Ruins should fit the setting, and have a well signposted purpose.*** If, + for example, your ruin is some kind of abandoned technical/research facility, + it should should have appropriately defined areas: an obvious entrance, a + working area for staff and crew, a testing lab, containment area for living + specimens, some way for staff to have food, restrooms, and living + quarters/showers if they are intended to stay on the facility for extended + periods of time. + +4. ***Avoid ‘magic’ power whenever possible.*** While infinitely regenerating + APCs and SMES units exist, they should be used sparingly. Ruins should be as + realistic as possible and afford players the ability to take advantage of + being powered or unpowered to navigate or exploit the ruin when possible. + +## Shuttle-Specific Guidance + +1. ***Shuttles should have clearly defined secure areas and bridges.*** Secure + areas are for security and prisoner seating. Bridges are for all Command and + dignitaries, and include the emergency shuttle console. Consideration should + be given for hijackers and accessibility to the emergency shuttle console, as + well as the ability of crew to storm the bridge if necessary to prevent a + hijack. diff --git a/docs/mapping/images/atmos_pipes_aligned.png b/docs/mapping/images/atmos_pipes_aligned.png new file mode 100644 index 000000000000..06001f5c82bc Binary files /dev/null and b/docs/mapping/images/atmos_pipes_aligned.png differ diff --git a/docs/mapping/images/atmos_pipes_unaligned.png b/docs/mapping/images/atmos_pipes_unaligned.png new file mode 100644 index 000000000000..3e181b3c7594 Binary files /dev/null and b/docs/mapping/images/atmos_pipes_unaligned.png differ diff --git a/docs/mapping/images/complex_pipes.png b/docs/mapping/images/complex_pipes.png new file mode 100644 index 000000000000..8f43df990ff9 Binary files /dev/null and b/docs/mapping/images/complex_pipes.png differ diff --git a/docs/mapping/quickstart.md b/docs/mapping/quickstart.md new file mode 100644 index 000000000000..130b6f1e341d --- /dev/null +++ b/docs/mapping/quickstart.md @@ -0,0 +1,82 @@ +# Mapping Quickstart + +The purpose of this guide is to provide basic instructions on how to start +mapping for Paradise Station and the tools needed to do so. + +## Tooling + +Once you have set up your [development environment][env], you will need several +other tools to edit maps and publish your changes for Pull Requests. + +### Mapmerge + +If you have a map change published as a PR, and someone else makes a change to +that map which is merged before yours, it is likely that there will be a merge +conflict. Because of the way map files are formatted, using git to resolve these +merge conflicts directly will result in a broken map. + +To deal with this, a separate tool, *Mapmerge*, is integrated into git. Mapmerge +has the ability to look at the changes between two maps, merge them together +correctly, and provide markers on the map where it requires a contributor to +make a manual change. + +To install Mapmerge, run `\tools\hooks\Install.bat`. Further usage of Mapmerge +is documented in the [Guide to Mapmerge](). + +
+ +Unless you know how to use git effectively, install Mapmerge **before** having +to deal with a map merge conflict. + +
+ +[env]: ../contributing/getting_started.md + +### StrongDMM + +[StrongDMM][] is the recommended tool for editing maps by a wide margin. It is +fast, provides easy searching for both objects on maps and objects in the +codebase, an intuitive varediting system, the ability to hide categories of +objects on the map while editing, and more. + +When using StrongDMM, the following options must be enabled. They can be found +under _File -> Preferences_: + + - "Sanitize Variables" must be checked. This removes variables that are + declared on the map, but are the same as their initial value.. (For example: + A standard floor turf that has `dir = 2` declared on the map will have that + variable deleted as it is redundant.) + - "Save Format" must be set to "TGM". + - "Nudge Mode" must be set to "pixel_x/pixel_y". + +[StrongDMM]: https://github.com/SpaiR/StrongDMM/releases + +### UpdatePaths + +_UpdatePaths_ is a utility which make it easier for mappers to share simple +large-scale changes across maps. It does this by allowing mappers to write +scripts which describe those changes, which are then applied to maps. For +example, when migrating pixel-pushed ATMs to their directional helpers, this +script was written: + +``` +/obj/machinery/economy/atm{pixel_x=-32} : /obj/machinery/economy/atm/directional/west +/obj/machinery/economy/atm{pixel_x=32} : /obj/machinery/economy/atm/directional/east +/obj/machinery/economy/atm{pixel_y=-32} : /obj/machinery/economy/atm/directional/south +/obj/machinery/economy/atm{pixel_y=32} : /obj/machinery/economy/atm/directional/north +``` + +This takes each object found on a map with the specified `pixel_x`/`y` varedits, +and replaces them with the object on the right side of the line. + +More information on UpdatePaths and how to use it is available in the +[UpdatePaths documentation][upd]. + +[upd]: https://github.com/ParadiseSS13/Paradise/blob/master/tools/UpdatePaths/readme.md + +## Mapping Tutorial + +Until a more specific guide is written for Paradise Station, /tg/station's +[Guide to Mapping](https://hackmd.io/@tgstation/SyVma0dS5#san7890s-A-Z-Guide-to-Mapping) +written by san7890 is a recommended resource for use SDMM, test mapping changes, +and reconcile map merge conflicts. diff --git a/docs/mapping/requirements.md b/docs/mapping/requirements.md new file mode 100644 index 000000000000..c0a01c5f406f --- /dev/null +++ b/docs/mapping/requirements.md @@ -0,0 +1,196 @@ +# Mapping Requirements + +In order for mapping changes to comply with our existing codebase, conventions, +and in-game systems, there are several guidelines that must be followed. + +## Technical Standards + +### Atmospherics and Cables + +1. Unless absolutely necessary, do not run atmospherics pipes or disposals pipes + under wall turfs. **NEVER** run cables under wall turfs. + +2. Every room should contain at least one air vent and scrubber. Use the + following "on" subtype of vents and scrubbers as opposed to varediting: + `/obj/machinery/atmospherics/unary/vent_scrubber/on` and + `/obj/machinery/atmospherics/unary/vent_pump/on`. + +3. Run air pipes together where possible. The first example below is to be + avoided, the second is optimal: + + ![](./images/atmos_pipes_unaligned.png) ![](./images/atmos_pipes_aligned.png) + + Pipe layouts should be logical and predictable, easy to understand at a + glance. Always avoid complex layouts like in this example: + + ![](./images/complex_pipes.png) + +4. External areas, or areas where depressurisation is expected and normal, + should use airless turf variants to prevent additional atmospherics load. + +5. Tiny fans (`/obj/structure/fans/tiny`) can be used to block airflow into + problematic areas, but are not a substitute for proper door and firelock + combinations. They are useful under blast doors that lead to space when + opened. + +### Wall Mounts + +1. Every station area (`/area/station` subtypes) should contain only one APC and + air alarm. + +2. Critical infrastructure rooms (such as the engine, arrivals, and medbay + areas) should be given an APC with a larger power cell. Use the + `/obj/machinery/power/apc/important` and `/obj/machinery/power/apc/critical` + mapping helpers for this purpose. + +3. Every room should contain at least one fire alarm. Fire alarms should not be + placed next to expected heat sources. + +4. Every room should contain at least one station intercom. Intercoms should be + set to frequency `145.9`, and be speaker ON Microphone OFF. This is so radio + signals can reach people even without headsets on. Larger rooms will require + more than one at a time. + +5. Every room should have at least one security camera with the caveats listed + in the [Design Guide](design.md). Larger rooms may require more than one + security camera. All security cameras should have a descriptive name that + makes it easy to find on a camera console. A good example would be the + template \[Department name\] - \[Area\], so Brig - Cell 1, or Medbay - + Treatment Center. Consistency is key to good camera naming. + +6. Every room should have at least one light switch. When possible, light + switches should be placed in such a position that a player can activate them + while standing on the same tile as the room's airlock. Players should not + have to wander through a dark room to find the light switch. + +7. Head of Staff offices should contain a requests console, using the + `/obj/machinery/requests_console/directional` helpers. Console department + names and types should not be varedited. + +8. Use lights sparingly. They draw a significant amount of power. + +### Windows, Walls, and Floors + +1. Electrochromic windows (`/obj/structure/window/reinforced/polarized`) and + doors/windoors (using the `/obj/effect/mapping_helpers/airlock/polarized` + helper) are preferred over shutters as the method of restricting view to a + room through windows. Shutters are sill appropriate in industrial/hazardous + areas of the station (engine rooms, HoP line, science test chamber, etc.). + Electrochromic window/windoor/door sets require a unique ID var, and a + window tint button (`/obj/machinery/button/windowtint`) with a matching ID + var. The default `range` of the button is 7 tiles but can be amended with a + varedit. + +2. Windows to secure areas or external areas should be reinforced. Windows in + engine areas should be reinforced plasma glass. Windows in high security + areas, such as the brig, bridge, and head of staff offices, should be + electrified by placing a wire node under the window. + +3. Engine areas, or areas with a high probability of receiving explosions, + should use reinforced flooring if appropriate. + +### Airlocks, Windoors, and Firelocks + +1. Firelocks should be used at area boundaries over doors and windoors, but not + windows. Firelocks can also be used to break up hallways at reasonable + intervals. Double firelocks are not permitted. Maintenance access doors + should never have firelocks placed over them. + +2. Door and windoor access must be correctly set by the + `/obj/effect/mapping_helpers/airlock/access` and + `/obj/effect/mapping_helpers/airlock/windoor/access` [helpers][], + respectively. Pay attention to the `any` and `all` subtypes; the `any` + subtype allows someone with any of the accesses on the airlock to use it, + and the `all` subtypes requires the user to have all of the access on the + airlock to use it. + + For example, on the Cerebron (Metastation), miners must walk through the + Cargo Bay to access the Mining Dock. They do not have Cargo Bay access, + rather the Cargo Bay airlocks have two access helpers on them: + + - `/obj/effect/mapping_helpers/airlock/access/any/supply/cargo_bay` + - `/obj/effect/mapping_helpers/airlock/access/any/supply/mining` + + This allows both cargo technicians and miners to use those airlocks. + + Old doors that use var edited access should be updated to use the correct + access helper, and the var edit on the door should be cleaned. + +### Other + +1. Edits in mapping tools should almost always be possible to replicate + in-game. For this reason, avoid stacking multiple structures on the same + tile (e.g. placing a light and an APC on the same wall). + +2. When adding new shuttles, or remapping departures areas, contributors must + ensure that all existing and new shuttles continue to fit and dock to the + correct airlocks as expected. Any time docking ports are edited, the author + needs to confirm the `width`, `height`, and `dwidth` variables between the + two permanent ports and mobile port are compatible. + +[helpers]: https://github.com/ParadiseSS13/Paradise/blob/master/code/modules/mapping/access_helpers.dm + +### Varedits + +*Varediting*, or variable editing, is the term for modifying a variable of an +object on the map, instead of in code. There are many legitimate reasons to do +so. For example, since nearly all floor tiles on the station are the same +object, their `icon_state` and `dir` variables need to be edited to modify their +appearance. + +However, there are also cases when varediting is not appropriate. In general, +when modifying the behavior of an object, creating a subtype in code is almost +always the better option. For example, let's say you have an `/obj/helmet` with +a variable, `strength`, which defines how much damage it can take. The default +is 10. You want to create a stronger helmet, so you add one into a map, and +varedit its `strength` to be 20. This may work for now, but what if the strength +of a helmet no longer is based off that variable? Your helmet will no longer +work as expected. If you instead made an `/obj/helmet/strong`, and made the +variable change there, then if the implementation for `/obj/helmet` changes, +your object will benefit from those changes. + +Another example of inappropriate varediting is doing it to an object with many +instances on a map, or multiple instances across maps. If you need to change the +variable, you will then have to find every instance of it across all of the +maps, and change them all. + +Areas should **never** be varedited on a map. All areas of a single type, +altered instance or not, are considered the same area within the code, and +editing their variables on a map can lead to issues with powernets and event +subsystems which are difficult to debug. + +Subtypes only intended to be used on ruins should be contained within an .dm +file with a name corresponding to that map within `code\modules\ruins`. This is +so in the event that the map is removed, that subtype will be removed at the +same time as well to minimize leftover/unused data within the repo. + +When not using StrongDMM (which handles the following automatically) please +attempt to clean out any dirty variables that may be contained within items you +alter through varediting. For example changing the `pixel_x` variable from 23 to +0 will leave a dirty record in the map's code of `pixel_x = 0`. + +Unless they require custom placement, when placing the following items use the +relevant directional mapper, as it has predefined pixel offsets and directions +that are standardised: APC, Air alarm, Fire alarm, station intercom, newscaster, +extinguisher cabinet, light switches. + +## Mapper Contribution Guidelines + +These guidelines apply to **all** mapping contributors. + +For mapping PRs, we do not accept 'change for the sake of change' remaps, unless +you have very good reasoning to do so. Maintainers reserve the right to close +your PR if we disagree with your reasoning. Large remaps, such as those to a +department, must be justified with clear, specific reasons. + +Before committing a map change, you **MUST** run Mapmerge to normalise your +changes. You can do this manually before every commit with +`tools\mapmerge2\Run Before Committing.bat` or by letting the +[git hooks](./quickstart.md#mapmerge) do it for you. Failure to run Mapmerge on +a map after editing greatly increases the risk of the map's key dictionary +becoming corrupted by future edits after running map merge. Resolving the +corruption issue involves rebuilding the map's key dictionary. + +If you are making non-minor edits to an area or room, (non-minor being anything +more than moving a few objects or fixing small bugs) then you should ensure the +entire area/room is updated to meet these standards. diff --git a/.github/AUTODOC_GUIDE.md b/docs/references/autodoc.md similarity index 53% rename from .github/AUTODOC_GUIDE.md rename to docs/references/autodoc.md index 80c366fecf8f..bb1278becf86 100644 --- a/.github/AUTODOC_GUIDE.md +++ b/docs/references/autodoc.md @@ -1,47 +1,51 @@ # dmdoc -[DOCUMENTATION]: https://codedocs.paradisestation.org/ -[BYOND]: https://secure.byond.com/ - -[DMDOC]: https://github.com/SpaceManiac/SpacemanDMM/tree/master/crates/dmdoc - -[DMDOC] is a documentation generator for DreamMaker, the scripting language -of the [BYOND] game engine. It produces simple static HTML files based on +[dmdoc] is a documentation generator for DreamMaker, the scripting language of +the [BYOND] game engine. It produces simple static HTML files based on documented files, macros, types, procs, and vars. -We use **dmdoc** to generate [DOCUMENTATION] for our code, and that documentation -is automatically generated and built on every new commit to the master branch +We use **dmdoc** to generate [documentation] for our code, and that +documentation is automatically generated and built on every new commit to the +master branch + +This gives new developers a clickable reference [documentation] they can browse +to better help gain understanding of the Paradise codebase structure and api +reference. -This gives new developers a clickable reference [DOCUMENTATION] they can browse to better help -gain understanding of the Paradise codebase structure and api reference. +[documentation]: https://codedocs.paradisestation.org/ +[BYOND]: https://secure.byond.com/ +[dmdoc]: https://github.com/SpaceManiac/SpacemanDMM/tree/master/crates/dmdoc -## Documenting code on Paradise -We use block comments to document procs and classes, and we use `///` line comments -when documenting individual variables. +## Documenting Code On Paradise +We use block comments to document procs and classes, and we use `///` line +comments when documenting individual variables. -Documentation is not required at Paradise, but it is highly recommended that all new code be covered with DMdoc code, according to the [Specifications](#Specification) +Documentation is not required at Paradise, but it is highly recommended that all +new code be covered with DMdoc code, according to the +[Specifications](#specification). We also recommend that when you touch older code, you document the functions that you have touched in the process of updating that code ### Specification -A class *should* always be autodocumented, and all public functions *should* be documented +A class *should* always be auto-documented, and all public functions *should* be +documented. -All class level defined variables *should* be documented +All class level defined variables *should* be documented. -Internal functions *can* be documented, but may not be +Internal functions *can* be documented, but may not be. A public function is any function that a developer might reasonably call while using or interacting with your object. Internal functions are helper functions that your -public functions rely on to implement logic - +public functions rely on to implement logic. ### Documenting a proc When documenting a proc, we give a short one line description (as this is shown next to the proc definition in the list of all procs for a type or global namespace), then a longer paragraph which will be shown when the user clicks on the proc to jump to it's definition -``` + +```dm /** * Short description of the proc * @@ -53,14 +57,13 @@ the proc to jump to it's definition */ ``` -### Documenting a class -We first give the name of the class as a header, this can be omitted if the name is -just going to be the typepath of the class, as dmdoc uses that by default +### Documenting Classes +We first give the name of the class as a header, this can be omitted if the name +is just going to be the typepath of the class, as dmdoc uses that by default. +Then we give a short one-line description of the class. Finally we give a longer +multi paragraph description of the class and it's details. -Then we give a short oneline description of the class - -Finally we give a longer multi paragraph description of the class and it's details -``` +```dm /** * # Classname (Can be omitted if it's just going to be the typepath) * @@ -75,7 +78,8 @@ Finally we give a longer multi paragraph description of the class and it's detai ### Documenting a variable/define Give a short explanation of what the variable, in the context of the class, or define is. -``` + +```dm /// Type path of item to go in suit slot var/suit = null ``` @@ -89,20 +93,20 @@ that will also be rendered and added to the modules tree. The structure for these is deliberately not defined, so you can be as freeform and as wheeling as you would like. -[Here is a representative example of what you might write](https://codedocs.paradisestation.org/code/modules/keybindings/readme.html) - ## Special variables -You can use certain special template variables in DM DOC comments and they will be expanded -``` - [DEFINE_NAME] - Expands to a link to the define definition if documented - [/mob] - Expands to a link to the docs for the /mob class - [/mob/proc/Dizzy] - Expands to a link that will take you to the /mob class and anchor you to the dizzy proc docs - [/mob/var/stat] - Expands to a link that will take you to the /mob class and anchor you to the stat var docs -``` +You can use certain special template variables in DM DOC comments and they will +be expanded. + +- `[DEFINE_NAME]` expands to a link to the define definition if documented. +- `[/mob]` expands to a link to the docs for the /mob class. +- `[/mob/proc/Dizzy]` expands to a link that will take you to the /mob class and + anchor you to the dizzy proc docs. +- `[/mob/var/stat]` expands to a link that will take you to the /mob class and + anchor you to the stat var docs -You can customise the link name by using `[link name][link shorthand].` +You can customise the link name by using `[link name][link shorthand]`. -eg. `[see more about dizzy here] [/mob/proc/Dizzy]` +e.g. `[see more about dizzy here][/mob/proc/Dizzy]` This is very useful to quickly link to other parts of the autodoc code to expand -upon a comment made, or reasoning about code +upon a comment made, or reasoning about code. diff --git a/.github/USING_FEEDBACK_DATA.md b/docs/references/feedback_data.md similarity index 54% rename from .github/USING_FEEDBACK_DATA.md rename to docs/references/feedback_data.md index 889c14e5ff9f..4c94ce915371 100644 --- a/.github/USING_FEEDBACK_DATA.md +++ b/docs/references/feedback_data.md @@ -2,13 +2,18 @@ ## Introduction -`Feedback` is the name of the data storage system used for logging game statistics to the database. It is managed by `SSblackbox` and can be recorded in many formats. This guide will contain information on how to record feedback data properly, as well as what should and should not be recorded. +`Feedback` is the name of the data storage system used for logging game +statistics to the database. It is managed by `SSblackbox` and can be recorded in +many formats. This guide will contain information on how to record feedback data +properly, as well as what should and should not be recorded. ## Things you should and should not record -Feedback data can be useful, depending on how you use it. You need to be careful with what you record to make sure you are not saving useless data. Examples of good things to record: +Feedback data can be useful, depending on how you use it. You need to be careful +with what you record to make sure you are not saving useless data. Examples of +good things to record: -- Antagonist win/loss rates if a new gamemode or antagonist is being added +- Antagonist win/loss rates if a new game mode or antagonist is being added - Department performance (IE: Slime cores produced in science) - Basically anything which has actual meaning @@ -16,17 +21,21 @@ Examples of bad things to record: - Amount of times a wrench is used (Why) - Hours spent on the server (We have other means of that) -- Basically, just think about it and ask yourself "Is this actually useful to base game design around" +- Basically, just think about it and ask yourself "Is this actually useful to + base game design around" -Also note that feedback data **must** be anonymous. The only exception here is for data *anyone* on the server can see, such as round end antagonist reports. +Also note that feedback data **must** be anonymous. The only exception here is +for data *anyone* on the server can see, such as round end antagonist reports. ## Feedback Data Recording -Feedback data can be reocrded in 5 formats. `amount`, `associative`, `nested tally`, `tally` and `text`. +Feedback data can be recorded in 5 formats. `amount`, `associative`, `nested +tally`, `tally` and `text`. ### Amount -`amount` is the simplest form of feedback data recording. They are simply a numerical number which increase with each feedback increment. For example: +`amount` is the simplest form of feedback data recording. They are simply a +numerical number which increase with each feedback increment. For example: These DM calls: @@ -40,15 +49,23 @@ Will produce the following JSON: ```json { - "data":10 + "data": 10 } ``` -Notice the lack of any specific identification other than it being the value of the `data` key. Amounts are designed to be simple with minimal complexity, and are useful for logging statistics such as how many times one specific, distinct action is done (e.g.: How many MMIs have been filled). If you want to log multiple similar things (e.g.: How many mechas have been created, broken down by the mecha type), use a `tally` with a sub-key for each different mecha, instead of an amount with its own key per mecha. +Notice the lack of any specific identification other than it being the value of +the `data` key. Amounts are designed to be simple with minimal complexity, and +are useful for logging statistics such as how many times one specific, distinct +action is done (e.g.: How many MMIs have been filled). If you want to log +multiple similar things (e.g.: How many mechas have been created, broken down by +the mecha type), use a `tally` with a sub-key for each different mecha, instead +of an amount with its own key per mecha. ### Associative -`associative` is used to record text that's associated with multiple key-value pairs. (e.g: coordinates). Further calls to the same key will append a new list to existing data. For example: +`associative` is used to record text that's associated with multiple key-value +pairs. (e.g: coordinates). Further calls to the same key will append a new list +to existing data. For example: ```dm SSblackbox.record_feedback("associative", "example", 1, list("text" = "example", "path" = /obj/item, "number" = 4)) @@ -59,28 +76,39 @@ Will produce the following JSON: ```json { - "data":{ - "1":{ - "text":"example", - "path":"/obj/item", - "number":"4" + "data": { + "1": { + "text": "example", + "path": "/obj/item", + "number": "4" }, - "2":{ - "number":"7", - "text":"example", - "other text":"sample" + "2": { + "number": "7", + "text": "example", + "other text": "sample" } } } ``` -Notice how everything is cast to strings, and each new entry added has its index increased ("1", "2", etc). Also take note how the `increment` parameter is not used here. It does nothing to the data, and `1` is used just as the value for consistency. +Notice how everything is cast to strings, and each new entry added has its index +increased ("1", "2", etc). Also take note how the `increment` parameter is not +used here. It does nothing to the data, and `1` is used just as the value for +consistency. ### Nested Tally -`nested tally` is used to track the number of occurances of structured semi-relational values (e.g.: the results of arcade machines). You can think of it as a running total, with the key being a list of strings (rather than a single string), with elements incrementally identifying the entity in question. +`nested tally` is used to track the number of occurrences of structured +semi-relational values (e.g.: the results of arcade machines). You can think of +it as a running total, with the key being a list of strings (rather than a +single string), with elements incrementally identifying the entity in question. -Technically, the values are nested in a multi-dimensional array. The final element in the data list is used as the tracking key, and all prior elements are used for nesting. Further calls to the same key will add or subtract from the saved value of the data key if it already exists in the same multi-dimensional position, and append the key and it's value if it doesn't exist already. This one is quite complicated, but an example is below: +Technically, the values are nested in a multi-dimensional array. The final +element in the data list is used as the tracking key, and all prior elements are +used for nesting. Further calls to the same key will add or subtract from the +saved value of the data key if it already exists in the same multi-dimensional +position, and append the key and it's value if it doesn't exist already. This +one is quite complicated, but an example is below: ```dm SSblackbox.record_feedback("nested tally", "example", 1, list("fruit", "orange", "apricot")) @@ -115,7 +143,8 @@ Will produce the following JSON: #### NOTE -Tracking values associated with a number can't merge with a nesting value, trying to do so will append the list +Tracking values associated with a number can't merge with a nesting value, +trying to do so will append the list ```dm SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange")) @@ -145,11 +174,15 @@ Will produce the following JSON: } ``` -Avoid doing this, since having duplicate keys in JSON (data.fruit.orange) will break when parsing. +Avoid doing this, since having duplicate keys in JSON (data.fruit.orange) will +break when parsing. ### Tally -`tally` is used to track the number of occurances of multiple related values (e.g.: how many times each type of gun is fired). Further calls to the same key will add or subtract from the saved value of the data key if it already exists, and append the key and it's value if it doesn't exist. +`tally` is used to track the number of occurrences of multiple related values +(e.g.: how many times each type of gun is fired). Further calls to the same key +will add or subtract from the saved value of the data key if it already exists, +and append the key and it's value if it doesn't exist. ```dm SSblackbox.record_feedback("tally", "example", 1, "sample data") @@ -170,7 +203,10 @@ Will produce the following JSON: ### Text -`text` is used for simple single-string records (e.g.: the current chaplain religion). Further calls to the same key will append saved data unless the overwrite argument is true or it already exists. When encoded, calls made with overwrite will lack square brackets. +`text` is used for simple single-string records (e.g.: the current chaplain +religion). Further calls to the same key will append saved data unless the +overwrite argument is true or it already exists. When encoded, calls made with +overwrite will lack square brackets. ```dm SSblackbox.record_feedback("text", "example", 1, "sample text") @@ -189,11 +225,16 @@ Will produce the following JSON: } ``` -Note how `"sample text"` only appears once. `text` is a set with no duplicates, instead of a list with duplicates. Also take note how the `increment` parameter is not used here. It does nothing to the data, and `1` is used just as the value for consistency. +Note how `"sample text"` only appears once. `text` is a set with no duplicates, +instead of a list with duplicates. Also take note how the `increment` parameter +is not used here. It does nothing to the data, and `1` is used just as the value +for consistency. ## Feedback Versioning -If the logging content (i.e.: What data is logged) for a variable is ever changed, the version needs bumping. This can be done with the `versions` list on the subsystem definition itself. All values default to `1`. +If the logging content (i.e.: What data is logged) for a variable is ever +changed, the version needs bumping. This can be done with the `versions` list on +the subsystem definition itself. All values default to `1`. ```dm var/list/versions = list( @@ -202,4 +243,5 @@ var/list/versions = list( "gun_fired" = 2) ``` -If you are doing a type change (i.e.: Changing from a `tally` to a `nested tally`), **USE AN ENTIRELY NEW KEY NAME**. +If you are doing a type change (i.e.: Changing from a `tally` to a `nested +tally`), **USE AN ENTIRELY NEW KEY NAME**. diff --git a/docs/references/glossary.md b/docs/references/glossary.md new file mode 100644 index 000000000000..ab9f8bf59713 --- /dev/null +++ b/docs/references/glossary.md @@ -0,0 +1,263 @@ +Below is a glossary of common used terms along with their meanings relating to +the coding side of SS13. If you notice any missing terms of discrepancies in +descriptions, feel free to contribute to the list! Feel free to ask any +questions in `#coding-chat` on the discord. Non-coding related Glossary can be +found on the [wiki][]. More in depth information can be found at BYOND's +official [documentation][]. + +[wiki]: https://paradisestation.org/wiki/index.php?title=Glossary +[documentation]: https://secure.byond.com/docs/ref/#/DM/ + +## .DM +DreamMaker code files, or .dm files are the file format for BYOND source code. +These files must be "ticked" in the .dme file for them to be included in the +game. + +## .DMB +"DreamMaker Build" or DMB files are compiled DME files and are +used with Dream Daemon to run the server. + +## .DME +"DreamMaker Environment" or DME files are what BYOND uses to compile the game. +It is a list of all .dm files used in the game, if you add a new file you will +need to "Tick" it or add it to this file manually. + +## .DMI +DreamMaker images or DMI files is how BYOND stores images (also known as icons), +these can be edited with BYOND's tools or external tools. + +## .DMM +DreamMaker maps or DMM files is how BYOND stores maps. These can be edited with +BYOND's tools or something like [StrongDMM](#strongdmm) + +## Area +From the [BYOND documentation](https://secure.byond.com/docs/ref/#/area/): + +> "Areas are derived from /area. Regions on the map may be assigned to an area +by painting it onto the map. Areas off the map serve as rooms that objects may +enter and exit. For each area type defined, one area object is created at +runtime. So for areas on the map, all squares with the same area type belong to +the same instance of the area." + +In SS13, this is often used to delineate station departments and station systems +such as power and atmospherics networks. + +## Atmos +The atmospherics system in SS13, which is very often old and confusing and/or +broken code. + +## Atom +From the [BYOND documentation](https://secure.byond.com/docs/ref/#/atom/): + +> "The /atom object type is the ancestor of all mappable +objects in the game. The types /area, /turf, /obj, and /mob are all +derived from /atom. You should not create instances of /atom directly +but should use /area, /turf, /obj, and /mob for actual objects. The +/atom object type exists for the purpose of defining variables or +procedures that are shared by all of the other 'physical' objects. +These are also the only objects for which verbs may be accessible to the +user. /atom is derived from /datum, so it inherits the basic properties that +are shared by all DM objects." + +## Baseturf +An SS13 variable that saves the data of what is underneath if that that is +removed. For example, under station floors there would be a space turf and under +Lavaland turfs there would be lava. + +## Buff +A buff is a change to a gameplay mechanic that makes it more powerful or more +useful. Generally the opposite of a [nerf](#nerf). + +## Commit +A record of files changed and how they were changed, they are each assigned a +special ID called a hash that specifies the changes it makes. + +## Config +The config.toml file for changing things about your local server. You will need +to copy this from the config/example folder if you haven't already. + +## Datum +From the [BYOND documentation](https://secure.byond.com/docs/ref/#/datum/): + +> "The datum object is the ancestor of all other data types in DM. (The only +exceptions are currently `/world`, `/client`, `/list`, and `/savefile`, but +those will be brought into conformance soon.) That means that the variables and +procedures of /datum are inherited by all other types of objects." + +Datums are useful to represent abstractions that don't physically exist in the +game world, such as information about a spell or a Syndicate uplink item. They +are also useful for vars or procs that all other data-types use. + +## Define +A way of declaring variable either global (across the whole game) or in a whole +file using DM's `#DEFINE` macro syntax. They should always be found at the +beginning of a file. Defines should always be capitalized (LIKE_THIS) and if not +global should undefined at the end of a file. + +## Fastmos +Fast atmos, usually featuring explosive decomposition and lots of in game death. +Fastmos is not used on Paradise. + +## Garbage +The garbage collector handles items being deleted and allows them to clean up +references, this allows objects to delete much more efficiently. + +## Head Admin +Head of the admin team and overseeing overall changes and the direction for the +entire Paradise codebase and server. Contact them or the Balance team about +large changes or balance changes before making a PR, including map additions, +new roles, new antagonists, and other similar things. + +## Icondiffbot +A tool on GitHub that renders before and after images of BYOND icons. It can be +viewed on any PR by scrolling down to the checks section and clicking details +next to it. + +## LGTM +"Looks Good To Me", used during code reviews. + +## Local +Your copy of your remote repository on your local machine or computer. Commits +need to be published to your remote repo before they can be pushed to the +upstream repo for a pull request. + +## Maintainer +A no longer used title, previously used for people who made sure code is +quality. Maintainers were split up into the Balance Team, Design Team, and several +other groups. Check [PR #18000](https://github.com/ParadiseSS13/Paradise/pull/18000/) for more +information. + +## Map merge +Tools that automatically attempt to merge maps when merging master or +committing. Map merge is a work in progress and may require manual editing too. + +## Mapdiffbot +A tool on GitHub that renders before and after images of BYOND maps. It can be +viewed on any PR by scrolling down to the checks section and clicking details +next to it. + +## Master Controller +The Master Controller controls all subsystems of +the game, such as the [garbage collector](#garbage). + +## MC +Short for Short for [Master Controller](#master-controller). + +## Merge Master +The process of merging master into your PR's branch, often to update it. + +## Mob +Mobs are "mobile objects", these include players and animals. This does not +include stuff like conveyors. + +## Nerf +Nerfs are changes to a gameplay mechanic that make it less powerful or decreases +its utility, typically done for the sake of improving game balance and +enjoyability. Generally the opposite of a [buff](#buff). + +## NPFC +"No Player-Facing Changes", used in the changelog of a PR, most often in +refractors and exploit fixes. + +## Object +Objects are things you can interact with in game, including things that do not +move. This includes weapons, items, machinery (consoles and machines), and +several other things. + +## Origin +Typically another name for your [remote repo](#remote). + +## PR +Short for [Pull Request](#pull-request). + +## Proc +Procs or Procedures are block of code that only runs when it is called. These +are similar to something like functions in other languages. + +## Publish +Uploading your code from your local machine. + +## Pull Request +A request to the Paradise Github Repository for certain +changes to be made to the code of the game. This includes maps and sprites. + +## Pulling code +Pulling is transferring commits from the main repo to your remote repo, or from +your remote repository to your local repository. + +## Pushing code +Pushing is how you transfer commits from your repository to the Upstream repo. + +## qdel +A function, `qdel()`, which tells the [garbage collector](#garbage) to handle +destruction of an object. This should always be used over `del()`. + +## QDEL_NULL +A [qdel](#qdel) function which first nulls out a variable before telling the +garbage collector to handle it. + +## Remote +Your forked copy of the upstream repo that you have complete access over. your +clean copy of master and any published branches you've made can be found here. + +## Repo +Short for [repository](#repository). + +## Repository +A collection of code which tracks the commits and changes to it. There are three +main types you will find the upstream repository, your remote repository, and +your local repository. + +## Runechat Chat +Chat messages which appear above player's characters, a feature added by +[#14141](https://github.com/ParadiseSS13/Paradise/pull/14141/). Often a joke +about players now missing important things in the chat window since they no +longer have to look there for reading messages from people. + +## Runtime +Runtimes most often refer to runtime errors, which are errors that happen after +compiling and happen in game. + +## StrongDMM +A [robust mapping tool](https://github.com/SpaiR/StrongDMM/) that is highly +recommended over BYOND's DMM editor, as it is much quicker and has much more +options. Using any version below 2.0 makes your PR very unlikely to be accepted +as it messes with variables. + +## TGUI +A JavaScript based format for displaying an interface. It is +used for our user interfaces (except OOC stuff like admin panels), or +are planned to be converted to TGUI. TGUI uses InfernoJS (based off of +reactJS) which is an extension to JavaScript. More information can be +found at the [TGUI Tutorial][]. + +[TGUI Tutorial]: https://github.com/ParadiseSS13/Paradise/blob/master/tgui/docs/tutorial-and-examples.md + +## Turf +Turfs are floors, stuff like space, floors, carpets, or lava, or walls. This +does not include windows, as they are objects. + +## Upstream +The original repo that you have forked your remote repository from. For us, it +is [ParadiseSS13/Paradise](https://github.com/ParadiseSS13/Paradise/). + +## Var +A variable, used for temporarily storing data. For more +permanent data, check out [defines](#define). + +## Verb +A special type of proc, which is only available to mobs. + +## View Variables +An admin tool that can be used in game to view the variables of anything, giving +you more information about them. Very useful for debugging. + +## VSC +Short for [Visual Studio Code](https://code.visualstudio.com/). + +## VV +Short for [View Variables](#view-variables). + +## WYCI +"When You Code It", a joking response to someone asking when something will be +added to the game. diff --git a/docs/references/tick_order.md b/docs/references/tick_order.md new file mode 100644 index 000000000000..84116c536646 --- /dev/null +++ b/docs/references/tick_order.md @@ -0,0 +1,53 @@ +# BYOND Tick Order + +This document roughly describes the order in which BYOND performs operations in a given tick. + +The BYOND tick proceeds as follows: + +1. Procs sleeping via `walk()` are resumed (I don't know why these are first). + +2. Normal sleeping procs are resumed, in the order they went to sleep in the + first place. This is where the MC wakes up and processes subsystems. A + consequence of this is that the MC almost never resumes before other sleeping + procs, because it only goes to sleep for 1 tick 99% of the time, and 99% of + procs either go to sleep for less time than the MC (which guarantees that + they entered the sleep queue earlier when its time to wake up) and/or were + called synchronously from the MC's execution, almost all of the time the MC + is the last sleeping proc to resume in any given tick. This is good because + it means the MC can account for the cost of previous resuming procs in the + tick, and minimizes overtime. + +3. Control is passed to BYOND after all of our code's procs stop execution for this tick. + +4. A few small things happen in BYOND internals. + +5. SendMaps is called for this tick, which processes the game state for all + clients connected to the game and handles sending them changes in appearances + within their view range. This is expensive and takes up a significant portion + of our tick, about 0.45% per connected player as of 3/20/2022. This means + that with 50 players, 22.5% of our tick is being used up by just SendMaps, + after all of our code has stopped executing. That's only the average across + all rounds, for most high-pop rounds it can look like 0.6% of the tick per + player, which is 30% for 50 players. + +6. After SendMaps ends, client verbs sent to the server are executed, and it's + the last major step before the next tick begins. During the course of the + tick, a client can send a command to the server saying that they have + executed any verb. The actual code defined for that /verb/name() proc isn't + executed until this point, and the way the MC is designed makes this + especially likely to make verbs "overrun" the bounds of the tick they + executed in, stopping the other tick from starting and thus delaying the MC + firing in that tick. + +The master controller can derive how much of the tick was used in: procs +executing before it woke up (because of world.tick_usage), and SendMaps (because +of world.map_cpu, since this is a running average you cant derive the tick spent +on maptick on any particular tick). It cannot derive how much of the tick was +used for sleeping procs resuming after the MC ran, or for verbs executing after +SendMaps. + +It is for these reasons why you should heavily limit processing done in verbs. +While procs resuming after the MC are rare, verbs are not, and are much more +likely to cause overtime since they're literally at the end of the tick. If you +make a verb, try to offload any expensive work to the beginning of the next tick +via a verb management subsystem. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 000000000000..e5ea2885260c --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,88 @@ +site_name: Paradise Contributor Documentation + +theme: + name: material + icon: + logo: material/palm-tree + palette: + accent: custom + primary: custom + favicon: ./images/favicon.png + +extra: + social: + - icon: material/forum + name: Paradise Station Discussion Forum + link: https://www.paradisestation.org/forum + - icon: fontawesome/brands/wikipedia-w + name: Paradise Station Wiki + link: https://www.paradisestation.org/wiki + - icon: fontawesome/brands/github + name: Paradise Station GitHub + link: https://github.com/ParadiseSS13/Paradise + - icon: fontawesome/brands/discord + name: Paradise Station Discord + link: https://discord.gg/paradisess13 + - icon: fontawesome/brands/patreon + name: Paradise Station Patreon + link: https://www.patreon.com/ParadiseStation + generator: false + +plugins: + - gh-admonitions + +markdown_extensions: + - admonition + - attr_list + - smarty + - pymdownx.highlight: + use_pygments: false + - pymdownx.details + - pymdownx.superfences + - pymdownx.snippets + +hooks: + - 'docs/hooks/contributing_path.py' + +extra_javascript: + - 'javascripts/highlight.min.js' + - 'javascripts/highlight-dm.js' + - 'javascripts/init.js' + +extra_css: + - 'css/para.css' + - 'css/atom-one-dark.css' + +nav: + # TODO: Coding / Code Style Guide + # TODO: Mapping / Guide to Mapmerge + # TODO: Spriting + # TODO: SS13 for Experienced Coders (maybe) + + - 'Introduction': 'index.md' + - 'Code of Conduct': 'CODE_OF_CONDUCT.md' + - 'Contributing Guidelines': 'CONTRIBUTING.md' + + - 'Contributing': + - 'Getting Started': './contributing/getting_started.md' + - 'Reviewer Crash Course': './contributing/reviewer.md' + - 'Writing Quality PRs': './contributing/quality_prs.md' + + - 'Coding': + - 'Coding Quickstart': './coding/quickstart.md' + - 'Guide to Testing': './coding/testing_guide.md' + - 'Guide to Debugging': './coding/debugging.md' + - 'Style Guidelines': './coding/style_guidelines.md' + - 'Coding Requirements': './coding/coding_requirements.md' + - 'Testing Requirements': './coding/testing_requirements.md' + + - 'Mapping': + - 'Mapping Quickstart': './mapping/quickstart.md' + - 'Mapping Requirements': './mapping/requirements.md' + - 'Design Guidelines': './mapping/design.md' + + - 'References': + - 'Glossary': './references/glossary.md' + - 'Autodoc Guide': './references/autodoc.md' + - 'Using Feedback Data': './references/feedback_data.md' + - 'Tick Order': './references/tick_order.md'