Skip to content

Commit

Permalink
[Port] Food Allergy Quirk (#5596)
Browse files Browse the repository at this point in the history
## About The Pull Request
- Ports food allergies from tg
tgstation/tgstation#79118
- a few minor changes had to be made because tg's food complexity system
has not been ported
  - IPCs get punished for taking this quirk
- anaphylaxis uses the old disease type as it's less hacky than trying
to port it to the new system

## Why It's Good For The Game
- This was requested by a player and it adds another mechanic players
can apply to their characters

## Changelog
:cl:
add: Adds Food Allergies as a -2 quirk. You can select which food you're
allergic to or rock a random option.
/:cl:
  • Loading branch information
Sorenon authored Feb 25, 2025
1 parent 94c0a43 commit 09a8d5c
Show file tree
Hide file tree
Showing 18 changed files with 446 additions and 59 deletions.
82 changes: 77 additions & 5 deletions code/__DEFINES/food.dm
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,82 @@ DEFINE_BITFIELD(foodtypes, list(
"Gore", \
)

#define DRINK_NICE 1
#define DRINK_GOOD 2
#define DRINK_VERYGOOD 3
#define DRINK_FANTASTIC 4
#define FOOD_AMAZING 5
#define DRINK_REVOLTING 1
#define DRINK_NICE 2
#define DRINK_GOOD 3
#define DRINK_VERYGOOD 4
#define DRINK_FANTASTIC 5
#define FOOD_AMAZING 6

#define FOOD_QUALITY_NORMAL 1
#define FOOD_QUALITY_NICE 2
#define FOOD_QUALITY_GOOD 3
#define FOOD_QUALITY_VERYGOOD 4
#define FOOD_QUALITY_FANTASTIC 5
#define FOOD_QUALITY_AMAZING 6
#define FOOD_QUALITY_TOP 7

/* MONKESTATION REMOVAL START - food complexity is unported
#define FOOD_COMPLEXITY_0 0
#define FOOD_COMPLEXITY_1 1
#define FOOD_COMPLEXITY_2 2
#define FOOD_COMPLEXITY_3 3
#define FOOD_COMPLEXITY_4 4
#define FOOD_COMPLEXITY_5 5
*/

/// Labels for food quality
GLOBAL_LIST_INIT(food_quality_description, list(
FOOD_QUALITY_NORMAL = "okay",
FOOD_QUALITY_NICE = "nice",
FOOD_QUALITY_GOOD = "good",
FOOD_QUALITY_VERYGOOD = "very good",
FOOD_QUALITY_FANTASTIC = "fantastic",
FOOD_QUALITY_AMAZING = "amazing",
FOOD_QUALITY_TOP = "godlike",
))

/* MONKESTATION REMOVAL START - food complexity is unported
/// Mood events for food quality
GLOBAL_LIST_INIT(food_quality_events, list(
FOOD_QUALITY_NORMAL = /datum/mood_event/food,
FOOD_QUALITY_NICE = /datum/mood_event/food/nice,
FOOD_QUALITY_GOOD = /datum/mood_event/food/good,
FOOD_QUALITY_VERYGOOD = /datum/mood_event/food/verygood,
FOOD_QUALITY_FANTASTIC = /datum/mood_event/food/fantastic,
FOOD_QUALITY_AMAZING = /datum/mood_event/food/amazing,
FOOD_QUALITY_TOP = /datum/mood_event/food/top,
))
/// Crafted food buffs grouped by crafting_complexity
GLOBAL_LIST_INIT(food_buffs, list(
FOOD_COMPLEXITY_1 = list(
/datum/status_effect/food/haste = 1,
),
FOOD_COMPLEXITY_2 = list(
/datum/status_effect/food/haste = 1,
),
FOOD_COMPLEXITY_3 = list(
/datum/status_effect/food/haste = 1,
),
FOOD_COMPLEXITY_4 = list(
/datum/status_effect/food/haste = 1,
/datum/status_effect/food/trait/shockimmune = 1,
),
FOOD_COMPLEXITY_5 = list(
/datum/status_effect/food/haste = 1,
/datum/status_effect/food/trait/shockimmune = 2,
),
))
*/

/// Food quality change according to species diet
#define DISLIKED_FOOD_QUALITY_CHANGE -2
#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 @@ -131,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
122 changes: 103 additions & 19 deletions code/datums/components/food/edible.dm
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,19 @@ Behavior that's still missing from this component that original food items had t
var/list/types = bitfield_to_list(foodtypes, FOOD_FLAGS)
examine_list += span_notice("It is [lowertext(english_list(types))].")

var/quality = get_perceived_food_quality(user)
if(quality > 0)
var/quality_label = GLOB.food_quality_description[quality]
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
examine_list += span_warning("You find this meal inedible.")

var/datum/mind/mind = user.mind
if(mind && HAS_TRAIT_FROM(parent, TRAIT_FOOD_CHEF_MADE, REF(mind)))
examine_list += span_green("[parent] was made by you!")
Expand Down Expand Up @@ -519,32 +532,103 @@ Behavior that's still missing from this component that original food items had t
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_taste_reaction
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)
/* MONKESTATION REMOVAL START - food complexity is unported
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))
return 0 // It is factory made. Soulless.
// monkestation edit start
/* original - food complexity is unported
var/obj/item/food/food = parent
return food.crafting_complexity
*/
return FOOD_QUALITY_NICE
// monkestatione edit end

/// Get food quality adjusted according to eater's preferences
/datum/component/edible/proc/get_perceived_food_quality(mob/living/carbon/human/eater)
var/food_quality = get_recipe_complexity()

if(HAS_TRAIT(parent, TRAIT_FOOD_SILVER)) // it's not real food
if(!isjellyperson(eater)) //if you aren't a jellyperson, it makes you sick no matter how nice it looks
return TOXIC_FOOD_QUALITY_THRESHOLD
food_quality += LIKED_FOOD_QUALITY_CHANGE

if(check_liked) //Callback handling; use this as an override for special food like donuts
food_taste_reaction = check_liked.Invoke(fraction, gourmand)

if(!food_taste_reaction)
food_taste_reaction = gourmand.get_food_taste_reaction(parent, foodtypes)

switch(food_taste_reaction)
if(FOOD_TOXIC)
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)
if(FOOD_DISLIKED)
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)
if(FOOD_LIKED)
to_chat(gourmand,span_notice("I love this taste!"))
gourmand.adjust_disgust(-5 + -2.5 * fraction)
gourmand.add_mood_event("fav_food", /datum/mood_event/favorite_food)
var/special_reaction = check_liked.Invoke(eater)
switch(special_reaction) //return early for special foods
if(FOOD_LIKED)
return LIKED_FOOD_QUALITY_CHANGE
if(FOOD_DISLIKED)
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
return 0
food_quality += DISLIKED_FOOD_QUALITY_CHANGE * count_matching_foodtypes(foodtypes, eater.get_disliked_foodtypes())
food_quality += LIKED_FOOD_QUALITY_CHANGE * count_matching_foodtypes(foodtypes, eater.get_liked_foodtypes())

