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>
@@ -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:
|
||||
|
||||

|
||||
|
||||
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
|
||||
@@ -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`.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
Looking at the `/obj/effect/beam/i_beam` object we see that this is indeed the
|
||||
case.
|
||||
|
||||

|
||||
|
||||
`/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`
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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`.
|
||||
|
||||

|
||||
|
||||
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)`.
|
||||
|
||||

|
||||
|
||||
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`.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
We are not interested in most of them and instead, we want to find `holder`
|
||||
|
||||

|
||||
|
||||
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?
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 22 KiB |
@@ -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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
#### 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.
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
`/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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
@@ -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 code’s purpose
|
||||
or functionality.
|
||||
|
||||
```dm
|
||||
// This is a single-line comment
|
||||
```
|
||||
|
||||
### Multi-Line Comments
|
||||
|
||||
Used for longer explanations or comments spanning multiple lines. Good for
|
||||
documenting parameters of procs.
|
||||
|
||||
```dm
|
||||
/*
|
||||
* This is a multi-line comment.
|
||||
* It spans multiple lines and provides detailed explanations.
|
||||
*/
|
||||
```
|
||||
|
||||
### Autodoc Comments
|
||||
|
||||
[Autodoc][] is used for documenting variables, procs, and other elements that
|
||||
require additional clarification. This is a useful tool, as it allows a coder to
|
||||
see additional information about a variable or proc without having to navigate
|
||||
to its declaration. To apply properly, an Autodoc comment should be used
|
||||
**BEFORE** the actual declaration of a variable or proc.
|
||||
|
||||
```dm
|
||||
/// This is an Autodoc example
|
||||
var/example_variable = TRUE
|
||||
```
|
||||
|
||||
[Autodoc]: ../references/autodoc.md
|
||||
|
||||
### Define Comments
|
||||
|
||||
When documenting single-line macros such as constants, use the "enclosing
|
||||
comment format", `//!`. This prevents issues with macro expansion:
|
||||
|
||||
```dm
|
||||
#define BLUE_KEY 90 //! The access code for the blue key.
|
||||
```
|
||||
|
||||
These constant names can then be referred to in other Autodoc comments by
|
||||
enclosing their names in brackets:
|
||||
|
||||
```dm
|
||||
/// This door only opens if your access is [BLUE_KEY].
|
||||
/obj/door/blue
|
||||
...
|
||||
```
|
||||
|
||||
### Mark Comments
|
||||
|
||||
Used to delineate distinct sections within a file when necessary. It should only
|
||||
be used for that purpose. Avoid using it for items, procs, or datums.
|
||||
|
||||
```dm
|
||||
// MARK: [Section Name]
|
||||
```
|
||||
|
||||
### Commented Out Code
|
||||
|
||||
Commented out code is generally not permitted within the code base unless it is
|
||||
used for the purpose of debugging. Code that is commented out during a
|
||||
contribution should be removed prior to creating a pull request. If you are
|
||||
unsure whether or not something should be left commented out, please contact a
|
||||
development team member.
|
||||
|
||||
## Multi-Line Procs and List Formatting
|
||||
|
||||
When calling procs with very long arguments (such as list or proc definitions
|
||||
with multiple or especially dense arguments), it may sometimes be preferable to
|
||||
spread them out across multiple lines for clarity. For some more text-heavy
|
||||
procs where readability of the arguments is especially important (such as
|
||||
visible_message), you're asked to always multi-line them if you're providing
|
||||
multiple arguments.
|
||||
|
||||
For lists that may be subject to frequent code churn, we suggest adding a
|
||||
trailing comma as well, as it prevents the line from needing to be changed
|
||||
unnecessarily down the line.
|
||||
|
||||
```dm
|
||||
// Bad
|
||||
var/list/numbers = list(
|
||||
1, 2, 3)
|
||||
|
||||
user.visible_message("<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 language’s conventions.
|
||||
|
||||
## Operators
|
||||
|
||||
### Spacing
|
||||
|
||||
Code readability is an important aspect of developing on a large-scale project,
|
||||
especially when it comes to open-source. As emphasized by other places in this
|
||||
document, it is important to keep the code as readable as possible. One way we
|
||||
do that is through spacing. Maintain a single space between all operators and
|
||||
operands, including during variable declarations and value assignments.
|
||||
|
||||
```dm
|
||||
// Bad
|
||||
var/example_variable=5
|
||||
|
||||
// Also Bad
|
||||
example_variable=example_variable*2
|
||||
|
||||
// Good
|
||||
var/example_variable = 5
|
||||
|
||||
// Also Good
|
||||
example_variable = example_variable * 2
|
||||
```
|
||||
|
||||
## Boolean Defines
|
||||
|
||||
Use `TRUE` and `FALSE` instead of 1 and 0 for booleans. This improves
|
||||
readability and clarity, making it clear that values represent true or false
|
||||
conditions.
|
||||
|
||||
```dm
|
||||
// Bad
|
||||
var/example = 1
|
||||
if(example)
|
||||
example_proc()
|
||||
|
||||
// Good
|
||||
var/example = TRUE
|
||||
if(example)
|
||||
example_proc()
|
||||
```
|
||||
|
||||
## File Naming and References
|
||||
|
||||
### Naming
|
||||
|
||||
When naming files, it is important to keep readability in mind. Use these
|
||||
guidelines when creating a new file.
|
||||
|
||||
- Keep names short (≤ 25 characters).
|
||||
- Do not add spaces. Use underscores to separate words.
|
||||
- Do not include special characters such as: " / \ [ ] : ; | = , < ? > & $ # ! ' { } *.
|
||||
|
||||
### References
|
||||
|
||||
When referencing files, use single quotes (') around the file name instead of
|
||||
double quotes.
|
||||
|
||||
```dm
|
||||
// Bad
|
||||
var/sound_effect = "sounds/machines/wooden_closet_open.ogg"
|
||||
|
||||
// Good
|
||||
var/sound_effect = 'sounds/machines/wooden_closet_open.ogg'
|
||||
```
|
||||
|
||||
## HTML Tag Format
|
||||
|
||||
Though uppercase or mixing cases will work, we prefer to follow the W3 standard
|
||||
for writing HTML tags. This means tags should be written in lowercase. This
|
||||
makes code more readable and it just looks better.
|
||||
|
||||
```dm
|
||||
// Bad
|
||||
<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!
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
#### 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
@@ -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
|
||||