[MIRROR] tgui Preferences Menu + total rewrite of the preferences backend (#8153)

* tgui Preferences Menu + total rewrite of the preferences backend

* nah, we dont need to ping those people

* trying to remove the funny stuff

* unmodularizing this

* prefs reset

* this may need to be reverted, who knows

* okay, this part

* perhaps

* EEEEEEEEE

* unsanitary

* E

* Stage 1 + loadout system

* more fixes

* E

* I mean, it launches?

* More fixes and reorganisation

* E

* customisation code is spaget.

* disable ERP prefs

* Update erp_preferences.dm

* Update erp_preferences.dm

* E

* Slowly getting there

* It may be time for help :)

* tri...colors... help

* preferences now pass preferences

* Update dna.dm

* Fuck this man

* missing savefile return, set_species works, removed dumb stuff from updateappearance

* https://github.com/Skyrat-SS13/Skyrat-tg/pull/8199

* https://github.com/Skyrat-SS13/Skyrat-tg/pull/8224

* https://github.com/tgstation/tgstation/pull/61519

* https://github.com/Skyrat-SS13/Skyrat-tg/pull/8278

* e

* le butonAZARAK HELLO

* hhh

* Proper recognition where it's due, MrMelbert!

* EEEE

* examine block

* Better gen hit sounds from whitedream

* final loadout touches, more bug fixes im sure to come

* i said there would be bugfixes

* Update LoadoutManager.js

* Missing preferences in the html menu

* LIVE TESTING PHASE BABY

* Update LoadoutManager.js

* EEE

* LAUNCH TEST FIRE

* Update job.dm

* Update new_player.dm

* 50gb DAY ONE PATCH

* EEE

* Update preferences.dm

* buggle fixes

* Update examine.dm

