mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-18 11:36:24 +01:00
[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:
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
|
||||
+399
-1917
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,476 @@
|
||||
# Preferences (by Mothblocks)
|
||||
|
||||
This does not contain all the information on specific values--you can find those as doc-comments in relevant paths, such as `/datum/preference`. Rather, this gives you an overview for creating *most* preferences, and getting your foot in the door to create more advanced ones.
|
||||
|
||||
## Anatomy of a preference (A.K.A. how do I make one?)
|
||||
|
||||
Most preferences consist of two parts:
|
||||
|
||||
1. A `/datum/preference` type.
|
||||
2. A tgui representation in a TypeScript file.
|
||||
|
||||
Every `/datum/preference` requires these three values be set:
|
||||
1. `category` - See [Categories](#Categories).
|
||||
2. `savefile_key` - The value which will be saved in the savefile. This will also be the identifier for tgui.
|
||||
3. `savefile_identifier` - Whether or not this is a character specific preference (`PREFERENCE_CHARACTER`) or one that affects the player (`PREFERENCE_PLAYER`). As an example: hair color is `PREFERENCE_CHARACTER` while your UI settings are `PREFERENCE_PLAYER`, since they do not change between characters.
|
||||
|
||||
For the tgui representation, most preferences will create a `.tsx` file in `tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/`. If your preference is a character preference, make a new file in `character_preferences`. Otherwise, put it in `game_preferences`. The filename does not matter, and this file can hold multiple relevant preferences if you would like.
|
||||
|
||||
From here, you will want to write code resembling:
|
||||
|
||||
```ts
|
||||
import { Feature } from "../base";
|
||||
|
||||
export const savefile_key_here: Feature<T> = {
|
||||
name: "Preference Name Here",
|
||||
component: Component,
|
||||
|
||||
// Necessary for game preferences, unused for others
|
||||
category: "CATEGORY",
|
||||
|
||||
// Optional, only shown in game preferences
|
||||
description: "This preference will blow your mind!",
|
||||
}
|
||||
```
|
||||
|
||||
`T` and `Component` depend on the type of preference you're making. Here are all common examples...
|
||||
|
||||
## Numeric preferences
|
||||
|
||||
Examples include age and FPS.
|
||||
|
||||
A numeric preference derives from `/datum/preference/numeric`.
|
||||
|
||||
```dm
|
||||
/datum/preference/numeric/legs
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "legs"
|
||||
|
||||
minimum = 1
|
||||
maximum = 8
|
||||
```
|
||||
|
||||
You can optionally provide a `step` field. This value is 1 by default, meaning only integers are accepted.
|
||||
|
||||
Your `.tsx` file would look like:
|
||||
|
||||
```ts
|
||||
import { Feature, FeatureNumberInput } from "../base";
|
||||
|
||||
export const legs: Feature<number> = {
|
||||
name: "Legs",
|
||||
component: FeatureNumberInput,
|
||||
}
|
||||
```
|
||||
|
||||
## Toggle preferences
|
||||
|
||||
Examples include enabling tooltips.
|
||||
|
||||
```dm
|
||||
/datum/preference/toggle/enable_breathing
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "enable_breathing"
|
||||
|
||||
// Optional, TRUE by default
|
||||
default_value = FALSE
|
||||
```
|
||||
|
||||
Your `.tsx` file would look like:
|
||||
|
||||
```ts
|
||||
import { CheckboxInput, FeatureToggle } from "../base";
|
||||
|
||||
export const enable_breathing: 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"
|
||||
```
|
||||
|
||||

|
||||
|
||||
> SECONDARY_FEATURES or NON_CONTEXTUAL?
|
||||
|
||||
Secondary features tend to be species specific. Non contextual features shouldn't change much from character to character.
|
||||
|
||||
## Default values and randomization
|
||||
|
||||
There are three procs to be aware of in regards to this topic:
|
||||
|
||||
- `create_default_value()`. This is used when a value deserializes improperly or when a new character is created.
|
||||
- `create_informed_default_value(datum/preferences/preferences)` - Used for more complicated default values, like how names require the gender. Will call `create_default_value()` by default.
|
||||
- `create_random_value(datum/preferences/preferences)` - Explicitly used for random values, such as when a character is being randomized.
|
||||
|
||||
`create_default_value()` in most preferences will create a random value. If this is a problem (like how default characters should always be human), you can override `create_default_value()`. By default (without overriding `create_random_value`), random values are just default values.
|
||||
|
||||
## Advanced - Server data
|
||||
|
||||
As previewed in [the display names implementation](#Choiced-preferences---Display-names), there exists a `compile_constant_data()` proc you can override.
|
||||
|
||||
Compiled data is used wherever the server needs to give the client some value it can't figure out on its own. Skin tones use this to tell the client what colors they represent, for example.
|
||||
|
||||
Compiled data is sent to the `serverData` field in the `FeatureValueProps`.
|
||||
|
||||
## Advanced - Creating your own tgui component
|
||||
|
||||
If you have good knowledge with tgui (especially TypeScript), you'll be able to create your own component to represent preferences.
|
||||
|
||||
The `component` field in a feature accepts __any__ component that accepts `FeatureValueProps<TReceiving, TSending = TReceiving, TServerData = undefined>`.
|
||||
|
||||
This will give you the fields:
|
||||
|
||||
```ts
|
||||
act: typeof sendAct,
|
||||
featureId: string,
|
||||
handleSetValue: (newValue: TSending) => void,
|
||||
serverData: TServerData | undefined,
|
||||
shrink?: boolean,
|
||||
value: TReceiving,
|
||||
```
|
||||
|
||||
`act` is the same as the one you get from `useBackend`.
|
||||
|
||||
`featureId` is the savefile_key of the feature.
|
||||
|
||||
`handleSetValue` is a function that, when called, will tell the server the new value, as well as changing the value immediately locally.
|
||||
|
||||
`serverData` is the [server data](#Advanced---Server-data), if it has been fetched yet (and exists).
|
||||
|
||||
`shrink` is whether or not the UI should appear smaller. This is only used for supplementary features.
|
||||
|
||||
`value` is the current value, could be predicted (meaning that the value was changed locally, but has not yet reached the server).
|
||||
|
||||
For a basic example of how this can look, observe `CheckboxInput`:
|
||||
|
||||
```tsx
|
||||
export const CheckboxInput = (
|
||||
props: FeatureValueProps<BooleanLike, boolean>
|
||||
) => {
|
||||
return (<Button.Checkbox
|
||||
checked={!!props.value}
|
||||
onClick={() => {
|
||||
props.handleSetValue(!props.value);
|
||||
}}
|
||||
/>);
|
||||
};
|
||||
```
|
||||
|
||||
## Advanced - Middleware
|
||||
A `/datum/preference_middleware` is a way to inject your own data at specific points, as well as hijack actions.
|
||||
|
||||
Middleware can hijack actions by specifying `action_delegations`:
|
||||
|
||||
```dm
|
||||
/datum/preference_middleware/congratulations
|
||||
action_delegations = list(
|
||||
"congratulate_me" = .proc/congratulate_me,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/congratulations/proc/congratulate_me(list/params, mob/user)
|
||||
to_chat(user, span_notice("Wow, you did a great job learning about middleware!"))
|
||||
|
||||
return TRUE
|
||||
```
|
||||
|
||||
Middleware can inject its own data at several points, such as providing new UI assets, compiled data (used by middleware such as quirks to tell the client what quirks exist), etc. Look at `code/modules/client/preferences/middleware/_middleware.dm` for full information.
|
||||
|
||||
---
|
||||
|
||||
## Jobs
|
||||
|
||||
Every job must have an associated `.ts` file so that the UI knows where to place it.
|
||||
|
||||
Create a file in `tgui/packages/tgui/interfaces/PreferencesMenu/jobs/jobs/`.
|
||||
|
||||
This will specify the description and department of the job. Here is the details for the medical doctor:
|
||||
|
||||
```ts
|
||||
import { Job } from "../base";
|
||||
import { Service } from "../departments";
|
||||
|
||||
const Cook: Job = {
|
||||
name: "Cook",
|
||||
// If you need more room, use `multiline`
|
||||
description: "Serve food, cook meat, keep the crew fed.",
|
||||
department: Service,
|
||||
};
|
||||
|
||||
export default Cook;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Antagonists
|
||||
|
||||
In order to make an antagonist selectable, you must do a few things:
|
||||
|
||||
1. Your antagonist needs an icon.
|
||||
2. Your antagonist must be in a Dynamic ruleset. The ruleset must specify the antagonist as its `antag_flag`.
|
||||
3. Your antagonist needs a file in `tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/filename.ts`. This file name MUST be the `antag_flag` of your ruleset, with nothing but letters remaining (e.g. "Nuclear Operative" -> `nuclearoperative`).
|
||||
4. Add it to `special_roles`.
|
||||
|
||||
## Creating icons
|
||||
|
||||
If you are satisfied with your icon just being a dude with some clothes, then you can specify `preview_outfit` in your `/datum/antagonist`.
|
||||
|
||||
Space Ninja, for example, looks like:
|
||||
|
||||
```dm
|
||||
/datum/antagonist/ninja
|
||||
preview_outift = /datum/outfit/ninja
|
||||
```
|
||||
|
||||
However, if you want to get creative, you can override `/get_preview_icon()`. This proc should return an icon of size `ANTAGONIST_PREVIEW_ICON_SIZE`x`ANTAGONIST_PREVIEW_ICON_SIZE`.
|
||||
|
||||
There are some helper procs you can use as well. `render_preview_outfit(outfit_type)` will take an outfit and give you an icon of someone wearing those clothes. `finish_preview_outfit` will, given an icon, resize it appropriately and zoom in on the head. Note that this will look bad on anything that isn't a human, so if you have a non-human antagonist (such as sentient disease), just run `icon.Scale(ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE)`.
|
||||
|
||||
For inspiration, here is changeling's:
|
||||
|
||||
```dm
|
||||
/datum/antagonist/changeling/get_preview_icon()
|
||||
var/icon/final_icon = render_preview_outfit(/datum/outfit/changeling)
|
||||
var/icon/split_icon = render_preview_outfit(/datum/outfit/job/engineer)
|
||||
|
||||
final_icon.Shift(WEST, world.icon_size / 2)
|
||||
final_icon.Shift(EAST, world.icon_size / 2)
|
||||
|
||||
split_icon.Shift(EAST, world.icon_size / 2)
|
||||
split_icon.Shift(WEST, world.icon_size / 2)
|
||||
|
||||
final_icon.Blend(split_icon, ICON_OVERLAY)
|
||||
|
||||
return finish_preview_icon(final_icon)
|
||||
```
|
||||
|
||||
...which creates:
|
||||
|
||||

|
||||
|
||||
## Creating the tgui representation
|
||||
|
||||
In the `.ts` file you created earlier, you must now give the information of your antagonist. For reference, this is the changeling's:
|
||||
|
||||
```ts
|
||||
import { Antagonist, Category } from "../base";
|
||||
import { multiline } from "common/string";
|
||||
|
||||
const Changeling: Antagonist = {
|
||||
key: "changeling", // This must be the same as your filename
|
||||
name: "Changeling",
|
||||
description: [
|
||||
multiline`
|
||||
A highly intelligent alien predator that is capable of altering their
|
||||
shape to flawlessly resemble a human.
|
||||
`,
|
||||
|
||||
multiline`
|
||||
Transform yourself or others into different identities, and buy from an
|
||||
arsenal of biological weaponry with the DNA you collect.
|
||||
`,
|
||||
],
|
||||
category: Category.Roundstart, // Category.Roundstart, Category.Midround, or Category.Latejoin
|
||||
};
|
||||
|
||||
export default Changeling;
|
||||
```
|
||||
|
||||
## Readying the Dynamic ruleset
|
||||
|
||||
You already need to create a Dynamic ruleset, so in order to get your antagonist recognized, you just need to specify `antag_flag`. This must be unique per ruleset.
|
||||
|
||||
Two other values to note are `antag_flag_override` and `antag_preference`.
|
||||
|
||||
`antag_flag_override` exists for cases where you want the banned antagonist to be separate from `antag_flag`. As an example: roundstart, midround, and latejoin traitors have separate `antag_flag`, but all have `antag_flag_override = ROLE_TRAITOR`. This is because admins want to ban a player from Traitor altogether, not specific rulesets.
|
||||
|
||||
If `antag_preference` is set, it will refer to that preference instead of `antag_flag`. This is used for clown operatives, which we want to be on the same preference as standard nuke ops, but must specify a unique `antag_flag` for.
|
||||
|
||||
## Updating special_roles
|
||||
|
||||
In `code/__DEFINES/role_preferences.dm` (the same place you'll need to make your ROLE_\* defined), simply add your antagonist to the `special_roles` assoc list. The key is your ROLE, the value is the number of days since your first game in order to play as that antagonist.
|
||||
@@ -0,0 +1,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
|
||||
@@ -0,0 +1,41 @@
|
||||
/datum/preference/color/asay_color
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "asaycolor"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/color/asay_color/create_default_value()
|
||||
return DEFAULT_ASAY_COLOR
|
||||
|
||||
/datum/preference/color/asay_color/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return is_admin(preferences.parent) && CONFIG_GET(flag/allow_admin_asaycolor)
|
||||
|
||||
/// What outfit to equip when spawning as a briefing officer for an ERT
|
||||
/datum/preference/choiced/brief_outfit
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "brief_outfit"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/choiced/brief_outfit/deserialize(input, datum/preferences/preferences)
|
||||
var/path = text2path(input)
|
||||
if (!ispath(path, /datum/outfit))
|
||||
return create_default_value()
|
||||
|
||||
return path
|
||||
|
||||
/datum/preference/choiced/brief_outfit/serialize(input)
|
||||
return "[input]"
|
||||
|
||||
/datum/preference/choiced/brief_outfit/create_default_value()
|
||||
return /datum/outfit/centcom/commander
|
||||
|
||||
/datum/preference/choiced/brief_outfit/init_possible_values()
|
||||
return subtypesof(/datum/outfit)
|
||||
|
||||
/datum/preference/choiced/brief_outfit/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return is_admin(preferences.parent)
|
||||
@@ -0,0 +1,10 @@
|
||||
/datum/preference/numeric/age
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_key = "age"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
|
||||
minimum = AGE_MIN
|
||||
maximum = AGE_MAX
|
||||
|
||||
/datum/preference/numeric/age/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.age = value
|
||||
@@ -0,0 +1,25 @@
|
||||
/// What to show on the AI screen
|
||||
/datum/preference/choiced/ai_core_display
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "preferred_ai_core_display"
|
||||
should_generate_icons = TRUE
|
||||
|
||||
/datum/preference/choiced/ai_core_display/init_possible_values()
|
||||
var/list/values = list()
|
||||
|
||||
values["Random"] = icon('icons/mob/ai.dmi', "ai-empty")
|
||||
|
||||
for (var/screen in GLOB.ai_core_display_screens - "Portrait" - "Random")
|
||||
values[screen] = icon('icons/mob/ai.dmi', resolve_ai_icon_sync(screen))
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/choiced/ai_core_display/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return istype(preferences.get_highest_priority_job(), /datum/job/ai)
|
||||
|
||||
/datum/preference/choiced/ai_core_display/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
@@ -0,0 +1,12 @@
|
||||
/// Whether or not to toggle ambient occlusion, the shadows around people
|
||||
/datum/preference/toggle/ambient_occlusion
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "ambientocclusion"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/ambient_occlusion/apply_to_client(client/client, value)
|
||||
var/atom/movable/screen/plane_master/game_world/plane_master = locate() in client?.screen
|
||||
if (!plane_master)
|
||||
return
|
||||
|
||||
plane_master.backdrop(client.mob)
|
||||
@@ -0,0 +1,66 @@
|
||||
/// Assets generated from `/datum/preference` icons
|
||||
/datum/asset/spritesheet/preferences
|
||||
name = "preferences"
|
||||
early = TRUE
|
||||
|
||||
/datum/asset/spritesheet/preferences/register()
|
||||
var/list/to_insert = list()
|
||||
|
||||
for (var/preference_key in GLOB.preference_entries_by_key)
|
||||
var/datum/preference/choiced/preference = GLOB.preference_entries_by_key[preference_key]
|
||||
if (!istype(preference))
|
||||
continue
|
||||
|
||||
if (!preference.should_generate_icons)
|
||||
continue
|
||||
|
||||
var/list/choices = preference.get_choices_serialized()
|
||||
for (var/preference_value in choices)
|
||||
var/create_icon_of = choices[preference_value]
|
||||
|
||||
var/icon/icon
|
||||
var/icon_state
|
||||
|
||||
if (ispath(create_icon_of, /atom))
|
||||
var/atom/atom_icon_source = create_icon_of
|
||||
icon = initial(atom_icon_source.icon)
|
||||
icon_state = initial(atom_icon_source.icon_state)
|
||||
else if (isicon(create_icon_of))
|
||||
icon = create_icon_of
|
||||
else
|
||||
CRASH("[create_icon_of] is an invalid preference value (from [preference_key]:[preference_value]).")
|
||||
|
||||
to_insert[preference.get_spritesheet_key(preference_value)] = list(icon, icon_state)
|
||||
|
||||
for (var/spritesheet_key in to_insert)
|
||||
var/list/inserting = to_insert[spritesheet_key]
|
||||
Insert(spritesheet_key, inserting[1], inserting[2])
|
||||
|
||||
return ..()
|
||||
|
||||
/// Returns the key that will be used in the spritesheet for a given value.
|
||||
/datum/preference/proc/get_spritesheet_key(value)
|
||||
return "[savefile_key]___[sanitize_css_class_name(value)]"
|
||||
|
||||
/// Sends information needed for shared details on individual preferences
|
||||
/datum/asset/json/preferences
|
||||
name = "preferences"
|
||||
|
||||
/datum/asset/json/preferences/generate()
|
||||
var/list/preference_data = list()
|
||||
|
||||
for (var/middleware_type in subtypesof(/datum/preference_middleware))
|
||||
var/datum/preference_middleware/middleware = new middleware_type
|
||||
var/data = middleware.get_constant_data()
|
||||
if (!isnull(data))
|
||||
preference_data[middleware.key] = data
|
||||
|
||||
qdel(middleware)
|
||||
|
||||
for (var/preference_type in GLOB.preference_entries)
|
||||
var/datum/preference/preference_entry = GLOB.preference_entries[preference_type]
|
||||
var/data = preference_entry.compile_constant_data()
|
||||
if (!isnull(data))
|
||||
preference_data[preference_entry.savefile_key] = data
|
||||
|
||||
return preference_data
|
||||
@@ -0,0 +1,7 @@
|
||||
/datum/preference/toggle/auto_fit_viewport
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "auto_fit_viewport"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/auto_fit_viewport/apply_to_client_updated(client/client, value)
|
||||
INVOKE_ASYNC(client, /client/verb/fit_viewport)
|
||||
@@ -0,0 +1,21 @@
|
||||
/datum/preference/choiced/body_type
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
priority = PREFERENCE_PRIORITY_BODY_TYPE
|
||||
savefile_key = "body_type"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
|
||||
/datum/preference/choiced/body_type/init_possible_values()
|
||||
return list(MALE, FEMALE)
|
||||
|
||||
/datum/preference/choiced/body_type/apply_to_human(mob/living/carbon/human/target, value)
|
||||
if (target.gender != MALE && target.gender != FEMALE)
|
||||
target.body_type = value
|
||||
else
|
||||
target.body_type = target.gender
|
||||
|
||||
/datum/preference/choiced/body_type/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
var/gender = preferences.read_preference(/datum/preference/choiced/gender)
|
||||
return gender != MALE && gender != FEMALE
|
||||
@@ -0,0 +1,5 @@
|
||||
/// Whether or not to announce when the player logs in or out.
|
||||
/datum/preference/toggle/broadcast_login_logout
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "broadcast_login_logout"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
@@ -0,0 +1,5 @@
|
||||
/datum/preference/toggle/buttons_locked
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "buttons_locked"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
default_value = FALSE
|
||||
@@ -0,0 +1,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
|
||||
@@ -0,0 +1,17 @@
|
||||
/datum/preference/numeric/fps
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "clientfps"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
minimum = -1
|
||||
maximum = 240
|
||||
|
||||
/datum/preference/numeric/fps/apply_to_client(client/client, value)
|
||||
client.fps = (value < 0) ? RECOMMENDED_FPS : value
|
||||
|
||||
/datum/preference/numeric/fps/compile_constant_data()
|
||||
var/list/data = ..()
|
||||
|
||||
data["recommended_fps"] = RECOMMENDED_FPS
|
||||
|
||||
return data
|
||||
@@ -0,0 +1,11 @@
|
||||
/// Gender preference
|
||||
/datum/preference/choiced/gender
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "gender"
|
||||
priority = PREFERENCE_PRIORITY_GENDER
|
||||
|
||||
/datum/preference/choiced/gender/init_possible_values()
|
||||
return list(MALE, FEMALE, PLURAL)
|
||||
|
||||
/datum/preference/choiced/gender/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.gender = value
|
||||
@@ -0,0 +1,178 @@
|
||||
/// Determines what accessories your ghost will look like they have.
|
||||
/datum/preference/choiced/ghost_accessories
|
||||
savefile_key = "ghost_accs"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
|
||||
/datum/preference/choiced/ghost_accessories/init_possible_values()
|
||||
return list(GHOST_ACCS_NONE, GHOST_ACCS_DIR, GHOST_ACCS_FULL)
|
||||
|
||||
/datum/preference/choiced/ghost_accessories/create_default_value()
|
||||
return GHOST_ACCS_DEFAULT_OPTION
|
||||
|
||||
/datum/preference/choiced/ghost_accessories/apply_to_client(client/client, value)
|
||||
var/mob/dead/observer/ghost = client.mob
|
||||
if (!istype(ghost))
|
||||
return
|
||||
|
||||
ghost.ghost_accs = value
|
||||
ghost.update_appearance()
|
||||
|
||||
/datum/preference/choiced/ghost_accessories/deserialize(input, datum/preferences/preferences)
|
||||
// Old ghost preferences used to be 1/50/100.
|
||||
// Whoever did that wasted an entire day of my time trying to get those sent
|
||||
// properly, so I'm going to buck them.
|
||||
if (isnum(input))
|
||||
switch (input)
|
||||
if (1)
|
||||
input = GHOST_ACCS_NONE
|
||||
if (50)
|
||||
input = GHOST_ACCS_DIR
|
||||
if (100)
|
||||
input = GHOST_ACCS_FULL
|
||||
|
||||
return ..(input)
|
||||
|
||||
/// Determines the appearance of your ghost to others, when you are a BYOND member
|
||||
/datum/preference/choiced/ghost_form
|
||||
savefile_key = "ghost_form"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
should_generate_icons = TRUE
|
||||
|
||||
var/static/list/ghost_forms = list(
|
||||
"catghost" = "Cat",
|
||||
"ghost" = "Default",
|
||||
"ghost_black" = "Black",
|
||||
"ghost_blazeit" = "Blaze it",
|
||||
"ghost_blue" = "Blue",
|
||||
"ghost_camo" = "Camo",
|
||||
"ghost_cyan" = "Cyan",
|
||||
"ghost_dblue" = "Dark blue",
|
||||
"ghost_dcyan" = "Dark cyan",
|
||||
"ghost_dgreen" = "Dark green",
|
||||
"ghost_dpink" = "Dark pink",
|
||||
"ghost_dred" = "Dark red",
|
||||
"ghost_dyellow" = "Dark yellow",
|
||||
"ghost_fire" = "Fire",
|
||||
"ghost_funkypurp" = "Funky purple",
|
||||
"ghost_green" = "Green",
|
||||
"ghost_grey" = "Grey",
|
||||
"ghost_mellow" = "Mellow",
|
||||
"ghost_pink" = "Pink",
|
||||
"ghost_pinksherbert" = "Pink Sherbert",
|
||||
"ghost_purpleswirl" = "Purple Swirl",
|
||||
"ghost_rainbow" = "Rainbow",
|
||||
"ghost_red" = "Red",
|
||||
"ghost_yellow" = "Yellow",
|
||||
"ghostian2" = "Ian",
|
||||
"ghostking" = "King",
|
||||
"skeleghost" = "Skeleton",
|
||||
)
|
||||
|
||||
/datum/preference/choiced/ghost_form/init_possible_values()
|
||||
var/list/values = list()
|
||||
|
||||
for (var/ghost_form in ghost_forms)
|
||||
values[ghost_form] = icon('icons/mob/mob.dmi', ghost_form)
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/choiced/ghost_form/create_default_value()
|
||||
return "ghost"
|
||||
|
||||
/datum/preference/choiced/ghost_form/apply_to_client(client/client, value)
|
||||
var/mob/dead/observer/ghost = client.mob
|
||||
if (!istype(ghost))
|
||||
return
|
||||
|
||||
if (!client.is_content_unlocked())
|
||||
return
|
||||
|
||||
ghost.update_icon(ALL, value)
|
||||
|
||||
/datum/preference/choiced/ghost_form/compile_constant_data()
|
||||
var/list/data = ..()
|
||||
|
||||
data[CHOICED_PREFERENCE_DISPLAY_NAMES] = ghost_forms
|
||||
|
||||
return data
|
||||
|
||||
/// Toggles the HUD for ghosts
|
||||
/datum/preference/toggle/ghost_hud
|
||||
savefile_key = "ghost_hud"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
|
||||
/datum/preference/toggle/ghost_hud/apply_to_client(client/client, value)
|
||||
if (isobserver(client?.mob))
|
||||
client?.mob.hud_used?.show_hud()
|
||||
|
||||
/// Determines what ghosts orbiting look like to you.
|
||||
/datum/preference/choiced/ghost_orbit
|
||||
savefile_key = "ghost_orbit"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
|
||||
/datum/preference/choiced/ghost_orbit/init_possible_values()
|
||||
return list(
|
||||
GHOST_ORBIT_CIRCLE,
|
||||
GHOST_ORBIT_TRIANGLE,
|
||||
GHOST_ORBIT_SQUARE,
|
||||
GHOST_ORBIT_HEXAGON,
|
||||
GHOST_ORBIT_PENTAGON,
|
||||
)
|
||||
|
||||
/datum/preference/choiced/ghost_orbit/apply_to_client(client/client, value)
|
||||
var/mob/dead/observer/ghost = client.mob
|
||||
if (!istype(ghost))
|
||||
return
|
||||
|
||||
if (!client.is_content_unlocked())
|
||||
return
|
||||
|
||||
ghost.ghost_orbit = value
|
||||
|
||||
/// Determines how to show other ghosts
|
||||
/datum/preference/choiced/ghost_others
|
||||
savefile_key = "ghost_others"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
|
||||
/datum/preference/choiced/ghost_others/init_possible_values()
|
||||
return list(
|
||||
GHOST_OTHERS_SIMPLE,
|
||||
GHOST_OTHERS_DEFAULT_SPRITE,
|
||||
GHOST_OTHERS_THEIR_SETTING,
|
||||
)
|
||||
|
||||
/datum/preference/choiced/ghost_others/create_default_value()
|
||||
return GHOST_OTHERS_DEFAULT_OPTION
|
||||
|
||||
/datum/preference/choiced/ghost_others/apply_to_client(client/client, value)
|
||||
var/mob/dead/observer/ghost = client.mob
|
||||
if (!istype(ghost))
|
||||
return
|
||||
|
||||
ghost.update_sight()
|
||||
|
||||
/datum/preference/choiced/ghost_others/deserialize(input, datum/preferences/preferences)
|
||||
// Old ghost preferences used to be 1/50/100.
|
||||
// Whoever did that wasted an entire day of my time trying to get those sent
|
||||
// properly, so I'm going to buck them.
|
||||
if (isnum(input))
|
||||
switch (input)
|
||||
if (1)
|
||||
input = GHOST_OTHERS_SIMPLE
|
||||
if (50)
|
||||
input = GHOST_OTHERS_DEFAULT_SPRITE
|
||||
if (100)
|
||||
input = GHOST_OTHERS_THEIR_SETTING
|
||||
|
||||
return ..(input, preferences)
|
||||
|
||||
/// Whether or not ghosts can examine things by clicking on them.
|
||||
/datum/preference/toggle/inquisitive_ghost
|
||||
savefile_key = "inquisitive_ghost"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
@@ -0,0 +1,7 @@
|
||||
/datum/preference/toggle/hotkeys
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "hotkeys"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/hotkeys/apply_to_client(client/client, value)
|
||||
client.hotkeys = value
|
||||
@@ -0,0 +1,4 @@
|
||||
/datum/preference/toggle/item_outlines
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "itemoutline_pref"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
@@ -0,0 +1,12 @@
|
||||
/datum/preference/choiced/jobless_role
|
||||
savefile_key = "joblessrole"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/choiced/jobless_role/create_default_value()
|
||||
return BERANDOMJOB
|
||||
|
||||
/datum/preference/choiced/jobless_role/init_possible_values()
|
||||
return list(BEOVERFLOW, BERANDOMJOB, RETURNTOLOBBY)
|
||||
|
||||
/datum/preference/choiced/jobless_role/should_show_on_page(preference_tab)
|
||||
return preference_tab == PREFERENCE_TAB_CHARACTER_PREFERENCES
|
||||
@@ -0,0 +1,52 @@
|
||||
/// Preference middleware is code that helps to decentralize complicated preference features.
|
||||
/datum/preference_middleware
|
||||
/// The preferences datum
|
||||
var/datum/preferences/preferences
|
||||
|
||||
/// The key that will be used for get_constant_data().
|
||||
/// If null, will use the typepath minus /datum/preference_middleware.
|
||||
var/key = null
|
||||
|
||||
/// Map of ui_act actions -> proc paths to call.
|
||||
/// Signature is `(list/params, mob/user) -> TRUE/FALSE.
|
||||
/// Return output is the same as ui_act--TRUE if it should update, FALSE if it should not
|
||||
var/list/action_delegations = list()
|
||||
|
||||
/datum/preference_middleware/New(datum/preferences)
|
||||
src.preferences = preferences
|
||||
|
||||
if (isnull(key))
|
||||
// + 2 coming from the off-by-one of copytext, and then another from the slash
|
||||
key = copytext("[type]", length("[parent_type]") + 2)
|
||||
|
||||
/datum/preference_middleware/Destroy()
|
||||
preferences = null
|
||||
return ..()
|
||||
|
||||
/// Append all of these into ui_data
|
||||
/datum/preference_middleware/proc/get_ui_data(mob/user)
|
||||
return list()
|
||||
|
||||
/// Append all of these into ui_static_data
|
||||
/datum/preference_middleware/proc/get_ui_static_data(mob/user)
|
||||
return list()
|
||||
|
||||
/// Append all of these into ui_assets
|
||||
/datum/preference_middleware/proc/get_ui_assets()
|
||||
return list()
|
||||
|
||||
/// Append all of these into /datum/asset/json/preferences.
|
||||
/datum/preference_middleware/proc/get_constant_data()
|
||||
return null
|
||||
|
||||
/// Merge this into the result of compile_character_preferences.
|
||||
/datum/preference_middleware/proc/get_character_preferences(mob/user)
|
||||
return null
|
||||
|
||||
/// Called every set_preference, returns TRUE if this handled it.
|
||||
/datum/preference_middleware/proc/pre_set_preference(mob/user, preference, value)
|
||||
return FALSE
|
||||
|
||||
/// Called when a character is changed.
|
||||
/datum/preference_middleware/proc/on_new_character(mob/user)
|
||||
return
|
||||
@@ -0,0 +1,163 @@
|
||||
/datum/preference_middleware/antags
|
||||
action_delegations = list(
|
||||
"set_antags" = .proc/set_antags,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/antags/get_ui_static_data(mob/user)
|
||||
if (preferences.current_window != PREFERENCE_TAB_CHARACTER_PREFERENCES)
|
||||
return list()
|
||||
|
||||
var/list/data = list()
|
||||
|
||||
var/list/selected_antags = list()
|
||||
|
||||
for (var/antag in preferences.be_special)
|
||||
selected_antags += serialize_antag_name(antag)
|
||||
|
||||
data["selected_antags"] = selected_antags
|
||||
|
||||
var/list/antag_bans = get_antag_bans()
|
||||
if (antag_bans.len)
|
||||
data["antag_bans"] = antag_bans
|
||||
|
||||
var/list/antag_days_left = get_antag_days_left()
|
||||
if (antag_days_left?.len)
|
||||
data["antag_days_left"] = antag_days_left
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference_middleware/antags/get_ui_assets()
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/spritesheet/antagonists),
|
||||
)
|
||||
|
||||
/datum/preference_middleware/antags/proc/set_antags(list/params, mob/user)
|
||||
SHOULD_NOT_SLEEP(TRUE)
|
||||
|
||||
var/sent_antags = params["antags"]
|
||||
var/toggled = params["toggled"]
|
||||
|
||||
var/antags = list()
|
||||
|
||||
var/serialized_antags = get_serialized_antags()
|
||||
|
||||
for (var/sent_antag in sent_antags)
|
||||
var/special_role = serialized_antags[sent_antag]
|
||||
if (!special_role)
|
||||
continue
|
||||
|
||||
antags += special_role
|
||||
|
||||
if (toggled)
|
||||
preferences.be_special |= antags
|
||||
else
|
||||
preferences.be_special -= antags
|
||||
|
||||
// This is predicted on the client
|
||||
return FALSE
|
||||
|
||||
/datum/preference_middleware/antags/proc/get_antag_bans()
|
||||
var/list/antag_bans = list()
|
||||
|
||||
for (var/datum/dynamic_ruleset/dynamic_ruleset as anything in subtypesof(/datum/dynamic_ruleset))
|
||||
var/antag_flag = initial(dynamic_ruleset.antag_flag)
|
||||
var/antag_flag_override = initial(dynamic_ruleset.antag_flag_override)
|
||||
|
||||
if (isnull(antag_flag))
|
||||
continue
|
||||
|
||||
if (is_banned_from(preferences.parent.ckey, list(antag_flag_override || antag_flag, ROLE_SYNDICATE)))
|
||||
antag_bans += serialize_antag_name(antag_flag)
|
||||
|
||||
return antag_bans
|
||||
|
||||
/datum/preference_middleware/antags/proc/get_antag_days_left()
|
||||
if (!CONFIG_GET(flag/use_age_restriction_for_jobs))
|
||||
return
|
||||
|
||||
var/list/antag_days_left = list()
|
||||
|
||||
for (var/datum/dynamic_ruleset/dynamic_ruleset as anything in subtypesof(/datum/dynamic_ruleset))
|
||||
var/antag_flag = initial(dynamic_ruleset.antag_flag)
|
||||
var/antag_flag_override = initial(dynamic_ruleset.antag_flag_override)
|
||||
|
||||
if (isnull(antag_flag))
|
||||
continue
|
||||
|
||||
var/days_needed = preferences.parent?.get_remaining_days(
|
||||
GLOB.special_roles[antag_flag_override || antag_flag]
|
||||
)
|
||||
|
||||
if (days_needed > 0)
|
||||
antag_days_left[serialize_antag_name(antag_flag)] = days_needed
|
||||
|
||||
return antag_days_left
|
||||
|
||||
/datum/preference_middleware/antags/proc/get_serialized_antags()
|
||||
var/list/serialized_antags
|
||||
|
||||
if (isnull(serialized_antags))
|
||||
serialized_antags = list()
|
||||
|
||||
for (var/special_role in GLOB.special_roles)
|
||||
serialized_antags[serialize_antag_name(special_role)] = special_role
|
||||
|
||||
return serialized_antags
|
||||
|
||||
/// Sprites generated for the antagonists panel
|
||||
/datum/asset/spritesheet/antagonists
|
||||
name = "antagonists"
|
||||
early = TRUE
|
||||
|
||||
/datum/asset/spritesheet/antagonists/register()
|
||||
// Antagonists that don't have a dynamic ruleset, but do have a preference
|
||||
var/static/list/non_ruleset_antagonists = list(
|
||||
ROLE_LONE_OPERATIVE = /datum/antagonist/nukeop/lone,
|
||||
)
|
||||
|
||||
var/list/antagonists = non_ruleset_antagonists.Copy()
|
||||
|
||||
for (var/datum/dynamic_ruleset/ruleset as anything in subtypesof(/datum/dynamic_ruleset))
|
||||
var/datum/antagonist/antagonist_type = initial(ruleset.antag_datum)
|
||||
if (isnull(antagonist_type))
|
||||
continue
|
||||
|
||||
// antag_flag is guaranteed to be unique by unit tests.
|
||||
antagonists[initial(ruleset.antag_flag)] = antagonist_type
|
||||
|
||||
var/list/generated_icons = list()
|
||||
var/list/to_insert = list()
|
||||
|
||||
for (var/antag_flag in antagonists)
|
||||
var/datum/antagonist/antagonist_type = antagonists[antag_flag]
|
||||
|
||||
// antag_flag is guaranteed to be unique by unit tests.
|
||||
var/spritesheet_key = serialize_antag_name(antag_flag)
|
||||
|
||||
if (!isnull(generated_icons[antagonist_type]))
|
||||
to_insert[spritesheet_key] = generated_icons[antagonist_type]
|
||||
continue
|
||||
|
||||
var/datum/antagonist/antagonist = new antagonist_type
|
||||
var/icon/preview_icon = antagonist.get_preview_icon()
|
||||
|
||||
if (isnull(preview_icon))
|
||||
continue
|
||||
|
||||
qdel(antagonist)
|
||||
|
||||
// preview_icons are not scaled at this stage INTENTIONALLY.
|
||||
// If an icon is not prepared to be scaled to that size, it looks really ugly, and this
|
||||
// makes it harder to figure out what size it *actually* is.
|
||||
generated_icons[antagonist_type] = preview_icon
|
||||
to_insert[spritesheet_key] = preview_icon
|
||||
|
||||
for (var/spritesheet_key in to_insert)
|
||||
Insert(spritesheet_key, to_insert[spritesheet_key])
|
||||
|
||||
return ..()
|
||||
|
||||
/// Serializes an antag name to be used for preferences UI
|
||||
/proc/serialize_antag_name(antag_name)
|
||||
// These are sent through CSS, so they need to be safe to use as class names.
|
||||
return lowertext(sanitize_css_class_name(antag_name))
|
||||
@@ -0,0 +1,82 @@
|
||||
/datum/preference_middleware/jobs
|
||||
action_delegations = list(
|
||||
"set_job_preference" = .proc/set_job_preference,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/jobs/proc/set_job_preference(list/params, mob/user)
|
||||
var/job_title = params["job"]
|
||||
var/level = params["level"]
|
||||
|
||||
if (level != null && level != JP_LOW && level != JP_MEDIUM && level != JP_HIGH)
|
||||
return FALSE
|
||||
|
||||
var/datum/job/job = SSjob.GetJob(job_title)
|
||||
|
||||
if (isnull(job))
|
||||
return FALSE
|
||||
|
||||
if (job.faction != FACTION_STATION)
|
||||
return FALSE
|
||||
|
||||
if (!preferences.set_job_preference_level(job, level))
|
||||
return FALSE
|
||||
|
||||
preferences.character_preview_view?.update_body()
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/preference_middleware/jobs/get_ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["job_preferences"] = preferences.job_preferences
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference_middleware/jobs/get_ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
var/list/required_job_playtime = get_required_job_playtime(user)
|
||||
if (!isnull(required_job_playtime))
|
||||
data += required_job_playtime
|
||||
|
||||
var/list/job_bans = get_job_bans(user)
|
||||
if (job_bans.len)
|
||||
data["job_bans"] = job_bans
|
||||
|
||||
return data.len > 0 ? data : null
|
||||
|
||||
/datum/preference_middleware/jobs/proc/get_required_job_playtime(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
var/list/job_days_left = list()
|
||||
var/list/job_required_experience = list()
|
||||
|
||||
for (var/datum/job/job as anything in SSjob.all_occupations)
|
||||
var/required_playtime_remaining = job.required_playtime_remaining(user.client)
|
||||
if (required_playtime_remaining)
|
||||
job_required_experience[job.title] = list(
|
||||
"experience_type" = job.get_exp_req_type(),
|
||||
"required_playtime" = required_playtime_remaining,
|
||||
)
|
||||
|
||||
continue
|
||||
|
||||
if (!job.player_old_enough(user.client))
|
||||
job_days_left[job.title] = job.available_in_days(user.client)
|
||||
|
||||
if (job_days_left.len)
|
||||
data["job_days_left"] = job_days_left
|
||||
|
||||
if (job_required_experience)
|
||||
data["job_required_experience"] = job_required_experience
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference_middleware/jobs/proc/get_job_bans(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
for (var/datum/job/job as anything in SSjob.all_occupations)
|
||||
if (is_banned_from(user.client?.ckey, job.title))
|
||||
data += job.title
|
||||
|
||||
return data
|
||||
@@ -0,0 +1,89 @@
|
||||
#define MAX_HOTKEY_SLOTS 3
|
||||
|
||||
/// Middleware to handle keybindings
|
||||
/datum/preference_middleware/keybindings
|
||||
action_delegations = list(
|
||||
"reset_keybinds_to_defaults" = .proc/reset_keybinds_to_defaults,
|
||||
"set_keybindings" = .proc/set_keybindings,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/keybindings/get_ui_static_data(mob/user)
|
||||
if (preferences.current_window == PREFERENCE_TAB_CHARACTER_PREFERENCES)
|
||||
return list()
|
||||
|
||||
var/list/keybindings = preferences.key_bindings
|
||||
|
||||
return list(
|
||||
"keybindings" = keybindings,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/keybindings/get_ui_assets()
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/json/keybindings)
|
||||
)
|
||||
|
||||
/datum/preference_middleware/keybindings/proc/reset_keybinds_to_defaults(list/params, mob/user)
|
||||
var/keybind_name = params["keybind_name"]
|
||||
var/datum/keybinding/keybinding = GLOB.keybindings_by_name[keybind_name]
|
||||
|
||||
if (isnull(keybinding))
|
||||
return FALSE
|
||||
|
||||
preferences.key_bindings[keybind_name] = preferences.parent.hotkeys ? keybinding.hotkey_keys : keybinding.classic_keys
|
||||
preferences.key_bindings_by_key = preferences.get_key_bindings_by_key(preferences.key_bindings)
|
||||
|
||||
preferences.update_static_data(user)
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/preference_middleware/keybindings/proc/set_keybindings(list/params)
|
||||
var/keybind_name = params["keybind_name"]
|
||||
|
||||
if (isnull(GLOB.keybindings_by_name[keybind_name]))
|
||||
return FALSE
|
||||
|
||||
var/list/raw_hotkeys = params["hotkeys"]
|
||||
if (!istype(raw_hotkeys))
|
||||
return FALSE
|
||||
|
||||
if (raw_hotkeys.len > MAX_HOTKEY_SLOTS)
|
||||
return FALSE
|
||||
|
||||
// There's no optimal, easy way to check if something is an array
|
||||
// and not an object in BYOND, so just sanitize it to make sure.
|
||||
var/list/hotkeys = list()
|
||||
for (var/hotkey in raw_hotkeys)
|
||||
if (!istext(hotkey))
|
||||
return FALSE
|
||||
|
||||
// Fairly arbitrary number, it's just so you don't save enormous fake keybinds.
|
||||
if (length(hotkey) > 100)
|
||||
return FALSE
|
||||
|
||||
hotkeys += hotkey
|
||||
|
||||
preferences.key_bindings[keybind_name] = hotkeys
|
||||
preferences.key_bindings_by_key = preferences.get_key_bindings_by_key(preferences.key_bindings)
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/asset/json/keybindings
|
||||
name = "keybindings"
|
||||
|
||||
/datum/asset/json/keybindings/generate()
|
||||
var/list/keybindings = list()
|
||||
|
||||
for (var/name in GLOB.keybindings_by_name)
|
||||
var/datum/keybinding/keybinding = GLOB.keybindings_by_name[name]
|
||||
|
||||
if (!(keybinding.category in keybindings))
|
||||
keybindings[keybinding.category] = list()
|
||||
|
||||
keybindings[keybinding.category][keybinding.name] = list(
|
||||
"name" = keybinding.full_name,
|
||||
"description" = keybinding.description,
|
||||
)
|
||||
|
||||
return keybindings
|
||||
|
||||
#undef MAX_HOTKEY_SLOTS
|
||||
@@ -0,0 +1,132 @@
|
||||
/// In the before times, toggles were all stored in one bitfield.
|
||||
/// In order to preserve this existing data (and code) without massive
|
||||
/// migrations, this middleware attempts to handle this in a way
|
||||
/// transparent to the preferences UI itself.
|
||||
/// In the future, the existing toggles data should just be migrated to
|
||||
/// individual `/datum/preference/toggle`s.
|
||||
/datum/preference_middleware/legacy_toggles
|
||||
// DO NOT ADD ANY NEW TOGGLES HERE!
|
||||
// Use `/datum/preference/toggle` instead.
|
||||
var/static/list/legacy_toggles = list(
|
||||
"admin_ignore_cult_ghost" = ADMIN_IGNORE_CULT_GHOST,
|
||||
"announce_login" = ANNOUNCE_LOGIN,
|
||||
"combohud_lighting" = COMBOHUD_LIGHTING,
|
||||
"deadmin_always" = DEADMIN_ALWAYS,
|
||||
"deadmin_antagonist" = DEADMIN_ANTAGONIST,
|
||||
"deadmin_position_head" = DEADMIN_POSITION_HEAD,
|
||||
"deadmin_position_security" = DEADMIN_POSITION_SECURITY,
|
||||
"deadmin_position_silicon" = DEADMIN_POSITION_SILICON,
|
||||
"disable_arrivalrattle" = DISABLE_ARRIVALRATTLE,
|
||||
"disable_deathrattle" = DISABLE_DEATHRATTLE,
|
||||
"member_public" = MEMBER_PUBLIC,
|
||||
"sound_adminhelp" = SOUND_ADMINHELP,
|
||||
"sound_ambience" = SOUND_AMBIENCE,
|
||||
"sound_announcements" = SOUND_ANNOUNCEMENTS,
|
||||
"sound_combatmode" = SOUND_COMBATMODE,
|
||||
"sound_endofround" = SOUND_ENDOFROUND,
|
||||
"sound_instruments" = SOUND_INSTRUMENTS,
|
||||
"sound_lobby" = SOUND_LOBBY,
|
||||
"sound_midi" = SOUND_MIDI,
|
||||
"sound_prayers" = SOUND_PRAYERS,
|
||||
"sound_ship_ambience" = SOUND_SHIP_AMBIENCE,
|
||||
"split_admin_tabs" = SPLIT_ADMIN_TABS,
|
||||
)
|
||||
|
||||
var/list/legacy_chat_toggles = list(
|
||||
"chat_bankcard" = CHAT_BANKCARD,
|
||||
"chat_dead" = CHAT_DEAD,
|
||||
"chat_ghostears" = CHAT_GHOSTEARS,
|
||||
"chat_ghostlaws" = CHAT_GHOSTLAWS,
|
||||
"chat_ghostpda" = CHAT_GHOSTPDA,
|
||||
"chat_ghostradio" = CHAT_GHOSTRADIO,
|
||||
"chat_ghostsight" = CHAT_GHOSTSIGHT,
|
||||
"chat_ghostwhisper" = CHAT_GHOSTWHISPER,
|
||||
"chat_login_logout" = CHAT_LOGIN_LOGOUT,
|
||||
"chat_ooc" = CHAT_OOC,
|
||||
"chat_prayer" = CHAT_PRAYER,
|
||||
"chat_pullr" = CHAT_PULLR,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/legacy_toggles/get_character_preferences(mob/user)
|
||||
if (preferences.current_window != PREFERENCE_TAB_GAME_PREFERENCES)
|
||||
return list()
|
||||
|
||||
var/static/list/admin_only_legacy_toggles = list(
|
||||
"admin_ignore_cult_ghost",
|
||||
"announce_login",
|
||||
"combohud_lighting",
|
||||
"deadmin_always",
|
||||
"deadmin_antagonist",
|
||||
"deadmin_position_head",
|
||||
"deadmin_position_security",
|
||||
"deadmin_position_silicon",
|
||||
"sound_adminhelp",
|
||||
"sound_prayers",
|
||||
"split_admin_tabs",
|
||||
)
|
||||
|
||||
var/static/list/admin_only_chat_toggles = list(
|
||||
"chat_dead",
|
||||
"chat_prayer",
|
||||
)
|
||||
|
||||
var/static/list/deadmin_flags = list(
|
||||
"deadmin_antagonist",
|
||||
"deadmin_position_head",
|
||||
"deadmin_position_security",
|
||||
"deadmin_position_silicon",
|
||||
)
|
||||
|
||||
var/list/new_game_preferences = list()
|
||||
var/is_admin = is_admin(user.client)
|
||||
|
||||
for (var/toggle_name in legacy_toggles)
|
||||
if (!is_admin && (toggle_name in admin_only_legacy_toggles))
|
||||
continue
|
||||
|
||||
if (is_admin && (toggle_name in deadmin_flags) && (preferences.toggles & DEADMIN_ALWAYS))
|
||||
continue
|
||||
|
||||
if (toggle_name == "member_public" && !preferences.unlock_content)
|
||||
continue
|
||||
|
||||
new_game_preferences[toggle_name] = (preferences.toggles & legacy_toggles[toggle_name]) != 0
|
||||
|
||||
for (var/toggle_name in legacy_chat_toggles)
|
||||
if (!is_admin && (toggle_name in admin_only_chat_toggles))
|
||||
continue
|
||||
|
||||
new_game_preferences[toggle_name] = (preferences.chat_toggles & legacy_chat_toggles[toggle_name]) != 0
|
||||
|
||||
return list(
|
||||
PREFERENCE_CATEGORY_GAME_PREFERENCES = new_game_preferences,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/legacy_toggles/pre_set_preference(mob/user, preference, value)
|
||||
var/legacy_flag = legacy_toggles[preference]
|
||||
if (!isnull(legacy_flag))
|
||||
if (value)
|
||||
preferences.toggles |= legacy_flag
|
||||
else
|
||||
preferences.toggles &= ~legacy_flag
|
||||
|
||||
// I know this looks silly, but this is the only one that cares
|
||||
// and NO NEW LEGACY TOGGLES should ever be added.
|
||||
if (legacy_flag == SOUND_LOBBY)
|
||||
if (value && isnewplayer(user))
|
||||
user.client?.playtitlemusic()
|
||||
else
|
||||
user.stop_sound_channel(CHANNEL_LOBBYMUSIC)
|
||||
|
||||
return TRUE
|
||||
|
||||
var/legacy_chat_flag = legacy_chat_toggles[preference]
|
||||
if (!isnull(legacy_chat_flag))
|
||||
if (value)
|
||||
preferences.chat_toggles |= legacy_chat_flag
|
||||
else
|
||||
preferences.chat_toggles &= ~legacy_chat_flag
|
||||
|
||||
return TRUE
|
||||
|
||||
return FALSE
|
||||
@@ -0,0 +1,56 @@
|
||||
/// Middleware that handles telling the UI which name to show, and waht names
|
||||
/// they have.
|
||||
/datum/preference_middleware/names
|
||||
action_delegations = list(
|
||||
"randomize_name" = .proc/randomize_name,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/names/get_constant_data()
|
||||
var/list/data = list()
|
||||
|
||||
var/list/types = list()
|
||||
|
||||
for (var/preference_type in GLOB.preference_entries)
|
||||
var/datum/preference/name/name_preference = GLOB.preference_entries[preference_type]
|
||||
if (!istype(name_preference))
|
||||
continue
|
||||
|
||||
types[name_preference.savefile_key] = list(
|
||||
"can_randomize" = name_preference.is_randomizable(),
|
||||
"explanation" = name_preference.explanation,
|
||||
"group" = name_preference.group,
|
||||
)
|
||||
|
||||
data["types"] = types
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference_middleware/names/get_ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["name_to_use"] = get_name_to_use()
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference_middleware/names/proc/get_name_to_use()
|
||||
var/highest_priority_job = preferences.get_highest_priority_job()
|
||||
|
||||
for (var/preference_type in GLOB.preference_entries)
|
||||
var/datum/preference/name/name_preference = GLOB.preference_entries[preference_type]
|
||||
if (!istype(name_preference))
|
||||
continue
|
||||
|
||||
if (isnull(name_preference.relevant_job))
|
||||
continue
|
||||
|
||||
if (istype(highest_priority_job, name_preference.relevant_job))
|
||||
return name_preference.savefile_key
|
||||
|
||||
return "real_name"
|
||||
|
||||
/datum/preference_middleware/names/proc/randomize_name(list/params, mob/user)
|
||||
var/datum/preference/name/name_preference = GLOB.preference_entries_by_key[params["preference"]]
|
||||
if (!istype(name_preference))
|
||||
return FALSE
|
||||
|
||||
return preferences.update_preference(name_preference, name_preference.create_random_value(preferences))
|
||||
@@ -0,0 +1,89 @@
|
||||
/// Middleware to handle quirks
|
||||
/datum/preference_middleware/quirks
|
||||
var/tainted = FALSE
|
||||
|
||||
action_delegations = list(
|
||||
"give_quirk" = .proc/give_quirk,
|
||||
"remove_quirk" = .proc/remove_quirk,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/quirks/get_ui_static_data(mob/user)
|
||||
if (preferences.current_window != PREFERENCE_TAB_CHARACTER_PREFERENCES)
|
||||
return list()
|
||||
|
||||
var/list/data = list()
|
||||
|
||||
data["selected_quirks"] = get_selected_quirks()
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference_middleware/quirks/get_ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
if (tainted)
|
||||
tainted = FALSE
|
||||
data["selected_quirks"] = get_selected_quirks()
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference_middleware/quirks/get_constant_data()
|
||||
var/list/quirk_info = list()
|
||||
|
||||
for (var/quirk_name in SSquirks.quirks)
|
||||
var/datum/quirk/quirk = SSquirks.quirks[quirk_name]
|
||||
quirk_info[sanitize_css_class_name(quirk_name)] = list(
|
||||
"description" = initial(quirk.desc),
|
||||
"icon" = initial(quirk.icon),
|
||||
"name" = quirk_name,
|
||||
"value" = initial(quirk.value),
|
||||
)
|
||||
|
||||
return list(
|
||||
"max_positive_quirks" = MAX_QUIRKS,
|
||||
"quirk_info" = quirk_info,
|
||||
"quirk_blacklist" = SSquirks.quirk_blacklist,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/quirks/on_new_character(mob/user)
|
||||
tainted = TRUE
|
||||
|
||||
/datum/preference_middleware/quirks/proc/give_quirk(list/params, mob/user)
|
||||
var/quirk_name = params["quirk"]
|
||||
|
||||
var/list/new_quirks = preferences.all_quirks | quirk_name
|
||||
if (SSquirks.filter_invalid_quirks(new_quirks) != new_quirks)
|
||||
// If the client is sending an invalid give_quirk, that means that
|
||||
// something went wrong with the client prediction, so we should
|
||||
// catch it back up to speed.
|
||||
preferences.update_static_data(user)
|
||||
return TRUE
|
||||
|
||||
preferences.all_quirks = new_quirks
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/preference_middleware/quirks/proc/remove_quirk(list/params, mob/user)
|
||||
var/quirk_name = params["quirk"]
|
||||
|
||||
var/list/new_quirks = preferences.all_quirks - quirk_name
|
||||
if ( \
|
||||
!(quirk_name in preferences.all_quirks) \
|
||||
|| SSquirks.filter_invalid_quirks(new_quirks) != new_quirks \
|
||||
)
|
||||
// If the client is sending an invalid remove_quirk, that means that
|
||||
// something went wrong with the client prediction, so we should
|
||||
// catch it back up to speed.
|
||||
preferences.update_static_data(user)
|
||||
return TRUE
|
||||
|
||||
preferences.all_quirks = new_quirks
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/preference_middleware/quirks/proc/get_selected_quirks()
|
||||
var/list/selected_quirks = list()
|
||||
|
||||
for (var/quirk in preferences.all_quirks)
|
||||
selected_quirks += sanitize_css_class_name(quirk)
|
||||
|
||||
return selected_quirks
|
||||
@@ -0,0 +1,84 @@
|
||||
/// Middleware for handling randomization preferences
|
||||
/datum/preference_middleware/random
|
||||
action_delegations = list(
|
||||
"randomize_character" = .proc/randomize_character,
|
||||
"set_random_preference" = .proc/set_random_preference,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/random/get_character_preferences(mob/user)
|
||||
return list(
|
||||
"randomization" = preferences.randomise,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/random/get_constant_data()
|
||||
var/list/randomizable = list()
|
||||
|
||||
for (var/preference_type in GLOB.preference_entries)
|
||||
var/datum/preference/preference = GLOB.preference_entries[preference_type]
|
||||
if (!preference.is_randomizable())
|
||||
continue
|
||||
|
||||
randomizable += preference.savefile_key
|
||||
|
||||
return list(
|
||||
"randomizable" = randomizable,
|
||||
)
|
||||
|
||||
/datum/preference_middleware/random/proc/randomize_character()
|
||||
for (var/datum/preference/preference as anything in get_preferences_in_priority_order())
|
||||
if (preferences.should_randomize(preference))
|
||||
preferences.write_preference(preference, preference.create_random_value(src))
|
||||
|
||||
preferences.character_preview_view.update_body()
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/preference_middleware/random/proc/set_random_preference(list/params, mob/user)
|
||||
var/requested_preference_key = params["preference"]
|
||||
var/value = params["value"]
|
||||
|
||||
var/datum/preference/requested_preference = GLOB.preference_entries_by_key[requested_preference_key]
|
||||
if (isnull(requested_preference))
|
||||
return FALSE
|
||||
|
||||
if (!requested_preference.is_randomizable())
|
||||
return FALSE
|
||||
|
||||
if (value == RANDOM_ANTAG_ONLY)
|
||||
preferences.randomise[requested_preference_key] = RANDOM_ANTAG_ONLY
|
||||
else if (value == RANDOM_ENABLED)
|
||||
preferences.randomise[requested_preference_key] = RANDOM_ENABLED
|
||||
else if (value == RANDOM_DISABLED)
|
||||
preferences.randomise -= requested_preference_key
|
||||
else
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
/// Returns if a preference should be randomized.
|
||||
/datum/preferences/proc/should_randomize(datum/preference/preference, is_antag)
|
||||
if (!preference.is_randomizable())
|
||||
return FALSE
|
||||
|
||||
var/requested_randomization = randomise[preference.savefile_key]
|
||||
|
||||
if (istype(preference, /datum/preference/name))
|
||||
requested_randomization = read_preference(/datum/preference/choiced/random_name)
|
||||
|
||||
switch (requested_randomization)
|
||||
if (RANDOM_ENABLED)
|
||||
return TRUE
|
||||
if (RANDOM_ANTAG_ONLY)
|
||||
return is_antag
|
||||
else
|
||||
return FALSE
|
||||
|
||||
/// Given randomization flags, will return whether or not this preference should be randomized.
|
||||
/datum/preference/proc/included_in_randomization_flags(randomize_flags)
|
||||
return TRUE
|
||||
|
||||
/datum/preference/name/included_in_randomization_flags(randomize_flags)
|
||||
return !!(randomize_flags & RANDOMIZE_NAME)
|
||||
|
||||
/datum/preference/choiced/species/included_in_randomization_flags(randomize_flags)
|
||||
return !!(randomize_flags & RANDOMIZE_SPECIES)
|
||||
@@ -0,0 +1,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 ..()
|
||||
@@ -0,0 +1,156 @@
|
||||
/// A preference for a name. Used not just for normal names, but also for clown names, etc.
|
||||
/datum/preference/name
|
||||
category = "names"
|
||||
priority = PREFERENCE_PRIORITY_NAMES
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
abstract_type = /datum/preference/name
|
||||
|
||||
/// The display name when showing on the "other names" panel
|
||||
var/explanation
|
||||
|
||||
/// These will be grouped together on the preferences menu
|
||||
var/group
|
||||
|
||||
/// Whether or not to allow numbers in the person's name
|
||||
var/allow_numbers = FALSE
|
||||
|
||||
/// If the highest priority job matches this, will prioritize this name in the UI
|
||||
var/relevant_job
|
||||
|
||||
/datum/preference/name/apply_to_human(mob/living/carbon/human/target, value)
|
||||
// Only real_name applies directly, everything else is applied by something else
|
||||
return
|
||||
|
||||
/datum/preference/name/deserialize(input, datum/preferences/preferences)
|
||||
return reject_bad_name("[input]", allow_numbers)
|
||||
|
||||
/datum/preference/name/serialize(input)
|
||||
// `is_valid` should always be run before `serialize`, so it should not
|
||||
// be possible for this to return `null`.
|
||||
return reject_bad_name(input, allow_numbers)
|
||||
|
||||
/datum/preference/name/is_valid(value)
|
||||
return istext(value) && !isnull(reject_bad_name(value, allow_numbers))
|
||||
|
||||
/// A character's real name
|
||||
/datum/preference/name/real_name
|
||||
explanation = "Name"
|
||||
// The `_` makes it first in ABC order.
|
||||
group = "_real_name"
|
||||
savefile_key = "real_name"
|
||||
|
||||
/datum/preference/name/real_name/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.real_name = value
|
||||
target.name = value
|
||||
|
||||
/datum/preference/name/real_name/create_informed_default_value(datum/preferences/preferences)
|
||||
var/species_type = preferences.read_preference(/datum/preference/choiced/species)
|
||||
var/gender = preferences.read_preference(/datum/preference/choiced/gender)
|
||||
|
||||
var/datum/species/species = new species_type
|
||||
|
||||
return species.random_name(gender, unique = TRUE)
|
||||
|
||||
/datum/preference/name/real_name/deserialize(input, datum/preferences/preferences)
|
||||
input = ..(input)
|
||||
if (!input)
|
||||
return input
|
||||
|
||||
if (CONFIG_GET(flag/humans_need_surnames) && preferences.read_preference(/datum/preference/choiced/species) == /datum/species/human)
|
||||
var/first_space = findtext(input, " ")
|
||||
if(!first_space) //we need a surname
|
||||
input += " [pick(GLOB.last_names)]"
|
||||
else if(first_space == length(input))
|
||||
input += "[pick(GLOB.last_names)]"
|
||||
|
||||
return reject_bad_name(input, allow_numbers)
|
||||
|
||||
/// The name for a backup human, when nonhumans are made into head of staff
|
||||
/datum/preference/name/backup_human
|
||||
explanation = "Backup human name"
|
||||
group = "backup_human"
|
||||
savefile_key = "human_name"
|
||||
|
||||
/datum/preference/name/backup_human/create_informed_default_value(datum/preferences/preferences)
|
||||
var/gender = preferences.read_preference(/datum/preference/choiced/gender)
|
||||
|
||||
return random_unique_name(gender)
|
||||
|
||||
/datum/preference/name/clown
|
||||
savefile_key = "clown_name"
|
||||
|
||||
explanation = "Clown name"
|
||||
group = "fun"
|
||||
relevant_job = /datum/job/clown
|
||||
|
||||
/datum/preference/name/clown/create_default_value()
|
||||
return pick(GLOB.clown_names)
|
||||
|
||||
/datum/preference/name/mime
|
||||
savefile_key = "mime_name"
|
||||
|
||||
explanation = "Mime name"
|
||||
group = "fun"
|
||||
relevant_job = /datum/job/mime
|
||||
|
||||
/datum/preference/name/mime/create_default_value()
|
||||
return pick(GLOB.mime_names)
|
||||
|
||||
/datum/preference/name/cyborg
|
||||
savefile_key = "cyborg_name"
|
||||
|
||||
allow_numbers = TRUE
|
||||
can_randomize = FALSE
|
||||
|
||||
explanation = "Cyborg name"
|
||||
group = "silicons"
|
||||
relevant_job = /datum/job/cyborg
|
||||
|
||||
/datum/preference/name/cyborg/create_default_value()
|
||||
return DEFAULT_CYBORG_NAME
|
||||
|
||||
/datum/preference/name/ai
|
||||
savefile_key = "ai_name"
|
||||
|
||||
allow_numbers = TRUE
|
||||
explanation = "AI name"
|
||||
group = "silicons"
|
||||
relevant_job = /datum/job/ai
|
||||
|
||||
/datum/preference/name/ai/create_default_value()
|
||||
return pick(GLOB.ai_names)
|
||||
|
||||
/datum/preference/name/religion
|
||||
savefile_key = "religion_name"
|
||||
|
||||
allow_numbers = TRUE
|
||||
|
||||
explanation = "Religion name"
|
||||
group = "religion"
|
||||
|
||||
/datum/preference/name/religion/create_default_value()
|
||||
return pick(GLOB.religion_names)
|
||||
|
||||
/datum/preference/name/deity
|
||||
savefile_key = "deity_name"
|
||||
|
||||
allow_numbers = TRUE
|
||||
can_randomize = FALSE
|
||||
|
||||
explanation = "Deity name"
|
||||
group = "religion"
|
||||
|
||||
/datum/preference/name/deity/create_default_value()
|
||||
return DEFAULT_DEITY
|
||||
|
||||
/datum/preference/name/bible
|
||||
savefile_key = "bible_name"
|
||||
|
||||
allow_numbers = TRUE
|
||||
can_randomize = FALSE
|
||||
|
||||
explanation = "Bible name"
|
||||
group = "religion"
|
||||
|
||||
/datum/preference/name/bible/create_default_value()
|
||||
return DEFAULT_BIBLE
|
||||
@@ -0,0 +1,14 @@
|
||||
/// The color admins will speak in for OOC.
|
||||
/datum/preference/color/ooc_color
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "ooccolor"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/color/ooc_color/create_default_value()
|
||||
return "#c43b23"
|
||||
|
||||
/datum/preference/color/ooc_color/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return is_admin(preferences.parent) || preferences.unlock_content
|
||||
@@ -0,0 +1,38 @@
|
||||
/// Determines parallax, "fancy space"
|
||||
/datum/preference/choiced/parallax
|
||||
savefile_key = "parallax"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
|
||||
/datum/preference/choiced/parallax/init_possible_values()
|
||||
return list(
|
||||
PARALLAX_INSANE,
|
||||
PARALLAX_HIGH,
|
||||
PARALLAX_MED,
|
||||
PARALLAX_LOW,
|
||||
PARALLAX_DISABLE,
|
||||
)
|
||||
|
||||
/datum/preference/choiced/parallax/create_default_value()
|
||||
return PARALLAX_HIGH
|
||||
|
||||
/datum/preference/choiced/parallax/apply_to_client(client/client, value)
|
||||
client.mob?.hud_used?.update_parallax_pref(client?.mob)
|
||||
|
||||
/datum/preference/choiced/parallax/deserialize(input, datum/preferences/preferences)
|
||||
// Old preferences were numbers, which causes annoyances when
|
||||
// sending over as lists that isn't worth dealing with.
|
||||
if (isnum(input))
|
||||
switch (input)
|
||||
if (-1)
|
||||
input = PARALLAX_INSANE
|
||||
if (0)
|
||||
input = PARALLAX_HIGH
|
||||
if (1)
|
||||
input = PARALLAX_MED
|
||||
if (2)
|
||||
input = PARALLAX_LOW
|
||||
if (3)
|
||||
input = PARALLAX_DISABLE
|
||||
|
||||
return ..(input)
|
||||
@@ -0,0 +1,17 @@
|
||||
/// The color of a PDA
|
||||
/datum/preference/color/pda_color
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "pda_color"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/color/pda_color/create_default_value()
|
||||
return COLOR_OLIVE
|
||||
|
||||
/// The visual style of a PDA
|
||||
/datum/preference/choiced/pda_style
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "pda_style"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/choiced/pda_style/init_possible_values()
|
||||
return GLOB.pda_styles
|
||||
@@ -0,0 +1,16 @@
|
||||
/datum/preference/choiced/phobia
|
||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
||||
savefile_key = "phobia"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
|
||||
/datum/preference/choiced/phobia/init_possible_values()
|
||||
return GLOB.phobia_types
|
||||
|
||||
/datum/preference/choiced/phobia/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return "Phobia" in preferences.all_quirks
|
||||
|
||||
/datum/preference/choiced/phobia/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
@@ -0,0 +1,15 @@
|
||||
/datum/preference/numeric/pixel_size
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "pixel_size"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
minimum = 0
|
||||
maximum = 3
|
||||
|
||||
step = 0.5
|
||||
|
||||
/datum/preference/numeric/pixel_size/create_default_value()
|
||||
return 0
|
||||
|
||||
/datum/preference/numeric/pixel_size/apply_to_client(client/client, value)
|
||||
client?.view_size?.resetFormat()
|
||||
@@ -0,0 +1,20 @@
|
||||
/// This can be set to TRUE from the prefs menu only once the user has
|
||||
/// gained over 5K playtime hours.
|
||||
/// If true, it allows the user to get a cool looking roundstart cloak.
|
||||
/datum/preference/toggle/playtime_reward_cloak
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "playtime_reward_cloak"
|
||||
|
||||
/datum/preference/toggle/playtime_reward_cloak/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return preferences.parent?.is_veteran()
|
||||
|
||||
/datum/preference/toggle/playtime_reward_cloak/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
|
||||
/// Returns whether the client should receive the gamer cloak
|
||||
/client/proc/is_veteran()
|
||||
return get_exp_living(pure_numeric = TRUE) >= PLAYTIME_VETERAN
|
||||
@@ -0,0 +1,52 @@
|
||||
/// During map rotation, this will help determine the chosen map.
|
||||
/datum/preference/choiced/preferred_map
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "preferred_map"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/choiced/preferred_map/init_possible_values()
|
||||
var/list/maps = list()
|
||||
maps += ""
|
||||
|
||||
for (var/map in config.maplist)
|
||||
var/datum/map_config/map_config = config.maplist[map]
|
||||
if (!map_config.votable)
|
||||
continue
|
||||
|
||||
maps += map
|
||||
|
||||
return maps
|
||||
|
||||
/datum/preference/choiced/preferred_map/create_default_value()
|
||||
return ""
|
||||
|
||||
/datum/preference/choiced/preferred_map/compile_constant_data()
|
||||
var/list/data = ..()
|
||||
|
||||
var/display_names = list()
|
||||
|
||||
if (config.defaultmap)
|
||||
display_names[""] = "Default ([config.defaultmap.map_name])"
|
||||
else
|
||||
display_names[""] = "Default"
|
||||
|
||||
for (var/choice in get_choices())
|
||||
if (choice == "")
|
||||
continue
|
||||
|
||||
var/datum/map_config/map_config = config.maplist[choice]
|
||||
|
||||
var/map_name = map_config.map_name
|
||||
if (map_config.voteweight <= 0)
|
||||
map_name += " (disabled)"
|
||||
display_names[choice] = map_name
|
||||
|
||||
data[CHOICED_PREFERENCE_DISPLAY_NAMES] = display_names
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference/choiced/preferred_map/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return CONFIG_GET(flag/preference_map_voting)
|
||||
@@ -0,0 +1,53 @@
|
||||
/datum/preference/choiced/random_body
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_key = "random_body"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
can_randomize = FALSE
|
||||
|
||||
/datum/preference/choiced/random_body/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
|
||||
/datum/preference/choiced/random_body/init_possible_values()
|
||||
return list(
|
||||
RANDOM_ANTAG_ONLY,
|
||||
RANDOM_DISABLED,
|
||||
RANDOM_ENABLED,
|
||||
)
|
||||
|
||||
/datum/preference/choiced/random_body/create_default_value()
|
||||
return RANDOM_DISABLED
|
||||
|
||||
/datum/preference/toggle/random_hardcore
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_key = "random_hardcore"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
can_randomize = FALSE
|
||||
default_value = FALSE
|
||||
|
||||
/datum/preference/toggle/random_hardcore/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
|
||||
/datum/preference/toggle/random_hardcore/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return preferences.parent.get_exp_living(pure_numeric = TRUE) >= PLAYTIME_HARDCORE_RANDOM
|
||||
|
||||
/datum/preference/choiced/random_name
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_key = "random_name"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
can_randomize = FALSE
|
||||
|
||||
/datum/preference/choiced/random_name/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
|
||||
/datum/preference/choiced/random_name/init_possible_values()
|
||||
return list(
|
||||
RANDOM_ANTAG_ONLY,
|
||||
RANDOM_DISABLED,
|
||||
RANDOM_ENABLED,
|
||||
)
|
||||
|
||||
/datum/preference/choiced/random_name/create_default_value()
|
||||
return RANDOM_DISABLED
|
||||
@@ -0,0 +1,25 @@
|
||||
/datum/preference/toggle/enable_runechat
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "chat_on_map"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/enable_runechat_non_mobs
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "see_chat_non_mob"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/see_rc_emotes
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "see_rc_emotes"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/numeric/max_chat_length
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "max_chat_length"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
minimum = 1
|
||||
maximum = CHAT_MESSAGE_MAX_LENGTH
|
||||
|
||||
/datum/preference/numeric/max_chat_length/create_default_value()
|
||||
return CHAT_MESSAGE_MAX_LENGTH
|
||||
@@ -0,0 +1,14 @@
|
||||
/// The scaling method to show the world in, e.g. nearest neighbor
|
||||
/datum/preference/choiced/scaling_method
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "scaling_method"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/choiced/scaling_method/create_default_value()
|
||||
return SCALING_METHOD_DISTORT
|
||||
|
||||
/datum/preference/choiced/scaling_method/init_possible_values()
|
||||
return list(SCALING_METHOD_DISTORT, SCALING_METHOD_BLUR, SCALING_METHOD_NORMAL)
|
||||
|
||||
/datum/preference/choiced/scaling_method/apply_to_client(client/client, value)
|
||||
client?.view_size?.setZoomMode()
|
||||
@@ -0,0 +1,18 @@
|
||||
/datum/preference/toggle/enable_screentips
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "screentip_pref"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/enable_screentips/apply_to_client(client/client, value)
|
||||
client.mob?.hud_used?.screentips_enabled = value
|
||||
|
||||
/datum/preference/color/screentip_color
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "screentip_color"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/color/screentip_color/apply_to_client(client/client, value)
|
||||
client.mob?.hud_used?.screentip_color = value
|
||||
|
||||
/datum/preference/color/screentip_color/create_default_value()
|
||||
return "#ffd391"
|
||||
@@ -0,0 +1,27 @@
|
||||
/// Which department to put security officers in, when the config is enabled
|
||||
/datum/preference/choiced/security_department
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
can_randomize = FALSE
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "prefered_security_department"
|
||||
|
||||
// This is what that #warn wants you to remove :)
|
||||
/datum/preference/choiced/security_department/deserialize(input, datum/preferences/preferences)
|
||||
if (!(input in GLOB.security_depts_prefs))
|
||||
return SEC_DEPT_NONE
|
||||
return ..(input, preferences)
|
||||
|
||||
/datum/preference/choiced/security_department/init_possible_values()
|
||||
return GLOB.security_depts_prefs
|
||||
|
||||
/datum/preference/choiced/security_department/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
|
||||
/datum/preference/choiced/security_department/create_default_value()
|
||||
return SEC_DEPT_NONE
|
||||
|
||||
/datum/preference/choiced/security_department/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return !CONFIG_GET(flag/sec_start_brig)
|
||||
@@ -0,0 +1,37 @@
|
||||
/datum/preference/choiced/skin_tone
|
||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "skin_tone"
|
||||
|
||||
/datum/preference/choiced/skin_tone/init_possible_values()
|
||||
return GLOB.skin_tones
|
||||
|
||||
/datum/preference/choiced/skin_tone/compile_constant_data()
|
||||
var/list/data = ..()
|
||||
|
||||
data[CHOICED_PREFERENCE_DISPLAY_NAMES] = GLOB.skin_tone_names
|
||||
|
||||
var/list/to_hex = list()
|
||||
for (var/choice in get_choices())
|
||||
var/hex_value = skintone2hex(choice)
|
||||
var/list/rgb = hex2rgb("#[hex_value]")
|
||||
var/list/hsl = rgb2hsl(rgb[1], rgb[2], rgb[3])
|
||||
|
||||
to_hex[choice] = list(
|
||||
"lightness" = hsl[3],
|
||||
"value" = hex_value,
|
||||
)
|
||||
|
||||
data["to_hex"] = to_hex
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference/choiced/skin_tone/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.skin_tone = value
|
||||
|
||||
/datum/preference/choiced/skin_tone/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
var/datum/species/species_type = preferences.read_preference(/datum/preference/choiced/species)
|
||||
return initial(species_type.use_skintones)
|
||||
@@ -0,0 +1,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
|
||||
*/
|
||||
@@ -0,0 +1,20 @@
|
||||
/datum/preference/toggle/tgui_fancy
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "tgui_fancy"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/tgui_fancy/apply_to_client(client/client, value)
|
||||
for (var/datum/tgui/tgui as anything in client.mob?.tgui_open_uis)
|
||||
// Force it to reload either way
|
||||
tgui.update_static_data(client.mob)
|
||||
|
||||
/datum/preference/toggle/tgui_lock
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "tgui_lock"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
default_value = FALSE
|
||||
|
||||
/datum/preference/toggle/tgui_lock/apply_to_client(client/client, value)
|
||||
for (var/datum/tgui/tgui as anything in client.mob?.tgui_open_uis)
|
||||
// Force it to reload either way
|
||||
tgui.update_static_data(client.mob)
|
||||
@@ -0,0 +1,116 @@
|
||||
/// Handle the migrations necessary from pre-tgui prefs to post-tgui prefs
|
||||
/datum/preferences/proc/migrate_preferences_to_tgui_prefs_menu()
|
||||
migrate_antagonists()
|
||||
migrate_key_bindings()
|
||||
|
||||
/// Handle the migrations necessary from pre-tgui prefs to post-tgui prefs, for characters
|
||||
/datum/preferences/proc/migrate_character_to_tgui_prefs_menu()
|
||||
migrate_randomization()
|
||||
|
||||
// Key bindings used to be "key" -> list("action"),
|
||||
// such as "X" -> list("swap_hands").
|
||||
// This made it impossible to determine any order, meaning placing a new
|
||||
// hotkey would produce non-deterministic order.
|
||||
// tgui prefs menu moves this over to "swap_hands" -> list("X").
|
||||
/datum/preferences/proc/migrate_key_bindings()
|
||||
var/new_key_bindings = list()
|
||||
|
||||
for (var/unbound_hotkey in key_bindings["Unbound"])
|
||||
new_key_bindings[unbound_hotkey] = list()
|
||||
|
||||
for (var/hotkey in key_bindings)
|
||||
if (hotkey == "Unbound")
|
||||
continue
|
||||
|
||||
for (var/keybind in key_bindings[hotkey])
|
||||
if (keybind in new_key_bindings)
|
||||
new_key_bindings[keybind] |= hotkey
|
||||
else
|
||||
new_key_bindings[keybind] = list(hotkey)
|
||||
|
||||
key_bindings = new_key_bindings
|
||||
|
||||
// Before tgui preferences menu, "traitor" would handle both roundstart, midround, and latejoin.
|
||||
// These were split apart.
|
||||
/datum/preferences/proc/migrate_antagonists()
|
||||
migrate_antagonist(ROLE_HERETIC, list(ROLE_HERETIC_SMUGGLER))
|
||||
migrate_antagonist(ROLE_MALF, list(ROLE_MALF_MIDROUND))
|
||||
migrate_antagonist(ROLE_OPERATIVE, list(ROLE_OPERATIVE_MIDROUND, ROLE_LONE_OPERATIVE))
|
||||
migrate_antagonist(ROLE_REV_HEAD, list(ROLE_PROVOCATEUR))
|
||||
migrate_antagonist(ROLE_TRAITOR, list(ROLE_SYNDICATE_INFILTRATOR, ROLE_SLEEPER_AGENT))
|
||||
migrate_antagonist(ROLE_WIZARD, list(ROLE_WIZARD_MIDROUND))
|
||||
|
||||
// "Familes [sic] Antagonists" was the old name of the catch-all.
|
||||
migrate_antagonist("Familes Antagonists", list(ROLE_FAMILIES, ROLE_FAMILY_HEAD_ASPIRANT))
|
||||
|
||||
/datum/preferences/proc/migrate_antagonist(will_exist, list/to_add)
|
||||
if (will_exist in be_special)
|
||||
for (var/add in to_add)
|
||||
be_special += add
|
||||
|
||||
// Randomization used to be an assoc list of fields to TRUE.
|
||||
// Antagonist randomization was not even available to all options.
|
||||
// tgui prefs menu changes from list("random_socks" = TRUE, "random_name_antag" = TRUE)
|
||||
// to list("socks" = "enabled", "name" = "antag")
|
||||
// as well as removing anything that was set to FALSE, as this can be extrapolated.
|
||||
/datum/preferences/proc/migrate_randomization()
|
||||
var/static/list/random_settings = list(
|
||||
"random_age" = "age",
|
||||
"random_backpack" = "backpack",
|
||||
"random_eye_color" = "eye_color",
|
||||
"random_facial_hair_color" = "facial_hair_color",
|
||||
"random_facial_hairstyle" = "facial_hairstyle",
|
||||
"random_gender" = "gender",
|
||||
"random_hair_color" = "hair_color",
|
||||
"random_hairstyle" = "hairstyle",
|
||||
"random_jumpsuit_style" = "jumpsuit_style",
|
||||
"random_skin_tone" = "skin_tone",
|
||||
"random_socks" = "socks",
|
||||
"random_species" = "species",
|
||||
"random_undershirt" = "undershirt",
|
||||
"random_underwear" = "underwear",
|
||||
"random_underwear_color" = "underwear_color",
|
||||
)
|
||||
|
||||
var/static/list/random_antag_settings = list(
|
||||
"random_age_antag" = "age",
|
||||
"random_gender_antag" = "gender",
|
||||
"random_name_antag" = "name",
|
||||
)
|
||||
|
||||
var/list/new_randomise = list()
|
||||
|
||||
for (var/old_setting in random_settings)
|
||||
if (randomise[old_setting])
|
||||
new_randomise[random_settings[old_setting]] = RANDOM_ENABLED
|
||||
|
||||
for (var/old_antag_setting in random_antag_settings)
|
||||
if (randomise[old_antag_setting])
|
||||
new_randomise[random_settings[old_antag_setting]] = RANDOM_ANTAG_ONLY
|
||||
|
||||
migrate_randomization_to_new_pref(
|
||||
/datum/preference/choiced/random_body,
|
||||
"random_body",
|
||||
"random_body_antag",
|
||||
)
|
||||
|
||||
migrate_randomization_to_new_pref(
|
||||
/datum/preference/choiced/random_name,
|
||||
"random_name",
|
||||
"random_name_antag",
|
||||
)
|
||||
|
||||
if (randomise["random_hardcore"])
|
||||
write_preference(GLOB.preference_entries[/datum/preference/toggle/random_hardcore], TRUE)
|
||||
|
||||
randomise = new_randomise
|
||||
|
||||
/datum/preferences/proc/migrate_randomization_to_new_pref(
|
||||
preference_type,
|
||||
key,
|
||||
key_antag,
|
||||
)
|
||||
if (randomise[key_antag])
|
||||
write_preference(GLOB.preference_entries[preference_type], RANDOM_ANTAG_ONLY)
|
||||
else if (randomise[key])
|
||||
write_preference(GLOB.preference_entries[preference_type], RANDOM_ENABLED)
|
||||
@@ -0,0 +1,15 @@
|
||||
/datum/preference/numeric/tooltip_delay
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "tip_delay"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
minimum = 0
|
||||
maximum = 5000
|
||||
|
||||
/datum/preference/numeric/tooltip_delay/create_default_value()
|
||||
return 500
|
||||
|
||||
/datum/preference/toggle/enable_tooltips
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "enable_tips"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
@@ -0,0 +1,26 @@
|
||||
/// UI style preference
|
||||
/datum/preference/choiced/ui_style
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
savefile_key = "UI_style"
|
||||
should_generate_icons = TRUE
|
||||
|
||||
/datum/preference/choiced/ui_style/init_possible_values()
|
||||
var/list/values = list()
|
||||
|
||||
for (var/style in GLOB.available_ui_styles)
|
||||
var/icon/icons = GLOB.available_ui_styles[style]
|
||||
|
||||
var/icon/icon = icon(icons, "hand_r")
|
||||
icon.Crop(1, 1, world.icon_size * 2, world.icon_size)
|
||||
icon.Blend(icon(icons, "hand_l"), ICON_OVERLAY, world.icon_size)
|
||||
|
||||
values[style] = icon
|
||||
|
||||
return values
|
||||
|
||||
/datum/preference/choiced/ui_style/create_default_value()
|
||||
return GLOB.available_ui_styles[1]
|
||||
|
||||
/datum/preference/choiced/ui_style/apply_to_client(client/client, value)
|
||||
client.mob?.hud_used?.update_ui_style(ui_style2icon(value))
|
||||
@@ -0,0 +1,15 @@
|
||||
/datum/preference/color_legacy/underwear_color
|
||||
savefile_key = "underwear_color"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
category = PREFERENCE_CATEGORY_SUPPLEMENTAL_FEATURES
|
||||
|
||||
/datum/preference/color_legacy/underwear_color/apply_to_human(mob/living/carbon/human/target, value)
|
||||
target.underwear_color = value
|
||||
|
||||
/datum/preference/color_legacy/underwear_color/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
var/species_type = preferences.read_preference(/datum/preference/choiced/species)
|
||||
var/datum/species/species = new species_type
|
||||
return !(NO_UNDERWEAR in species.species_traits)
|
||||
@@ -0,0 +1,26 @@
|
||||
/datum/preference/choiced/uplink_location
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "uplink_loc"
|
||||
can_randomize = FALSE
|
||||
|
||||
/datum/preference/choiced/uplink_location/init_possible_values()
|
||||
return list(UPLINK_PDA, UPLINK_RADIO, UPLINK_PEN, UPLINK_IMPLANT)
|
||||
|
||||
/datum/preference/choiced/uplink_location/compile_constant_data()
|
||||
var/list/data = ..()
|
||||
|
||||
data[CHOICED_PREFERENCE_DISPLAY_NAMES] = list(
|
||||
UPLINK_PDA = "PDA",
|
||||
UPLINK_RADIO = "Radio",
|
||||
UPLINK_PEN = "Pen",
|
||||
UPLINK_IMPLANT = "Implant ([UPLINK_IMPLANT_TELECRYSTAL_COST]TC)",
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
/datum/preference/choiced/uplink_location/create_default_value()
|
||||
return UPLINK_PDA
|
||||
|
||||
/datum/preference/choiced/uplink_location/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
@@ -0,0 +1,7 @@
|
||||
/datum/preference/toggle/widescreen
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "widescreenpref"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/widescreen/apply_to_client(client/client, value)
|
||||
client.view_size?.setDefault(getScreenSize(value))
|
||||
@@ -0,0 +1,5 @@
|
||||
/// Enables flashing the window in your task tray for important events
|
||||
/datum/preference/toggle/window_flashing
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "windowflashing"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
@@ -0,0 +1,26 @@
|
||||
/datum/verbs/menu/Preferences/verb/open_character_preferences()
|
||||
set category = "OOC"
|
||||
set name = "Open Character Preferences"
|
||||
set desc = "Open Character Preferences"
|
||||
|
||||
var/datum/preferences/preferences = usr?.client?.prefs
|
||||
if (!preferences)
|
||||
return
|
||||
|
||||
preferences.current_window = PREFERENCE_TAB_CHARACTER_PREFERENCES
|
||||
preferences.update_static_data(usr)
|
||||
preferences.ui_interact(usr)
|
||||
|
||||
/datum/verbs/menu/Preferences/verb/open_game_preferences()
|
||||
set category = "OOC"
|
||||
set name = "Open Game Preferences"
|
||||
set desc = "Open Game Preferences"
|
||||
|
||||
var/datum/preferences/preferences = usr?.client?.prefs
|
||||
if (!preferences)
|
||||
return
|
||||
|
||||
preferences.current_window = PREFERENCE_TAB_GAME_PREFERENCES
|
||||
preferences.update_static_data(usr)
|
||||
preferences.ui_interact(usr)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/client/verb/toggle_tips()
|
||||
set name = "Toggle Examine Tooltips"
|
||||
set desc = "Toggles examine hover-over tooltips"
|
||||
set category = "Preferences"
|
||||
|
||||
prefs.enable_tips = !prefs.enable_tips
|
||||
prefs.save_preferences()
|
||||
to_chat(usr, span_danger("Examine tooltips [prefs.enable_tips ? "en" : "dis"]abled."))
|
||||
|
||||
/client/verb/change_tip_delay()
|
||||
set name = "Set Examine Tooltip Delay"
|
||||
set desc = "Sets the delay in milliseconds before examine tooltips appear"
|
||||
set category = "Preferences"
|
||||
|
||||
var/indelay = stripped_input(usr, "Enter the tooltip delay in milliseconds (default: 500)", "Enter tooltip delay", "", 10)
|
||||
indelay = text2num(indelay)
|
||||
if(usr)//is this what you mean?
|
||||
prefs.tip_delay = indelay
|
||||
prefs.save_preferences()
|
||||
to_chat(usr, span_danger("Tooltip delay set to [indelay] milliseconds."))
|
||||
@@ -58,7 +58,7 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
|
||||
var/keyname = key
|
||||
if(prefs.unlock_content)
|
||||
if(prefs.toggles & MEMBER_PUBLIC)
|
||||
keyname = "<font color='[prefs.ooccolor ? prefs.ooccolor : GLOB.normal_ooc_colour]'>[icon2html('icons/member_content.dmi', world, "blag")][keyname]</font>"
|
||||
keyname = "<font color='[prefs.read_preference(/datum/preference/color/ooc_color) || GLOB.normal_ooc_colour]'>[icon2html('icons/member_content.dmi', world, "blag")][keyname]</font>"
|
||||
if(prefs.hearted)
|
||||
var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat)
|
||||
keyname = "[sheet.icon_tag("emoji-heart")][keyname]"
|
||||
@@ -74,7 +74,8 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
|
||||
if(holder)
|
||||
if(!holder.fakekey || receiver.holder)
|
||||
if(check_rights_for(src, R_ADMIN))
|
||||
to_chat(receiver, span_adminooc("[CONFIG_GET(flag/allow_admin_ooccolor) && prefs.ooccolor ? "<font color=[prefs.ooccolor]>" :"" ][span_prefix("OOC:")] <EM>[keyname][holder.fakekey ? "/([holder.fakekey])" : ""]:</EM> <span class='message linkify'>[msg]</span>"), avoid_highlighting = avoid_highlight)
|
||||
var/ooc_color = prefs.read_preference(/datum/preference/color/ooc_color)
|
||||
to_chat(receiver, span_adminooc("[CONFIG_GET(flag/allow_admin_ooccolor) && ooc_color ? "<font color=[ooc_color]>" :"" ][span_prefix("OOC:")] <EM>[keyname][holder.fakekey ? "/([holder.fakekey])" : ""]:</EM> <span class='message linkify'>[msg]</span>"), avoid_highlighting = avoid_highlight)
|
||||
else
|
||||
to_chat(receiver, span_adminobserverooc(span_prefix("OOC:</span> <EM>[keyname][holder.fakekey ? "/([holder.fakekey])" : ""]:</EM> <span class='message linkify'>[msg]")), avoid_highlighting = avoid_highlight)
|
||||
else
|
||||
@@ -123,7 +124,7 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
|
||||
message_admins("[usr.key] has attempted to use the Set Player OOC Color verb!")
|
||||
log_admin("[key_name(usr)] tried to set player ooc color without authorization.")
|
||||
return
|
||||
var/new_color = sanitize_ooccolor(newColor)
|
||||
var/new_color = sanitize_color(newColor)
|
||||
message_admins("[key_name_admin(usr)] has set the players' ooc color to [new_color].")
|
||||
log_admin("[key_name_admin(usr)] has set the player ooc color to [new_color].")
|
||||
GLOB.OOC_COLOR = new_color
|
||||
@@ -145,36 +146,6 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
|
||||
log_admin("[key_name_admin(usr)] has reset player ooc color.")
|
||||
GLOB.OOC_COLOR = null
|
||||
|
||||
|
||||
/client/verb/colorooc()
|
||||
set name = "Set Your OOC Color"
|
||||
set category = "Preferences"
|
||||
|
||||
if(!holder || !check_rights_for(src, R_ADMIN))
|
||||
if(!is_content_unlocked())
|
||||
return
|
||||
|
||||
var/new_ooccolor = input(src, "Please select your OOC color.", "OOC color", prefs.ooccolor) as color|null
|
||||
if(isnull(new_ooccolor))
|
||||
return
|
||||
new_ooccolor = sanitize_ooccolor(new_ooccolor)
|
||||
prefs.ooccolor = new_ooccolor
|
||||
prefs.save_preferences()
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Set OOC Color") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
|
||||
/client/verb/resetcolorooc()
|
||||
set name = "Reset Your OOC Color"
|
||||
set desc = "Returns your OOC Color to default"
|
||||
set category = "Preferences"
|
||||
|
||||
if(!holder || !check_rights_for(src, R_ADMIN))
|
||||
if(!is_content_unlocked())
|
||||
return
|
||||
|
||||
prefs.ooccolor = initial(prefs.ooccolor)
|
||||
prefs.save_preferences()
|
||||
|
||||
//Checks admin notice
|
||||
/client/verb/admin_notice()
|
||||
set name = "Adminnotice"
|
||||
|
||||
Reference in New Issue
Block a user