/tg/ pref datums (part 1) (#16219)

* TG Prefs (Step 1: JSON savefiles)

* TG Prefs (Step 2: Preference Datum Code)

* TG Prefs (Step 3: Convert /datum/client_preferences)

* TG Prefs (Step 4: Clean up and finishing touches)

* Fix some weird compile errors from the rebase
This commit is contained in:
ShadowLarkens
2024-08-31 07:09:05 +10:00
committed by GitHub
parent 0cfc6e9e94
commit faac97e352
184 changed files with 4968 additions and 2198 deletions
+456
View File
@@ -0,0 +1,456 @@
# Preferences (by Mothblocks)
This does not contain all the information on specific values--you can find those as doc-comments in relevant paths, such as `/datum/preference`. Rather, this gives you an overview for creating *most* preferences, and getting your foot in the door to create more advanced ones.
## Anatomy of a preference (A.K.A. how do I make one?)
Most preferences consist of two parts:
1. A `/datum/preference` type.
2. A tgui representation in a TypeScript file.
Every `/datum/preference` requires these three values be set:
1. `category` - See [Categories](#Categories).
2. `savefile_key` - The value which will be saved in the savefile. This will also be the identifier for tgui.
3. `savefile_identifier` - Whether or not this is a character specific preference (`PREFERENCE_CHARACTER`) or one that affects the player (`PREFERENCE_PLAYER`). As an example: hair color is `PREFERENCE_CHARACTER` while your UI settings are `PREFERENCE_PLAYER`, since they do not change between characters.
For the tgui representation, most preferences will create a `.tsx` file in `tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/`. If your preference is a character preference, make a new file in `character_preferences`. Otherwise, put it in `game_preferences`. The filename does not matter, and this file can hold multiple relevant preferences if you would like.
From here, you will want to write code resembling:
```ts
import { Feature } from "../base";
export const savefile_key_here: Feature<T> = {
name: "Preference Name Here",
component: Component,
// Necessary for game preferences, unused for others
category: "CATEGORY",
// Optional, shown as a tooltip
description: "This preference will blow your mind!",
}
```
`T` and `Component` depend on the type of preference you're making. Here are all common examples...
## Numeric preferences
Examples include age and FPS.
A numeric preference derives from `/datum/preference/numeric`.
```dm
/datum/preference/numeric/legs
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
savefile_identifier = PREFERENCE_CHARACTER
savefile_key = "legs"
minimum = 1
maximum = 8
```
You can optionally provide a `step` field. This value is 1 by default, meaning only integers are accepted.
Your `.tsx` file would look like:
```ts
import { Feature, FeatureNumberInput } from "../base";
export const legs: Feature<number> = {
name: "Legs",
component: FeatureNumberInput,
}
```
## Toggle preferences
Examples include enabling tooltips.
```dm
/datum/preference/toggle/enable_breathing
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
savefile_identifier = PREFERENCE_CHARACTER
savefile_key = "enable_breathing"
// Optional, TRUE by default
default_value = FALSE
```
Your `.tsx` file would look like:
```ts
import { CheckboxInput, FeatureToggle } from "../base";
export const enable_breathing: FeatureToggle = {
name: "Enable breathing",
component: CheckboxInput,
}
```
## Choiced preferences
A choiced preference is one where the only options are in a distinct few amount of choices. Examples include skin tone, shirt, and UI style.
To create one, derive from `/datum/preference/choiced`.
```dm
/datum/preference/choiced/favorite_drink
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
savefile_identifier = PREFERENCE_CHARACTER
savefile_key = "favorite_drink"
```
Now we need to tell the game what the choices are. We do this by overriding `init_possible_values()`. This will return a list of possible options.
```dm
/datum/preference/choiced/favorite_drink/init_possible_values()
return list(
"Milk",
"Cola",
"Water",
)
```
Your `.tsx` file would then look like:
```tsx
import { FeatureChoiced, FeatureDropdownInput } from "../base";
export const favorite_drink: FeatureChoiced = {
name: "Favorite drink",
component: FeatureDropdownInput,
};
```
This will create a dropdown input for your preference.
### Choiced preferences - Icons
Choiced preferences can generate icons. This is how the clothing/species preferences work, for instance. However, if we just want a basic dropdown input with icons, it would look like this:
```dm
/datum/preference/choiced/favorite_drink
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
savefile_identifier = PREFERENCE_CHARACTER
savefile_key = "favorite_drink"
should_generate_icons = TRUE // NEW! This is necessary.
/datum/preference/choiced/favorite_drink/init_possible_values()
return list("Milk", "Cola", "Water")
// New! This proc will get called for every value.
/datum/preference/choiced/favorite_drink/icon_for(value)
switch (value)
if ("Milk")
return icon('drinks.dmi', "milk")
if ("Cola")
return icon('drinks.dmi', "cola")
if ("Water")
return icon('drinks.dmi', "water")
```
Then, change your `.tsx` file to look like:
```tsx
import { FeatureChoiced, FeatureIconnedDropdownInput } from "../base";
export const favorite_drink: FeatureChoiced = {
name: "Favorite drink",
component: FeatureIconnedDropdownInput,
};
```
### Choiced preferences - Display names
Sometimes the values you want to save in code aren't the same as the ones you want to display. You can specify display names to change this.
The only thing you will add is "compiled data".
```dm
/datum/preference/choiced/favorite_drink/compile_constant_data()
var/list/data = ..()
// An assoc list of values to display names
data[CHOICED_PREFERENCE_DISPLAY_NAMES] = list(
"Milk" = "Delicious Milk",
"Cola" = "Crisp Cola",
"Water" = "Plain Ol' Water",
)
return data
```
Your `.tsx` file does not change. The UI will figure it out for you!
## Color preferences
These refer to colors, such as your OOC color. When read, these values will be given as 6 hex digits, *without* the pound sign.
```dm
/datum/preference/color/eyeliner_color
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
savefile_identifier = PREFERENCE_CHARACTER
savefile_key = "eyeliner_color"
```
Your `.tsx` file would look like:
```ts
import { FeatureColorInput, Feature } from "../base";
export const eyeliner_color: Feature<string> = {
name: "Eyeliner color",
component: FeatureColorInput,
};
```
## Name preferences
These refer to an alternative name. Examples include AI names and backup human names.
These exist in `code/modules/client/preferences/names.dm`.
These do not need a `.ts` file, and will be created in the UI automatically.
```dm
/datum/preference/name/doctor
savefile_key = "doctor_name"
// The name on the UI
explanation = "Doctor name"
// This groups together with anything else with the same group
group = "medicine"
// Optional, if specified the UI will show this name actively
// when the player is a medical doctor.
relevant_job = /datum/job/medical_doctor
```
## Making your preference do stuff
There are a handful of procs preferences can use to act on their own:
```dm
/// Apply this preference onto the given client.
/// Called when the savefile_identifier == PREFERENCE_PLAYER.
/datum/preference/proc/apply_to_client(client/client, value)
/// Fired when the preference is updated.
/// Calls apply_to_client by default, but can be overridden.
/datum/preference/proc/apply_to_client_updated(client/client, value)
/// Apply this preference onto the given human.
/// Must be overriden by subtypes.
/// Called when the savefile_identifier == PREFERENCE_CHARACTER.
/datum/preference/proc/apply_to_human(mob/living/carbon/human/target, value)
```
For example, `/datum/preference/numeric/age` contains:
```dm
/datum/preference/numeric/age/apply_to_human(mob/living/carbon/human/target, value)
target.age = value
```
If your preference is `PREFERENCE_CHARACTER`, it MUST override `apply_to_human`, even if just to immediately `return`.
You can also read preferences directly with `prefs.read_preference(/datum/preference/type/here)`, which will return the stored value.
## Categories
Every preference needs to be in a `category`. These can be found in `code/__DEFINES/preferences.dm`.
```dm
/// These will be shown in the character sidebar, but at the bottom.
#define PREFERENCE_CATEGORY_FEATURES "features"
/// Any preferences that will show to the sides of the character in the setup menu.
#define PREFERENCE_CATEGORY_CLOTHING "clothing"
/// Preferences that will be put into the 3rd list, and are not contextual.
#define PREFERENCE_CATEGORY_NON_CONTEXTUAL "non_contextual"
/// Will be put under the game preferences window.
#define PREFERENCE_CATEGORY_GAME_PREFERENCES "game_preferences"
/// These will show in the list to the right of the character preview.
#define PREFERENCE_CATEGORY_SECONDARY_FEATURES "secondary_features"
/// These are preferences that are supplementary for main features,
/// such as hair color being affixed to hair.
#define PREFERENCE_CATEGORY_SUPPLEMENTAL_FEATURES "supplemental_features"
```
![Preference categories for the main page](https://raw.githubusercontent.com/tgstation/documentation-assets/main/preferences/preference_categories.png)
> SECONDARY_FEATURES or NON_CONTEXTUAL?
Secondary features tend to be species specific. Non contextual features shouldn't change much from character to character.
## Default values and randomization
There are three procs to be aware of in regards to this topic:
- `create_default_value()`. This is used when a value deserializes improperly or when a new character is created.
- `create_informed_default_value(datum/preferences/preferences)` - Used for more complicated default values, like how names require the gender. Will call `create_default_value()` by default.
- `create_random_value(datum/preferences/preferences)` - Explicitly used for random values, such as when a character is being randomized.
`create_default_value()` in most preferences will create a random value. If this is a problem (like how default characters should always be human), you can override `create_default_value()`. By default (without overriding `create_random_value`), random values are just default values.
## Advanced - Server data
As previewed in [the display names implementation](#Choiced-preferences---Display-names), there exists a `compile_constant_data()` proc you can override.
Compiled data is used wherever the server needs to give the client some value it can't figure out on its own. Skin tones use this to tell the client what colors they represent, for example.
Compiled data is sent to the `serverData` field in the `FeatureValueProps`.
## Advanced - Creating your own tgui component
If you have good knowledge with tgui (especially TypeScript), you'll be able to create your own component to represent preferences.
The `component` field in a feature accepts __any__ component that accepts `FeatureValueProps<TReceiving, TSending = TReceiving, TServerData = undefined>`.
This will give you the fields:
```ts
act: typeof sendAct,
featureId: string,
handleSetValue: (newValue: TSending) => void,
serverData: TServerData | undefined,
shrink?: boolean,
value: TReceiving,
```
`act` is the same as the one you get from `useBackend`.
`featureId` is the savefile_key of the feature.
`handleSetValue` is a function that, when called, will tell the server the new value, as well as changing the value immediately locally.
`serverData` is the [server data](#Advanced---Server-data), if it has been fetched yet (and exists).
`shrink` is whether or not the UI should appear smaller. This is only used for supplementary features.
`value` is the current value, could be predicted (meaning that the value was changed locally, but has not yet reached the server).
For a basic example of how this can look, observe `CheckboxInput`:
```tsx
export const CheckboxInput = (
props: FeatureValueProps<BooleanLike, boolean>
) => {
return (<Button.Checkbox
checked={!!props.value}
onClick={() => {
props.handleSetValue(!props.value);
}}
/>);
};
```
## Advanced - Middleware
A `/datum/preference_middleware` is a way to inject your own data at specific points, as well as hijack actions.
Middleware can hijack actions by specifying `action_delegations`:
```dm
/datum/preference_middleware/congratulations
action_delegations = list(
"congratulate_me" = PROC_REF(congratulate_me),
)
/datum/preference_middleware/congratulations/proc/congratulate_me(list/params, mob/user)
to_chat(user, span_notice("Wow, you did a great job learning about middleware!"))
return TRUE
```
Middleware can inject its own data at several points, such as providing new UI assets, compiled data (used by middleware such as quirks to tell the client what quirks exist), etc. Look at `code/modules/client/preferences/middleware/_middleware.dm` for full information.
---
## Antagonists
In order to make an antagonist selectable, you must do a few things:
1. Your antagonist needs an icon.
2. Your antagonist must be in a Dynamic ruleset. The ruleset must specify the antagonist as its `antag_flag`.
3. Your antagonist needs a file in `tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/filename.ts`. This file name MUST be the `antag_flag` of your ruleset, with nothing but letters remaining (e.g. "Nuclear Operative" -> `nuclearoperative`).
4. Add it to `special_roles`.
## Creating icons
If you are satisfied with your icon just being a dude with some clothes, then you can specify `preview_outfit` in your `/datum/antagonist`.
Space Ninja, for example, looks like:
```dm
/datum/antagonist/ninja
preview_outift = /datum/outfit/ninja
```
However, if you want to get creative, you can override `/get_preview_icon()`. This proc should return an icon of size `ANTAGONIST_PREVIEW_ICON_SIZE`x`ANTAGONIST_PREVIEW_ICON_SIZE`.
There are some helper procs you can use as well. `render_preview_outfit(outfit_type)` will take an outfit and give you an icon of someone wearing those clothes. `finish_preview_outfit` will, given an icon, resize it appropriately and zoom in on the head. Note that this will look bad on anything that isn't a human, so if you have a non-human antagonist (such as sentient disease), just run `icon.Scale(ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE)`.
For inspiration, here is changeling's:
```dm
/datum/antagonist/changeling/get_preview_icon()
var/icon/final_icon = render_preview_outfit(/datum/outfit/changeling)
var/icon/split_icon = render_preview_outfit(/datum/outfit/job/engineer)
final_icon.Shift(WEST, world.icon_size / 2)
final_icon.Shift(EAST, world.icon_size / 2)
split_icon.Shift(EAST, world.icon_size / 2)
split_icon.Shift(WEST, world.icon_size / 2)
final_icon.Blend(split_icon, ICON_OVERLAY)
return finish_preview_icon(final_icon)
```
...which creates:
![Changeling icon](https://raw.githubusercontent.com/tgstation/documentation-assets/main/preferences/changeling.png)
## Creating the tgui representation
In the `.ts` file you created earlier, you must now give the information of your antagonist. For reference, this is the changeling's:
```ts
import { Antagonist, Category } from "../base";
import { multiline } from "common/string";
const Changeling: Antagonist = {
key: "changeling", // This must be the same as your filename
name: "Changeling",
description: [
multiline`
A highly intelligent alien predator that is capable of altering their
shape to flawlessly resemble a human.
`,
multiline`
Transform yourself or others into different identities, and buy from an
arsenal of biological weaponry with the DNA you collect.
`,
],
category: Category.Roundstart, // Category.Roundstart, Category.Midround, or Category.Latejoin
};
export default Changeling;
```
## Readying the Dynamic ruleset
You already need to create a Dynamic ruleset, so in order to get your antagonist recognized, you just need to specify `antag_flag`. This must be unique per ruleset.
Two other values to note are `antag_flag_override` and `antag_preference`.
`antag_flag_override` exists for cases where you want the banned antagonist to be separate from `antag_flag`. As an example: roundstart, midround, and latejoin traitors have separate `antag_flag`, but all have `antag_flag_override = ROLE_TRAITOR`. This is because admins want to ban a player from Traitor altogether, not specific rulesets.
If `antag_preference` is set, it will refer to that preference instead of `antag_flag`. This is used for clown operatives, which we want to be on the same preference as standard nuke ops, but must specify a unique `antag_flag` for.
## Updating special_roles
In `code/__DEFINES/role_preferences.dm` (the same place you'll need to make your ROLE_\* defined), simply add your antagonist to the `special_roles` assoc list. The key is your ROLE, the value is the number of days since your first game in order to play as that antagonist.
@@ -0,0 +1,546 @@
// Priorities must be in order!
/// The default priority level
#define PREFERENCE_PRIORITY_DEFAULT 1
/// The priority at which species runs, needed for external organs to apply properly.
#define PREFERENCE_PRIORITY_SPECIES 2
/**
* Some preferences get applied directly to bodyparts (anything head_flags related right now).
* These must apply after species, as species gaining might replace the bodyparts of the human.
*/
#define PREFERENCE_PRIORITY_BODYPARTS 3
/// The priority at which gender is determined, needed for proper randomization.
#define PREFERENCE_PRIORITY_GENDER 4
/// The priority at which body type is decided, applied after gender so we can
/// support the "use gender" option.
#define PREFERENCE_PRIORITY_BODY_TYPE 5
/// Used for preferences that rely on body setup being finalized.
#define PREFERENCE_PRORITY_LATE_BODY_TYPE 6
/// Equpping items based on preferences.
/// Should happen after species and body type to make sure it looks right.
/// Mostly redundant, but a safety net for saving/loading.
#define PREFERENCE_PRIORITY_LOADOUT 7
/// The priority at which names are decided, needed for proper randomization.
#define PREFERENCE_PRIORITY_NAMES 8
/// Preferences that aren't names, but change the name changes set by PREFERENCE_PRIORITY_NAMES.
#define PREFERENCE_PRIORITY_NAME_MODIFICATIONS 9
/// The maximum preference priority, keep this updated, but don't use it for `priority`.
#define MAX_PREFERENCE_PRIORITY PREFERENCE_PRIORITY_NAME_MODIFICATIONS
/// For choiced preferences, this key will be used to set display names in constant data.
#define CHOICED_PREFERENCE_DISPLAY_NAMES "display_names"
/// For main feature preferences, this key refers to a feature considered supplemental.
/// For instance, hair color being supplemental to hair.
#define SUPPLEMENTAL_FEATURE_KEY "supplemental_feature"
/// An assoc list list of types to instantiated `/datum/preference` instances
GLOBAL_LIST_INIT(preference_entries, init_preference_entries())
/// An assoc list of preference entries by their `savefile_key`
GLOBAL_LIST_INIT(preference_entries_by_key, init_preference_entries_by_key())
/proc/init_preference_entries()
var/list/output = list()
for(var/datum/preference/preference_type as anything in subtypesof(/datum/preference))
if(is_abstract(preference_type))
continue
output[preference_type] = new preference_type
return output
/proc/init_preference_entries_by_key()
var/list/output = list()
for(var/datum/preference/preference_type as anything in subtypesof(/datum/preference))
if(is_abstract(preference_type))
continue
output[initial(preference_type.savefile_key)] = GLOB.preference_entries[preference_type]
return output
/// Returns a flat list of preferences in order of their priority
/proc/get_preferences_in_priority_order()
var/list/preferences[MAX_PREFERENCE_PRIORITY]
for(var/preference_type in GLOB.preference_entries)
var/datum/preference/preference = GLOB.preference_entries[preference_type]
LAZYADD(preferences[preference.priority], preference)
var/list/flattened = list()
for(var/index in 1 to MAX_PREFERENCE_PRIORITY)
// Don't add nulls to the list if there's no preferences in a given priority level
if(LAZYLEN(preferences[index]))
flattened += preferences[index]
return flattened
/// Represents an individual preference.
/datum/preference
/// The key inside the savefile to use.
/// This is also sent to the UI.
/// Once you pick this, don't change it.
var/savefile_key
/// The category of preference, for use by the PreferencesMenu.
/// This isn't used for anything other than as a key for UI data.
/// It is up to the PreferencesMenu UI itself to interpret it.
var/category = "misc"
/// Do not instantiate if type matches this.
abstract_type = /datum/preference
/// What savefile should this preference be read from?
/// Valid values are PREFERENCE_CHARACTER and PREFERENCE_PLAYER.
/// See the documentation in [code/__DEFINES/preferences.dm].
var/savefile_identifier
/// The priority of when to apply this preference.
/// Used for when you need to rely on another preference.
var/priority = PREFERENCE_PRIORITY_DEFAULT
/// If set, will be available to randomize, but only if the preference
/// is for PREFERENCE_CHARACTER.
var/can_randomize = TRUE
/// If randomizable (PREFERENCE_CHARACTER and can_randomize), whether
/// or not to enable randomization by default.
/// This doesn't mean it'll always be random, but rather if a player
/// DOES have random body on, will this already be randomized?
var/randomize_by_default = TRUE
/// If the selected species has this in its /datum/species/mutant_bodyparts,
/// will show the feature as selectable.
var/relevant_mutant_bodypart = null
/// If the selected species has this in its /datum/species/body_markings,
/// will show the feature as selectable.
var/relevant_body_markings = null
/// If the selected species has this in its /datum/species/inherent_traits,
/// will show the feature as selectable.
var/relevant_inherent_trait = null
/// If the selected species has this in its /datum/species/var/external_organs,
/// will show the feature as selectable.
var/relevant_external_organ = null
/// If the selected species has this head_flag by default,
/// will show the feature as selectable.
var/relevant_head_flag = null
/// Called on the saved input when retrieving.
/// Also called by the value sent from the user through UI. Do not trust it.
/// Input is the value inside the savefile, output is to tell other code
/// what the value is.
/// This is useful either for more optimal data saving or for migrating
/// older data.
/// Must be overridden by subtypes.
/// Can return null if no value was found.
/datum/preference/proc/pref_deserialize(input, datum/preferences/preferences)
SHOULD_NOT_SLEEP(TRUE)
SHOULD_CALL_PARENT(FALSE)
CRASH("`pref_deserialize()` was not implemented on [type]!")
/// Called on the input while saving.
/// Input is the current value, output is what to save in the savefile.
/datum/preference/proc/pref_serialize(input)
SHOULD_NOT_SLEEP(TRUE)
return input
/// Produce a default, potentially random value for when no value for this
/// preference is found in the savefile.
/// Either this or create_informed_default_value must be overriden by subtypes.
/datum/preference/proc/create_default_value()
SHOULD_NOT_SLEEP(TRUE)
SHOULD_CALL_PARENT(FALSE)
CRASH("`create_default_value()` was not implemented on [type]!")
/// Produce a default, potentially random value for when no value for this
/// preference is found in the savefile.
/// Unlike create_default_value(), will provide the preferences object if you
/// need to use it.
/// If not overriden, will call create_default_value() instead.
/datum/preference/proc/create_informed_default_value(datum/preferences/preferences)
return create_default_value()
/// Produce a random value for the purposes of character randomization.
/// Will just create a default value by default.
/datum/preference/proc/create_random_value(datum/preferences/preferences)
return create_informed_default_value(preferences)
/// Returns whether or not a preference can be randomized.
/datum/preference/proc/is_randomizable()
SHOULD_NOT_OVERRIDE(TRUE)
return savefile_identifier == PREFERENCE_CHARACTER && can_randomize
/// Given a savefile, return either the saved data or an acceptable default.
/// This will write to the savefile if a value was not found with the new value.
/datum/preference/proc/read(list/save_data, datum/preferences/preferences)
SHOULD_NOT_OVERRIDE(TRUE)
var/value
if(!isnull(save_data))
value = save_data[savefile_key]
if(isnull(value))
return null
else
return pref_deserialize(value, preferences)
/// Given a savefile, writes the inputted value.
/// Returns TRUE for a successful application.
/// Return FALSE if it is invalid.
/datum/preference/proc/write(list/save_data, value)
SHOULD_NOT_OVERRIDE(TRUE)
if(!is_valid(value))
return FALSE
if(!isnull(save_data))
save_data[savefile_key] = pref_serialize(value)
return TRUE
/// Apply this preference onto the given client.
/// Called when the savefile_identifier == PREFERENCE_PLAYER.
/datum/preference/proc/apply_to_client(client/client, value)
SHOULD_NOT_SLEEP(TRUE)
SHOULD_CALL_PARENT(FALSE)
return
/// Fired when the preference is updated.
/// Calls apply_to_client by default, but can be overridden.
/datum/preference/proc/apply_to_client_updated(client/client, value)
SHOULD_NOT_SLEEP(TRUE)
apply_to_client(client, value)
/// Apply this preference onto the given human.
/// Must be overriden by subtypes.
/// Called when the savefile_identifier == PREFERENCE_CHARACTER.
/datum/preference/proc/apply_to_human(mob/living/carbon/human/target, value)
SHOULD_NOT_SLEEP(TRUE)
SHOULD_CALL_PARENT(FALSE)
CRASH("`apply_to_human()` was not implemented for [type]!")
/// Returns which savefile to use for a given savefile identifier
/datum/preferences/proc/get_save_data_for_savefile_identifier(savefile_identifier)
RETURN_TYPE(/list)
if(!client)
return null
if(!savefile)
CRASH("Attempted to get the savedata for [savefile_identifier] of [client] without a savefile. This should have been handled by load_preferences()")
// Both of these will cache savefiles, but only for a tick.
// This is because storing a savefile will lock it, causing later issues down the line.
// Do not change them to addtimer, since the timer SS might not be running at this time.
switch (savefile_identifier)
if(PREFERENCE_CHARACTER)
return savefile.get_entry("character[default_slot]")
if(PREFERENCE_PLAYER)
return savefile.get_entry()
else
CRASH("Unknown savefile identifier [savefile_identifier]")
/// Read a /datum/preference type and return its value.
/// This will write to the savefile if a value was not found with the new value.
/datum/preferences/proc/read_preference(preference_type)
var/datum/preference/preference_entry = GLOB.preference_entries[preference_type]
if(isnull(preference_entry))
var/extra_info = ""
// Current initializing subsystem is important to know because it might be a problem with
// things running pre-assets-initialization.
// if(!isnull(Master.current_initializing_subsystem))
// extra_info = "Info was attempted to be retrieved while [Master.current_initializing_subsystem] was initializing."
// else if(!MC_RUNNING())
// extra_info = "Info was attempted to be retrieved before the MC started, but not while it was actively initializing a subsystem"
CRASH("Preference type `[preference_type]` is invalid! [extra_info]")
if(preference_type in value_cache)
return value_cache[preference_type]
var/value = preference_entry.read(get_save_data_for_savefile_identifier(preference_entry.savefile_identifier), src)
if(isnull(value))
value = preference_entry.create_informed_default_value(src)
if(write_preference(preference_entry, value))
return value
else
CRASH("Couldn't write the default value for [preference_type] (received [value])")
value_cache[preference_type] = value
return value
/// Read a /datum/preference type and return its value.
/mob/proc/read_preference(preference_type)
return client?.prefs?.read_preference(preference_type)
/// Set a /datum/preference entry.
/// Returns TRUE for a successful preference application.
/// Returns FALSE if it is invalid.
/datum/preferences/proc/write_preference(datum/preference/preference, preference_value)
var/save_data = get_save_data_for_savefile_identifier(preference.savefile_identifier)
var/new_value = preference.pref_deserialize(preference_value, src)
var/success = preference.write(save_data, new_value)
if(success)
value_cache[preference.type] = new_value
return success
/// Will perform an update on the preference, but not write to the savefile.
/// This will, for instance, update the character preference view.
/// Performs sanity checks.
/datum/preferences/proc/update_preference(datum/preference/preference, preference_value)
if(!preference.is_accessible(src))
return FALSE
var/new_value = preference.pref_deserialize(preference_value, src)
var/success = preference.write(null, new_value)
if(!success)
return FALSE
recently_updated_keys |= preference.type
value_cache[preference.type] = new_value
if(preference.savefile_identifier == PREFERENCE_PLAYER)
preference.apply_to_client_updated(client, read_preference(preference.type))
else
update_preview_icon()
return TRUE
/// Checks that a given value is valid.
/// Must be overriden by subtypes.
/// Any type can be passed through.
/datum/preference/proc/is_valid(value)
SHOULD_NOT_SLEEP(TRUE)
SHOULD_CALL_PARENT(FALSE)
CRASH("`is_valid()` was not implemented for [type]!")
/// Returns data to be sent to users in the menu
/datum/preference/proc/compile_ui_data(mob/user, value)
SHOULD_NOT_SLEEP(TRUE)
return pref_serialize(value)
/// Returns data compiled into the preferences JSON asset
/datum/preference/proc/compile_constant_data()
SHOULD_NOT_SLEEP(TRUE)
return null
/// Returns whether or not this preference is accessible.
/// If FALSE, will not show in the UI and will not be editable (by update_preference).
/datum/preference/proc/is_accessible(datum/preferences/preferences)
SHOULD_CALL_PARENT(TRUE)
SHOULD_NOT_SLEEP(TRUE)
// if(
// !isnull(relevant_mutant_bodypart)
// || !isnull(relevant_inherent_trait)
// || !isnull(relevant_external_organ)
// || !isnull(relevant_head_flag)
// || !isnull(relevant_body_markings)
// )
// var/species_type = preferences.read_preference(/datum/preference/choiced/species)
// var/datum/species/species = GLOB.species_prototypes[species_type]
// if(!(savefile_key in species.get_features()))
// return FALSE
if(!should_show_on_page(preferences.current_window))
return FALSE
return TRUE
/// Returns whether or not, given the PREFERENCE_TAB_*, this preference should
/// appear.
/datum/preference/proc/should_show_on_page(preference_tab)
var/is_on_character_page = preference_tab == PREFERENCE_TAB_CHARACTER_PREFERENCES
var/is_character_preference = savefile_identifier == PREFERENCE_CHARACTER
return is_on_character_page == is_character_preference
/// A preference that is a choice of one option among a fixed set.
/// Used for preferences such as clothing.
/datum/preference/choiced
/// If this is TRUE, an icon will be generated for every value.
/// If you implement this, you must implement `icon_for(value)` for every possible value.
var/should_generate_icons = FALSE
var/list/cached_values
/// If the preference is a main feature (PREFERENCE_CATEGORY_FEATURES or PREFERENCE_CATEGORY_CLOTHING)
/// this is the name of the feature that will be presented.
var/main_feature_name
abstract_type = /datum/preference/choiced
/// Returns a list of every possible value.
/// The first time this is called, will run `init_values()`.
/// Return value can be in the form of:
/// - A flat list of raw values, such as list(MALE, FEMALE, PLURAL).
/// - An assoc list of raw values to atoms/icons.
/datum/preference/choiced/proc/get_choices()
// Override `init_values()` instead.
SHOULD_NOT_OVERRIDE(TRUE)
if(isnull(cached_values))
cached_values = init_possible_values()
ASSERT(cached_values.len)
return cached_values
/// Returns a list of every possible value, serialized.
/datum/preference/choiced/proc/get_choices_serialized()
// Override `init_values()` instead.
SHOULD_NOT_OVERRIDE(TRUE)
var/list/serialized_choices = list()
for(var/choice in get_choices())
serialized_choices += pref_serialize(choice)
return serialized_choices
/// Returns a list of every possible value.
/// This must be overriden by `/datum/preference/choiced` subtypes.
/// If `should_generate_icons` is TRUE, then you will also need to implement `icon_for(value)`
/// for every possible value.
/datum/preference/choiced/proc/init_possible_values()
CRASH("`init_possible_values()` was not implemented for [type]!")
/// When `should_generate_icons` is TRUE, this proc is called for every value.
/// It can return either an icon or a typepath to an atom to create.
/datum/preference/choiced/proc/icon_for(value)
SHOULD_CALL_PARENT(FALSE)
SHOULD_NOT_SLEEP(TRUE)
CRASH("`icon_for()` was not implemented for [type], even though should_generate_icons = TRUE!")
/datum/preference/choiced/is_valid(value)
return value in get_choices()
/datum/preference/choiced/pref_deserialize(input, datum/preferences/preferences)
return sanitize_inlist(input, get_choices(), create_default_value())
/datum/preference/choiced/create_default_value()
return pick(get_choices())
/datum/preference/choiced/compile_constant_data()
var/list/data = list()
var/list/choices = list()
for(var/choice in get_choices())
choices += choice
data["choices"] = choices
if(should_generate_icons)
var/list/icons = list()
// for(var/choice in choices) // TODO: Pref spritesheet asset
// icons[choice] = get_spritesheet_key(choice)
data["icons"] = icons
if(!isnull(main_feature_name))
data["name"] = main_feature_name
return data
/// A preference that represents an RGB color of something.
/// Will give the value as 6 hex digits, without a hash.
/datum/preference/color
abstract_type = /datum/preference/color
/datum/preference/color/pref_deserialize(input, datum/preferences/preferences)
return sanitize_hexcolor(input)
/datum/preference/color/create_default_value()
return random_color()
/datum/preference/color/pref_serialize(input)
return sanitize_hexcolor(input)
/datum/preference/color/is_valid(value)
return findtext(value, GLOB.is_color)
/// A numeric preference with a minimum and maximum value
/datum/preference/numeric
/// The minimum value
var/minimum
/// The maximum value
var/maximum
/// The step of the number, such as 1 for integers or 0.5 for half-steps.
var/step = 1
abstract_type = /datum/preference/numeric
/datum/preference/numeric/pref_deserialize(input, datum/preferences/preferences)
if(istext(input)) // Sometimes TGUI will return a string instead of a number, so we take that into account.
input = text2num(input) // Worst case, it's null, it'll just use create_default_value()
return sanitize_float(input, minimum, maximum, step, create_default_value())
/datum/preference/numeric/pref_serialize(input)
return sanitize_float(input, minimum, maximum, step, create_default_value())
/datum/preference/numeric/create_default_value()
return rand(minimum, maximum)
/datum/preference/numeric/is_valid(value)
return isnum(value) && value >= round(minimum, step) && value <= round(maximum, step)
/datum/preference/numeric/compile_constant_data()
return list(
"minimum" = minimum,
"maximum" = maximum,
"step" = step,
)
/// A preference whose value is always TRUE or FALSE
/datum/preference/toggle
abstract_type = /datum/preference/toggle
/// The default value of the toggle, if create_default_value is not specified
var/default_value = TRUE
/datum/preference/toggle/create_default_value()
return default_value
/datum/preference/toggle/pref_deserialize(input, datum/preferences/preferences)
return !!input
/datum/preference/toggle/is_valid(value)
return value == TRUE || value == FALSE
/// A string-based preference accepting arbitrary string values entered by the user, with a maximum length.
/datum/preference/text
abstract_type = /datum/preference/text
/// What is the maximum length of the value allowed in this field?
var/maximum_value_length = 256
/// Should we strip HTML the input or simply restrict it to the maximum_value_length?
var/should_strip_html = TRUE
/datum/preference/text/pref_deserialize(input, datum/preferences/preferences)
return should_strip_html ? STRIP_HTML_SIMPLE(input, maximum_value_length) : copytext(input, 1, maximum_value_length)
/datum/preference/text/create_default_value()
return ""
/datum/preference/text/is_valid(value)
return istext(value) && length(value) < maximum_value_length
/datum/preference/text/compile_constant_data()
return list("maximum_length" = maximum_value_length)
@@ -0,0 +1,52 @@
/// Preference middleware is code that helps to decentralize complicated preference features.
/datum/preference_middleware
/// The preferences datum
var/datum/preferences/preferences
/// The key that will be used for get_constant_data().
/// If null, will use the typepath minus /datum/preference_middleware.
var/key = null
/// Map of ui_act actions -> proc paths to call.
/// Signature is `(list/params, mob/user) -> TRUE/FALSE.
/// Return output is the same as ui_act--TRUE if it should update, FALSE if it should not
var/list/action_delegations = list()
/datum/preference_middleware/New(datum/preferences)
src.preferences = preferences
if (isnull(key))
// + 2 coming from the off-by-one of copytext, and then another from the slash
key = copytext("[type]", length("[parent_type]") + 2)
/datum/preference_middleware/Destroy()
preferences = null
return ..()
/// Append all of these into ui_data
/datum/preference_middleware/proc/get_ui_data(mob/user)
return list()
/// Append all of these into ui_static_data
/datum/preference_middleware/proc/get_ui_static_data(mob/user)
return list()
/// Append all of these into ui_assets
/datum/preference_middleware/proc/get_ui_assets()
return list()
/// Append all of these into /datum/asset/json/preferences.
/datum/preference_middleware/proc/get_constant_data()
return null
/// Merge this into the result of compile_character_preferences.
/datum/preference_middleware/proc/get_character_preferences(mob/user)
return null
/// Called every set_preference, returns TRUE if this handled it.
/datum/preference_middleware/proc/pre_set_preference(mob/user, preference, value)
return FALSE
/// Called when a character is changed.
/datum/preference_middleware/proc/on_new_character(mob/user)
return
@@ -0,0 +1,9 @@
/// Transforms the bay style `"preferences": ["SOUND_MIDI", ...]` to `"SOUND_MIDI": 1` and `"preferences_disabled": [...]` to `0`.
/datum/preferences/proc/migration_13_preferences(datum/json_savefile/S)
var/list/preferences_enabled = S.get_entry("preferences")
for(var/key in preferences_enabled)
S.set_entry("[key]", TRUE)
var/list/preferences_disabled = S.get_entry("preferences_disabled")
for(var/key in preferences_disabled)
S.set_entry("[key]", FALSE)
@@ -0,0 +1,28 @@
// Contains all of the variables and such to make tg prefs work
/datum/preferences
/// The savefile relating to character preferences, PREFERENCE_CHARACTER
var/list/character_data
/// A list of keys that have been updated since the last save.
var/list/recently_updated_keys = list()
/// A cache of preference entries to values.
/// Used to avoid expensive READ_FILE every time a preference is retrieved.
var/value_cache = list()
/// If set to TRUE, will update character_profiles on the next ui_data tick.
var/tainted_character_profiles = FALSE
var/current_window = PREFERENCE_TAB_GAME_PREFERENCES
/// A list of instantiated middleware
var/list/datum/preference_middleware/middleware = list()
/// Applies all PREFERENCE_PLAYER preferences
/datum/preferences/proc/apply_all_client_preferences()
for(var/datum/preference/preference as anything in get_preferences_in_priority_order())
if(preference.savefile_identifier != PREFERENCE_PLAYER)
continue
value_cache -= preference.type
preference.apply_to_client(client, read_preference(preference.type))
@@ -0,0 +1,73 @@
/datum/preference/toggle/show_attack_logs
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "CHAT_ATTACKLOGS"
default_value = FALSE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/show_attack_logs/is_accessible(datum/preferences/preferences)
. = ..()
if(!.)
return
return check_rights(R_MOD|R_ADMIN, FALSE, preferences.client)
/datum/preference/toggle/show_debug_logs
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "CHAT_DEBUGLOGS"
default_value = FALSE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/show_debug_logs/is_accessible(datum/preferences/preferences)
. = ..()
if(!.)
return
return check_rights(R_DEBUG|R_ADMIN, FALSE, preferences.client)
/datum/preference/toggle/show_chat_prayers
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "CHAT_PRAYER"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/show_chat_prayers/is_accessible(datum/preferences/preferences)
. = ..()
if(!.)
return
return check_rights(R_EVENT|R_ADMIN, FALSE, preferences.client)
// General holder prefs
/datum/preference/toggle/holder
abstract_type = /datum/preference/toggle/holder
/datum/preference/toggle/holder/is_accessible(datum/preferences/preferences)
. = ..(preferences)
if(!.)
return
return preferences.client.holder
/datum/preference/toggle/holder/play_adminhelp_ping
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SOUND_ADMINHELP"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/holder/hear_radio
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "CHAT_RADIO"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/holder/show_rlooc
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "CHAT_RLOOC"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/holder/show_staff_dsay
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "CHAT_ADSAY"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
@@ -0,0 +1,69 @@
/datum/preference/toggle/chat_tags
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "CHAT_SHOWICONS"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/show_typing_indicator
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SHOW_TYPING"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/show_typing_indicator/apply_to_client(client/client, value)
if(!value)
client.stop_thinking()
/datum/preference/toggle/show_typing_indicator_subtle
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SHOW_TYPING_SUBTLE"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/show_ooc
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "CHAT_OOC"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/show_looc
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "CHAT_LOOC"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/show_dsay
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "CHAT_DEAD"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/check_mention
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "CHAT_MENTION"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/vore_health_bars
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "VORE_HEALTH_BARS"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/show_lore_news
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "NEWS_POPUP"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/player_tips
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "RECEIVE_TIPS"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/pain_frequency
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "PAIN_FREQUENCY"
default_value = FALSE
savefile_identifier = PREFERENCE_PLAYER
@@ -0,0 +1,29 @@
/datum/preference/toggle/whisubtle_vis
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "WHISUBTLE_VIS"
default_value = FALSE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/ghost_see_whisubtle
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "GHOST_SEE_WHISUBTLE"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/ghost_ears
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "CHAT_GHOSTEARS"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/ghost_sight
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "CHAT_GHOSTSIGHT"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/ghost_radio
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "CHAT_GHOSTRADIO"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
@@ -0,0 +1,71 @@
/// Whether or not to toggle ambient occlusion, the shadows around people
/datum/preference/toggle/ambient_occlusion
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "AMBIENT_OCCLUSION_PREF"
default_value = FALSE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/ambient_occlusion/apply_to_client(client/client, value)
var/datum/plane_holder/PH = client?.mob?.plane_holder
if(PH)
PH.set_ao(VIS_OBJS, value)
PH.set_ao(VIS_MOBS, value)
/datum/preference/toggle/mob_tooltips
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "MOB_TOOLTIPS"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/inv_tooltips
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "INV_TOOLTIPS"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/attack_icons
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "ATTACK_ICONS"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/precision_placement
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "PRECISE_PLACEMENT"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/hotkeys_default
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "HUD_HOTKEYS"
default_value = FALSE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/show_progress_bar
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SHOW_PROGRESS"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/safefiring
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SAFE_FIRING"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/status_indicators
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SHOW_STATUS"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/status_indicators/apply_to_client(client/client, value)
var/datum/plane_holder/PH = client?.mob?.plane_holder
if(PH)
PH.set_vis(VIS_STATUS, value)
/datum/preference/toggle/auto_afk
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "AUTO_AFK"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
@@ -0,0 +1,23 @@
/datum/preference/toggle/runechat_mob
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "RUNECHAT_MOB"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/runechat_obj
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "RUNECHAT_OBJ"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/runechat_border
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "RUNECHAT_BORDER"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/runechat_long_messages
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "RUNECHAT_LONG"
default_value = FALSE
savefile_identifier = PREFERENCE_PLAYER
@@ -0,0 +1,149 @@
/datum/preference/toggle/play_admin_midis
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SOUND_MIDI"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/play_lobby_music
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SOUND_LOBBY"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/play_lobby_music/apply_to_client(client/client, value)
if(value)
client?.playtitlemusic()
else
client?.media?.stop_music()
/datum/preference/toggle/play_ambience
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SOUND_AMBIENCE"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/play_ambience/apply_to_client(client/client, value)
if(!value)
client << sound(null, repeat = 0, wait = 0, volume = 0, channel = CHANNEL_AMBIENCE_FORCED)
client << sound(null, repeat = 0, wait = 0, volume = 0, channel = CHANNEL_AMBIENCE)
/datum/preference/toggle/play_jukebox
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SOUND_JUKEBOX"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/play_jukebox/apply_to_client(client/client, value)
if(value)
client?.mob?.update_music()
else
client?.mob?.stop_all_music()
/datum/preference/toggle/instrument_toggle
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SOUND_INSTRUMENT"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/emote_noises
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "EMOTE_NOISES"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/radio_sounds
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "RADIO_SOUNDS"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/say_sounds
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SAY_SOUNDS"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/emote_sounds
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "EMOTE_SOUNDS"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/whisper_sounds
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "WHISPER_SOUNDS"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/subtle_sounds
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SUBTLE_SOUNDS"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/air_pump_noise
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SOUND_AIRPUMP"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/old_door_sounds
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SOUND_OLDDOORS"
default_value = FALSE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/department_door_sounds
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SOUND_DEPARTMENTDOORS"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/pickup_sounds
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SOUND_PICKED"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/drop_sounds
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SOUND_DROPPED"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/weather_sounds
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SOUND_WEATHER"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/supermatter_hum
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SOUND_SUPERMATTER"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/play_mentorhelp_ping
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "SOUND_MENTORHELP"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
// Vorey sounds
/datum/preference/toggle/belch_noises // Belching noises - pref toggle for 'em
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "BELCH_NOISES"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/eating_noises
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "EATING_NOISES"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/digestion_noises
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "DIGEST_NOISES"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
@@ -0,0 +1,34 @@
/datum/preference/toggle/browser_style
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "BROWSER_STYLED"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/vchat_enable
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "VCHAT_ENABLE"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/vchat_enable/apply_to_client(client/client, value)
if(value)
client.tgui_panel.oldchat = FALSE
else
client.tgui_panel.oldchat = TRUE
client.nuke_chat()
/datum/preference/toggle/tgui_say
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "TGUI_SAY"
default_value = TRUE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/tgui_say_light
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "TGUI_SAY_LIGHT_MODE"
default_value = FALSE
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/tgui_say_light/apply_to_client(client/client, value)
client.tgui_say?.load()