* >LOOC starts on

Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com>
Co-authored-by: jjpark-kb <55967837+jjpark-kb@users.noreply.github.com>
Co-authored-by: Gandalf <jzo123@hotmail.com>
Co-authored-by: Azarak <azarak10@gmail.com>
This commit is contained in:
SkyratBot
2021-09-23 01:40:37 +02:00
committed by GitHub
parent a5bfc42228
commit 124ddd7cca
582 changed files with 19548 additions and 8366 deletions
+3
View File
@@ -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
+17 -49
View File
@@ -110,13 +110,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)
@@ -249,12 +242,12 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
prefs = GLOB.preferences_datums[ckey]
if(prefs)
prefs.parent = src
prefs.apply_all_client_preferences()
else
prefs = new /datum/preferences(src)
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)
@@ -413,7 +406,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()
@@ -472,7 +465,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()
@@ -490,7 +483,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)
@@ -534,6 +527,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
QDEL_NULL(view_size)
QDEL_NULL(void)
QDEL_NULL(tooltips)
QDEL_NULL(open_loadout_ui) //SKYRAT EDIT ADDITION
seen_messages = null
Master.UpdateTickRate()
..() //Even though we're going to be hard deleted there are still some things that want to know the destroy is happening
@@ -918,7 +912,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.
@@ -1010,8 +1004,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
@@ -1045,7 +1039,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()
@@ -1062,35 +1056,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)
@@ -1158,11 +1123,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)
@@ -1215,3 +1175,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"))
File diff suppressed because it is too large Load Diff
+476
View File
@@ -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: FeatureToggle = {
name: "Enable breathing",
component: CheckboxInput,
}
```
## Choiced preferences
A choiced preference is one where the only options are in a distinct few amount of choices. Examples include skin tone, shirt, and UI style.
To create one, derive from `/datum/preference/choiced`.
```dm
/datum/preference/choiced/favorite_drink
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
savefile_identifier = PREFERENCE_CHARACTER
savefile_key = "favorite_drink"
```
Now we need to tell the game what the choices are. We do this by overriding `init_possible_values()`. This will return a list of possible options.
```dm
/datum/preference/choiced/favorite_drink/init_possible_values()
return list(
"Milk",
"Cola",
"Water",
)
```
Your `.tsx` file would then look like:
```tsx
import { FeatureChoiced, FeatureDropdownInput } from "../base";
export const favorite_drink: FeatureChoiced = {
name: "Favorite drink",
component: FeatureDropdownInput,
};
```
This will create a dropdown input for your preference.
### Choiced preferences - Icons
Choiced preferences can generate icons. This is how the clothing/species preferences work, for instance. However, if we just want a basic dropdown input with icons, it would look like this:
```dm
/datum/preference/choiced/favorite_drink
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
savefile_identifier = PREFERENCE_CHARACTER
savefile_key = "favorite_drink"
should_generate_icons = TRUE // NEW! This is necessary.
// 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"
```
![Preference categories for the main page](https://raw.githubusercontent.com/tgstation/documentation-assets/main/preferences/preference_categories.png)
> SECONDARY_FEATURES or NON_CONTEXTUAL?
Secondary features tend to be species specific. Non contextual features shouldn't change much from character to character.
## Default values and randomization
There are three procs to be aware of in regards to this topic:
- `create_default_value()`. This is used when a value deserializes improperly or when a new character is created.
- `create_informed_default_value(datum/preferences/preferences)` - Used for more complicated default values, like how names require the gender. Will call `create_default_value()` by default.
- `create_random_value(datum/preferences/preferences)` - Explicitly used for random values, such as when a character is being randomized.
`create_default_value()` in most preferences will create a random value. If this is a problem (like how default characters should always be human), you can override `create_default_value()`. By default (without overriding `create_random_value`), random values are just default values.
## Advanced - Server data
As previewed in [the display names implementation](#Choiced-preferences---Display-names), there exists a `compile_constant_data()` proc you can override.
Compiled data is used wherever the server needs to give the client some value it can't figure out on its own. Skin tones use this to tell the client what colors they represent, for example.
Compiled data is sent to the `serverData` field in the `FeatureValueProps`.
## Advanced - Creating your own tgui component
If you have good knowledge with tgui (especially TypeScript), you'll be able to create your own component to represent preferences.
The `component` field in a feature accepts __any__ component that accepts `FeatureValueProps<TReceiving, TSending = TReceiving, TServerData = undefined>`.
This will give you the fields:
```ts
act: typeof sendAct,
featureId: string,
handleSetValue: (newValue: TSending) => void,
serverData: TServerData | undefined,
shrink?: boolean,
value: TReceiving,
```
`act` is the same as the one you get from `useBackend`.
`featureId` is the savefile_key of the feature.
`handleSetValue` is a function that, when called, will tell the server the new value, as well as changing the value immediately locally.
`serverData` is the [server data](#Advanced---Server-data), if it has been fetched yet (and exists).
`shrink` is whether or not the UI should appear smaller. This is only used for supplementary features.
`value` is the current value, could be predicted (meaning that the value was changed locally, but has not yet reached the server).
For a basic example of how this can look, observe `CheckboxInput`:
```tsx
export const CheckboxInput = (
props: FeatureValueProps<BooleanLike, boolean>
) => {
return (<Button.Checkbox
checked={!!props.value}
onClick={() => {
props.handleSetValue(!props.value);
}}
/>);
};
```
## Advanced - Middleware
A `/datum/preference_middleware` is a way to inject your own data at specific points, as well as hijack actions.
Middleware can hijack actions by specifying `action_delegations`:
```dm
/datum/preference_middleware/congratulations
action_delegations = list(
"congratulate_me" = .proc/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:
![Changeling icon](https://raw.githubusercontent.com/tgstation/documentation-assets/main/preferences/changeling.png)
## Creating the tgui representation
In the `.ts` file you created earlier, you must now give the information of your antagonist. For reference, this is the changeling's:
```ts
import { Antagonist, Category } from "../base";
import { multiline } from "common/string";
const Changeling: Antagonist = {
key: "changeling", // This must be the same as your filename
name: "Changeling",
description: [
multiline`
A highly intelligent alien predator that is capable of altering their
shape to flawlessly resemble a human.
`,
multiline`
Transform yourself or others into different identities, and buy from an
arsenal of biological weaponry with the DNA you collect.
`,
],
category: Category.Roundstart, // Category.Roundstart, Category.Midround, or Category.Latejoin
};
export default Changeling;
```
## Readying the Dynamic ruleset
You already need to create a Dynamic ruleset, so in order to get your antagonist recognized, you just need to specify `antag_flag`. This must be unique per ruleset.
Two other values to note are `antag_flag_override` and `antag_preference`.
`antag_flag_override` exists for cases where you want the banned antagonist to be separate from `antag_flag`. As an example: roundstart, midround, and latejoin traitors have separate `antag_flag`, but all have `antag_flag_override = ROLE_TRAITOR`. This is because admins want to ban a player from Traitor altogether, not specific rulesets.
If `antag_preference` is set, it will refer to that preference instead of `antag_flag`. This is used for clown operatives, which we want to be on the same preference as standard nuke ops, but must specify a unique `antag_flag` for.
## Updating special_roles
In `code/__DEFINES/role_preferences.dm` (the same place you'll need to make your ROLE_\* defined), simply add your antagonist to the `special_roles` assoc list. The key is your ROLE, the value is the number of days since your first game in order to play as that antagonist.
@@ -0,0 +1,545 @@
// 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, datum/preferences/preferecnes) //SKYRAT EDIT CHANGE
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)
// Both of these will cache savefiles, but only for a tick.
// This is because storing a savefile will lock it, causing later issues down the line.
// Do not change them to addtimer, since the timer SS might not be running at this time.
if (!parent)
return null
switch (savefile_identifier)
if (PREFERENCE_CHARACTER)
if (!character_savefile)
character_savefile = new /savefile(path)
character_savefile.cd = "/character[default_slot]"
spawn (1)
character_savefile = null
return character_savefile
if (PREFERENCE_PLAYER)
if (!game_savefile)
game_savefile = new /savefile(path)
game_savefile.cd = "/"
spawn (1)
game_savefile = null
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
+41
View File
@@ -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)
+10
View File
@@ -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)
+66
View File
@@ -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
+153
View File
@@ -0,0 +1,153 @@
/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(accessory.icon, accessory.icon_state) //SKYRAT EDIT CHANGE
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
/* SKYRAT EDIT TODO - FOR WHATEVER REASON, THIS IS BROKEN
/// 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
+17
View File
@@ -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
+11
View File
@@ -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
+178
View File
@@ -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,43 @@
/// Handles the assets for species icons
/datum/preference_middleware/species
//SKYRAT EDIT ADDITION
/datum/preference_middleware/species/pre_set_preference(mob/user, preference, value)
var/datum/species/species_type = GLOB.species_list[value]
preferences.species_updated(species_type)
return ..()
//SKYRAT EDIT END
/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 ..()
+156
View File
@@ -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
+14
View File
@@ -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)
+17
View File
@@ -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
+16
View File
@@ -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)
+53
View File
@@ -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,75 @@
/// 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, datum/preferences/prefs)
target.set_species(value, FALSE, prefs?.features.Copy(), prefs?.mutant_bodyparts.Copy(), prefs?.body_markings.Copy())
//SKYRAT EDIT ADDITION
target.dna.update_body_size()
for(var/organ_key in list(ORGAN_SLOT_VAGINA, ORGAN_SLOT_PENIS, ORGAN_SLOT_BREASTS))
var/obj/item/organ/genital/gent = target.getorganslot(organ_key)
if(gent)
gent.aroused = prefs.arousal_preview
gent.update_sprite_suffix()
if(length(prefs.augments))
for(var/key in prefs.augments)
var/datum/augment_item/aug = GLOB.augment_items[prefs.augments[key]]
aug.apply(target, prefs = prefs)
//SKYRAT EDIT END
/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,35 @@
/* SKYRAT EDIT REMOVAL
/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.sprite_accessories["tail"])
/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 GLOB.sprite_accessories["ears"]
/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,163 @@
/*
/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.sprite_accessories["body_markings"]) //SKYRAT EDIT
var/datum/sprite_accessory/sprite_accessory = GLOB.sprite_accessories["body_markings"][name] //SKYRAT EDIT
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)
if(!target.dna.mutant_bodyparts["body_markings"])
target.dna.mutant_bodyparts["body_markings"] = list()
target.dna.mutant_bodyparts["body_markings"][MUTANT_INDEX_NAME] = value
target.dna.mutant_bodyparts["body_markings"][MUTANT_INDEX_COLOR_LIST] = list("333", "222", "444")
/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.sprite_accessories["frills"], "frills") //SKYRAT EDIT
/datum/preference/choiced/lizard_frills/apply_to_human(mob/living/carbon/human/target, value)
if(!target.dna.species.mutant_bodyparts["frills"])
target.dna.species.mutant_bodyparts["frills"] = list()
target.dna.species.mutant_bodyparts["frills"][MUTANT_INDEX_NAME] = value
target.dna.species.mutant_bodyparts["frills"][MUTANT_INDEX_COLOR_LIST] = list("333", "222", "444")
/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.sprite_accessories["horns"], "horns") //SKYRAT EDIT
/datum/preference/choiced/lizard_horns/apply_to_human(mob/living/carbon/human/target, value)
if(!target.dna.species.mutant_bodyparts["horns"])
target.dna.species.mutant_bodyparts["horns"] = list()
target.dna.species.mutant_bodyparts["horns"][MUTANT_INDEX_NAME] = value
target.dna.species.mutant_bodyparts["horns"][MUTANT_INDEX_COLOR_LIST] = list("333", "222", "444")
/* SKYRAT EDIT REMOVAL
/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 GLOB.sprite_accessories["legs"] //SKYRAT EDIT CHANGE
/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_lizrd_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.sprite_accessories["snout"], "snout", include_snout = FALSE) //SKYRAT EDIT
/datum/preference/choiced/lizard_snout/apply_to_human(mob/living/carbon/human/target, value)
if(!target.dna.species.mutant_bodyparts["snout"])
target.dna.species.mutant_bodyparts["snout"] = list()
target.dna.species.mutant_bodyparts["snout"][MUTANT_INDEX_NAME] = value
target.dna.species.mutant_bodyparts["snout"][MUTANT_INDEX_COLOR_LIST] = list("333", "222", "444")
/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 GLOB.sprite_accessories["spines"] //SKYRAT EDIT
/datum/preference/choiced/lizard_spines/apply_to_human(mob/living/carbon/human/target, value)
if(!target.dna.species.mutant_bodyparts["spines"])
target.dna.species.mutant_bodyparts["spines"] = list()
target.dna.species.mutant_bodyparts["spines"][MUTANT_INDEX_NAME] = value
target.dna.species.mutant_bodyparts["spines"][MUTANT_INDEX_COLOR_LIST] = list("333", "222", "444")
/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 GLOB.sprite_accessories["tail"] //SKYRAT EDIT
/datum/preference/choiced/lizard_tail/apply_to_human(mob/living/carbon/human/target, value)
if(!target.dna.species.mutant_bodyparts["tail"])
target.dna.species.mutant_bodyparts["tail"] = list()
target.dna.species.mutant_bodyparts["tail"][MUTANT_INDEX_NAME] = value
target.dna.species.mutant_bodyparts["tail"][MUTANT_INDEX_COLOR_LIST] = list("333", "222", "444")
*/
@@ -0,0 +1,101 @@
/* SKYRAT EDIT REMOVAL
/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.sprite_accessories["moth_antennae"]) //SKYRAT EDIT
var/datum/sprite_accessory/antennae = GLOB.sprite_accessories["moth_antennae"][antennae_name] //SKYRAT EDIT
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.sprite_accessories["moth_markings"]) //SKYRAT EDIT
var/datum/sprite_accessory/markings = GLOB.sprite_accessories["moth_markings"][markings_name] //SKYRAT EDIT
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.sprite_accessories["moth_wings"]) //SKYRAT EDIT
// 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,22 @@
/* SKYRAT EDIT REMOVAL
/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
*/
+20
View File
@@ -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
+26
View File
@@ -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)
+80 -296
View File
@@ -1,13 +1,11 @@
//This is the lowest supported version, anything below this is completely obsolete and the entire savefile will be wiped.
//SKYRAT EDIT REMOVAL BEGIN - CUSTOMIZATION (moved to modular)
/*
#define SAVEFILE_VERSION_MIN 32
//This is the current version, anything below this will attempt to update (if it's not obsolete)
// 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
@@ -48,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
@@ -72,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
@@ -95,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
@@ -130,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")
@@ -163,54 +163,18 @@ 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)
apply_all_client_preferences()
//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)
@@ -228,7 +192,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)
@@ -238,47 +201,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
@@ -310,61 +242,39 @@ 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))
return FALSE
character_savefile = null
var/savefile/S = new /savefile(path)
if(!S)
return FALSE
@@ -381,75 +291,19 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
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)
@@ -462,59 +316,12 @@ 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))
loadout_list = sanitize_loadout_list(update_loadout_list(loadout_list)) //SKYRAT EDIT ADDITION
//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)
@@ -523,9 +330,13 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
all_quirks = SANITIZE_LIST(all_quirks)
validate_quirks()
load_character_skyrat(S) //SKYRAT EDIT ADDITION
return TRUE
/datum/preferences/proc/save_character()
SHOULD_NOT_SLEEP(TRUE)
if(!path)
return FALSE
var/savefile/S = new /savefile(path)
@@ -533,71 +344,46 @@ 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)
//Quirks
WRITE_FILE(S["all_quirks"] , all_quirks)
save_character_skyrat(S) //SKYRAT EDIT ADDITION
return TRUE
/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
@@ -616,5 +402,3 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S.ImportText("/",file("[path].txt"))
#endif
*/
//SKYRAT EDIT REMOVAL END
-20
View File
@@ -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."))
+4 -33
View File
@@ -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"