mirror of
https://github.com/yogstation13/Yogstation.git
synced 2025-02-26 09:04:50 +00:00
Updates development documentation on Github (#17581)
* Create STYLE.md * Create STANDARDS.md * Move AUTODOC.md * rip walk manager * Update CONTRIBUTING.md * Add link to porting section * A few changes * Move some README stuff to the guides folder * Feature freeze over * Typo * Modern maps * Add a few more guides * !!!
This commit is contained in:
361
.github/CONTRIBUTING.md
vendored
361
.github/CONTRIBUTING.md
vendored
@@ -1,5 +1,17 @@
|
||||
# CONTRIBUTING
|
||||
|
||||
1. [Reporting Issues](#reporting-issues)
|
||||
2. [Introduction](#introduction)
|
||||
3. [Getting Started](#getting-started)
|
||||
4. [Meet the Team](#meet-the-team)
|
||||
1. [Head Developer](#head-developer)
|
||||
2. [Maintainers](#maintainers)
|
||||
5. [Yogstation GitHub Guidelines](#yogstation-github-guidelines)
|
||||
6. [Development Guides](#development-guides)
|
||||
7. [Pull Request Process](#pull-request-process)
|
||||
8. [Porting features/sprites/sounds/tools from other codebases](#porting-featuresspritessoundstools-from-other-codebases)
|
||||
9. [A word on Git](#a-word-on-git)
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
See [this page](http://tgstation13.org/wiki/Reporting_Issues) for a guide and format to issue reports.
|
||||
@@ -22,11 +34,11 @@ You can of course, as always, ask for help in #coder-public on the [discord](htt
|
||||
|
||||
## Meet the Team
|
||||
|
||||
**Head Developer**
|
||||
### Head Developer
|
||||
|
||||
The Head Developer(s) are responsible for controlling, adding, and removing maintainers from the project. In addition to filling the role of a normal maintainer, they have sole authority on who becomes a maintainer, as well as who remains a maintainer and who does not.
|
||||
|
||||
**Maintainers**
|
||||
### Maintainers
|
||||
|
||||
Maintainers are quality control. If a proposed pull request doesn't meet the specifications laid out in this document, they can request you to change it, or simply just close the pull request. Maintainers are required to wait 24 hours before closing a pull request, and must give a reason for closing the pull request.
|
||||
|
||||
@@ -77,343 +89,24 @@ Maintainers can revert your changes if they feel they are not worth maintaining
|
||||
|
||||
**These policies are enforced by the Head Developer(s) and are subject to change at their discretion, with or without notification to the general public.**
|
||||
|
||||
## Specifications
|
||||
## Development Guides
|
||||
|
||||
As mentioned before, you are expected to follow these specifications in order to make everyone's lives easier. It'll save both your time and ours, by making sure you don't have to make any changes and we don't have to ask you to. Thank you for reading this section!
|
||||
#### Writing readable code
|
||||
[Style guide](./guides/STYLE.md)
|
||||
|
||||
### 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.
|
||||
#### Writing sane code
|
||||
[Code standards](./guides/STANDARDS.md)
|
||||
|
||||
### All BYOND paths must contain the full path
|
||||
(i.e. absolute pathing)
|
||||
#### Writing understandable code
|
||||
[Autodocumenting code](./guides/AUTODOC.md)
|
||||
|
||||
DM will allow you nest almost any type keyword into a block, such as:
|
||||
#### Misc
|
||||
|
||||
```DM
|
||||
datum
|
||||
datum1
|
||||
var
|
||||
varname1 = 1
|
||||
varname2
|
||||
proc
|
||||
proc1()
|
||||
code
|
||||
proc2()
|
||||
code
|
||||
|
||||
datum2
|
||||
varname1 = 0
|
||||
proc
|
||||
proc3()
|
||||
code
|
||||
proc2()
|
||||
..()
|
||||
code
|
||||
```
|
||||
|
||||
The use of this 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
|
||||
var/varname2
|
||||
|
||||
/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
|
||||
```
|
||||
|
||||
### No overriding type safety checks
|
||||
The use of the : operator to override type safety checks is not allowed. You must cast the variable to the proper type.
|
||||
|
||||
### Type paths must begin with a /
|
||||
eg: `/datum/thing`, not `datum/thing`
|
||||
|
||||
### Type paths must be lowercase
|
||||
eg: `/datum/thing/blue`, not `datum/thing/BLUE` or `datum/thing/Blue`
|
||||
|
||||
### Datum type paths must began with "datum"
|
||||
In DM, this is optional, but omitting it makes finding definitions harder.
|
||||
|
||||
### 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
|
||||
//Good
|
||||
var/path_type = /obj/item/baseball_bat
|
||||
|
||||
//Bad
|
||||
var/path_type = "/obj/item/baseball_bat"
|
||||
```
|
||||
|
||||
### Use var/name format when declaring variables
|
||||
While DM allows other ways of declaring variables, this one should be used for consistency.
|
||||
|
||||
### Variable names must not be the same as a proc name
|
||||
```DM
|
||||
//Don't do this
|
||||
/obj
|
||||
var/block_attack = TRUE
|
||||
|
||||
/obj/proc/block_attack()
|
||||
return density
|
||||
```
|
||||
### Tabs, not spaces
|
||||
You must use tabs to indent your code, NOT SPACES.
|
||||
|
||||
(You may use spaces to align something, but you should tab to the block level first, then add the remaining spaces)
|
||||
|
||||
### No hacky code
|
||||
Hacky code, such as adding specific checks, is highly discouraged and only allowed when there is ***no*** other option. (Protip: 'I couldn't immediately think of a proper way so thus there must be no other option' is not gonna cut it here! If you can't think of anything else, say that outright and admit that you need help with it. Maintainers 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.
|
||||
|
||||
### No duplicated code
|
||||
Copying code from one place to another may be suitable for small, short-time projects, but yogstation 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. See here for details on `Initialize`: https://github.com/tgstation/tgstation/blob/master/code/game/atoms.dm#L49
|
||||
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 dependant on parent and children procs so unrelated random conversions of existing things can cause bugs that take months to figure out.
|
||||
|
||||
### 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
|
||||
/datum/proc/do_the_thing(thing_to_do)
|
||||
switch(thing_to_do)
|
||||
if(1)
|
||||
(...)
|
||||
if(2)
|
||||
(...)
|
||||
````
|
||||
There's no indication of what "1" and "2" mean! Instead, you'd do something like this:
|
||||
````DM
|
||||
#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)
|
||||
(...)
|
||||
if(DO_THE_THING_EFFICIENTLY)
|
||||
(...)
|
||||
````
|
||||
This is clearer and enhances readability of your code! Get used to doing it!
|
||||
|
||||
### Control statements
|
||||
(if, while, for, etc)
|
||||
|
||||
* All control statements must not contain code on the same line as the statement (`if (blah) return`)
|
||||
* All control statements comparing a variable to a number should use the formula of `thing` `operator` `number`, not the reverse (eg: `if (count <= 10)` not `if (10 >= count)`)
|
||||
|
||||
### Use early return
|
||||
Do not enclose a proc in an if-block when returning on a condition is more feasible
|
||||
This is bad:
|
||||
````DM
|
||||
/datum/datum1/proc/proc1()
|
||||
if (thing1)
|
||||
if (!thing2)
|
||||
if (thing3 == 30)
|
||||
do stuff
|
||||
````
|
||||
This is good:
|
||||
````DM
|
||||
/datum/datum1/proc/proc1()
|
||||
if (!thing1)
|
||||
return
|
||||
if (thing2)
|
||||
return
|
||||
if (thing3 != 30)
|
||||
return
|
||||
do stuff
|
||||
````
|
||||
This prevents nesting levels from getting deeper then they need to be.
|
||||
|
||||
### 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 sanitizeSQL to escape text based database entries from players or admins, and isnum() for number based database entries from players or admins.
|
||||
|
||||
* 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.
|
||||
|
||||
* It is recommended as well you do not expose information about the players - even something as simple as the number of people who have readied up at the start of the round can and has been used to try to identify the round type.
|
||||
|
||||
* Where you have code that can cause large-scale modification and *FUN*, make sure you start it out locked behind one of the default admin roles - use common sense to determine which role fits the level of damage a function could do.
|
||||
|
||||
### Files
|
||||
* Because runtime errors do not give the full path, try to avoid having files with the same name across folders.
|
||||
|
||||
* File names should not be mixed case, or contain spaces or any character that would require escaping in a uri.
|
||||
|
||||
* Files and path accessed and referenced by code above simply being #included should be strictly lowercase to avoid issues on filesystems where case matters.
|
||||
|
||||
### 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.
|
||||
|
||||
* All changes to the database's layout(schema) must be specified in the database changelog in SQL, as well as reflected in the schema files
|
||||
|
||||
* Any time the schema is changed the `schema_revision` table and `DB_MAJOR_VERSION` or `DB_MINOR_VERSION` defines must be incremented.
|
||||
|
||||
* Queries must never specify the database, be it in code, or in text files in the repo.
|
||||
|
||||
* Primary keys are inherently immutable and you must never do anything to change the primary key of a row or entity. This includes preserving auto increment numbers of rows when copying data to a table in a conversion script. No amount of bitching about gaps in ids or out of order ids will save you from this policy.
|
||||
|
||||
### Mapping Standards
|
||||
* TGM Format & Map Merge
|
||||
* All new maps submitted to the repo through a pull request must be in TGM format (unless there is a valid reason present to have it in the default BYOND format.) This is done using the [Map Merge](https://github.com/tgstation/tgstation/wiki/Map-Merger) utility included in the repo to convert the file to TGM format.
|
||||
* Likewise, you MUST run Map Merge prior to opening your PR when updating existing maps to minimize the change differences (even when using third party mapping programs such as FastDMM.)
|
||||
* Failure to run Map Merge on a map after using third party mapping programs (such as FastDMM) greatly increases the risk of the map's key dictionary becoming corrupted by future edits after running map merge. Resolving the corruption issue involves rebuilding the map's key dictionary; id est rewriting all the keys contained within the map by reconverting it from BYOND to TGM format - which creates very large differences that ultimately delay the PR process and is extremely likely to cause merge conflicts with other pull requests.
|
||||
|
||||
* Variable Editing (Var-edits)
|
||||
* While var-editing an item within the editor is perfectly fine, it is preferred that when you are changing the base behavior of an item (how it functions) that you make a new subtype of that item within the code, especially if you plan to use the item in multiple locations on the same map, or across multiple maps. This makes it easier to make corrections as needed to all instances of the item at one time as opposed to having to find each instance of it and change them all individually.
|
||||
* Subtypes only intended to be used on away mission or ruin maps should be contained within an .dm file with a name corresponding to that map within `code\modules\awaymissions` or `code\modules\ruins` respectively. This is so in the event that the map is removed, that subtype will be removed at the same time as well to minimize leftover/unused data within the repo.
|
||||
* Please attempt to clean out any dirty variables that may be contained within items you alter through var-editing. For example, due to how DM functions, changing the `pixel_x` variable from 23 to 0 will leave a dirty record in the map's code of `pixel_x = 0`. Likewise this can happen when changing an item's icon to something else and then back. This can lead to some issues where an item's icon has changed within the code, but becomes broken on the map due to it still attempting to use the old entry.
|
||||
* Areas should not be var-edited on a map to change it's name or attributes. All areas of a single type and it's altered instances are considered the same area within the code, and editing their variables on a map can lead to issues with powernets and event subsystems which are difficult to debug.
|
||||
|
||||
|
||||
### Other Notes
|
||||
* Code should be modular where possible; if you are working on a new addition, then strongly consider putting it in its own file unless it makes sense to put it with similar ones (i.e. a new tool would go in the "tools.dm" file)
|
||||
|
||||
* Bloated code may be necessary to add a certain feature, which means there has to be a judgement over whether the feature is worth having or not. You can help make this decision easier by making sure your code is modular.
|
||||
|
||||
* You are expected to help maintain the code that you add, meaning that if there is a problem then you are likely to be approached in order to fix any issues, runtimes, or bugs.
|
||||
|
||||
* Do not divide when you can easily convert it to multiplication. (ie `4/2` should be done as `4*0.5`)
|
||||
|
||||
* If you used regex to replace code during development of your code, post the regex in your PR for the benefit of future developers and downstream users.
|
||||
|
||||
* Changes to the `/config` tree must be made in a way that allows for updating server deployments while preserving previous behaviour. This is due to the fact that the config tree is to be considered owned by the user and not necessarily updated alongside the remainder of the code. The code to preserve previous behaviour may be removed at some point in the future given the OK by maintainers.
|
||||
|
||||
* The dlls section of tgs3.json is not designed for dlls that are purely `call()()`ed since those handles are closed between world reboots. Only put in dlls that may have to exist between world reboots.
|
||||
|
||||
#### Enforced not enforced
|
||||
The following coding styles are not only not enforced at all, but are generally frowned upon to change for little to no reason:
|
||||
|
||||
* English/British spelling on var/proc names
|
||||
* Color/Colour - both are fine, but keep in mind that BYOND uses `color` as a base variable
|
||||
* Spaces after control statements
|
||||
* `if()` and `if ()` - nobody cares!
|
||||
|
||||
### Operators
|
||||
#### Spacing
|
||||
(this is not strictly enforced, but more a guideline for readability's sake)
|
||||
|
||||
* Operators that should be separated by spaces
|
||||
* Boolean and logic operators like &&, || <, >, ==, etc (but not !)
|
||||
* Bitwise AND &
|
||||
* Argument separator operators like , (and ; when used in a forloop)
|
||||
* Assignment operators like = or += or the like
|
||||
* Operators that should not be separated by spaces
|
||||
* Bitwise OR |
|
||||
* Access operators like . and :
|
||||
* Parentheses ()
|
||||
* logical not !
|
||||
|
||||
Math operators like +, -, /, *, etc are up in the air, just choose which version looks more readable.
|
||||
|
||||
#### Use
|
||||
* Bitwise AND - '&'
|
||||
* Should be written as ```bitfield & bitflag``` NEVER ```bitflag & bitfield```, both are valid, but the latter is confusing and nonstandard.
|
||||
* Associated lists declarations must have their key value quoted if it's a string
|
||||
* WRONG: list(a = "b")
|
||||
* RIGHT: list("a" = "b")
|
||||
|
||||
### Dream Maker Quirks/Tricks
|
||||
Like all languages, Dream Maker has its quirks, some of them are beneficial to us, like these
|
||||
|
||||
#### In-To for-loops
|
||||
```for(var/i = 1, i <= some_value, i++)``` is a fairly standard way to write an incremental for loop in most languages (especially those in the C family), but DM's ```for(var/i in 1 to some_value)``` syntax is oddly faster than its implementation of the former syntax; where possible, it's advised to use DM's syntax. (Note, the ```to``` keyword is inclusive, so it automatically defaults to replacing ```<=```; if you want ```<``` then you should write it as ```1 to some_value-1```).
|
||||
|
||||
HOWEVER, if either ```some_value``` or ```i``` changes within the body of the for (underneath the ```for(...)``` header) or if you are looping over a list AND changing the length of the list then you can NOT use this type of for-loop!
|
||||
|
||||
### for(var/A in list) VS for(var/i in 1 to list.len)
|
||||
The former is faster than the latter, as shown by the following profile results:
|
||||
https://file.house/zy7H.png
|
||||
Code used for the test in a readable format:
|
||||
https://pastebin.com/w50uERkG
|
||||
|
||||
|
||||
#### Istypeless for loops
|
||||
A name for a differing syntax for writing for-each style loops in DM. It's NOT DM's standard syntax, hence why this is considered a quirk. Take a look at this:
|
||||
```DM
|
||||
var/list/bag_of_items = list(sword, apple, coinpouch, sword, sword)
|
||||
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(sword, sword, sword, sword)
|
||||
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
|
||||
```
|
||||
specifies a type for DM to filter by.
|
||||
|
||||
With the previous example that's perfectly fine, we only want swords, but here 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.
|
||||
|
||||
#### 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.
|
||||
- [Embedding TGUI Components in Chat](../../tgui/docs/chat-embedded-components.md)
|
||||
- [Hard Deletes](./guides/HARDDELETES.md)
|
||||
- [MC Tab Guide](./guides/MC_tab.md)
|
||||
- [Tick system](./guides/TICK_ORDER.md)
|
||||
- [UI Development](../tgui/README.md)
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
|
||||
23
.github/guides/DOWNLOADING.md
vendored
Normal file
23
.github/guides/DOWNLOADING.md
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
## DOWNLOADING
|
||||
|
||||
There are a number of ways to download the source code. Some are described here, an alternative all-inclusive guide is also located at https://wiki.yogstation.net/wiki/Downloading_the_source_code
|
||||
|
||||
Option 1:
|
||||
Follow this: https://wiki.yogstation.net/wiki/Setting_up_git
|
||||
|
||||
Option 2: Download the source code as a zip by clicking the ZIP button in the
|
||||
code tab of https://github.com/yogstation13/Yogstation
|
||||
(note: this will use a lot of bandwidth if you wish to update and is a lot of
|
||||
hassle if you want to make any changes at all, so it's not recommended.)
|
||||
|
||||
## The Yogstation codebase recommends compiling using version [514.1589](https://www.byond.com/download/build/514/514.1589_byond.exe) and may potentially NOT work on newer or older versions.
|
||||
|
||||
## :exclamation: How to compile :exclamation:
|
||||
|
||||
Recently we have changed the way to compile the codebase.
|
||||
|
||||
Find `BUILD.bat` here in the root folder of yogstation, and double click it to initiate the build. It consists of multiple steps and might take around 1-5 minutes to compile.
|
||||
|
||||
After it finishes, you can then setup the server normally by opening `yogstation.dmb` in DreamDaemon. See further down for instructions
|
||||
|
||||
**Building yogstation in DreamMaker directly is now deprecated and might produce errors**, such as `'tgui.bundle.js': cannot find file`.
|
||||
289
.github/guides/HARDDELETES.md
vendored
Normal file
289
.github/guides/HARDDELETES.md
vendored
Normal file
@@ -0,0 +1,289 @@
|
||||
# Hard Deletes
|
||||
|
||||
> Garbage collection is pretty gothic when you think about it.
|
||||
>
|
||||
>An object in code is like a ghost, clinging to its former life, and especially to the people it knew. It can only pass on and truly die when it has dealt with its unfinished business. And only when its been forgotten by everyone who ever knew it. If even one other object remembers it, it has a connection to the living world that lets it keep hanging on
|
||||
>
|
||||
>There is a kind of sombre tone to fixing GC errors too, its almost shamanistic, making sure all these little objects clear up their final affairs in life before they die, to ensure they don't become ghosts
|
||||
>
|
||||
> -- <cite>Nanako</cite>
|
||||
|
||||
### Table of contents
|
||||
|
||||
1. [What is hard deletion](#What-is-hard-deletion)
|
||||
2. [Causes of hard deletes](#causes-of-hard-deletes)
|
||||
3. [Detecting hard deletes](#detecting-hard-deletes)
|
||||
4. [Techniques for fixing hard deletes](#techniques-for-fixing-hard-deletes)
|
||||
5. [Help my code is erroring how fix](#help-my-code-is-erroring-how-fix)
|
||||
|
||||
|
||||
## What is Hard Deletion
|
||||
|
||||
Hard deletion is a very expensive operation that basically clears all references to some "thing" from memory. Objects that undergo this process are referred to as hard deletes, or simply harddels
|
||||
|
||||
What follows is a discussion of the theory behind this, why we would ever do it, and the what we do to avoid doing it as often as possible
|
||||
|
||||
I'm gonna be using words like references and garbage collection, but don't worry, it's not complex, just a bit hard to pierce
|
||||
|
||||
### Why do we need to Hard Delete?
|
||||
|
||||
Ok so let's say you're some guy called Jerry, and you're writing a programming language
|
||||
|
||||
You want your coders to be able to pass around objects without doing a full copy. So you'll store the pack of data somewhere in memory
|
||||
|
||||
```dm
|
||||
/someobject
|
||||
var/id = 42
|
||||
var/name = "some shit"
|
||||
```
|
||||
|
||||
Then you want them to be able to pass that object into say a proc, without doing a full copy. So you let them pass in the object's location in memory instead
|
||||
This is called passing something by reference
|
||||
|
||||
```dm
|
||||
someshit(someobject) //This isn't making a copy of someobject, it's passing in a reference to it
|
||||
```
|
||||
|
||||
This of course means they can store that location in memory in another object's vars, or in a list, or whatever
|
||||
|
||||
```dm
|
||||
/datum
|
||||
var/reference
|
||||
|
||||
/proc/someshit(mem_location)
|
||||
var/datum/some_obj = new()
|
||||
some_obj.reference = mem_location
|
||||
```
|
||||
|
||||
But what happens when you get rid of the object we're passing around references to? If we just cleared it out from memory, everything that holds a reference to it would suddenly be pointing to nowhere, or worse, something totally different!
|
||||
|
||||
So then, you've gotta do something to clean up these references when you want to delete an object
|
||||
|
||||
We could hold a list of references to everything that references us, but god, that'd get really expensive wouldn't it
|
||||
|
||||
Why not keep count of how many times we're referenced then? If an object's ref count is ever 0, nothing whatsoever cares about it, so we can freely get rid of it
|
||||
|
||||
But if something's holding onto a reference to us, we're not gonna have any idea where or what it is
|
||||
|
||||
So I guess you should scan all of memory for that reference?
|
||||
|
||||
```dm
|
||||
del(someobject) //We now need to scan memory until we find the thing holding a ref to us, and clear it
|
||||
```
|
||||
|
||||
This pattern is about how BYOND handles this problem of hanging references, or Garbage Collection
|
||||
|
||||
It's not a broken system, but as you can imagine scanning all of memory gets expensive fast
|
||||
|
||||
What can we do to help that?
|
||||
|
||||
### How we can avoid hard deletes
|
||||
|
||||
If hard deletion is so slow, we're gonna need to clean up all our references ourselves
|
||||
|
||||
In our codebase we do this with `/datum/proc/Destroy()`, a proc called by `qdel()`, whose purpose I will explain later
|
||||
|
||||
This procs only job is cleaning up references to the object it's called on. Nothing more, nothing else. Don't let me catch you giving it side effects
|
||||
|
||||
There's a long long list of things this does, since we use it a TON. So I can't really give you a short description. It will always move the object to nullspace though
|
||||
|
||||
## Causes Of Hard Deletes
|
||||
|
||||
Now that you know the theory, let's go over what can actually cause hard deletes. Some of this is obvious, some of it's much less so.
|
||||
|
||||
The BYOND reference has a list [Here](https://secure.byond.com/docs/ref/#/DM/garbage), but it's not a complete one
|
||||
|
||||
* Stored in a var
|
||||
* An item in a list, or associated with a list item
|
||||
* Has a tag
|
||||
* Is on the map (always true for turfs)
|
||||
* Inside another atom's contents
|
||||
* Inside an atom's vis_contents
|
||||
* A temporary value in a still-running proc
|
||||
* Is a mob with a key
|
||||
* Is an image object attached to an atom
|
||||
|
||||
Let's briefly go over the more painful ones yeah?
|
||||
|
||||
### Sleeping procs
|
||||
|
||||
Any proc that calls `sleep()`, `spawn()`, or anything that creates a separate "thread" (not technically a thread, but it's the same in these terms. Not gonna cause any race conditions tho) will hang references to any var inside it. This includes the usr it started from, the src it was called on, and any vars created as a part of processing
|
||||
|
||||
### Static vars
|
||||
|
||||
`/static` and `/global` vars count for this too, they'll hang references just as well as anything. Be wary of this, these suckers can be a pain to solve
|
||||
|
||||
### Range() and View() like procs
|
||||
|
||||
Some internal BYOND procs will hold references to objects passed into them for a time after the proc is finished doing work, because they cache the returned info to make some code faster. You should never run into this issue, since we wait for what should be long enough to avoid this issue as a part of garbage collection
|
||||
|
||||
This is what `qdel()` does by the by, it literally just means queue deletion. A reference to the object gets put into a queue, and if it still exists after 5 minutes or so, we hard delete it
|
||||
|
||||
### Walk() procs
|
||||
|
||||
Calling `walk()` on something will put it in an internal queue, which it'll remain in until `walk(thing, 0)` is called on it, which removes it from the queue
|
||||
|
||||
This sort is very cheap to harddel, since BYOND prioritizes checking this queue first when it's clearing refs, but it should be avoided since it causes false positives
|
||||
|
||||
You can read more about how BYOND prioritizes these things [Here](https://www.patreon.com/posts/diving-for-35855766)
|
||||
|
||||
## Detecting Hard Deletes
|
||||
|
||||
For very simple hard deletes, simple inspection should be enough to find them. Look at what the object does during `Initialize()`, and see if it's doing anything it doesn't undo later.
|
||||
If that fails, search the object's typepath, and look and see if anything is holding a reference to it without regard for the object deleting
|
||||
|
||||
BYOND currently doesn't have the capability to give us information about where a hard delete is. Fortunately we can search for most all of then ourselves.
|
||||
The procs to perform this search are hidden behind compile time defines, since they'd be way too risky to expose to admin button pressing
|
||||
|
||||
If you're having issues solving a harddel and want to perform this check yourself, go to `_compile_options.dm` and uncomment `TESTING`, `REFERENCE_TRACKING`, and `GC_FAILURE_HARD_LOOKUP`
|
||||
|
||||
You can read more about what each of these do in that file, but the long and short of it is if something would hard delete our code will search for the reference (This will look like your game crashing, just hold out) and print information about anything it finds to the runtime log, which you can find inside the round folder inside `/data/logs/year/month/day`
|
||||
|
||||
It'll tell you what object is holding the ref if it's in an object, or what pattern of list transversal was required to find the ref if it's hiding in a list of some sort
|
||||
|
||||
## Techniques For Fixing Hard Deletes
|
||||
|
||||
Once you've found the issue, it becomes a matter of making sure the ref is cleared as a part of Destroy(). I'm gonna walk you through a few patterns and discuss how you might go about fixing them
|
||||
|
||||
### Our Tools
|
||||
|
||||
First and simplest we have `Destroy()`. Use this to clean up after yourself for simple cases
|
||||
|
||||
```dm
|
||||
/someobject/Initialize(mapload)
|
||||
. = ..()
|
||||
GLOB.somethings += src //We add ourselves to some global list
|
||||
|
||||
/someobject/Destroy()
|
||||
GLOB.somethings -= src //So when we Destroy() clean yourself from the list
|
||||
return ..()
|
||||
```
|
||||
|
||||
Next, and slightly more complex, pairs of objects that reference each other
|
||||
|
||||
This is helpful when for cases where both objects "own" each other
|
||||
|
||||
```dm
|
||||
/someobject
|
||||
var/someotherobject/buddy
|
||||
|
||||
/someotherobject
|
||||
var/someobject/friend
|
||||
|
||||
/someobject/Initialize(mapload)
|
||||
if(!buddy)
|
||||
buddy = new()
|
||||
buddy.friend = src
|
||||
|
||||
/someotherobject/Initialize(mapload)
|
||||
if(!friend)
|
||||
friend = new()
|
||||
friend.buddy = src
|
||||
|
||||
/someobject/Destroy()
|
||||
if(buddy)
|
||||
buddy.friend = null //Make sure to clear their ref to you
|
||||
buddy = null //We clear our ref to them to make sure nothing goes wrong
|
||||
|
||||
/someotherobject/Destroy()
|
||||
if(friend)
|
||||
friend.buddy = null //Make sure to clear their ref to you
|
||||
friend = null //We clear our ref to them to make sure nothing goes wrong
|
||||
```
|
||||
|
||||
Something similar can be accomplished with `QDELETED()`, a define that checks to see if something has started being `Destroy()`'d yet, and `QDEL_NULL()`, a define that `qdel()`'s a var and then sets it to null
|
||||
|
||||
Now let's discuss something a bit more complex, weakrefs
|
||||
|
||||
You'll need a bit of context, so let's do that now
|
||||
|
||||
BYOND has an internal bit of behavior that looks like this
|
||||
|
||||
`var/string = "\ref[someobject]"`
|
||||
|
||||
This essentially gets that object's position in memory directly. Unlike normal references, this doesn't count for hard deletes. You can retrieve the object in question by using `locate()`
|
||||
|
||||
`var/someobject/someobj = locate(string)`
|
||||
|
||||
This has some flaws however, since the bit of memory we're pointing to might change, which would cause issues. Fortunately we've developed a datum to handle worrying about this for you, `/datum/weakref`
|
||||
|
||||
You can create one using the `WEAKREF()` proc, and use weakref.resolve() to retrieve the actual object
|
||||
|
||||
This should be used for things that your object doesn't "own", but still cares about
|
||||
|
||||
For instance, a paper bin would own the paper inside it, but the paper inside it would just hold a weakref to the bin
|
||||
|
||||
There's no need to clean these up, just make sure you account for it being null, since it'll return that if the object doesn't exist or has been queued for deletion
|
||||
|
||||
```dm
|
||||
/someobject
|
||||
var/datum/weakref/our_coin
|
||||
|
||||
/someobject/proc/set_coin(/obj/item/coin/new_coin)
|
||||
our_coin = WEAKREF(new_coin)
|
||||
|
||||
/someobject/proc/get_value()
|
||||
if(!our_coin)
|
||||
return 0
|
||||
|
||||
var/obj/item/coin/potential_coin = our_coin.resolve()
|
||||
if(!potential_coin)
|
||||
our_coin = null //Remember to clear the weakref if we get nothing
|
||||
return 0
|
||||
return potential_coin.value
|
||||
```
|
||||
|
||||
Now, for the worst case scenario
|
||||
|
||||
Let's say you've got a var that's used too often to be weakref'd without making the code too expensive
|
||||
|
||||
You can't hold a paired reference to it because it's not like it would ever care about you outside of just clearing the ref
|
||||
|
||||
So then, we want to temporarily remember to clear a reference when it's deleted
|
||||
|
||||
This is where I might lose you, but we're gonna use signals
|
||||
|
||||
`qdel()`, the proc that sets off this whole deletion business, sends a signal called `COMSIG_PARENT_QDELETING`
|
||||
|
||||
We can listen for that signal, and if we hear it clear whatever reference we may have
|
||||
|
||||
Here's an example
|
||||
|
||||
```dm
|
||||
/somemob
|
||||
var/mob/target
|
||||
|
||||
/somemob/proc/set_target(new_target)
|
||||
if(target)
|
||||
UnregisterSignal(target, COMSIG_PARENT_QDELETING) //We need to make sure any old signals are cleared
|
||||
target = new_target
|
||||
if(target)
|
||||
RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(clear_target)) //Call clear_target if target is ever qdel()'d
|
||||
|
||||
/somemob/proc/clear_target(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
set_target(null)
|
||||
```
|
||||
|
||||
This really should be your last resort, since signals have some limitations. If some subtype of somemob also registered for parent_qdeleting on the same target you'd get a runtime, since signals don't support it
|
||||
|
||||
But if you can't do anything else for reasons of conversion ease, or hot code, this will work
|
||||
|
||||
## Help My Code Is Erroring How Fix
|
||||
|
||||
First, do a quick check.
|
||||
|
||||
Are you doing anything to the object in `Initialize()` that you don't undo in `Destroy()`? I don't mean like, setting its name, but are you adding it to any lists, stuff like that
|
||||
|
||||
If this fails, you're just gonna have to read over this doc. You can skip the theory if you'd like, but it's all pretty important for having an understanding of this problem
|
||||
|
||||
## Misc facts
|
||||
|
||||
> i like rust and all, buuut it removes garbage collecctor, and i pretend garbage collector is a cute girl checking my code
|
||||
>
|
||||
> -- <cite>Armhulenn</cite>
|
||||
|
||||
- The reference tracker, while powerful, is incredibly easy to break<br>
|
||||
If it weren't for those unit tests we'd still be missing list["a"] = list(ref)
|
||||
- Everyone but me sucks, because everyone but me keeps adding new hard deletes
|
||||
- Garbage collection is a spook, best practice is to use a random reference in place of null, it scares the compiler demons
|
||||
36
.github/guides/MAPS_AND_AWAY_MISSIONS.md
vendored
Normal file
36
.github/guides/MAPS_AND_AWAY_MISSIONS.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
## MAPS
|
||||
|
||||
Yogstation currently comes equipped with the following maps. Our currenly active map rotation can be found [here](https://wiki.yogstation.net/wiki/Maps). All maps not in active rotation should be considered **unmaintained**.
|
||||
|
||||
* [BoxStation (default)](https://wiki.yogstation.net/wiki/BoxStation)
|
||||
* [AsteroidStation](https://wiki.yogstation.net/wiki/AsteroidStation)
|
||||
* [MetaStation](https://wiki.yogstation.net/wiki/MetaStation)
|
||||
* [NVS Gax](https://wiki.yogstation.net/wiki/NVS_Gax)
|
||||
|
||||
All maps have their own code file that is in the base of the _maps directory. Maps are loaded dynamically when the game starts. Follow this guideline when adding your own map, to your fork, for easy compatibility.
|
||||
|
||||
The map that will be loaded for the upcoming round is determined by reading data/next_map.json, which is a copy of the json files found in the _maps tree. If this file does not exist, the default map from config/maps.txt will be loaded. Failing that, BoxStation will be loaded. If you want to set a specific map to load next round you can use the Change Map verb in game before restarting the server or copy a json from _maps to data/next_map.json before starting the server. Also, for debugging purposes, ticking a corresponding map's code file in Dream Maker will force that map to load every round.
|
||||
|
||||
If you are hosting a server, and want randomly picked maps to be played each round, you can enable map rotation in [config.txt](config/config.txt) and then set the maps to be picked in the [maps.txt](config/maps.txt) file.
|
||||
|
||||
Anytime you want to make changes to a map it's imperative you use the [Map Merging tools](https://tgstation13.org/wiki/Map_Merger)
|
||||
|
||||
## EDITING MAPS
|
||||
|
||||
### [Click here for a Quick-Start Guide To Mapping.](https://hackmd.io/@tgstation/SyVma0dS5)
|
||||
|
||||
<b>It is absolutely inadvisable to <i>ever</i> use the mapping utility offered by Dream Maker</b>. It is clunky and dated software that will steal your time, patience, and creative desires.
|
||||
|
||||
Instead, Yogstation map maintainers will always recommend using one of two modern and actively maintained programs.
|
||||
* [StrongDMM](https://github.com/SpaiR/StrongDMM) (Windows/Linux/MacOS)
|
||||
* [FastDMM2](https://github.com/monster860/FastDMM2) (Web-based Utility)
|
||||
|
||||
Both of the above programs have native TGM support, which is mandatory for all maps being submitted to this repository. Anytime you want to make changes to a map, it is imperative you use the [Map Merging tools](https://tgstation13.org/wiki/Map_Merger). When you clone your repository onto your machine for mapping, it's always a great idea to run `tools/hooks/Install.bat` at the very start of your mapping endeavors, as this will install Git hooks that help you automatically resolve any merge conflicts that come up while mapping.
|
||||
|
||||
## AWAY MISSIONS
|
||||
|
||||
Yogstation supports loading away missions however they are disabled by default.
|
||||
|
||||
Map files for away missions are located in the _maps/RandomZLevels directory. Each away mission includes it's own code definitions located in /code/modules/awaymissions/mission_code. These files must be included and compiled with the server beforehand otherwise the server will crash upon trying to load away missions that lack their code.
|
||||
|
||||
To enable an away mission open `config/awaymissionconfig.txt` and uncomment one of the .dmm lines by removing the #. If more than one away mission is uncommented then the away mission loader will randomly select one the enabled ones to load.
|
||||
36
.github/guides/MC_tab.md
vendored
Normal file
36
.github/guides/MC_tab.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
The MC tab hold information on how the game is performing. Here's a crash course on what the most important of those numbers mean.
|
||||
|
||||
If you already know what these numbers mean and you want to see them update faster than the default refresh rate of once every 2 seconds, you can enable the admin pref to make the MC tab refresh every 4 deciseconds. Please don't do this unless you actually need that information at a faster refresh rate since updating every subsystems information is expensive.
|
||||
|
||||
# Main Entries:
|
||||
|
||||
* CPU: What percentage of a tick the game is using before starting the next tick. If this is above 100 it means we are over budget.
|
||||
|
||||
* TickCount: How many ticks should have elapsed since the start of the game if no ticks were ever delayed from starting.
|
||||
|
||||
* TickDrift: How many ticks since the game started that have been delayed. Essentially this is how many ticks the game is running behind. If this is increasing then the game is currently not able to keep up with demand.
|
||||
|
||||
* Internal Tick Usage: You might have heard of this referred to as "maptick". It's how much of the tick that an internal byond function called SendMaps() has taken recently. The higher this is the less time our code has to run. SendMaps() deals with sending players updates of their view of the game world so it has to run every tick but it's expensive so ideally this is optimized as much as possible. You can see a more detailed breakdown of the cost of SendMaps by looking at the profiler in the debug tab -> "Send Maps Profile".
|
||||
|
||||
# Master Controller Entry:
|
||||
|
||||
* TickRate: How many Byond ticks go between each master controller iteration. By default this is 1 meaning the MC runs once every byond tick. But certain configurations can increase this slightly.
|
||||
|
||||
* Iteration: How many times the MC has ran since starting.
|
||||
|
||||
* TickLimit: This SHOULD be what percentage of the tick the MC can use when it starts a run, however currently it just represents how much of the tick the MC can use by the time that SSstatpanels fires. Someone should fix that.
|
||||
|
||||
# Subsystem Entries:
|
||||
|
||||
Subsystems will typically have a base stat entry of the form:
|
||||
[ ] Name 12ms|28%(2%)|3
|
||||
|
||||
The brackets hold a letter if the subsystem is in a state other than idle.
|
||||
|
||||
The first numbered entry is the cost of the subsystem, which is a running average of how many milliseconds the subsystem takes to complete a full run. This is increased every time the subsystem resumes an uncompleted run or starts a new run and decays when runs take less time. If this balloons to huge values then it means that the amount of work the subsystem needs to complete in a run is far greater than the amount of time it actually has to execute in whenever it is its turn to fire.
|
||||
|
||||
The second numbered entry is like cost, but in percentage of an ideal tick this subsystem takes to complete a run. They both represent the same data.
|
||||
|
||||
The third entry (2%) is how much time this subsystem spent executing beyond the time it was allocated by the MC. This is bad, it means that this subsystem doesn't yield when it's taking too much time and makes the job of the MC harder. The MC will attempt to account for this but it is better for all subsystems to be able to correctly yield when their turn is done.
|
||||
|
||||
The fourth entry represents how many times this subsystem fires before it completes a run.
|
||||
98
.github/guides/RUNNING_A_SERVER.md
vendored
Normal file
98
.github/guides/RUNNING_A_SERVER.md
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
## INSTALLATION
|
||||
|
||||
First-time installation should be fairly straightforward. First, you'll need
|
||||
BYOND installed. You can get it from https://www.byond.com/download. Once you've done
|
||||
that, extract the game files to wherever you want to keep them. This is a
|
||||
sourcecode-only release, so the next step is to compile the server files.
|
||||
Follow the above steps to do this.
|
||||
|
||||
If you see any errors or warnings, something has gone wrong - possibly a corrupt
|
||||
download or the files extracted wrong. If problems persist, ask for assistance
|
||||
in #development-public on discord.
|
||||
|
||||
Once that's done, open up the config folder. You'll want to edit config.txt to
|
||||
set the probabilities for different gamemodes in Secret and to set your server
|
||||
location so that all your players don't get disconnected at the end of each
|
||||
round. It's recommended you don't turn on the gamemodes with probability 0,
|
||||
except Extended, as they have various issues and aren't currently being tested,
|
||||
so they may have unknown and bizarre bugs. Extended is essentially no mode, and
|
||||
isn't in the Secret rotation by default as it's just not very fun.
|
||||
|
||||
You'll also want to edit config/admins.txt to remove the default admins and add
|
||||
your own. "Game Master" is the highest level of access, and probably the one
|
||||
you'll want to use for now. You can set up your own ranks and find out more in
|
||||
config/admin_ranks.txt
|
||||
|
||||
The format is
|
||||
|
||||
```
|
||||
byondkey = Rank
|
||||
```
|
||||
|
||||
where the admin rank must be properly capitalised.
|
||||
|
||||
This codebase also depends on a native library called rust-g. A precompiled
|
||||
Windows DLL is included in this repository, but Linux users will need to build
|
||||
and install it themselves. Directions can be found at the [rust-g
|
||||
repo](https://github.com/tgstation/rust-g). The `hash` feature is required.
|
||||
|
||||
Finally, to start the server, run Dream Daemon and enter the path to your
|
||||
compiled yogstation.dmb file. Make sure to set the port to the one you
|
||||
specified in the config.txt, and set the Security box to 'Trusted'. Then press GO
|
||||
and the server should start up and be ready to join. It is also recommended that
|
||||
you set up the SQL backend (see below).
|
||||
|
||||
## UPDATING
|
||||
|
||||
To update an existing installation, first back up your /config and /data folders
|
||||
as these store your server configuration, player preferences and banlist.
|
||||
|
||||
Then, extract the new files (preferably into a clean directory, but updating in
|
||||
place should work fine), copy your /config and /data folders back into the new
|
||||
install, overwriting when prompted except if we've specified otherwise, and
|
||||
recompile the game. Once you start the server up again, you should be running
|
||||
the new version.
|
||||
|
||||
## HOSTING
|
||||
|
||||
If you'd like a more robust server hosting option for tgstation and its
|
||||
derivatives. Check out /tg/station's server tools suite at
|
||||
https://github.com/tgstation/tgstation-server
|
||||
|
||||
## MAPS
|
||||
|
||||
Yogstation currently comes equipped with the following maps.
|
||||
|
||||
* [BoxStation (default)](https://wiki.yogstation.net/wiki/BoxStation)
|
||||
* [DeltaStation](https://wiki.yogstation.net/wiki/DeltaStation)
|
||||
* [EclipseStation](https://wiki.yogstation.net/wiki/Maps)
|
||||
* [MetaStation](https://wiki.yogstation.net/wiki/MetaStation)
|
||||
* [OmegaStation](https://wiki.yogstation.net/wiki/OmegaStation)
|
||||
|
||||
All maps have their own code file that is in the base of the _maps directory. Maps are loaded dynamically when the game starts. Follow this guideline when adding your own map, to your fork, for easy compatibility.
|
||||
|
||||
The map that will be loaded for the upcoming round is determined by reading data/next_map.json, which is a copy of the json files found in the _maps tree. If this file does not exist, the default map from config/maps.txt will be loaded. Failing that, BoxStation will be loaded. If you want to set a specific map to load next round you can use the Change Map verb in game before restarting the server or copy a json from _maps to data/next_map.json before starting the server. Also, for debugging purposes, ticking a corresponding map's code file in Dream Maker will force that map to load every round.
|
||||
|
||||
If you are hosting a server, and want randomly picked maps to be played each round, you can enable map rotation in [config.txt](config/config.txt) and then set the maps to be picked in the [maps.txt](config/maps.txt) file.
|
||||
|
||||
Anytime you want to make changes to a map it's imperative you use the [Map Merging tools](https://tgstation13.org/wiki/Map_Merger)
|
||||
|
||||
## SQL SETUP
|
||||
|
||||
The SQL backend requires a Mariadb server running 10.2 or later. Mysql is not supported but Mariadb is a drop in replacement for mysql. SQL is required for the library, stats tracking, admin notes, and job-only bans, among other features, mostly related to server administration. Your server details go in /config/dbconfig.txt, and the SQL schema is in /SQL/tgstation_schema.sql and /SQL/tgstation_schema_prefix.sql depending on if you want table prefixes. More detailed setup instructions are located here: https://www.tgstation13.org/wiki/Downloading_the_source_code#Setting_up_the_database
|
||||
|
||||
If you are hosting a testing server on windows you can use a standalone version of MariaDB pre load with a blank (but initialized) tgdb database. Find them here: https://tgstation13.download/database/ Just unzip and run for a working (but insecure) database server. Includes a zipped copy of the data folder for easy resetting back to square one.
|
||||
|
||||
## WEB/CDN RESOURCE DELIVERY
|
||||
|
||||
Web delivery of game resources makes it quicker for players to join and reduces some of the stress on the game server.
|
||||
|
||||
1. Edit compile_options.dm to set the `PRELOAD_RSC` define to `0`
|
||||
1. Add a url to config/external_rsc_urls pointing to a .zip file containing the .rsc.
|
||||
* If you keep up to date with /tg/ you could reuse /tg/'s rsc cdn at http://tgstation13.download/byond/tgstation.zip. Otherwise you can use cdn services like CDN77 or cloudflare (requires adding a page rule to enable caching of the zip), or roll your own cdn using route 53 and vps providers.
|
||||
* Regardless even offloading the rsc to a website without a CDN will be a massive improvement over the in game system for transferring files.
|
||||
|
||||
## IRC BOT SETUP
|
||||
|
||||
Included in the repository is a python3 compatible IRC bot capable of relaying adminhelps to a specified
|
||||
IRC channel/server, see the /tools/minibot folder for more
|
||||
525
.github/guides/STANDARDS.md
vendored
Normal file
525
.github/guides/STANDARDS.md
vendored
Normal file
@@ -0,0 +1,525 @@
|
||||
# Code Standards
|
||||
|
||||
These are our code standards. They include information about how to properly work with our code, rules about how to structure what you're writing, and some more general information about BYOND and Dream Maker.
|
||||
|
||||
As with the style guide, you are expected to follow these specifications in order to make everyone's lives easier. It'll save both your time and ours, by making sure you don't have to make any changes and we don't have to ask you to. Thank you for reading this file!
|
||||
|
||||
1. [General](#general)
|
||||
2. [Structural](#structural)
|
||||
3. [Optimization](#optimization)
|
||||
4. [BYOND Quirks](#dream-maker-quirks/tricks)
|
||||
5. [SQL](#sql)
|
||||
|
||||
## General
|
||||
|
||||
### 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.
|
||||
|
||||
### Avoid hacky code
|
||||
Hacky code, such as adding specific checks, is highly discouraged and only allowed when there is ***no*** other option. (Protip: "I couldn't immediately think of a proper way so thus there must be no other option" is not gonna cut it here! If you can't think of anything else, say that outright and admit that you need help with it. Maintainers 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.
|
||||
|
||||
### 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 sanitizeSQL to escape text based database entries from players or admins, and isnum() for number based database entries from players or admins.
|
||||
|
||||
* 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.
|
||||
|
||||
* It is recommended as well you do not expose information about the players - even something as simple as the number of people who have readied up at the start of the round can and has been used to try to identify the round type.
|
||||
|
||||
* 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.
|
||||
|
||||
### User Interfaces
|
||||
|
||||
* All new player-facing user interfaces must use TGUI, unless they are critical user interfaces.
|
||||
* All critical user interfaces must be usable with HTML or the interface.dmf, with tgui being *optional* for this UI.
|
||||
* Examples of critical user interfaces are the chat box, the observe button, the stat panel, and the chat input.
|
||||
* Documentation for TGUI can be found at:
|
||||
* [tgui/README.md](../../tgui/README.md)
|
||||
* [tgui/tutorial-and-examples.md](../../tgui/docs/tutorial-and-examples.md)
|
||||
|
||||
### Dont override type safety checks
|
||||
|
||||
The use of the : operator to override type safety checks is not allowed. You must cast the variable to the proper type.
|
||||
|
||||
### Do not 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
|
||||
//Good
|
||||
var/path_type = /obj/item/baseball_bat
|
||||
|
||||
//Bad
|
||||
var/path_type = "/obj/item/baseball_bat"
|
||||
```
|
||||
|
||||
### Other Notes
|
||||
|
||||
* Code should be modular where possible; if you are working on a new addition, then strongly consider putting it in its own file unless it makes sense to put it with similar ones (i.e. a new tool would go in the "tools.dm" file)
|
||||
|
||||
* Bloated code may be necessary to add a certain feature, which means there has to be a judgement over whether the feature is worth having or not. You can help make this decision easier by making sure your code is modular.
|
||||
|
||||
* You are expected to help maintain the code that you add, meaning that if there is a problem then you are likely to be approached in order to fix any issues, runtimes, or bugs.
|
||||
|
||||
* Do not divide when you can easily convert it to multiplication. (ie `4/2` should be done as `4*0.5`)
|
||||
|
||||
* Separating single lines into more readable blocks is not banned, however you should use it only where it makes new information more accessible, or aids maintainability. We do not have a column limit, and mass conversions will not be received well.
|
||||
|
||||
* If you used regex to replace code during development of your code, post the regex in your PR for the benefit of future developers and downstream users.
|
||||
|
||||
* Changes to the `/config` tree must be made in a way that allows for updating server deployments while preserving previous behaviour. This is due to the fact that the config tree is to be considered owned by the user and not necessarily updated alongside the remainder of the code. The code to preserve previous behaviour may be removed at some point in the future given the OK by maintainers.
|
||||
|
||||
* The dlls section of tgs3.json is not designed for dlls that are purely `call()()`ed since those handles are closed between world reboots. Only put in dlls that may have to exist between world reboots.
|
||||
|
||||
## Structural
|
||||
### No duplicated code (Don't repeat yourself)
|
||||
Copying code from one place to another may be suitable for small, short-time projects, but /tg/station 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.
|
||||
|
||||
### 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. See here for details on `Initialize`: https://github.com/yogstation13/Yogstation/blob/df044da8608d94cbe1fe242264f749a92ca8283b/code/game/atoms.dm#L111
|
||||
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.
|
||||
|
||||
### Files
|
||||
|
||||
* Because runtime errors do not give the full path, try to avoid having files with the same name across folders.
|
||||
|
||||
* File names should not be mixed case, or contain spaces or any character that would require escaping in a uri.
|
||||
|
||||
* Files and path accessed and referenced by code above simply being #included should be strictly lowercase to avoid issues on filesystems where case matters.
|
||||
|
||||
### RegisterSignal()
|
||||
|
||||
#### PROC_REF Macros
|
||||
When referencing procs in RegisterSignal, Callback and other procs you should use PROC_REF,TYPE_PROC_REF and GLOBAL_PROC_REF macros.
|
||||
They ensure compilation fails if the reffered to procs change names or get removed.
|
||||
The macro to be used depends on how the proc you're in relates to the proc you want to use:
|
||||
|
||||
PROC_REF if the proc you want to use is defined on the current proc type or any of it's ancestor types.
|
||||
Example:
|
||||
```
|
||||
/mob/proc/funny()
|
||||
to_chat(world,"knock knock")
|
||||
|
||||
/mob/subtype/proc/very_funny()
|
||||
to_chat(world,"who's there?")
|
||||
|
||||
/mob/subtype/proc/do_something()
|
||||
// Proc on our own type
|
||||
RegisterSignal(x, COMSIG_OTHER_FAKE, PROC_REF(very_funny))
|
||||
// Proc on ancestor type, /mob is parent type of /mob/subtype
|
||||
RegisterSignal(x, COMSIG_FAKE, PROC_REF(funny))
|
||||
```
|
||||
|
||||
TYPE_PROC_REF if the proc you want to use is defined on a different unrelated type
|
||||
Example:
|
||||
```
|
||||
/obj/thing/proc/funny()
|
||||
to_chat(world,"knock knock")
|
||||
|
||||
/mob/subtype/proc/do_something()
|
||||
var/obj/thing/x = new()
|
||||
// we're referring to /obj/thing proc inside /mob/subtype proc
|
||||
RegisterSignal(x, COMSIG_FAKE, TYPE_PROC_REF(/obj/thing, funny))
|
||||
```
|
||||
|
||||
GLOBAL_PROC_REF if the proc you want to use is a global proc.
|
||||
Example:
|
||||
```
|
||||
/proc/funny()
|
||||
to_chat(world,"knock knock")
|
||||
|
||||
/mob/subtype/proc/do_something()
|
||||
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(funny)), 100))
|
||||
```
|
||||
|
||||
|
||||
#### Signal Handlers
|
||||
|
||||
All procs that are registered to listen for signals using `RegisterSignal()` must contain at the start of the proc `SIGNAL_HANDLER` eg;
|
||||
```
|
||||
/type/path/proc/signal_callback()
|
||||
SIGNAL_HANDLER
|
||||
// rest of the code
|
||||
```
|
||||
This is to ensure that it is clear the proc handles signals and turns on a lint to ensure it does not sleep.
|
||||
|
||||
Any sleeping behaviour that you need to perform inside a `SIGNAL_HANDLER` proc must be called asynchronously (e.g. with `INVOKE_ASYNC()`) or be redone to work asynchronously.
|
||||
|
||||
#### `override`
|
||||
|
||||
Each atom can only register a signal on the same object once, or else you will get a runtime. Overriding signals is usually a bug, but if you are confident that it is not, you can silence this runtime with `override = TRUE`.
|
||||
|
||||
```dm
|
||||
RegisterSignal(fork, COMSIG_FORK_STAB, PROC_REF(on_fork_stab), override = TRUE)
|
||||
```
|
||||
|
||||
If you decide to do this, you should make it clear with a comment explaining why it is necessary. This helps us to understand that the signal override is not a bug, and may help us to remove it in the future if the assumptions change.
|
||||
|
||||
### Enforcing parent calling
|
||||
|
||||
When adding new signals to root level procs, eg;
|
||||
```
|
||||
/atom/proc/setDir(newdir)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir)
|
||||
dir = newdir
|
||||
```
|
||||
The `SHOULD_CALL_PARENT(TRUE)` lint should be added to ensure that overrides/child procs call the parent chain and ensure the signal is sent.
|
||||
|
||||
### Avoid unnecessary type checks and obscuring nulls in lists
|
||||
|
||||
Typecasting in `for` loops carries an implied `istype()` check that filters non-matching types, nulls included. The `as anything` key can be used to skip the check.
|
||||
|
||||
If we know the list is supposed to only contain the desired type then we want to skip the check not only for the small optimization it offers, but also to catch any null entries that may creep into the list.
|
||||
|
||||
Nulls in lists tend to point to improperly-handled references, making hard deletes hard to debug. Generating a runtime in those cases is more often than not positive.
|
||||
|
||||
This is bad:
|
||||
```DM
|
||||
var/list/bag_of_atoms = list(new /obj, new /mob, new /atom, new /atom/movable, new /atom/movable)
|
||||
var/highest_alpha = 0
|
||||
for(var/atom/thing in bag_of_atoms)
|
||||
if(thing.alpha <= highest_alpha)
|
||||
continue
|
||||
highest_alpha = thing.alpha
|
||||
```
|
||||
|
||||
This is good:
|
||||
```DM
|
||||
var/list/bag_of_atoms = list(new /obj, new /mob, new /atom, new /atom/movable, new /atom/movable)
|
||||
var/highest_alpha = 0
|
||||
for(var/atom/thing as anything in bag_of_atoms)
|
||||
if(thing.alpha <= highest_alpha)
|
||||
continue
|
||||
highest_alpha = thing.alpha
|
||||
```
|
||||
|
||||
### All `process` procs need to make use of delta-time and be frame independent
|
||||
|
||||
In a lot of our older code, `process()` is frame dependent. Here's some example mob code:
|
||||
|
||||
```DM
|
||||
/mob/testmob
|
||||
var/health = 100
|
||||
var/health_loss = 4 //We want to lose 2 health per second, so 4 per SSmobs process
|
||||
|
||||
/mob/testmob/process(delta_time) //SSmobs runs once every 2 seconds
|
||||
health -= health_loss
|
||||
```
|
||||
|
||||
As the mobs subsystem runs once every 2 seconds, the mob now loses 4 health every process, or 2 health per second. This is called frame dependent programming.
|
||||
|
||||
Why is this an issue? If someone decides to make it so the mobs subsystem processes once every second (2 times as fast), your effects in process() will also be two times as fast. Resulting in 4 health loss per second rather than 2.
|
||||
|
||||
How do we solve this? By using delta-time. Delta-time is the amount of seconds you would theoretically have between 2 process() calls. In the case of the mobs subsystem, this would be 2 (As there is 2 seconds between every call in `process()`). Here is a new example using delta-time:
|
||||
|
||||
```DM
|
||||
/mob/testmob
|
||||
var/health = 100
|
||||
var/health_loss = 2 //Health loss every second
|
||||
|
||||
/mob/testmob/process(delta_time) //SSmobs runs once every 2 seconds
|
||||
health -= health_loss * delta_time
|
||||
```
|
||||
|
||||
In the above example, we made our health_loss variable a per second value rather than per process. In the actual process() proc we then make use of deltatime. Because SSmobs runs once every 2 seconds. Delta_time would have a value of 2. This means that by doing health_loss * delta_time, you end up with the correct amount of health_loss per process, but if for some reason the SSmobs subsystem gets changed to be faster or slower in a PR, your health_loss variable will work the same.
|
||||
|
||||
For example, if SSmobs is set to run once every 4 seconds, it would call process once every 4 seconds and multiply your health_loss var by 4 before subtracting it. Ensuring that your code is frame independent.
|
||||
|
||||
## Optimization
|
||||
### 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.
|
||||
|
||||
### Icons are for image manipulation and defining an obj's `.icon` var, appearances are for everything else.
|
||||
|
||||
BYOND will allow you to use a raw icon file or even an icon datum for underlays, overlays, and what not (you can even use strings to refer to an icon state on the current icon). The issue is these get converted by BYOND to appearances on every overlay insert or removal involving them, and this process requires inserting the new appearance into the global list of appearances, and informing clients about them.
|
||||
|
||||
Converting them yourself to appearances and storing this converted value will ensure this process only has to happen once for the lifetime of the round. Helper functions exist to do most of the work for you.
|
||||
|
||||
|
||||
Bad:
|
||||
```dm
|
||||
/obj/machine/update_overlays(blah)
|
||||
if (stat & broken)
|
||||
add_overlay(icon(broken_icon)) //this icon gets created, passed to byond, converted to an appearance, then deleted.
|
||||
return
|
||||
if (is_on)
|
||||
add_overlay("on") //also bad, the converstion to an appearance still has to happen
|
||||
else
|
||||
add_overlay(iconstate2appearance(icon, "off")) //this might seem alright, but not storing the value just moves the repeated appearance generation to this proc rather then the core overlay management. It would only be acceptable (and to some degree perferred) if this overlay is only ever added once (like in init code)
|
||||
```
|
||||
|
||||
Good:
|
||||
```dm
|
||||
/obj/machine/update_overlays(var/blah)
|
||||
var/static/on_overlay
|
||||
var/static/off_overlay
|
||||
var/static/broken_overlay
|
||||
if(isnull(on_overlay)) //static vars initialize with global variables, meaning src is null and this won't pass integration tests unless you check.
|
||||
on_overlay = iconstate2appearance(icon, "on")
|
||||
off_overlay = iconstate2appearance(icon, "off")
|
||||
broken_overlay = icon2appearance(broken_icon)
|
||||
if (stat & broken)
|
||||
add_overlay(broken_overlay)
|
||||
return
|
||||
if (is_on)
|
||||
add_overlay(on_overlay)
|
||||
else
|
||||
add_overlay(off_overlay)
|
||||
...
|
||||
```
|
||||
|
||||
Note: images are appearances with extra steps, and don't incur the overhead in conversion.
|
||||
|
||||
|
||||
### Do not abuse associated lists.
|
||||
|
||||
Associated lists that could instead be variables or statically defined number indexed lists will use more memory, as associated lists have a 24 bytes per item overhead (vs 8 for lists and most vars), and are slower to search compared to static/global variables and lists with known indexes.
|
||||
|
||||
|
||||
Bad:
|
||||
```dm
|
||||
/obj/machine/update_overlays(var/blah)
|
||||
var/static/our_overlays
|
||||
if (isnull(our_overlays))
|
||||
our_overlays = list("on" = iconstate2appearance(overlay_icon, "on"), "off" = iconstate2appearance(overlay_icon, "off"), "broken" = iconstate2appearance(overlay_icon, "broken"))
|
||||
if (stat & broken)
|
||||
add_overlay(our_overlays["broken"])
|
||||
return
|
||||
...
|
||||
```
|
||||
|
||||
Good:
|
||||
```dm
|
||||
#define OUR_ON_OVERLAY 1
|
||||
#define OUR_OFF_OVERLAY 2
|
||||
#define OUR_BROKEN_OVERLAY 3
|
||||
|
||||
/obj/machine/update_overlays(var/blah)
|
||||
var/static/our_overlays
|
||||
if (isnull(our_overlays))
|
||||
our_overlays = list(iconstate2appearance(overlay_icon, "on"), iconstate2appearance(overlay_icon, "off"), iconstate2appearance(overlay_icon, "broken"))
|
||||
if (stat & broken)
|
||||
add_overlay(our_overlays[OUR_BROKEN_OVERLAY])
|
||||
return
|
||||
...
|
||||
|
||||
#undef OUR_ON_OVERLAY
|
||||
#undef OUR_OFF_OVERLAY
|
||||
#undef OUR_BROKEN_OVERLAY
|
||||
```
|
||||
Storing these in a flat (non-associated) list saves on memory, and using defines to reference locations in the list saves CPU time searching the list.
|
||||
|
||||
Also good:
|
||||
```dm
|
||||
/obj/machine/update_overlays(var/blah)
|
||||
var/static/on_overlay
|
||||
var/static/off_overlay
|
||||
var/static/broken_overlay
|
||||
if(isnull(on_overlay))
|
||||
on_overlay = iconstate2appearance(overlay_icon, "on")
|
||||
off_overlay = iconstate2appearance(overlay_icon, "off")
|
||||
broken_overlay = iconstate2appearance(overlay_icon, "broken")
|
||||
if (stat & broken)
|
||||
add_overlay(broken_overlay)
|
||||
return
|
||||
...
|
||||
```
|
||||
Proc variables, static variables, and global variables are resolved at compile time, so the above is equivalent to the second example, but is easier to read, and avoids the need to store a list.
|
||||
|
||||
Note: While there has historically been a strong impulse to use associated lists for caching of computed values, this is the easy way out and leaves a lot of hidden overhead. Please keep this in mind when designing core/root systems that are intended for use by other code/coders. It's normally better for consumers of such systems to handle their own caching using vars and number indexed lists, than for you to do it using associated lists.
|
||||
|
||||
## Dream Maker Quirks/Tricks
|
||||
|
||||
Like all languages, Dream Maker has its quirks, some of them are beneficial to us, some are harmful.
|
||||
|
||||
### Loops
|
||||
#### 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)` versus `for(var/i in 1 to list.len)`
|
||||
|
||||
The former is faster than the latter, as shown by the following profile results:
|
||||
https://file.house/zy7H.png
|
||||
Code used for the test in a readable format:
|
||||
https://pastebin.com/w50uERkG
|
||||
|
||||
### Dot variable (`.`)
|
||||
|
||||
The `.` variable is present in all procs. It refers to the value returned by a proc.
|
||||
|
||||
```dm
|
||||
/proc/return_six()
|
||||
. = 3
|
||||
. *= 2
|
||||
|
||||
// ...is equivalent to...
|
||||
/proc/return_six()
|
||||
var/output = 3
|
||||
output *= 2
|
||||
return output
|
||||
```
|
||||
|
||||
At its best, it can make some very common patterns easy to use, and harder to mess up. However, at its worst, it can make it significantly harder to understand what a proc does.
|
||||
|
||||
```dm
|
||||
/proc/complex_proc()
|
||||
if (do_something())
|
||||
some_code()
|
||||
if (do_something_else())
|
||||
. = TRUE // Uh oh, what's going on!
|
||||
|
||||
// even
|
||||
// more
|
||||
// code
|
||||
if (bad_condition())
|
||||
return // This actually will return something set from earlier!
|
||||
```
|
||||
|
||||
This sort of behavior can create some nasty to debug errors with things returning when you don't expect them to. Would you see `return` and it expect it to return a value, without reading all the code before it? Furthermore, a simple `return` statement cannot easily be checked by the LSP, meaning you can't easily check what is actually being returned. Basically, `return output` lets you go to where `output` is defined/set. `return` does not.
|
||||
|
||||
Even in simple cases, this can create some just generally hard to read code, seemingly in the pursuit of being clever.
|
||||
|
||||
```dm
|
||||
/client/p_were(gender)
|
||||
. = "was"
|
||||
if (gender == PLURAL || gender == NEUTER)
|
||||
. = "were"
|
||||
```
|
||||
|
||||
Because of these problems, it is encouraged to prefer standard, explicit return statements. The above code would be best written as:
|
||||
|
||||
```dm
|
||||
/client/p_were(gender)
|
||||
if (gender == PLURAL || gender == NEUTER)
|
||||
return "were"
|
||||
else
|
||||
return "was"
|
||||
```
|
||||
|
||||
#### Exception: `. = ..()`
|
||||
|
||||
As hinted at before, `. = ..()` is *extremely* common. This will call the parent function, and preserve its return type. Code like this:
|
||||
|
||||
```dm
|
||||
/obj/item/spoon/attack()
|
||||
. = ..()
|
||||
visible_message("Whack!")
|
||||
```
|
||||
|
||||
...is completely accepted, and in fact, usually *prefered* over:
|
||||
|
||||
```dm
|
||||
/obj/item/spoon/attack()
|
||||
var/output = ..()
|
||||
visible_message("Whack!")
|
||||
return output
|
||||
```
|
||||
|
||||
#### Exception: Runtime resilience
|
||||
|
||||
One unique property of DM is the ability for procs to error, but for code to continue. For instance, the following:
|
||||
|
||||
```dm
|
||||
/proc/uh_oh()
|
||||
CRASH("oh no!")
|
||||
|
||||
/proc/main()
|
||||
to_chat(world, "1")
|
||||
uh_oh()
|
||||
to_chat(world, "2")
|
||||
```
|
||||
|
||||
...would print both 1 *and* 2, which may be unexpected if you come from other languages.
|
||||
|
||||
This is where `.` provides a new useful behavior--**a proc that runtimes will return `.`**.
|
||||
|
||||
Meaning:
|
||||
|
||||
```dm
|
||||
/proc/uh_oh()
|
||||
. = "woah!"
|
||||
CRASH("oh no!")
|
||||
|
||||
/proc/main()
|
||||
to_chat(world, uh_oh())
|
||||
```
|
||||
|
||||
...will print `woah!`.
|
||||
|
||||
For this reason, it is acceptable for `.` to be used in places where consumers can reasonably continue in the event of a runtime.
|
||||
|
||||
If you are using `.` in this case (or for another case that might be acceptable, other than most uses of `. = ..()`), it is still prefered that you explicitly `return .` in order to prevent both editor issues and readability/error-prone issues.
|
||||
|
||||
```dm
|
||||
/proc/uh_oh()
|
||||
. = "woah!"
|
||||
|
||||
if (do_something())
|
||||
call_code()
|
||||
if (!working_fine())
|
||||
return . // Instead of `return`, we explicitly `return .`
|
||||
|
||||
if (some_fail_state())
|
||||
CRASH("youch!")
|
||||
|
||||
return . // `return .` is used at the end, to signify it has been used
|
||||
```
|
||||
|
||||
```dm
|
||||
/obj/item/spoon/super_attack()
|
||||
. = ..()
|
||||
if (. == BIGGER_SUPER_ATTACK)
|
||||
return BIGGER_SUPER_ATTACK // More readable than `.`
|
||||
|
||||
// Due to how common it is, most uses of `. = ..()` do not need a trailing `return .`
|
||||
```
|
||||
|
||||
### BYOND hellspawn
|
||||
|
||||
What follows is documentation of inconsistent or strange behavior found in our engine, BYOND.
|
||||
It's listed here in the hope that it will prevent fruitless debugging in future.
|
||||
|
||||
#### Icon hell
|
||||
|
||||
Due to how they are internally represented as part of appearance, overlays and underlays which have an icon_state named the same as an icon_state on the parent object will use the parent's icon_state and look completely wrong. This has caused two bugs with underlay lighting whenever a turf had the icon_state of "transparent" or "dark" and their lighting objects also had those states - because when the lighting underlays were in those modes they would be rendered by the client to look like the icons the floor used. When adding something as an overlay or an underlay make sure it can't match icon_state names with whatever you're adding it to.
|
||||
|
||||
## 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.
|
||||
|
||||
* All changes to the database's layout(schema) must be specified in the database changelog in SQL, as well as reflected in the schema files
|
||||
|
||||
* Any time the schema is changed the `schema_revision` table and `DB_MAJOR_VERSION` or `DB_MINOR_VERSION` defines must be incremented.
|
||||
|
||||
* Queries must never specify the database, be it in code, or in text files in the repo.
|
||||
|
||||
* Primary keys are inherently immutable and you must never do anything to change the primary key of a row or entity. This includes preserving auto increment numbers of rows when copying data to a table in a conversion script. No amount of bitching about gaps in ids or out of order ids will save you from this policy.
|
||||
|
||||
* The ttl for data from the database is 10 seconds. You must have a compelling reason to store and reuse data for longer then this.
|
||||
|
||||
* Do not write stored and transformed data to the database, instead, apply the transformation to the data in the database directly.
|
||||
* ie: SELECTing a number from the database, doubling it, then updating the database with the doubled number. If the data in the database changed between step 1 and 3, you'll get an incorrect result. Instead, directly double it in the update query. `UPDATE table SET num = num*2` instead of `UPDATE table SET num = [num]`.
|
||||
* if the transformation is user provided (such as allowing a user to edit a string), you should confirm the value being updated did not change in the database in the intervening time before writing the new user provided data by checking the old value with the current value in the database, and if it has changed, allow the user to decide what to do next.
|
||||
810
.github/guides/STYLE.md
vendored
Normal file
810
.github/guides/STYLE.md
vendored
Normal file
@@ -0,0 +1,810 @@
|
||||
# Style Guide
|
||||
This is the style you must follow when writing code. It's important to note that large parts of the codebase do not consistently follow these rules, but this does not free you of the requirement to follow them.
|
||||
|
||||
1. [General Guidelines](#general-guidelines)
|
||||
2. [Paths and Inheritence](#paths-and-inheritence)
|
||||
3. [Variables](#variables)
|
||||
4. [Procs](#procs)
|
||||
5. [Macros](#macros)
|
||||
6. [Things that do not matter](#things-that-do-not-matter)
|
||||
|
||||
## General Guidelines
|
||||
|
||||
### Tabs, not spaces
|
||||
You must use tabs to indent your code, NOT SPACES.
|
||||
|
||||
Do not use tabs/spaces for indentation in the middle of a code line. Not only is this inconsistent because the size of a tab is undefined, but it means that, should the line you're aligning to change size at all, we have to adjust a ton of other code. Plus, it often time hurts readability.
|
||||
|
||||
```dm
|
||||
// Bad
|
||||
#define SPECIES_MOTH "moth"
|
||||
#define SPECIES_LIZARDMAN "lizardman"
|
||||
#define SPECIES_FELINID "felinid"
|
||||
|
||||
// Good
|
||||
#define SPECIES_MOTH "moth"
|
||||
#define SPECIES_LIZARDMAN "lizardman"
|
||||
#define SPECIES_FELINID "felinid"
|
||||
```
|
||||
|
||||
### Control statements
|
||||
(if, while, for, etc)
|
||||
|
||||
* No control statement may contain code on the same line as the statement (`if (blah) return`)
|
||||
* All control statements comparing a variable to a number should use the formula of `thing` `operator` `number`, not the reverse (eg: `if (count <= 10)` not `if (10 >= count)`)
|
||||
|
||||
### Operators
|
||||
#### Spacing
|
||||
* Operators that should be separated by spaces
|
||||
* Boolean and logic operators like &&, || <, >, ==, etc (but not !)
|
||||
* Bitwise AND &
|
||||
* Argument separator operators like , (and ; when used in a forloop)
|
||||
* Assignment operators like = or += or the like
|
||||
* Operators that should not be separated by spaces
|
||||
* Bitwise OR |
|
||||
* Access operators like . and :
|
||||
* Parentheses ()
|
||||
* logical not !
|
||||
|
||||
Math operators like +, -, /, *, etc are up in the air, just choose which version looks more readable.
|
||||
|
||||
#### Use
|
||||
* Bitwise AND - '&'
|
||||
* Should be written as `variable & CONSTANT` NEVER `CONSTANT & variable`. Both are valid, but the latter is confusing and nonstandard.
|
||||
* Associated lists declarations must have their key value quoted if it's a string
|
||||
* WRONG: `list(a = "b")`
|
||||
* RIGHT: `list("a" = "b")`
|
||||
|
||||
### Use static instead of global
|
||||
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.
|
||||
|
||||
### Use early returns
|
||||
Do not enclose a proc in an if-block when returning on a condition is more feasible
|
||||
This is bad:
|
||||
````DM
|
||||
/datum/datum1/proc/proc1()
|
||||
if (thing1)
|
||||
if (!thing2)
|
||||
if (thing3 == 30)
|
||||
do stuff
|
||||
````
|
||||
This is good:
|
||||
````DM
|
||||
/datum/datum1/proc/proc1()
|
||||
if (!thing1)
|
||||
return
|
||||
if (thing2)
|
||||
return
|
||||
if (thing3 != 30)
|
||||
return
|
||||
do stuff
|
||||
````
|
||||
This prevents nesting levels from getting deeper then they need to be.
|
||||
|
||||
### 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
|
||||
/datum/proc/do_the_thing(thing_to_do)
|
||||
switch(thing_to_do)
|
||||
if(1)
|
||||
(...)
|
||||
if(2)
|
||||
(...)
|
||||
````
|
||||
There's no indication of what "1" and "2" mean! Instead, you'd do something like this:
|
||||
````DM
|
||||
#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)
|
||||
(...)
|
||||
if(DO_THE_THING_EFFICIENTLY)
|
||||
(...)
|
||||
````
|
||||
This is clearer and enhances readability of your code! Get used to doing it!
|
||||
|
||||
### Use our time defines
|
||||
|
||||
The codebase contains some defines which will automatically multiply a number by the correct amount to get a number in deciseconds. Using these is preffered over using a literal amount in deciseconds.
|
||||
|
||||
The defines are as follows:
|
||||
* SECONDS
|
||||
* MINUTES
|
||||
* HOURS
|
||||
|
||||
This is bad:
|
||||
````DM
|
||||
/datum/datum1/proc/proc1()
|
||||
if(do_after(mob, 15))
|
||||
mob.dothing()
|
||||
````
|
||||
|
||||
This is good:
|
||||
````DM
|
||||
/datum/datum1/proc/proc1()
|
||||
if(do_after(mob, 1.5 SECONDS))
|
||||
mob.dothing()
|
||||
````
|
||||
|
||||
## Paths and Inheritence
|
||||
### All BYOND paths must contain the full path
|
||||
(i.e. absolute pathing)
|
||||
|
||||
DM will allow you nest almost any type keyword into a block, such as:
|
||||
|
||||
```DM
|
||||
// Not our style!
|
||||
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 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
|
||||
// Matches /tg/station style.
|
||||
/datum/datum1
|
||||
var/varname1
|
||||
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
|
||||
```
|
||||
|
||||
### Type paths must begin with a `/`
|
||||
eg: `/datum/thing`, not `datum/thing`
|
||||
|
||||
### Type paths must be snake case
|
||||
eg: `/datum/blue_bird`, not `/datum/BLUEBIRD` or `/datum/BlueBird` or `/datum/Bluebird` or `/datum/blueBird`
|
||||
|
||||
### Datum type paths must began with "datum"
|
||||
In DM, this is optional, but omitting it makes finding definitions harder.
|
||||
|
||||
## Variables
|
||||
|
||||
### Use `var/name` format when declaring variables
|
||||
While DM allows other ways of declaring variables, this one should be used for consistency.
|
||||
|
||||
### Use descriptive and obvious names
|
||||
Optimize for readability, not writability. While it is certainly easier to write `M` than `victim`, it will cause issues down the line for other developers to figure out what exactly your code is doing, even if you think the variable's purpose is obvious.
|
||||
|
||||
### Don't use abbreviations
|
||||
Avoid variables like C, M, and H. Prefer names like "user", "victim", "weapon", etc.
|
||||
|
||||
```dm
|
||||
// What is M? The user? The target?
|
||||
// What is A? The target? The item?
|
||||
/proc/use_item(mob/M, atom/A)
|
||||
|
||||
// Much better!
|
||||
/proc/use_item(mob/user, atom/target)
|
||||
```
|
||||
|
||||
Unless it is otherwise obvious, try to avoid just extending variables like "C" to "carbon"--this is slightly more helpful, but does not describe the *context* of the use of the variable.
|
||||
|
||||
### Naming things when typecasting
|
||||
When typecasting, keep your names descriptive:
|
||||
```dm
|
||||
var/mob/living/living_target = target
|
||||
var/mob/living/carbon/carbon_target = living_target
|
||||
```
|
||||
|
||||
Of course, if you have a variable name that better describes the situation when typecasting, feel free to use it.
|
||||
|
||||
Note that it's okay, semantically, to use the same variable name as the type, e.g.:
|
||||
```dm
|
||||
var/atom/atom
|
||||
var/client/client
|
||||
var/mob/mob
|
||||
```
|
||||
|
||||
Your editor may highlight the variable names, but BYOND, and we, accept these as variable names:
|
||||
|
||||
```dm
|
||||
// This functions properly!
|
||||
var/client/client = CLIENT_FROM_VAR(usr)
|
||||
// vvv this may be highlighted, but it's fine!
|
||||
client << browse(...)
|
||||
```
|
||||
|
||||
### Name things as directly as possible
|
||||
`was_called` is better than `has_been_called`. `notify` is better than `do_notification`.
|
||||
|
||||
### Avoid negative variable names
|
||||
`is_flying` is better than `is_not_flying`. `late` is better than `not_on_time`.
|
||||
This prevents double-negatives (such as `if (!is_not_flying)` which can make complex checks more difficult to parse.
|
||||
|
||||
### Exceptions to variable names
|
||||
|
||||
Exceptions can be made in the case of inheriting existing procs, as it makes it so you can use named parameters, but *new* variable names must follow these standards. It is also welcome, and encouraged, to refactor existing procs to use clearer variable names.
|
||||
|
||||
Naming numeral iterator variables `i` is also allowed, but do remember to [Avoid unnecessary type checks and obscuring nulls in lists](./STANDARDS.md#avoid-unnecessary-type-checks-and-obscuring-nulls-in-lists), and making more descriptive variables is always encouraged.
|
||||
|
||||
```dm
|
||||
// Bad
|
||||
for (var/datum/reagent/R as anything in reagents)
|
||||
|
||||
// Good
|
||||
for (var/datum/reagent/deadly_reagent as anything in reagents)
|
||||
|
||||
// Allowed, but still has the potential to not be clear. What does `i` refer to?
|
||||
for (var/i in 1 to 12)
|
||||
|
||||
// Better
|
||||
for (var/month in 1 to 12)
|
||||
|
||||
// Bad, only use `i` for numeral loops
|
||||
for (var/i in reagents)
|
||||
```
|
||||
|
||||
### Don't abuse the increment/decrement operators
|
||||
`x++` and `++x` both will increment x, but the former will return x *before* it was incremented, while the latter will return x *after* it was incremented. Great if you want to be clever, or if you were a C programmer in the 70s, but it hurts the readability of code to anyone who isn't familiar with this. The convenience is not nearly good enough to justify this burden.
|
||||
|
||||
```dm
|
||||
// Bad
|
||||
world.log << "You now have [++apples] apples."
|
||||
|
||||
// Good
|
||||
apples++
|
||||
// apples += 1 - Allowed
|
||||
world.log << "You now have [apples] apples."
|
||||
|
||||
// Bad
|
||||
world.log << "[apples--] apples left, taking one."
|
||||
|
||||
// Good
|
||||
world.log << "[apples] apples left, taking one."
|
||||
apples--
|
||||
```
|
||||
|
||||
## Procs
|
||||
|
||||
### Getters and setters
|
||||
|
||||
* Avoid getter procs. They are useful tools in languages with that properly enforce variable privacy and encapsulation, but DM is not one of them. The upfront cost in proc overhead is met with no benefits, and it may tempt to develop worse code.
|
||||
|
||||
This is bad:
|
||||
```DM
|
||||
/datum/datum1/proc/simple_getter()
|
||||
return gotten_variable
|
||||
```
|
||||
Prefer to either access the variable directly or use a macro/define.
|
||||
|
||||
|
||||
* Make usage of variables or traits, set up through condition setters, for a more maintainable alternative to compex and redefined getters.
|
||||
|
||||
These are bad:
|
||||
```DM
|
||||
/datum/datum1/proc/complex_getter()
|
||||
return condition ? VALUE_A : VALUE_B
|
||||
|
||||
/datum/datum1/child_datum/complex_getter()
|
||||
return condition ? VALUE_C : VALUE_D
|
||||
```
|
||||
|
||||
This is good:
|
||||
```DM
|
||||
/datum/datum1
|
||||
var/getter_turned_into_variable
|
||||
|
||||
/datum/datum1/proc/set_condition(new_value)
|
||||
if(condition == new_value)
|
||||
return
|
||||
condition = new_value
|
||||
on_condition_change()
|
||||
|
||||
/datum/datum1/proc/on_condition_change()
|
||||
getter_turned_into_variable = condition ? VALUE_A : VALUE_B
|
||||
|
||||
/datum/datum1/child_datum/on_condition_change()
|
||||
getter_turned_into_variable = condition ? VALUE_C : VALUE_D
|
||||
```
|
||||
|
||||
### When passing vars through New() or Initialize()'s arguments, use src.var
|
||||
Using src.var + naming the arguments the same as the var is the most readable and intuitive way to pass arguments into a new instance's vars. The main benefit is that you do not need to give arguments odd names with prefixes and suffixes that are easily forgotten in `new()` when sending named args.
|
||||
|
||||
This is very bad:
|
||||
```DM
|
||||
/atom/thing
|
||||
var/is_red
|
||||
|
||||
/atom/thing/Initialize(mapload, enable_red)
|
||||
is_red = enable_red
|
||||
|
||||
/proc/make_red_thing()
|
||||
new /atom/thing(null, enable_red = TRUE)
|
||||
```
|
||||
|
||||
Future coders using this code will have to remember two differently named variables which are near-synonyms of eachother. One of them is only used in Initialize for one line.
|
||||
|
||||
This is bad:
|
||||
```DM
|
||||
/atom/thing
|
||||
var/is_red
|
||||
|
||||
/atom/thing/Initialize(mapload, _is_red)
|
||||
is_red = _is_red
|
||||
|
||||
/proc/make_red_thing()
|
||||
new /atom/thing(null, _is_red = TRUE)
|
||||
```
|
||||
|
||||
`_is_red` is being used to set `is_red` and yet means a random '_' needs to be appended to the front of the arg, same as all other args like this.
|
||||
|
||||
This is good:
|
||||
```DM
|
||||
/atom/thing
|
||||
var/is_red
|
||||
|
||||
/atom/thing/Initialize(mapload, is_red)
|
||||
src.is_red = is_red
|
||||
|
||||
/proc/make_red_thing()
|
||||
new /atom/thing(null, is_red = TRUE)
|
||||
```
|
||||
|
||||
Setting `is_red` in args is simple, and directly names the variable the argument sets.
|
||||
|
||||
### Prefer named arguments when the meaning is not obvious.
|
||||
|
||||
Pop-quiz, what does this do?
|
||||
|
||||
```dm
|
||||
give_pizza(TRUE, 2)
|
||||
```
|
||||
|
||||
Well, obviously the `TRUE` makes the pizza hot, and `2` is the number of toppings.
|
||||
|
||||
Code like this can be very difficult to read, especially since our LSP does not show argument names at this time. Because of this, you should prefer to use named arguments where the meaning is not otherwise obvious.
|
||||
|
||||
```dm
|
||||
give_pizza(hot = TRUE, toppings = 2)
|
||||
```
|
||||
|
||||
What is "obvious" is subjective--for instance, `give_pizza(PIZZA_HOT, toppings = 2)` is completely acceptable.
|
||||
|
||||
Other examples:
|
||||
|
||||
```dm
|
||||
deal_damage(10) // Fine! The proc name makes it obvious `10` is the damage...at least it better be.
|
||||
deal_damage(10, FIRE) // Also fine! `FIRE` makes it obvious the second parameter is damage type.
|
||||
deal_damage(damage = 10) // Redundant, but not prohibited.
|
||||
|
||||
use_power(30) // Fine! `30` is obviously something like watts.
|
||||
turn_on(30) // Not fine!
|
||||
turn_on(power_usage = 30) // Fine!
|
||||
|
||||
set_invincible(FALSE) // Fine! Boolean parameters don't always need to be named. In this case, it is obvious what it means.
|
||||
```
|
||||
|
||||
## Multi-lining
|
||||
|
||||
Whether it's a very long proc call, a long list people will be adding to, or something else entirely, there may be times where splitting code across multiple lines is the most readable. When you have to is up to maintainer discretion, but if you do, follow this consistent style.
|
||||
|
||||
```dm
|
||||
proc_call_on_one_line(
|
||||
arg1, // Only indent once! Remember to not align tabs.
|
||||
arg2,
|
||||
arg3, // End with a trailing comma
|
||||
) // The parenthesis should be on the same indentation level as the proc call
|
||||
```
|
||||
|
||||
For example:
|
||||
```dm
|
||||
/area/town
|
||||
var/list/places_to_visit = list(
|
||||
"Coffee Shop",
|
||||
"Dance Club",
|
||||
"Gift Shop",
|
||||
)
|
||||
```
|
||||
|
||||
This is not a strict rule and there may be times where you can place the lines in a more sensible spot. For example:
|
||||
|
||||
```dm
|
||||
act(list(
|
||||
// Fine!
|
||||
))
|
||||
|
||||
act(
|
||||
list(
|
||||
// Fine, though verbose
|
||||
)
|
||||
)
|
||||
|
||||
act(x, list(
|
||||
// Also fine!
|
||||
))
|
||||
|
||||
act(x, list(
|
||||
|
||||
), y) // Getting clunky, might want to split this up!
|
||||
```
|
||||
|
||||
Occasionally, you will need to use backslashes to multiline. This happens when you are calling a macro. This comes up often with `AddComponent`. For example,
|
||||
|
||||
```dm
|
||||
AddComponent( \
|
||||
/datum/component/makes_sound, \
|
||||
"chirp", \
|
||||
volume = 10, \
|
||||
)
|
||||
```
|
||||
|
||||
Backslashes should only be used when necessary, and they are only necessary for macros.
|
||||
|
||||
## Macros
|
||||
|
||||
Macros are, in essence, direct copy and pastes into the code. They are one of the few zero cost abstractions we have in DM, and you will see them often. Macros have strange syntax requirements, so if you see lots of backslashes and semicolons and braces that you wouldn't normally see, that is why.
|
||||
|
||||
This section will assume you understand the following concepts:
|
||||
|
||||
### Language - Hygienic
|
||||
We say a macro is [**hygienic**](https://en.wikipedia.org/wiki/Hygienic_macro) if, generally, it does not rely on input not given to it directly through the call site, and does not affect the call site outside of it in a way that could not be easily reused somewhere else.
|
||||
|
||||
An example of a non-hygienic macro is:
|
||||
|
||||
```dm
|
||||
#define GET_HEALTH(health_percent) ((##health_percent) * max_health)
|
||||
```
|
||||
|
||||
In here, we rely on the external `max_health` value.
|
||||
|
||||
Here are two examples of non-hygienic macros, because it affects its call site:
|
||||
|
||||
```dm
|
||||
#define DECLARE_MOTH(name) var/mob/living/moth/moth = new(##name)
|
||||
#define RETURN_IF(condition) if (condition) { return; }
|
||||
```
|
||||
|
||||
### Language - Side effects/Pure
|
||||
We say something has [**side effects**](https://en.wikipedia.org/wiki/Side_effect_(computer_science)) if it mutates anything outside of itself. We say something is **pure** if it does not.
|
||||
|
||||
For example, this has no side effects, and is pure:
|
||||
```dm
|
||||
#define MOTH_MAX_HEALTH 500
|
||||
```
|
||||
|
||||
This, however, performs a side effect of updating the health:
|
||||
```dm
|
||||
#define MOTH_SET_HEALTH(moth, new_health) ##moth.set_health(##new_health)
|
||||
```
|
||||
|
||||
Now that you're caught up on the terms, let's get into the guidelines.
|
||||
|
||||
### Naming
|
||||
With little exception, macros should be SCREAMING_SNAKE_CASE.
|
||||
|
||||
### Put macro segments inside parentheses where possible.
|
||||
This will save you from bugs down the line with operator precedence.
|
||||
|
||||
For example, the following macro:
|
||||
|
||||
```dm
|
||||
#define MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION T20C + 10
|
||||
```
|
||||
|
||||
...will break when order of operations comes into play:
|
||||
|
||||
```dm
|
||||
var/temperature = MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION * 50
|
||||
|
||||
// ...is preprocessed as...
|
||||
var/temperature = T20C + 10 * 50 // Oh no! T20C + 500!
|
||||
```
|
||||
|
||||
This is [a real bug that tends to come up](https://github.com/tgstation/tgstation/pull/37116), so to prevent it, we defensively wrap macro bodies with parentheses where possible.
|
||||
|
||||
```dm
|
||||
// Phew!
|
||||
#define MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION (T20C + 10)
|
||||
```
|
||||
|
||||
The same goes for arguments passed to a macro...
|
||||
|
||||
```
|
||||
// Guarantee
|
||||
#define CALCULATE_TEMPERATURE(base) (T20C + (##base))
|
||||
```
|
||||
|
||||
### Be hygienic where reasonably possible
|
||||
|
||||
Consider the previously mentioned non-hygienic macro:
|
||||
|
||||
```dm
|
||||
#define GET_HEALTH(health_percent) ((##health_percent) * max_health)
|
||||
```
|
||||
|
||||
This relies on "max_health", but it is not obviously clear what the source is. This will also become worse if we *do* want to change where we get the source from. This would be preferential as:
|
||||
|
||||
```dm
|
||||
#define GET_HEALTH(source, health_percent) ((##health_percent) * (##source).max_health)
|
||||
```
|
||||
|
||||
When a macro can't be hygienic, such as in the case where a macro is preferred to do something like define a variable, it should still do its best to rely only on input given to it:
|
||||
|
||||
```dm
|
||||
#define DECLARE_MOTH(name) var/mob/living/moth/moth = new(##name)
|
||||
```
|
||||
|
||||
...would ideally be written as...
|
||||
|
||||
```dm
|
||||
#define DECLARE_MOTH(var_name, name) var/mob/living/moth/##var_name = new(##name)
|
||||
```
|
||||
|
||||
As usual, exceptions exist--for instance, accessing a global like a subsystem within a macro is generally acceptable.
|
||||
|
||||
### Preserve hygiene using double underscores (`__`) and `do...while (FALSE)`
|
||||
|
||||
Some macros will want to create variables for themselves, and not the consumer. For instance, consider this macro:
|
||||
|
||||
```dm
|
||||
#define HOW_LONG(proc_to_call) \
|
||||
var/current_time = world.time; \
|
||||
##proc_to_call(); \
|
||||
world.log << "That took [world.time - current_time] deciseconds to complete.";
|
||||
```
|
||||
|
||||
There are two problems here.
|
||||
|
||||
One is that it is unhygienic. The `current_time` variable is leaking into the call site.
|
||||
|
||||
The second is that this will create weird errors if `current_time` is a variable that already exists, for instance:
|
||||
|
||||
```dm
|
||||
var/current_time = world.time
|
||||
|
||||
HOW_LONG(make_soup) // This will error!
|
||||
```
|
||||
|
||||
If this seems unlikely to you, then also consider that this:
|
||||
|
||||
```dm
|
||||
HOW_LONG(make_soup)
|
||||
HOW_LONG(eat_soup)
|
||||
```
|
||||
|
||||
...will also error, since they are both declaring the same variable!
|
||||
|
||||
There is a way to solve both of these, and it is through both the `do...while (FALSE)` pattern and by using `__` for variable names.
|
||||
|
||||
This code would change to look like:
|
||||
|
||||
```dm
|
||||
#define HOW_LONG(proc_to_call) \
|
||||
do { \
|
||||
var/__current_time = world.time; \
|
||||
##proc_to_call(); \
|
||||
world.log << "That took [world.time - current_time] deciseconds to complete."; \
|
||||
} while (FALSE)
|
||||
```
|
||||
|
||||
The point of the `do...while (FALSE)` here is to **create another scope**. It is impossible for `__current_time` to be used outside of the define itself. If you haven't seen `do...while` syntax before, it is just saying "do this while the condition is true", and by passing `FALSE`, we ensure it will only run once.
|
||||
|
||||
### Keep anything you use more than once in variables
|
||||
|
||||
Remember that macros are just pastes. This means that, if you're not thinking, you can end up [creating some really weird macros by reusing variables](https://github.com/tgstation/tgstation/pull/55074).
|
||||
|
||||
```dm
|
||||
#define TEST_ASSERT_EQUAL(a, b) \
|
||||
if ((##a) != (##b)) { \
|
||||
return Fail("Expected [##a] to be equal to [##b]."); \
|
||||
}
|
||||
```
|
||||
|
||||
This code may look benign, but consider the following code:
|
||||
|
||||
```dm
|
||||
/// Deal damage to the mob, and return their new health
|
||||
/mob/living/proc/attack_mob(damage)
|
||||
health -= damage
|
||||
say("Ouch!")
|
||||
return health
|
||||
|
||||
// Later, in a test, assuming mobs start at 100 health
|
||||
TEST_ASSERT_EQUAL(victim.attack_mob(20), 60)
|
||||
```
|
||||
|
||||
We're only dealing 20 damage to the mob, so it'll have 80 health left. But the test will fail, and report `Expected 60 to be equal to 60.`
|
||||
|
||||
Uh oh! That's because this compiled to:
|
||||
|
||||
```dm
|
||||
if ((victim.attack_mob(20)) != 60)
|
||||
return Fail("Expected [victim.attack_mob(20)] to be equal to [60].")
|
||||
```
|
||||
|
||||
It's running the proc twice!
|
||||
|
||||
To fix this, we need to make sure the proc only runs once, by creating a variable for it, and using our `do...while (FALSE)` pattern we learned earlier.
|
||||
|
||||
```dm
|
||||
#define TEST_ASSERT_EQUAL(a, b) \
|
||||
do { \
|
||||
var/__a_value = ##a;
|
||||
var/__b_value = ##b;
|
||||
|
||||
if (__a_value != __b_value) { \
|
||||
return Fail("Expected [__a_value] to be equal to [__b_value]."); \
|
||||
} \
|
||||
} while (FALSE)
|
||||
```
|
||||
|
||||
Now our code correctly reports `Expected 80 to be equal to 60`.
|
||||
|
||||
### ...but if you must be unhygienic, try to restrict the scope.
|
||||
|
||||
This guideline can make some code look extremely noisy if you are writing a large proc, or using the macro a large amount of times.
|
||||
|
||||
In this case, if your macro is only used by one proc, define the macro in that proc, ideally after whatever variables it uses.
|
||||
|
||||
```dm
|
||||
/proc/some_complex_proc()
|
||||
var/counter = 0
|
||||
|
||||
#define MY_NECESSARY_MACRO counter += 5; do_something(counter);
|
||||
|
||||
// My complex code that uses MY_NECESSARY_MACRO here...
|
||||
|
||||
#undef MY_NECESSARY_MACRO
|
||||
```
|
||||
|
||||
### Don't perform work in an unsuspecting macro
|
||||
|
||||
Suppose we have the following macro:
|
||||
|
||||
```dm
|
||||
#define PARTY_LIGHT_COLOR (pick(COLOR_BLUE, COLOR_RED, COLOR_GREEN))
|
||||
```
|
||||
|
||||
When this is used, it'll look like:
|
||||
|
||||
```dm
|
||||
set_color(PARTY_LIGHT_COLOR)
|
||||
```
|
||||
|
||||
Because of how common using defines as constants is, this would seemingly imply the same thing! It does not look like any code should be executing at all. This code would preferably look like:
|
||||
|
||||
```dm
|
||||
set_color(PARTY_LIGHT_COLOR())
|
||||
```
|
||||
|
||||
...which *does* imply some work is happening.
|
||||
|
||||
BYOND does not support `#define PARTY_LIGHT_COLOR()`, so instead we would write the define as:
|
||||
|
||||
```dm
|
||||
#define PARTY_LIGHT_COLOR(...) (pick(COLOR_BLUE, COLOR_RED, COLOR_GREEN))
|
||||
```
|
||||
|
||||
### `#undef` any macros you create, unless they are needed elsewhere
|
||||
|
||||
We do not want macros to leak outside their file, this will create odd dependencies that are based on the filenames. Thus, you should `#undef` any macro you make.
|
||||
|
||||
```dm
|
||||
// Start of corn.dm
|
||||
#define CORN_KERNELS 5
|
||||
|
||||
// All my brilliant corn code
|
||||
|
||||
#undef CORN_KERNELS
|
||||
```
|
||||
|
||||
It is often preferable for your `#define` and `#undef` to surround the code that actually uses them, for instance:
|
||||
|
||||
```dm
|
||||
/obj/item/corn
|
||||
name = "yummy corn"
|
||||
|
||||
#define CORN_HEALTH_GAIN 5
|
||||
|
||||
/obj/item/corn/proc/eat(mob/living/consumer)
|
||||
consumer.health += CORN_HEALTH_GAIN // yum
|
||||
|
||||
#undef CORN_HEALTH_GAIN
|
||||
|
||||
// My other corn code
|
||||
```
|
||||
|
||||
If you want other files to use macros, put them in somewhere such as a file in `__DEFINES`. That way, the files are included in a consistent order:
|
||||
|
||||
```dm
|
||||
#include "__DEFINES/my_defines.dm" // Will always be included first, because of the underscores
|
||||
#include "game/my_object.dm" // This will be able to consistently use defines put in my_defines.dm
|
||||
```
|
||||
|
||||
### Use `##` to help with ambiguities
|
||||
|
||||
Especially with complex macros, it might not be immediately obvious what's part of the macro and what isn't.
|
||||
|
||||
```dm
|
||||
#define CALL_PROC_COMPLEX(source, proc_name) \
|
||||
if (source.is_ready()) { \
|
||||
source.proc_name(); \
|
||||
}
|
||||
```
|
||||
|
||||
`source` and `proc_name` are both going to be directly pasted in, but they look just like any other normal code, and so it makes reading this macro a bit harder.
|
||||
|
||||
Consider instead:
|
||||
|
||||
```dm
|
||||
#define CALL_PROC_COMPLEX(source, proc_name) \
|
||||
if (##source.is_ready()) { \
|
||||
##source.##proc_name(); \
|
||||
}
|
||||
```
|
||||
|
||||
`##` will paste in the define parameter directly, and makes it more clear what belongs to the macro.
|
||||
|
||||
This is the most subjective of all the guidelines here, as it might just create visual noise in very simple macros, so use your best judgment.
|
||||
|
||||
### For impure/unhygienic defines, use procs/normal code when reasonable
|
||||
|
||||
Sometimes the best macro is one that doesn't exist at all. Macros can make some code fairly hard to maintain, due to their very weird syntax restrictions, and can be generally fairly mysterious, and hurt readability. Thus, if you don't have a strong reason to use a macro, consider just writing the code out normally or using a proc.
|
||||
|
||||
```dm
|
||||
#define SWORD_HIT(sword, victim) { /* Ugly backslashes! */ \
|
||||
##sword.attack(##victim); /* Ugly semicolons! */ \
|
||||
##victim.say("Ouch!"); /* Even ugly comments! */ \
|
||||
}
|
||||
```
|
||||
|
||||
This is a fairly egregious macro, and would be better off just written like:
|
||||
```dm
|
||||
/obj/item/sword/proc/hit(mob/victim)
|
||||
attack(victim)
|
||||
victim.say("Ouch!")
|
||||
```
|
||||
|
||||
## Things that do not matter
|
||||
The following coding styles are not only not enforced at all, but are generally frowned upon to change for little to no reason:
|
||||
|
||||
* English/British spelling on var/proc names
|
||||
* Color/Colour - both are fine, but keep in mind that BYOND uses `color` as a base variable
|
||||
* Spaces after control statements
|
||||
* `if()` and `if ()` - nobody cares!
|
||||
21
.github/guides/TICK_ORDER.md
vendored
Normal file
21
.github/guides/TICK_ORDER.md
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The byond tick proceeds as follows:
|
||||
1. procs sleeping via walk() are resumed (i dont know why these are first)
|
||||
|
||||
2. normal sleeping procs are resumed, in the order they went to sleep in the first place, this is where the MC wakes up and processes subsystems. a consequence of this is that the MC almost never resumes before other sleeping procs, because it only goes to sleep for 1 tick 99% of the time, and 99% of procs either go to sleep for less time than the MC (which guarantees that they entered the sleep queue earlier when its time to wake up) and/or were called synchronously from the MC's execution, almost all of the time the MC is the last sleeping proc to resume in any given tick. This is good because it means the MC can account for the cost of previous resuming procs in the tick, and minimizes overtime.
|
||||
|
||||
3. control is passed to byond after all of our code's procs stop execution for this tick
|
||||
|
||||
4. a few small things happen in byond internals
|
||||
|
||||
5. SendMaps is called for this tick, which processes the game state for all clients connected to the game and handles sending them changes
|
||||
in appearances within their view range. This is expensive and takes up a significant portion of our tick, about 0.45% per connected player
|
||||
as of 3/20/2022. meaning that with 50 players, 22.5% of our tick is being used up by just SendMaps, after all of our code has stopped executing. Thats only the average across all rounds, for most highpop rounds it can look like 0.6% of the tick per player, which is 30% for 50 players.
|
||||
|
||||
6. After SendMaps ends, client verbs sent to the server are executed, and its the last major step before the next tick begins.
|
||||
During the course of the tick, a client can send a command to the server saying that they have executed any verb. The actual code defined
|
||||
for that /verb/name() proc isnt executed until this point, and the way the MC is designed makes this especially likely to make verbs
|
||||
"overrun" the bounds of the tick they executed in, stopping the other tick from starting and thus delaying the MC firing in that tick.
|
||||
|
||||
The master controller can derive how much of the tick was used in: procs executing before it woke up (because of world.tick_usage), and SendMaps (because of world.map_cpu, since this is a running average you cant derive the tick spent on maptick on any particular tick). It cannot derive how much of the tick was used for sleeping procs resuming after the MC ran, or for verbs executing after SendMaps.
|
||||
|
||||
It is for these reasons why you should heavily limit processing done in verbs, while procs resuming after the MC are rare, verbs are not, and are much more likely to cause overtime since theyre literally at the end of the tick. If you make a verb, try to offload any expensive work to the beginning of the next tick via a verb management subsystem.
|
||||
119
README.md
119
README.md
@@ -28,17 +28,11 @@
|
||||
|
||||
## DOWNLOADING
|
||||
|
||||
There are a number of ways to download the source code. Some are described here, an alternative all-inclusive guide is also located at https://wiki.yogstation.net/wiki/Downloading_the_source_code
|
||||
[Downloading](.github/guides/DOWNLOADING.md)
|
||||
|
||||
Option 1:
|
||||
Follow this: https://wiki.yogstation.net/wiki/Setting_up_git
|
||||
[Running a server](.github/guides/RUNNING_A_SERVER.md)
|
||||
|
||||
Option 2: Download the source code as a zip by clicking the ZIP button in the
|
||||
code tab of https://github.com/yogstation13/Yogstation
|
||||
(note: this will use a lot of bandwidth if you wish to update and is a lot of
|
||||
hassle if you want to make any changes at all, so it's not recommended.)
|
||||
|
||||
## The Yogstation codebase recommends compiling using version [514.1589](https://www.byond.com/download/build/514/514.1589_byond.exe) and may potentially NOT work on newer or older versions.
|
||||
[Maps and Away Missions](.github/guides/MAPS_AND_AWAY_MISSIONS.md)
|
||||
|
||||
## :exclamation: How to compile :exclamation:
|
||||
|
||||
@@ -50,113 +44,6 @@ After it finishes, you can then setup the server normally by opening `yogstation
|
||||
|
||||
**Building yogstation in DreamMaker directly is now deprecated and might produce errors**, such as `'tgui.bundle.js': cannot find file`.
|
||||
|
||||
## INSTALLATION
|
||||
|
||||
First-time installation should be fairly straightforward. First, you'll need
|
||||
BYOND installed. You can get it from https://www.byond.com/download. Once you've done
|
||||
that, extract the game files to wherever you want to keep them. This is a
|
||||
sourcecode-only release, so the next step is to compile the server files.
|
||||
Follow the above steps to do this.
|
||||
|
||||
If you see any errors or warnings, something has gone wrong - possibly a corrupt
|
||||
download or the files extracted wrong. If problems persist, ask for assistance
|
||||
in #development-public on discord.
|
||||
|
||||
Once that's done, open up the config folder. You'll want to edit config.txt to
|
||||
set the probabilities for different gamemodes in Secret and to set your server
|
||||
location so that all your players don't get disconnected at the end of each
|
||||
round. It's recommended you don't turn on the gamemodes with probability 0,
|
||||
except Extended, as they have various issues and aren't currently being tested,
|
||||
so they may have unknown and bizarre bugs. Extended is essentially no mode, and
|
||||
isn't in the Secret rotation by default as it's just not very fun.
|
||||
|
||||
You'll also want to edit config/admins.txt to remove the default admins and add
|
||||
your own. "Game Master" is the highest level of access, and probably the one
|
||||
you'll want to use for now. You can set up your own ranks and find out more in
|
||||
config/admin_ranks.txt
|
||||
|
||||
The format is
|
||||
|
||||
```
|
||||
byondkey = Rank
|
||||
```
|
||||
|
||||
where the admin rank must be properly capitalised.
|
||||
|
||||
This codebase also depends on a native library called rust-g. A precompiled
|
||||
Windows DLL is included in this repository, but Linux users will need to build
|
||||
and install it themselves. Directions can be found at the [rust-g
|
||||
repo](https://github.com/tgstation/rust-g). The `hash` feature is required.
|
||||
|
||||
Finally, to start the server, run Dream Daemon and enter the path to your
|
||||
compiled yogstation.dmb file. Make sure to set the port to the one you
|
||||
specified in the config.txt, and set the Security box to 'Trusted'. Then press GO
|
||||
and the server should start up and be ready to join. It is also recommended that
|
||||
you set up the SQL backend (see below).
|
||||
|
||||
## UPDATING
|
||||
|
||||
To update an existing installation, first back up your /config and /data folders
|
||||
as these store your server configuration, player preferences and banlist.
|
||||
|
||||
Then, extract the new files (preferably into a clean directory, but updating in
|
||||
place should work fine), copy your /config and /data folders back into the new
|
||||
install, overwriting when prompted except if we've specified otherwise, and
|
||||
recompile the game. Once you start the server up again, you should be running
|
||||
the new version.
|
||||
|
||||
## HOSTING
|
||||
|
||||
If you'd like a more robust server hosting option for tgstation and its
|
||||
derivatives. Check out /tg/station's server tools suite at
|
||||
https://github.com/tgstation/tgstation-server
|
||||
|
||||
## MAPS
|
||||
|
||||
Yogstation currently comes equipped with the following maps.
|
||||
|
||||
* [BoxStation (default)](https://wiki.yogstation.net/wiki/BoxStation)
|
||||
* [DeltaStation](https://wiki.yogstation.net/wiki/DeltaStation)
|
||||
* [EclipseStation](https://wiki.yogstation.net/wiki/Maps)
|
||||
* [MetaStation](https://wiki.yogstation.net/wiki/MetaStation)
|
||||
* [OmegaStation](https://wiki.yogstation.net/wiki/OmegaStation)
|
||||
|
||||
All maps have their own code file that is in the base of the _maps directory. Maps are loaded dynamically when the game starts. Follow this guideline when adding your own map, to your fork, for easy compatibility.
|
||||
|
||||
The map that will be loaded for the upcoming round is determined by reading data/next_map.json, which is a copy of the json files found in the _maps tree. If this file does not exist, the default map from config/maps.txt will be loaded. Failing that, BoxStation will be loaded. If you want to set a specific map to load next round you can use the Change Map verb in game before restarting the server or copy a json from _maps to data/next_map.json before starting the server. Also, for debugging purposes, ticking a corresponding map's code file in Dream Maker will force that map to load every round.
|
||||
|
||||
If you are hosting a server, and want randomly picked maps to be played each round, you can enable map rotation in [config.txt](config/config.txt) and then set the maps to be picked in the [maps.txt](config/maps.txt) file.
|
||||
|
||||
Anytime you want to make changes to a map it's imperative you use the [Map Merging tools](https://tgstation13.org/wiki/Map_Merger)
|
||||
|
||||
## AWAY MISSIONS
|
||||
|
||||
Yogstation supports loading away missions however they are disabled by default.
|
||||
|
||||
Map files for away missions are located in the _maps/RandomZLevels directory. Each away mission includes it's own code definitions located in /code/modules/awaymissions/mission_code. These files must be included and compiled with the server beforehand otherwise the server will crash upon trying to load away missions that lack their code.
|
||||
|
||||
To enable an away mission open `config/awaymissionconfig.txt` and uncomment one of the .dmm lines by removing the #. If more than one away mission is uncommented then the away mission loader will randomly select one the enabled ones to load.
|
||||
|
||||
## SQL SETUP
|
||||
|
||||
The SQL backend requires a Mariadb server running 10.2 or later. Mysql is not supported but Mariadb is a drop in replacement for mysql. SQL is required for the library, stats tracking, admin notes, and job-only bans, among other features, mostly related to server administration. Your server details go in /config/dbconfig.txt, and the SQL schema is in /SQL/tgstation_schema.sql and /SQL/tgstation_schema_prefix.sql depending on if you want table prefixes. More detailed setup instructions are located here: https://www.tgstation13.org/wiki/Downloading_the_source_code#Setting_up_the_database
|
||||
|
||||
If you are hosting a testing server on windows you can use a standalone version of MariaDB pre load with a blank (but initialized) tgdb database. Find them here: https://tgstation13.download/database/ Just unzip and run for a working (but insecure) database server. Includes a zipped copy of the data folder for easy resetting back to square one.
|
||||
|
||||
## WEB/CDN RESOURCE DELIVERY
|
||||
|
||||
Web delivery of game resources makes it quicker for players to join and reduces some of the stress on the game server.
|
||||
|
||||
1. Edit compile_options.dm to set the `PRELOAD_RSC` define to `0`
|
||||
1. Add a url to config/external_rsc_urls pointing to a .zip file containing the .rsc.
|
||||
* If you keep up to date with /tg/ you could reuse /tg/'s rsc cdn at http://tgstation13.download/byond/tgstation.zip. Otherwise you can use cdn services like CDN77 or cloudflare (requires adding a page rule to enable caching of the zip), or roll your own cdn using route 53 and vps providers.
|
||||
* Regardless even offloading the rsc to a website without a CDN will be a massive improvement over the in game system for transferring files.
|
||||
|
||||
## IRC BOT SETUP
|
||||
|
||||
Included in the repository is a python3 compatible IRC bot capable of relaying adminhelps to a specified
|
||||
IRC channel/server, see the /tools/minibot folder for more
|
||||
|
||||
## CONTRIBUTING
|
||||
|
||||
Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md)
|
||||
|
||||
Reference in New Issue
Block a user