Centralize developer documentation. (#26485)

* Documentation.

* Documentation.

* Testing reqs update

* Post feature-freeze code of conduct updates

* spell checking

* Style Guidelines

* wrap

* link up, bring headers up one level

* wrap

* fix old link

* support github admonition syntax for mkdocs

* link rules, rename to guard clauses

* ffffucking vscode

---------

Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
This commit is contained in:
warriorstar-orion
2024-09-04 12:43:33 -04:00
committed by GitHub
parent 1b2b8434bd
commit 1d538adc1d
108 changed files with 5646 additions and 1338 deletions
-904
View File
@@ -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
"<a href='byond://?src=\ref[src];'>Link!</a>"
//Good
"<a href='byond://?src=[UID()];'>Link!</a>"
```
### 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<<x)` is actually very simple and, as the name implies, shifts the bits of a given binary number over by one digit.
```dm
000100 (4, or (1<<2))
<<
001000 (8, or (1<<3))
```
Using this system makes the code more readable and less prone to error.
### Legacy Code
SS13 has a lot of legacy code that's never been updated. Here are some examples of common legacy trends which are no longer acceptable:
- To display messages to all mobs that can view `user`, you should use `visible_message()`.
```dm
//Bad
for(var/mob/M in viewers(user))
M.show_message("<span class='warning'>Arbitrary text</span>")
//Good
user.visible_message("<span class='warning'>Arbitrary text</span>")
```
- You should not use color macros (`\red, \blue, \green, \black`) to color text, instead, you should use span classes. `<span class='warning'>Red text</span>`, `<span class='notice'>Blue text</span>`.
```dm
//Bad
to_chat(user, "\red Red text \black Black text")
//Good
to_chat(user, "<span class='warning'>Red text</span>Black 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**
-105
View File
@@ -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
+1 -1
View File
@@ -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.
<!-- Replace the box with [x] to mark as complete. -->
<!-- Ensure there are no spaces between the x and the square brackets [] else this will not work properly. -->
<hr>
-178
View File
@@ -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.
-21
View File
@@ -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.
+3
View File
@@ -74,3 +74,6 @@ $RECYCLE.BIN
# Rust output.
/milla/target/*
# mkdocs output.
site
-55
View File
@@ -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.
<!-- Add a fixed link so that changing the heading name doesn't break the link. -->
<a id="types-of-changes-that-need-approval"></a>
> [!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**
+4 -4
View File
@@ -56,7 +56,7 @@
> [!TIP]
> Want to contribute for the first time but unsure where to start?<br>
> Join our Discord and check out the [#coding_chat](https://discord.com/channels/145533722026967040/145700319819464704) channel for helpful links and advice!<br>
> 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.
+112
View File
@@ -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.
<!-- Add a fixed link so that changing the heading name doesn't break the link. -->
<a id="types-of-changes-that-need-approval"></a>
> [!IMPORTANT]
>
> <h3>Currently, changes to the following types of content requires pre-approval:</h3>
>
> - **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**
+226
View File
@@ -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
<https://www.paradisestation.org/dev/policy/>.
+867
View File
@@ -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
"<a href='byond://?src=\ref[src];'>Link!</a>"
//Good
"<a href='byond://?src=[UID()];'>Link!</a>"
```
[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<<x)` is actually very simple and,
as the name implies, shifts the bits of a given binary number over by one digit.
```dm
000100 (4, or (1<<2))
<<
001000 (8, or (1<<3))
```
Using this system makes the code more readable and less prone to error.
## Legacy Code
SS13 has a lot of legacy code that's never been updated. Here are some examples
of common legacy trends which are no longer acceptable:
- To display messages to all mobs that can view `user`, you should use
`visible_message()`.
```dm
//Bad
for(var/mob/M in viewers(user))
M.show_message("<span class='warning'>Arbitrary text</span>")
//Good
user.visible_message("<span class='warning'>Arbitrary text</span>")
```
- You should not use color macros (`\red, \blue, \green, \black`) to color text,
instead, you should use span classes. `<span class='warning'>Red text</span>`,
`<span class='notice'>Blue text</span>`.
```dm
//Bad
to_chat(user, "\red Red text \black Black text")
//Good
to_chat(user, "<span class='warning'>Red text</span>Black 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
+304
View File
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+519
View File
@@ -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, "<span class='warning'>That's not a machine!</span>")
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 = "<i>'Special delivery!'</i>"
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)
+345
View File
@@ -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(
"<span class='notice'>[user] writes Hello World!</span>",
"<span class='notice'>You write Hello World!.</span>",
"<span class='notice'>You hear someone writing Hello World!.</span>"
)
```
#### 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, "<span class='notice'>Message with the notice style.</span>")
```
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 codes 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("<span class='notice'>[user] writes the style guide.</span>",
"<span class='notice'>You wonder if you're following the guide correctly.</span>")
// Good
var/list/numbers = list(
1,
2,
3,
)
user.visible_message(
"<span class='notice'>[user] writes the style guide.</span>",
"<span class='notice'>You write the style guide.</span>",
"<span class='notice'>You hear typing.</span>"
)
// 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 languages 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
<B>This is an example of how not to do it.</B>
// Good
<b>This is an example of how it should be.</b>
```
## 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!
+225
View File
@@ -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.
+77
View File
@@ -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
+478
View File
@@ -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: <https://code.visualstudio.com/>
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 <https://vscode.github.com/>.
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 <https://github.com/settings/emails>.
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 <https://github.com/ParadiseSS13/rust-g>
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 thats 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 its 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, youll 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 youve done and youll 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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+272
View File
@@ -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.
+317
View File
@@ -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, "<span class='notice'>[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 `<span class='notice'>` and close it with `</span>` 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, "<span class='notice'>[user] is very [pick(announce_verbs)]</span>")
is_cool = TRUE
return
to_chat(user, "<span class='notice'>[user] is not very [pick(announce_verbs)]</span>")
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, "<span class='notice'>[user] is very [pick(announce_verbs)]</span>")
is_cool = TRUE
return
to_chat(user, "<span class='notice'>[user] is not very [pick(announce_verbs)]</span>")
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).
+90
View File
@@ -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
}
+28
View File
@@ -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;
}
+36
View File
@@ -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)
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+15
View File
@@ -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
+113
View File
@@ -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 <pieterjan.briers@gmail.com>
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();
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
hljs.highlightAll();
+197
View File
@@ -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 chefs freezer should not be larger than their
kitchen. The dorms bathroom should not be larger than the Captains 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 Therapists 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 Securitys 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 supermatters 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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Some files were not shown because too many files have changed in this diff Show More