Skip to content
Erki edited this page Oct 19, 2017 · 3 revisions

General Overview

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.

Categories

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 new player_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 is SQL_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 the save button in character setup, and loaded whenever a new character is loaded.

Category Items

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.

load/save_character

/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

SQL Loading & Saving

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

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.

gather_load_parameters

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)

gather_save_query

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.

gather_save_parameters

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

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.

Content Generation & Logic

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.

Standards and Guidelines

A collection of standards and guidelines applied to the codebase.

Common API Documentation

Documentation regarding common APIs which speed up feature implementation and should be known by all coders.

Less Common APIs

Documentation for less used APIs that are not often needed.

Subsystems

Documentation regarding our implementation of StonedMC (SMC).

Decrepit

Decrepit or unused systems.

  • Dynamic Maps (Not to be confused with the newer away mission implementation.)
Clone this wiki locally