[MIRROR] Adds Food Allergies as a counterpart to Medicine Allergies [MDB IGNORE] (#24493)

* Adds Food Allergies as a counterpart to Medicine Allergies (#79118)

## About The Pull Request

Adds food allergies as a counterpart to medicinal allergies.

This is about as you would expect. Eating food you are allergic to
causes Anaphylaxis, which can be cured with epinephrine. If you are not
cured rapidly, you will shortly fall into a coma and die.

There is a preference for choosing your character's allergy.
Not all food types are viable allergen types: Only `"Alcohol", "Bugs",
"Dairy", "Fruit", "Grain", "Meat", "Nuts", "Seafood", "Sugar",
"Vegetables"`.

You also get a dogtag like med allergies.

Minor implementation detail: I added a "dangerous" level of food quality
/ scale, so I gave it to the Death Sandwich.

## Why It's Good For The Game

This was a request, and I saw it fairly harmless, so I said why not.

Similar to Medicine Allergy, it adds a small mechanic to concern
yourself with when you visit the kitchen, encouraging you to get a bit
more familiar with food types before diving into food at the kitchen.

As it is not as impactful as Medicine Allergy, it is only a -2 quirk.

## Changelog

🆑 Melbert
add: Adds Food Allergies as a -2 quirk. You can select which food you're
allergic to or rock a random option.
/🆑

* Adds Food Allergies as a counterpart to Medicine Allergies

---------

Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
This commit is contained in:
SkyratBot
2023-10-21 15:14:19 -04:00
committed by GitHub
co-authored by MrMelbert
parent a91947e6ed
commit 117c2edc20
10 changed files with 216 additions and 24 deletions
+3
View File
@@ -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
+42 -23
View File
@@ -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,35 +528,48 @@ 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
//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)
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)
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."))
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)
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)
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)
return
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()
if(!HAS_TRAIT(parent, TRAIT_FOOD_CHEF_MADE) || !istype(parent, /obj/item/food))
@@ -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
+83
View File
@@ -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)
+5
View File
@@ -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
@@ -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.")
@@ -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.
@@ -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
+8
View File
@@ -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.
+3
View File
@@ -1294,6 +1294,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"
@@ -1649,6 +1650,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"
@@ -3538,6 +3540,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"
@@ -0,0 +1,6 @@
import { FeatureChoiced, FeatureDropdownInput } from '../base';
export const food_allergy: FeatureChoiced = {
name: 'Food Allergy',
component: FeatureDropdownInput,
};