Centralize developer documentation. (#26485)

* Documentation.

* Documentation.

* Testing reqs update

* Post feature-freeze code of conduct updates

* spell checking

* Style Guidelines

* wrap

* link up, bring headers up one level

* wrap

* fix old link

* support github admonition syntax for mkdocs

* link rules, rename to guard clauses

* ffffucking vscode

---------

Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
This commit is contained in:
warriorstar-orion
2024-09-04 16:43:33 +00:00
committed by GitHub
co-authored by Burzah
parent 1b2b8434bd
commit 1d538adc1d
108 changed files with 5646 additions and 1338 deletions
+867
View File
@@ -0,0 +1,867 @@
# Coding Requirements
Coders are expected to follow these specifications in order to make everyone's
lives easier. It'll save both your time and ours, by making sure you don't have
to make any changes and we don't have to ask you to.
## Object Oriented Code
As BYOND's Dream Maker (henceforth "DM") is an object-oriented language, code
must be object-oriented when possible in order to be more flexible when adding
content to it. If you don't know what "object-oriented" means, we highly
recommend you do some light research to grasp the basics.
## Use absolute pathing
DM will allow you nest almost any type keyword into a block, as in the following:
```dm
datum
datum1
var
varname1 = 1
varname2
static
varname3
varname4
proc
proc1()
code
proc2()
code
datum2
varname1 = 0
proc
proc3()
code
proc2()
..()
code
```
The use of this format is **not** allowed in this project, as it makes finding
definitions via full text searching next to impossible. The only exception is
the variables of an object may be nested to the object, but must not nest
further.
The previous code made compliant:
```dm
/datum/datum1
var/varname1 = 1
var/varname2
var/static/varname3
var/static/varname4
/datum/datum1/proc/proc1()
code
/datum/datum1/proc/proc2()
code
/datum/datum1/datum2
varname1 = 0
/datum/datum1/datum2/proc/proc3()
code
/datum/datum1/datum2/proc2()
..()
code
```
## Do not compare boolean values to `TRUE` or `FALSE`
Do not compare boolean values to `TRUE` or `FALSE`. For `TRUE` you should just
check if there's a value in that address. For `FALSE` you should use the `!`
operator. An exception is made to this when working with JavaScript or other
external languages. If a function/variable can contain more values beyond `null`
or 0 or `TRUE`, use numbers and defines instead of true/false comparisons.
```dm
// Bad
var/thing = pick(TRUE, FALSE)
if(thing == TRUE)
return "bleh"
var/other_thing = pick(TRUE, FALSE)
if(other_thing == FALSE)
return "meh"
// Good
var/thing = pick(TRUE, FALSE)
if(thing)
return "bleh"
var/other_thing = pick(TRUE, FALSE)
if(!other_thing)
return "meh"
```
## Use `pick(x, y, z)`, not `pick(list(x, y, z))`
`pick()` takes a fixed set of options. Wrapping them in a list is
redundant and slightly less efficient.
```dm
// Bad
var/text = pick(list("test_1", "test_2", "test_3"))
to_chat(world, text)
// Good
var/text = pick("test_1", "test_2", "test_3")
to_chat(world, text)
```
## User Interfaces
All new user interfaces in the game must be created using the TGUI framework.
Documentation can be found inside the [`tgui/docs`][tgui_docs] folder, and the
[`README.md`][tgui_readme] file. This is to ensure all ingame UIs are
snappy and responsive. An exception is made for user interfaces which are
purely for OOC actions (Such as character creation, or anything admin related)
[tgui_docs]: https://github.com/ParadiseSS13/Paradise/tree/master/tgui/docs
[tgui_readme]: https://github.com/ParadiseSS13/Paradise/blob/master/tgui/README.md
## No overriding type safety checks
The use of the [`:`][colon] "runtime search" operator to override type safety
checks is not allowed. Variables must be casted to the proper type.
[colon]: http://www.byond.com/docs/ref/#/operator/:
## Do not chain proc calls and variable access
The use of the pointer operator, `.`, should not be used to access the return
values of functions directly. This can cause unintended behavior and is
difficult to read.
```dm
//Bad
var/our_x = get_turf(thing).x
//Good
var/turf/our_turf = get_turf(thing)
var/our_x = our_turf.x
```
## Type paths must begin with a /
e.g.: `/datum/thing`, not `datum/thing`
## Datum type paths must began with "datum"
In DM, this is optional, but omitting it makes finding definitions harder. To be
specific, you can declare the path `/arbitrary`, but it will still be, in
actuality, `/datum/arbitrary`. Write your code to reflect this.
## Do not use list operators in strings
The use of list operators to augment strings is not allowed. This is roughly 10
times slower than using a list with a Join() Function.
```dm
//Bad
var/text = "text"
text += "More text"
to_chat(world, text)
//Good
var/list/text = list("text")
text += "More text"
to_chat(world, text.Join(""))
```
## Do not use text/string based type paths
It is rarely allowed to put type paths in a text format, as there are no compile
errors if the type path no longer exists. Here is an example:
```dm
//Bad
var/path_type = "/obj/item/baseball_bat"
//Good
var/path_type = /obj/item/baseball_bat
```
## Do not use `\The`
The `\The` macro doesn't actually do anything when used in the format `\The
[atom reference]`. Directly referencing an atom in an embedded string will
automatically prefix `The` or `the` to it as appropriate. As an extension, when
referencing an atom, don't use `[atom.name]`, use `[atom]`. The only exception
to this rule is when dealing with items "belonging" to a mob, in which case you
should use `[mob]'s [atom.name]` to avoid `The` ever forming.
```dm
//Bad
var/atom/A
"\The [A]"
//Good
var/atom/A
"[A]"
```
## Use the pronoun library instead of `\his` macros
We have a system in [`code/__HELPERS/pronouns.dm`][pronouns]
for addressing all forms of pronouns. This is useful in a number of ways;
- BYOND's `\his` macro can be unpredictable on what object it references. Take
this example: `"[user] waves \his [user.weapon] around, hitting \his
opponents!"`. This will end up referencing the user's gender in the first
occurrence, but what about the second? It'll actually print the gender set on
the weapon he's carrying, which is unintended - and there's no way around
this.
- It always prints the real `gender` variable of the atom it's referencing. This
can lead to exposing a mob's gender even when their face is covered, which
would normally prevent it's gender from being printed.
The way to avoid these problems is to use the pronoun system. Instead of
`"[user] waves \his arms."`, you can do `"[user] waves [user.p_their()] arms."`
```dm
//Bad
"[H] waves \his hands!"
"[user] waves \his [user.weapon] around, hitting \his opponents!"
//Good
"[H] waves [H.p_their()] hands!"
"[user] waves [H.p_their()] [user.weapon] around, hitting [H.p_their()] opponents!"`
```
[pronouns]: https://github.com/ParadiseSS13/Paradise/blob/master/code/__HELPERS/pronouns.dm
## Use `[A.UID()]` over `\ref[A]`
BYOND has a system to pass "soft references" to datums, using the format
`"\ref[datum]"` inside a string. This allows you to find the object just based
off of a text string, which is especially useful when dealing with the bridge
between BYOND code and HTML/JS in UIs. It's resolved back into an object
reference by using `locate("\ref[datum]")` when the code comes back to BYOND.
The issue with this is that `locate()` can return a unexpected datum if the
original datum has been deleted - BYOND recycles the references.
UID's are actually unique; they work off of a global counter and are not
recycled. Each datum has one assigned to it when it's created, which can be
accessed by [`[datum.UID()]`][duid]. You can use this as a snap-in replacement for
`\ref` by changing any `locate(ref)` calls in your code to `locateUID(ref)`.
Usage of this system is mandatory for any `Topic()` calls, and will produce
errors in Dream Daemon if it's not used.
```dm
//Bad
"<a href='byond://?src=\ref[src];'>Link!</a>"
//Good
"<a href='byond://?src=[UID()];'>Link!</a>"
```
[duid]: https://codedocs.paradisestation.org/datum.html#proc/UID
## Use `var/name` format when declaring variables
While DM allows other ways of declaring variables, this one should be used for
consistency.
## Tabs, not spaces
You must use tabs to indent your code, **not spaces**. You may use spaces to align
text, but you should tab to the block level first, then add the remaining
spaces.
## No hacky code
Hacky code, such as adding specific checks (ex: `istype(src, /obj/whatever)`),
is highly discouraged and only allowed when there is **_no_** other option.
(Pro-tip: 'I couldn't immediately think of a proper way so thus there must be no
other option' is not gonna cut it here! If you can't think of anything else, say
that outright and admit that you need help with it. Maintainers, PR Reviewers,
and other contributors who can help you exist for exactly that reason.)
You can avoid hacky code by using object-oriented methodologies, such as
overriding a function (called "procs" in DM) or sectioning code into functions
and then overriding them as required.
The same also applies to bugfixes - If an invalid value is being passed into a
proc from something that shouldn't have that value, don't fix it on the proc
itself, fix it at its origin! (Where feasible)
## No duplicated code
Copying code from one place to another may be suitable for small, short-time
projects, but Paradise is a long-term project and highly discourages this.
Instead you can use object orientation, or simply placing repeated code in a
function, to obey this specification easily.
## Startup/Runtime tradeoffs with lists and the "hidden" init proc
First, read the comments in [this BYOND thread](http://www.byond.com/forum/?post=2086980&page=2#comment19776775), starting where the link takes you.
There are two key points here:
1. Defining a list in the variable's definition calls a hidden proc - init. If
you have to define a list at startup, do so in `New()` (or preferably
`Initialize()`) and avoid the overhead of a second call (`init()` and then
`New()`)
2. It also consumes more memory to the point where the list is actually
required, even if the object in question may never use it!
Remember: although this tradeoff makes sense in many cases, it doesn't cover
them all. Think carefully about your addition before deciding if you need to use
it.
## Prefer `Initialize()` over `New()` for atoms
Our game controller is pretty good at handling long operations and lag, but it
can't control what happens when the map is loaded, which calls `New()` for all
atoms on the map. If you're creating a new atom, use the `Initialize()` proc to
do what you would normally do in `New()`. This cuts down on the number of proc
calls needed when the world is loaded.
While we normally encourage (and in some cases, even require) bringing out of
date code up to date when you make unrelated changes near the out of date code,
that is not the case for `New()` -> `Initialize()` conversions. These systems
are generally more dependent on parent and children procs, so unrelated random
conversions of existing things can cause bugs that take months to figure out.
## No implicit `var/`
When you declare a parameter in a proc, the `var/` is implicit. Do not include
any implicit `var/` when declaring a variable.
```dm
//Bad
/obj/item/proc1(var/mob/input1, var/input2)
code
//Good
/obj/item/proc1(mob/input1, input2)
code
```
## No magic numbers or strings
This means stuff like having a "mode" variable for an object set to "1" or "2"
with no clear indicator of what that means. Make these #defines with a name that
more clearly states what it's for. For instance:
```dm
//Bad
/datum/proc/do_the_thing(thing_to_do)
switch(thing_to_do)
if(1)
do_stuff()
if(2)
do_other_stuff()
```
There's no indication of what "1" and "2" mean! Instead, you should do something
like this:
```dm
//Good
#define DO_THE_THING_REALLY_HARD 1
#define DO_THE_THING_EFFICIENTLY 2
/datum/proc/do_the_thing(thing_to_do)
switch(thing_to_do)
if(DO_THE_THING_REALLY_HARD)
do_stuff()
if(DO_THE_THING_EFFICIENTLY)
do_other_stuff()
```
This is clearer and enhances readability of your code! Get used to doing it!
## Control statements
- All control statements comparing a variable to a number should use the formula
of `thing` `operator` `number`, not the reverse (e.g. `if(count <= 10)` not
`if(10 >= count)`)
- All control statements must be spaced as `if()`, with the brackets touching
the keyword.
- All control statements must not contain code on the same line as the
statement.
```dm
//Bad
if(x) return
//Good
if(x)
return
```
## Player Output
Due to the use of "TGchat", Paradise requires a special syntax for outputting
text messages to players. Instead of `mob << "message"`, you must use
`to_chat(mob, "message")`. Failure to do so will lead to your code not working.
## Use guard clauses
_Guard clauses_ are early returns in a proc for specific conditions. This
is preferred wrapping most of a proc's behavior in an in-block, as procs
will often check a handful of early conditions to bail out on.
This is bad:
```dm
/datum/datum1/proc/proc1()
if(thing1)
if(!thing2)
if(thing3 == 30)
do stuff
```
This is good:
```dm
/datum/datum1/proc/proc1()
if(!thing1)
return
if(thing2)
return
if(thing3 != 30)
return
do stuff
```
This prevents nesting levels from getting deeper then they need to be.
## Use `addtimer()` instead of `sleep()` or `spawn()`
If you need to call a proc after a set amount of time, use `addtimer()` instead
of `spawn()` / `sleep()` where feasible. Though more complex, this method has
greater performance. Additionally, unlike `spawn()` or `sleep()`, it can be
cancelled. For more details, see
[https://github.com/tgstation/tgstation/pull/22933](https://github.com/tgstation/tgstation/pull/22933).
Look for code examples on how to properly use it.
```dm
//Bad
/datum/datum1/proc/proc1(target)
spawn(5 SECONDS)
target.dothing(arg1, arg2, arg3)
//Good
/datum/datum1/proc/proc1(target)
addtimer(CALLBACK(target, PROC_REF(dothing), arg1, arg2, arg3), 5 SECONDS)
```
## Signals
Signals are a slightly more advanced topic, but are often useful for attaching
external behavior to objects that should be triggered when a specific event
occurs.
When defining procs that should be called by signals, you must include
`SIGNAL_HANDLER` after the proc header. This ensures that no sleeping code can
be called from within a signal handler, as that can cause problems with the
signal system.
Since callbacks can be connected to many signals with `RegisterSignal`, it can
be difficult to pin down the source that a callback is invoked from. Any new
`SIGNAL_HANDLER` should be followed by a comment listing the signals that the
proc is expected to be invoked for. If there are multiple signals to be handled,
separate them with a `+`.
```dm
/atom/movable/proc/when_moved(atom/movable/A)
SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
do_something()
/datum/component/foo/proc/on_enter(datum/source, atom/enterer)
SIGNAL_HANDLER // COMSIG_ATOM_ENTERED + COMSIG_ATOM_INITIALIZED_ON
do_something_else()
```
If your proc does have something that needs to sleep (such as a `do_after()`),
do not simply omit the `SIGNAL_HANDLER`. Instead, call the sleeping code with
`INVOKE_ASYNC` from within the signal handling function.
```dm
/atom/movable/proc/when_moved(atom/movable/A)
SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
INVOKE_ASYNC(src, PROC_REF(thing_that_sleeps), arg1)
```
## Operators
### Spacing of operators
- Operators that should be separated by spaces:
- Boolean and logic operators like `&&`, `||` `<`, `>`, `==`, etc. (But not `!`)
- Bitwise AND `&` and OR `|`.
- Argument separator operators like `,`. (and `;` when used in a forloop)
- Assignment operators like `=` or `+=` or the like.
- Math operators like `+`, `-`, `/`, or `*`.
- Operators that should NOT be separated by spaces:
- Access operators like `.` and `:`.
- Parentheses `()`.
- Logical not `!`.
### Use of operators
- Bitwise ANDs (`&`) should be written as `bitfield & bitflag` NEVER `bitflag &
bitfield`. Both are valid, but the latter is confusing and nonstandard.
- Associated lists declarations must have their key value quoted if it's a string.
```dm
//Bad
list(a = "b")
//Good
list("a" = "b")
```
### Bitflags
Bitshift operators are mandatory, opposed to directly typing out the value:
```dm
#define MACRO_ONE (1<<0)
#define MACRO_TWO (1<<1)
#define MACRO_THREE (1<<2)
```
Is accepted, whereas the following is not:
```dm
#define MACRO_ONE 1
#define MACRO_TWO 2
#define MACRO_THREE 4
```
While it may initially look intimidating, `(1<<x)` is actually very simple and,
as the name implies, shifts the bits of a given binary number over by one digit.
```dm
000100 (4, or (1<<2))
<<
001000 (8, or (1<<3))
```
Using this system makes the code more readable and less prone to error.
## Legacy Code
SS13 has a lot of legacy code that's never been updated. Here are some examples
of common legacy trends which are no longer acceptable:
- To display messages to all mobs that can view `user`, you should use
`visible_message()`.
```dm
//Bad
for(var/mob/M in viewers(user))
M.show_message("<span class='warning'>Arbitrary text</span>")
//Good
user.visible_message("<span class='warning'>Arbitrary text</span>")
```
- You should not use color macros (`\red, \blue, \green, \black`) to color text,
instead, you should use span classes. `<span class='warning'>Red text</span>`,
`<span class='notice'>Blue text</span>`.
```dm
//Bad
to_chat(user, "\red Red text \black Black text")
//Good
to_chat(user, "<span class='warning'>Red text</span>Black text")
```
- To use variables in strings, you should **never** use the `text()` operator,
use embedded expressions directly in the string.
```dm
//Bad
to_chat(user, text("[] is leaking []!", name, liquid_type))
//Good
to_chat(user, "[name] is leaking [liquid_type]!")
```
- To reference a variable/proc on the src object, you should **not** use
`src.var`/`src.proc()`. The `src.` in these cases is implied, so you should
just use `var`/`proc()`.
```dm
//Bad
var/user = src.interactor
src.fill_reserves(user)
//Good
var/user = interactor
fill_reserves(user)
```
## Develop Secure Code
- Player input must always be escaped safely. We recommend you use
`stripped_input()` in all cases where you would use input. Essentially, just
always treat input from players as inherently malicious and design with that
use case in mind.
- Calls to the database must be escaped properly; use proper parameters (values
starting with a `:`). You can then replace these with a list of parameters, and
these will be properly escaped during the query, and prevent any SQL
injection.
```dm
//Bad
var/datum/db_query/query_watch = SSdbcore.NewQuery("SELECT reason FROM [format_table_name("watch")] WHERE ckey='[target_ckey]'")
//Good
var/datum/db_query/query_watch = SSdbcore.NewQuery("SELECT reason FROM [format_table_name("watch")] WHERE ckey=:target_ckey", list(
"target_ckey" = target_ckey
)) // Note the use of parameters on the above line and :target_ckey in the query.
```
- All calls to topics must be checked for correctness. Topic href calls can be
easily faked by clients, so you should ensure that the call is valid for the
state the item is in. Do not rely on the UI code to provide only valid topic
calls, because it won't.
- Information that players could use to metagame (that is, to identify round
information and/or antagonist type via information that would not be available
to them in character) should be kept as administrator-only.
- Where you have code that can cause large-scale modification and _FUN_, make
sure you start it out locked behind one of the default admin roles - use
common sense to determine which role fits the level of damage a function could
do.
## Files
- Because runtime errors do not give the full path, try to avoid having files
with the same name across folders.
- File names should not be mixed case, or contain spaces or any character that
would require escaping in a URI.
- Files and path accessed and referenced by code above simply being `#include`d
should be strictly lowercase to avoid issues on filesystems where case
matters.
### Modular Code in a File
- Code should be modular where possible; if you are working on a new addition,
then strongly consider putting it in its own file unless it makes sense to put
it with similar ones (e.g. a new tool would go in the `tools.dm` file).
- Our codebase also has support for checking files so that they only contain one
specific typepath, including none of its subtypes. This can be done by adding
a specific header at the beginning of the file, which the CI will look for
when running. An example can be seen below. You can also run this test locally
using `/tools/ci/restrict_file_types.py`
```dm
RESTRICT_TYPE(/datum/foo)
/datum/proc/do_thing() // Error: '/datum' proc found in a file restricted to '/datum/foo'
/datum/foo
/datum/foo/do_thing()
/datum/foo/bar // Error: '/datum/foo/bar' type definition found in a file restricted to '/datum/foo'
/datum/foo/bar/do_thing() // Error: '/datum/foo/bar' proc found in a file restricted to '/datum/foo'
```
## SQL
- Do not use the shorthand SQL insert format (where no column names are
specified) because it unnecessarily breaks all queries on minor column changes
and prevents using these tables for tracking outside related info such as in a
connected site/forum.
- Use parameters for queries, as mentioned above in [Develop Secure Code](#develop-secure-code).
- Always check your queries for success with `if(!query.warn_execute())`. By
using this standard format, you can ensure the correct log messages are used.
- Always `qdel()` your queries after you are done with them. This cleans up the
results and helps things run smoother.
- All changes to the database's layout (schema) must be specified in the
database changelog in SQL, as well as reflected in the schema file.
- Any time the schema is changed, the `SQL_VERSION` defines must be incremented,
as well as the example config, with an appropriate conversion kit placed in
the `SQL/updates` folder.
- Queries must never specify the database, be it in code, or in text files in
the repo.
## Dream Maker Quirks/Tricks
Like all languages, Dream Maker has its quirks and some of them are beneficial
to us.
### In-To for-loops
`for(var/i = 1, i <= some_value, i++)` is a fairly standard way to write an
incremental for loop in most languages (especially those in the C family), but
DM's `for(var/i in 1 to some_value)` syntax is oddly faster than its
implementation of the former syntax; where possible, it's advised to use DM's
syntax. (Note, the `to` keyword is inclusive, so it automatically defaults to
replacing `<=`; if you want `<` then you should write it as `1 to
some_value-1`).
**However**, if either `some_value` or `i` changes within the body of the for
(underneath the `for(...)` header) or if you are looping over a list **and**
changing the length of the list, then you can **not** use this type of for-loop!
### `for(var/A in list)` VS `for(var/i in 1 to length(list))`
The former is faster than the latter, as shown by the following profile results:
![](./images/for_loop_timing.png)
Code used for the test:
```dm
var/list/numbers_to_use = list()
proc/initialize_shit()
for(var/i in 1 to 1000000)
numbers_to_use += rand(1,100000)
proc/old_loop_method()
for(var/i in numbers_to_use)
var/numvar = i
proc/new_loop_method()
for(var/i in 1 to numbers_to_use.len)
var/numvar = numbers_to_use[i]
```
### `istype()`-less `for` loops
A name for a differing syntax for writing for-each style loops in DM. It's **not**
DM's standard syntax, hence why this is considered a quirk. Take a look at this:
```dm
var/list/bag_of_items = list(sword1, apple, coinpouch, sword2, sword3)
var/obj/item/sword/best_sword
for(var/obj/item/sword/S in bag_of_items)
if(!best_sword || S.damage > best_sword.damage)
best_sword = S
```
The above is a simple proc for checking all swords in a container and returning
the one with the highest damage, and it uses DM's standard syntax for a for-loop
by specifying a type in the variable of the for's header that DM interprets as a
type to filter by. It performs this filter using `istype()` (or some
internal-magic similar to `istype()` - this is BYOND, after all). This is fine
in its current state for `bag_of_items`, but if `bag_of_items` contained ONLY
swords, or only SUBTYPES of swords, then the above is inefficient. For example:
```dm
var/list/bag_of_swords = list(sword1, sword2, sword3, sword4)
var/obj/item/sword/best_sword
for(var/obj/item/sword/S in bag_of_swords)
if(!best_sword || S.damage > best_sword.damage)
best_sword = S
```
The above code specifies a type for DM to filter by.
With the previous example that's perfectly fine, we only want swords, but if the
bag only contains swords? Is DM still going to try to filter because we gave it
a type to filter by? YES, and here comes the inefficiency. Wherever a list (or
other container, such as an atom (in which case you're technically accessing
their special contents list, but that's irrelevant)) contains datums of the same
datatype or subtypes of the datatype you require for your loop's body, you can
circumvent DM's filtering and automatic `istype()` checks by writing the loop as
such:
```dm
var/list/bag_of_swords = list(sword, sword, sword, sword)
var/obj/item/sword/best_sword
for(var/s in bag_of_swords)
var/obj/item/sword/S = s
if(!best_sword || S.damage > best_sword.damage)
best_sword = S
```
Of course, if the list contains data of a mixed type, then the above
optimisation is **dangerous**, as it will blindly typecast all data in the list
as the specified type, even if it isn't really that type, causing runtime errors
(aka your shit won't work if this happens).
### Dot variable
Like other languages in the C family, DM has a `.` or "Dot" operator, used for
accessing variables/members/functions of an object instance. eg:
```dm
var/mob/living/carbon/human/H = YOU_THE_READER
H.gib()
```
However, DM also has a dot _variable_, accessed just as `.` on its own,
defaulting to a value of null. Now, what's special about the dot operator is
that it is automatically returned (as in the `return` statement) at the end of a
proc, provided the proc does not already manually return (`return count` for
example.) Why is this special?
With `.` being everpresent in every proc, can we use it as a temporary variable?
Of course we can! However, the `.` operator cannot replace a typecasted variable
- it can hold data any other var in DM can, it just can't be accessed as one,
although the `.` operator is compatible with a few operators that look weird but
work perfectly fine, such as: `.++` for incrementing `.'s` value, or `.[1]` for
accessing the first element of `.`, provided that it's a list.
### Globals versus static
DM has a var keyword, called `global`. This var keyword is for vars inside of
types. For instance:
```dm
/mob
var/global/thing = TRUE
```
This does **not** mean that you can access it everywhere like a global var. Instead, it means that that var will only exist once for all instances of its type, in this case that var will only exist once for all mobs - it's shared across everything in its type. (Much more like the keyword `static` in other languages like PHP/C++/C#/Java)
Isn't that confusing?
There is also an undocumented keyword called `static` that has the same
behaviour as global but more correctly describes BYOND's behaviour. Therefore,
we always use static instead of global where we need it, as it reduces suprise
when reading BYOND code.
### Global Vars
All new global vars must use the defines in
[`code/__DEFINES/_globals.dm`][globals]. Basic usage is as follows:
To declare a global var:
```dm
GLOBAL_VAR(my_global_here)
```
To access it:
```dm
GLOB.my_global_here = X
```
There are a few other defines that do other things. `GLOBAL_REAL` shouldn't be
used unless you know exactly what you're doing. `GLOBAL_VAR_INIT` allows you to
set an initial value on the var, like `GLOBAL_VAR_INIT(number_one, 1)`.
`GLOBAL_LIST_INIT` allows you to define a list global var with an initial value,
etc.
[globals]: https://github.com/ParadiseSS13/Paradise/blob/master/code/__DEFINES/_globals.dm
+304
View File
@@ -0,0 +1,304 @@
# Guide to Debugging
## Intro
Got a bug and you're unable to find it by just looking at your code? Try
debugging! This guide will teach you the basics of debugging, how to read the
values and some tips and tricks. It will be written as a chronological story.
Where the chapters explain the next part of the debugging process.
Be sure to look at [Getting Started](../contributing/getting_started.md) if
you're new and need help with setting up your repo. Do also remember that all
below here is how I do it. There are many ways but I find that this works for
me.
### What Is Debugging
> "Debugging is the process of detecting and removing of existing and potential
errors (also called as "bugs") in a software code that can cause it to behave
unexpectedly or crash."
[(source)](https://economictimes.indiatimes.com/definition/debugging)
As you can see from this quote. It is a very broad term. This guide will use a
code debugger to step through your code and look at what is happening.
## How To Debug
We will be using [#15958](https://github.com/ParadiseSS13/Paradise/issues/15958) as an example issue.
### Finding The Issue
First of all, you need to understand what is happening functionally. This
usually gives you hints as to where it goes wrong.
Looking at the GitHub issue we can see that the author (luckily) wrote a clear
reproduction. Without one we'd only be guessing as to where it goes wrong
exactly. Here a tripwire mine activates when it is in a container such as a
closed locker or a bag.
This gives us the hint that the trigger mechanism does not check if the object
is directly on a turf. Using this hint we go look for the proc which causes the
trigger to happen. Using [my previous guide's advice][contrib]
we quickly find `/obj/item/assembly/infra`.
![image](./images/debug_infra_search_results.png)
In the current file of `/obj/item/assembly/infra`, "infrared.dm", we can see a
lot of different procs. We're only really interested in the proc which triggers
the bomb.
![image](./images/debug_trigger_proc.png)
We see that the proc `toggle_secure` starts and stops processing of the object.
This gives us a hint as to where the triggering happens. Looking at
`/obj/item/assembly/infra/process` we see that it creates beams when it is
active. Those beams are functionally used to trigger the bomb itself.
![image](./images/debug_infra_process.png)
Looking at the `/obj/effect/beam/i_beam` object we see that this is indeed the
case.
![image](./images/debug_ibeam_hit.png)
`/obj/effect/beam/i_beam/proc/hit()` is defined here which calls `trigger_beam`
on master. `hit()` in term is called whenever something `Bumped` or `Crossed`
the beam. I found this by right clicking on the `hit` proc and choosing `Find
All References`
![image](./images/debug_ibeam_findreferences.png)
![image](./images/debug_ibeam_findresults.png)
So now we know what is causing the triggering of the bomb. We know that beams
are sent when the bomb is active. And we functionally know that these beams are
also sent when the bomb is hidden in a locker or bag.
[contrib]: ./quickstart.md
### Breakpoints
Now we know what is happening we can start debugging. I have a suspicion already
of what is the cause of the issue. Namely that the `infrared emitter` does not
check the `loc` of the actual bomb in the `/obj/item/assembly/infra/process()`
proc.
To confirm this I will place a breakpoint just when the `process` proc begins.
You do this by clicking just left of the line number where you want to put the
breakpoint.
![image](./images/debug_set_breakpoint.png)
The red dot there is a breakpoint that is set. Clicking it again removes it.
After doing this we will follow the reproduction steps. Once the game hits your
breakpoint it will freeze your game and your VS Code instance should pop up to
the front. If not just open VS Code.
When testing this I noticed that the breakpoint got hit multiple times before I
could do my reproduction. If that is the case then you have multiple options for
combating this annoyance. Either you disable the breakpoint and re-enable it
when you are ready to test. This is not a great way of doing it for this case
but in some cases, this is enough. Or you move the breakpoint to a better
location. I choose this one and I've moved it a bit lower.
![image](./images/debug_lower_breakpoint.png)
I moved it past the `if(!on)` check which eliminates all trip wires which are
not turned on. (Quick note. This is really bad code. They should not be
processing when they are not turned on)
I spawned a premade grenade instead of making my own grenade here. Saves me
quite some time and annoyance in trying to look up how to do this again.
Remember the search results when looking for the trip mine? Yeah, use one of
those.
### In Debug Mode
Once I turn on the bomb I notice that VS Code indeed pops up to the front. The
game is now paused till you say it can continue.
![image](./images/debug_game_paused.png)
The breakpoint line is now highlighted. The highlight shows where the code is
currently. It is about to do the `if(!secured)` check. Let's make the code
execute that one step by stepping over the line. F10 as a shortcut or you can
press the "Step Over" button in your active debugger window.
![image](./images/debug_stepthrough.png)
Now the code is executed and the highlight moved to the next line. Step over is
handy to quickly move over your code. It will jump over any proc call. Step Into
(F11) is for when you want to actually step into the proc call. This will be
explained later when it is needed.
If you step over a bunch of times you will see that it will go and create the
`i_beam`.
![image](./images/debug_stepthrough_create.png)
Even though I am currently holding the bomb in my hands (same situation as when
it is in a bag/locker code-wise). Why is this the case? In the code, you can see
that a beam is created when the bomb is `on`, `secured` and `first` and `last`
are both null. These all have nothing to do with our issue. But the last check
checks if `T` is not null. `T` is defined earlier in the proc as
`get_turf(src)`.
![image](./images/debug_stepthrough_getturf.png)
In other words. `T` is the turf below my characters feet. And of course, this
one does exist in this case. What is missing here is a check to see if the
actual bomb is on a `turf`. How do we even check this?
Time to go look for a reference to the actual bomb as `/obj/item/assembly/infra`
is just the mechanism used by the bomb. `/obj/item/assembly/infra` itself is a
subtype of `/obj/item/assembly` which is the base type that is used for bomb
mechanisms. So we best look at the definition of `/obj/item/assembly`. We do
this by `Ctrl`-clicking on the `assembly` part of `/obj/item/assembly/infra`.
![image](./images/debug_assembly_def.png)
Here we see the definition. We can see that `/obj/item/assembly` has a variable
called `holder` which is of type `/obj/item/assembly_holder`. This is most
likely the thing we want.
Now to check if this is correct. Go to your debug window and open `Arguments`
and then `src`. `src` is of course the object we currently are. Which is the
`/obj/item/assembly/infra`. Once it is open you can see a **LOT** of variables.
![image](./images/debug_arg_vars.png)
We are not interested in most of them and instead, we want to find `holder`
![image](./images/debug_holder_var.png)
Yep, this is the one we want. Now how do we check if this object is directly on
a turf? How do we even check its location? The answer is `loc`. `loc` contains
the location of the object. Here I found out that the `loc` of the
`assembly_holder` wasn't actually my character but instead it was the grenade.
Good thing we checked further before we started coding right?
![image](./images/debug_mine_location.png)
### Finding/Making The Right Variable To Use
Now it becomes a bit odd. Chemistry bomb code is fairly ... bad. But we can make
this work. First, we go look a bit more into the code to find the proper
variable to use. We *can* use `holder.loc.loc` but that says very little about
what it actually means. It would cause even more headaches for people working
with the code in the future. Instead, we will help our future co-developers a
bit and look into improving the existing code. Later on, we also see that this
is not the correct way of fixing it fully.
Let's take a look at the `assembly` code in `assembly.dm`. Let's see how the
assembly determines what its grenade is. When looking around the file I found
the proc `/obj/item/assembly/proc/pulse(radio = FALSE)`. This one seems
promising.
![image](./images/debug_radio_pulse.png)
Here we can see that either the holder is used or if `loc` is a grenade it will
be primed using `prime`. As you can see a previous coder even stated that this
is a hack. This however does give me an idea of how to handle this and the edge
cases that exist. Namely the case where a grenade owns the trip laser as a
mechanism.
The idea I had in mind is to create a proc which returns the physical outer
object. The payload, grenade or such. Where does this proc go? Well in the
assembly file since it will be usable for other code as well. I just put it at
the bottom of the file since nowhere else seemed to fit better. I quickly found
that I needed some more info for this. What if the `holder` was not attached
yet? Or it is remotely attached to a bomb? For this I made a proc for the
`/obj/item/assembly_holder` object which will return the actual outermost
object.
![image](./images/debug_get_outer_object.png)
I found out that `master` is the bomb linked by the line at the top of the
image. This all allows me to complete my other proc.
![image](./images/debug_complete_other_proc.png)
Now we have a proper method to use to find the outermost object.
### Applying The Fix And Testing It
Now we can go and fix the issue at hand. We want to check if the outermost
object its `loc` is a turf. If not it should not fire new lasers and kill the
old ones.
We already have the turf that the `/obj/item/assembly/infra` is located on. Now
we just have to check if that turf is the same turf as our outermost object.
![image](./images/debug_check_turf.png)
Now, this should work. But you of course have to test it! Build it and run the
game. Run it without a breakpoint set first to see if it works functionally.
And it seems to work! Now let's trip it and see if it actually keeps working.
### Runtimes And Stacktrace Traveling
And the game froze. VS Code began blinking. What is happening? The game ran into
a runtime exception
![image](./images/debug_runtime.png)
Now let's see if this is actually our fault or not. Since we did not touch any
timers. To check this go to the debug window and open the call stack.
![image](./images/debug_call_stack.png)
Here you can see **ALL** the current "threads" of the game. DM itself is not a
multithreaded language but it can have multiple threads. I won't go into detail
on that here for simplicity sake. The top item is the one you are currently on.
Just click on it to open it.
![image](./images/debug_top_call_stack.png)
Here we can see the entire stack trace of our current thread. The stack trace is
the entire path the code took thus far. At the top is the last called proc and
at the bottom is the origin of the call. Clicking on each stack will jump you to
the location in the code. The message said that `addtimer` was called on a
`QDELETED` item. This means that the item it is made for is already deleted.
Lets click on `/obj/item/assembly/infra/trigger_beam()` in the stack trace to go
to that call location.
![image](./images/debug_addtimer.png)
Here we see where it went wrong. It seems that `addtimer(CALLBACK(src,
.proc/process_cooldown), 10)` is called even though the `src` is already
deleted. How can this happen? To save us all some time. `pulse` is the proc
which triggers the bomb. In our case our bomb exploded, destroying itself.
Now... is this our fault? No. But we can fix it nonetheless.
![image](./images/debug_pulse_false.png)
This code change will ensure the message is sent and that the runtime stops from
happening.
### Stepping Into VS Stepping Over
Now back to the testing. Once build again start up the game again and try it
this time using a raw `/obj/item/assembly/infra`. As you can see it works only
when you hold it. But not when it is on the floor itself. Seems we made a
mistake! Place a breakpoint again in the `/obj/item/assembly/infra/process()`
proc since there something goes wrong.
![image](./images/debug_outer_object_breakpoint.png)
Now we want to step into the current line. This means that we will go into
`get_outer_object`. Press either F11 or the "Step Into" button.
![image](./images/debug_step_into_button.png)
This will get us to the proc itself. Now step over till you see it return `loc`.
`loc` here is the turf. Which is not the outermost object. Seems we need to do
another check there. This almost certainly also goes for
`/obj/item/assembly_holder/proc/get_outer_object()` as we used mostly the same
logic there.
![image](./images/debug_common_logic.png)
## Outro
Now let's test again. And it seems that there are more issues! The
assembly_holder still shoots a laser if you hold it or put it in a bag. Same for
the infra itself. Now I know the fix already but where is the fun in that.
I'm leaving the last solution (and honour of making the PR that solves the
issue) open for you! If you feel like testing yourself then please pick up this
issue and fix it the way you think would work. I'd love to see your method!
Please also let me know what you think of this format of doing a guide. This one
was more a look into how I do it step by step compared to a more structured
guide.
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+519
View File
@@ -0,0 +1,519 @@
# Code Contribution Quickstart
by *Farie82*
## Intro
Glad you're reading this and hopefully this guide will help you start
contributing to this codebase! First a word of wisdom. Start small with your
first set of PRs even if you are already experienced with developing for other
languages or codebases. Every codebase has its own quirks and standards which
you will discover by doing and receiving feedback on your work. This guide will
help you set up your git and make your first PR. It will also include some tips
on how to (in my opinion) best handle the codebase. This guide will assume that
you have at least (very) minor knowledge of how programming works. Knowing what
a `string` is and how `if` statements work for example. The guide will also
assume that you will use VS Code which the [Getting
Started](../contributing/getting_started.md) guide helps you set up.
Be sure to also take a look at the [contributing page](../CONTRIBUTING.md) so
you know what the coding standards are here.
I've also made a [debugging tutorial](./debugging.md) which will help you find
the cause of bugs and how to fix them.
## Quick DM tutorial
For your first PR you won't need an in-depth knowledge of how to code in DM, but
here are some of the basics. Feel free to skip these and come back to this once
you feel like you are missing some info.
The [DM reference guide](http://www.byond.com/docs/ref/) is also great when you
want to look up how a proc or such works. VS Code does have a build-in reference
guide for you to use as well. Just `Ctrl`-click on any BYOND proc or variable to
see the reference on it.
### Objects and Inheritance
An object is defined the following way:
```dm
/obj/item/multitool
```
Here we can see a `multitool` being defined. A `multitool` is an `item` which is
an `obj`. This is how the class inheritance works for DM. A real-life example is
that a dog is an animal and a cat is an animal. But a dog is not a cat. In DM it
could look something like this:
```dm
/mob/animal/cat
name = "Cat"
/mob/animal/dog
name = "Dog"
```
Where `mob` is a being in DM. Thus something that "lives" and can do things.
### Procs
The way DM groups a set of instructions is as follows. It uses a *proc* or in
other languages also called a method or function.
```dm
/obj/item/pen/multi/proc/select_colour(mob/user)
var/newcolour = input(user, "Which colour would you like to use?", name, colour) as null|anything in colour_choices
if(newcolour)
colour = newcolour
playsound(loc, 'sound/effects/pop.ogg', 50, 1)
update_icon()
```
`/obj/item/pen/multi/proc/select_colour` here is the proc definition. Meaning
this is the first instance of this proc. For this, you need to add `proc/` in
front of the method name (`select_colour` in this case). `mob/user` is here a
parameter given to the proc. The name of the parameter is `user` and its type is
`mob`.
As with other languages you can also override the behaviour of a proc.
```dm
/obj/item/pen/multi/attack_self(mob/living/user)
select_colour(user)
```
Here the proc `attack_self` is overridden with new behaviour. It will call
`select_colour` with as a parameter the given `user`.
#### Overriding Procs
When overriding a proc you can also call the parent's implementation. This is
especially handy when you want to extend the existing behaviour with new
behaviour.
```dm
/obj/item/pen/multi/Initialize(mapload)
. = ..()
update_icon()
```
Here `Initialize` is overridden with `mapload` as a parameter. `..()` means call
the parent implementation of this proc with the parameters given to this
version. So `mapload` will be passed through. `. = ..()` means assign the value
that the parent's version returns as our default return value. `.` is the
default return value in DM. So if you don't return an explicit value at the end
of the proc then `.` will be returned.
```dm
/proc/test()
. = "Yes"
return "No"
```
This will return `"No"` since you explicitly state to return `"No"`.
Small tip. You can also `Ctrl`-click on `..()` to go to the parent's definition.
### Putting values easily in strings
Other languages use something like `string.format("{0} says {1}", mob_name,
say_text)`. But DM has something nifty for that. The same result can be achieved
in DM using the following:
```dm
"[mob_name] says [say_text]"
```
`[...]` will run the code and return the outcome inside the `[]`. In the case
above it will just return the value of the variables but you can also use logic
here.
```dm
var/val = 1
world.log << "val is [val]. val plus 10 is: [val + 10]"
```
Which will produce `"val is 1. val plus 10 is: 11"`
### Scoping
If you come from another language then you might think. "Hey, where are the
{}'s?!". Well, we do not use those (usually). Instead scoping is done by
whitespace. Tabs in our case. One tab means one scope deeper.
```dm
/mob
name = "Thing"
/mob/proc/test()
world.log << name // We can access name here since we are in the mob
if(name == "Thing")
var/value = 10
world.log << "[value]" // We can also access value here since it is in the same scope or higher as us.
world.log << "Will only happen if name is Thing"
else
world.log << "Will only happen if name is not Thing"
world.log << "Will always happen even if name is not Thing"
world.log << "[value]" // This will produce an error since value is not defined in our current scope or higher
```
In VS Code you can make your life easier by turning on the rendering of
whitespace. Go to the settings and search for whitespace.
![image](./images/quickstart_vsc_whitespace_setting.png)
I have set it up so that I can only see the boundary whitespace. Meaning that I
visually see spaces and tabs on the outmost left and right side of a line. Very
handy in spotting indentation errors.
### Deleting stuff
DM has a build-in proc called `del`. **DO NOT USE THIS**. `del` is very slow and
gives us no control over properly destroying the object. Instead, most/all SS13
codebases have made their own version for this. `qdel` which will queue a delete
for a given item. You should always call `qdel` when deleting an object. This
will not only be better performance-wise but it will also ensure that other
objects get notified about its deletion if needed.
### Coding Standards
**Before you start coding it is best to read our** [contributing page](../CONTRIBUTING.md).
It contains all of the coding standards and some tips and tricks on how to write
good and safe code.
### Terminology
We will be using some terminology moving forward you should be comfortable with:
- [PR](../references/glossary.md#pull-request), an abbreviation for pull
request. This is the thing that will get your changes into the actual game. In
short, it will say you request certain changes to be approved and merged into
the master branch. Which is then used to run the actual game.
- [VS Code](../references/glossary.md#vsc), short for Visual Studio Code. The
place where you do all your magic. It is both a text editor with a lot of
helpful tools and a place where you can run and debug your code.
- Scoping; defining what code belongs to what. You don't want to make everything
public to the whole codebase so you use scoping. See the explanation above for
more info.
- Feature branch; the branch where your new feature or fix is located on. Git
works with branches. Each branch containing a different version of the
codebase. When making a PR git will look at the differences between your
branch and the master branch.
## Setup
Code contributions require setting up a development environment. If you haven't
done that already, follow the guide at [Getting Started](../contributing/getting_started.md)
first.
## Your First PR
Once you've completed the setup you can continue with making an actual PR.
I'd suggest keeping it small since setting up all of the git stuff was already a
task of its own. My suggestion would be to look at issues with the [Good First
Issue][gfi] label. These usually are considered to be easy to solve. Usually,
they will also contain some comments containing hints on how to solve them. When
picking one be sure that you do not pick an issue that already has an open PR
attached to it like in the picture below.
![image](./images/quickstart_good_first_issue.png)
You *can* make a PR that solves that issue. But it would be a waste of time
since somebody else already solved it before you but their PR is still awaiting
approval.
If there are no suitable good first issues then you can look through the issue
list yourself to find some. Good ones include typos or small logic errors. If
you know of any issues that are not yet listed in the issues list then those are
also fine candidates.
Alternatively, you can implement a small new feature or change. Good examples
include:
- More or changed flavour text to an item/ability etc.
- Adding (existing) sound effects to abilities/actions.
- Adding administrative logging where it is missing. For example, a martial arts
combo not being logged.
- A new set of clothing or a simple item.
There of course are many more options that are not included in this list.
[gfi]: https://github.com/ParadiseSS13/Paradise/labels/Good%20First%20Issue
### Finding The Relevant Code
The first thing you will need to do is to find the relevant code once you
figured out what you want to add/change. This is no exact science and requires
some creative thinking but I will list a few methods I use myself when finding
code.
For all of these, you will need to have VS code open and use the search
functionality. I tend to only look for things in dm files. Which are the code
files.
![image](./images/quickstart_search_dm.png)
#### Finding Existing Items
If you're looking for an existing item then it might be easiest to look for the
name of the item. Let's take a multitool as an example here.
When looking for the term `multitool` you will tend to find a lot of results.
305 results on my current version of the game in fact.
![image](./images/quickstart_search_multitool.png)
Alternatively, you can search for `"multitool"` (the string with the value
`multitool`) and find a lot fewer results. For demonstration purposes, I will
exclude the `""` here. This will give you the following match:
![image](./images/quickstart_search_multitool_quotes.png)
This might seem like a lot (and it is) but you don't have to go through them all
to find the item itself. Using some deduction we can find the result we need. Or
find it via another result we found. Here we can see that there are some matches
with `obj/item/multitool` for example:
![image](./images/quickstart_search_toolhelpers.png)
We know that we are looking for a multitool and that a multitool is an item so
it looks like this is what we want to find. When hovering over the `multitool`
part of `obj/item/multitool` and holding the `Ctrl` key you will see the
definition of the object.
![image](./images/quickstart_ctrl_key.png)
Perfect! This is the one we need. How how do we get to that definition? Simple
you click on `multitool` when holding `Ctrl`. This will send you to the definition
of the object.
Most of the times the file containing the definition will also include the
looked for proc or value you want to change.
#### Finding Behaviour
This is a very wide concept and thus hard to exactly find.
For this, we will use the above method and use keywords explaining the behaviour
you want to search. For example a mob gibbing. Simply looking for `gib` here
will find us too many results. About 1073 in my case. Instead, we will try to
look for a proc named gib. `/gib(` will be used as our search criteria here.
![image](./images/quickstart_gib_search.png)
Et voila, just 15 results.
Say we want to delete the pet collar of animals such as Ian when he is gibbed.
Here we need to find something stating that the `gib` belongs to an animal.
![image](./images/quickstart_animal_gib.png)
`/mob/living/simple_animal/gib()` is what we are looking for here. Ian is an
animal. `simple_animal` in code.
This will find us the following code (on my current branch):
```dm
/mob/living/simple_animal/gib()
if(icon_gib)
flick(icon_gib, src)
if(butcher_results)
var/atom/Tsec = drop_location()
for(var/path in butcher_results)
for(var/i in 1 to butcher_results[path])
new path(Tsec)
if(pcollar)
pcollar.forceMove(drop_location())
pcollar = null
..()
```
The behaviour we're looking for here has to do with the `pcollar` code there. It
will currently move the attached pet collar (if any) to the drop location of the
animal when they are gibbed.
#### Finding A Suitable Place To Add A New Item
When adding a new item you want to ensure that it is placed in a logical file or
that you make a new file in a logical directory. I find that it is best to find
other similar items and see how they are defined. For example a special jumpsuit
without armour values. Here we first go look for the existing non-job-related
jumpsuits such as the `"mailman's jumpsuit"`. Say we don't know the exact name
of that jumpsuit but we do know that it is for a mailman.
Our best bet will be to look for the term `mailman` and see what pops up. This
is a rather uncommon term so it should give only a few results.
![image](./images/quickstart_mailman_search.png)
Perfect. Even the item definition has the name mailman in it.
We already see from the search results that the item is defined in the
`miscellaneous.dm` file. Navigating to it will show us the directory it is in.
![image](./images/quickstart_folder_navigate.png)
As you can see most clothing items are defined in this `clothing` directory.
Depending on your to add the item you can pick one of those files and see if it
would fit in there. Feel free to ask for advice from others if you are unsure.
### Solving The Actual Issue
Now comes the **Fun** part. How to achieve what you want to achieve? The answer
is. "That depends" Fun, isn't it? Every problem has its own way of solving it. I
will list some of the more common solutions to a problem down here. This list
will of course not be complete.
A great hotkey for building your code quick is `Ctrl` + `Shift` + `B`. Then press enter to
select to build via Byond. This will start building your code in the console at
the bottom of your screen (by default). It will also show any errors in the
build process.
![image](./images/quickstart_build_errors.png)
Here I have "accidentally" placed some text where it should not belong. Going to
the "Problems" tab and clicking on the error will bring you to where it goes
wrong.
![image](./images/quickstart_problems_tab.png)
This error does not tell much on its own (Byond is not great at telling you what
goes wrong sometimes) but going to the location shows the problem quite easily.
![image](./images/quickstart_yellow_underline.png)
More cases might be added later.
#### Typo Or Grammar
The easiest of them all if you can properly speak English. Say the multitool
description text is: `"Used for pusling wires to test which to cut. Not
recommended by doctors."` Then you can easily fix the typo ("pusling" to
"pulsing") by just changing the value of the string to the correct spelling.
#### Wrong Logic
This one really depends on the context. But let us take the following example.
You cannot link machinery using a multitool. Something it should do.
```dm
/obj/item/multitool/proc/set_multitool_buffer(mob/user, obj/machinery/M)
if(ismachinery(M))
to_chat(user, "<span class='warning'>That's not a machine!</span>")
return
```
Here a simple mistake is made. A `!` is forgotten. `!` will negate the outcome
of any given value. In this case, a check to see if `M` is indeed a piece of
machinery. This seems dumb to forget or do wrong but it can happen when a large
PR gets made and is changed often. Testing every case is difficult and cases can
slip under the radar.
#### Adding A New Item
You can start defining the new item once you found the proper file the item
should belong to. Depending on the item you will have to write different code
(duh). We will take the new jumpsuit as an example again and will put it in the
`miscellaneous` file.
When defining a new jumpsuit you can easily copy an existing one and change the
definition values. We will take the mailman outfit as a template.
```dm
/obj/item/clothing/under/rank/mailman
name = "mailman's jumpsuit"
desc = "<i>'Special delivery!'</i>"
icon_state = "mailman"
item_state = "b_suit"
item_color = "mailman"
```
As seen here a jumpsuit has multiple values you can define. The `name` is pretty
straight forward. `desc` is the description of the item. `icon_state` is the
name the sprite has in the DMI file. `item_state` is the name of the sprite of
the suit while held in your hands has in the DMI file. `item_color` is the name
of the sprite in the `icons/mob/uniform.dmi` file. This value will indicate what
sprite will be used when a person wears this jumpsuit. (I know `item_color` is a
weird name for this)
We of course also have to change the path of the newly created object. We'll
name it `/obj/item/clothing/under/rank/tutorial`. This alone will make it so
that you can spawn the item using admin powers. It will not automagically appear
in vendors or such.
### Testing Your Code
Once you are done coding you can start testing your code, assuming your code compiles of course.
To do this simply press `F5` on your keyboard. This will by default build your
code and start the game with a debugger attached. This allows you to debug the
code in more detail.
Later I will include a more detailed testing plan in this guide.
### Making The PR
Once you are done with your changes you can make a new PR.
New PRs must be created on _branches_. Branches are copies of the `master`
branch that constitutes the server codebase. Making a separate branch for each
PR ensures your `master` branch remains clean and can pull in changes from
upstream easily.
![image](./images/quickstart_checkout_to.png)
Select "Create new branch" and then give your new branch a name in the top text
bar in VS code. Press enter once you are done and you will have created a new
branch. Your saved changes will be carried over to the new branch.
![image](./images/quickstart_new_branch_name.png)
You are ready to commit the changes once you are on your feature branch and when
the code is done and tested. Simply write a (short) useful commit message
explaining what you've done. For example when implementing the pet collar
gibbing example
![image](./images/quickstart_commit_message.png)
Here you can also see the changes you have made. Clicking on a file will show
you the difference between how it was before and how it is now. When you are
happy with the existing changes you can press the commit button. The checkmark.
By default it will commit all the changes there are unless you staged changes.
Then it will only commit the staged changes. Handy when you want to commit some
code while you have some experimental code still there.
Once it is committed you will have to also publish the branch.
![image](./images/quickstart_push_branch.png)
When pressing push it will ask you if you want to publish the branch since there
is no known remote version of this yet (not on Github). Say yes to that and
select "origin" from the list of available remotes. Now your code is safely
pushed to Github.
Now go to the Github page of this codebase and you will see the following:
![image](./images/quickstart_recent_pushes.png)
Click on the green button and Github will auto-create a PR template for you for
the branch you just pushed. Be sure to fill in the template shown to you. It is
quite straight forward but it is important to note down the thing you changed in
enough detail. If you fixed an issue then you can write the following `fixes #12345`
where 12345 is the number of the issue. Put this line of text in the
*What Does This PR Do* part of the PR.
You can submit it once you are happy with how the PR looks. After this, your PR
will be made public and visible to others to review. In due time a maintainer
will look at your PR and will merge it if it is deemed an addition to the
codebase.
## Tips And Tricks
Here I will list some of my tips and tricks that can be useful for you when
developing our codebase.
- Sounds like a simple and logical one. But always feel free to ask others for
help/advice. This project is an open-source project run by a lot of passionate
people who want to improve the codebase together. Asking for help/advice will
not only help you get your code running but will also show you are interested
and will help you improve your skills.
- Learn the VS Code shortcuts. This really saves you a lot of time. `Ctrl` + `Shift` + `B`
will build your project. `F5` will run it in debug mode. `Ctrl` + `Shift` + `F`
will global search. `Ctrl` + `P` will find definitions of things (super handy).
- Use find references when right-clicking on a variable or proc. This will list
all the uses of this var/proc so you can see what it is actually used for or
where you need to change things as well etc.
- You can remove the build command from F5. This saves you some time when you
develop. Be sure to manually build your code though! This can be done by
removing the `preLaunchTask` line in the debug config:
![image](./images/quickstart_vsc_tasks_config.png)
+345
View File
@@ -0,0 +1,345 @@
# Style Guidelines
These guidelines are designed to maintain readability and establish a standard
for future contributions. By following these guidelines, we can reduce the
overhead during the review process and pave the way for future content, fixes,
and more.
## Variables
Variable conventions and naming are an important part of the development
process. We have a few rules for variable naming, some dictated by BYOND itself.
While naming variables can be tough, we ask that variable names are descriptive.
This helps contributors of all different levels understand the code better.
These guidelines only apply to DM, as TGUI uses a different convention. Avoid
using single-letter variables. Variable naming is to follow American English
spelling of words. This means that variables using British English will be
rejected. This is to maintain consistency with BYOND.
Variables written in DM require the use of snake_case, which means words will be
spaced by an underscore while remaining lowercase.
```dm
// An example of a variable written in snake_case
var/example_variable
```
## Strings and Messages
### Strings
When it comes to strings, they should be enclosed in double quotations. Like the
naming convention for variables, American English spelling is to be used.
```dm
var/example_string = "An example of a properly formatted string!"
```
If a string is too long, break it into smaller parts for better readability.
This is especially useful for long descriptions or sentences, making the text
easier to read and understand.
```dm
var/example_long_string = "This is a longer than average string \
and how it should be formatted. This \
is the method we prefer!"
```
Variables may be incorporated within strings to dynamically convey their values.
This practice is beneficial when the variable's value may change, enhancing
flexibility and maintainability. Avoid hardcoding values directly into strings,
as it is considered poor practice and can lead to less adaptable code.
```dm
// Bad
var/bad_example_string = "There are 20 items in the box."
// Good
var/item_count = 20
var/good_example_string = "There are [item_count] items in the box."
```
### Messages
Messages are anything that is sent to the chat window. This can include system
messages, messages to the user, as well as messages between users.
#### Sending to chat
Though there are multiple ways to send a message to the chat window, only
certain methods will be accepted. Avoid using `<<` when sending information to
the chat window. You can check out other examples throughout the codebase to see
how messages are typically handled.
```dm
// Bad
world << "Hello World!"
// Good
to_chat(world, "Hello World!")
// Also Good
user.visible_message(
"<span class='notice'>[user] writes Hello World!</span>",
"<span class='notice'>You write Hello World!.</span>",
"<span class='notice'>You hear someone writing Hello World!.</span>"
)
```
#### Common Classes
- ``'notice'``: used to convey anything the player should be aware of, including
actions, successes, and other pertinent information that is non-threatening.
This also includes information directly unrelated to gameplay.
- ``'warning'``: used for failures, errors, and warnings
- ``'danger'``: danger occurring around the player or to other players, damage
to things around the player
- ``'userdanger'``: used to convey to the player that they are being attacked or
damaged
These can be set using in-line span tags.
```dm
proc/my_example_proc
to_chat(user, "<span class='notice'>Message with the notice style.</span>")
```
You are not limited to the styles listed above. It is important, however, to
evaluate and choose the right style accordingly. You can find additional styles
located within the chat style sheets.
## Comments
Comments are essential for documenting your code. They help others understand
what the code does by explaining its behavior and providing useful details. Use
comments where needed, even if the code seems clear. Proper commenting keeps the
codebase organized and provides valuable context for development.
### Single-Line Comments
Single-line comments are used for brief explanations or notes about the code.
They provide quick, straightforward context to help clarify the codes purpose
or functionality.
```dm
// This is a single-line comment
```
### Multi-Line Comments
Used for longer explanations or comments spanning multiple lines. Good for
documenting parameters of procs.
```dm
/*
* This is a multi-line comment.
* It spans multiple lines and provides detailed explanations.
*/
```
### Autodoc Comments
[Autodoc][] is used for documenting variables, procs, and other elements that
require additional clarification. This is a useful tool, as it allows a coder to
see additional information about a variable or proc without having to navigate
to its declaration. To apply properly, an Autodoc comment should be used
**BEFORE** the actual declaration of a variable or proc.
```dm
/// This is an Autodoc example
var/example_variable = TRUE
```
[Autodoc]: ../references/autodoc.md
### Define Comments
When documenting single-line macros such as constants, use the "enclosing
comment format", `//!`. This prevents issues with macro expansion:
```dm
#define BLUE_KEY 90 //! The access code for the blue key.
```
These constant names can then be referred to in other Autodoc comments by
enclosing their names in brackets:
```dm
/// This door only opens if your access is [BLUE_KEY].
/obj/door/blue
...
```
### Mark Comments
Used to delineate distinct sections within a file when necessary. It should only
be used for that purpose. Avoid using it for items, procs, or datums.
```dm
// MARK: [Section Name]
```
### Commented Out Code
Commented out code is generally not permitted within the code base unless it is
used for the purpose of debugging. Code that is commented out during a
contribution should be removed prior to creating a pull request. If you are
unsure whether or not something should be left commented out, please contact a
development team member.
## Multi-Line Procs and List Formatting
When calling procs with very long arguments (such as list or proc definitions
with multiple or especially dense arguments), it may sometimes be preferable to
spread them out across multiple lines for clarity. For some more text-heavy
procs where readability of the arguments is especially important (such as
visible_message), you're asked to always multi-line them if you're providing
multiple arguments.
For lists that may be subject to frequent code churn, we suggest adding a
trailing comma as well, as it prevents the line from needing to be changed
unnecessarily down the line.
```dm
// Bad
var/list/numbers = list(
1, 2, 3)
user.visible_message("<span class='notice'>[user] writes the style guide.</span>",
"<span class='notice'>You wonder if you're following the guide correctly.</span>")
// Good
var/list/numbers = list(
1,
2,
3,
)
user.visible_message(
"<span class='notice'>[user] writes the style guide.</span>",
"<span class='notice'>You write the style guide.</span>",
"<span class='notice'>You hear typing.</span>"
)
// Also good
var/list/letters = list("a", "b", "c")
```
## Indentation
Indentation in DM is used to define code blocks and scopes. Our code base
requiress tab spacing. Singular spacing to the length of four spaces will not be
accepted.
```dm
// Good
for(var/example in 1 to 10)
if(example > 5)
to_chat(world, "Higher than five")
else
to_chat(world, "Lower than five")
```
Only when it comes to defines, curly braces can be used. This is allowed in some
instances to keep code neat and readable, and to ensure that macros expand
properly regardless of their indentation level in code
```dm
// Good
#define FOO /datum/foo {\
var/name = "my_foo"}
```
Not only is it easier to write, but the DM compiler also optimizes the preferred
method to run faster than the bad example. Using the DM style loop enhances
readability and aligns with the languages conventions.
## Operators
### Spacing
Code readability is an important aspect of developing on a large-scale project,
especially when it comes to open-source. As emphasized by other places in this
document, it is important to keep the code as readable as possible. One way we
do that is through spacing. Maintain a single space between all operators and
operands, including during variable declarations and value assignments.
```dm
// Bad
var/example_variable=5
// Also Bad
example_variable=example_variable*2
// Good
var/example_variable = 5
// Also Good
example_variable = example_variable * 2
```
## Boolean Defines
Use `TRUE` and `FALSE` instead of 1 and 0 for booleans. This improves
readability and clarity, making it clear that values represent true or false
conditions.
```dm
// Bad
var/example = 1
if(example)
example_proc()
// Good
var/example = TRUE
if(example)
example_proc()
```
## File Naming and References
### Naming
When naming files, it is important to keep readability in mind. Use these
guidelines when creating a new file.
- Keep names short (≤ 25 characters).
- Do not add spaces. Use underscores to separate words.
- Do not include special characters such as: " / \ [ ] : ; | = , < ? > & $ # ! ' { } *.
### References
When referencing files, use single quotes (') around the file name instead of
double quotes.
```dm
// Bad
var/sound_effect = "sounds/machines/wooden_closet_open.ogg"
// Good
var/sound_effect = 'sounds/machines/wooden_closet_open.ogg'
```
## HTML Tag Format
Though uppercase or mixing cases will work, we prefer to follow the W3 standard
for writing HTML tags. This means tags should be written in lowercase. This
makes code more readable and it just looks better.
```dm
// Bad
<B>This is an example of how not to do it.</B>
// Good
<b>This is an example of how it should be.</b>
```
## A Final Note
These guidelines are subject to change, and this document may be expanded on in
the future. Contributors and reviewers should take note of it and reference it
when needed. By following these guidelines, we can promote consistency across
the codebase and improve the quality of our code. Not only that, by doing so,
you help reduce the workload for those responsible for reviewing and managing
your intended changes. Thank you for taking the time to review this document.
Happy contributing!
+225
View File
@@ -0,0 +1,225 @@
# Guide to Testing
Code by nature works *as coded* and not always *as intended*, while you
know that your code compiles and passes tests on your Pull Request you
may not know if it breaks in edge cases or works fully in-game. In order
to ensure your changes actually work, you will need to know how to
**Test Your Code**. As part of this process, you will learn how to use
various in-game debugging tools to fully utilize your changes in a test
server, analyse variables at run-time, test for edge cases, and stress
test features. This guide will also explain more advanced concepts and
testing such as advanced proc calls, garbage collection testing, and
breakpoints.
## Prerequisites
- You will need to first [set up your development environment][setup] and
successfully launch a local server.
- Give yourself host-level permissions on your local server (You should have
access to every verb and tab available in-game).
- Have an open and patient mindset.
- Approach the QA process as if you're asking yourself questions and
answering them by performing successful (or unsuccessful) tests.
[setup]: ../contributing/getting_started.md
### Prep Work
In order to speed this up, especially for experienced devs, know what
you're looking for and write down a list (mental lists work as well) of
what you want to test. If you're only changing an attribute, list the
interactions that attribute has with other functions so you remember to
test each one. If you're adding a new atom, write down possible
interactions that atom may have with other relevant atoms (think parent
objects, tools, materials, machinery such as autholathes, antagonists).
Make your tests atomic. i.e. don't try and test everything at once,
pick one specific thing (or closely related groups of things) to test
on. If your item affects other items you want to test, consider
restarting and using a clean round or properly cleaning up the test
area.
### Basic In-Game Tools
While mastery of these tools is not required, basic familiarity with
them will be paramount to proper testing.
#### Game Panel
The Game Panel is a small menu that allows the user to set the game mode
for the round or spawn in atoms (turfs, objects, mobs, etc). In order to
access the Game Panel, you will need to click the *Game Panel* verb
under the admin tab.
![](./images/Game_Panel.png)
The Game panel has five buttons:
1. **Change Gamemode** will allow the user to set the round game mode. This is
only binding if the round has not started yet.
2. **Create Object** allows the user to spawn in any object. The search bar will
return all type paths relevant to the search given.
3. **Quick Create Object** allows the user to search for objects in a more
specific scope (only guns, only mechs, etc).
4. **Create Turf** allows the user to change the turf they are directly over.
5. **Create Mob** allows the user to spawn in a mob.
The most important buttons are the four create buttons. By clicking on them you
can open up the game panel create menu. For beginners, there are five important
aspects of the game panel that you will need to know (the other inputs and
buttons are very sparsely used, and likely not needed in your case).
1. The type path to search for, this input will tell the panel to query
for any typepath that contains the given string, so if you searched
for "book" it would return type paths such as
"machinery/bookBinder", "spellbook/mime/oneuse", or
"book/codex_gigas". Keep in mind this will return *all* type paths
with the given string, since the game panel is tied to your client
CPU usage, trying to search type paths with a query such as "item"
or "mob" will return thousands of results and likely freeze your
client for some time or crash it.
2. The number or amount of the element you want to spawn, if you were
spawning a book and typed in three, it would spawn three books.
3. Where this object will spawn, generally you will want the default
"On the floor below mob" or if you're a human, "in own mob's
hands". If you're specifically trying to spawn the element inside
another object, you can mark the object and use that option.
4. The list of type paths to select, you will need to click the
typepath to select it. Alternatively, if you want to spawn in
multiple types at once, you can click-drag up to 5 type paths and
spawn them all at once.
5. The button that spawns stuff with the parameters you gave the panel.
![](./images/Game_Panel_Create.png)
#### Runtime Viewer
The runtime viewer interface is a menu that displays every runtime that
occurred during the current round. It is available by clicking the *View
Runtimes* verb under the "Debug" tab.
![](./images/Runtime_Viewer.png)
The runtime viewer displays a list of *almost* every runtime in a round, a few
unimportant or repeated runtimes are skipped. Essentially, runtimes are errors
that occur when the server is running (as compared to a build error that occurs
when attempting to compile). Clicking on a runtime will open up more details
about it.
1. The runtime error. This will generally include information about the
type of error (null reference, bad proc calls, etc), what file it
occurred in, what line it occurred at, and information about the
proc it occurred in. Some errors will also include "call stacks"
or the procs called leading up to the error.
2. user VV button, will open the view variables panel on the mob that
caused the runtime
3. user PP button, will open the player panel on the mob that caused
the runtime
4. user follow button, will force the user to follow/orbit the mob that
caused the runtime
5. loc VV button, will open the view variables panel on the loc (turf
or thing that contains the object) of the object that caused the
runtime
6. loc jump button, will force the user to jump to the loc where the
runtime occurred.
![](./images/Runtime_Viewer_View_Runtime.png)
## Does it Even Work?
The first step in testing is to see if your change spawns in/displays
*at all*. This part of testing focuses solely on finding out when and
where your changes break, not particularly how or why it breaks.
If your change is creation/removal of an atom. open up the [Game Panel](#game-panel)
and see if the atom has been added/removed as a typepath. If it's not, make sure
your code was actually compiled and check to either see if you:
- actually defined a new typepath properly and have the file ticked in the DME
file, and
- you removed ALL instances where the type path is used (even proc
definitions!).
If your change is a map change, please see the [Mapping Requirements](../mapping/requirements.md).
Use the game panel to spawn your atom with the given type path. Ensure the following:
- Does it appear?
- Is the sprite correct?
- Is the name/appearance/description of the atom correct?
_Note:_ An Atom in DM refers to all elements of type "area", "turf", "object",
or "mob." Each has different behaviors for spawning, deletion, and interaction,
so keep that in mind. Additionally, there will not be much reference/relevance
in this section to "Area" type atoms.
### Does it Work the Way You Want it to?
Test the attributes of your atom:
- If it has health, can you kill or break it?
- If it has a menu, can you open up and interact with the UI correctly, can you
press buttons?
- If you added a special feature, can you activate it correctly?
- Does your new turf have proper atmospherics?
You may not have touched a certain section of code, but it's entirely
possible that you broke it with a nearby change, check to make sure it
still works the way it's supposed to (or even at all). For example, if
you modified a variable inside a book object, can the barcode scanner
still scan it into the library system? If you changed the way xenomorphs
handle combat, will disablers, lasers, batons, etc still work the same
way or function at all?
### Be Concise and Specific
Your goal here is to break your change in every (relevant) way possible,
use your change in every way you intended it to be used and then use it
every way it wasn't intended to be used. However, this isn't to say
you need to test every use case or test every single object that may be
affected. As a contributor, you have limited time in your day to spend
on coding, don't waste all of it trying out every different testing
scenario. Here are a few tips to be efficient:
- Know the code so that you know what other objects or parts of a feature will
be affected, then you have a mental list of things that need to be tested.
- Focus on testing one feature at a time, especially ones that you're focused on
coding at the moment; This keeps your attention scoped to that feature so you
can quickly make the needed changes and move on (this will help avoid
"*scope-creep*.")
- If your feature is built on another feature working (i.e. your feature working
*depends* completely on another feature working properly), test the dependency
if your feature is breaking to ensure the point of failure isn't just a
dependency breaking.
## Why Doesn't it Work?
The previous question of "does it work" often answers itself just by
spooling up a test environment, however, figuring out why things break
is a much more difficult and in-depth task. This section will avoid
getting into technical discussion and will instead explore conceptually
how to begin understanding why your change is not working.
### Does it Produce Errors?
Changes that clearly break often come saddled with a few *runtimes* which are
errors that occur while the server is actively running. In your preferences tab,
you can click the *Toggle Debug Log Messages* verb to toggle on debug message
which will allow you to see runtimes pop up exactly when they happen in the chat
box. You will need to do this every round unless you have a database properly
set up. Additionally, you can view all runtimes in a round by clicking the
[*View Runtimes*](#runtime-viewer) verb in the debug tab to open up the runtime
viewer.
This will allow you to identify the errors your changes are producing
and possibly even identify where, how, and what is breaking in your
code. Do note that this will often not reveal larger issues with your
code that is sourced from bad design decisions or unintentional effects.
## TODOs
Further sections are forthcoming, including assertion checking.
+77
View File
@@ -0,0 +1,77 @@
# Pull Request Testing Requirements
Testing is a critical aspect of the pull request process for the development
here at Paradise. Bugs often arise due to insufficient testing, which
can compromise the hard work of our contributors and development team members.
It is mandatory that all pull requests undergo thorough testing before merging.
Failure to comply may result in closure of the pull request and possible
disciplinary action.
## Testing Procedures
### Local Testing
1. Compile and Run: Ensure the code compiles without errors.
2. Game Loading: Use your preferred debugging method to load the game and verify
changes are applied correctly.
3. Functional Testing: Test new features or changes to ensure they integrate
smoothly with existing functionality.
### Validation
1. Feature Integrity: Confirm that new additions do not break existing game
features.
2. Performance Testing: Assess the performance impact of changes to ensure they
meet acceptable standards.
### Comprehensive Review
1. Edge Cases: Test edge cases to ensure robustness of the code.
2. Error Handling: Verify error handling mechanisms are effective and
informative.
### Documentation and Reporting
1. Update Documentation: If changes impact user-facing features or developer
documentation, update them accordingly.
2. Reporting: Provide clear and concise feedback in the pull request regarding
testing outcomes and any discovered issues.
### Test Merging Into Production
1. Additional Testing: If further validation is necessary, a test merge into
production may be scheduled to assess the code in a live environment. It is
imperative that the code functions correctly before proceeding with a test
merge.
2. Requesting a Test Merge: Authors of pull requests may request a test merge
during the review phase. Additionally, development team members may initiate
a test merge if deemed necessary.
3. Responsibilities During Test Merge: If your pull request is selected for a
test merge, it is your responsibility to actively manage and update it as
needed for integration into the codebase. This includes promptly addressing
reported bugs and related issues.
4. Consequences of Non-compliance: Failure to address requested changes during
the test merge process will result in the pull request being reverted from
production and potentially closed.
Testing plays a vital role in our process. As an open-source project with a
diverse community of contributors, it's essential to safeguard everyone's
contributions from potential issues. Thorough testing not only helps maintain
the quality of our code but also eases the workload for our development team,
who ensure that each pull request meets our agreed-upon standards. By following
these steps, we can ensure a streamlined process when implementing changes.
Do you need some help with testing your Pull Request? You can ask questions to
the development team on the [Paradise Station Discord][discord]. We also have a
[guide about testing pull requests here!][testing-guide]
[discord]: https://discord.gg/YJDsXFE
[testing-guide]: testing_guide.md