mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-24 13:38:41 +01:00
[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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { FeatureChoiced, FeatureDropdownInput } from '../base';
|
||||
|
||||
export const food_allergy: FeatureChoiced = {
|
||||
name: 'Food Allergy',
|
||||
component: FeatureDropdownInput,
|
||||
};
|
||||
Reference in New Issue
Block a user