Skip to content

Commit

Permalink
[MIRROR] Adds Food Allergies as a counterpart to Medicine Allergies […
Browse files Browse the repository at this point in the history
…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

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

* Adds Food Allergies as a counterpart to Medicine Allergies

---------

Co-authored-by: MrMelbert <[email protected]>
  • Loading branch information
2 people authored and FFMirrorBot committed Oct 21, 2023
1 parent a9d96ac commit c823eb8
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 16 deletions.
3 changes: 3 additions & 0 deletions code/__DEFINES/food.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
49 changes: 34 additions & 15 deletions code/datums/components/food/edible.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down
83 changes: 83 additions & 0 deletions code/datums/diseases/anaphylaxis.dm
Original file line number Diff line number Diff line change
@@ -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 changes: 5 additions & 0 deletions code/datums/mood_events/food_events.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 45 additions & 0 deletions code/datums/quirks/negative_quirks/food_allergy.dm
Original file line number Diff line number Diff line change
@@ -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.")
2 changes: 1 addition & 1 deletion code/game/objects/items/food/sandwichtoast.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
20 changes: 20 additions & 0 deletions code/modules/client/preferences/food_allergy.dm
Original file line number Diff line number Diff line change
@@ -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 changes: 8 additions & 0 deletions code/modules/mob/living/taste.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions tgstation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { FeatureChoiced, FeatureDropdownInput } from '../base';

export const food_allergy: FeatureChoiced = {
name: 'Food Allergy',
component: FeatureDropdownInput,
};

0 comments on commit c823eb8

Please sign in to comment.