mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-11 10:11:09 +00:00
Adds a little button to quirks that allows for relatively easy customization (#79251)
## About The Pull Request Title. Adds a little cog button that expands a popper with a preference list of relevant preferences. https://github.com/tgstation/tgstation/assets/59709059/5718ad5d-fadb-489f-9a31-9e7173c6f35a ## Why It's Good For The Game Customizable quirks are cool. Having a proper framework for customizable quirks is even cooler. Good UX is even COOLER. ## Changelog 🆑 code: Quirks are now customizable on the quirks page instead of on the character prefs page /🆑 --------- Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com>
This commit is contained in:
@@ -131,6 +131,9 @@
|
|||||||
/// such as hair color being affixed to hair.
|
/// such as hair color being affixed to hair.
|
||||||
#define PREFERENCE_CATEGORY_SUPPLEMENTAL_FEATURES "supplemental_features"
|
#define PREFERENCE_CATEGORY_SUPPLEMENTAL_FEATURES "supplemental_features"
|
||||||
|
|
||||||
|
/// These preferences will not be rendered on the preferences page, and are practically invisible unless specifically rendered. Used for quirks, currently.
|
||||||
|
#define PREFERENCE_CATEGORY_MANUALLY_RENDERED "manually_rendered_features"
|
||||||
|
|
||||||
// Playtime is tracked in minutes
|
// Playtime is tracked in minutes
|
||||||
/// The time needed to unlock hardcore random mode in preferences
|
/// The time needed to unlock hardcore random mode in preferences
|
||||||
#define PLAYTIME_HARDCORE_RANDOM 120 // 2 hours
|
#define PLAYTIME_HARDCORE_RANDOM 120 // 2 hours
|
||||||
|
|||||||
72
code/datums/quirks/_quirk_constant_data.dm
Normal file
72
code/datums/quirks/_quirk_constant_data.dm
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
GLOBAL_LIST_INIT_TYPED(all_quirk_constant_data, /datum/quirk_constant_data, generate_quirk_constant_data())
|
||||||
|
|
||||||
|
/// Constructs [GLOB.all_quirk_constant_data] by iterating through a typecache of pregen data, ignoring abstract types, and instantiating the rest.
|
||||||
|
/proc/generate_quirk_constant_data()
|
||||||
|
RETURN_TYPE(/list/datum/quirk_constant_data)
|
||||||
|
|
||||||
|
var/list/datum/quirk_constant_data/all_constant_data = list()
|
||||||
|
|
||||||
|
for (var/datum/quirk_constant_data/iterated_path as anything in typecacheof(path = /datum/quirk_constant_data, ignore_root_path = TRUE))
|
||||||
|
if (initial(iterated_path.abstract_type) == iterated_path)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (!isnull(all_constant_data[initial(iterated_path.associated_typepath)]))
|
||||||
|
stack_trace("pre-existing pregen data for [initial(iterated_path.associated_typepath)] when [iterated_path] was being considered: [all_constant_data[initial(iterated_path.associated_typepath)]]. \
|
||||||
|
this is definitely a bug, and is probably because one of the two pregen data have the wrong quirk typepath defined. [iterated_path] will not be instantiated")
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
var/datum/quirk_constant_data/pregen_data = new iterated_path
|
||||||
|
all_constant_data[pregen_data.associated_typepath] = pregen_data
|
||||||
|
|
||||||
|
return all_constant_data
|
||||||
|
|
||||||
|
/// A singleton datum representing constant data and procs used by quirks.
|
||||||
|
/datum/quirk_constant_data
|
||||||
|
/// Abstract in OOP terms. If this is our type, we will not be instantiated.
|
||||||
|
var/abstract_type = /datum/quirk_constant_data
|
||||||
|
|
||||||
|
/// The typepath of the quirk we will be associated with in the global list. This is what we represent.
|
||||||
|
var/datum/quirk/associated_typepath
|
||||||
|
|
||||||
|
/// A lazylist of preference datum typepaths. Any character pref put in here will be rendered in the quirks page under a dropdown.
|
||||||
|
var/list/datum/preference/customization_options
|
||||||
|
|
||||||
|
/datum/quirk_constant_data/New()
|
||||||
|
. = ..()
|
||||||
|
|
||||||
|
ASSERT(abstract_type != type && !isnull(associated_typepath), "associated_typepath null - please set it! occured on: [src.type]")
|
||||||
|
|
||||||
|
/// Returns a list of savefile_keys derived from the preference typepaths in [customization_options]. Used in quirks middleware to supply the preferences to render.
|
||||||
|
/datum/quirk_constant_data/proc/get_customization_data()
|
||||||
|
RETURN_TYPE(/list)
|
||||||
|
|
||||||
|
var/list/customization_data = list()
|
||||||
|
|
||||||
|
for (var/datum/preference/pref_type as anything in customization_options)
|
||||||
|
var/datum/preference/pref_instance = GLOB.preference_entries[pref_type]
|
||||||
|
if (isnull(pref_instance))
|
||||||
|
stack_trace("get_customization_data was called before instantiation of [pref_type]!")
|
||||||
|
continue // just in case its a fluke and its only this one thats not instantiated, we'll check the other pref entries
|
||||||
|
|
||||||
|
customization_data += pref_instance.savefile_key
|
||||||
|
|
||||||
|
return customization_data
|
||||||
|
|
||||||
|
/// Is this quirk customizable? If true, a button will appear within the quirk's description box in the quirks page, and upon clicking it,
|
||||||
|
/// will open a customization menu for the quirk.
|
||||||
|
/datum/quirk_constant_data/proc/is_customizable()
|
||||||
|
return LAZYLEN(customization_options) > 0
|
||||||
|
|
||||||
|
/datum/quirk_constant_data/Destroy(force, ...)
|
||||||
|
var/error_message = "[src], a singleton quirk constant data instance, was destroyed! This should not happen!"
|
||||||
|
if (force)
|
||||||
|
error_message += " NOTE: This Destroy() was called with force == TRUE. This instance will be deleted and replaced with a new one."
|
||||||
|
stack_trace(error_message)
|
||||||
|
|
||||||
|
if (!force)
|
||||||
|
return QDEL_HINT_LETMELIVE
|
||||||
|
|
||||||
|
. = ..()
|
||||||
|
|
||||||
|
GLOB.all_quirk_constant_data[associated_typepath] = new src.type //recover
|
||||||
@@ -25,6 +25,10 @@ GLOBAL_LIST_INIT(possible_food_allergies, list(
|
|||||||
/// Footype flags that will trigger the allergy
|
/// Footype flags that will trigger the allergy
|
||||||
var/target_foodtypes = NONE
|
var/target_foodtypes = NONE
|
||||||
|
|
||||||
|
/datum/quirk_constant_data/food_allergy
|
||||||
|
associated_typepath = /datum/quirk/item_quirk/food_allergic
|
||||||
|
customization_options = list(/datum/preference/choiced/food_allergy)
|
||||||
|
|
||||||
/datum/quirk/item_quirk/food_allergic/add(client/client_source)
|
/datum/quirk/item_quirk/food_allergic/add(client/client_source)
|
||||||
if(target_foodtypes != NONE) // Already set, don't care
|
if(target_foodtypes != NONE) // Already set, don't care
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_CHANGES_APPEARANCE
|
quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_CHANGES_APPEARANCE
|
||||||
mail_goodies = list(/obj/item/clothing/glasses/regular) // extra pair if orginal one gets broken by somebody mean
|
mail_goodies = list(/obj/item/clothing/glasses/regular) // extra pair if orginal one gets broken by somebody mean
|
||||||
|
|
||||||
|
/datum/quirk_constant_data/nearsighted
|
||||||
|
associated_typepath = /datum/quirk/item_quirk/nearsighted
|
||||||
|
customization_options = list(/datum/preference/choiced/glasses)
|
||||||
|
|
||||||
/datum/quirk/item_quirk/nearsighted/add_unique(client/client_source)
|
/datum/quirk/item_quirk/nearsighted/add_unique(client/client_source)
|
||||||
var/glasses_name = client_source?.prefs.read_preference(/datum/preference/choiced/glasses) || "Regular"
|
var/glasses_name = client_source?.prefs.read_preference(/datum/preference/choiced/glasses) || "Regular"
|
||||||
var/obj/item/clothing/glasses/glasses_type
|
var/obj/item/clothing/glasses/glasses_type
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
/// the original limb from before the prosthetic was applied
|
/// the original limb from before the prosthetic was applied
|
||||||
var/obj/item/bodypart/old_limb
|
var/obj/item/bodypart/old_limb
|
||||||
|
|
||||||
|
/datum/quirk_constant_data/prosthetic_limb
|
||||||
|
associated_typepath = /datum/quirk/prosthetic_limb
|
||||||
|
customization_options = list(/datum/preference/choiced/prosthetic)
|
||||||
|
|
||||||
/datum/quirk/prosthetic_limb/add_unique(client/client_source)
|
/datum/quirk/prosthetic_limb/add_unique(client/client_source)
|
||||||
var/limb_type = GLOB.limb_choice[client_source?.prefs?.read_preference(/datum/preference/choiced/prosthetic)]
|
var/limb_type = GLOB.limb_choice[client_source?.prefs?.read_preference(/datum/preference/choiced/prosthetic)]
|
||||||
if(isnull(limb_type)) //Client gone or they chose a random prosthetic
|
if(isnull(limb_type)) //Client gone or they chose a random prosthetic
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
medical_record_text = "Patient has an irrational fear of something."
|
medical_record_text = "Patient has an irrational fear of something."
|
||||||
mail_goodies = list(/obj/item/clothing/glasses/blindfold, /obj/item/storage/pill_bottle/psicodine)
|
mail_goodies = list(/obj/item/clothing/glasses/blindfold, /obj/item/storage/pill_bottle/psicodine)
|
||||||
|
|
||||||
|
/datum/quirk_constant_data/phobia
|
||||||
|
associated_typepath = /datum/quirk/phobia
|
||||||
|
customization_options = list(/datum/preference/choiced/phobia)
|
||||||
|
|
||||||
// Phobia will follow you between transfers
|
// Phobia will follow you between transfers
|
||||||
/datum/quirk/phobia/add(client/client_source)
|
/datum/quirk/phobia/add(client/client_source)
|
||||||
var/phobia = client_source?.prefs.read_preference(/datum/preference/choiced/phobia)
|
var/phobia = client_source?.prefs.read_preference(/datum/preference/choiced/phobia)
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
medical_record_text = "Patient speaks multiple languages."
|
medical_record_text = "Patient speaks multiple languages."
|
||||||
mail_goodies = list(/obj/item/taperecorder, /obj/item/clothing/head/frenchberet, /obj/item/clothing/mask/fakemoustache/italian)
|
mail_goodies = list(/obj/item/taperecorder, /obj/item/clothing/head/frenchberet, /obj/item/clothing/mask/fakemoustache/italian)
|
||||||
|
|
||||||
|
/datum/quirk_constant_data/bilingual
|
||||||
|
associated_typepath = /datum/quirk/bilingual
|
||||||
|
customization_options = list(/datum/preference/choiced/language)
|
||||||
|
|
||||||
/datum/quirk/bilingual/add_unique(client/client_source)
|
/datum/quirk/bilingual/add_unique(client/client_source)
|
||||||
var/wanted_language = client_source?.prefs.read_preference(/datum/preference/choiced/language)
|
var/wanted_language = client_source?.prefs.read_preference(/datum/preference/choiced/language)
|
||||||
var/datum/language/language_type
|
var/datum/language/language_type
|
||||||
|
|||||||
@@ -14,6 +14,10 @@
|
|||||||
/obj/item/canvas/twentythree_twentythree
|
/obj/item/canvas/twentythree_twentythree
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/datum/quirk_constant_data/tagger
|
||||||
|
associated_typepath = /datum/quirk/item_quirk/tagger
|
||||||
|
customization_options = list(/datum/preference/color/paint_color)
|
||||||
|
|
||||||
/datum/quirk/item_quirk/tagger/add_unique(client/client_source)
|
/datum/quirk/item_quirk/tagger/add_unique(client/client_source)
|
||||||
var/obj/item/toy/crayon/spraycan/can = new
|
var/obj/item/toy/crayon/spraycan/can = new
|
||||||
can.set_painting_tool_color(client_source?.prefs.read_preference(/datum/preference/color/paint_color))
|
can.set_painting_tool_color(client_source?.prefs.read_preference(/datum/preference/color/paint_color))
|
||||||
|
|||||||
@@ -314,13 +314,13 @@ GLOBAL_LIST_EMPTY(preferences_datums)
|
|||||||
if (!preference.is_accessible(src))
|
if (!preference.is_accessible(src))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
LAZYINITLIST(preferences[preference.category])
|
|
||||||
|
|
||||||
var/value = read_preference(preference.type)
|
var/value = read_preference(preference.type)
|
||||||
var/data = preference.compile_ui_data(user, value)
|
var/data = preference.compile_ui_data(user, value)
|
||||||
|
|
||||||
|
LAZYINITLIST(preferences[preference.category])
|
||||||
preferences[preference.category][preference.savefile_key] = data
|
preferences[preference.category][preference.savefile_key] = data
|
||||||
|
|
||||||
|
|
||||||
for (var/datum/preference_middleware/preference_middleware as anything in middleware)
|
for (var/datum/preference_middleware/preference_middleware as anything in middleware)
|
||||||
var/list/append_character_preferences = preference_middleware.get_character_preferences(user)
|
var/list/append_character_preferences = preference_middleware.get_character_preferences(user)
|
||||||
if (isnull(append_character_preferences))
|
if (isnull(append_character_preferences))
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/datum/preference/choiced/food_allergy
|
/datum/preference/choiced/food_allergy
|
||||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
|
||||||
savefile_key = "food_allergy"
|
savefile_key = "food_allergy"
|
||||||
savefile_identifier = PREFERENCE_CHARACTER
|
savefile_identifier = PREFERENCE_CHARACTER
|
||||||
can_randomize = FALSE
|
can_randomize = FALSE
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/datum/preference/choiced/glasses
|
/datum/preference/choiced/glasses
|
||||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
|
||||||
savefile_key = "glasses"
|
savefile_key = "glasses"
|
||||||
savefile_identifier = PREFERENCE_CHARACTER
|
savefile_identifier = PREFERENCE_CHARACTER
|
||||||
should_generate_icons = TRUE
|
should_generate_icons = TRUE
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/datum/preference/choiced/language
|
/datum/preference/choiced/language
|
||||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
|
||||||
savefile_key = "language"
|
savefile_key = "language"
|
||||||
savefile_identifier = PREFERENCE_CHARACTER
|
savefile_identifier = PREFERENCE_CHARACTER
|
||||||
|
|
||||||
|
|||||||
@@ -33,11 +33,16 @@
|
|||||||
|
|
||||||
for (var/quirk_name in quirks)
|
for (var/quirk_name in quirks)
|
||||||
var/datum/quirk/quirk = quirks[quirk_name]
|
var/datum/quirk/quirk = quirks[quirk_name]
|
||||||
|
var/datum/quirk_constant_data/constant_data = GLOB.all_quirk_constant_data[quirk]
|
||||||
|
var/list/datum/preference/customization_options = constant_data?.get_customization_data()
|
||||||
|
|
||||||
quirk_info[sanitize_css_class_name(quirk_name)] = list(
|
quirk_info[sanitize_css_class_name(quirk_name)] = list(
|
||||||
"description" = initial(quirk.desc),
|
"description" = initial(quirk.desc),
|
||||||
"icon" = initial(quirk.icon),
|
"icon" = initial(quirk.icon),
|
||||||
"name" = quirk_name,
|
"name" = quirk_name,
|
||||||
"value" = initial(quirk.value),
|
"value" = initial(quirk.value),
|
||||||
|
"customizable" = constant_data?.is_customizable(),
|
||||||
|
"customization_options" = customization_options,
|
||||||
)
|
)
|
||||||
|
|
||||||
return list(
|
return list(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
/datum/preference/color/paint_color
|
/datum/preference/color/paint_color
|
||||||
savefile_key = "paint_color"
|
savefile_key = "paint_color"
|
||||||
savefile_identifier = PREFERENCE_CHARACTER
|
savefile_identifier = PREFERENCE_CHARACTER
|
||||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
|
||||||
|
|
||||||
/datum/preference/color/paint_color/is_accessible(datum/preferences/preferences)
|
/datum/preference/color/paint_color/is_accessible(datum/preferences/preferences)
|
||||||
if (!..(preferences))
|
if (!..(preferences))
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/datum/preference/choiced/phobia
|
/datum/preference/choiced/phobia
|
||||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
|
||||||
savefile_key = "phobia"
|
savefile_key = "phobia"
|
||||||
savefile_identifier = PREFERENCE_CHARACTER
|
savefile_identifier = PREFERENCE_CHARACTER
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/datum/preference/choiced/prosthetic
|
/datum/preference/choiced/prosthetic
|
||||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
|
||||||
savefile_key = "prosthetic"
|
savefile_key = "prosthetic"
|
||||||
savefile_identifier = PREFERENCE_CHARACTER
|
savefile_identifier = PREFERENCE_CHARACTER
|
||||||
|
|
||||||
|
|||||||
@@ -1583,6 +1583,7 @@
|
|||||||
#include "code\datums\proximity_monitor\fields\projectile_dampener.dm"
|
#include "code\datums\proximity_monitor\fields\projectile_dampener.dm"
|
||||||
#include "code\datums\proximity_monitor\fields\timestop.dm"
|
#include "code\datums\proximity_monitor\fields\timestop.dm"
|
||||||
#include "code\datums\quirks\_quirk.dm"
|
#include "code\datums\quirks\_quirk.dm"
|
||||||
|
#include "code\datums\quirks\_quirk_constant_data.dm"
|
||||||
#include "code\datums\quirks\negative_quirks\allergic.dm"
|
#include "code\datums\quirks\negative_quirks\allergic.dm"
|
||||||
#include "code\datums\quirks\negative_quirks\bad_back.dm"
|
#include "code\datums\quirks\negative_quirks\bad_back.dm"
|
||||||
#include "code\datums\quirks\negative_quirks\bad_touch.dm"
|
#include "code\datums\quirks\negative_quirks\bad_touch.dm"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { classes } from 'common/react';
|
import { classes } from 'common/react';
|
||||||
import { sendAct, useBackend, useLocalState } from '../../backend';
|
import { sendAct, useBackend, useLocalState } from '../../backend';
|
||||||
import { Autofocus, Box, Button, Flex, LabeledList, Popper, Stack, TrackOutsideClicks } from '../../components';
|
import { Autofocus, Box, Button, Flex, LabeledList, Popper, Stack, TrackOutsideClicks } from '../../components';
|
||||||
import { createSetPreference, PreferencesMenuData, RandomSetting } from './data';
|
import { createSetPreference, PreferencesMenuData, RandomSetting, ServerData } from './data';
|
||||||
import { CharacterPreview } from '../common/CharacterPreview';
|
import { CharacterPreview } from '../common/CharacterPreview';
|
||||||
import { RandomizationButton } from './RandomizationButton';
|
import { RandomizationButton } from './RandomizationButton';
|
||||||
import { ServerPreferencesFetcher } from './ServerPreferencesFetcher';
|
import { ServerPreferencesFetcher } from './ServerPreferencesFetcher';
|
||||||
@@ -345,10 +345,11 @@ const sortPreferences = sortBy<[string, unknown]>(([featureId, _]) => {
|
|||||||
return feature?.name;
|
return feature?.name;
|
||||||
});
|
});
|
||||||
|
|
||||||
const PreferenceList = (props: {
|
export const PreferenceList = (props: {
|
||||||
act: typeof sendAct;
|
act: typeof sendAct;
|
||||||
preferences: Record<string, unknown>;
|
preferences: Record<string, unknown>;
|
||||||
randomizations: Record<string, RandomSetting>;
|
randomizations: Record<string, RandomSetting>;
|
||||||
|
maxHeight: string;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Stack.Item
|
<Stack.Item
|
||||||
@@ -359,7 +360,8 @@ const PreferenceList = (props: {
|
|||||||
padding: '4px',
|
padding: '4px',
|
||||||
}}
|
}}
|
||||||
overflowX="hidden"
|
overflowX="hidden"
|
||||||
overflowY="scroll">
|
overflowY="auto"
|
||||||
|
maxHeight={props.maxHeight}>
|
||||||
<LabeledList>
|
<LabeledList>
|
||||||
{sortPreferences(Object.entries(props.preferences)).map(
|
{sortPreferences(Object.entries(props.preferences)).map(
|
||||||
([featureId, value]) => {
|
([featureId, value]) => {
|
||||||
@@ -408,6 +410,37 @@ const PreferenceList = (props: {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getRandomization = (
|
||||||
|
preferences: Record<string, unknown>,
|
||||||
|
serverData: ServerData | undefined,
|
||||||
|
randomBodyEnabled: boolean,
|
||||||
|
context
|
||||||
|
): Record<string, RandomSetting> => {
|
||||||
|
if (!serverData) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = useBackend<PreferencesMenuData>(context);
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
filterMap(Object.keys(preferences), (preferenceKey) => {
|
||||||
|
if (serverData.random.randomizable.indexOf(preferenceKey) === -1) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!randomBodyEnabled) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
preferenceKey,
|
||||||
|
data.character_preferences.randomization[preferenceKey] ||
|
||||||
|
RandomSetting.Disabled,
|
||||||
|
];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const MainPage = (
|
export const MainPage = (
|
||||||
props: {
|
props: {
|
||||||
openSpecies: () => void;
|
openSpecies: () => void;
|
||||||
@@ -454,36 +487,11 @@ export const MainPage = (
|
|||||||
data.character_preferences.non_contextual.random_body !==
|
data.character_preferences.non_contextual.random_body !==
|
||||||
RandomSetting.Disabled || randomToggleEnabled;
|
RandomSetting.Disabled || randomToggleEnabled;
|
||||||
|
|
||||||
const getRandomization = (
|
|
||||||
preferences: Record<string, unknown>
|
|
||||||
): Record<string, RandomSetting> => {
|
|
||||||
if (!serverData) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.fromEntries(
|
|
||||||
filterMap(Object.keys(preferences), (preferenceKey) => {
|
|
||||||
if (
|
|
||||||
serverData.random.randomizable.indexOf(preferenceKey) === -1
|
|
||||||
) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!randomBodyEnabled) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
preferenceKey,
|
|
||||||
data.character_preferences.randomization[preferenceKey] ||
|
|
||||||
RandomSetting.Disabled,
|
|
||||||
];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const randomizationOfMainFeatures = getRandomization(
|
const randomizationOfMainFeatures = getRandomization(
|
||||||
Object.fromEntries(mainFeatures)
|
Object.fromEntries(mainFeatures),
|
||||||
|
serverData,
|
||||||
|
randomBodyEnabled,
|
||||||
|
context
|
||||||
);
|
);
|
||||||
|
|
||||||
const nonContextualPreferences = {
|
const nonContextualPreferences = {
|
||||||
@@ -600,14 +608,26 @@ export const MainPage = (
|
|||||||
<Stack vertical fill>
|
<Stack vertical fill>
|
||||||
<PreferenceList
|
<PreferenceList
|
||||||
act={act}
|
act={act}
|
||||||
randomizations={getRandomization(contextualPreferences)}
|
randomizations={getRandomization(
|
||||||
|
contextualPreferences,
|
||||||
|
serverData,
|
||||||
|
randomBodyEnabled,
|
||||||
|
context
|
||||||
|
)}
|
||||||
preferences={contextualPreferences}
|
preferences={contextualPreferences}
|
||||||
|
maxHeight="auto"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PreferenceList
|
<PreferenceList
|
||||||
act={act}
|
act={act}
|
||||||
randomizations={getRandomization(nonContextualPreferences)}
|
randomizations={getRandomization(
|
||||||
|
nonContextualPreferences,
|
||||||
|
serverData,
|
||||||
|
randomBodyEnabled,
|
||||||
|
context
|
||||||
|
)}
|
||||||
preferences={nonContextualPreferences}
|
preferences={nonContextualPreferences}
|
||||||
|
maxHeight="auto"
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { StatelessComponent } from 'inferno';
|
import { StatelessComponent } from 'inferno';
|
||||||
import { Box, Icon, Stack, Tooltip } from '../../components';
|
import { Box, Button, Icon, Popper, Stack, Tooltip } from '../../components';
|
||||||
import { PreferencesMenuData, Quirk } from './data';
|
import { PreferencesMenuData, Quirk, RandomSetting, ServerData } from './data';
|
||||||
import { useBackend, useLocalState } from '../../backend';
|
import { useBackend, useLocalState } from '../../backend';
|
||||||
import { ServerPreferencesFetcher } from './ServerPreferencesFetcher';
|
import { ServerPreferencesFetcher } from './ServerPreferencesFetcher';
|
||||||
|
import { filterMap } from 'common/collections';
|
||||||
|
import { getRandomization, PreferenceList } from './MainPage';
|
||||||
|
import { useRandomToggleState } from './useRandomToggleState';
|
||||||
|
|
||||||
const getValueClass = (value: number): string => {
|
const getValueClass = (value: number): string => {
|
||||||
if (value > 0) {
|
if (value > 0) {
|
||||||
@@ -14,6 +17,21 @@ const getValueClass = (value: number): string => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getCorrespondingPreferences = (
|
||||||
|
customization_options: string[],
|
||||||
|
relevant_preferences: Record<string, string>
|
||||||
|
): Record<string, unknown> => {
|
||||||
|
return Object.fromEntries(
|
||||||
|
filterMap(Object.keys(relevant_preferences), (key) => {
|
||||||
|
if (!customization_options.includes(key)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [key, relevant_preferences[key]];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const QuirkList = (props: {
|
const QuirkList = (props: {
|
||||||
quirks: [
|
quirks: [
|
||||||
string,
|
string,
|
||||||
@@ -22,13 +40,33 @@ const QuirkList = (props: {
|
|||||||
}
|
}
|
||||||
][];
|
][];
|
||||||
onClick: (quirkName: string, quirk: Quirk) => void;
|
onClick: (quirkName: string, quirk: Quirk) => void;
|
||||||
|
selected: boolean;
|
||||||
|
serverData: ServerData;
|
||||||
|
randomBodyEnabled: boolean;
|
||||||
|
context;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { act, data } = useBackend<PreferencesMenuData>(props.context);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// Stack is not used here for a variety of IE flex bugs
|
// Stack is not used here for a variety of IE flex bugs
|
||||||
<Box className="PreferencesMenu__Quirks__QuirkList">
|
<Box className="PreferencesMenu__Quirks__QuirkList">
|
||||||
{props.quirks.map(([quirkKey, quirk]) => {
|
{props.quirks.map(([quirkKey, quirk]) => {
|
||||||
|
const [customizationExpanded, setCustomizationExpanded] =
|
||||||
|
useLocalState<boolean>(
|
||||||
|
props.context,
|
||||||
|
quirk.name + ' customization',
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
const className = 'PreferencesMenu__Quirks__QuirkList__quirk';
|
const className = 'PreferencesMenu__Quirks__QuirkList__quirk';
|
||||||
|
|
||||||
|
const hasExpandableCustomization =
|
||||||
|
quirk.customizable &&
|
||||||
|
props.selected &&
|
||||||
|
customizationExpanded &&
|
||||||
|
quirk.customization_options &&
|
||||||
|
Object.entries(quirk.customization_options).length > 0;
|
||||||
|
|
||||||
const child = (
|
const child = (
|
||||||
<Box
|
<Box
|
||||||
className={className}
|
className={className}
|
||||||
@@ -36,6 +74,9 @@ const QuirkList = (props: {
|
|||||||
role="button"
|
role="button"
|
||||||
tabIndex="1"
|
tabIndex="1"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
if (props.selected) {
|
||||||
|
setCustomizationExpanded(false);
|
||||||
|
}
|
||||||
props.onClick(quirkKey, quirk);
|
props.onClick(quirkKey, quirk);
|
||||||
}}>
|
}}>
|
||||||
<Stack fill>
|
<Stack fill>
|
||||||
@@ -95,6 +136,72 @@ const QuirkList = (props: {
|
|||||||
'padding': '3px',
|
'padding': '3px',
|
||||||
}}>
|
}}>
|
||||||
{quirk.description}
|
{quirk.description}
|
||||||
|
{!!quirk.customizable && (
|
||||||
|
<Popper
|
||||||
|
options={{
|
||||||
|
placement: 'bottom-end',
|
||||||
|
}}
|
||||||
|
popperContent={
|
||||||
|
<Box>
|
||||||
|
{!!quirk.customization_options &&
|
||||||
|
hasExpandableCustomization && (
|
||||||
|
<Box
|
||||||
|
mt="1px"
|
||||||
|
style={{
|
||||||
|
'box-shadow':
|
||||||
|
'0px 4px 8px 3px rgba(0, 0, 0, 0.7)',
|
||||||
|
}}>
|
||||||
|
<Stack
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
maxWidth="300px"
|
||||||
|
backgroundColor="black"
|
||||||
|
px="5px"
|
||||||
|
py="3px">
|
||||||
|
<Stack.Item>
|
||||||
|
<PreferenceList
|
||||||
|
act={act}
|
||||||
|
preferences={getCorrespondingPreferences(
|
||||||
|
quirk.customization_options,
|
||||||
|
data.character_preferences
|
||||||
|
.manually_rendered_features
|
||||||
|
)}
|
||||||
|
randomizations={getRandomization(
|
||||||
|
getCorrespondingPreferences(
|
||||||
|
quirk.customization_options,
|
||||||
|
data.character_preferences
|
||||||
|
.manually_rendered_features
|
||||||
|
),
|
||||||
|
props.serverData,
|
||||||
|
props.randomBodyEnabled,
|
||||||
|
props.context
|
||||||
|
)}
|
||||||
|
maxHeight="100px"
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
}>
|
||||||
|
{props.selected && (
|
||||||
|
<Button
|
||||||
|
selected={customizationExpanded}
|
||||||
|
icon="cog"
|
||||||
|
tooltip="Customize"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
setCustomizationExpanded(!customizationExpanded);
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
'float': 'right',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Popper>
|
||||||
|
)}
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
@@ -129,6 +236,12 @@ const StatDisplay: StatelessComponent<{}> = (props) => {
|
|||||||
export const QuirksPage = (props, context) => {
|
export const QuirksPage = (props, context) => {
|
||||||
const { act, data } = useBackend<PreferencesMenuData>(context);
|
const { act, data } = useBackend<PreferencesMenuData>(context);
|
||||||
|
|
||||||
|
// this is mainly just here to copy from MainPage.tsx
|
||||||
|
const [randomToggleEnabled] = useRandomToggleState(context);
|
||||||
|
const randomBodyEnabled =
|
||||||
|
data.character_preferences.non_contextual.random_body !==
|
||||||
|
RandomSetting.Disabled || randomToggleEnabled;
|
||||||
|
|
||||||
const [selectedQuirks, setSelectedQuirks] = useLocalState(
|
const [selectedQuirks, setSelectedQuirks] = useLocalState(
|
||||||
context,
|
context,
|
||||||
`selectedQuirks_${data.active_slot}`,
|
`selectedQuirks_${data.active_slot}`,
|
||||||
@@ -137,8 +250,8 @@ export const QuirksPage = (props, context) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ServerPreferencesFetcher
|
<ServerPreferencesFetcher
|
||||||
render={(data) => {
|
render={(server_data) => {
|
||||||
if (!data) {
|
if (!server_data) {
|
||||||
return <Box>Loading quirks...</Box>;
|
return <Box>Loading quirks...</Box>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +259,7 @@ export const QuirksPage = (props, context) => {
|
|||||||
max_positive_quirks: maxPositiveQuirks,
|
max_positive_quirks: maxPositiveQuirks,
|
||||||
quirk_blacklist: quirkBlacklist,
|
quirk_blacklist: quirkBlacklist,
|
||||||
quirk_info: quirkInfo,
|
quirk_info: quirkInfo,
|
||||||
} = data.quirks;
|
} = server_data.quirks;
|
||||||
|
|
||||||
const quirks = Object.entries(quirkInfo);
|
const quirks = Object.entries(quirkInfo);
|
||||||
quirks.sort(([_, quirkA], [__, quirkB]) => {
|
quirks.sort(([_, quirkA], [__, quirkB]) => {
|
||||||
@@ -238,6 +351,7 @@ export const QuirksPage = (props, context) => {
|
|||||||
|
|
||||||
<Stack.Item grow width="100%">
|
<Stack.Item grow width="100%">
|
||||||
<QuirkList
|
<QuirkList
|
||||||
|
selected={false}
|
||||||
onClick={(quirkName, quirk) => {
|
onClick={(quirkName, quirk) => {
|
||||||
if (getReasonToNotAdd(quirkName) !== undefined) {
|
if (getReasonToNotAdd(quirkName) !== undefined) {
|
||||||
return;
|
return;
|
||||||
@@ -260,6 +374,9 @@ export const QuirksPage = (props, context) => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
})}
|
})}
|
||||||
|
serverData={server_data}
|
||||||
|
randomBodyEnabled={randomBodyEnabled}
|
||||||
|
context={context}
|
||||||
/>
|
/>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -287,6 +404,7 @@ export const QuirksPage = (props, context) => {
|
|||||||
|
|
||||||
<Stack.Item grow width="100%">
|
<Stack.Item grow width="100%">
|
||||||
<QuirkList
|
<QuirkList
|
||||||
|
selected
|
||||||
onClick={(quirkName, quirk) => {
|
onClick={(quirkName, quirk) => {
|
||||||
if (getReasonToNotRemove(quirkName) !== undefined) {
|
if (getReasonToNotRemove(quirkName) !== undefined) {
|
||||||
return;
|
return;
|
||||||
@@ -313,6 +431,9 @@ export const QuirksPage = (props, context) => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
})}
|
})}
|
||||||
|
serverData={server_data}
|
||||||
|
randomBodyEnabled={randomBodyEnabled}
|
||||||
|
context={context}
|
||||||
/>
|
/>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ export type Quirk = {
|
|||||||
icon: string;
|
icon: string;
|
||||||
name: string;
|
name: string;
|
||||||
value: number;
|
value: number;
|
||||||
|
customizable: boolean;
|
||||||
|
customization_options?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QuirkInfo = {
|
export type QuirkInfo = {
|
||||||
@@ -135,6 +137,7 @@ export type PreferencesMenuData = {
|
|||||||
};
|
};
|
||||||
secondary_features: Record<string, unknown>;
|
secondary_features: Record<string, unknown>;
|
||||||
supplemental_features: Record<string, unknown>;
|
supplemental_features: Record<string, unknown>;
|
||||||
|
manually_rendered_features: Record<string, string>;
|
||||||
|
|
||||||
names: Record<string, string>;
|
names: Record<string, string>;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user