From 35cf3d82829c61c92ee8a5c5a7c44fcd4a55ce73 Mon Sep 17 00:00:00 2001 From: Iajret Creature <122297233+AnArgonianLizardThatStealsPRs@users.noreply.github.com> Date: Sun, 22 Oct 2023 02:19:07 +0300 Subject: [PATCH] [MIRROR] Adds Food Allergies as a counterpart to Medicine Allergies [MDB IGNORE] (#206) * Adds Food Allergies as a counterpart to Medicine Allergies (#79118) --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> --- code/__DEFINES/food.dm | 3 + code/datums/components/food/edible.dm | 49 +++++++---- code/datums/diseases/anaphylaxis.dm | 83 +++++++++++++++++++ code/datums/mood_events/food_events.dm | 5 ++ .../quirks/negative_quirks/food_allergy.dm | 45 ++++++++++ code/game/objects/items/food/sandwichtoast.dm | 2 +- .../client/preferences/food_allergy.dm | 20 +++++ code/modules/mob/living/taste.dm | 8 ++ tgstation.dme | 3 + .../character_preferences/food_allergy.tsx | 6 ++ 10 files changed, 208 insertions(+), 16 deletions(-) create mode 100644 code/datums/diseases/anaphylaxis.dm create mode 100644 code/datums/quirks/negative_quirks/food_allergy.dm create mode 100644 code/modules/client/preferences/food_allergy.dm create mode 100644 tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/food_allergy.tsx diff --git a/code/__DEFINES/food.dm b/code/__DEFINES/food.dm index aa7833711eb..85ced17e67c 100644 --- a/code/__DEFINES/food.dm +++ b/code/__DEFINES/food.dm @@ -169,6 +169,8 @@ GLOBAL_LIST_INIT(food_buffs, list( #define LIKED_FOOD_QUALITY_CHANGE 2 /// Threshold for food to give a toxic reaction #define TOXIC_FOOD_QUALITY_THRESHOLD -8 +/// Food is dangerous to consume +#define FOOD_QUALITY_DANGEROUS -100 /// Food is "in a container", not in a code sense, but in a literal sense (canned foods) #define FOOD_IN_CONTAINER (1<<0) @@ -200,6 +202,7 @@ DEFINE_BITFIELD(food_flags, list( #define FOOD_LIKED 1 #define FOOD_DISLIKED 2 #define FOOD_TOXIC 3 +#define FOOD_ALLERGIC 4 ///Venue reagent requirement #define VENUE_BAR_MINIMUM_REAGENTS 10 diff --git a/code/datums/components/food/edible.dm b/code/datums/components/food/edible.dm index 7c86ddc2243..dd9c91c6ec5 100644 --- a/code/datums/components/food/edible.dm +++ b/code/datums/components/food/edible.dm @@ -223,6 +223,8 @@ Behavior that's still missing from this component that original food items had t examine_list += span_green("You find this meal [quality_label].") else if (quality == 0) examine_list += span_notice("You find this meal edible.") + else if (quality <= FOOD_QUALITY_DANGEROUS) + examine_list += span_warning("You may die from eating this meal.") else if (quality <= TOXIC_FOOD_QUALITY_THRESHOLD) examine_list += span_warning("You find this meal disgusting!") else @@ -526,34 +528,47 @@ Behavior that's still missing from this component that original food items had t if(!ishuman(eater)) return FALSE var/mob/living/carbon/human/gourmand = eater + + if(istype(parent, /obj/item/food)) + var/obj/item/food/food = parent + if(food.venue_value >= FOOD_PRICE_EXOTIC) + gourmand.add_mob_memory(/datum/memory/good_food, food = parent) + //Bruh this breakfast thing is cringe and shouldve been handled separately from food-types, remove this in the future (Actually, just kill foodtypes in general) if((foodtypes & BREAKFAST) && world.time - SSticker.round_start_time < STOP_SERVING_BREAKFAST) gourmand.add_mood_event("breakfast", /datum/mood_event/breakfast) last_check_time = world.time - var/food_quality = get_perceived_food_quality(gourmand, parent) + var/food_quality = get_perceived_food_quality(gourmand) + if(food_quality <= FOOD_QUALITY_DANGEROUS && (foodtypes & gourmand.get_allergic_foodtypes())) // Only cause anaphylaxis if we're ACTUALLY allergic, otherwise it just tastes horrible + if(gourmand.ForceContractDisease(new /datum/disease/anaphylaxis(), make_copy = FALSE, del_on_fail = TRUE)) + to_chat(gourmand, span_warning("You feel your throat start to itch.")) + gourmand.add_mood_event("allergic_food", /datum/mood_event/allergic_food) + return + if(food_quality <= TOXIC_FOOD_QUALITY_THRESHOLD) to_chat(gourmand,span_warning("What the hell was that thing?!")) gourmand.adjust_disgust(25 + 30 * fraction) gourmand.add_mood_event("toxic_food", /datum/mood_event/disgusting_food) - else if(food_quality < 0) + return + + if(food_quality < 0) to_chat(gourmand,span_notice("That didn't taste very good...")) gourmand.adjust_disgust(11 + 15 * fraction) gourmand.add_mood_event("gross_food", /datum/mood_event/gross_food) - else if(food_quality > 0) - food_quality = min(food_quality, FOOD_QUALITY_TOP) - var/atom/owner = parent - var/timeout_mod = owner.reagents.get_average_purity(/datum/reagent/consumable) * 2 // mood event duration is 100% at average purity of 50% - var/event = GLOB.food_quality_events[food_quality] - gourmand.add_mood_event("quality_food", event, timeout_mod) - gourmand.adjust_disgust(-5 + -2 * food_quality * fraction) - var/quality_label = GLOB.food_quality_description[food_quality] - to_chat(gourmand, span_notice("That's \an [quality_label] meal.")) + return - if(istype(parent, /obj/item/food)) - var/obj/item/food/food = parent - if(food.venue_value >= FOOD_PRICE_EXOTIC) - gourmand.add_mob_memory(/datum/memory/good_food, food = parent) + if(food_quality == 0) + return // meh + + food_quality = min(food_quality, FOOD_QUALITY_TOP) + var/atom/owner = parent + var/timeout_mod = owner.reagents.get_average_purity(/datum/reagent/consumable) * 2 // mood event duration is 100% at average purity of 50% + var/event = GLOB.food_quality_events[food_quality] + gourmand.add_mood_event("quality_food", event, timeout_mod) + gourmand.adjust_disgust(-5 + -2 * food_quality * fraction) + var/quality_label = GLOB.food_quality_description[food_quality] + to_chat(gourmand, span_notice("That's \an [quality_label] meal.")) /// Get the complexity of the crafted food /datum/component/edible/proc/get_recipe_complexity() @@ -580,8 +595,12 @@ Behavior that's still missing from this component that original food items had t return DISLIKED_FOOD_QUALITY_CHANGE if(FOOD_TOXIC) return TOXIC_FOOD_QUALITY_THRESHOLD + if(FOOD_ALLERGIC) + return FOOD_QUALITY_DANGEROUS if(ishuman(eater)) + if(foodtypes & eater.get_allergic_foodtypes()) + return FOOD_QUALITY_DANGEROUS if(count_matching_foodtypes(foodtypes, eater.get_toxic_foodtypes())) //if the food is toxic, we don't care about anything else return TOXIC_FOOD_QUALITY_THRESHOLD if(HAS_TRAIT(eater, TRAIT_AGEUSIA)) //if you can't taste it, it doesn't taste good diff --git a/code/datums/diseases/anaphylaxis.dm b/code/datums/diseases/anaphylaxis.dm new file mode 100644 index 00000000000..12d408ad215 --- /dev/null +++ b/code/datums/diseases/anaphylaxis.dm @@ -0,0 +1,83 @@ +/datum/disease/anaphylaxis + form = "Shock" + name = "Anaphylaxis" + desc = "Patient is undergoing a life-threatening allergic reaction and will die if not treated." + max_stages = 3 + cure_text = "Epinephrine" + cures = list(/datum/reagent/medicine/epinephrine) + cure_chance = 20 + agent = "Allergy" + viable_mobtypes = list(/mob/living/carbon/human) + disease_flags = CURABLE + severity = DISEASE_SEVERITY_DANGEROUS + spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS + spread_text = "None" + visibility_flags = HIDDEN_PANDEMIC + bypasses_immunity = TRUE + stage_prob = 5 + +/datum/disease/anaphylaxis/stage_act(seconds_per_tick, times_fired) + . = ..() + if(!.) + return + + if(HAS_TRAIT(affected_mob, TRAIT_TOXINLOVER)) // You are no fun + cure() + return + + // Cool them enough to feel cold to the touch, and then some, because temperature mechanics are dumb + affected_mob.adjust_bodytemperature(-10 * seconds_per_tick * stage, min_temp = BODYTEMP_COLD_DAMAGE_LIMIT - 70) + + switch(stage) + // early symptoms: mild shakes and dizziness + if(1) + if(affected_mob.num_hands >= 1 && SPT_PROB(5, seconds_per_tick)) + to_chat(affected_mob, span_warning("You feel your hand[affected_mob.num_hands == 1 ? "":"s"] start to shake.")) + affected_mob.adjust_jitter_up_to(4 SECONDS * seconds_per_tick, 1 MINUTES) + if(affected_mob.num_legs >= 1 && SPT_PROB(5, seconds_per_tick)) + to_chat(affected_mob, span_warning("You feel your leg[affected_mob.num_hands == 1 ? "":"s"] start to shake.")) + affected_mob.adjust_jitter_up_to(4 SECONDS * seconds_per_tick, 1 MINUTES) + if(SPT_PROB(2, seconds_per_tick)) + affected_mob.adjust_dizzy_up_to(5 SECONDS * seconds_per_tick, 1 MINUTES) + if(SPT_PROB(1, seconds_per_tick)) + to_chat(affected_mob, span_danger("Your throat itches.")) + + // warning symptoms: violent shakes, dizziness, blurred vision, difficulty breathing + if(2) + affected_mob.apply_damage(0.33 * seconds_per_tick, TOX, spread_damage = TRUE) + + if(affected_mob.num_hands >= 1 && SPT_PROB(5, seconds_per_tick)) + to_chat(affected_mob, span_warning("You feel your hand[affected_mob.num_hands == 1 ? "":"s"] shake violently.")) + affected_mob.adjust_jitter_up_to(8 SECONDS * seconds_per_tick, 1 MINUTES) + if(prob(20)) + affected_mob.drop_all_held_items() + if(affected_mob.num_legs >= 1 && SPT_PROB(5, seconds_per_tick)) + to_chat(affected_mob, span_warning("You feel your leg[affected_mob.num_hands == 1 ? "":"s"] shake violently.")) + affected_mob.adjust_jitter_up_to(8 SECONDS * seconds_per_tick, 1 MINUTES) + if(prob(40) && affected_mob.getStaminaLoss() < 75) + affected_mob.adjustStaminaLoss(15) + if(affected_mob.get_organ_slot(ORGAN_SLOT_EYES) && SPT_PROB(4, seconds_per_tick)) + affected_mob.adjust_eye_blur(4 SECONDS * seconds_per_tick) + to_chat(affected_mob, span_warning("It's getting harder to see clearly.")) + if(!HAS_TRAIT(affected_mob, TRAIT_NOBREATH) && SPT_PROB(4, seconds_per_tick)) + affected_mob.apply_damage(2 * seconds_per_tick, OXY) + affected_mob.losebreath += (2 * seconds_per_tick) + to_chat(affected_mob, span_warning("It's getting harder to breathe.")) + if(SPT_PROB(2, seconds_per_tick)) + affected_mob.adjust_drowsiness_up_to(3 SECONDS * seconds_per_tick, 30 SECONDS) + if(SPT_PROB(2, seconds_per_tick)) + affected_mob.adjust_dizzy_up_to(5 SECONDS * seconds_per_tick, 1 MINUTES) + affected_mob.adjust_confusion_up_to(1 SECONDS * seconds_per_tick, 10 SECONDS) + if(SPT_PROB(2, seconds_per_tick)) + affected_mob.vomit(MOB_VOMIT_MESSAGE|MOB_VOMIT_HARM) + affected_mob.Stun(2 SECONDS) // The full 20 second vomit stun would be lethal + if(SPT_PROB(1, seconds_per_tick)) + affected_mob.emote("cough") + if(SPT_PROB(1, seconds_per_tick)) + to_chat(affected_mob, span_danger("Your throat feels sore.")) + + // "you are too late" symptoms: death. + if(3) + affected_mob.apply_damage(3 * seconds_per_tick, TOX, spread_damage = TRUE) + affected_mob.apply_damage(1 * seconds_per_tick, OXY) + affected_mob.Unconscious(3 SECONDS * seconds_per_tick) diff --git a/code/datums/mood_events/food_events.dm b/code/datums/mood_events/food_events.dm index 7d2dcc439de..7d33e7e57ce 100644 --- a/code/datums/mood_events/food_events.dm +++ b/code/datums/mood_events/food_events.dm @@ -13,6 +13,11 @@ mood_change = -6 timeout = 4 MINUTES +/datum/mood_event/allergic_food + description = "My throat itches." + mood_change = -2 + timeout = 4 MINUTES + /datum/mood_event/breakfast description = "Nothing like a hearty breakfast to start the shift." mood_change = 2 diff --git a/code/datums/quirks/negative_quirks/food_allergy.dm b/code/datums/quirks/negative_quirks/food_allergy.dm new file mode 100644 index 00000000000..c2f4eae4d0e --- /dev/null +++ b/code/datums/quirks/negative_quirks/food_allergy.dm @@ -0,0 +1,45 @@ +GLOBAL_LIST_INIT(possible_food_allergies, list( + "Alcohol" = ALCOHOL, + "Bugs" = BUGS, + "Dairy" = DAIRY, + "Fruit" = FRUIT, + "Grain" = GRAIN, + "Meat" = MEAT, + "Nuts" = NUTS, + "Seafood" = SEAFOOD, + "Sugar" = SUGAR, + "Vegetables" = VEGETABLES, +)) + +/datum/quirk/item_quirk/food_allergic + name = "Food Allergy" + desc = "Ever since you were a kid, you've been allergic to certain foods." + icon = FA_ICON_SHRIMP + value = -2 + gain_text = span_danger("You feel your immune system shift.") + lose_text = span_notice("You feel your immune system phase back into perfect shape.") + medical_record_text = "Patient's immune system responds violently to certain food." + hardcore_value = 1 + quirk_flags = QUIRK_HUMAN_ONLY + mail_goodies = list(/obj/item/reagent_containers/hypospray/medipen) + /// Footype flags that will trigger the allergy + var/target_foodtypes = NONE + +/datum/quirk/item_quirk/food_allergic/add(client/client_source) + if(target_foodtypes != NONE) // Already set, don't care + return + + var/desired_allergy = client_source?.prefs.read_preference(/datum/preference/choiced/food_allergy) || "Random" + if(desired_allergy != "Random") + target_foodtypes = GLOB.possible_food_allergies[desired_allergy] + if(target_foodtypes != NONE) // Got a preference, don't care + return + + target_foodtypes = pick(flatten_list(GLOB.possible_food_allergies)) + +/datum/quirk/item_quirk/food_allergic/add_unique(client/client_source) + var/what_are_we_actually_killed_by = english_list(bitfield_to_list(target_foodtypes, FOOD_FLAGS_IC)) // This should never be more than one thing but just in case we can support it + to_chat(client_source.mob, span_info("You are allergic to [what_are_we_actually_killed_by]. Watch what you eat!")) + + var/obj/item/clothing/accessory/dogtag/allergy/dogtag = new(quirk_holder, what_are_we_actually_killed_by) + give_item_to_holder(dogtag, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS), flavour_text = "Keep it close around the kitchen.") diff --git a/code/game/objects/items/food/sandwichtoast.dm b/code/game/objects/items/food/sandwichtoast.dm index 8b91b04621e..c6488f67a1e 100644 --- a/code/game/objects/items/food/sandwichtoast.dm +++ b/code/game/objects/items/food/sandwichtoast.dm @@ -284,7 +284,7 @@ // Closest thing to a mullet we have if(consumer.hairstyle == "Gelled Back" && istype(consumer.get_item_by_slot(ITEM_SLOT_ICLOTHING), /obj/item/clothing/under/rank/civilian/cookjorts)) return FOOD_LIKED - return FOOD_DISLIKED + return FOOD_ALLERGIC /** * Callback to be used with the edible component. diff --git a/code/modules/client/preferences/food_allergy.dm b/code/modules/client/preferences/food_allergy.dm new file mode 100644 index 00000000000..461c3b31e2a --- /dev/null +++ b/code/modules/client/preferences/food_allergy.dm @@ -0,0 +1,20 @@ +/datum/preference/choiced/food_allergy + category = PREFERENCE_CATEGORY_SECONDARY_FEATURES + savefile_key = "food_allergy" + savefile_identifier = PREFERENCE_CHARACTER + can_randomize = FALSE + +/datum/preference/choiced/food_allergy/init_possible_values() + return list("Random") + assoc_to_keys(GLOB.possible_food_allergies) + +/datum/preference/choiced/food_allergy/create_default_value() + return "Random" + +/datum/preference/choiced/food_allergy/is_accessible(datum/preferences/preferences) + if (!..()) + return FALSE + + return "Food Allergy" in preferences.all_quirks + +/datum/preference/choiced/food_allergy/apply_to_human(mob/living/carbon/human/target, value) + return diff --git a/code/modules/mob/living/taste.dm b/code/modules/mob/living/taste.dm index 8f414a2e603..f0bc01eb84c 100644 --- a/code/modules/mob/living/taste.dm +++ b/code/modules/mob/living/taste.dm @@ -85,6 +85,14 @@ return TOXIC return tongue.toxic_foodtypes +/** + * Gets food this mob is allergic to + * Essentially toxic food+, not only disgusting but outright lethal + */ +/mob/living/proc/get_allergic_foodtypes() + var/datum/quirk/item_quirk/food_allergic/allergy = get_quirk(/datum/quirk/item_quirk/food_allergic) + return allergy?.target_foodtypes || NONE + /** * Gets the food reaction a mob would normally have from the given food item, * assuming that no check_liked callback was used in the edible component. diff --git a/tgstation.dme b/tgstation.dme index 06d64820e51..1e73952b6fd 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1303,6 +1303,7 @@ #include "code\datums\diseases\_disease.dm" #include "code\datums\diseases\_MobProcs.dm" #include "code\datums\diseases\adrenal_crisis.dm" +#include "code\datums\diseases\anaphylaxis.dm" #include "code\datums\diseases\anxiety.dm" #include "code\datums\diseases\beesease.dm" #include "code\datums\diseases\brainrot.dm" @@ -1658,6 +1659,7 @@ #include "code\datums\quirks\negative_quirks\deafness.dm" #include "code\datums\quirks\negative_quirks\depression.dm" #include "code\datums\quirks\negative_quirks\family_heirloom.dm" +#include "code\datums\quirks\negative_quirks\food_allergy.dm" #include "code\datums\quirks\negative_quirks\frail.dm" #include "code\datums\quirks\negative_quirks\glass_jaw.dm" #include "code\datums\quirks\negative_quirks\heavy_sleeper.dm" @@ -3547,6 +3549,7 @@ #include "code\modules\client\preferences\broadcast_login_logout.dm" #include "code\modules\client\preferences\clothing.dm" #include "code\modules\client\preferences\darkened_flash.dm" +#include "code\modules\client\preferences\food_allergy.dm" #include "code\modules\client\preferences\fov_darkness.dm" #include "code\modules\client\preferences\fps.dm" #include "code\modules\client\preferences\gender.dm" diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/food_allergy.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/food_allergy.tsx new file mode 100644 index 00000000000..f5b0ea37ca8 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/food_allergy.tsx @@ -0,0 +1,6 @@ +import { FeatureChoiced, FeatureDropdownInput } from '../base'; + +export const food_allergy: FeatureChoiced = { + name: 'Food Allergy', + component: FeatureDropdownInput, +};