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] (#206)

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

---------
Co-authored-by: MrMelbert <[email protected]>
  • Loading branch information
Steals-The-PRs authored Oct 21, 2023
1 parent 4fc3e22 commit 35cf3d8
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 35cf3d8

Please sign in to comment.