System for restricting quirks based on species, no more blood deficiency on bloodless species (#90326)

## About The Pull Request
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
## Why It's Good For The Game
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
## Changelog
🆑
fix: Species without blood can no longer be blood deficient
/🆑
This commit is contained in:
FlufflesTheDog
2025-04-05 10:42:52 -07:00
committed by GitHub
parent 46168e6881
commit 5bc18586a7
9 changed files with 87 additions and 10 deletions

View File

@@ -52,6 +52,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()
@@ -78,6 +79,7 @@ PROCESSING_SUBSYSTEM_DEF(quirks)
if(initial(quirk_type.abstract_parent_type) == type)
continue
quirk_prototypes[type] = new type
quirks[initial(quirk_type.name)] = quirk_type
quirk_points[initial(quirk_type.name)] = initial(quirk_type.value)

View File

@@ -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.

View File

@@ -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

View File

@@ -231,6 +231,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if (istype(requested_preference, /datum/preference/name))
tainted_character_profiles = TRUE
for(var/datum/preference_middleware/preference_middleware as anything in middleware)
preference_middleware.post_set_preference(ui.user, requested_preference_key, value)
return TRUE
if ("set_color_preference")
var/requested_preference_key = params["preference"]
@@ -419,10 +421,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.

View File

@@ -50,3 +50,7 @@
/// Called when a character is changed.
/datum/preference_middleware/proc/on_new_character(mob/user)
return
/// Called after every update_preference
/datum/preference_middleware/proc/post_set_preference(mob/user, preference, value)
return

View File

@@ -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
@@ -63,6 +100,7 @@
/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) != new_quirks)
// If the client is sending an invalid give_quirk, that means that

View File

@@ -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

View File

@@ -383,7 +383,9 @@ export function QuirksPage(props) {
}
}
}
if (data.species_disallowed_quirks.includes(quirk.name)) {
return 'This quirk is incompatible with your selected species.';
}
return;
}

View File

@@ -174,6 +174,7 @@ export type PreferencesMenuData = {
keybindings: Record<string, string[]>;
overflow_role: string;
selected_quirks: string[];
species_disallowed_quirks: string[];
antag_bans?: string[];
antag_days_left?: Record<string, number>;