-
Notifications
You must be signed in to change notification settings - Fork 516
Preferences
Preferences on Aurora are primarily geared towards utilizing the MySQL database. All tables prefixes with ss13_preferences
and ss13_characters_
hold data pertaining to either global player preferences, which are unaffected by the currently loaded character, or to specific characters, respectively.
The API for preferences suggests the creation of list datums per every visible page in the character setup window.
Each category is defined by a new /datum/category_group/player_setup_category
class. These are all declared in the code/modules/client/preference_setup/preference_setup.dm
file. There are four variables per prototype that you should pay attention to:
-
name
- self-explanatory, a simple name for the category. -
sort_order
- the order in which to algorithmically sort the category. This affects both load and save order, so it should be paid attention to if one category depends on the loading of another! -
category_item_type
- the parent class for all item subclasses that'll belong to this group. General naming convention is to call the type the same as you called your newplayer_setup_category
class. -
sql_role
- defines whether or not the MySQL loader should interpret this category as global preferences or character specific preferences. Do not use both flags! By default, the value isSQL_CHARACTER
. The differences are:-
SQL_PREFERENCES
are loaded first, before character data, and are saved whenever global preferences are edited. -
SQL_CHARACTER
are loaded after preferences. They're also only saved whenever the player presses thesave
button in character setup, and loaded whenever a new character is loaded.
-
Category items is where the "magic happens". It's where you handle specific data and generate the UI for editing it. Each item also manages its loading and data validation, allowing for special conditions and backend logic.
Each category item is a child of /datum/category_item/player_setup_item/category/
class. You don't need to declare the parent of it, simply start creating children. Much like with category groups, for each item class you'll have to define the name
and sort_order
variables. Their functions are the same.
The general idea is that category items will describe how variables owned by either the player's preferences object, accessible via src.pref
, or client variables, src.pref.client
, are handled and edited. All variables where you wish to store data to should thus be declared to either the /datum/preferences
class or in the /client
class, and not to a specific item.
To set up a category item properly, you will need to override a set of procs.
/datum/category_item/player_setup_item/proc/save_character(var/savefile/S)
/datum/category_item/player_setup_item/proc/load_character(var/savefile/S)
These two are old file system saving and loading procs. They're simple enough, and define where in the savefile to save and load the data from. Definitions are as simple as:
/datum/category_item/player_setup_item/general/body/load_character(var/savefile/S)
S["hair_red"] >> pref.r_hair
S["hair_green"] >> pref.g_hair
S["hair_blue"] >> pref.b_hair
/datum/category_item/player_setup_item/general/body/save_character(var/savefile/S)
S["hair_red"] << pref.r_hair
S["hair_green"] << pref.g_hair
S["hair_blue"] << pref.b_hair
The MySQL API is a bit more complex and more powerful. There are four procs that you should be aware of:
/datum/category_item/player_setup_item/proc/gather_load_query()
/datum/category_item/player_setup_item/proc/gather_save_query()
/datum/category_item/player_setup_item/proc/gather_load_parameters()
/datum/category_item/player_setup_item/proc/gather_save_parameters()
All of these must return an instance of type /list
, even if said list empty.
gather_load_query
is responsible for describing what data is getting pulled from what tables, and based on what arguments the query is executed. It also describes into which variables the data will be stored right after loading. The list has the following key-value structure:
list(
"table_name" = list(
"vars" = list(
"column_name1",
"column_name2" = "var_name"
),
"args" = list("id")
)
)
"table_name"
can be able table name from which the data is being pulled from. You can return a list describing multiple tables, though note you should never duplicate these key values in one return value.
"vars"
and "args"
are keywords and need to always be present. They must also always be associated with an instance of type /list
.
The list attached to "vars"
is an optionally associated list containing SQL column names -> preferences variable names
. If only the column name is present, then the variable name is implicitly associated with a variable of exactly the same name. Data from the described column is thus loaded into the variable specified. The variable name mark-up also has support for associated lists, by the following style: variable_name/key_value
. So describing "robot_generic" = "flavourtext_robot/generic"
is equal to saving the contents of the robot_generic
column to preferences.flavourtext_robot["generic"]
.
The list attached to "args"
describes what is written into the WHERE
clause of the category's query. The args are populated each query by gather_load_parameters
. So whatever values are written into the "args"
list must be populated by gather_load_parameters
of the same class.
This proc populates the arguments for the WHERE
clause in the load query. As described earlier, all arguments referenced in the queries of gather_load_query
must be populated here. Though this list is not table dependent.
Note that all arguments are properly sanitized and escaped automatically. Do not add data sanitization here unless you know exactly what you're doing.
The list returned from this proc is a very simple key -> value list of "argument_name" = argument_value.
A classic argument to use is the character ID:
/datum/category_item/player_setup_item/general/body/gather_load_parameters()
return list("id" = pref.current_character)
Similarly to gather_load_query
, this describes what data to save into which columns. Though the key list structure is simpler, as it does not require the specification of arguments explicitly. Nor does it require the specification of variable names. Just like with gather_load_query
, multiple tables can be specified.
An example is as follows:
list(
"ss13_characters" = list(
"hair_colour",
"id" = 1,
"ckey" = 1
)
)
The optional value 1
describes whether or not the data field is to be updated on duplicate key value. If 1
, then the data field is left untouched whenever a duplicate key is found. Generally speaking, you want to specify this for "id"
and "ckey"
: data that is immutable after the entry is created.
In this instance, it is important to know what the end query is and how it works in MySQL. This is the query generated by the example above:
INSERT INTO ss13_characters (hair_colour, id, ckey) VALUES (:hair_colour:, :id:, :ckey:) ON DUPLICATE KEY UPDATE hair_colour = :hair_colour:;
Effectively, id
and ckey
take the role of arguments with this query. And as such, they should not be updated on duplicate key entry. To minimize potential errors.
Functionally the same as gather_load_parameters
, this is a table agnostic function for gathering values to all of the arguments referenced in gather_save_query
. Everything defined there must be given a value here. The list is a simple key -> value list, containing argument name and its value.
Once again, variables are sanitized before being uploaded, so be careful with avoiding double sanitization.
An example list returned is as follows:
list(
"hair_colour" = rgb(pref.r_hair, pref.g_hair, pref.b_hair),
"id" = pref.current_character,
"ckey" = pref.client.ckey
)
Sanitization of loaded data is the second key thing handled by category items. All of this work is done in the /datum/category_item/player_setup_item/proc/sanitize_character(sql_load = FALSE)
and */proc/sanitize_preferences(sql_load = FALSE)
procs. The name describes which proc is called at which juncture: sanitize_character
every time the character load procs are called, sanitize_preferences
every time player global preferences are reloaded.
sql_load
helps determine whether or not the data was loaded from the database, in which case sql_load
is set to TRUE
, or from a save file. Whenever it's loaded from the database, all data is initially present in text form! So numbers must be converted to numbers, lists deserialized, etcetera.
After that sanitize of all loaded data should be checked for, and potentially old data formats updated to new ones.
All editable menu content is generated by category items in the /datum/category_item/player_setup_item/proc/content(mob/user)
proc. Its output must return a text string. This string is then displayed to the user in a category menu.
A category item is responsible for handling all links its content
proc generates. This is done in the /datum/category_item/player_setup_item/proc/OnTopic(href, list/href_list, mob/user)
proc. The functionality of this proc is very similar to a standard Topic
proc, with one notable exception: the return value must be one of the following:
-
TOPIC_REFRESH
- informs the preferences class that the view must be regenerated after all topics are handled. Also marks the category as having been altered. -
TOPIC_HANDLED
- informs the preferences class that some action has been taken, but no refresh of the view is required. Marks the category as having been altered. -
TOPIC_NOACTION
- informs the preferences class that no action has been taken: input is either invalid or some other logic failed. This is the only case where a category is not marked as having been altered.
During saving, only categories marked as altered are saved, to minimize SQL churn whenever possible.
A collection of standards and guidelines applied to the codebase.
Documentation regarding common APIs which speed up feature implementation and should be known by all coders.
- Atom Initialization
- Garbage, Queued Deletion, and Destroy
- Callbacks
- Timers
- Lazy Lists
- Overlays
- Processing APIs
- Common Helpers
- Global Listeners
- Singletons
Documentation for less used APIs that are not often needed.
Documentation regarding our implementation of StonedMC (SMC).
Decrepit or unused systems.
- Dynamic Maps (Not to be confused with the newer away mission implementation.)