return food_quality

/// Get the number of matching food types in provided bitfields
/datum/component/edible/proc/count_matching_foodtypes(bitfield_one, bitfield_two)
var/count = 0
var/matching_bits = bitfield_one & bitfield_two
while (matching_bits > 0)
if (matching_bits & 1)
count++
matching_bits >>= 1
return count

///Delete the item when it is fully eaten
/datum/component/edible/proc/On_Consume(mob/living/eater, mob/living/feeder)
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.stamina.loss_as_percent < 75)
affected_mob.stamina.adjust(-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()
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)
46 changes: 46 additions & 0 deletions code/datums/mood_events/food_events.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/datum/mood_event/favorite_food
description = "I really enjoyed eating that."
mood_change = 5
timeout = 4 MINUTES

/datum/mood_event/gross_food
description = "I really didn't like that food."
mood_change = -2
timeout = 4 MINUTES

/datum/mood_event/disgusting_food
description = "That food was disgusting!"
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
timeout = 10 MINUTES

/* monkestation removal - food complexity is unported
/datum/mood_event/food
timeout = 5 MINUTES
var/quality = FOOD_QUALITY_NORMAL
/datum/mood_event/food/New(mob/M, ...)
. = ..()
mood_change = 2 + 2 * quality
description = "That food was [GLOB.food_quality_description[quality]]."
/datum/mood_event/food/nice
quality = FOOD_QUALITY_NICE
/datum/mood_event/food/good
quality = FOOD_QUALITY_GOOD
/datum/mood_event/food/verygood
quality = FOOD_QUALITY_VERYGOOD
/datum/mood_event/food/fantastic
quality = FOOD_QUALITY_FANTASTIC
/datum/mood_event/food/amazing
quality = FOOD_QUALITY_AMAZING
/datum/mood_event/food/top
quality = FOOD_QUALITY_TOP
*/
20 changes: 0 additions & 20 deletions code/datums/mood_events/needs_events.dm
Original file line number Diff line number Diff line change
Expand Up @@ -62,26 +62,6 @@
mood_change = -12

//Generic needs events
/datum/mood_event/favorite_food
description = "I really enjoyed eating that."
mood_change = 5
timeout = 4 MINUTES

/datum/mood_event/gross_food
description = "I really didn't like that food."
mood_change = -2
timeout = 4 MINUTES

/datum/mood_event/disgusting_food
description = "That food was disgusting!"
mood_change = -6
timeout = 4 MINUTES

/datum/mood_event/breakfast
description = "Nothing like a hearty breakfast to start the shift."
mood_change = 2
timeout = 10 MINUTES

/datum/mood_event/nice_shower
description = "I have recently had a nice shower."
mood_change = 4
Expand Down
Loading

0 comments on commit 09a8d5c

Please sign in to comment.