From 15ed8137da50a3311831a89c9cf9299c0cc15e59 Mon Sep 17 00:00:00 2001 From: FlufflesTheDog Date: Sat, 5 Apr 2025 10:42:52 -0700 Subject: [PATCH] System for restricting quirks based on species, no more blood deficiency on bloodless species (#90326) Quirks can now define if they're "species appropriate," where the base proc's behavior is simply "does this species already have the quirk's main trait" I am unsure if that on its own imposes any new restrictions, currently. Additionally, blood deficiency cannot be picked on any species without blood or that doesn't breathe. I'm sure there's more that might make sense, I'm open to suggestions Alternative to #90238 Currently, reduces the possibility of taking something like blood deficiency on a race which suffers no downside from it in order to get free positive quirks. Future-ly, potentially allows quirks exclusive to only a select few species as offered by #90238 :cl: fix: Species without blood can no longer be blood deficient /:cl: --- .../subsystem/processing/quirks.dm | 2 + code/datums/quirks/_quirk.dm | 6 +++ .../negative_quirks/blood_deficiency.dm | 8 ++++ code/modules/client/preferences.dm | 24 +++++++++--- .../preferences/middleware/_middleware.dm | 2 +- .../client/preferences/middleware/quirks.dm | 39 +++++++++++++++++++ .../mob/dead/new_player/preferences_setup.dm | 12 +++--- .../CharacterPreferences/QuirksPage.tsx | 4 +- .../tgui/interfaces/PreferencesMenu/types.ts | 1 + 9 files changed, 85 insertions(+), 13 deletions(-) diff --git a/code/controllers/subsystem/processing/quirks.dm b/code/controllers/subsystem/processing/quirks.dm index b649bff0730..719d1a3c75f 100644 --- a/code/controllers/subsystem/processing/quirks.dm +++ b/code/controllers/subsystem/processing/quirks.dm @@ -72,6 +72,7 @@ PROCESSING_SUBSYSTEM_DEF(quirks) wait = 1 SECONDS var/list/quirks = list() //Assoc. list of all roundstart quirk datum types; "name" = /path/ + var/list/datum/quirk/quirk_prototypes = list() var/list/quirk_points = list() //Assoc. list of quirk names and their "point cost"; positive numbers are good traits, and negative ones are bad ///An assoc list of quirks that can be obtained as a hardcore character, and their hardcore value. var/list/hardcore_quirks = list() @@ -108,6 +109,7 @@ PROCESSING_SUBSYSTEM_DEF(quirks) #endif // SKYRAT EDIT ADDITION END + quirk_prototypes[type] = new type quirks[initial(quirk_type.name)] = quirk_type quirk_points[initial(quirk_type.name)] = initial(quirk_type.value) diff --git a/code/datums/quirks/_quirk.dm b/code/datums/quirks/_quirk.dm index c9f6173a8f3..fc01f7ca3dc 100644 --- a/code/datums/quirks/_quirk.dm +++ b/code/datums/quirks/_quirk.dm @@ -196,6 +196,12 @@ SIGNAL_HANDLER update_process() +/// If a quirk is able to be selected for the mob's species +/datum/quirk/proc/is_species_appropriate(datum/species/mob_species) + if(mob_trait in GLOB.species_prototypes[mob_species].inherent_traits) + return FALSE + return TRUE + /// Subtype quirk that has some bonus logic to spawn items for the player. /datum/quirk/item_quirk /// Lazylist of strings describing where all the quirk items have been spawned. diff --git a/code/datums/quirks/negative_quirks/blood_deficiency.dm b/code/datums/quirks/negative_quirks/blood_deficiency.dm index a5ac11714bd..b0fa4cfeae4 100644 --- a/code/datums/quirks/negative_quirks/blood_deficiency.dm +++ b/code/datums/quirks/negative_quirks/blood_deficiency.dm @@ -21,6 +21,14 @@ /datum/quirk/blooddeficiency/remove() UnregisterSignal(quirk_holder, list(COMSIG_HUMAN_ON_HANDLE_BLOOD, COMSIG_SPECIES_GAIN)) +/datum/quirk/blooddeficiency/is_species_appropriate(datum/species/mob_species) + var/datum/species_traits = GLOB.species_prototypes[mob_species].inherent_traits + if(TRAIT_NOBLOOD in species_traits) + return FALSE + if(TRAIT_NOBREATH in species_traits) + return FALSE + return ..() + /datum/quirk/blooddeficiency/proc/lose_blood(datum/source, seconds_per_tick, times_fired) SIGNAL_HANDLER diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 10804a11fa1..173a06b677d 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -270,8 +270,8 @@ GLOBAL_LIST_EMPTY(preferences_datums) tainted_character_profiles = TRUE //SKYRAT EDIT update_body_parts(requested_preference) - for (var/datum/preference_middleware/preference_middleware as anything in middleware) - if (preference_middleware.post_set_preference(usr, requested_preference_key, value)) + for(var/datum/preference_middleware/preference_middleware as anything in middleware) + if(preference_middleware.post_set_preference(ui.user, requested_preference_key, value)) return TRUE //SKYRAT EDIT END return TRUE @@ -571,10 +571,24 @@ GLOBAL_LIST_EMPTY(preferences_datums) .++ /datum/preferences/proc/validate_quirks() - if(CONFIG_GET(flag/disable_quirk_points)) - return - if(GetQuirkBalance() < 0) + var/datum/species/species_type = read_preference(/datum/preference/choiced/species) + var/list/quirks_removed + for(var/quirk_name in all_quirks) + var/quirk_path = SSquirks.quirks[quirk_name] + var/datum/quirk/quirk_prototype = SSquirks.quirk_prototypes[quirk_path] + if(!quirk_prototype.is_species_appropriate(species_type)) + all_quirks -= quirk_name + LAZYADD(quirks_removed, quirk_name) + var/list/feedback + if(LAZYLEN(quirks_removed)) + LAZYADD(feedback, "The following quirks are incompatible with your species:") + LAZYADD(feedback, quirks_removed) + if(!CONFIG_GET(flag/disable_quirk_points) && GetQuirkBalance() < 0) + LAZYADD(feedback, "Your quirks have been reset.") all_quirks = list() + if(LAZYLEN(feedback)) + to_chat(parent, boxed_message(span_greentext(feedback.Join("\n")))) + /** * Safely read a given preference datum from a given client. diff --git a/code/modules/client/preferences/middleware/_middleware.dm b/code/modules/client/preferences/middleware/_middleware.dm index 6c666544883..272d6e71742 100644 --- a/code/modules/client/preferences/middleware/_middleware.dm +++ b/code/modules/client/preferences/middleware/_middleware.dm @@ -54,7 +54,7 @@ // SKYRAT EDIT /// Called after every update_preference, returns TRUE if this handled it. /datum/preference_middleware/proc/post_set_preference(mob/user, preference, value) - return FALSE + return /// Called when applying preferences to the mob. /datum/preference_middleware/proc/apply_to_human(mob/living/carbon/human/target, datum/preferences/preferences, visuals_only = FALSE) //SKYRAT EDIT CHANGE diff --git a/code/modules/client/preferences/middleware/quirks.dm b/code/modules/client/preferences/middleware/quirks.dm index e1833b1572e..107b9a9e9f7 100644 --- a/code/modules/client/preferences/middleware/quirks.dm +++ b/code/modules/client/preferences/middleware/quirks.dm @@ -6,6 +6,41 @@ "give_quirk" = PROC_REF(give_quirk), "remove_quirk" = PROC_REF(remove_quirk), ) +/datum/preference_middleware/quirks/pre_set_preference(mob/user, preference, value) + if(preference != "species") + return + var/list/incompatible_quirks + var/selected_species_type = GLOB.species_list[value] + for(var/quirk_name in preferences.all_quirks) + var/quirk_path = SSquirks.quirks[quirk_name] + var/datum/quirk/quirk_prototype = SSquirks.quirk_prototypes[quirk_path] + if(!quirk_prototype.is_species_appropriate(selected_species_type)) + LAZYADD(incompatible_quirks, quirk_name) + if(!LAZYLEN(incompatible_quirks)) + return + var/list/message = list("The following quirks are incompatible with your selected species and will be removed: [incompatible_quirks.Join(", ")].") + if(CONFIG_GET(flag/disable_quirk_points)) + message += "Would you like to continue?" + else + message += "If you do not have enough points to cover the removed quirks, your quirks will be reset. Would you like to continue?" + var/response = tgui_alert(user, message.Join(" "), "Quirks Incompatible", list("Yes", "No")) + if(response != "Yes") + return TRUE + + +/datum/preference_middleware/quirks/post_set_preference(mob/user, preference, value) + if(preference != "species") + return + tainted = TRUE + preferences.validate_quirks() + +/datum/preference_middleware/quirks/proc/get_species_compatibility() + var/list/species_blacklist = list() + var/datum/species/mob_species = preferences.read_preference(/datum/preference/choiced/species) + for(var/datum/quirk/quirk_type as anything in SSquirks.quirk_prototypes) + if(!SSquirks.quirk_prototypes[quirk_type].is_species_appropriate(mob_species)) + species_blacklist += quirk_type::name + return species_blacklist /datum/preference_middleware/quirks/get_ui_static_data(mob/user) if (preferences.current_window != PREFERENCE_TAB_CHARACTER_PREFERENCES) @@ -14,6 +49,7 @@ var/list/data = list() data["selected_quirks"] = get_selected_quirks() + data["species_disallowed_quirks"] = get_species_compatibility() return data @@ -23,6 +59,7 @@ if (tainted) tainted = FALSE data["selected_quirks"] = get_selected_quirks() + data["species_disallowed_quirks"] = get_species_compatibility() return data @@ -69,6 +106,8 @@ /datum/preference_middleware/quirks/proc/give_quirk(list/params, mob/user) var/quirk_name = params["quirk"] + + preferences.validate_quirks() var/list/new_quirks = preferences.all_quirks | quirk_name if (SSquirks.filter_invalid_quirks(new_quirks, preferences.augments) != new_quirks)// SKYRAT EDIT - AUGMENTS+ // If the client is sending an invalid give_quirk, that means that diff --git a/code/modules/mob/dead/new_player/preferences_setup.dm b/code/modules/mob/dead/new_player/preferences_setup.dm index 8be060496bc..6bc453f02af 100644 --- a/code/modules/mob/dead/new_player/preferences_setup.dm +++ b/code/modules/mob/dead/new_player/preferences_setup.dm @@ -23,7 +23,7 @@ ///Setup the random hardcore quirks and give the character the new score prize. /datum/preferences/proc/hardcore_random_setup(mob/living/carbon/human/character) - var/next_hardcore_score = select_hardcore_quirks() + var/next_hardcore_score = select_hardcore_quirks(character.dna.species.type) character.hardcore_survival_score = next_hardcore_score ** 1.2 //30 points would be about 60 score log_game("[character] started hardcore random with [english_list(all_quirks)], for a score of [next_hardcore_score].") @@ -35,7 +35,7 @@ * Goes through all quirks that can be used in hardcore mode and select some based on a random budget. * Returns the new value to be gained with this setup, plus the previously earned score. **/ -/datum/preferences/proc/select_hardcore_quirks() +/datum/preferences/proc/select_hardcore_quirks(species) . = 0 var/quirk_budget = rand(8, 35) @@ -45,10 +45,10 @@ var/list/available_hardcore_quirks = SSquirks.hardcore_quirks.Copy() while(quirk_budget > 0) - for(var/i in available_hardcore_quirks) //Remove from available quirks if its too expensive. - var/datum/quirk/available_quirk = i - if(available_hardcore_quirks[available_quirk] > quirk_budget) - available_hardcore_quirks -= available_quirk + for(var/quirk in available_hardcore_quirks) //Remove from available quirks if its too expensive. + var/datum/quirk/quirk_prototype = SSquirks.quirk_prototypes[quirk] + if(available_hardcore_quirks[quirk] > quirk_budget || !quirk_prototype.is_species_appropriate(species)) + available_hardcore_quirks -= quirk if(!available_hardcore_quirks.len) break diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/QuirksPage.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/QuirksPage.tsx index 566d78b86c3..dce3758b3ce 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/QuirksPage.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/QuirksPage.tsx @@ -403,7 +403,9 @@ export function QuirksPage(props) { } // BUBBER EDIT ADDITION END } - + if (data.species_disallowed_quirks.includes(quirk.name)) { + return 'This quirk is incompatible with your selected species.'; + } return; } diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/types.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/types.ts index 3c8f4e8c290..47cbb1e84d6 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/types.ts +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/types.ts @@ -243,6 +243,7 @@ export type PreferencesMenuData = { keybindings: Record; overflow_role: string; selected_quirks: string[]; + species_disallowed_quirks: string[]; antag_bans?: string[]; antag_days_left?: Record;