mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-24 21:48:39 +01:00
tgui Preferences Menu + total rewrite of the preferences backend (#61313)
About The Pull Request Rewrites the entire preferences menu in tgui. Rewrites the entire backend to be built upon datumized preferences, rather than constant additions to the preferences base datum. Splits game preferences into its own window. Antagonists are now split into their individual rulesets. You can now be a roundstart heretic without signing up for latejoin heretic, as an example. This iteration matches parity, and provides very little new functionality, but adding anything new will be much easier. Fixes #60823 Fixes #28907 Fixes #44887 Fixes #59912 Fixes #58458 Fixes #59181 Major TODOs Quirk icons, from @Fikou (with some slight adjustments from me) Lore text, from @EOBGames (4/6, need moths and then ethereal lore from @AMonkeyThatCodes) Heavy documentation on how one would add new preferences, species, jobs, etc A lot of specialized testing so that people's real data don't get corrupted Changelog cl Mothblocks, Floyd on lots of the design refactor: The preferences menu has been completely rewritten in tgui. refactor: The "Stop Sounds" verb has been moved to OOC. /cl
This commit is contained in:
@@ -211,3 +211,6 @@
|
||||
|
||||
/// If the client is currently under the restrictions of the interview system
|
||||
var/interviewee = FALSE
|
||||
|
||||
/// Whether or not this client has standard hotkeys enabled
|
||||
var/hotkeys = TRUE
|
||||
|
||||
@@ -106,13 +106,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
|
||||
hsrc = holder
|
||||
if("usr")
|
||||
hsrc = mob
|
||||
if("prefs")
|
||||
if (inprefs)
|
||||
return
|
||||
inprefs = TRUE
|
||||
. = prefs.process_link(usr,href_list)
|
||||
inprefs = FALSE
|
||||
return
|
||||
if("vars")
|
||||
return view_var_Topic(href,href_list,hsrc)
|
||||
|
||||
@@ -250,7 +243,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
|
||||
GLOB.preferences_datums[ckey] = prefs
|
||||
prefs.last_ip = address //these are gonna be used for banning
|
||||
prefs.last_id = computer_id //these are gonna be used for banning
|
||||
fps = (prefs.clientfps < 0) ? RECOMMENDED_FPS : prefs.clientfps
|
||||
|
||||
if(fexists(roundend_report_file()))
|
||||
add_verb(src, /client/proc/show_previous_roundend_report)
|
||||
@@ -409,7 +401,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
|
||||
adminGreet()
|
||||
if (mob && reconnecting)
|
||||
var/stealth_admin = mob.client?.holder?.fakekey
|
||||
var/announce_leave = mob.client?.prefs?.broadcast_login_logout
|
||||
var/announce_leave = mob.client?.prefs?.read_preference(/datum/preference/toggle/broadcast_login_logout)
|
||||
if (!stealth_admin)
|
||||
deadchat_broadcast(" has reconnected.", "<b>[mob][mob.get_realname_string()]</b>", follow_target = mob, turf_target = get_turf(mob), message_type = DEADCHAT_LOGIN_LOGOUT, admin_only=!announce_leave)
|
||||
add_verbs_from_config()
|
||||
@@ -468,7 +460,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
|
||||
if (!interviewee)
|
||||
initialize_menus()
|
||||
|
||||
view_size = new(src, getScreenSize(prefs.widescreenpref))
|
||||
view_size = new(src, getScreenSize(prefs.read_preference(/datum/preference/toggle/widescreen)))
|
||||
view_size.resetFormat()
|
||||
view_size.setZoomMode()
|
||||
Master.UpdateTickRate()
|
||||
@@ -486,7 +478,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
|
||||
/client/Destroy()
|
||||
if(mob)
|
||||
var/stealth_admin = mob.client?.holder?.fakekey
|
||||
var/announce_join = mob.client?.prefs?.broadcast_login_logout
|
||||
var/announce_join = mob.client?.prefs?.read_preference(/datum/preference/toggle/broadcast_login_logout)
|
||||
if (!stealth_admin)
|
||||
deadchat_broadcast(" has disconnected.", "<b>[mob][mob.get_realname_string()]</b>", follow_target = mob, turf_target = get_turf(mob), message_type = DEADCHAT_LOGIN_LOGOUT, admin_only=!announce_join)
|
||||
|
||||
@@ -894,7 +886,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
|
||||
to_chat(src, span_danger("Your previous click was ignored because you've done too many in a second"))
|
||||
return
|
||||
|
||||
if (prefs.hotkeys)
|
||||
if (hotkeys)
|
||||
// If hotkey mode is enabled, then clicking the map will automatically
|
||||
// unfocus the text bar. This removes the red color from the text bar
|
||||
// so that the visual focus indicator matches reality.
|
||||
@@ -986,8 +978,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
|
||||
if(!D?.key_bindings)
|
||||
return
|
||||
movement_keys = list()
|
||||
for(var/key in D.key_bindings)
|
||||
for(var/kb_name in D.key_bindings[key])
|
||||
for(var/kb_name in D.key_bindings)
|
||||
for(var/key in D.key_bindings[kb_name])
|
||||
switch(kb_name)
|
||||
if("North")
|
||||
movement_keys[key] = NORTH
|
||||
@@ -1015,7 +1007,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
|
||||
if (isliving(mob))
|
||||
var/mob/living/M = mob
|
||||
M.update_damage_hud()
|
||||
if (prefs.auto_fit_viewport)
|
||||
if (prefs.read_preference(/datum/preference/toggle/auto_fit_viewport))
|
||||
addtimer(CALLBACK(src,.verb/fit_viewport,10)) //Delayed to avoid wingets from Login calls.
|
||||
|
||||
/client/proc/generate_clickcatcher()
|
||||
@@ -1032,35 +1024,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
|
||||
if(prefs && prefs.chat_toggles & CHAT_PULLR)
|
||||
to_chat(src, announcement)
|
||||
|
||||
/client/proc/show_character_previews(mutable_appearance/source)
|
||||
LAZYINITLIST(char_render_holders)
|
||||
if(!LAZYLEN(char_render_holders))
|
||||
for(var/plane_master_path as anything in subtypesof(/atom/movable/screen/plane_master))
|
||||
var/atom/movable/screen/plane_master/plane_master = new plane_master_path()
|
||||
char_render_holders["plane_master-[plane_master.plane]"] = plane_master
|
||||
plane_master.backdrop(mob)
|
||||
screen |= plane_master
|
||||
plane_master.screen_loc = "character_preview_map:0,CENTER"
|
||||
|
||||
var/pos = 0
|
||||
for(var/dir in GLOB.cardinals)
|
||||
pos++
|
||||
var/atom/movable/screen/preview = char_render_holders["preview-[dir]"]
|
||||
if(!preview)
|
||||
preview = new
|
||||
char_render_holders["preview-[dir]"] = preview
|
||||
screen |= preview
|
||||
preview.appearance = source
|
||||
preview.dir = dir
|
||||
preview.screen_loc = "character_preview_map:0,[pos]"
|
||||
|
||||
/client/proc/clear_character_previews()
|
||||
for(var/index in char_render_holders)
|
||||
var/atom/movable/screen/S = char_render_holders[index]
|
||||
screen -= S
|
||||
qdel(S)
|
||||
char_render_holders = null
|
||||
|
||||
///Redirect proc that makes it easier to call the unlock achievement proc. Achievement type is the typepath to the award, user is the mob getting the award, and value is an optional variable used for leaderboard value increments
|
||||
/client/proc/give_award(achievement_type, mob/user, value = 1)
|
||||
return player_details.achievements.unlock(achievement_type, user, value)
|
||||
@@ -1128,11 +1091,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
|
||||
if (verbpath.name[1] != "@")
|
||||
new child(src)
|
||||
|
||||
for (var/thing in prefs.menuoptions)
|
||||
var/datum/verbs/menu/menuitem = GLOB.menulist[thing]
|
||||
if (menuitem)
|
||||
menuitem.Load_checked(src)
|
||||
|
||||
/client/proc/open_filter_editor(atom/in_atom)
|
||||
if(holder)
|
||||
holder.filteriffic = new /datum/filter_editor(in_atom)
|
||||
@@ -1185,3 +1143,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
|
||||
|
||||
var/mob/dead/observer/observer = mob
|
||||
observer.ManualFollow(target)
|
||||
|
||||
/client/verb/stop_client_sounds()
|
||||
set name = "Stop Sounds"
|
||||
set category = "OOC"
|
||||
set desc = "Stop Current Sounds"
|
||||
SEND_SOUND(usr, sound(null))
|
||||
tgui_panel?.stop_music()
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Stop Self Sounds"))
|
||||
|
||||
+346
-1910
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,476 @@
|
||||
# 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, only shown in game preferences
|
||||
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: Feature<number> = {
|
||||
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.
|
||||
|
||||
// Instead of returning a flat list, this now returns an assoc list
|
||||
// of values to icons.
|
||||
/datum/preference/choiced/favorite_drink/init_possible_values()
|
||||
return list(
|
||||
"Milk" = icon('drinks.dmi', "milk"),
|
||||
"Cola" = icon('drinks.dmi', "cola"),
|
||||
"Water" = 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 `preferences.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"
|
||||
```
|
||||
|
||||

|
||||
|
||||
> 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/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.
|
||||
|
||||
---
|
||||
|
||||
## Jobs
|
||||
|
||||
Every job must have an associated `.ts` file so that the UI knows where to place it.
|
||||
|
||||
Create a file in `tgui/packages/tgui/interfaces/PreferencesMenu/jobs/jobs/`.
|
||||
|
||||
This will specify the description and department of the job. Here is the details for the medical doctor:
|
||||
|
||||
```ts
|
||||
import { Job } from "../base";
|
||||
import { Service } from "../departments";
|
||||
|
||||
const Cook: Job = {
|
||||
name: "Cook",
|
||||
// If you need more room, use `multiline`
|
||||
description: "Serve food, cook meat, keep the crew fed.",
|
||||
department: Service,
|
||||
};
|
||||
|
||||
export default Cook;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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:
|
||||
|
||||

|
||||
|
||||
## 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,534 @@
|
||||
// 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
|
||||
|
||||
/// The priority at which gender is determined, needed for proper randomization.
|
||||
#define PREFERENCE_PRIORITY_GENDER 3
|
||||
|
||||
/// The priority at which body type is decided, applied after gender so we can
|
||||
/// make sure they're non-binary.
|
||||
#define PREFERENCE_PRIORITY_BODY_TYPE 4
|
||||
|
||||
/// The priority at which names are decided, needed for proper randomization.
|
||||
#define PREFERENCE_PRIORITY_NAMES 5
|
||||
|
||||
/// The maximum preference priority, keep this updated, but don't use it for `priority`.
|
||||
#define MAX_PREFERENCE_PRIORITY PREFERENCE_PRIORITY_NAMES
|
||||
|
||||
/// 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 (initial(preference_type.abstract_type) == 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 (initial(preference_type.abstract_type) == 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)
|
||||
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.
|
||||
var/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/species_traits,
|
||||
/// will show the feature as selectable.
|
||||
var/relevant_species_trait = 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/deserialize(input, datum/preferences/preferences)
|
||||
SHOULD_NOT_SLEEP(TRUE)
|
||||
SHOULD_CALL_PARENT(FALSE)
|
||||
CRASH("`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/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(savefile/savefile, datum/preferences/preferences)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
|
||||
var/value
|
||||
|
||||
if (!isnull(savefile))
|
||||
READ_FILE(savefile[savefile_key], value)
|
||||
|
||||
if (isnull(value))
|
||||
return null
|
||||
else
|
||||
return 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(savefile/savefile, value)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
|
||||
if (!is_valid(value))
|
||||
return FALSE
|
||||
|
||||
if (!isnull(savefile))
|
||||
WRITE_FILE(savefile[savefile_key], 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_savefile_for_savefile_identifier(savefile_identifier)
|
||||
RETURN_TYPE(/savefile)
|
||||
|
||||
if (!parent)
|
||||
return null
|
||||
|
||||
switch (savefile_identifier)
|
||||
if (PREFERENCE_CHARACTER)
|
||||
return character_savefile
|
||||
if (PREFERENCE_PLAYER)
|
||||
if (!game_savefile)
|
||||
game_savefile = new /savefile(path)
|
||||
game_savefile.cd = "/"
|
||||
return game_savefile
|
||||
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."
|
||||
|
||||
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_savefile_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
|
||||
|
||||
/// 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/savefile = get_savefile_for_savefile_identifier(preference.savefile_identifier)
|
||||
var/new_value = preference.deserialize(preference_value, src)
|
||||
var/success = preference.write(savefile, 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.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(parent, read_preference(preference.type))
|
||||
else
|
||||
character_preview_view?.update_body()
|
||||
|
||||
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 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_species_trait))
|
||||
var/species_type = preferences.read_preference(/datum/preference/choiced/species)
|
||||
|
||||
var/datum/species/species = new 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, icons will be generated.
|
||||
/// This is necessary for if your `init_possible_values()` override
|
||||
/// returns an assoc list of names to atoms/icons.
|
||||
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.
|
||||
/// Return value can be in the form of:
|
||||
/// - A flat list of serialized values, such as list(MALE, FEMALE, PLURAL).
|
||||
/// - An assoc list of serialized values to atoms/icons.
|
||||
/datum/preference/choiced/proc/get_choices_serialized()
|
||||
// Override `init_values()` instead.
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
|
||||
var/list/serialized_choices = list()
|
||||
var/choices = get_choices()
|
||||
|
||||
if (should_generate_icons)
|
||||
for (var/choice in choices)
|
||||
serialized_choices[serialize(choice)] = choices[choice]
|
||||
else
|
||||
for (var/choice in choices)
|
||||
serialized_choices += serialize(choice)
|
||||
|
||||
return serialized_choices
|
||||
|
||||
/// Returns a list of every possible value.
|
||||
/// This must be overriden by `/datum/preference/choiced` subtypes.
|
||||
/// 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, in which case
|
||||
/// icons will be generated.
|
||||
/datum/preference/choiced/proc/init_possible_values()
|
||||
CRASH("`init_possible_values()` was not implemented for [type]!")
|
||||
|
||||
/datum/preference/choiced/is_valid(value)
|
||||
return value in get_choices()
|
||||
|
||||
/datum/preference/choiced/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)
|
||||
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, crunched down to 3 hex numbers.
|
||||
/// Was used heavily in the past, but doesn't provide as much range and only barely conserves space.
|
||||
/datum/preference/color_legacy
|
||||
abstract_type = /datum/preference/color_legacy
|
||||
|
||||
/datum/preference/color_legacy/deserialize(input, datum/preferences/preferences)
|
||||
return sanitize_hexcolor(input)
|
||||
|
||||
/datum/preference/color_legacy/create_default_value()
|
||||
return random_short_color()
|
||||
|
||||
/datum/preference/color_legacy/is_valid(value)
|
||||
var/static/regex/is_legacy_color = regex(@"^[0-9a-fA-F]{3}$")
|
||||
return findtext(value, is_legacy_color)
|
||||
|
||||
/// 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/deserialize(input, datum/preferences/preferences)
|
||||
return sanitize_color(input)
|
||||
|
||||
/datum/preference/color/create_default_value()
|
||||
return random_color()
|
||||
|
||||
/datum/preference/color/is_valid(value)
|
||||
return findtext(value, GLOB.is_color)
|
||||
|
||||
/// Takes an assoc list of names to /datum/sprite_accessory and returns a value
|
||||
/// fit for `/datum/preference/init_possible_values()`
|
||||
/proc/possible_values_for_sprite_accessory_list(list/datum/sprite_accessory/sprite_accessories)
|
||||
var/list/possible_values = list()
|
||||
for (var/name in sprite_accessories)
|
||||
var/datum/sprite_accessory/sprite_accessory = sprite_accessories[name]
|
||||
if (istype(sprite_accessory))
|
||||
possible_values[name] = icon(sprite_accessory.icon, sprite_accessory.icon_state)
|
||||
else
|
||||
// This means it didn't have an icon state
|
||||
possible_values[name] = icon('icons/mob/landmarks.dmi', "x")
|
||||
return possible_values
|
||||
|
||||
/// Takes an assoc list of names to /datum/sprite_accessory and returns a value
|
||||
/// fit for `/datum/preference/init_possible_values()`
|
||||
/// Different from `possible_values_for_sprite_accessory_list` in that it takes a list of layers
|
||||
/// such as BEHIND, FRONT, and ADJ.
|
||||
/// It also takes a "body part name", such as body_markings, moth_wings, etc
|
||||
/// They are expected to be in order from lowest to top.
|
||||
/proc/possible_values_for_sprite_accessory_list_for_body_part(
|
||||
list/datum/sprite_accessory/sprite_accessories,
|
||||
body_part,
|
||||
list/layers,
|
||||
)
|
||||
var/list/possible_values = list()
|
||||
|
||||
for (var/name in sprite_accessories)
|
||||
var/datum/sprite_accessory/sprite_accessory = sprite_accessories[name]
|
||||
|
||||
var/icon/final_icon
|
||||
|
||||
for (var/layer in layers)
|
||||
var/icon/icon = icon(sprite_accessory.icon, "m_[body_part]_[sprite_accessory.icon_state]_[layer]")
|
||||
|
||||
if (isnull(final_icon))
|
||||
final_icon = icon
|
||||
else
|
||||
final_icon.Blend(icon, ICON_OVERLAY)
|
||||
|
||||
possible_values[name] = final_icon
|
||||
|
||||
return possible_values
|
||||
|
||||
/// 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/deserialize(input, datum/preferences/preferences)
|
||||
return sanitize_float(input, minimum, maximum, step, create_default_value())
|
||||
|
||||
/datum/preference/numeric/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 >= minimum && value <= maximum
|
||||
|
||||
/datum/preference/numeric/compile_constant_data()
|
||||
return list(
|
||||
"minimum" = minimum,
|
||||
"maximum" = maximum,
|
||||
"step" = step,
|
||||
)
|
||||
|
||||
/// A prefernece 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/deserialize(input, datum/preferences/preferences)
|
||||
return !!input
|
||||
|
||||
/datum/preference/toggle/is_valid(value)
|
||||
return value == TRUE || value == FALSE
|
||||
@@ -0,0 +1,41 @@
|
||||
/datum/preference/color/asay_color
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "asaycolor"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/color/asay_color/create_default_value()
|
||||
return DEFAULT_ASAY_COLOR
|
||||
|
||||
/datum/preference/color/asay_color/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return is_admin(preferences.parent) && CONFIG_GET(flag/allow_admin_asaycolor)
|
||||
|
||||
/// What outfit to equip when spawning as a briefing officer for an ERT
|
||||
/datum/preference/choiced/brief_outfit
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "brief_outfit"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/choiced/brief_outfit/deserialize(input, datum/preferences/preferences)
|
||||
var/path = text2path(input)
|
||||
if (!ispath(path, /datum/outfit))
|
||||
return create_default_value()
|
||||
|
||||
return path
|
||||
|
||||
/datum/preference/choiced/brief_outfit/serialize(input)
|
||||
return "[input]"
|
||||
|
||||
/datum/preference/choiced/brief_outfit/create_default_value()
|
||||
return /datum/outfit/centcom/commander
|
||||
|
||||
/datum/preference/choiced/brief_outfit/init_possible_values()
|
||||
return subtypesof(/datum/outfit)
|
||||
|
||||
/datum/preference/choiced/brief_outfit/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return is_admin(preferences.parent)
|
||||
@@ -0,0 +1,10 @@
|
||||
/datum/preference/numeric/age
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_key = "age"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
|
||||
minimum = AGE_MIN
|
||||
maximum = AGE_MAX
|
||||
|
||||
/datum/preference/numeric/age/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.age = value
|
||||
@@ -0,0 +1,25 @@
|
||||
/// What to show on the AI screen
|
||||
/datum/preference/choiced/ai_core_display
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "preferred_ai_core_display"
|
||||
should_generate_icons = TRUE
|
||||
|
||||
/datum/preference/choiced/ai_core_display/init_possible_values()
|
||||
var/list/values = list()
|
||||
|
||||
values["Random"] = icon('icons/mob/ai.dmi', "ai-empty")
|
||||
|
||||
for (var/screen in GLOB.ai_core_display_screens - "Portrait" - "Random")
|
||||
values[screen] = icon('icons/mob/ai.dmi', resolve_ai_icon_sync(screen))
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/choiced/ai_core_display/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return istype(preferences.get_highest_priority_job(), /datum/job/ai)
|
||||
|
||||
/datum/preference/choiced/ai_core_display/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
@@ -0,0 +1,12 @@
|
||||
/// Whether or not to toggle ambient occlusion, the shadows around people
|
||||
/datum/preference/toggle/ambient_occlusion
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "ambientocclusion"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/ambient_occlusion/apply_to_client(client/client, value)
|
||||
var/atom/movable/screen/plane_master/game_world/plane_master = locate() in client?.screen
|
||||
if (!plane_master)
|
||||
return
|
||||
|
||||
plane_master.backdrop(client.mob)
|
||||
@@ -0,0 +1,66 @@
|
||||
/// Assets generated from `/datum/preference` icons
|
||||
/datum/asset/spritesheet/preferences
|
||||
name = "preferences"
|
||||
early = TRUE
|
||||
|
||||
/datum/asset/spritesheet/preferences/register()
|
||||
var/list/to_insert = list()
|
||||
|
||||
for (var/preference_key in GLOB.preference_entries_by_key)
|
||||
var/datum/preference/choiced/preference = GLOB.preference_entries_by_key[preference_key]
|
||||
if (!istype(preference))
|
||||
continue
|
||||
|
||||
if (!preference.should_generate_icons)
|
||||
continue
|
||||
|
||||
var/list/choices = preference.get_choices_serialized()
|
||||
for (var/preference_value in choices)
|
||||
var/create_icon_of = choices[preference_value]
|
||||
|
||||
var/icon/icon
|
||||
var/icon_state
|
||||
|
||||
if (ispath(create_icon_of, /atom))
|
||||
var/atom/atom_icon_source = create_icon_of
|
||||
icon = initial(atom_icon_source.icon)
|
||||
icon_state = initial(atom_icon_source.icon_state)
|
||||
else if (isicon(create_icon_of))
|
||||
icon = create_icon_of
|
||||
else
|
||||
CRASH("[create_icon_of] is an invalid preference value (from [preference_key]:[preference_value]).")
|
||||
|
||||
to_insert[preference.get_spritesheet_key(preference_value)] = list(icon, icon_state)
|
||||
|
||||
for (var/spritesheet_key in to_insert)
|
||||
var/list/inserting = to_insert[spritesheet_key]
|
||||
Insert(spritesheet_key, inserting[1], inserting[2])
|
||||
|
||||
return ..()
|
||||
|
||||
/// Returns the key that will be used in the spritesheet for a given value.
|
||||
/datum/preference/proc/get_spritesheet_key(value)
|
||||
return "[savefile_key]___[sanitize_css_class_name(value)]"
|
||||
|
||||
/// Sends information needed for shared details on individual preferences
|
||||
/datum/asset/json/preferences
|
||||
name = "preferences"
|
||||
|
||||
/datum/asset/json/preferences/generate()
|
||||
var/list/preference_data = list()
|
||||
|
||||
for (var/middleware_type in subtypesof(/datum/preference_middleware))
|
||||
var/datum/preference_middleware/middleware = new middleware_type
|
||||
var/data = middleware.get_constant_data()
|
||||
if (!isnull(data))
|
||||
preference_data[middleware.key] = data
|
||||
|
||||
qdel(middleware)
|
||||
|
||||
for (var/preference_type in GLOB.preference_entries)
|
||||
var/datum/preference/preference_entry = GLOB.preference_entries[preference_type]
|
||||
var/data = preference_entry.compile_constant_data()
|
||||
if (!isnull(data))
|
||||
preference_data[preference_entry.savefile_key] = data
|
||||
|
||||
return preference_data
|
||||
@@ -0,0 +1,7 @@
|
||||
/datum/preference/toggle/auto_fit_viewport
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "auto_fit_viewport"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/auto_fit_viewport/apply_to_client_updated(client/client, value)
|
||||
INVOKE_ASYNC(client, /client/verb/fit_viewport)
|
||||
@@ -0,0 +1,21 @@
|
||||
/datum/preference/choiced/body_type
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
priority = PREFERENCE_PRIORITY_BODY_TYPE
|
||||
savefile_key = "body_type"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
|
||||
/datum/preference/choiced/body_type/init_possible_values()
|
||||
return list(MALE, FEMALE)
|
||||
|
||||
/datum/preference/choiced/body_type/apply_to_human(mob/living/carbon/human/target, value)
|
||||
if (target.gender != MALE && target.gender != FEMALE)
|
||||
target.body_type = value
|
||||
else
|
||||
target.body_type = target.gender
|
||||
|
||||
/datum/preference/choiced/body_type/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
var/gender = preferences.read_preference(/datum/preference/choiced/gender)
|
||||
return gender != MALE && gender != FEMALE
|
||||
@@ -0,0 +1,5 @@
|
||||
/// Whether or not to announce when the player logs in or out.
|
||||
/datum/preference/toggle/broadcast_login_logout
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "broadcast_login_logout"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
@@ -0,0 +1,5 @@
|
||||
/datum/preference/toggle/buttons_locked
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "buttons_locked"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
default_value = FALSE
|
||||
@@ -0,0 +1,151 @@
|
||||
/proc/generate_values_for_underwear(list/accessory_list, list/icons, color)
|
||||
var/icon/lower_half = icon('icons/blanks/32x32.dmi', "nothing")
|
||||
|
||||
for (var/icon in icons)
|
||||
lower_half.Blend(icon('icons/mob/human_parts_greyscale.dmi', icon), ICON_OVERLAY)
|
||||
|
||||
var/list/values = list()
|
||||
|
||||
for (var/accessory_name in accessory_list)
|
||||
var/icon/icon_with_socks = new(lower_half)
|
||||
|
||||
if (accessory_name != "Nude")
|
||||
var/datum/sprite_accessory/accessory = accessory_list[accessory_name]
|
||||
|
||||
var/icon/accessory_icon = icon('icons/mob/clothing/underwear.dmi', accessory.icon_state)
|
||||
if (color && !accessory.use_static)
|
||||
accessory_icon.Blend(color, ICON_MULTIPLY)
|
||||
icon_with_socks.Blend(accessory_icon, ICON_OVERLAY)
|
||||
|
||||
icon_with_socks.Crop(10, 1, 22, 13)
|
||||
icon_with_socks.Scale(32, 32)
|
||||
|
||||
values[accessory_name] = icon_with_socks
|
||||
|
||||
return values
|
||||
|
||||
/// Backpack preference
|
||||
/datum/preference/choiced/backpack
|
||||
savefile_key = "backpack"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
main_feature_name = "Backpack"
|
||||
category = PREFERENCE_CATEGORY_CLOTHING
|
||||
should_generate_icons = TRUE
|
||||
|
||||
/datum/preference/choiced/backpack/init_possible_values()
|
||||
var/list/values = list()
|
||||
|
||||
values[GBACKPACK] = /obj/item/storage/backpack
|
||||
values[GSATCHEL] = /obj/item/storage/backpack/satchel
|
||||
values[LSATCHEL] = /obj/item/storage/backpack/satchel/leather
|
||||
values[GDUFFELBAG] = /obj/item/storage/backpack/duffelbag
|
||||
|
||||
// In a perfect world, these would be your department's backpack.
|
||||
// However, this doesn't factor in assistants, or no high slot, and would
|
||||
// also increase the spritesheet size a lot.
|
||||
// I play medical doctor, and so medical doctor you get.
|
||||
values[DBACKPACK] = /obj/item/storage/backpack/medic
|
||||
values[DSATCHEL] = /obj/item/storage/backpack/satchel/med
|
||||
values[DDUFFELBAG] = /obj/item/storage/backpack/duffelbag/med
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/choiced/backpack/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.backpack = value
|
||||
|
||||
/// Jumpsuit preference
|
||||
/datum/preference/choiced/jumpsuit
|
||||
savefile_key = "jumpsuit_style"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
main_feature_name = "Jumpsuit"
|
||||
category = PREFERENCE_CATEGORY_CLOTHING
|
||||
should_generate_icons = TRUE
|
||||
|
||||
/datum/preference/choiced/jumpsuit/init_possible_values()
|
||||
var/list/values = list()
|
||||
|
||||
values[PREF_SUIT] = /obj/item/clothing/under/color/grey
|
||||
values[PREF_SKIRT] = /obj/item/clothing/under/color/jumpskirt/grey
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/choiced/jumpsuit/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.jumpsuit_style = value
|
||||
|
||||
/// Socks preference
|
||||
/datum/preference/choiced/socks
|
||||
savefile_key = "socks"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
main_feature_name = "Socks"
|
||||
category = PREFERENCE_CATEGORY_CLOTHING
|
||||
should_generate_icons = TRUE
|
||||
|
||||
/datum/preference/choiced/socks/init_possible_values()
|
||||
return generate_values_for_underwear(GLOB.socks_list, list("human_r_leg", "human_l_leg"))
|
||||
|
||||
/datum/preference/choiced/socks/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.socks = value
|
||||
|
||||
/// Undershirt preference
|
||||
/datum/preference/choiced/undershirt
|
||||
savefile_key = "undershirt"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
main_feature_name = "Undershirt"
|
||||
category = PREFERENCE_CATEGORY_CLOTHING
|
||||
should_generate_icons = TRUE
|
||||
|
||||
/datum/preference/choiced/undershirt/init_possible_values()
|
||||
var/icon/body = icon('icons/mob/human_parts_greyscale.dmi', "human_r_leg")
|
||||
body.Blend(icon('icons/mob/human_parts_greyscale.dmi', "human_l_leg"), ICON_OVERLAY)
|
||||
body.Blend(icon('icons/mob/human_parts_greyscale.dmi', "human_r_arm"), ICON_OVERLAY)
|
||||
body.Blend(icon('icons/mob/human_parts_greyscale.dmi', "human_l_arm"), ICON_OVERLAY)
|
||||
body.Blend(icon('icons/mob/human_parts_greyscale.dmi', "human_r_hand"), ICON_OVERLAY)
|
||||
body.Blend(icon('icons/mob/human_parts_greyscale.dmi', "human_l_hand"), ICON_OVERLAY)
|
||||
body.Blend(icon('icons/mob/human_parts_greyscale.dmi', "human_chest_m"), ICON_OVERLAY)
|
||||
|
||||
var/list/values = list()
|
||||
|
||||
for (var/accessory_name in GLOB.undershirt_list)
|
||||
var/icon/icon_with_undershirt = icon(body)
|
||||
|
||||
if (accessory_name != "Nude")
|
||||
var/datum/sprite_accessory/accessory = GLOB.undershirt_list[accessory_name]
|
||||
icon_with_undershirt.Blend(icon('icons/mob/clothing/underwear.dmi', accessory.icon_state), ICON_OVERLAY)
|
||||
|
||||
icon_with_undershirt.Crop(9, 9, 23, 23)
|
||||
icon_with_undershirt.Scale(32, 32)
|
||||
values[accessory_name] = icon_with_undershirt
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/choiced/undershirt/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.undershirt = value
|
||||
|
||||
/// Underwear preference
|
||||
/datum/preference/choiced/underwear
|
||||
savefile_key = "underwear"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
main_feature_name = "Underwear"
|
||||
category = PREFERENCE_CATEGORY_CLOTHING
|
||||
should_generate_icons = TRUE
|
||||
|
||||
/datum/preference/choiced/underwear/init_possible_values()
|
||||
return generate_values_for_underwear(GLOB.underwear_list, list("human_chest_m", "human_r_leg", "human_l_leg"), COLOR_ALMOST_BLACK)
|
||||
|
||||
/datum/preference/choiced/underwear/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.underwear = value
|
||||
|
||||
/datum/preference/choiced/underwear/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
var/species_type = preferences.read_preference(/datum/preference/choiced/species)
|
||||
var/datum/species/species = new species_type
|
||||
return !(NO_UNDERWEAR in species.species_traits)
|
||||
|
||||
/datum/preference/choiced/underwear/compile_constant_data()
|
||||
var/list/data = ..()
|
||||
|
||||
data[SUPPLEMENTAL_FEATURE_KEY] = "underwear_color"
|
||||
|
||||
return data
|
||||
@@ -0,0 +1,6 @@
|
||||
/// When toggled, being flashed will show a dark screen rather than a light one.
|
||||
/datum/preference/toggle/darkened_flash
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
default_value = FALSE
|
||||
savefile_key = "darkened_flash"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
@@ -0,0 +1,17 @@
|
||||
/datum/preference/numeric/fps
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "clientfps"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
minimum = -1
|
||||
maximum = 240
|
||||
|
||||
/datum/preference/numeric/fps/apply_to_client(client/client, value)
|
||||
client.fps = (value < 0) ? RECOMMENDED_FPS : value
|
||||
|
||||
/datum/preference/numeric/fps/compile_constant_data()
|
||||
var/list/data = ..()
|
||||
|
||||
data["recommended_fps"] = RECOMMENDED_FPS
|
||||
|
||||
return data
|
||||
@@ -0,0 +1,11 @@
|
||||
/// Gender preference
|
||||
/datum/preference/choiced/gender
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "gender"
|
||||
priority = PREFERENCE_PRIORITY_GENDER
|
||||
|
||||
/datum/preference/choiced/gender/init_possible_values()
|
||||
return list(MALE, FEMALE, PLURAL)
|
||||
|
||||
/datum/preference/choiced/gender/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.gender = value
|
||||
@@ -0,0 +1,178 @@
|
||||
/// Determines what accessories your ghost will look like they have.
|
||||
/datum/preference/choiced/ghost_accessories
|
||||
savefile_key = "ghost_accs"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
|
||||
/datum/preference/choiced/ghost_accessories/init_possible_values()
|
||||
return list(GHOST_ACCS_NONE, GHOST_ACCS_DIR, GHOST_ACCS_FULL)
|
||||
|
||||
/datum/preference/choiced/ghost_accessories/create_default_value()
|
||||
return GHOST_ACCS_DEFAULT_OPTION
|
||||
|
||||
/datum/preference/choiced/ghost_accessories/apply_to_client(client/client, value)
|
||||
var/mob/dead/observer/ghost = client.mob
|
||||
if (!istype(ghost))
|
||||
return
|
||||
|
||||
ghost.ghost_accs = value
|
||||
ghost.update_appearance()
|
||||
|
||||
/datum/preference/choiced/ghost_accessories/deserialize(input, datum/preferences/preferences)
|
||||
// Old ghost preferences used to be 1/50/100.
|
||||
// Whoever did that wasted an entire day of my time trying to get those sent
|
||||
// properly, so I'm going to buck them.
|
||||
if (isnum(input))
|
||||
switch (input)
|
||||
if (1)
|
||||
input = GHOST_ACCS_NONE
|
||||
if (50)
|
||||
input = GHOST_ACCS_DIR
|
||||
if (100)
|
||||
input = GHOST_ACCS_FULL
|
||||
|
||||
return ..(input)
|
||||
|
||||
/// Determines the appearance of your ghost to others, when you are a BYOND member
|
||||
/datum/preference/choiced/ghost_form
|
||||
savefile_key = "ghost_form"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
should_generate_icons = TRUE
|
||||
|
||||
var/static/list/ghost_forms = list(
|
||||
"catghost" = "Cat",
|
||||
"ghost" = "Default",
|
||||
"ghost_black" = "Black",
|
||||
"ghost_blazeit" = "Blaze it",
|
||||
"ghost_blue" = "Blue",
|
||||
"ghost_camo" = "Camo",
|
||||
"ghost_cyan" = "Cyan",
|
||||
"ghost_dblue" = "Dark blue",
|
||||
"ghost_dcyan" = "Dark cyan",
|
||||
"ghost_dgreen" = "Dark green",
|
||||
"ghost_dpink" = "Dark pink",
|
||||
"ghost_dred" = "Dark red",
|
||||
"ghost_dyellow" = "Dark yellow",
|
||||
"ghost_fire" = "Fire",
|
||||
"ghost_funkypurp" = "Funky purple",
|
||||
"ghost_green" = "Green",
|
||||
"ghost_grey" = "Grey",
|
||||
"ghost_mellow" = "Mellow",
|
||||
"ghost_pink" = "Pink",
|
||||
"ghost_pinksherbert" = "Pink Sherbert",
|
||||
"ghost_purpleswirl" = "Purple Swirl",
|
||||
"ghost_rainbow" = "Rainbow",
|
||||
"ghost_red" = "Red",
|
||||
"ghost_yellow" = "Yellow",
|
||||
"ghostian2" = "Ian",
|
||||
"ghostking" = "King",
|
||||
"skeleghost" = "Skeleton",
|
||||
)
|
||||
|
||||
/datum/preference/choiced/ghost_form/init_possible_values()
|
||||
var/list/values = list()
|
||||
|
||||
for (var/ghost_form in ghost_forms)
|
||||
values[ghost_form] = icon('icons/mob/mob.dmi', ghost_form)
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/choiced/ghost_form/create_default_value()
|
||||
return "ghost"
|
||||
|
||||
/datum/preference/choiced/ghost_form/apply_to_client(client/client, value)
|
||||
var/mob/dead/observer/ghost = client.mob
|
||||
if (!istype(ghost))
|
||||
return
|
||||
|
||||
if (!client.is_content_unlocked())
|
||||
return
|
||||
|
||||
ghost.update_icon(ALL, value)
|
||||
|
||||
/datum/preference/choiced/ghost_form/compile_constant_data()
|
||||
var/list/data = ..()
|
||||
|
||||
data[CHOICED_PREFERENCE_DISPLAY_NAMES] = ghost_forms
|
||||
|
||||
return data
|
||||
|
||||
/// Toggles the HUD for ghosts
|
||||
/datum/preference/toggle/ghost_hud
|
||||
savefile_key = "ghost_hud"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
|
||||
/datum/preference/toggle/ghost_hud/apply_to_client(client/client, value)
|
||||
if (isobserver(client?.mob))
|
||||
client?.mob.hud_used?.show_hud()
|
||||
|
||||
/// Determines what ghosts orbiting look like to you.
|
||||
/datum/preference/choiced/ghost_orbit
|
||||
savefile_key = "ghost_orbit"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
|
||||
/datum/preference/choiced/ghost_orbit/init_possible_values()
|
||||
return list(
|
||||
GHOST_ORBIT_CIRCLE,
|
||||
GHOST_ORBIT_TRIANGLE,
|
||||
GHOST_ORBIT_SQUARE,
|
||||
GHOST_ORBIT_HEXAGON,
|
||||
GHOST_ORBIT_PENTAGON,
|
||||
)
|
||||
|
||||
/datum/preference/choiced/ghost_orbit/apply_to_client(client/client, value)
|
||||
var/mob/dead/observer/ghost = client.mob
|
||||
if (!istype(ghost))
|
||||
return
|
||||
|
||||
if (!client.is_content_unlocked())
|
||||
return
|
||||
|
||||
ghost.ghost_orbit = value
|
||||
|
||||
/// Determines how to show other ghosts
|
||||
/datum/preference/choiced/ghost_others
|
||||
savefile_key = "ghost_others"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
|
||||
/datum/preference/choiced/ghost_others/init_possible_values()
|
||||
return list(
|
||||
GHOST_OTHERS_SIMPLE,
|
||||
GHOST_OTHERS_DEFAULT_SPRITE,
|
||||
GHOST_OTHERS_THEIR_SETTING,
|
||||
)
|
||||
|
||||
/datum/preference/choiced/ghost_others/create_default_value()
|
||||
return GHOST_OTHERS_DEFAULT_OPTION
|
||||
|
||||
/datum/preference/choiced/ghost_others/apply_to_client(client/client, value)
|
||||
var/mob/dead/observer/ghost = client.mob
|
||||
if (!istype(ghost))
|
||||
return
|
||||
|
||||
ghost.update_sight()
|
||||
|
||||
/datum/preference/choiced/ghost_others/deserialize(input, datum/preferences/preferences)
|
||||
// Old ghost preferences used to be 1/50/100.
|
||||
// Whoever did that wasted an entire day of my time trying to get those sent
|
||||
// properly, so I'm going to buck them.
|
||||
if (isnum(input))
|
||||
switch (input)
|
||||
if (1)
|
||||
input = GHOST_OTHERS_SIMPLE
|
||||
if (50)
|
||||
input = GHOST_OTHERS_DEFAULT_SPRITE
|
||||
if (100)
|
||||
input = GHOST_OTHERS_THEIR_SETTING
|
||||
|
||||
return ..(input, preferences)
|
||||
|
||||
/// Whether or not ghosts can examine things by clicking on them.
|
||||
/datum/preference/toggle/inquisitive_ghost
|
||||
savefile_key = "inquisitive_ghost"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
@@ -0,0 +1,7 @@
|
||||
/datum/preference/toggle/hotkeys
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "hotkeys"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/hotkeys/apply_to_client(client/client, value)
|
||||
client.hotkeys = value
|
||||
@@ -0,0 +1,4 @@
|
||||
/datum/preference/toggle/item_outlines
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "itemoutline_pref"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
@@ -0,0 +1,12 @@
|
||||
/datum/preference/choiced/jobless_role
|
||||
savefile_key = "joblessrole"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/choiced/jobless_role/create_default_value()
|
||||
return BERANDOMJOB
|
||||
|
||||
/datum/preference/choiced/jobless_role/init_possible_values()
|
||||
return list(BEOVERFLOW, BERANDOMJOB, RETURNTOLOBBY)
|
||||
|
||||
/datum/preference/choiced/jobless_role/should_show_on_page(preference_tab)
|
||||
return preference_tab == PREFERENCE_TAB_CHARACTER_PREFERENCES
|
||||
@@ -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,163 @@
|
||||
/datum/preference_middleware/antags
|
||||
action_delegations = list(
|
||||
"set_antags" = .proc/set_antags,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/antags/get_ui_static_data(mob/user)
|
||||
if (preferences.current_window != PREFERENCE_TAB_CHARACTER_PREFERENCES)
|
||||
return list()
|
||||
|
||||
var/list/data = list()
|
||||
|
||||
var/list/selected_antags = list()
|
||||
|
||||
for (var/antag in preferences.be_special)
|
||||
selected_antags += serialize_antag_name(antag)
|
||||
|
||||
data["selected_antags"] = selected_antags
|
||||
|
||||
var/list/antag_bans = get_antag_bans()
|
||||
if (antag_bans.len)
|
||||
data["antag_bans"] = antag_bans
|
||||
|
||||
var/list/antag_days_left = get_antag_days_left()
|
||||
if (antag_days_left?.len)
|
||||
data["antag_days_left"] = antag_days_left
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference_middleware/antags/get_ui_assets()
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/spritesheet/antagonists),
|
||||
)
|
||||
|
||||
/datum/preference_middleware/antags/proc/set_antags(list/params, mob/user)
|
||||
SHOULD_NOT_SLEEP(TRUE)
|
||||
|
||||
var/sent_antags = params["antags"]
|
||||
var/toggled = params["toggled"]
|
||||
|
||||
var/antags = list()
|
||||
|
||||
var/serialized_antags = get_serialized_antags()
|
||||
|
||||
for (var/sent_antag in sent_antags)
|
||||
var/special_role = serialized_antags[sent_antag]
|
||||
if (!special_role)
|
||||
continue
|
||||
|
||||
antags += special_role
|
||||
|
||||
if (toggled)
|
||||
preferences.be_special |= antags
|
||||
else
|
||||
preferences.be_special -= antags
|
||||
|
||||
// This is predicted on the client
|
||||
return FALSE
|
||||
|
||||
/datum/preference_middleware/antags/proc/get_antag_bans()
|
||||
var/list/antag_bans = list()
|
||||
|
||||
for (var/datum/dynamic_ruleset/dynamic_ruleset as anything in subtypesof(/datum/dynamic_ruleset))
|
||||
var/antag_flag = initial(dynamic_ruleset.antag_flag)
|
||||
var/antag_flag_override = initial(dynamic_ruleset.antag_flag_override)
|
||||
|
||||
if (isnull(antag_flag))
|
||||
continue
|
||||
|
||||
if (is_banned_from(preferences.parent.ckey, list(antag_flag_override || antag_flag, ROLE_SYNDICATE)))
|
||||
antag_bans += serialize_antag_name(antag_flag)
|
||||
|
||||
return antag_bans
|
||||
|
||||
/datum/preference_middleware/antags/proc/get_antag_days_left()
|
||||
if (!CONFIG_GET(flag/use_age_restriction_for_jobs))
|
||||
return
|
||||
|
||||
var/list/antag_days_left = list()
|
||||
|
||||
for (var/datum/dynamic_ruleset/dynamic_ruleset as anything in subtypesof(/datum/dynamic_ruleset))
|
||||
var/antag_flag = initial(dynamic_ruleset.antag_flag)
|
||||
var/antag_flag_override = initial(dynamic_ruleset.antag_flag_override)
|
||||
|
||||
if (isnull(antag_flag))
|
||||
continue
|
||||
|
||||
var/days_needed = preferences.parent?.get_remaining_days(
|
||||
GLOB.special_roles[antag_flag_override || antag_flag]
|
||||
)
|
||||
|
||||
if (days_needed > 0)
|
||||
antag_days_left[serialize_antag_name(antag_flag)] = days_needed
|
||||
|
||||
return antag_days_left
|
||||
|
||||
/datum/preference_middleware/antags/proc/get_serialized_antags()
|
||||
var/list/serialized_antags
|
||||
|
||||
if (isnull(serialized_antags))
|
||||
serialized_antags = list()
|
||||
|
||||
for (var/special_role in GLOB.special_roles)
|
||||
serialized_antags[serialize_antag_name(special_role)] = special_role
|
||||
|
||||
return serialized_antags
|
||||
|
||||
/// Sprites generated for the antagonists panel
|
||||
/datum/asset/spritesheet/antagonists
|
||||
name = "antagonists"
|
||||
early = TRUE
|
||||
|
||||
/datum/asset/spritesheet/antagonists/register()
|
||||
// Antagonists that don't have a dynamic ruleset, but do have a preference
|
||||
var/static/list/non_ruleset_antagonists = list(
|
||||
ROLE_LONE_OPERATIVE = /datum/antagonist/nukeop/lone,
|
||||
)
|
||||
|
||||
var/list/antagonists = non_ruleset_antagonists.Copy()
|
||||
|
||||
for (var/datum/dynamic_ruleset/ruleset as anything in subtypesof(/datum/dynamic_ruleset))
|
||||
var/datum/antagonist/antagonist_type = initial(ruleset.antag_datum)
|
||||
if (isnull(antagonist_type))
|
||||
continue
|
||||
|
||||
// antag_flag is guaranteed to be unique by unit tests.
|
||||
antagonists[initial(ruleset.antag_flag)] = antagonist_type
|
||||
|
||||
var/list/generated_icons = list()
|
||||
var/list/to_insert = list()
|
||||
|
||||
for (var/antag_flag in antagonists)
|
||||
var/datum/antagonist/antagonist_type = antagonists[antag_flag]
|
||||
|
||||
// antag_flag is guaranteed to be unique by unit tests.
|
||||
var/spritesheet_key = serialize_antag_name(antag_flag)
|
||||
|
||||
if (!isnull(generated_icons[antagonist_type]))
|
||||
to_insert[spritesheet_key] = generated_icons[antagonist_type]
|
||||
continue
|
||||
|
||||
var/datum/antagonist/antagonist = new antagonist_type
|
||||
var/icon/preview_icon = antagonist.get_preview_icon()
|
||||
|
||||
if (isnull(preview_icon))
|
||||
continue
|
||||
|
||||
qdel(antagonist)
|
||||
|
||||
// preview_icons are not scaled at this stage INTENTIONALLY.
|
||||
// If an icon is not prepared to be scaled to that size, it looks really ugly, and this
|
||||
// makes it harder to figure out what size it *actually* is.
|
||||
generated_icons[antagonist_type] = preview_icon
|
||||
to_insert[spritesheet_key] = preview_icon
|
||||
|
||||
for (var/spritesheet_key in to_insert)
|
||||
Insert(spritesheet_key, to_insert[spritesheet_key])
|
||||
|
||||
return ..()
|
||||
|
||||
/// Serializes an antag name to be used for preferences UI
|
||||
/proc/serialize_antag_name(antag_name)
|
||||
// These are sent through CSS, so they need to be safe to use as class names.
|
||||
return lowertext(sanitize_css_class_name(antag_name))
|
||||
@@ -0,0 +1,82 @@
|
||||
/datum/preference_middleware/jobs
|
||||
action_delegations = list(
|
||||
"set_job_preference" = .proc/set_job_preference,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/jobs/proc/set_job_preference(list/params, mob/user)
|
||||
var/job_title = params["job"]
|
||||
var/level = params["level"]
|
||||
|
||||
if (level != null && level != JP_LOW && level != JP_MEDIUM && level != JP_HIGH)
|
||||
return FALSE
|
||||
|
||||
var/datum/job/job = SSjob.GetJob(job_title)
|
||||
|
||||
if (isnull(job))
|
||||
return FALSE
|
||||
|
||||
if (job.faction != FACTION_STATION)
|
||||
return FALSE
|
||||
|
||||
if (!preferences.set_job_preference_level(job, level))
|
||||
return FALSE
|
||||
|
||||
preferences.character_preview_view?.update_body()
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/preference_middleware/jobs/get_ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["job_preferences"] = preferences.job_preferences
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference_middleware/jobs/get_ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
var/list/required_job_playtime = get_required_job_playtime(user)
|
||||
if (!isnull(required_job_playtime))
|
||||
data += required_job_playtime
|
||||
|
||||
var/list/job_bans = get_job_bans(user)
|
||||
if (job_bans.len)
|
||||
data["job_bans"] = job_bans
|
||||
|
||||
return data.len > 0 ? data : null
|
||||
|
||||
/datum/preference_middleware/jobs/proc/get_required_job_playtime(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
var/list/job_days_left = list()
|
||||
var/list/job_required_experience = list()
|
||||
|
||||
for (var/datum/job/job as anything in SSjob.all_occupations)
|
||||
var/required_playtime_remaining = job.required_playtime_remaining(user.client)
|
||||
if (required_playtime_remaining)
|
||||
job_required_experience[job.title] = list(
|
||||
"experience_type" = job.get_exp_req_type(),
|
||||
"required_playtime" = required_playtime_remaining,
|
||||
)
|
||||
|
||||
continue
|
||||
|
||||
if (!job.player_old_enough(user.client))
|
||||
job_days_left[job.title] = job.available_in_days(user.client)
|
||||
|
||||
if (job_days_left.len)
|
||||
data["job_days_left"] = job_days_left
|
||||
|
||||
if (job_required_experience)
|
||||
data["job_required_experience"] = job_required_experience
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference_middleware/jobs/proc/get_job_bans(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
for (var/datum/job/job as anything in SSjob.all_occupations)
|
||||
if (is_banned_from(user.client?.ckey, job.title))
|
||||
data += job.title
|
||||
|
||||
return data
|
||||
@@ -0,0 +1,89 @@
|
||||
#define MAX_HOTKEY_SLOTS 3
|
||||
|
||||
/// Middleware to handle keybindings
|
||||
/datum/preference_middleware/keybindings
|
||||
action_delegations = list(
|
||||
"reset_keybinds_to_defaults" = .proc/reset_keybinds_to_defaults,
|
||||
"set_keybindings" = .proc/set_keybindings,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/keybindings/get_ui_static_data(mob/user)
|
||||
if (preferences.current_window == PREFERENCE_TAB_CHARACTER_PREFERENCES)
|
||||
return list()
|
||||
|
||||
var/list/keybindings = preferences.key_bindings
|
||||
|
||||
return list(
|
||||
"keybindings" = keybindings,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/keybindings/get_ui_assets()
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/json/keybindings)
|
||||
)
|
||||
|
||||
/datum/preference_middleware/keybindings/proc/reset_keybinds_to_defaults(list/params, mob/user)
|
||||
var/keybind_name = params["keybind_name"]
|
||||
var/datum/keybinding/keybinding = GLOB.keybindings_by_name[keybind_name]
|
||||
|
||||
if (isnull(keybinding))
|
||||
return FALSE
|
||||
|
||||
preferences.key_bindings[keybind_name] = preferences.parent.hotkeys ? keybinding.hotkey_keys : keybinding.classic_keys
|
||||
preferences.key_bindings_by_key = preferences.get_key_bindings_by_key(preferences.key_bindings)
|
||||
|
||||
preferences.update_static_data(user)
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/preference_middleware/keybindings/proc/set_keybindings(list/params)
|
||||
var/keybind_name = params["keybind_name"]
|
||||
|
||||
if (isnull(GLOB.keybindings_by_name[keybind_name]))
|
||||
return FALSE
|
||||
|
||||
var/list/raw_hotkeys = params["hotkeys"]
|
||||
if (!istype(raw_hotkeys))
|
||||
return FALSE
|
||||
|
||||
if (raw_hotkeys.len > MAX_HOTKEY_SLOTS)
|
||||
return FALSE
|
||||
|
||||
// There's no optimal, easy way to check if something is an array
|
||||
// and not an object in BYOND, so just sanitize it to make sure.
|
||||
var/list/hotkeys = list()
|
||||
for (var/hotkey in raw_hotkeys)
|
||||
if (!istext(hotkey))
|
||||
return FALSE
|
||||
|
||||
// Fairly arbitrary number, it's just so you don't save enormous fake keybinds.
|
||||
if (length(hotkey) > 100)
|
||||
return FALSE
|
||||
|
||||
hotkeys += hotkey
|
||||
|
||||
preferences.key_bindings[keybind_name] = hotkeys
|
||||
preferences.key_bindings_by_key = preferences.get_key_bindings_by_key(preferences.key_bindings)
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/asset/json/keybindings
|
||||
name = "keybindings"
|
||||
|
||||
/datum/asset/json/keybindings/generate()
|
||||
var/list/keybindings = list()
|
||||
|
||||
for (var/name in GLOB.keybindings_by_name)
|
||||
var/datum/keybinding/keybinding = GLOB.keybindings_by_name[name]
|
||||
|
||||
if (!(keybinding.category in keybindings))
|
||||
keybindings[keybinding.category] = list()
|
||||
|
||||
keybindings[keybinding.category][keybinding.name] = list(
|
||||
"name" = keybinding.full_name,
|
||||
"description" = keybinding.description,
|
||||
)
|
||||
|
||||
return keybindings
|
||||
|
||||
#undef MAX_HOTKEY_SLOTS
|
||||
@@ -0,0 +1,132 @@
|
||||
/// In the before times, toggles were all stored in one bitfield.
|
||||
/// In order to preserve this existing data (and code) without massive
|
||||
/// migrations, this middleware attempts to handle this in a way
|
||||
/// transparent to the preferences UI itself.
|
||||
/// In the future, the existing toggles data should just be migrated to
|
||||
/// individual `/datum/preference/toggle`s.
|
||||
/datum/preference_middleware/legacy_toggles
|
||||
// DO NOT ADD ANY NEW TOGGLES HERE!
|
||||
// Use `/datum/preference/toggle` instead.
|
||||
var/static/list/legacy_toggles = list(
|
||||
"admin_ignore_cult_ghost" = ADMIN_IGNORE_CULT_GHOST,
|
||||
"announce_login" = ANNOUNCE_LOGIN,
|
||||
"combohud_lighting" = COMBOHUD_LIGHTING,
|
||||
"deadmin_always" = DEADMIN_ALWAYS,
|
||||
"deadmin_antagonist" = DEADMIN_ANTAGONIST,
|
||||
"deadmin_position_head" = DEADMIN_POSITION_HEAD,
|
||||
"deadmin_position_security" = DEADMIN_POSITION_SECURITY,
|
||||
"deadmin_position_silicon" = DEADMIN_POSITION_SILICON,
|
||||
"disable_arrivalrattle" = DISABLE_ARRIVALRATTLE,
|
||||
"disable_deathrattle" = DISABLE_DEATHRATTLE,
|
||||
"member_public" = MEMBER_PUBLIC,
|
||||
"sound_adminhelp" = SOUND_ADMINHELP,
|
||||
"sound_ambience" = SOUND_AMBIENCE,
|
||||
"sound_announcements" = SOUND_ANNOUNCEMENTS,
|
||||
"sound_combatmode" = SOUND_COMBATMODE,
|
||||
"sound_endofround" = SOUND_ENDOFROUND,
|
||||
"sound_instruments" = SOUND_INSTRUMENTS,
|
||||
"sound_lobby" = SOUND_LOBBY,
|
||||
"sound_midi" = SOUND_MIDI,
|
||||
"sound_prayers" = SOUND_PRAYERS,
|
||||
"sound_ship_ambience" = SOUND_SHIP_AMBIENCE,
|
||||
"split_admin_tabs" = SPLIT_ADMIN_TABS,
|
||||
)
|
||||
|
||||
var/list/legacy_chat_toggles = list(
|
||||
"chat_bankcard" = CHAT_BANKCARD,
|
||||
"chat_dead" = CHAT_DEAD,
|
||||
"chat_ghostears" = CHAT_GHOSTEARS,
|
||||
"chat_ghostlaws" = CHAT_GHOSTLAWS,
|
||||
"chat_ghostpda" = CHAT_GHOSTPDA,
|
||||
"chat_ghostradio" = CHAT_GHOSTRADIO,
|
||||
"chat_ghostsight" = CHAT_GHOSTSIGHT,
|
||||
"chat_ghostwhisper" = CHAT_GHOSTWHISPER,
|
||||
"chat_login_logout" = CHAT_LOGIN_LOGOUT,
|
||||
"chat_ooc" = CHAT_OOC,
|
||||
"chat_prayer" = CHAT_PRAYER,
|
||||
"chat_pullr" = CHAT_PULLR,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/legacy_toggles/get_character_preferences(mob/user)
|
||||
if (preferences.current_window != PREFERENCE_TAB_GAME_PREFERENCES)
|
||||
return list()
|
||||
|
||||
var/static/list/admin_only_legacy_toggles = list(
|
||||
"admin_ignore_cult_ghost",
|
||||
"announce_login",
|
||||
"combohud_lighting",
|
||||
"deadmin_always",
|
||||
"deadmin_antagonist",
|
||||
"deadmin_position_head",
|
||||
"deadmin_position_security",
|
||||
"deadmin_position_silicon",
|
||||
"sound_adminhelp",
|
||||
"sound_prayers",
|
||||
"split_admin_tabs",
|
||||
)
|
||||
|
||||
var/static/list/admin_only_chat_toggles = list(
|
||||
"chat_dead",
|
||||
"chat_prayer",
|
||||
)
|
||||
|
||||
var/static/list/deadmin_flags = list(
|
||||
"deadmin_antagonist",
|
||||
"deadmin_position_head",
|
||||
"deadmin_position_security",
|
||||
"deadmin_position_silicon",
|
||||
)
|
||||
|
||||
var/list/new_game_preferences = list()
|
||||
var/is_admin = is_admin(user.client)
|
||||
|
||||
for (var/toggle_name in legacy_toggles)
|
||||
if (!is_admin && (toggle_name in admin_only_legacy_toggles))
|
||||
continue
|
||||
|
||||
if (is_admin && (toggle_name in deadmin_flags) && (preferences.toggles & DEADMIN_ALWAYS))
|
||||
continue
|
||||
|
||||
if (toggle_name == "member_public" && !preferences.unlock_content)
|
||||
continue
|
||||
|
||||
new_game_preferences[toggle_name] = (preferences.toggles & legacy_toggles[toggle_name]) != 0
|
||||
|
||||
for (var/toggle_name in legacy_chat_toggles)
|
||||
if (!is_admin && (toggle_name in admin_only_chat_toggles))
|
||||
continue
|
||||
|
||||
new_game_preferences[toggle_name] = (preferences.chat_toggles & legacy_chat_toggles[toggle_name]) != 0
|
||||
|
||||
return list(
|
||||
PREFERENCE_CATEGORY_GAME_PREFERENCES = new_game_preferences,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/legacy_toggles/pre_set_preference(mob/user, preference, value)
|
||||
var/legacy_flag = legacy_toggles[preference]
|
||||
if (!isnull(legacy_flag))
|
||||
if (value)
|
||||
preferences.toggles |= legacy_flag
|
||||
else
|
||||
preferences.toggles &= ~legacy_flag
|
||||
|
||||
// I know this looks silly, but this is the only one that cares
|
||||
// and NO NEW LEGACY TOGGLES should ever be added.
|
||||
if (legacy_flag == SOUND_LOBBY)
|
||||
if (value && isnewplayer(user))
|
||||
user.client?.playtitlemusic()
|
||||
else
|
||||
user.stop_sound_channel(CHANNEL_LOBBYMUSIC)
|
||||
|
||||
return TRUE
|
||||
|
||||
var/legacy_chat_flag = legacy_chat_toggles[preference]
|
||||
if (!isnull(legacy_chat_flag))
|
||||
if (value)
|
||||
preferences.chat_toggles |= legacy_chat_flag
|
||||
else
|
||||
preferences.chat_toggles &= ~legacy_chat_flag
|
||||
|
||||
return TRUE
|
||||
|
||||
return FALSE
|
||||
@@ -0,0 +1,56 @@
|
||||
/// Middleware that handles telling the UI which name to show, and waht names
|
||||
/// they have.
|
||||
/datum/preference_middleware/names
|
||||
action_delegations = list(
|
||||
"randomize_name" = .proc/randomize_name,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/names/get_constant_data()
|
||||
var/list/data = list()
|
||||
|
||||
var/list/types = list()
|
||||
|
||||
for (var/preference_type in GLOB.preference_entries)
|
||||
var/datum/preference/name/name_preference = GLOB.preference_entries[preference_type]
|
||||
if (!istype(name_preference))
|
||||
continue
|
||||
|
||||
types[name_preference.savefile_key] = list(
|
||||
"can_randomize" = name_preference.is_randomizable(),
|
||||
"explanation" = name_preference.explanation,
|
||||
"group" = name_preference.group,
|
||||
)
|
||||
|
||||
data["types"] = types
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference_middleware/names/get_ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["name_to_use"] = get_name_to_use()
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference_middleware/names/proc/get_name_to_use()
|
||||
var/highest_priority_job = preferences.get_highest_priority_job()
|
||||
|
||||
for (var/preference_type in GLOB.preference_entries)
|
||||
var/datum/preference/name/name_preference = GLOB.preference_entries[preference_type]
|
||||
if (!istype(name_preference))
|
||||
continue
|
||||
|
||||
if (isnull(name_preference.relevant_job))
|
||||
continue
|
||||
|
||||
if (istype(highest_priority_job, name_preference.relevant_job))
|
||||
return name_preference.savefile_key
|
||||
|
||||
return "real_name"
|
||||
|
||||
/datum/preference_middleware/names/proc/randomize_name(list/params, mob/user)
|
||||
var/datum/preference/name/name_preference = GLOB.preference_entries_by_key[params["preference"]]
|
||||
if (!istype(name_preference))
|
||||
return FALSE
|
||||
|
||||
return preferences.update_preference(name_preference, name_preference.create_random_value(preferences))
|
||||
@@ -0,0 +1,89 @@
|
||||
/// Middleware to handle quirks
|
||||
/datum/preference_middleware/quirks
|
||||
var/tainted = FALSE
|
||||
|
||||
action_delegations = list(
|
||||
"give_quirk" = .proc/give_quirk,
|
||||
"remove_quirk" = .proc/remove_quirk,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/quirks/get_ui_static_data(mob/user)
|
||||
if (preferences.current_window != PREFERENCE_TAB_CHARACTER_PREFERENCES)
|
||||
return list()
|
||||
|
||||
var/list/data = list()
|
||||
|
||||
data["selected_quirks"] = get_selected_quirks()
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference_middleware/quirks/get_ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
if (tainted)
|
||||
tainted = FALSE
|
||||
data["selected_quirks"] = get_selected_quirks()
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference_middleware/quirks/get_constant_data()
|
||||
var/list/quirk_info = list()
|
||||
|
||||
for (var/quirk_name in SSquirks.quirks)
|
||||
var/datum/quirk/quirk = SSquirks.quirks[quirk_name]
|
||||
quirk_info[sanitize_css_class_name(quirk_name)] = list(
|
||||
"description" = initial(quirk.desc),
|
||||
"icon" = initial(quirk.icon),
|
||||
"name" = quirk_name,
|
||||
"value" = initial(quirk.value),
|
||||
)
|
||||
|
||||
return list(
|
||||
"max_positive_quirks" = MAX_QUIRKS,
|
||||
"quirk_info" = quirk_info,
|
||||
"quirk_blacklist" = SSquirks.quirk_blacklist,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/quirks/on_new_character(mob/user)
|
||||
tainted = TRUE
|
||||
|
||||
/datum/preference_middleware/quirks/proc/give_quirk(list/params, mob/user)
|
||||
var/quirk_name = params["quirk"]
|
||||
|
||||
var/list/new_quirks = preferences.all_quirks | quirk_name
|
||||
if (SSquirks.filter_invalid_quirks(new_quirks) != new_quirks)
|
||||
// If the client is sending an invalid give_quirk, that means that
|
||||
// something went wrong with the client prediction, so we should
|
||||
// catch it back up to speed.
|
||||
preferences.update_static_data(user)
|
||||
return TRUE
|
||||
|
||||
preferences.all_quirks = new_quirks
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/preference_middleware/quirks/proc/remove_quirk(list/params, mob/user)
|
||||
var/quirk_name = params["quirk"]
|
||||
|
||||
var/list/new_quirks = preferences.all_quirks - quirk_name
|
||||
if ( \
|
||||
!(quirk_name in preferences.all_quirks) \
|
||||
|| SSquirks.filter_invalid_quirks(new_quirks) != new_quirks \
|
||||
)
|
||||
// If the client is sending an invalid remove_quirk, that means that
|
||||
// something went wrong with the client prediction, so we should
|
||||
// catch it back up to speed.
|
||||
preferences.update_static_data(user)
|
||||
return TRUE
|
||||
|
||||
preferences.all_quirks = new_quirks
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/preference_middleware/quirks/proc/get_selected_quirks()
|
||||
var/list/selected_quirks = list()
|
||||
|
||||
for (var/quirk in preferences.all_quirks)
|
||||
selected_quirks += sanitize_css_class_name(quirk)
|
||||
|
||||
return selected_quirks
|
||||
@@ -0,0 +1,84 @@
|
||||
/// Middleware for handling randomization preferences
|
||||
/datum/preference_middleware/random
|
||||
action_delegations = list(
|
||||
"randomize_character" = .proc/randomize_character,
|
||||
"set_random_preference" = .proc/set_random_preference,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/random/get_character_preferences(mob/user)
|
||||
return list(
|
||||
"randomization" = preferences.randomise,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/random/get_constant_data()
|
||||
var/list/randomizable = list()
|
||||
|
||||
for (var/preference_type in GLOB.preference_entries)
|
||||
var/datum/preference/preference = GLOB.preference_entries[preference_type]
|
||||
if (!preference.is_randomizable())
|
||||
continue
|
||||
|
||||
randomizable += preference.savefile_key
|
||||
|
||||
return list(
|
||||
"randomizable" = randomizable,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/random/proc/randomize_character()
|
||||
for (var/datum/preference/preference as anything in get_preferences_in_priority_order())
|
||||
if (preferences.should_randomize(preference))
|
||||
preferences.write_preference(preference, preference.create_random_value(src))
|
||||
|
||||
preferences.character_preview_view.update_body()
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/preference_middleware/random/proc/set_random_preference(list/params, mob/user)
|
||||
var/requested_preference_key = params["preference"]
|
||||
var/value = params["value"]
|
||||
|
||||
var/datum/preference/requested_preference = GLOB.preference_entries_by_key[requested_preference_key]
|
||||
if (isnull(requested_preference))
|
||||
return FALSE
|
||||
|
||||
if (!requested_preference.is_randomizable())
|
||||
return FALSE
|
||||
|
||||
if (value == RANDOM_ANTAG_ONLY)
|
||||
preferences.randomise[requested_preference_key] = RANDOM_ANTAG_ONLY
|
||||
else if (value == RANDOM_ENABLED)
|
||||
preferences.randomise[requested_preference_key] = RANDOM_ENABLED
|
||||
else if (value == RANDOM_DISABLED)
|
||||
preferences.randomise -= requested_preference_key
|
||||
else
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
/// Returns if a preference should be randomized.
|
||||
/datum/preferences/proc/should_randomize(datum/preference/preference, is_antag)
|
||||
if (!preference.is_randomizable())
|
||||
return FALSE
|
||||
|
||||
var/requested_randomization = randomise[preference.savefile_key]
|
||||
|
||||
if (istype(preference, /datum/preference/name))
|
||||
requested_randomization = read_preference(/datum/preference/choiced/random_name)
|
||||
|
||||
switch (requested_randomization)
|
||||
if (RANDOM_ENABLED)
|
||||
return TRUE
|
||||
if (RANDOM_ANTAG_ONLY)
|
||||
return is_antag
|
||||
else
|
||||
return FALSE
|
||||
|
||||
/// Given randomization flags, will return whether or not this preference should be randomized.
|
||||
/datum/preference/proc/included_in_randomization_flags(randomize_flags)
|
||||
return TRUE
|
||||
|
||||
/datum/preference/name/included_in_randomization_flags(randomize_flags)
|
||||
return !!(randomize_flags & RANDOMIZE_NAME)
|
||||
|
||||
/datum/preference/choiced/species/included_in_randomization_flags(randomize_flags)
|
||||
return !!(randomize_flags & RANDOMIZE_SPECIES)
|
||||
@@ -0,0 +1,36 @@
|
||||
/// Handles the assets for species icons
|
||||
/datum/preference_middleware/species
|
||||
|
||||
/datum/preference_middleware/species/get_ui_assets()
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/spritesheet/species),
|
||||
)
|
||||
|
||||
/datum/asset/spritesheet/species
|
||||
name = "species"
|
||||
early = TRUE
|
||||
|
||||
/datum/asset/spritesheet/species/register()
|
||||
var/list/to_insert = list()
|
||||
|
||||
for (var/species_id in get_selectable_species())
|
||||
var/datum/species/species_type = GLOB.species_list[species_id]
|
||||
|
||||
var/mob/living/carbon/human/dummy/consistent/dummy = new
|
||||
dummy.set_species(species_type)
|
||||
dummy.equipOutfit(/datum/outfit/job/assistant/consistent, visualsOnly = TRUE)
|
||||
dummy.dna.species.prepare_human_for_preview(dummy)
|
||||
COMPILE_OVERLAYS(dummy)
|
||||
|
||||
var/icon/dummy_icon = getFlatIcon(dummy)
|
||||
dummy_icon.Scale(64, 64)
|
||||
dummy_icon.Crop(15, 64, 15 + 31, 64 - 31)
|
||||
dummy_icon.Scale(64, 64)
|
||||
to_insert[sanitize_css_class_name(initial(species_type.name))] = dummy_icon
|
||||
|
||||
SSatoms.prepare_deletion(dummy)
|
||||
|
||||
for (var/spritesheet_key in to_insert)
|
||||
Insert(spritesheet_key, to_insert[spritesheet_key])
|
||||
|
||||
return ..()
|
||||
@@ -0,0 +1,156 @@
|
||||
/// A preference for a name. Used not just for normal names, but also for clown names, etc.
|
||||
/datum/preference/name
|
||||
category = "names"
|
||||
priority = PREFERENCE_PRIORITY_NAMES
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
abstract_type = /datum/preference/name
|
||||
|
||||
/// The display name when showing on the "other names" panel
|
||||
var/explanation
|
||||
|
||||
/// These will be grouped together on the preferences menu
|
||||
var/group
|
||||
|
||||
/// Whether or not to allow numbers in the person's name
|
||||
var/allow_numbers = FALSE
|
||||
|
||||
/// If the highest priority job matches this, will prioritize this name in the UI
|
||||
var/relevant_job
|
||||
|
||||
/datum/preference/name/apply_to_human(mob/living/carbon/human/target, value)
|
||||
// Only real_name applies directly, everything else is applied by something else
|
||||
return
|
||||
|
||||
/datum/preference/name/deserialize(input, datum/preferences/preferences)
|
||||
return reject_bad_name("[input]", allow_numbers)
|
||||
|
||||
/datum/preference/name/serialize(input)
|
||||
// `is_valid` should always be run before `serialize`, so it should not
|
||||
// be possible for this to return `null`.
|
||||
return reject_bad_name(input, allow_numbers)
|
||||
|
||||
/datum/preference/name/is_valid(value)
|
||||
return istext(value) && !isnull(reject_bad_name(value, allow_numbers))
|
||||
|
||||
/// A character's real name
|
||||
/datum/preference/name/real_name
|
||||
explanation = "Name"
|
||||
// The `_` makes it first in ABC order.
|
||||
group = "_real_name"
|
||||
savefile_key = "real_name"
|
||||
|
||||
/datum/preference/name/real_name/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.real_name = value
|
||||
target.name = value
|
||||
|
||||
/datum/preference/name/real_name/create_informed_default_value(datum/preferences/preferences)
|
||||
var/species_type = preferences.read_preference(/datum/preference/choiced/species)
|
||||
var/gender = preferences.read_preference(/datum/preference/choiced/gender)
|
||||
|
||||
var/datum/species/species = new species_type
|
||||
|
||||
return species.random_name(gender, unique = TRUE)
|
||||
|
||||
/datum/preference/name/real_name/deserialize(input, datum/preferences/preferences)
|
||||
input = ..(input)
|
||||
if (!input)
|
||||
return input
|
||||
|
||||
if (CONFIG_GET(flag/humans_need_surnames) && preferences.read_preference(/datum/preference/choiced/species) == /datum/species/human)
|
||||
var/first_space = findtext(input, " ")
|
||||
if(!first_space) //we need a surname
|
||||
input += " [pick(GLOB.last_names)]"
|
||||
else if(first_space == length(input))
|
||||
input += "[pick(GLOB.last_names)]"
|
||||
|
||||
return reject_bad_name(input, allow_numbers)
|
||||
|
||||
/// The name for a backup human, when nonhumans are made into head of staff
|
||||
/datum/preference/name/backup_human
|
||||
explanation = "Backup human name"
|
||||
group = "backup_human"
|
||||
savefile_key = "human_name"
|
||||
|
||||
/datum/preference/name/backup_human/create_informed_default_value(datum/preferences/preferences)
|
||||
var/gender = preferences.read_preference(/datum/preference/choiced/gender)
|
||||
|
||||
return random_unique_name(gender)
|
||||
|
||||
/datum/preference/name/clown
|
||||
savefile_key = "clown_name"
|
||||
|
||||
explanation = "Clown name"
|
||||
group = "fun"
|
||||
relevant_job = /datum/job/clown
|
||||
|
||||
/datum/preference/name/clown/create_default_value()
|
||||
return pick(GLOB.clown_names)
|
||||
|
||||
/datum/preference/name/mime
|
||||
savefile_key = "mime_name"
|
||||
|
||||
explanation = "Mime name"
|
||||
group = "fun"
|
||||
relevant_job = /datum/job/mime
|
||||
|
||||
/datum/preference/name/mime/create_default_value()
|
||||
return pick(GLOB.mime_names)
|
||||
|
||||
/datum/preference/name/cyborg
|
||||
savefile_key = "cyborg_name"
|
||||
|
||||
allow_numbers = TRUE
|
||||
can_randomize = FALSE
|
||||
|
||||
explanation = "Cyborg name"
|
||||
group = "silicons"
|
||||
relevant_job = /datum/job/cyborg
|
||||
|
||||
/datum/preference/name/cyborg/create_default_value()
|
||||
return DEFAULT_CYBORG_NAME
|
||||
|
||||
/datum/preference/name/ai
|
||||
savefile_key = "ai_name"
|
||||
|
||||
allow_numbers = TRUE
|
||||
explanation = "AI name"
|
||||
group = "silicons"
|
||||
relevant_job = /datum/job/ai
|
||||
|
||||
/datum/preference/name/ai/create_default_value()
|
||||
return pick(GLOB.ai_names)
|
||||
|
||||
/datum/preference/name/religion
|
||||
savefile_key = "religion_name"
|
||||
|
||||
allow_numbers = TRUE
|
||||
|
||||
explanation = "Religion name"
|
||||
group = "religion"
|
||||
|
||||
/datum/preference/name/religion/create_default_value()
|
||||
return pick(GLOB.religion_names)
|
||||
|
||||
/datum/preference/name/deity
|
||||
savefile_key = "deity_name"
|
||||
|
||||
allow_numbers = TRUE
|
||||
can_randomize = FALSE
|
||||
|
||||
explanation = "Deity name"
|
||||
group = "religion"
|
||||
|
||||
/datum/preference/name/deity/create_default_value()
|
||||
return DEFAULT_DEITY
|
||||
|
||||
/datum/preference/name/bible
|
||||
savefile_key = "bible_name"
|
||||
|
||||
allow_numbers = TRUE
|
||||
can_randomize = FALSE
|
||||
|
||||
explanation = "Bible name"
|
||||
group = "religion"
|
||||
|
||||
/datum/preference/name/bible/create_default_value()
|
||||
return DEFAULT_BIBLE
|
||||
@@ -0,0 +1,14 @@
|
||||
/// The color admins will speak in for OOC.
|
||||
/datum/preference/color/ooc_color
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "ooccolor"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/color/ooc_color/create_default_value()
|
||||
return "#c43b23"
|
||||
|
||||
/datum/preference/color/ooc_color/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return is_admin(preferences.parent) || preferences.unlock_content
|
||||
@@ -0,0 +1,38 @@
|
||||
/// Determines parallax, "fancy space"
|
||||
/datum/preference/choiced/parallax
|
||||
savefile_key = "parallax"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
|
||||
/datum/preference/choiced/parallax/init_possible_values()
|
||||
return list(
|
||||
PARALLAX_INSANE,
|
||||
PARALLAX_HIGH,
|
||||
PARALLAX_MED,
|
||||
PARALLAX_LOW,
|
||||
PARALLAX_DISABLE,
|
||||
)
|
||||
|
||||
/datum/preference/choiced/parallax/create_default_value()
|
||||
return PARALLAX_HIGH
|
||||
|
||||
/datum/preference/choiced/parallax/apply_to_client(client/client, value)
|
||||
client.mob?.hud_used?.update_parallax_pref(client?.mob)
|
||||
|
||||
/datum/preference/choiced/parallax/deserialize(input, datum/preferences/preferences)
|
||||
// Old preferences were numbers, which causes annoyances when
|
||||
// sending over as lists that isn't worth dealing with.
|
||||
if (isnum(input))
|
||||
switch (input)
|
||||
if (-1)
|
||||
input = PARALLAX_INSANE
|
||||
if (0)
|
||||
input = PARALLAX_HIGH
|
||||
if (1)
|
||||
input = PARALLAX_MED
|
||||
if (2)
|
||||
input = PARALLAX_LOW
|
||||
if (3)
|
||||
input = PARALLAX_DISABLE
|
||||
|
||||
return ..(input)
|
||||
@@ -0,0 +1,17 @@
|
||||
/// The color of a PDA
|
||||
/datum/preference/color/pda_color
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "pda_color"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/color/pda_color/create_default_value()
|
||||
return COLOR_OLIVE
|
||||
|
||||
/// The visual style of a PDA
|
||||
/datum/preference/choiced/pda_style
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "pda_style"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/choiced/pda_style/init_possible_values()
|
||||
return GLOB.pda_styles
|
||||
@@ -0,0 +1,16 @@
|
||||
/datum/preference/choiced/phobia
|
||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
||||
savefile_key = "phobia"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
|
||||
/datum/preference/choiced/phobia/init_possible_values()
|
||||
return GLOB.phobia_types
|
||||
|
||||
/datum/preference/choiced/phobia/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return "Phobia" in preferences.all_quirks
|
||||
|
||||
/datum/preference/choiced/phobia/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
@@ -0,0 +1,15 @@
|
||||
/datum/preference/numeric/pixel_size
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "pixel_size"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
minimum = 0
|
||||
maximum = 3
|
||||
|
||||
step = 0.5
|
||||
|
||||
/datum/preference/numeric/pixel_size/create_default_value()
|
||||
return 0
|
||||
|
||||
/datum/preference/numeric/pixel_size/apply_to_client(client/client, value)
|
||||
client?.view_size?.resetFormat()
|
||||
@@ -0,0 +1,20 @@
|
||||
/// This can be set to TRUE from the prefs menu only once the user has
|
||||
/// gained over 5K playtime hours.
|
||||
/// If true, it allows the user to get a cool looking roundstart cloak.
|
||||
/datum/preference/toggle/playtime_reward_cloak
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "playtime_reward_cloak"
|
||||
|
||||
/datum/preference/toggle/playtime_reward_cloak/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return preferences.parent?.is_veteran()
|
||||
|
||||
/datum/preference/toggle/playtime_reward_cloak/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
|
||||
/// Returns whether the client should receive the gamer cloak
|
||||
/client/proc/is_veteran()
|
||||
return get_exp_living(pure_numeric = TRUE) >= PLAYTIME_VETERAN
|
||||
@@ -0,0 +1,52 @@
|
||||
/// During map rotation, this will help determine the chosen map.
|
||||
/datum/preference/choiced/preferred_map
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "preferred_map"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/choiced/preferred_map/init_possible_values()
|
||||
var/list/maps = list()
|
||||
maps += ""
|
||||
|
||||
for (var/map in config.maplist)
|
||||
var/datum/map_config/map_config = config.maplist[map]
|
||||
if (!map_config.votable)
|
||||
continue
|
||||
|
||||
maps += map
|
||||
|
||||
return maps
|
||||
|
||||
/datum/preference/choiced/preferred_map/create_default_value()
|
||||
return ""
|
||||
|
||||
/datum/preference/choiced/preferred_map/compile_constant_data()
|
||||
var/list/data = ..()
|
||||
|
||||
var/display_names = list()
|
||||
|
||||
if (config.defaultmap)
|
||||
display_names[""] = "Default ([config.defaultmap.map_name])"
|
||||
else
|
||||
display_names[""] = "Default"
|
||||
|
||||
for (var/choice in get_choices())
|
||||
if (choice == "")
|
||||
continue
|
||||
|
||||
var/datum/map_config/map_config = config.maplist[choice]
|
||||
|
||||
var/map_name = map_config.map_name
|
||||
if (map_config.voteweight <= 0)
|
||||
map_name += " (disabled)"
|
||||
display_names[choice] = map_name
|
||||
|
||||
data[CHOICED_PREFERENCE_DISPLAY_NAMES] = display_names
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference/choiced/preferred_map/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return CONFIG_GET(flag/preference_map_voting)
|
||||
@@ -0,0 +1,53 @@
|
||||
/datum/preference/choiced/random_body
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_key = "random_body"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
can_randomize = FALSE
|
||||
|
||||
/datum/preference/choiced/random_body/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
|
||||
/datum/preference/choiced/random_body/init_possible_values()
|
||||
return list(
|
||||
RANDOM_ANTAG_ONLY,
|
||||
RANDOM_DISABLED,
|
||||
RANDOM_ENABLED,
|
||||
)
|
||||
|
||||
/datum/preference/choiced/random_body/create_default_value()
|
||||
return RANDOM_DISABLED
|
||||
|
||||
/datum/preference/toggle/random_hardcore
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_key = "random_hardcore"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
can_randomize = FALSE
|
||||
default_value = FALSE
|
||||
|
||||
/datum/preference/toggle/random_hardcore/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
|
||||
/datum/preference/toggle/random_hardcore/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return preferences.parent.get_exp_living(pure_numeric = TRUE) >= PLAYTIME_HARDCORE_RANDOM
|
||||
|
||||
/datum/preference/choiced/random_name
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_key = "random_name"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
can_randomize = FALSE
|
||||
|
||||
/datum/preference/choiced/random_name/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
|
||||
/datum/preference/choiced/random_name/init_possible_values()
|
||||
return list(
|
||||
RANDOM_ANTAG_ONLY,
|
||||
RANDOM_DISABLED,
|
||||
RANDOM_ENABLED,
|
||||
)
|
||||
|
||||
/datum/preference/choiced/random_name/create_default_value()
|
||||
return RANDOM_DISABLED
|
||||
@@ -0,0 +1,25 @@
|
||||
/datum/preference/toggle/enable_runechat
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "chat_on_map"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/enable_runechat_non_mobs
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "see_chat_non_mob"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/see_rc_emotes
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "see_rc_emotes"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/numeric/max_chat_length
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "max_chat_length"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
minimum = 1
|
||||
maximum = CHAT_MESSAGE_MAX_LENGTH
|
||||
|
||||
/datum/preference/numeric/max_chat_length/create_default_value()
|
||||
return CHAT_MESSAGE_MAX_LENGTH
|
||||
@@ -0,0 +1,14 @@
|
||||
/// The scaling method to show the world in, e.g. nearest neighbor
|
||||
/datum/preference/choiced/scaling_method
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "scaling_method"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/choiced/scaling_method/create_default_value()
|
||||
return SCALING_METHOD_DISTORT
|
||||
|
||||
/datum/preference/choiced/scaling_method/init_possible_values()
|
||||
return list(SCALING_METHOD_DISTORT, SCALING_METHOD_BLUR, SCALING_METHOD_NORMAL)
|
||||
|
||||
/datum/preference/choiced/scaling_method/apply_to_client(client/client, value)
|
||||
client?.view_size?.setZoomMode()
|
||||
@@ -0,0 +1,18 @@
|
||||
/datum/preference/toggle/enable_screentips
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "screentip_pref"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/enable_screentips/apply_to_client(client/client, value)
|
||||
client.mob?.hud_used?.screentips_enabled = value
|
||||
|
||||
/datum/preference/color/screentip_color
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "screentip_color"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/color/screentip_color/apply_to_client(client/client, value)
|
||||
client.mob?.hud_used?.screentip_color = value
|
||||
|
||||
/datum/preference/color/screentip_color/create_default_value()
|
||||
return "#ffd391"
|
||||
@@ -0,0 +1,27 @@
|
||||
/// Which department to put security officers in, when the config is enabled
|
||||
/datum/preference/choiced/security_department
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
can_randomize = FALSE
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "prefered_security_department"
|
||||
|
||||
// This is what that #warn wants you to remove :)
|
||||
/datum/preference/choiced/security_department/deserialize(input, datum/preferences/preferences)
|
||||
if (!(input in GLOB.security_depts_prefs))
|
||||
return SEC_DEPT_NONE
|
||||
return ..(input, preferences)
|
||||
|
||||
/datum/preference/choiced/security_department/init_possible_values()
|
||||
return GLOB.security_depts_prefs
|
||||
|
||||
/datum/preference/choiced/security_department/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
|
||||
/datum/preference/choiced/security_department/create_default_value()
|
||||
return SEC_DEPT_NONE
|
||||
|
||||
/datum/preference/choiced/security_department/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return !CONFIG_GET(flag/sec_start_brig)
|
||||
@@ -0,0 +1,37 @@
|
||||
/datum/preference/choiced/skin_tone
|
||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "skin_tone"
|
||||
|
||||
/datum/preference/choiced/skin_tone/init_possible_values()
|
||||
return GLOB.skin_tones
|
||||
|
||||
/datum/preference/choiced/skin_tone/compile_constant_data()
|
||||
var/list/data = ..()
|
||||
|
||||
data[CHOICED_PREFERENCE_DISPLAY_NAMES] = GLOB.skin_tone_names
|
||||
|
||||
var/list/to_hex = list()
|
||||
for (var/choice in get_choices())
|
||||
var/hex_value = skintone2hex(choice)
|
||||
var/list/rgb = hex2rgb("#[hex_value]")
|
||||
var/list/hsl = rgb2hsl(rgb[1], rgb[2], rgb[3])
|
||||
|
||||
to_hex[choice] = list(
|
||||
"lightness" = hsl[3],
|
||||
"value" = hex_value,
|
||||
)
|
||||
|
||||
data["to_hex"] = to_hex
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference/choiced/skin_tone/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.skin_tone = value
|
||||
|
||||
/datum/preference/choiced/skin_tone/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
var/datum/species/species_type = preferences.read_preference(/datum/preference/choiced/species)
|
||||
return initial(species_type.use_skintones)
|
||||
@@ -0,0 +1,60 @@
|
||||
/// Species preference
|
||||
/datum/preference/choiced/species
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "species"
|
||||
priority = PREFERENCE_PRIORITY_SPECIES
|
||||
randomize_by_default = FALSE
|
||||
|
||||
/datum/preference/choiced/species/deserialize(input, datum/preferences/preferences)
|
||||
return GLOB.species_list[sanitize_inlist(input, get_choices_serialized(), "human")]
|
||||
|
||||
/datum/preference/choiced/species/serialize(input)
|
||||
var/datum/species/species = input
|
||||
return initial(species.id)
|
||||
|
||||
/datum/preference/choiced/species/create_default_value()
|
||||
return /datum/species/human
|
||||
|
||||
/datum/preference/choiced/species/create_random_value(datum/preferences/preferences)
|
||||
return pick(get_choices())
|
||||
|
||||
/datum/preference/choiced/species/init_possible_values()
|
||||
var/list/values = list()
|
||||
|
||||
for (var/species_id in get_selectable_species())
|
||||
values += GLOB.species_list[species_id]
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/choiced/species/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.set_species(value, icon_update = FALSE, pref_load = TRUE)
|
||||
|
||||
/datum/preference/choiced/species/compile_constant_data()
|
||||
var/list/data = list()
|
||||
|
||||
var/list/food_flags = FOOD_FLAGS
|
||||
|
||||
for (var/species_id in get_selectable_species())
|
||||
var/species_type = GLOB.species_list[species_id]
|
||||
var/datum/species/species = new species_type
|
||||
|
||||
var/list/diet = list()
|
||||
|
||||
if (!(TRAIT_NOHUNGER in species.inherent_traits))
|
||||
diet = list(
|
||||
"liked_food" = bitfield2list(species.liked_food, food_flags),
|
||||
"disliked_food" = bitfield2list(species.disliked_food, food_flags),
|
||||
"toxic_food" = bitfield2list(species.toxic_food, food_flags),
|
||||
)
|
||||
|
||||
data[species_id] = list(
|
||||
"name" = species.name,
|
||||
"icon" = sanitize_css_class_name(species.name),
|
||||
|
||||
"use_skintones" = species.use_skintones,
|
||||
"sexes" = species.sexes,
|
||||
|
||||
"enabled_features" = species.get_features(),
|
||||
) + diet
|
||||
|
||||
return data
|
||||
@@ -0,0 +1,101 @@
|
||||
/proc/generate_possible_values_for_sprite_accessories_on_head(accessories)
|
||||
var/list/values = possible_values_for_sprite_accessory_list(accessories)
|
||||
|
||||
var/icon/head_icon = icon('icons/mob/human_parts_greyscale.dmi', "human_head_m")
|
||||
head_icon.Blend("#[skintone2hex("caucasian1")]", ICON_MULTIPLY)
|
||||
|
||||
for (var/name in values)
|
||||
var/datum/sprite_accessory/accessory = accessories[name]
|
||||
if (accessory == null || accessory.icon_state == null)
|
||||
continue
|
||||
|
||||
var/icon/final_icon = new(head_icon)
|
||||
|
||||
var/icon/beard_icon = values[name]
|
||||
beard_icon.Blend(COLOR_DARK_BROWN, ICON_MULTIPLY)
|
||||
final_icon.Blend(beard_icon, ICON_OVERLAY)
|
||||
|
||||
final_icon.Crop(10, 19, 22, 31)
|
||||
final_icon.Scale(32, 32)
|
||||
|
||||
values[name] = final_icon
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/color_legacy/eye_color
|
||||
savefile_key = "eye_color"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
||||
relevant_species_trait = EYECOLOR
|
||||
|
||||
/datum/preference/color_legacy/eye_color/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.eye_color = value
|
||||
|
||||
var/obj/item/organ/eyes/eyes_organ = target.getorgan(/obj/item/organ/eyes)
|
||||
if (istype(eyes_organ))
|
||||
if (!initial(eyes_organ.eye_color))
|
||||
eyes_organ.eye_color = value
|
||||
eyes_organ.old_eye_color = value
|
||||
|
||||
/datum/preference/color_legacy/eye_color/create_default_value()
|
||||
return random_eye_color()
|
||||
|
||||
/datum/preference/choiced/facial_hairstyle
|
||||
savefile_key = "facial_style_name"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_FEATURES
|
||||
main_feature_name = "Facial hair"
|
||||
should_generate_icons = TRUE
|
||||
relevant_species_trait = FACEHAIR
|
||||
|
||||
/datum/preference/choiced/facial_hairstyle/init_possible_values()
|
||||
return generate_possible_values_for_sprite_accessories_on_head(GLOB.facial_hairstyles_list)
|
||||
|
||||
/datum/preference/choiced/facial_hairstyle/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.facial_hairstyle = value
|
||||
|
||||
/datum/preference/choiced/facial_hairstyle/compile_constant_data()
|
||||
var/list/data = ..()
|
||||
|
||||
data[SUPPLEMENTAL_FEATURE_KEY] = "facial_hair_color"
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference/color_legacy/facial_hair_color
|
||||
savefile_key = "facial_hair_color"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_SUPPLEMENTAL_FEATURES
|
||||
relevant_species_trait = FACEHAIR
|
||||
|
||||
/datum/preference/color_legacy/facial_hair_color/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.facial_hair_color = value
|
||||
|
||||
/datum/preference/color_legacy/hair_color
|
||||
savefile_key = "hair_color"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_SUPPLEMENTAL_FEATURES
|
||||
relevant_species_trait = HAIR
|
||||
|
||||
/datum/preference/color_legacy/hair_color/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.hair_color = value
|
||||
|
||||
/datum/preference/choiced/hairstyle
|
||||
savefile_key = "hairstyle_name"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_FEATURES
|
||||
main_feature_name = "Hairstyle"
|
||||
should_generate_icons = TRUE
|
||||
relevant_species_trait = HAIR
|
||||
|
||||
/datum/preference/choiced/hairstyle/init_possible_values()
|
||||
return generate_possible_values_for_sprite_accessories_on_head(GLOB.hairstyles_list)
|
||||
|
||||
/datum/preference/choiced/hairstyle/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.hairstyle = value
|
||||
|
||||
/datum/preference/choiced/hairstyle/compile_constant_data()
|
||||
var/list/data = ..()
|
||||
|
||||
data[SUPPLEMENTAL_FEATURE_KEY] = "hair_color"
|
||||
|
||||
return data
|
||||
@@ -0,0 +1,33 @@
|
||||
/datum/preference/choiced/ethereal_color
|
||||
savefile_key = "feature_ethcolor"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_FEATURES
|
||||
main_feature_name = "Ethereal color"
|
||||
should_generate_icons = TRUE
|
||||
|
||||
/datum/preference/choiced/ethereal_color/init_possible_values()
|
||||
var/list/values = list()
|
||||
|
||||
var/icon/ethereal_base = icon('icons/mob/human_parts_greyscale.dmi', "ethereal_head_m")
|
||||
ethereal_base.Blend(icon('icons/mob/human_parts_greyscale.dmi', "ethereal_chest_m"), ICON_OVERLAY)
|
||||
ethereal_base.Blend(icon('icons/mob/human_parts_greyscale.dmi', "ethereal_l_arm"), ICON_OVERLAY)
|
||||
ethereal_base.Blend(icon('icons/mob/human_parts_greyscale.dmi', "ethereal_r_arm"), ICON_OVERLAY)
|
||||
|
||||
var/icon/eyes = icon('icons/mob/human_face.dmi', "eyes")
|
||||
eyes.Blend(COLOR_BLACK, ICON_MULTIPLY)
|
||||
ethereal_base.Blend(eyes, ICON_OVERLAY)
|
||||
|
||||
ethereal_base.Scale(64, 64)
|
||||
ethereal_base.Crop(15, 64, 15 + 31, 64 - 31)
|
||||
|
||||
for (var/name in GLOB.color_list_ethereal)
|
||||
var/color = GLOB.color_list_ethereal[name]
|
||||
|
||||
var/icon/icon = new(ethereal_base)
|
||||
icon.Blend("#[color]", ICON_MULTIPLY)
|
||||
values[name] = icon
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/choiced/ethereal_color/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.dna.features["ethcolor"] = GLOB.color_list_ethereal[value]
|
||||
@@ -0,0 +1,33 @@
|
||||
/datum/preference/choiced/tail_human
|
||||
savefile_key = "feature_human_tail"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
||||
can_randomize = FALSE
|
||||
relevant_mutant_bodypart = "tail_human"
|
||||
|
||||
/datum/preference/choiced/tail_human/init_possible_values()
|
||||
return assoc_to_keys(GLOB.tails_list_human)
|
||||
|
||||
/datum/preference/choiced/tail_human/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.dna.features["tail_human"] = value
|
||||
|
||||
/datum/preference/choiced/tail_human/create_default_value()
|
||||
var/datum/sprite_accessory/tails/human/cat/tail = /datum/sprite_accessory/tails/human/cat
|
||||
return initial(tail.name)
|
||||
|
||||
/datum/preference/choiced/ears
|
||||
savefile_key = "feature_human_ears"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
||||
can_randomize = FALSE
|
||||
relevant_mutant_bodypart = "ears"
|
||||
|
||||
/datum/preference/choiced/ears/init_possible_values()
|
||||
return assoc_to_keys(GLOB.ears_list)
|
||||
|
||||
/datum/preference/choiced/ears/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.dna.features["ears"] = value
|
||||
|
||||
/datum/preference/choiced/ears/create_default_value()
|
||||
var/datum/sprite_accessory/ears/cat/ears = /datum/sprite_accessory/ears/cat
|
||||
return initial(ears.name)
|
||||
@@ -0,0 +1,141 @@
|
||||
/proc/generate_lizard_side_shots(list/sprite_accessories, key, include_snout = TRUE)
|
||||
var/list/values = list()
|
||||
|
||||
var/icon/lizard = icon('icons/mob/human_parts_greyscale.dmi', "lizard_head_m", EAST)
|
||||
|
||||
var/icon/eyes = icon('icons/mob/human_face.dmi', "eyes", EAST)
|
||||
eyes.Blend(COLOR_GRAY, ICON_MULTIPLY)
|
||||
lizard.Blend(eyes, ICON_OVERLAY)
|
||||
|
||||
if (include_snout)
|
||||
lizard.Blend(icon('icons/mob/mutant_bodyparts.dmi', "m_snout_round_ADJ", EAST), ICON_OVERLAY)
|
||||
|
||||
for (var/name in sprite_accessories)
|
||||
var/datum/sprite_accessory/sprite_accessory = sprite_accessories[name]
|
||||
|
||||
var/icon/final_icon = icon(lizard)
|
||||
|
||||
if (sprite_accessory.icon_state != "none")
|
||||
var/icon/accessory_icon = icon(sprite_accessory.icon, "m_[key]_[sprite_accessory.icon_state]_ADJ", EAST)
|
||||
final_icon.Blend(accessory_icon, ICON_OVERLAY)
|
||||
|
||||
final_icon.Crop(11, 20, 23, 32)
|
||||
final_icon.Scale(32, 32)
|
||||
final_icon.Blend(COLOR_VIBRANT_LIME, ICON_MULTIPLY)
|
||||
|
||||
values[name] = final_icon
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/choiced/lizard_body_markings
|
||||
savefile_key = "feature_lizard_body_markings"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_FEATURES
|
||||
main_feature_name = "Body markings"
|
||||
should_generate_icons = TRUE
|
||||
relevant_mutant_bodypart = "body_markings"
|
||||
|
||||
/datum/preference/choiced/lizard_body_markings/init_possible_values()
|
||||
var/list/values = list()
|
||||
|
||||
var/icon/lizard = icon('icons/mob/human_parts_greyscale.dmi', "lizard_chest_m")
|
||||
|
||||
for (var/name in GLOB.body_markings_list)
|
||||
var/datum/sprite_accessory/sprite_accessory = GLOB.body_markings_list[name]
|
||||
|
||||
var/icon/final_icon = icon(lizard)
|
||||
|
||||
if (sprite_accessory.icon_state != "none")
|
||||
var/icon/body_markings_icon = icon(
|
||||
'icons/mob/mutant_bodyparts.dmi',
|
||||
"m_body_markings_[sprite_accessory.icon_state]_ADJ",
|
||||
)
|
||||
|
||||
final_icon.Blend(body_markings_icon, ICON_OVERLAY)
|
||||
|
||||
final_icon.Blend(COLOR_VIBRANT_LIME, ICON_MULTIPLY)
|
||||
final_icon.Crop(10, 8, 22, 23)
|
||||
final_icon.Scale(26, 32)
|
||||
final_icon.Crop(-2, 1, 29, 32)
|
||||
|
||||
values[name] = final_icon
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/choiced/lizard_body_markings/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.dna.features["body_markings"] = value
|
||||
|
||||
/datum/preference/choiced/lizard_frills
|
||||
savefile_key = "feature_lizard_frills"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_FEATURES
|
||||
main_feature_name = "Frills"
|
||||
should_generate_icons = TRUE
|
||||
|
||||
/datum/preference/choiced/lizard_frills/init_possible_values()
|
||||
return generate_lizard_side_shots(GLOB.frills_list, "frills")
|
||||
|
||||
/datum/preference/choiced/lizard_frills/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.dna.features["frills"] = value
|
||||
|
||||
/datum/preference/choiced/lizard_horns
|
||||
savefile_key = "feature_lizard_horns"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_FEATURES
|
||||
main_feature_name = "Horns"
|
||||
should_generate_icons = TRUE
|
||||
|
||||
/datum/preference/choiced/lizard_horns/init_possible_values()
|
||||
return generate_lizard_side_shots(GLOB.horns_list, "horns")
|
||||
|
||||
/datum/preference/choiced/lizard_horns/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.dna.features["horns"] = value
|
||||
|
||||
/datum/preference/choiced/lizard_legs
|
||||
savefile_key = "feature_lizard_legs"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
||||
relevant_mutant_bodypart = "legs"
|
||||
|
||||
/datum/preference/choiced/lizard_legs/init_possible_values()
|
||||
return assoc_to_keys(GLOB.legs_list)
|
||||
|
||||
/datum/preference/choiced/lizard_legs/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.dna.features["legs"] = value
|
||||
|
||||
/datum/preference/choiced/lizard_snout
|
||||
savefile_key = "feature_lizard_snout"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_FEATURES
|
||||
main_feature_name = "Snout"
|
||||
should_generate_icons = TRUE
|
||||
|
||||
/datum/preference/choiced/lizard_snout/init_possible_values()
|
||||
return generate_lizard_side_shots(GLOB.snouts_list, "snout", include_snout = FALSE)
|
||||
|
||||
/datum/preference/choiced/lizard_snout/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.dna.features["snout"] = value
|
||||
|
||||
/datum/preference/choiced/lizard_spines
|
||||
savefile_key = "feature_lizard_spines"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
||||
relevant_mutant_bodypart = "spines"
|
||||
|
||||
/datum/preference/choiced/lizard_spines/init_possible_values()
|
||||
return assoc_to_keys(GLOB.spines_list)
|
||||
|
||||
/datum/preference/choiced/lizard_spines/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.dna.features["spines"] = value
|
||||
|
||||
/datum/preference/choiced/lizard_tail
|
||||
savefile_key = "feature_lizard_tail"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
||||
relevant_mutant_bodypart = "tail_lizard"
|
||||
|
||||
/datum/preference/choiced/lizard_tail/init_possible_values()
|
||||
return assoc_to_keys(GLOB.tails_list_lizard)
|
||||
|
||||
/datum/preference/choiced/lizard_tail/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.dna.features["tail_lizard"] = value
|
||||
@@ -0,0 +1,102 @@
|
||||
/datum/preference/choiced/moth_antennae
|
||||
savefile_key = "feature_moth_antennae"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_FEATURES
|
||||
main_feature_name = "Antennae"
|
||||
should_generate_icons = TRUE
|
||||
|
||||
/datum/preference/choiced/moth_antennae/init_possible_values()
|
||||
var/list/values = list()
|
||||
|
||||
var/icon/moth_head = icon('icons/mob/human_parts.dmi', "moth_head_m")
|
||||
moth_head.Blend(icon('icons/mob/human_face.dmi', "motheyes"), ICON_OVERLAY)
|
||||
|
||||
for (var/antennae_name in GLOB.moth_antennae_list)
|
||||
var/datum/sprite_accessory/antennae = GLOB.moth_antennae_list[antennae_name]
|
||||
|
||||
var/icon/icon_with_antennae = new(moth_head)
|
||||
icon_with_antennae.Blend(icon('icons/mob/moth_antennae.dmi', "m_moth_antennae_[antennae.icon_state]_FRONT"), ICON_OVERLAY)
|
||||
icon_with_antennae.Scale(64, 64)
|
||||
icon_with_antennae.Crop(15, 64, 15 + 31, 64 - 31)
|
||||
|
||||
values[antennae.name] = icon_with_antennae
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/choiced/moth_antennae/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.dna.features["moth_antennae"] = value
|
||||
|
||||
/datum/preference/choiced/moth_markings
|
||||
savefile_key = "feature_moth_markings"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_FEATURES
|
||||
main_feature_name = "Body markings"
|
||||
should_generate_icons = TRUE
|
||||
relevant_mutant_bodypart = "moth_markings"
|
||||
|
||||
/datum/preference/choiced/moth_markings/init_possible_values()
|
||||
var/list/values = list()
|
||||
|
||||
var/icon/moth_body = icon('icons/blanks/32x32.dmi', "nothing")
|
||||
|
||||
moth_body.Blend(icon('icons/mob/moth_wings.dmi', "m_moth_wings_plain_BEHIND"), ICON_OVERLAY)
|
||||
|
||||
var/list/body_parts = list(
|
||||
BODY_ZONE_HEAD,
|
||||
BODY_ZONE_CHEST,
|
||||
BODY_ZONE_L_ARM,
|
||||
BODY_ZONE_R_ARM,
|
||||
)
|
||||
|
||||
for (var/body_part in body_parts)
|
||||
var/gender = (body_part == "chest" || body_part == "head") ? "_m" : ""
|
||||
moth_body.Blend(icon('icons/mob/human_parts.dmi', "moth_[body_part][gender]"), ICON_OVERLAY)
|
||||
|
||||
moth_body.Blend(icon('icons/mob/human_face.dmi', "motheyes"), ICON_OVERLAY)
|
||||
|
||||
for (var/markings_name in GLOB.moth_markings_list)
|
||||
var/datum/sprite_accessory/markings = GLOB.moth_markings_list[markings_name]
|
||||
var/icon/icon_with_markings = new(moth_body)
|
||||
|
||||
if (markings_name != "None")
|
||||
for (var/body_part in body_parts)
|
||||
var/icon/body_part_icon = icon(markings.icon, "[markings.icon_state]_[body_part]")
|
||||
body_part_icon.Crop(1, 1, 32, 32)
|
||||
icon_with_markings.Blend(body_part_icon, ICON_OVERLAY)
|
||||
|
||||
icon_with_markings.Blend(icon('icons/mob/moth_wings.dmi', "m_moth_wings_plain_FRONT"), ICON_OVERLAY)
|
||||
icon_with_markings.Blend(icon('icons/mob/moth_antennae.dmi', "m_moth_antennae_plain_FRONT"), ICON_OVERLAY)
|
||||
|
||||
// Zoom in on the top of the head and the chest
|
||||
icon_with_markings.Scale(64, 64)
|
||||
icon_with_markings.Crop(15, 64, 15 + 31, 64 - 31)
|
||||
|
||||
values[markings.name] = icon_with_markings
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/choiced/moth_markings/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.dna.features["moth_markings"] = value
|
||||
|
||||
/datum/preference/choiced/moth_wings
|
||||
savefile_key = "feature_moth_wings"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_FEATURES
|
||||
main_feature_name = "Moth wings"
|
||||
should_generate_icons = TRUE
|
||||
|
||||
/datum/preference/choiced/moth_wings/init_possible_values()
|
||||
var/list/icon/values = possible_values_for_sprite_accessory_list_for_body_part(
|
||||
GLOB.moth_wings_list,
|
||||
"moth_wings",
|
||||
list("BEHIND", "FRONT"),
|
||||
)
|
||||
|
||||
// Moth wings are in a stupid dimension
|
||||
for (var/name in values)
|
||||
values[name].Crop(1, 1, 32, 32)
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/choiced/moth_wings/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.dna.features["moth_wings"] = value
|
||||
@@ -0,0 +1,20 @@
|
||||
/datum/preference/color_legacy/mutant_color
|
||||
savefile_key = "feature_mcolor"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
||||
relevant_species_trait = MUTCOLORS
|
||||
|
||||
/datum/preference/color_legacy/mutant_color/create_default_value()
|
||||
return sanitize_hexcolor("[pick("7F", "FF")][pick("7F", "FF")][pick("7F", "FF")]")
|
||||
|
||||
/datum/preference/color_legacy/mutant_color/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.dna.features["mcolor"] = value
|
||||
|
||||
/datum/preference/color_legacy/mutant_color/is_valid(value)
|
||||
if (!..(value))
|
||||
return FALSE
|
||||
|
||||
if (is_color_dark(expand_three_digit_color(value)))
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
@@ -0,0 +1,20 @@
|
||||
/datum/preference/toggle/tgui_fancy
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "tgui_fancy"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/tgui_fancy/apply_to_client(client/client, value)
|
||||
for (var/datum/tgui/tgui as anything in client.mob?.tgui_open_uis)
|
||||
// Force it to reload either way
|
||||
tgui.update_static_data(client.mob)
|
||||
|
||||
/datum/preference/toggle/tgui_lock
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "tgui_lock"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
default_value = FALSE
|
||||
|
||||
/datum/preference/toggle/tgui_lock/apply_to_client(client/client, value)
|
||||
for (var/datum/tgui/tgui as anything in client.mob?.tgui_open_uis)
|
||||
// Force it to reload either way
|
||||
tgui.update_static_data(client.mob)
|
||||
@@ -0,0 +1,116 @@
|
||||
/// Handle the migrations necessary from pre-tgui prefs to post-tgui prefs
|
||||
/datum/preferences/proc/migrate_preferences_to_tgui_prefs_menu()
|
||||
migrate_antagonists()
|
||||
migrate_key_bindings()
|
||||
|
||||
/// Handle the migrations necessary from pre-tgui prefs to post-tgui prefs, for characters
|
||||
/datum/preferences/proc/migrate_character_to_tgui_prefs_menu()
|
||||
migrate_randomization()
|
||||
|
||||
// Key bindings used to be "key" -> list("action"),
|
||||
// such as "X" -> list("swap_hands").
|
||||
// This made it impossible to determine any order, meaning placing a new
|
||||
// hotkey would produce non-deterministic order.
|
||||
// tgui prefs menu moves this over to "swap_hands" -> list("X").
|
||||
/datum/preferences/proc/migrate_key_bindings()
|
||||
var/new_key_bindings = list()
|
||||
|
||||
for (var/unbound_hotkey in key_bindings["Unbound"])
|
||||
new_key_bindings[unbound_hotkey] = list()
|
||||
|
||||
for (var/hotkey in key_bindings)
|
||||
if (hotkey == "Unbound")
|
||||
continue
|
||||
|
||||
for (var/keybind in key_bindings[hotkey])
|
||||
if (keybind in new_key_bindings)
|
||||
new_key_bindings[keybind] |= hotkey
|
||||
else
|
||||
new_key_bindings[keybind] = list(hotkey)
|
||||
|
||||
key_bindings = new_key_bindings
|
||||
|
||||
// Before tgui preferences menu, "traitor" would handle both roundstart, midround, and latejoin.
|
||||
// These were split apart.
|
||||
/datum/preferences/proc/migrate_antagonists()
|
||||
migrate_antagonist(ROLE_HERETIC, list(ROLE_HERETIC_SMUGGLER))
|
||||
migrate_antagonist(ROLE_MALF, list(ROLE_MALF_MIDROUND))
|
||||
migrate_antagonist(ROLE_OPERATIVE, list(ROLE_OPERATIVE_MIDROUND, ROLE_LONE_OPERATIVE))
|
||||
migrate_antagonist(ROLE_REV_HEAD, list(ROLE_PROVOCATEUR))
|
||||
migrate_antagonist(ROLE_TRAITOR, list(ROLE_SYNDICATE_INFILTRATOR, ROLE_SLEEPER_AGENT))
|
||||
migrate_antagonist(ROLE_WIZARD, list(ROLE_WIZARD_MIDROUND))
|
||||
|
||||
// "Familes [sic] Antagonists" was the old name of the catch-all.
|
||||
migrate_antagonist("Familes Antagonists", list(ROLE_FAMILIES, ROLE_FAMILY_HEAD_ASPIRANT))
|
||||
|
||||
/datum/preferences/proc/migrate_antagonist(will_exist, list/to_add)
|
||||
if (will_exist in be_special)
|
||||
for (var/add in to_add)
|
||||
be_special += add
|
||||
|
||||
// Randomization used to be an assoc list of fields to TRUE.
|
||||
// Antagonist randomization was not even available to all options.
|
||||
// tgui prefs menu changes from list("random_socks" = TRUE, "random_name_antag" = TRUE)
|
||||
// to list("socks" = "enabled", "name" = "antag")
|
||||
// as well as removing anything that was set to FALSE, as this can be extrapolated.
|
||||
/datum/preferences/proc/migrate_randomization()
|
||||
var/static/list/random_settings = list(
|
||||
"random_age" = "age",
|
||||
"random_backpack" = "backpack",
|
||||
"random_eye_color" = "eye_color",
|
||||
"random_facial_hair_color" = "facial_hair_color",
|
||||
"random_facial_hairstyle" = "facial_hairstyle",
|
||||
"random_gender" = "gender",
|
||||
"random_hair_color" = "hair_color",
|
||||
"random_hairstyle" = "hairstyle",
|
||||
"random_jumpsuit_style" = "jumpsuit_style",
|
||||
"random_skin_tone" = "skin_tone",
|
||||
"random_socks" = "socks",
|
||||
"random_species" = "species",
|
||||
"random_undershirt" = "undershirt",
|
||||
"random_underwear" = "underwear",
|
||||
"random_underwear_color" = "underwear_color",
|
||||
)
|
||||
|
||||
var/static/list/random_antag_settings = list(
|
||||
"random_age_antag" = "age",
|
||||
"random_gender_antag" = "gender",
|
||||
"random_name_antag" = "name",
|
||||
)
|
||||
|
||||
var/list/new_randomise = list()
|
||||
|
||||
for (var/old_setting in random_settings)
|
||||
if (randomise[old_setting])
|
||||
new_randomise[random_settings[old_setting]] = RANDOM_ENABLED
|
||||
|
||||
for (var/old_antag_setting in random_antag_settings)
|
||||
if (randomise[old_antag_setting])
|
||||
new_randomise[random_settings[old_antag_setting]] = RANDOM_ANTAG_ONLY
|
||||
|
||||
migrate_randomization_to_new_pref(
|
||||
/datum/preference/choiced/random_body,
|
||||
"random_body",
|
||||
"random_body_antag",
|
||||
)
|
||||
|
||||
migrate_randomization_to_new_pref(
|
||||
/datum/preference/choiced/random_name,
|
||||
"random_name",
|
||||
"random_name_antag",
|
||||
)
|
||||
|
||||
if (randomise["random_hardcore"])
|
||||
write_preference(GLOB.preference_entries[/datum/preference/toggle/random_hardcore], TRUE)
|
||||
|
||||
randomise = new_randomise
|
||||
|
||||
/datum/preferences/proc/migrate_randomization_to_new_pref(
|
||||
preference_type,
|
||||
key,
|
||||
key_antag,
|
||||
)
|
||||
if (randomise[key_antag])
|
||||
write_preference(GLOB.preference_entries[preference_type], RANDOM_ANTAG_ONLY)
|
||||
else if (randomise[key])
|
||||
write_preference(GLOB.preference_entries[preference_type], RANDOM_ENABLED)
|
||||
@@ -0,0 +1,15 @@
|
||||
/datum/preference/numeric/tooltip_delay
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "tip_delay"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
minimum = 0
|
||||
maximum = 5000
|
||||
|
||||
/datum/preference/numeric/tooltip_delay/create_default_value()
|
||||
return 500
|
||||
|
||||
/datum/preference/toggle/enable_tooltips
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "enable_tips"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
@@ -0,0 +1,26 @@
|
||||
/// UI style preference
|
||||
/datum/preference/choiced/ui_style
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
savefile_key = "UI_style"
|
||||
should_generate_icons = TRUE
|
||||
|
||||
/datum/preference/choiced/ui_style/init_possible_values()
|
||||
var/list/values = list()
|
||||
|
||||
for (var/style in GLOB.available_ui_styles)
|
||||
var/icon/icons = GLOB.available_ui_styles[style]
|
||||
|
||||
var/icon/icon = icon(icons, "hand_r")
|
||||
icon.Crop(1, 1, world.icon_size * 2, world.icon_size)
|
||||
icon.Blend(icon(icons, "hand_l"), ICON_OVERLAY, world.icon_size)
|
||||
|
||||
values[style] = icon
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/choiced/ui_style/create_default_value()
|
||||
return GLOB.available_ui_styles[1]
|
||||
|
||||
/datum/preference/choiced/ui_style/apply_to_client(client/client, value)
|
||||
client.mob?.hud_used?.update_ui_style(ui_style2icon(value))
|
||||
@@ -0,0 +1,15 @@
|
||||
/datum/preference/color_legacy/underwear_color
|
||||
savefile_key = "underwear_color"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_SUPPLEMENTAL_FEATURES
|
||||
|
||||
/datum/preference/color_legacy/underwear_color/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.underwear_color = value
|
||||
|
||||
/datum/preference/color_legacy/underwear_color/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
var/species_type = preferences.read_preference(/datum/preference/choiced/species)
|
||||
var/datum/species/species = new species_type
|
||||
return !(NO_UNDERWEAR in species.species_traits)
|
||||
@@ -0,0 +1,26 @@
|
||||
/datum/preference/choiced/uplink_location
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "uplink_loc"
|
||||
can_randomize = FALSE
|
||||
|
||||
/datum/preference/choiced/uplink_location/init_possible_values()
|
||||
return list(UPLINK_PDA, UPLINK_RADIO, UPLINK_PEN, UPLINK_IMPLANT)
|
||||
|
||||
/datum/preference/choiced/uplink_location/compile_constant_data()
|
||||
var/list/data = ..()
|
||||
|
||||
data[CHOICED_PREFERENCE_DISPLAY_NAMES] = list(
|
||||
UPLINK_PDA = "PDA",
|
||||
UPLINK_RADIO = "Radio",
|
||||
UPLINK_PEN = "Pen",
|
||||
UPLINK_IMPLANT = "Implant ([UPLINK_IMPLANT_TELECRYSTAL_COST]TC)",
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference/choiced/uplink_location/create_default_value()
|
||||
return UPLINK_PDA
|
||||
|
||||
/datum/preference/choiced/uplink_location/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
@@ -0,0 +1,7 @@
|
||||
/datum/preference/toggle/widescreen
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "widescreenpref"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/widescreen/apply_to_client(client/client, value)
|
||||
client.view_size?.setDefault(getScreenSize(value))
|
||||
@@ -0,0 +1,5 @@
|
||||
/// Enables flashing the window in your task tray for important events
|
||||
/datum/preference/toggle/window_flashing
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "windowflashing"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
@@ -0,0 +1,26 @@
|
||||
/datum/verbs/menu/Preferences/verb/open_character_preferences()
|
||||
set category = "OOC"
|
||||
set name = "Open Character Preferences"
|
||||
set desc = "Open Character Preferences"
|
||||
|
||||
var/datum/preferences/preferences = usr?.client?.prefs
|
||||
if (!preferences)
|
||||
return
|
||||
|
||||
preferences.current_window = PREFERENCE_TAB_CHARACTER_PREFERENCES
|
||||
preferences.update_static_data(usr)
|
||||
preferences.ui_interact(usr)
|
||||
|
||||
/datum/verbs/menu/Preferences/verb/open_game_preferences()
|
||||
set category = "OOC"
|
||||
set name = "Open Game Preferences"
|
||||
set desc = "Open Game Preferences"
|
||||
|
||||
var/datum/preferences/preferences = usr?.client?.prefs
|
||||
if (!preferences)
|
||||
return
|
||||
|
||||
preferences.current_window = PREFERENCE_TAB_GAME_PREFERENCES
|
||||
preferences.update_static_data(usr)
|
||||
preferences.ui_interact(usr)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// You do not need to raise this if you are adding new values that have sane defaults.
|
||||
// Only raise this value when changing the meaning/format/name/layout of an existing value
|
||||
// where you would want the updater procs below to run
|
||||
#define SAVEFILE_VERSION_MAX 40
|
||||
#define SAVEFILE_VERSION_MAX 41
|
||||
|
||||
/*
|
||||
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
|
||||
@@ -46,7 +46,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
toggles |= SOUND_ENDOFROUND
|
||||
|
||||
if(current_version < 34)
|
||||
auto_fit_viewport = TRUE
|
||||
write_preference(/datum/preference/toggle/auto_fit_viewport, TRUE)
|
||||
|
||||
if(current_version < 35) //makes old keybinds compatible with #52040, sets the new default
|
||||
var/newkey = FALSE
|
||||
@@ -70,8 +70,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
key_bindings["ShiftQ"] = list("quick_equip_suit_storage")
|
||||
|
||||
if(current_version < 37)
|
||||
if(clientfps == 0)
|
||||
clientfps = -1
|
||||
if(read_preference(/datum/preference/numeric/fps) == 0)
|
||||
write_preference(GLOB.preference_entries[/datum/preference/numeric/fps], -1)
|
||||
|
||||
if (current_version < 38)
|
||||
var/found_block_movement = FALSE
|
||||
@@ -93,33 +93,38 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
if (current_version < 40)
|
||||
LAZYADD(key_bindings["Space"], "hold_throw_mode")
|
||||
|
||||
/datum/preferences/proc/update_character(current_version, savefile/S)
|
||||
return
|
||||
if (current_version < 41)
|
||||
migrate_preferences_to_tgui_prefs_menu()
|
||||
|
||||
/datum/preferences/proc/update_character(current_version, savefile/savefile)
|
||||
if (current_version < 41)
|
||||
migrate_character_to_tgui_prefs_menu()
|
||||
|
||||
/// checks through keybindings for outdated unbound keys and updates them
|
||||
/datum/preferences/proc/check_keybindings()
|
||||
if(!parent)
|
||||
return
|
||||
var/list/user_binds = list()
|
||||
for (var/key in key_bindings)
|
||||
for(var/kb_name in key_bindings[key])
|
||||
user_binds[kb_name] += list(key)
|
||||
var/list/binds_by_key = get_key_bindings_by_key(key_bindings)
|
||||
var/list/notadded = list()
|
||||
for (var/name in GLOB.keybindings_by_name)
|
||||
var/datum/keybinding/kb = GLOB.keybindings_by_name[name]
|
||||
if(length(user_binds[kb.name]))
|
||||
if(kb.name in key_bindings)
|
||||
continue // key is unbound and or bound to something
|
||||
|
||||
var/addedbind = FALSE
|
||||
if(hotkeys)
|
||||
key_bindings[kb.name] = list()
|
||||
|
||||
if(parent.hotkeys)
|
||||
for(var/hotkeytobind in kb.hotkey_keys)
|
||||
if(!length(key_bindings[hotkeytobind]) || hotkeytobind == "Unbound") //Only bind to the key if nothing else is bound expect for Unbound
|
||||
LAZYADD(key_bindings[hotkeytobind], kb.name)
|
||||
if(!length(binds_by_key[hotkeytobind]) && hotkeytobind != "Unbound") //Only bind to the key if nothing else is bound expect for Unbound
|
||||
key_bindings[kb.name] |= hotkeytobind
|
||||
addedbind = TRUE
|
||||
else
|
||||
for(var/classickeytobind in kb.classic_keys)
|
||||
if(!length(key_bindings[classickeytobind]) || classickeytobind == "Unbound") //Only bind to the key if nothing else is bound expect for Unbound
|
||||
LAZYADD(key_bindings[classickeytobind], kb.name)
|
||||
if(!length(binds_by_key[classickeytobind]) && classickeytobind != "Unbound") //Only bind to the key if nothing else is bound expect for Unbound
|
||||
key_bindings[kb.name] |= classickeytobind
|
||||
addedbind = TRUE
|
||||
|
||||
if(!addedbind)
|
||||
notadded += kb
|
||||
save_preferences() //Save the players pref so that new keys that were set to Unbound as default are permanently stored
|
||||
@@ -128,13 +133,10 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
|
||||
/datum/preferences/proc/announce_conflict(list/notadded)
|
||||
to_chat(parent, "<span class='warningplain'><b><u>Keybinding Conflict</u></b></span>\n\
|
||||
<span class='warningplain'><b>There are new <a href='?_src_=prefs;preference=tab;tab=3'>keybindings</a> that default to keys you've already bound. The new ones will be unbound.</b></span>")
|
||||
<span class='warningplain'><b>There are new <a href='?src=[REF(src)];open_keybindings=1'>keybindings</a> that default to keys you've already bound. The new ones will be unbound.</b></span>")
|
||||
for(var/item in notadded)
|
||||
var/datum/keybinding/conflicted = item
|
||||
to_chat(parent, span_danger("[conflicted.category]: [conflicted.full_name] needs updating"))
|
||||
LAZYADD(key_bindings["Unbound"], conflicted.name) // set it to unbound to prevent this from opening up again in the future
|
||||
save_preferences()
|
||||
|
||||
|
||||
|
||||
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
|
||||
@@ -161,54 +163,23 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
fcopy(S, bacpath) //byond helpfully lets you use a savefile for the first arg.
|
||||
return FALSE
|
||||
|
||||
//general preferences
|
||||
READ_FILE(S["asaycolor"], asaycolor)
|
||||
READ_FILE(S["brief_outfit"], brief_outfit)
|
||||
READ_FILE(S["ooccolor"], ooccolor)
|
||||
READ_FILE(S["screentip_color"], screentip_color)
|
||||
READ_FILE(S["lastchangelog"], lastchangelog)
|
||||
READ_FILE(S["UI_style"], UI_style)
|
||||
READ_FILE(S["hotkeys"], hotkeys)
|
||||
READ_FILE(S["chat_on_map"], chat_on_map)
|
||||
READ_FILE(S["max_chat_length"], max_chat_length)
|
||||
READ_FILE(S["see_chat_non_mob"] , see_chat_non_mob)
|
||||
READ_FILE(S["see_rc_emotes"] , see_rc_emotes)
|
||||
READ_FILE(S["broadcast_login_logout"] , broadcast_login_logout)
|
||||
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(parent, read_preference(preference.type))
|
||||
|
||||
//general preferences
|
||||
READ_FILE(S["lastchangelog"], lastchangelog)
|
||||
|
||||
READ_FILE(S["tgui_fancy"], tgui_fancy)
|
||||
READ_FILE(S["tgui_lock"], tgui_lock)
|
||||
READ_FILE(S["buttons_locked"], buttons_locked)
|
||||
READ_FILE(S["windowflash"], windowflashing)
|
||||
READ_FILE(S["be_special"] , be_special)
|
||||
|
||||
|
||||
READ_FILE(S["default_slot"], default_slot)
|
||||
READ_FILE(S["chat_toggles"], chat_toggles)
|
||||
READ_FILE(S["toggles"], toggles)
|
||||
READ_FILE(S["ghost_form"], ghost_form)
|
||||
READ_FILE(S["ghost_orbit"], ghost_orbit)
|
||||
READ_FILE(S["ghost_accs"], ghost_accs)
|
||||
READ_FILE(S["ghost_others"], ghost_others)
|
||||
READ_FILE(S["preferred_map"], preferred_map)
|
||||
READ_FILE(S["ignoring"], ignoring)
|
||||
READ_FILE(S["ghost_hud"], ghost_hud)
|
||||
READ_FILE(S["inquisitive_ghost"], inquisitive_ghost)
|
||||
READ_FILE(S["uses_glasses_colour"], uses_glasses_colour)
|
||||
READ_FILE(S["clientfps"], clientfps)
|
||||
READ_FILE(S["parallax"], parallax)
|
||||
READ_FILE(S["ambientocclusion"], ambientocclusion)
|
||||
READ_FILE(S["screentip_pref"], screentip_pref)
|
||||
READ_FILE(S["itemoutline_pref"], itemoutline_pref)
|
||||
READ_FILE(S["auto_fit_viewport"], auto_fit_viewport)
|
||||
READ_FILE(S["widescreenpref"], widescreenpref)
|
||||
READ_FILE(S["pixel_size"], pixel_size)
|
||||
READ_FILE(S["scaling_method"], scaling_method)
|
||||
READ_FILE(S["menuoptions"], menuoptions)
|
||||
READ_FILE(S["enable_tips"], enable_tips)
|
||||
READ_FILE(S["tip_delay"], tip_delay)
|
||||
READ_FILE(S["pda_style"], pda_style)
|
||||
READ_FILE(S["pda_color"], pda_color)
|
||||
READ_FILE(S["darkened_flash"], darkened_flash)
|
||||
|
||||
// OOC commendations
|
||||
READ_FILE(S["hearted_until"], hearted_until)
|
||||
@@ -226,7 +197,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
|
||||
// Custom hotkeys
|
||||
READ_FILE(S["key_bindings"], key_bindings)
|
||||
check_keybindings() // this apparently fails every time and overwrites any unloaded prefs with the default values, so don't load anything after this line or it won't actually save
|
||||
|
||||
//try to fix any outdated data if necessary
|
||||
if(needs_update >= 0)
|
||||
@@ -236,47 +206,16 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
fcopy(S, bacpath) //byond helpfully lets you use a savefile for the first arg.
|
||||
update_preferences(needs_update, S) //needs_update = savefile_version if we need an update (positive integer)
|
||||
|
||||
|
||||
check_keybindings() // this apparently fails every time and overwrites any unloaded prefs with the default values, so don't load anything after this line or it won't actually save
|
||||
key_bindings_by_key = get_key_bindings_by_key(key_bindings)
|
||||
|
||||
//Sanitize
|
||||
asaycolor = sanitize_ooccolor(sanitize_hexcolor(asaycolor, 6, 1, initial(asaycolor)))
|
||||
ooccolor = sanitize_ooccolor(sanitize_hexcolor(ooccolor, 6, 1, initial(ooccolor)))
|
||||
screentip_color = sanitize_ooccolor(sanitize_hexcolor(screentip_color, 6, 1, initial(screentip_color)))
|
||||
lastchangelog = sanitize_text(lastchangelog, initial(lastchangelog))
|
||||
UI_style = sanitize_inlist(UI_style, GLOB.available_ui_styles, GLOB.available_ui_styles[1])
|
||||
hotkeys = sanitize_integer(hotkeys, FALSE, TRUE, initial(hotkeys))
|
||||
chat_on_map = sanitize_integer(chat_on_map, FALSE, TRUE, initial(chat_on_map))
|
||||
max_chat_length = sanitize_integer(max_chat_length, 1, CHAT_MESSAGE_MAX_LENGTH, initial(max_chat_length))
|
||||
see_chat_non_mob = sanitize_integer(see_chat_non_mob, FALSE, TRUE, initial(see_chat_non_mob))
|
||||
see_rc_emotes = sanitize_integer(see_rc_emotes, FALSE, TRUE, initial(see_rc_emotes))
|
||||
broadcast_login_logout = sanitize_integer(broadcast_login_logout, FALSE, TRUE, initial(broadcast_login_logout))
|
||||
tgui_fancy = sanitize_integer(tgui_fancy, FALSE, TRUE, initial(tgui_fancy))
|
||||
tgui_lock = sanitize_integer(tgui_lock, FALSE, TRUE, initial(tgui_lock))
|
||||
buttons_locked = sanitize_integer(buttons_locked, FALSE, TRUE, initial(buttons_locked))
|
||||
windowflashing = sanitize_integer(windowflashing, FALSE, TRUE, initial(windowflashing))
|
||||
default_slot = sanitize_integer(default_slot, 1, max_save_slots, initial(default_slot))
|
||||
toggles = sanitize_integer(toggles, 0, (2**24)-1, initial(toggles))
|
||||
clientfps = sanitize_integer(clientfps, -1, 1000, 0)
|
||||
parallax = sanitize_integer(parallax, PARALLAX_INSANE, PARALLAX_DISABLE, null)
|
||||
ambientocclusion = sanitize_integer(ambientocclusion, FALSE, TRUE, initial(ambientocclusion))
|
||||
screentip_pref = sanitize_integer(screentip_pref, FALSE, TRUE, initial(screentip_pref))
|
||||
itemoutline_pref = sanitize_integer(itemoutline_pref, FALSE, TRUE, initial(itemoutline_pref))
|
||||
auto_fit_viewport = sanitize_integer(auto_fit_viewport, FALSE, TRUE, initial(auto_fit_viewport))
|
||||
widescreenpref = sanitize_integer(widescreenpref, FALSE, TRUE, initial(widescreenpref))
|
||||
pixel_size = sanitize_float(pixel_size, PIXEL_SCALING_AUTO, PIXEL_SCALING_3X, 0.5, initial(pixel_size))
|
||||
scaling_method = sanitize_text(scaling_method, initial(scaling_method))
|
||||
ghost_form = sanitize_inlist(ghost_form, GLOB.ghost_forms, initial(ghost_form))
|
||||
ghost_orbit = sanitize_inlist(ghost_orbit, GLOB.ghost_orbits, initial(ghost_orbit))
|
||||
ghost_accs = sanitize_inlist(ghost_accs, GLOB.ghost_accs_options, GHOST_ACCS_DEFAULT_OPTION)
|
||||
ghost_others = sanitize_inlist(ghost_others, GLOB.ghost_others_options, GHOST_OTHERS_DEFAULT_OPTION)
|
||||
menuoptions = SANITIZE_LIST(menuoptions)
|
||||
be_special = SANITIZE_LIST(be_special)
|
||||
brief_outfit = sanitize_inlist(brief_outfit, subtypesof(/datum/outfit), null)
|
||||
pda_style = sanitize_inlist(pda_style, GLOB.pda_styles, initial(pda_style))
|
||||
pda_color = sanitize_hexcolor(pda_color, 6, 1, initial(pda_color))
|
||||
key_bindings = sanitize_keybindings(key_bindings)
|
||||
favorite_outfits = SANITIZE_LIST(favorite_outfits)
|
||||
darkened_flash = sanitize_integer(darkened_flash, FALSE, TRUE, initial(darkened_flash))
|
||||
|
||||
if(needs_update >= 0) //save the updated version
|
||||
var/old_default_slot = default_slot
|
||||
@@ -308,57 +247,34 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
|
||||
WRITE_FILE(S["version"] , SAVEFILE_VERSION_MAX) //updates (or failing that the sanity checks) will ensure data is not invalid at load. Assume up-to-date
|
||||
|
||||
for (var/preference_type in GLOB.preference_entries)
|
||||
var/datum/preference/preference = GLOB.preference_entries[preference_type]
|
||||
if (preference.savefile_identifier != PREFERENCE_PLAYER)
|
||||
continue
|
||||
|
||||
if (!(preference.type in recently_updated_keys))
|
||||
continue
|
||||
|
||||
recently_updated_keys -= preference.type
|
||||
|
||||
if (preference_type in value_cache)
|
||||
write_preference(preference, preference.serialize(value_cache[preference_type]))
|
||||
|
||||
//general preferences
|
||||
WRITE_FILE(S["asaycolor"], asaycolor)
|
||||
WRITE_FILE(S["brief_outfit"], brief_outfit)
|
||||
WRITE_FILE(S["ooccolor"], ooccolor)
|
||||
WRITE_FILE(S["screentip_color"], screentip_color)
|
||||
WRITE_FILE(S["lastchangelog"], lastchangelog)
|
||||
WRITE_FILE(S["UI_style"], UI_style)
|
||||
WRITE_FILE(S["hotkeys"], hotkeys)
|
||||
WRITE_FILE(S["chat_on_map"], chat_on_map)
|
||||
WRITE_FILE(S["max_chat_length"], max_chat_length)
|
||||
WRITE_FILE(S["see_chat_non_mob"], see_chat_non_mob)
|
||||
WRITE_FILE(S["see_rc_emotes"], see_rc_emotes)
|
||||
WRITE_FILE(S["broadcast_login_logout"], broadcast_login_logout)
|
||||
WRITE_FILE(S["tgui_fancy"], tgui_fancy)
|
||||
WRITE_FILE(S["tgui_lock"], tgui_lock)
|
||||
WRITE_FILE(S["buttons_locked"], buttons_locked)
|
||||
WRITE_FILE(S["windowflash"], windowflashing)
|
||||
WRITE_FILE(S["be_special"], be_special)
|
||||
WRITE_FILE(S["default_slot"], default_slot)
|
||||
WRITE_FILE(S["toggles"], toggles)
|
||||
WRITE_FILE(S["chat_toggles"], chat_toggles)
|
||||
WRITE_FILE(S["ghost_form"], ghost_form)
|
||||
WRITE_FILE(S["ghost_orbit"], ghost_orbit)
|
||||
WRITE_FILE(S["ghost_accs"], ghost_accs)
|
||||
WRITE_FILE(S["ghost_others"], ghost_others)
|
||||
WRITE_FILE(S["preferred_map"], preferred_map)
|
||||
WRITE_FILE(S["ignoring"], ignoring)
|
||||
WRITE_FILE(S["ghost_hud"], ghost_hud)
|
||||
WRITE_FILE(S["inquisitive_ghost"], inquisitive_ghost)
|
||||
WRITE_FILE(S["uses_glasses_colour"], uses_glasses_colour)
|
||||
WRITE_FILE(S["clientfps"], clientfps)
|
||||
WRITE_FILE(S["parallax"], parallax)
|
||||
WRITE_FILE(S["ambientocclusion"], ambientocclusion)
|
||||
WRITE_FILE(S["screentip_pref"], screentip_pref)
|
||||
WRITE_FILE(S["itemoutline_pref"], itemoutline_pref)
|
||||
WRITE_FILE(S["auto_fit_viewport"], auto_fit_viewport)
|
||||
WRITE_FILE(S["widescreenpref"], widescreenpref)
|
||||
WRITE_FILE(S["pixel_size"], pixel_size)
|
||||
WRITE_FILE(S["scaling_method"], scaling_method)
|
||||
WRITE_FILE(S["menuoptions"], menuoptions)
|
||||
WRITE_FILE(S["enable_tips"], enable_tips)
|
||||
WRITE_FILE(S["tip_delay"], tip_delay)
|
||||
WRITE_FILE(S["pda_style"], pda_style)
|
||||
WRITE_FILE(S["pda_color"], pda_color)
|
||||
WRITE_FILE(S["key_bindings"], key_bindings)
|
||||
WRITE_FILE(S["hearted_until"], (hearted_until > world.realtime ? hearted_until : null))
|
||||
WRITE_FILE(S["favorite_outfits"], favorite_outfits)
|
||||
WRITE_FILE(S["darkened_flash"], darkened_flash)
|
||||
return TRUE
|
||||
|
||||
/datum/preferences/proc/load_character(slot)
|
||||
SHOULD_NOT_SLEEP(TRUE)
|
||||
|
||||
if(!path)
|
||||
return FALSE
|
||||
if(!fexists(path))
|
||||
@@ -375,79 +291,24 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
WRITE_FILE(S["default_slot"] , slot)
|
||||
|
||||
S.cd = "/character[slot]"
|
||||
character_savefile = S
|
||||
var/needs_update = savefile_needs_update(S)
|
||||
if(needs_update == -2) //fatal, can't load any data
|
||||
return FALSE
|
||||
|
||||
//Species
|
||||
var/species_id
|
||||
READ_FILE(S["species"], species_id)
|
||||
if(species_id)
|
||||
var/newtype = GLOB.species_list[species_id]
|
||||
if(newtype)
|
||||
pref_species = new newtype
|
||||
// Read everything into cache
|
||||
for (var/preference_type in GLOB.preference_entries)
|
||||
var/datum/preference/preference = GLOB.preference_entries[preference_type]
|
||||
if (preference.savefile_identifier != PREFERENCE_CHARACTER)
|
||||
continue
|
||||
|
||||
value_cache -= preference_type
|
||||
read_preference(preference_type)
|
||||
|
||||
//Character
|
||||
READ_FILE(S["real_name"], real_name)
|
||||
READ_FILE(S["gender"], gender)
|
||||
READ_FILE(S["body_type"], body_type)
|
||||
READ_FILE(S["age"], age)
|
||||
READ_FILE(S["hair_color"], hair_color)
|
||||
READ_FILE(S["facial_hair_color"], facial_hair_color)
|
||||
READ_FILE(S["eye_color"], eye_color)
|
||||
READ_FILE(S["skin_tone"], skin_tone)
|
||||
READ_FILE(S["hairstyle_name"], hairstyle)
|
||||
READ_FILE(S["facial_style_name"], facial_hairstyle)
|
||||
READ_FILE(S["underwear"], underwear)
|
||||
READ_FILE(S["underwear_color"], underwear_color)
|
||||
READ_FILE(S["undershirt"], undershirt)
|
||||
READ_FILE(S["socks"], socks)
|
||||
READ_FILE(S["backpack"], backpack)
|
||||
READ_FILE(S["jumpsuit_style"], jumpsuit_style)
|
||||
READ_FILE(S["uplink_loc"], uplink_spawn_loc)
|
||||
READ_FILE(S["playtime_reward_cloak"], playtime_reward_cloak)
|
||||
READ_FILE(S["phobia"], phobia)
|
||||
READ_FILE(S["randomise"], randomise)
|
||||
READ_FILE(S["feature_mcolor"], features["mcolor"])
|
||||
READ_FILE(S["feature_ethcolor"], features["ethcolor"])
|
||||
READ_FILE(S["feature_lizard_tail"], features["tail_lizard"])
|
||||
READ_FILE(S["feature_lizard_snout"], features["snout"])
|
||||
READ_FILE(S["feature_lizard_horns"], features["horns"])
|
||||
READ_FILE(S["feature_lizard_frills"], features["frills"])
|
||||
READ_FILE(S["feature_lizard_spines"], features["spines"])
|
||||
READ_FILE(S["feature_lizard_body_markings"], features["body_markings"])
|
||||
READ_FILE(S["feature_lizard_legs"], features["legs"])
|
||||
READ_FILE(S["feature_moth_wings"], features["moth_wings"])
|
||||
READ_FILE(S["feature_moth_antennae"], features["moth_antennae"])
|
||||
READ_FILE(S["feature_moth_markings"], features["moth_markings"])
|
||||
READ_FILE(S["persistent_scars"] , persistent_scars)
|
||||
if(!CONFIG_GET(flag/join_with_mutant_humans))
|
||||
features["tail_human"] = "none"
|
||||
features["ears"] = "none"
|
||||
else
|
||||
READ_FILE(S["feature_human_tail"], features["tail_human"])
|
||||
READ_FILE(S["feature_human_ears"], features["ears"])
|
||||
|
||||
//Custom names
|
||||
for(var/custom_name_id in GLOB.preferences_custom_names)
|
||||
var/savefile_slot_name = custom_name_id + "_name" //TODO remove this
|
||||
READ_FILE(S[savefile_slot_name], custom_names[custom_name_id])
|
||||
|
||||
READ_FILE(S["preferred_ai_core_display"], preferred_ai_core_display)
|
||||
READ_FILE(S["prefered_security_department"], prefered_security_department)
|
||||
|
||||
// This is the version when the random security department was removed.
|
||||
// When the minimum is higher than that version, it's impossible for someone to have the "Random" department.
|
||||
#if SAVEFILE_VERSION_MIN > 40
|
||||
#warn The prefered_security_department check in preferences_savefile.dm is no longer necessary.
|
||||
#endif
|
||||
|
||||
if (!(prefered_security_department in GLOB.security_depts_prefs))
|
||||
prefered_security_department = SEC_DEPT_NONE
|
||||
|
||||
//Jobs
|
||||
READ_FILE(S["joblessrole"], joblessrole)
|
||||
//Load prefs
|
||||
READ_FILE(S["job_preferences"], job_preferences)
|
||||
|
||||
@@ -460,59 +321,10 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
update_character(needs_update, S) //needs_update == savefile_version if we need an update (positive integer)
|
||||
|
||||
//Sanitize
|
||||
real_name = reject_bad_name(real_name)
|
||||
gender = sanitize_gender(gender)
|
||||
body_type = sanitize_gender(body_type, FALSE, FALSE, gender)
|
||||
if(!real_name)
|
||||
real_name = random_unique_name(gender)
|
||||
|
||||
for(var/custom_name_id in GLOB.preferences_custom_names)
|
||||
var/namedata = GLOB.preferences_custom_names[custom_name_id]
|
||||
custom_names[custom_name_id] = reject_bad_name(custom_names[custom_name_id],namedata["allow_numbers"])
|
||||
if(!custom_names[custom_name_id])
|
||||
custom_names[custom_name_id] = get_default_name(custom_name_id)
|
||||
|
||||
if(!features["mcolor"] || features["mcolor"] == "#000")
|
||||
features["mcolor"] = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F")
|
||||
|
||||
if(!features["ethcolor"] || features["ethcolor"] == "#000")
|
||||
features["ethcolor"] = GLOB.color_list_ethereal[pick(GLOB.color_list_ethereal)]
|
||||
|
||||
randomise = SANITIZE_LIST(randomise)
|
||||
|
||||
hairstyle = sanitize_inlist(hairstyle, GLOB.hairstyles_list)
|
||||
facial_hairstyle = sanitize_inlist(facial_hairstyle, GLOB.facial_hairstyles_list)
|
||||
underwear = sanitize_inlist(underwear, GLOB.underwear_list)
|
||||
undershirt = sanitize_inlist(undershirt, GLOB.undershirt_list)
|
||||
socks = sanitize_inlist(socks, GLOB.socks_list)
|
||||
age = sanitize_integer(age, AGE_MIN, AGE_MAX, initial(age))
|
||||
hair_color = sanitize_hexcolor(hair_color, 3, 0)
|
||||
facial_hair_color = sanitize_hexcolor(facial_hair_color, 3, 0)
|
||||
underwear_color = sanitize_hexcolor(underwear_color, 3, 0)
|
||||
eye_color = sanitize_hexcolor(eye_color, 3, 0)
|
||||
skin_tone = sanitize_inlist(skin_tone, GLOB.skin_tones)
|
||||
backpack = sanitize_inlist(backpack, GLOB.backpacklist, initial(backpack))
|
||||
jumpsuit_style = sanitize_inlist(jumpsuit_style, GLOB.jumpsuitlist, initial(jumpsuit_style))
|
||||
uplink_spawn_loc = sanitize_inlist(uplink_spawn_loc, GLOB.uplink_spawn_loc_list_save, initial(uplink_spawn_loc))
|
||||
playtime_reward_cloak = sanitize_integer(playtime_reward_cloak)
|
||||
features["mcolor"] = sanitize_hexcolor(features["mcolor"], 3, 0)
|
||||
features["ethcolor"] = copytext_char(features["ethcolor"], 1, 7)
|
||||
features["tail_lizard"] = sanitize_inlist(features["tail_lizard"], GLOB.tails_list_lizard)
|
||||
features["tail_human"] = sanitize_inlist(features["tail_human"], GLOB.tails_list_human, "None")
|
||||
features["snout"] = sanitize_inlist(features["snout"], GLOB.snouts_list)
|
||||
features["horns"] = sanitize_inlist(features["horns"], GLOB.horns_list)
|
||||
features["ears"] = sanitize_inlist(features["ears"], GLOB.ears_list, "None")
|
||||
features["frills"] = sanitize_inlist(features["frills"], GLOB.frills_list)
|
||||
features["spines"] = sanitize_inlist(features["spines"], GLOB.spines_list)
|
||||
features["body_markings"] = sanitize_inlist(features["body_markings"], GLOB.body_markings_list)
|
||||
features["feature_lizard_legs"] = sanitize_inlist(features["legs"], GLOB.legs_list, "Normal Legs")
|
||||
features["moth_wings"] = sanitize_inlist(features["moth_wings"], GLOB.moth_wings_list, "Plain")
|
||||
features["moth_antennae"] = sanitize_inlist(features["moth_antennae"], GLOB.moth_antennae_list, "Plain")
|
||||
features["moth_markings"] = sanitize_inlist(features["moth_markings"], GLOB.moth_markings_list, "None")
|
||||
|
||||
persistent_scars = sanitize_integer(persistent_scars)
|
||||
|
||||
joblessrole = sanitize_integer(joblessrole, 1, 3, initial(joblessrole))
|
||||
//Validate job prefs
|
||||
for(var/j in job_preferences)
|
||||
if(job_preferences[j] != JP_LOW && job_preferences[j] != JP_MEDIUM && job_preferences[j] != JP_HIGH)
|
||||
@@ -524,6 +336,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
return TRUE
|
||||
|
||||
/datum/preferences/proc/save_character()
|
||||
SHOULD_NOT_SLEEP(TRUE)
|
||||
|
||||
if(!path)
|
||||
return FALSE
|
||||
var/savefile/S = new /savefile(path)
|
||||
@@ -531,56 +345,30 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
return FALSE
|
||||
S.cd = "/character[default_slot]"
|
||||
|
||||
for (var/datum/preference/preference as anything in get_preferences_in_priority_order())
|
||||
if (preference.savefile_identifier != PREFERENCE_CHARACTER)
|
||||
continue
|
||||
|
||||
if (!(preference.type in recently_updated_keys))
|
||||
continue
|
||||
|
||||
recently_updated_keys -= preference.type
|
||||
|
||||
if (preference.type in value_cache)
|
||||
write_preference(preference, preference.serialize(value_cache[preference.type]))
|
||||
|
||||
WRITE_FILE(S["version"] , SAVEFILE_VERSION_MAX) //load_character will sanitize any bad data, so assume up-to-date.)
|
||||
|
||||
// This is the version when the random security department was removed.
|
||||
// When the minimum is higher than that version, it's impossible for someone to have the "Random" department.
|
||||
#if SAVEFILE_VERSION_MIN > 40
|
||||
#warn The prefered_security_department check in code/modules/client/preferences/security_department.dm is no longer necessary.
|
||||
#endif
|
||||
|
||||
//Character
|
||||
WRITE_FILE(S["real_name"] , real_name)
|
||||
WRITE_FILE(S["gender"] , gender)
|
||||
WRITE_FILE(S["body_type"] , body_type)
|
||||
WRITE_FILE(S["age"] , age)
|
||||
WRITE_FILE(S["hair_color"] , hair_color)
|
||||
WRITE_FILE(S["facial_hair_color"] , facial_hair_color)
|
||||
WRITE_FILE(S["eye_color"] , eye_color)
|
||||
WRITE_FILE(S["skin_tone"] , skin_tone)
|
||||
WRITE_FILE(S["hairstyle_name"] , hairstyle)
|
||||
WRITE_FILE(S["facial_style_name"] , facial_hairstyle)
|
||||
WRITE_FILE(S["underwear"] , underwear)
|
||||
WRITE_FILE(S["underwear_color"] , underwear_color)
|
||||
WRITE_FILE(S["undershirt"] , undershirt)
|
||||
WRITE_FILE(S["socks"] , socks)
|
||||
WRITE_FILE(S["backpack"] , backpack)
|
||||
WRITE_FILE(S["jumpsuit_style"] , jumpsuit_style)
|
||||
WRITE_FILE(S["uplink_loc"] , uplink_spawn_loc)
|
||||
WRITE_FILE(S["playtime_reward_cloak"] , playtime_reward_cloak)
|
||||
WRITE_FILE(S["randomise"] , randomise)
|
||||
WRITE_FILE(S["species"] , pref_species.id)
|
||||
WRITE_FILE(S["phobia"], phobia)
|
||||
WRITE_FILE(S["feature_mcolor"] , features["mcolor"])
|
||||
WRITE_FILE(S["feature_ethcolor"] , features["ethcolor"])
|
||||
WRITE_FILE(S["feature_lizard_tail"] , features["tail_lizard"])
|
||||
WRITE_FILE(S["feature_human_tail"] , features["tail_human"])
|
||||
WRITE_FILE(S["feature_lizard_snout"] , features["snout"])
|
||||
WRITE_FILE(S["feature_lizard_horns"] , features["horns"])
|
||||
WRITE_FILE(S["feature_human_ears"] , features["ears"])
|
||||
WRITE_FILE(S["feature_lizard_frills"] , features["frills"])
|
||||
WRITE_FILE(S["feature_lizard_spines"] , features["spines"])
|
||||
WRITE_FILE(S["feature_lizard_body_markings"] , features["body_markings"])
|
||||
WRITE_FILE(S["feature_lizard_legs"] , features["legs"])
|
||||
WRITE_FILE(S["feature_moth_wings"] , features["moth_wings"])
|
||||
WRITE_FILE(S["feature_moth_antennae"] , features["moth_antennae"])
|
||||
WRITE_FILE(S["feature_moth_markings"] , features["moth_markings"])
|
||||
WRITE_FILE(S["persistent_scars"] , persistent_scars)
|
||||
|
||||
//Custom names
|
||||
for(var/custom_name_id in GLOB.preferences_custom_names)
|
||||
var/savefile_slot_name = custom_name_id + "_name" //TODO remove this
|
||||
WRITE_FILE(S[savefile_slot_name],custom_names[custom_name_id])
|
||||
|
||||
WRITE_FILE(S["preferred_ai_core_display"] , preferred_ai_core_display)
|
||||
WRITE_FILE(S["prefered_security_department"] , prefered_security_department)
|
||||
|
||||
//Jobs
|
||||
WRITE_FILE(S["joblessrole"] , joblessrole)
|
||||
//Write prefs
|
||||
WRITE_FILE(S["job_preferences"] , job_preferences)
|
||||
|
||||
@@ -592,10 +380,9 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
|
||||
/proc/sanitize_keybindings(value)
|
||||
var/list/base_bindings = sanitize_islist(value,list())
|
||||
for(var/key in base_bindings)
|
||||
base_bindings[key] = base_bindings[key] & GLOB.keybindings_by_name
|
||||
if(!length(base_bindings[key]))
|
||||
base_bindings -= key
|
||||
for(var/keybind_name in base_bindings)
|
||||
if (!(keybind_name in GLOB.keybindings_by_name))
|
||||
base_bindings -= keybind_name
|
||||
return base_bindings
|
||||
|
||||
#undef SAVEFILE_VERSION_MAX
|
||||
|
||||
@@ -1,516 +0,0 @@
|
||||
//this works as is to create a single checked item, but has no back end code for toggleing the check yet
|
||||
#define TOGGLE_CHECKBOX(PARENT, CHILD) PARENT/CHILD/abstract = TRUE;PARENT/CHILD/checkbox = CHECKBOX_TOGGLE;PARENT/CHILD/verb/CHILD
|
||||
|
||||
//Example usage TOGGLE_CHECKBOX(datum/verbs/menu/settings/Ghost/chatterbox, toggle_ghost_ears)()
|
||||
|
||||
/datum/verbs/menu/settings
|
||||
name = "Settings"
|
||||
|
||||
//override because we don't want to save preferences twice.
|
||||
/datum/verbs/menu/settings/Set_checked(client/C, verbpath)
|
||||
if (checkbox == CHECKBOX_GROUP)
|
||||
C.prefs.menuoptions[type] = verbpath
|
||||
else if (checkbox == CHECKBOX_TOGGLE)
|
||||
var/checked = Get_checked(C)
|
||||
C.prefs.menuoptions[type] = !checked
|
||||
winset(C, "[verbpath]", "is-checked = [!checked]")
|
||||
|
||||
/datum/verbs/menu/settings/verb/setup_character()
|
||||
set name = "Game Preferences"
|
||||
set category = "Preferences"
|
||||
set desc = "Open Game Preferences Window"
|
||||
usr.client.prefs.current_tab = 1
|
||||
usr.client.prefs.ShowChoices(usr)
|
||||
|
||||
/datum/verbs/menu/settings/ghost
|
||||
name = "Ghost"
|
||||
|
||||
//toggles
|
||||
/datum/verbs/menu/settings/ghost/chatterbox
|
||||
name = "Chat Box Spam"
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_ears)()
|
||||
set name = "Show/Hide GhostEars"
|
||||
set category = "Preferences"
|
||||
set desc = "See All Speech"
|
||||
usr.client.prefs.chat_toggles ^= CHAT_GHOSTEARS
|
||||
to_chat(usr, "<span class='infoplain'>As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTEARS) ? "see all speech in the world" : "only see speech from nearby mobs"].</span>")
|
||||
usr.client.prefs.save_preferences()
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Ears", "[usr.client.prefs.chat_toggles & CHAT_GHOSTEARS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_ears/Get_checked(client/C)
|
||||
return C.prefs.chat_toggles & CHAT_GHOSTEARS
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_sight)()
|
||||
set name = "Show/Hide GhostSight"
|
||||
set category = "Preferences"
|
||||
set desc = "See All Emotes"
|
||||
usr.client.prefs.chat_toggles ^= CHAT_GHOSTSIGHT
|
||||
to_chat(usr, "<span class='infoplain'>As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTSIGHT) ? "see all emotes in the world" : "only see emotes from nearby mobs"].</span>")
|
||||
usr.client.prefs.save_preferences()
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Sight", "[usr.client.prefs.chat_toggles & CHAT_GHOSTSIGHT ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_sight/Get_checked(client/C)
|
||||
return C.prefs.chat_toggles & CHAT_GHOSTSIGHT
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_whispers)()
|
||||
set name = "Show/Hide GhostWhispers"
|
||||
set category = "Preferences"
|
||||
set desc = "See All Whispers"
|
||||
usr.client.prefs.chat_toggles ^= CHAT_GHOSTWHISPER
|
||||
to_chat(usr, "<span class='infoplain'>As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTWHISPER) ? "see all whispers in the world" : "only see whispers from nearby mobs"].</span>")
|
||||
usr.client.prefs.save_preferences()
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Whispers", "[usr.client.prefs.chat_toggles & CHAT_GHOSTWHISPER ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_whispers/Get_checked(client/C)
|
||||
return C.prefs.chat_toggles & CHAT_GHOSTWHISPER
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_radio)()
|
||||
set name = "Show/Hide GhostRadio"
|
||||
set category = "Preferences"
|
||||
set desc = "See All Radio Chatter"
|
||||
usr.client.prefs.chat_toggles ^= CHAT_GHOSTRADIO
|
||||
to_chat(usr, "<span class='infoplain'>As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTRADIO) ? "see radio chatter" : "not see radio chatter"].</span>")
|
||||
usr.client.prefs.save_preferences()
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Radio", "[usr.client.prefs.chat_toggles & CHAT_GHOSTRADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! //social experiment, increase the generation whenever you copypaste this shamelessly GENERATION 1
|
||||
/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_radio/Get_checked(client/C)
|
||||
return C.prefs.chat_toggles & CHAT_GHOSTRADIO
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_pda)()
|
||||
set name = "Show/Hide GhostPDA"
|
||||
set category = "Preferences"
|
||||
set desc = "See All PDA Messages"
|
||||
usr.client.prefs.chat_toggles ^= CHAT_GHOSTPDA
|
||||
to_chat(usr, "<span class='infoplain'>As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTPDA) ? "see all pda messages in the world" : "only see pda messages from nearby mobs"].</span>")
|
||||
usr.client.prefs.save_preferences()
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost PDA", "[usr.client.prefs.chat_toggles & CHAT_GHOSTPDA ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_pda/Get_checked(client/C)
|
||||
return C.prefs.chat_toggles & CHAT_GHOSTPDA
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_laws)()
|
||||
set name = "Show/Hide GhostLaws"
|
||||
set category = "Preferences"
|
||||
set desc = "See All Law Changes"
|
||||
usr.client.prefs.chat_toggles ^= CHAT_GHOSTLAWS
|
||||
to_chat(usr, "<span class='infoplain'>As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTLAWS) ? "be notified of all law changes" : "no longer be notified of law changes"].</span>")
|
||||
usr.client.prefs.save_preferences()
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Laws", "[usr.client.prefs.chat_toggles & CHAT_GHOSTLAWS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_laws/Get_checked(client/C)
|
||||
return C.prefs.chat_toggles & CHAT_GHOSTLAWS
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_hear_login_logout)()
|
||||
set name = "Show/Hide Login/Logout messages"
|
||||
set category = "Preferences"
|
||||
set desc = "As a ghost, see when someone reconnects or disconnects"
|
||||
usr.client.prefs.chat_toggles ^= CHAT_LOGIN_LOGOUT
|
||||
to_chat(usr, "<span class='infoplain'>As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_LOGIN_LOGOUT) ? "be notified" : "no longer be notified"] when someone disconnects or reconnects.</span>")
|
||||
usr.client.prefs.save_preferences()
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Hearing Login/Logout", "[usr.client.prefs.chat_toggles & CHAT_LOGIN_LOGOUT ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
/datum/verbs/menu/settings/ghost/chatterbox/toggle_hear_login_logout/Get_checked(client/C)
|
||||
return C.prefs.chat_toggles & CHAT_LOGIN_LOGOUT
|
||||
|
||||
/datum/verbs/menu/settings/ghost/chatterbox/events
|
||||
name = "Events"
|
||||
|
||||
//please be aware that the following two verbs have inverted stat output, so that "Toggle Deathrattle|1" still means you activated it
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox/events, toggle_deathrattle)()
|
||||
set name = "Toggle Deathrattle"
|
||||
set category = "Preferences"
|
||||
set desc = "Death"
|
||||
usr.client.prefs.toggles ^= DISABLE_DEATHRATTLE
|
||||
usr.client.prefs.save_preferences()
|
||||
to_chat(usr, "<span class='infoplain'>You will [(usr.client.prefs.toggles & DISABLE_DEATHRATTLE) ? "no longer" : "now"] get messages when a sentient mob dies.</span>")
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Deathrattle", "[!(usr.client.prefs.toggles & DISABLE_DEATHRATTLE) ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, maybe you should spend some time reading the comments.
|
||||
/datum/verbs/menu/settings/ghost/chatterbox/events/toggle_deathrattle/Get_checked(client/C)
|
||||
return !(C.prefs.toggles & DISABLE_DEATHRATTLE)
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox/events, toggle_arrivalrattle)()
|
||||
set name = "Toggle Arrivalrattle"
|
||||
set category = "Preferences"
|
||||
set desc = "New Player Arrival"
|
||||
usr.client.prefs.toggles ^= DISABLE_ARRIVALRATTLE
|
||||
to_chat(usr, "<span class='infoplain'>You will [(usr.client.prefs.toggles & DISABLE_ARRIVALRATTLE) ? "no longer" : "now"] get messages when someone joins the station.</span>")
|
||||
usr.client.prefs.save_preferences()
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Arrivalrattle", "[!(usr.client.prefs.toggles & DISABLE_ARRIVALRATTLE) ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, maybe you should rethink where your life went so wrong.
|
||||
/datum/verbs/menu/settings/ghost/chatterbox/events/toggle_arrivalrattle/Get_checked(client/C)
|
||||
return !(C.prefs.toggles & DISABLE_ARRIVALRATTLE)
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost, togglemidroundantag)()
|
||||
set name = "Toggle Midround Antagonist"
|
||||
set category = "Preferences"
|
||||
set desc = "Midround Antagonist"
|
||||
usr.client.prefs.toggles ^= MIDROUND_ANTAG
|
||||
usr.client.prefs.save_preferences()
|
||||
to_chat(usr, "<span class='infoplain'>You will [(usr.client.prefs.toggles & MIDROUND_ANTAG) ? "now" : "no longer"] be considered for midround antagonist positions.</span>")
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Midround Antag", "[usr.client.prefs.toggles & MIDROUND_ANTAG ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
/datum/verbs/menu/settings/ghost/togglemidroundantag/Get_checked(client/C)
|
||||
return C.prefs.toggles & MIDROUND_ANTAG
|
||||
|
||||
/datum/verbs/menu/settings/sound
|
||||
name = "Sound"
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, toggletitlemusic)()
|
||||
set name = "Hear/Silence Lobby Music"
|
||||
set category = "Preferences"
|
||||
set desc = "Hear Music In Lobby"
|
||||
usr.client.prefs.toggles ^= SOUND_LOBBY
|
||||
usr.client.prefs.save_preferences()
|
||||
if(usr.client.prefs.toggles & SOUND_LOBBY)
|
||||
to_chat(usr, "<span class='infoplain'>You will now hear music in the game lobby.</span>")
|
||||
if(isnewplayer(usr))
|
||||
usr.client.playtitlemusic()
|
||||
else
|
||||
to_chat(usr, "<span class='infoplain'>You will no longer hear music in the game lobby.</span>")
|
||||
usr.stop_sound_channel(CHANNEL_LOBBYMUSIC)
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Lobby Music", "[usr.client.prefs.toggles & SOUND_LOBBY ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
/datum/verbs/menu/settings/sound/toggletitlemusic/Get_checked(client/C)
|
||||
return C.prefs.toggles & SOUND_LOBBY
|
||||
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, toggleendofroundsounds)()
|
||||
set name = "Hear/Silence End of Round Sounds"
|
||||
set category = "Preferences"
|
||||
set desc = "Hear Round End Sounds"
|
||||
usr.client.prefs.toggles ^= SOUND_ENDOFROUND
|
||||
usr.client.prefs.save_preferences()
|
||||
if(usr.client.prefs.toggles & SOUND_ENDOFROUND)
|
||||
to_chat(usr, "<span class='infoplain'>You will now hear sounds played before the server restarts.</span>")
|
||||
else
|
||||
to_chat(usr, "<span class='infoplain'>You will no longer hear sounds played before the server restarts.</span>")
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle End of Round Sounds", "[usr.client.prefs.toggles & SOUND_ENDOFROUND ? "Enabled" : "Disabled"]"))
|
||||
/datum/verbs/menu/settings/sound/toggleendofroundsounds/Get_checked(client/C)
|
||||
return C.prefs.toggles & SOUND_ENDOFROUND
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, togglecombatmodesound)()
|
||||
set name = "Hear/Silence Combat Mode Toggle Sound"
|
||||
set category = "Preferences"
|
||||
set desc = "Hear Combat Mode Toggle Sound"
|
||||
usr.client.prefs.toggles ^= SOUND_COMBATMODE
|
||||
usr.client.prefs.save_preferences()
|
||||
if(usr.client.prefs.toggles & SOUND_COMBATMODE)
|
||||
to_chat(usr, "<span class='infoplain'>You will now hear a sound when combat mode is turned on.</span>")
|
||||
else
|
||||
to_chat(usr, "<span class='infoplain'>You will no longer hear a sound when combat mode is turned on.</span>")
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Combat Mode Toggle Sounds", "[usr.client.prefs.toggles & SOUND_COMBATMODE ? "Enabled" : "Disabled"]"))
|
||||
|
||||
/datum/verbs/menu/settings/sound/togglecombatmodesound/Get_checked(client/C)
|
||||
return C.prefs.toggles & SOUND_COMBATMODE
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, togglemidis)()
|
||||
set name = "Hear/Silence Midis"
|
||||
set category = "Preferences"
|
||||
set desc = "Hear Admin Triggered Music/Sounds"
|
||||
usr.client.prefs.toggles ^= SOUND_MIDI
|
||||
usr.client.prefs.save_preferences()
|
||||
if(usr.client.prefs.toggles & SOUND_MIDI)
|
||||
to_chat(usr, "<span class='infoplain'>You will now hear any sounds uploaded by admins.</span>")
|
||||
else
|
||||
to_chat(usr, "<span class='infoplain'>You will no longer hear sounds uploaded by admins</span>")
|
||||
usr.stop_sound_channel(CHANNEL_ADMIN)
|
||||
var/client/C = usr.client
|
||||
C?.tgui_panel?.stop_music()
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Hearing Midis", "[usr.client.prefs.toggles & SOUND_MIDI ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
/datum/verbs/menu/settings/sound/togglemidis/Get_checked(client/C)
|
||||
return C.prefs.toggles & SOUND_MIDI
|
||||
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, toggle_instruments)()
|
||||
set name = "Hear/Silence Instruments"
|
||||
set category = "Preferences"
|
||||
set desc = "Hear In-game Instruments"
|
||||
usr.client.prefs.toggles ^= SOUND_INSTRUMENTS
|
||||
usr.client.prefs.save_preferences()
|
||||
if(usr.client.prefs.toggles & SOUND_INSTRUMENTS)
|
||||
to_chat(usr, "<span class='infoplain'>You will now hear people playing musical instruments.</span>")
|
||||
else
|
||||
to_chat(usr, "<span class='infoplain'>You will no longer hear musical instruments.</span>")
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Instruments", "[usr.client.prefs.toggles & SOUND_INSTRUMENTS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
/datum/verbs/menu/settings/sound/toggle_instruments/Get_checked(client/C)
|
||||
return C.prefs.toggles & SOUND_INSTRUMENTS
|
||||
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, Toggle_Soundscape)()
|
||||
set name = "Hear/Silence Ambience"
|
||||
set category = "Preferences"
|
||||
set desc = "Hear Ambient Sound Effects"
|
||||
usr.client.prefs.toggles ^= SOUND_AMBIENCE
|
||||
usr.client.prefs.save_preferences()
|
||||
if(usr.client.prefs.toggles & SOUND_AMBIENCE)
|
||||
to_chat(usr, "<span class='infoplain'>You will now hear ambient sounds.</span>")
|
||||
else
|
||||
to_chat(usr, "<span class='infoplain'>You will no longer hear ambient sounds.</span>")
|
||||
usr.stop_sound_channel(CHANNEL_AMBIENCE)
|
||||
usr.stop_sound_channel(CHANNEL_BUZZ)
|
||||
usr.client.update_ambience_pref()
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ambience", "[usr.client.prefs.toggles & SOUND_AMBIENCE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
/datum/verbs/menu/settings/sound/Toggle_Soundscape/Get_checked(client/C)
|
||||
return C.prefs.toggles & SOUND_AMBIENCE
|
||||
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, toggle_ship_ambience)()
|
||||
set name = "Hear/Silence Ship Ambience"
|
||||
set category = "Preferences"
|
||||
set desc = "Hear Ship Ambience Roar"
|
||||
usr.client.prefs.toggles ^= SOUND_SHIP_AMBIENCE
|
||||
usr.client.prefs.save_preferences()
|
||||
if(usr.client.prefs.toggles & SOUND_SHIP_AMBIENCE)
|
||||
to_chat(usr, "<span class='infoplain'>You will now hear ship ambience.</span>")
|
||||
else
|
||||
to_chat(usr, "<span class='infoplain'>You will no longer hear ship ambience.</span>")
|
||||
usr.stop_sound_channel(CHANNEL_BUZZ)
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ship Ambience", "[usr.client.prefs.toggles & SOUND_SHIP_AMBIENCE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, I bet you read this comment expecting to see the same thing :^)
|
||||
/datum/verbs/menu/settings/sound/toggle_ship_ambience/Get_checked(client/C)
|
||||
return C.prefs.toggles & SOUND_SHIP_AMBIENCE
|
||||
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, toggle_announcement_sound)()
|
||||
set name = "Hear/Silence Announcements"
|
||||
set category = "Preferences"
|
||||
set desc = "Hear Announcement Sound"
|
||||
usr.client.prefs.toggles ^= SOUND_ANNOUNCEMENTS
|
||||
to_chat(usr, "<span class='infoplain'>You will now [(usr.client.prefs.toggles & SOUND_ANNOUNCEMENTS) ? "hear announcement sounds" : "no longer hear announcements"].</span>")
|
||||
usr.client.prefs.save_preferences()
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Announcement Sound", "[usr.client.prefs.toggles & SOUND_ANNOUNCEMENTS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
/datum/verbs/menu/settings/sound/toggle_announcement_sound/Get_checked(client/C)
|
||||
return C.prefs.toggles & SOUND_ANNOUNCEMENTS
|
||||
|
||||
|
||||
/datum/verbs/menu/settings/sound/verb/stop_client_sounds()
|
||||
set name = "Stop Sounds"
|
||||
set category = "Preferences"
|
||||
set desc = "Stop Current Sounds"
|
||||
SEND_SOUND(usr, sound(null))
|
||||
var/client/C = usr.client
|
||||
C?.tgui_panel?.stop_music()
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Stop Self Sounds")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings, listen_ooc)()
|
||||
set name = "Show/Hide OOC"
|
||||
set category = "Preferences"
|
||||
set desc = "Show OOC Chat"
|
||||
usr.client.prefs.chat_toggles ^= CHAT_OOC
|
||||
usr.client.prefs.save_preferences()
|
||||
to_chat(usr, "<span class='infoplain'>You will [(usr.client.prefs.chat_toggles & CHAT_OOC) ? "now" : "no longer"] see messages on the OOC channel.</span>")
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Seeing OOC", "[usr.client.prefs.chat_toggles & CHAT_OOC ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
/datum/verbs/menu/settings/listen_ooc/Get_checked(client/C)
|
||||
return C.prefs.chat_toggles & CHAT_OOC
|
||||
|
||||
TOGGLE_CHECKBOX(/datum/verbs/menu/settings, listen_bank_card)()
|
||||
set name = "Show/Hide Income Updates"
|
||||
set category = "Preferences"
|
||||
set desc = "Show or hide updates to your income"
|
||||
usr.client.prefs.chat_toggles ^= CHAT_BANKCARD
|
||||
usr.client.prefs.save_preferences()
|
||||
to_chat(usr, "<span class='infoplain'>You will [(usr.client.prefs.chat_toggles & CHAT_BANKCARD) ? "now" : "no longer"] be notified when you get paid.</span>")
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Income Notifications", "[(usr.client.prefs.chat_toggles & CHAT_BANKCARD) ? "Enabled" : "Disabled"]"))
|
||||
/datum/verbs/menu/settings/listen_bank_card/Get_checked(client/C)
|
||||
return C.prefs.chat_toggles & CHAT_BANKCARD
|
||||
|
||||
|
||||
GLOBAL_LIST_INIT(ghost_forms, sortList(list("ghost","ghostking","ghostian2","skeleghost","ghost_red","ghost_black", \
|
||||
"ghost_blue","ghost_yellow","ghost_green","ghost_pink", \
|
||||
"ghost_cyan","ghost_dblue","ghost_dred","ghost_dgreen", \
|
||||
"ghost_dcyan","ghost_grey","ghost_dyellow","ghost_dpink", "ghost_purpleswirl","ghost_funkypurp","ghost_pinksherbert","ghost_blazeit",\
|
||||
"ghost_mellow","ghost_rainbow","ghost_camo","ghost_fire", "catghost")))
|
||||
/client/proc/pick_form()
|
||||
if(!is_content_unlocked())
|
||||
tgui_alert(usr,"This setting is for accounts with BYOND premium only.")
|
||||
return
|
||||
var/new_form = input(src, "Thanks for supporting BYOND - Choose your ghostly form:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_forms
|
||||
if(new_form)
|
||||
prefs.ghost_form = new_form
|
||||
prefs.save_preferences()
|
||||
if(isobserver(mob))
|
||||
var/mob/dead/observer/O = mob
|
||||
O.update_icon(ALL, new_form)
|
||||
|
||||
GLOBAL_LIST_INIT(ghost_orbits, list(GHOST_ORBIT_CIRCLE,GHOST_ORBIT_TRIANGLE,GHOST_ORBIT_SQUARE,GHOST_ORBIT_HEXAGON,GHOST_ORBIT_PENTAGON))
|
||||
|
||||
/client/proc/pick_ghost_orbit()
|
||||
if(!is_content_unlocked())
|
||||
tgui_alert(usr,"This setting is for accounts with BYOND premium only.")
|
||||
return
|
||||
var/new_orbit = input(src, "Thanks for supporting BYOND - Choose your ghostly orbit:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_orbits
|
||||
if(new_orbit)
|
||||
prefs.ghost_orbit = new_orbit
|
||||
prefs.save_preferences()
|
||||
if(isobserver(mob))
|
||||
var/mob/dead/observer/O = mob
|
||||
O.ghost_orbit = new_orbit
|
||||
|
||||
/client/proc/pick_ghost_accs()
|
||||
var/new_ghost_accs = tgui_alert(usr,"Do you want your ghost to show full accessories where possible, hide accessories but still use the directional sprites where possible, or also ignore the directions and stick to the default sprites?",,list("full accessories", "only directional sprites", "default sprites"))
|
||||
if(new_ghost_accs)
|
||||
switch(new_ghost_accs)
|
||||
if("full accessories")
|
||||
prefs.ghost_accs = GHOST_ACCS_FULL
|
||||
if("only directional sprites")
|
||||
prefs.ghost_accs = GHOST_ACCS_DIR
|
||||
if("default sprites")
|
||||
prefs.ghost_accs = GHOST_ACCS_NONE
|
||||
prefs.save_preferences()
|
||||
if(isobserver(mob))
|
||||
var/mob/dead/observer/O = mob
|
||||
O.update_appearance()
|
||||
|
||||
/client/verb/pick_ghost_customization()
|
||||
set name = "Ghost Customization"
|
||||
set category = "Preferences"
|
||||
set desc = "Customize your ghastly appearance."
|
||||
if(is_content_unlocked())
|
||||
switch(tgui_alert(usr,"Which setting do you want to change?",,list("Ghost Form","Ghost Orbit","Ghost Accessories")))
|
||||
if("Ghost Form")
|
||||
pick_form()
|
||||
if("Ghost Orbit")
|
||||
pick_ghost_orbit()
|
||||
if("Ghost Accessories")
|
||||
pick_ghost_accs()
|
||||
else
|
||||
pick_ghost_accs()
|
||||
|
||||
/client/verb/pick_ghost_others()
|
||||
set name = "Ghosts of Others"
|
||||
set category = "Preferences"
|
||||
set desc = "Change display settings for the ghosts of other players."
|
||||
var/new_ghost_others = tgui_alert(usr,"Do you want the ghosts of others to show up as their own setting, as their default sprites or always as the default white ghost?",,list("Their Setting", "Default Sprites", "White Ghost"))
|
||||
if(new_ghost_others)
|
||||
switch(new_ghost_others)
|
||||
if("Their Setting")
|
||||
prefs.ghost_others = GHOST_OTHERS_THEIR_SETTING
|
||||
if("Default Sprites")
|
||||
prefs.ghost_others = GHOST_OTHERS_DEFAULT_SPRITE
|
||||
if("White Ghost")
|
||||
prefs.ghost_others = GHOST_OTHERS_SIMPLE
|
||||
prefs.save_preferences()
|
||||
if(isobserver(mob))
|
||||
var/mob/dead/observer/O = mob
|
||||
O.update_sight()
|
||||
|
||||
/client/verb/toggle_ghost_hud_pref()
|
||||
set name = "Toggle Ghost HUD"
|
||||
set category = "Preferences"
|
||||
set desc = "Hide/Show Ghost HUD"
|
||||
|
||||
prefs.ghost_hud = !prefs.ghost_hud
|
||||
to_chat(src, "<span class='infoplain'>Ghost HUD will now be [prefs.ghost_hud ? "visible" : "hidden"].</span>")
|
||||
prefs.save_preferences()
|
||||
if(isobserver(mob))
|
||||
mob.hud_used.show_hud()
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost HUD", "[prefs.ghost_hud ? "Enabled" : "Disabled"]"))
|
||||
|
||||
/client/verb/toggle_inquisition() // warning: unexpected inquisition
|
||||
set name = "Toggle Inquisitiveness"
|
||||
set desc = "Sets whether your ghost examines everything on click by default"
|
||||
set category = "Preferences"
|
||||
|
||||
prefs.inquisitive_ghost = !prefs.inquisitive_ghost
|
||||
prefs.save_preferences()
|
||||
if(prefs.inquisitive_ghost)
|
||||
to_chat(src, span_notice("You will now examine everything you click on."))
|
||||
else
|
||||
to_chat(src, span_notice("You will no longer examine things you click on."))
|
||||
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Inquisitiveness", "[prefs.inquisitive_ghost ? "Enabled" : "Disabled"]"))
|
||||
|
||||
//Admin Preferences
|
||||
/client/proc/toggleadminhelpsound()
|
||||
set name = "Hear/Silence Adminhelps"
|
||||
set category = "Preferences.Admin"
|
||||
set desc = "Toggle hearing a notification when admin PMs are received"
|
||||
if(!holder)
|
||||
return
|
||||
prefs.toggles ^= SOUND_ADMINHELP
|
||||
prefs.save_preferences()
|
||||
to_chat(usr, "<span class='infoplain'>You will [(prefs.toggles & SOUND_ADMINHELP) ? "now" : "no longer"] hear a sound when adminhelps arrive.</span>")
|
||||
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Adminhelp Sound", "[prefs.toggles & SOUND_ADMINHELP ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/toggleannouncelogin()
|
||||
set name = "Do/Don't Announce Login"
|
||||
set category = "Preferences.Admin"
|
||||
set desc = "Toggle if you want an announcement to admins when you login during a round"
|
||||
if(!holder)
|
||||
return
|
||||
prefs.toggles ^= ANNOUNCE_LOGIN
|
||||
prefs.save_preferences()
|
||||
to_chat(usr, "<span class='infoplain'>You will [(prefs.toggles & ANNOUNCE_LOGIN) ? "now" : "no longer"] have an announcement to other admins when you login.</span>")
|
||||
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Login Announcement", "[prefs.toggles & ANNOUNCE_LOGIN ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/toggle_hear_radio()
|
||||
set name = "Show/Hide Radio Chatter"
|
||||
set category = "Preferences.Admin"
|
||||
set desc = "Toggle seeing radiochatter from nearby radios and speakers"
|
||||
if(!holder)
|
||||
return
|
||||
prefs.chat_toggles ^= CHAT_RADIO
|
||||
prefs.save_preferences()
|
||||
to_chat(usr, "You will [(prefs.chat_toggles & CHAT_RADIO) ? "now" : "no longer"] see radio chatter from nearby radios or speakers")
|
||||
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Radio Chatter", "[prefs.chat_toggles & CHAT_RADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/toggle_split_admin_tabs()
|
||||
set name = "Toggle Split Admin Tabs"
|
||||
set category = "Preferences.Admin"
|
||||
set desc = "Toggle the admin tab being split into separate tabs instead of being merged into one"
|
||||
if(!holder)
|
||||
return
|
||||
prefs.toggles ^= SPLIT_ADMIN_TABS
|
||||
prefs.save_preferences()
|
||||
to_chat(src, "<span class='infoplain'>Admin tabs will now [(prefs.toggles & SPLIT_ADMIN_TABS) ? "be" : "not be"] split.</span>")
|
||||
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Split Admin Tabs", "[prefs.toggles & SPLIT_ADMIN_TABS ? "Enabled" : "Disabled"]"))
|
||||
|
||||
/client/proc/deadchat()
|
||||
set name = "Show/Hide Deadchat"
|
||||
set category = "Preferences.Admin"
|
||||
set desc ="Toggles seeing deadchat"
|
||||
if(!holder)
|
||||
return
|
||||
prefs.chat_toggles ^= CHAT_DEAD
|
||||
prefs.save_preferences()
|
||||
to_chat(src, "<span class='infoplain'>You will [(prefs.chat_toggles & CHAT_DEAD) ? "now" : "no longer"] see deadchat.</span>")
|
||||
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Deadchat Visibility", "[prefs.chat_toggles & CHAT_DEAD ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/toggleprayers()
|
||||
set name = "Show/Hide Prayers"
|
||||
set category = "Preferences.Admin"
|
||||
set desc = "Toggles seeing prayers"
|
||||
if(!holder)
|
||||
return
|
||||
prefs.chat_toggles ^= CHAT_PRAYER
|
||||
prefs.save_preferences()
|
||||
to_chat(src, "<span class='infoplain'>You will [(prefs.chat_toggles & CHAT_PRAYER) ? "now" : "no longer"] see prayerchat.</span>")
|
||||
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Visibility", "[prefs.chat_toggles & CHAT_PRAYER ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/toggle_prayer_sound()
|
||||
set name = "Hear/Silence Prayer Sounds"
|
||||
set category = "Preferences.Admin"
|
||||
set desc = "Hear Prayer Sounds"
|
||||
if(!holder)
|
||||
return
|
||||
prefs.toggles ^= SOUND_PRAYERS
|
||||
prefs.save_preferences()
|
||||
to_chat(usr, "<span class='infoplain'>You will [(prefs.toggles & SOUND_PRAYERS) ? "now" : "no longer"] hear a sound when prayers arrive.</span>")
|
||||
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Sounds", "[usr.client.prefs.toggles & SOUND_PRAYERS ? "Enabled" : "Disabled"]"))
|
||||
|
||||
/client/proc/colorasay()
|
||||
set name = "Set Admin Say Color"
|
||||
set category = "Preferences.Admin"
|
||||
set desc = "Set the color of your ASAY messages"
|
||||
if(!holder)
|
||||
return
|
||||
if(!CONFIG_GET(flag/allow_admin_asaycolor))
|
||||
to_chat(src, "Custom Asay color is currently disabled by the server.")
|
||||
return
|
||||
var/new_asaycolor = input(src, "Please select your ASAY color.", "ASAY color", prefs.asaycolor) as color|null
|
||||
if(new_asaycolor)
|
||||
prefs.asaycolor = sanitize_ooccolor(new_asaycolor)
|
||||
prefs.save_preferences()
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Set ASAY Color")
|
||||
return
|
||||
|
||||
/client/proc/resetasaycolor()
|
||||
set name = "Reset your Admin Say Color"
|
||||
set desc = "Returns your ASAY Color to default"
|
||||
set category = "Preferences.Admin"
|
||||
if(!holder)
|
||||
return
|
||||
if(!CONFIG_GET(flag/allow_admin_asaycolor))
|
||||
to_chat(src, "<span class='infoplain'>Custom Asay color is currently disabled by the server.</span>")
|
||||
return
|
||||
prefs.asaycolor = initial(prefs.asaycolor)
|
||||
prefs.save_preferences()
|
||||
@@ -1,20 +0,0 @@
|
||||
/client/verb/toggle_tips()
|
||||
set name = "Toggle Examine Tooltips"
|
||||
set desc = "Toggles examine hover-over tooltips"
|
||||
set category = "Preferences"
|
||||
|
||||
prefs.enable_tips = !prefs.enable_tips
|
||||
prefs.save_preferences()
|
||||
to_chat(usr, span_danger("Examine tooltips [prefs.enable_tips ? "en" : "dis"]abled."))
|
||||
|
||||
/client/verb/change_tip_delay()
|
||||
set name = "Set Examine Tooltip Delay"
|
||||
set desc = "Sets the delay in milliseconds before examine tooltips appear"
|
||||
set category = "Preferences"
|
||||
|
||||
var/indelay = stripped_input(usr, "Enter the tooltip delay in milliseconds (default: 500)", "Enter tooltip delay", "", 10)
|
||||
indelay = text2num(indelay)
|
||||
if(usr)//is this what you mean?
|
||||
prefs.tip_delay = indelay
|
||||
prefs.save_preferences()
|
||||
to_chat(usr, span_danger("Tooltip delay set to [indelay] milliseconds."))
|
||||
@@ -58,7 +58,7 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
|
||||
var/keyname = key
|
||||
if(prefs.unlock_content)
|
||||
if(prefs.toggles & MEMBER_PUBLIC)
|
||||
keyname = "<font color='[prefs.ooccolor ? prefs.ooccolor : GLOB.normal_ooc_colour]'>[icon2html('icons/member_content.dmi', world, "blag")][keyname]</font>"
|
||||
keyname = "<font color='[prefs.read_preference(/datum/preference/color/ooc_color) || GLOB.normal_ooc_colour]'>[icon2html('icons/member_content.dmi', world, "blag")][keyname]</font>"
|
||||
if(prefs.hearted)
|
||||
var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat)
|
||||
keyname = "[sheet.icon_tag("emoji-heart")][keyname]"
|
||||
@@ -74,7 +74,8 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
|
||||
if(holder)
|
||||
if(!holder.fakekey || receiver.holder)
|
||||
if(check_rights_for(src, R_ADMIN))
|
||||
to_chat(receiver, span_adminooc("[CONFIG_GET(flag/allow_admin_ooccolor) && prefs.ooccolor ? "<font color=[prefs.ooccolor]>" :"" ][span_prefix("OOC:")] <EM>[keyname][holder.fakekey ? "/([holder.fakekey])" : ""]:</EM> <span class='message linkify'>[msg]</span>"), avoid_highlighting = avoid_highlight)
|
||||
var/ooc_color = prefs.read_preference(/datum/preference/color/ooc_color)
|
||||
to_chat(receiver, span_adminooc("[CONFIG_GET(flag/allow_admin_ooccolor) && ooc_color ? "<font color=[ooc_color]>" :"" ][span_prefix("OOC:")] <EM>[keyname][holder.fakekey ? "/([holder.fakekey])" : ""]:</EM> <span class='message linkify'>[msg]</span>"), avoid_highlighting = avoid_highlight)
|
||||
else
|
||||
to_chat(receiver, span_adminobserverooc(span_prefix("OOC:</span> <EM>[keyname][holder.fakekey ? "/([holder.fakekey])" : ""]:</EM> <span class='message linkify'>[msg]")), avoid_highlighting = avoid_highlight)
|
||||
else
|
||||
@@ -123,7 +124,7 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
|
||||
message_admins("[usr.key] has attempted to use the Set Player OOC Color verb!")
|
||||
log_admin("[key_name(usr)] tried to set player ooc color without authorization.")
|
||||
return
|
||||
var/new_color = sanitize_ooccolor(newColor)
|
||||
var/new_color = sanitize_color(newColor)
|
||||
message_admins("[key_name_admin(usr)] has set the players' ooc color to [new_color].")
|
||||
log_admin("[key_name_admin(usr)] has set the player ooc color to [new_color].")
|
||||
GLOB.OOC_COLOR = new_color
|
||||
@@ -145,36 +146,6 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
|
||||
log_admin("[key_name_admin(usr)] has reset player ooc color.")
|
||||
GLOB.OOC_COLOR = null
|
||||
|
||||
|
||||
/client/verb/colorooc()
|
||||
set name = "Set Your OOC Color"
|
||||
set category = "Preferences"
|
||||
|
||||
if(!holder || !check_rights_for(src, R_ADMIN))
|
||||
if(!is_content_unlocked())
|
||||
return
|
||||
|
||||
var/new_ooccolor = input(src, "Please select your OOC color.", "OOC color", prefs.ooccolor) as color|null
|
||||
if(isnull(new_ooccolor))
|
||||
return
|
||||
new_ooccolor = sanitize_ooccolor(new_ooccolor)
|
||||
prefs.ooccolor = new_ooccolor
|
||||
prefs.save_preferences()
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Set OOC Color") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
|
||||
/client/verb/resetcolorooc()
|
||||
set name = "Reset Your OOC Color"
|
||||
set desc = "Returns your OOC Color to default"
|
||||
set category = "Preferences"
|
||||
|
||||
if(!holder || !check_rights_for(src, R_ADMIN))
|
||||
if(!is_content_unlocked())
|
||||
return
|
||||
|
||||
prefs.ooccolor = initial(prefs.ooccolor)
|
||||
prefs.save_preferences()
|
||||
|
||||
//Checks admin notice
|
||||
/client/verb/admin_notice()
|
||||
set name = "Adminnotice"
|
||||
|
||||
Reference in New Issue
Block a user