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
)

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