mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-25 22:19:30 +01:00
+37






![tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>](/assets/img/avatar_default.png)


Alexis
GitHub
tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>
ArcaneMusic
MrMelbert
Rhials
rageguy505
LemonInTheDark
John Willard
Aliceee2ch
Time-Green
Tsar-Salat
SmArtKar
Maxipat
SyncIt21
deltanedas
SimplyLogan
loganuk
Leland Kemble
FalloutFalcon
Roxy
Lucy
siliconOpossum
Isratosh
TheRyeGuyWhoWillNowDie
Neocloudy
Alexander V.
ElGitificador
Twaticus
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bloop
Cameron Lennox
Tim
Iamgoofball
Layzu666
Arturlang
_0Steven
mrmanlikesbt
Ben10Omintrix
John F. Kennedy
Cursor
Josh
Josh Powell
Yobrocharlie
Hardly3D
shayoki
LT3
21b4095dfd
Upstream 04/17/2026 fixes https://github.com/Bubberstation/Bubberstation/issues/5549 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com> Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com> Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com> Co-authored-by: rageguy505 <54517726+rageguy505@users.noreply.github.com> Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Co-authored-by: Aliceee2ch <160794176+Aliceee2ch@users.noreply.github.com> Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com> Co-authored-by: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com> Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Co-authored-by: Maxipat <108554989+Maxipat112@users.noreply.github.com> Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com> Co-authored-by: loganuk <fakeemail123@aol.com> Co-authored-by: Leland Kemble <70413276+lelandkemble@users.noreply.github.com> Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com> Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com> Co-authored-by: Lucy <lucy@absolucy.moe> Co-authored-by: siliconOpossum <138069572+siliconOpossum@users.noreply.github.com> Co-authored-by: Isratosh <Isratosh@hotmail.com> Co-authored-by: TheRyeGuyWhoWillNowDie <70169560+TheRyeGuyWhoWillNowDie@users.noreply.github.com> Co-authored-by: Neocloudy <88008002+Neocloudy@users.noreply.github.com> Co-authored-by: Alexander V. <volas@ya.ru> Co-authored-by: ElGitificador <168473461+ElGitificador@users.noreply.github.com> Co-authored-by: Twaticus <46540570+Twaticus@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> Co-authored-by: Cameron Lennox <killer65311@gmail.com> Co-authored-by: Tim <timothymtorres@gmail.com> Co-authored-by: Iamgoofball <iamgoofball@gmail.com> Co-authored-by: Layzu666 <121319428+Layzu666@users.noreply.github.com> Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com> Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com> Co-authored-by: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com> Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Co-authored-by: John F. Kennedy <54908920+MacaroniCritter@users.noreply.github.com> Co-authored-by: Cursor <102828457+theselfish@users.noreply.github.com> Co-authored-by: Josh <josh.adam.powell@gmail.com> Co-authored-by: Josh Powell <josh.powell@softwire.com> Co-authored-by: Yobrocharlie <Charliemiller5617@gmail.com> Co-authored-by: Hardly3D <66234359+Hardly3D@users.noreply.github.com> Co-authored-by: shayoki <96078776+shayoki@users.noreply.github.com> Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
792 lines
31 KiB
Plaintext
792 lines
31 KiB
Plaintext
/*!
|
|
|
|
This component makes it possible to make things edible. What this means is that you can take a bite or force someone to take a bite (in the case of items).
|
|
These items take a specific time to eat, and can do most of the things our original food items could.
|
|
|
|
Behavior that's still missing from this component that original food items had that should either be put into separate components or somewhere else:
|
|
Components:
|
|
Drying component (jerky etc)
|
|
Processable component (Slicing and cooking behavior essentialy, making it go from item A to B when conditions are met.)
|
|
|
|
Misc:
|
|
Something for cakes (You can store things inside)
|
|
*/
|
|
|
|
#define DEFAULT_EDIBLE_VOLUME 50
|
|
|
|
/datum/component/edible
|
|
dupe_mode = COMPONENT_DUPE_SOURCES
|
|
///Amount of reagents taken per bite
|
|
var/bite_consumption = 2
|
|
///Amount of bites taken so far
|
|
var/bitecount = 0
|
|
///Flags for food
|
|
var/food_flags = NONE
|
|
///Bitfield of the types of this food
|
|
var/foodtypes = NONE
|
|
///Amount of seconds it takes to eat this food
|
|
var/eat_time = 3 SECONDS
|
|
///Defines how much it lowers someones satiety (Need to eat, essentialy)
|
|
var/junkiness = 0
|
|
///Message to send when eating
|
|
var/list/eatverbs
|
|
///Callback to be ran for when you take a bite of something
|
|
var/datum/callback/after_eat
|
|
///Callback to be ran for when you finish eating something
|
|
var/datum/callback/on_consume
|
|
///Callback to be ran for when the code check if the food is liked, allowing for unique overrides for special foods like donuts with cops.
|
|
var/datum/callback/check_liked
|
|
///Last time we checked for food likes
|
|
var/last_check_time
|
|
///Assoc list of sources and their foodtypes
|
|
var/list/foodtypes_by_source
|
|
///Assoc list of sources and their food flags
|
|
var/list/food_flags_by_source
|
|
///Assoc list of sources and their junkiness
|
|
var/list/junkiness_by_source
|
|
|
|
/datum/component/edible/Initialize(
|
|
list/initial_reagents,
|
|
food_flags = NONE,
|
|
foodtypes = NONE,
|
|
volume = DEFAULT_EDIBLE_VOLUME,
|
|
eat_time = 1 SECONDS,
|
|
list/tastes,
|
|
list/eatverbs = list("bite", "chew", "nibble", "gnaw", "gobble", "chomp"),
|
|
bite_consumption = 2,
|
|
junkiness,
|
|
datum/callback/after_eat,
|
|
datum/callback/on_consume,
|
|
datum/callback/check_liked,
|
|
reagent_purity = 0.5,
|
|
)
|
|
if(!isatom(parent))
|
|
return COMPONENT_INCOMPATIBLE
|
|
|
|
// If these args are not explicitly stated when initializing the component
|
|
// Use the defaults provided in this proc definition, so we don't have to worry
|
|
// about these being null. We cannot rely on on_add_source() for this lest these
|
|
// end up being unwantedly overriden by other sources.
|
|
src.bite_consumption = bite_consumption
|
|
src.food_flags = food_flags
|
|
src.foodtypes = foodtypes
|
|
src.eat_time = eat_time
|
|
src.eatverbs = string_list(eatverbs)
|
|
|
|
/datum/component/edible/RegisterWithParent()
|
|
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(examine))
|
|
RegisterSignal(parent, COMSIG_ATOM_ATTACK_ANIMAL, PROC_REF(UseByAnimal))
|
|
RegisterSignal(parent, COMSIG_ATOM_ON_CRAFT, PROC_REF(OnCraft))
|
|
RegisterSignal(parent, COMSIG_OOZE_EAT_ATOM, PROC_REF(on_ooze_eat))
|
|
RegisterSignal(parent, COMSIG_FOOD_INGREDIENT_ADDED, PROC_REF(edible_ingredient_added))
|
|
RegisterSignal(parent, COMSIG_ATOM_CREATEDBY_PROCESSING, PROC_REF(created_by_processing))
|
|
RegisterSignal(parent, COMSIG_ATOM_FINALIZE_MATERIAL_EFFECTS, PROC_REF(on_material_effects))
|
|
RegisterSignal(parent, COMSIG_ATOM_FINALIZE_REMOVE_MATERIAL_EFFECTS, PROC_REF(on_remove_material_effects))
|
|
|
|
if(isturf(parent))
|
|
RegisterSignal(parent, COMSIG_ATOM_ENTERED, PROC_REF(on_entered))
|
|
else
|
|
var/static/list/loc_connections = list(COMSIG_ATOM_ENTERED = PROC_REF(on_entered))
|
|
AddComponent(/datum/component/connect_loc_behalf, parent, loc_connections)
|
|
|
|
if(isitem(parent))
|
|
RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(UseFromHand))
|
|
RegisterSignal(parent, COMSIG_ITEM_USED_AS_INGREDIENT, PROC_REF(used_to_customize))
|
|
|
|
else if(isturf(parent) || isstructure(parent))
|
|
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(TryToEatIt))
|
|
|
|
if(foodtypes & GORE)
|
|
ADD_TRAIT(parent, TRAIT_VALID_DNA_INFUSION, REF(src))
|
|
|
|
/datum/component/edible/UnregisterFromParent()
|
|
UnregisterSignal(parent, list(
|
|
COMSIG_ATOM_ATTACK_ANIMAL,
|
|
COMSIG_ATOM_ATTACK_HAND,
|
|
COMSIG_ATOM_ON_CRAFT,
|
|
COMSIG_ATOM_CREATEDBY_PROCESSING,
|
|
COMSIG_ATOM_ENTERED,
|
|
COMSIG_FOOD_INGREDIENT_ADDED,
|
|
COMSIG_ITEM_ATTACK,
|
|
COMSIG_ITEM_USED_AS_INGREDIENT,
|
|
COMSIG_OOZE_EAT_ATOM,
|
|
COMSIG_ATOM_EXAMINE,
|
|
))
|
|
|
|
qdel(GetComponent(/datum/component/connect_loc_behalf))
|
|
|
|
if(foodtypes & GORE)
|
|
REMOVE_TRAIT(parent, TRAIT_VALID_DNA_INFUSION, REF(src))
|
|
|
|
/datum/component/edible/allow_source_update(source)
|
|
return source == SOURCE_EDIBLE_INNATE
|
|
|
|
/datum/component/edible/on_source_add(
|
|
source,
|
|
list/initial_reagents,
|
|
food_flags,
|
|
foodtypes,
|
|
volume,
|
|
eat_time,
|
|
list/tastes,
|
|
list/eatverbs,
|
|
bite_consumption,
|
|
junkiness,
|
|
datum/callback/after_eat,
|
|
datum/callback/on_consume,
|
|
datum/callback/check_liked,
|
|
reagent_purity = 0.5,
|
|
)
|
|
. = ..()
|
|
|
|
var/recalculate = FALSE
|
|
if(!isnull(foodtypes))
|
|
if(LAZYACCESS(foodtypes_by_source, source)) //foodtypes being overriden
|
|
recalculate = TRUE
|
|
LAZYSET(foodtypes_by_source, source, foodtypes)
|
|
if(!isnull(food_flags))
|
|
if(LAZYACCESS(food_flags_by_source, source)) //food_flags being overridden
|
|
recalculate = TRUE
|
|
LAZYSET(food_flags_by_source, source, food_flags)
|
|
if(!isnull(junkiness))
|
|
src.junkiness += junkiness - LAZYACCESS(junkiness_by_source, source)
|
|
LAZYSET(junkiness_by_source, source, junkiness)
|
|
|
|
if(recalculate)
|
|
recalculate_food_flags()
|
|
else
|
|
// nothing is being removed
|
|
src.food_flags |= food_flags
|
|
src.foodtypes |= foodtypes
|
|
if(foodtypes & GORE)
|
|
ADD_TRAIT(parent, TRAIT_VALID_DNA_INFUSION, REF(src))
|
|
|
|
// add newly passed in reagents
|
|
setup_initial_reagents(initial_reagents, reagent_purity, tastes, volume)
|
|
|
|
//Only the innate source is allowed to change the following vars if there are a plurality of sources.
|
|
if(source != SOURCE_EDIBLE_INNATE && length(sources) > 1)
|
|
return
|
|
|
|
// add all new eatverbs to the list
|
|
if(islist(eatverbs))
|
|
var/list/cached_verbs = src.eatverbs
|
|
if(islist(cached_verbs))
|
|
// eatverbs becomes a combination of existing verbs and new ones
|
|
src.eatverbs = string_list(cached_verbs | eatverbs)
|
|
else
|
|
src.eatverbs = string_list(eatverbs)
|
|
// just set these directly
|
|
if(!isnull(bite_consumption))
|
|
src.bite_consumption = bite_consumption
|
|
if(!isnull(eat_time))
|
|
src.eat_time = eat_time
|
|
if(!isnull(after_eat))
|
|
src.after_eat = after_eat
|
|
if(!isnull(on_consume))
|
|
src.on_consume = on_consume
|
|
if(!isnull(check_liked))
|
|
src.check_liked = check_liked
|
|
|
|
/datum/component/edible/on_source_remove(source)
|
|
//rebuild the foodtypes and food_flags bitfields without the removed source
|
|
LAZYREMOVE(foodtypes_by_source, source)
|
|
LAZYREMOVE(food_flags_by_source, source)
|
|
junkiness -= LAZYACCESS(junkiness_by_source, source)
|
|
LAZYREMOVE(junkiness_by_source, source)
|
|
recalculate_food_flags()
|
|
return ..()
|
|
|
|
/datum/component/edible/proc/recalculate_food_flags()
|
|
foodtypes = NONE
|
|
food_flags = NONE
|
|
for(var/source_key in foodtypes_by_source)
|
|
foodtypes |= LAZYACCESS(foodtypes_by_source, source_key)
|
|
food_flags |= LAZYACCESS(food_flags_by_source, source_key)
|
|
if(foodtypes & GORE)
|
|
ADD_TRAIT(parent, TRAIT_VALID_DNA_INFUSION, REF(src))
|
|
else
|
|
REMOVE_TRAIT(parent, TRAIT_VALID_DNA_INFUSION, REF(src))
|
|
|
|
/datum/component/edible/Destroy(force)
|
|
after_eat = null
|
|
on_consume = null
|
|
check_liked = null
|
|
return ..()
|
|
|
|
/// Sets up the initial reagents of the food.
|
|
/datum/component/edible/proc/setup_initial_reagents(list/reagents, reagent_purity, list/tastes, volume)
|
|
var/atom/owner = parent
|
|
if(!owner.reagents)
|
|
owner.create_reagents(volume || DEFAULT_EDIBLE_VOLUME, INJECTABLE)
|
|
else if(volume > owner.reagents.maximum_volume)
|
|
owner.reagents.maximum_volume = volume
|
|
|
|
for(var/rid in reagents)
|
|
var/amount = reagents[rid]
|
|
if(length(tastes) && ispath(rid, /datum/reagent/consumable/nutriment))
|
|
var/datum/reagent/consumable/nutriment/nid = rid
|
|
if(initial(nid.carry_food_tastes))
|
|
owner.reagents.add_reagent(rid, amount, tastes.Copy(), added_purity = reagent_purity)
|
|
continue
|
|
owner.reagents.add_reagent(rid, amount, added_purity = reagent_purity)
|
|
|
|
/datum/component/edible/proc/examine(datum/source, mob/user, list/examine_list)
|
|
SIGNAL_HANDLER
|
|
|
|
var/atom/owner = parent
|
|
if(food_flags & FOOD_NO_EXAMINE)
|
|
return
|
|
if(foodtypes)
|
|
var/list/types = bitfield_to_list(foodtypes, FOOD_FLAGS)
|
|
examine_list += span_notice("It is [LOWER_TEXT(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.")
|
|
|
|
if(owner.reagents.total_volume > 0)
|
|
var/purity = owner.reagents.get_average_purity(/datum/reagent/consumable)
|
|
switch(purity)
|
|
if(0 to 0.2)
|
|
examine_list += span_warning("It is made of terrible ingredients shortening the effect...")
|
|
if(0.2 to 0.4)
|
|
examine_list += span_warning("It is made of synthetic ingredients shortening the effect.")
|
|
if(0.4 to 0.6)
|
|
examine_list += span_notice("It is made of average quality ingredients.")
|
|
if(0.6 to 0.8)
|
|
examine_list += span_green("It is made of organic ingredients prolonging the effect.")
|
|
if(0.8 to 1)
|
|
examine_list += span_green("It is made of finest ingredients prolonging the effect!")
|
|
|
|
var/datum/mind/mind = user.mind
|
|
if(mind && HAS_TRAIT_FROM(owner, TRAIT_FOOD_CHEF_MADE, REF(mind)))
|
|
examine_list += span_green("[owner] was made by you!")
|
|
|
|
if(!(food_flags & FOOD_IN_CONTAINER))
|
|
switch(bitecount)
|
|
if(0)
|
|
pass()
|
|
if(1)
|
|
examine_list += span_notice("[owner] was bitten by someone!")
|
|
if(2, 3)
|
|
examine_list += span_notice("[owner] was bitten [bitecount] times!")
|
|
else
|
|
examine_list += span_notice("[owner] was bitten multiple times!")
|
|
|
|
if(GLOB.debugging_enabled)
|
|
examine_list += span_notice("Reagent purities:")
|
|
for(var/datum/reagent/reagent as anything in owner.reagents.reagent_list)
|
|
examine_list += span_notice("- [reagent.name] [reagent.volume]u: [round(reagent.purity * 100)]% pure")
|
|
|
|
if(!HAS_TRAIT(user, TRAIT_REMOTE_TASTING))
|
|
return
|
|
var/fraction = min(bite_consumption / owner.reagents.total_volume, 1)
|
|
checkLiked(fraction, user)
|
|
if (!owner.reagents.get_reagent_amount(/datum/reagent/consumable/salt))
|
|
examine_list += span_notice("It could use a little more Sodium Chloride...")
|
|
if (isliving(user))
|
|
var/mob/living/living_user = user
|
|
living_user.taste_container(owner.reagents)
|
|
|
|
/datum/component/edible/proc/UseFromHand(obj/item/source, mob/living/M, mob/living/user)
|
|
SIGNAL_HANDLER
|
|
|
|
return TryToEat(M, user)
|
|
|
|
/datum/component/edible/proc/TryToEatIt(datum/source, mob/user)
|
|
SIGNAL_HANDLER
|
|
|
|
if (!in_range(source, user))
|
|
return
|
|
return TryToEat(user, user)
|
|
|
|
///Called when food is created through processing (Usually this means it was sliced). We use this to pass the OG items reagents.
|
|
/datum/component/edible/proc/created_by_processing(datum/source, atom/original_atom, list/chosen_processing_option)
|
|
SIGNAL_HANDLER
|
|
|
|
if(!original_atom.reagents)
|
|
return
|
|
|
|
var/atom/this_food = parent
|
|
|
|
//Make sure we have a reagent container large enough to fit the original atom's reagents.
|
|
var/volume = ROUND_UP(original_atom.reagents.maximum_volume / chosen_processing_option[TOOL_PROCESSING_AMOUNT])
|
|
|
|
this_food.create_reagents(volume, this_food.reagents?.flags)
|
|
original_atom.reagents.trans_to(this_food, original_atom.reagents.total_volume / chosen_processing_option[TOOL_PROCESSING_AMOUNT], copy_only = TRUE)
|
|
|
|
if(!HAS_TRAIT(this_food, TRAIT_FOOD_DONT_INHERIT_NAME_FROM_PROCESSED) && original_atom.name != initial(original_atom.name))
|
|
this_food.name = "slice of [original_atom.name]"
|
|
//It inherits the name of the original, which may already have a prefix
|
|
//So we need to make sure we don't double up on prefixes
|
|
//This is called before set_custom_materials() anyway
|
|
this_food.material_flags &= ~MATERIAL_ADD_PREFIX
|
|
if(original_atom.desc != initial(original_atom.desc))
|
|
this_food.desc = "[original_atom.desc]"
|
|
|
|
///Called when food is crafted through a crafting recipe datum.
|
|
/datum/component/edible/proc/OnCraft(datum/source, list/components, datum/crafting_recipe/food/recipe)
|
|
SIGNAL_HANDLER
|
|
|
|
var/atom/this_food = parent
|
|
for(var/obj/item/food/crafted_part in components)
|
|
if(!crafted_part.reagents)
|
|
continue
|
|
this_food.reagents.maximum_volume += crafted_part.reagents.maximum_volume
|
|
crafted_part.reagents.trans_to(this_food.reagents, crafted_part.reagents.maximum_volume)
|
|
|
|
this_food.reagents.maximum_volume = ROUND_UP(this_food.reagents.maximum_volume) // Just because I like whole numbers for this.
|
|
|
|
BLACKBOX_LOG_FOOD_MADE(parent.type)
|
|
|
|
///Makes sure the thing hasn't been destroyed or fully eaten to prevent eating phantom edibles
|
|
/datum/component/edible/proc/IsFoodGone(atom/owner, mob/living/feeder)
|
|
if(QDELETED(owner) || !(IS_EDIBLE(owner)))
|
|
return TRUE
|
|
if(owner.reagents.total_volume)
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/// Normal time to forcefeed someone something
|
|
#define EAT_TIME_FORCE_FEED (3 SECONDS)
|
|
/// Multiplier for eat time if the eater has TRAIT_VORACIOUS
|
|
#define EAT_TIME_VORACIOUS_MULT 0.65 // voracious folk eat 35% faster
|
|
/// Multiplier for how much longer it takes a voracious folk to eat while full
|
|
#define EAT_TIME_VORACIOUS_FULL_MULT 4 // Takes at least 4 times as long to eat while full, so dorks cant just clear out the kitchen before they get robusted
|
|
|
|
///All the checks for the act of eating itself and
|
|
/datum/component/edible/proc/TryToEat(mob/living/eater, mob/living/feeder)
|
|
|
|
set waitfor = FALSE
|
|
|
|
var/atom/owner = parent
|
|
|
|
if(feeder.combat_mode)
|
|
return
|
|
|
|
. = COMPONENT_CANCEL_ATTACK_CHAIN //Point of no return I suppose
|
|
|
|
if(IsFoodGone(owner, feeder))
|
|
return
|
|
|
|
if(!CanConsume(eater, feeder))
|
|
return
|
|
var/fullness = eater.get_fullness() + 10 //The theoretical fullness of the person eating if they were to eat this
|
|
|
|
var/time_to_eat = (eater == feeder) ? eat_time : EAT_TIME_FORCE_FEED
|
|
if(HAS_TRAIT(eater, TRAIT_VORACIOUS) && !HAS_TRAIT(eater, TRAIT_GLUTTON)) //with TRAIT_GLUTTON you consume food without delay
|
|
if(fullness < NUTRITION_LEVEL_FAT || (eater != feeder)) // No extra delay when being forcefed
|
|
time_to_eat *= EAT_TIME_VORACIOUS_MULT
|
|
else
|
|
time_to_eat *= (fullness / NUTRITION_LEVEL_FAT) * EAT_TIME_VORACIOUS_FULL_MULT // takes longer to eat the more well fed you are
|
|
if(eater == feeder)//If you're eating it yourself.
|
|
if(eat_time > 0 && !do_after(feeder, time_to_eat, eater, timed_action_flags = food_flags & FOOD_FINGER_FOOD ? IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE : NONE)) //Gotta pass the minimal eat time
|
|
return
|
|
if(IsFoodGone(owner, feeder))
|
|
return
|
|
var/eatverb = pick(eatverbs)
|
|
|
|
var/message_to_nearby_audience = ""
|
|
var/message_to_consumer = ""
|
|
var/message_to_blind_consumer = ""
|
|
|
|
if(junkiness && eater.satiety < -150 && eater.nutrition > NUTRITION_LEVEL_STARVING + 50 && !HAS_TRAIT(eater, TRAIT_VORACIOUS) && !HAS_TRAIT(eater, TRAIT_GLUTTON))
|
|
to_chat(eater, span_warning("You don't feel like eating any more junk food at the moment!"))
|
|
return
|
|
else if(fullness > (600 * (1 + eater.overeatduration / (4000 SECONDS)))) // The more you eat - the more you can eat
|
|
if(HAS_TRAIT(eater, TRAIT_VORACIOUS) || HAS_TRAIT(eater, TRAIT_GLUTTON))
|
|
message_to_nearby_audience = span_notice("[eater] voraciously forces \the [parent] down [eater.p_their()] throat.")
|
|
message_to_consumer = span_notice("You voraciously force \the [parent] down your throat.")
|
|
else
|
|
message_to_nearby_audience = span_warning("[eater] cannot force any more of \the [parent] to go down [eater.p_their()] throat!")
|
|
message_to_consumer = span_warning("You cannot force any more of \the [parent] to go down your throat!")
|
|
message_to_blind_consumer = message_to_consumer
|
|
eater.show_message(message_to_consumer, MSG_VISUAL, message_to_blind_consumer)
|
|
eater.visible_message(message_to_nearby_audience, ignored_mobs = eater)
|
|
//if we're too full, return because we can't eat whatever it is we're trying to eat
|
|
return
|
|
else if(fullness > 500)
|
|
if(HAS_TRAIT(eater, TRAIT_VORACIOUS))
|
|
message_to_nearby_audience = span_notice("[eater] [eatverb]s \the [parent].")
|
|
message_to_consumer = span_notice("You [eatverb] \the [parent].")
|
|
else
|
|
message_to_nearby_audience = span_notice("[eater] unwillingly [eatverb]s a bit of \the [parent].")
|
|
message_to_consumer = span_notice("You unwillingly [eatverb] a bit of \the [parent].")
|
|
else if(fullness > 150)
|
|
message_to_nearby_audience = span_notice("[eater] [eatverb]s \the [parent].")
|
|
message_to_consumer = span_notice("You [eatverb] \the [parent].")
|
|
else if(fullness > 50)
|
|
message_to_nearby_audience = span_notice("[eater] hungrily [eatverb]s \the [parent].")
|
|
message_to_consumer = span_notice("You hungrily [eatverb] \the [parent].")
|
|
else
|
|
message_to_nearby_audience = span_notice("[eater] hungrily [eatverb]s \the [parent], gobbling it down!")
|
|
message_to_consumer = span_notice("You hungrily [eatverb] \the [parent], gobbling it down!")
|
|
|
|
//if we're blind, we want to feel how hungrily we ate that food
|
|
message_to_blind_consumer = message_to_consumer
|
|
eater.show_message(message_to_consumer, MSG_VISUAL, message_to_blind_consumer)
|
|
eater.visible_message(message_to_nearby_audience, ignored_mobs = eater)
|
|
|
|
else //If you're feeding it to someone else.
|
|
if(isbrain(eater))
|
|
to_chat(feeder, span_warning("[eater] doesn't seem to have a mouth!"))
|
|
return
|
|
if(fullness <= (600 * (1 + eater.overeatduration / (2000 SECONDS))) || HAS_TRAIT(eater, TRAIT_VORACIOUS))
|
|
eater.visible_message(
|
|
span_danger("[feeder] attempts to [eater.get_bodypart(BODY_ZONE_HEAD) ? "feed [eater] [parent]." : "stuff [parent] down [eater]'s throat hole! Gross."]"),
|
|
span_userdanger("[feeder] attempts to [eater.get_bodypart(BODY_ZONE_HEAD) ? "feed you [parent]." : "stuff [parent] down your throat hole! Gross."]")
|
|
)
|
|
if(eater.is_blind())
|
|
to_chat(eater, span_userdanger("You feel someone trying to feed you something!"))
|
|
else
|
|
eater.visible_message(
|
|
span_danger("[feeder] cannot force any more of [parent] down [eater]'s [eater.get_bodypart(BODY_ZONE_HEAD) ? "throat!" : "throat hole! Eugh."]"),
|
|
span_userdanger("[feeder] cannot force any more of [parent] down your [eater.get_bodypart(BODY_ZONE_HEAD) ? "throat!" : "throat hole! Eugh."]")
|
|
)
|
|
if(eater.is_blind())
|
|
to_chat(eater, span_userdanger("You're too full to eat what's being fed to you!"))
|
|
return
|
|
if(!do_after(feeder, delay = time_to_eat, target = eater)) //Wait 3-ish seconds before you can feed
|
|
return
|
|
if(IsFoodGone(owner, feeder))
|
|
return
|
|
log_combat(feeder, eater, "fed", owner.reagents.get_reagent_log_string())
|
|
eater.visible_message(
|
|
span_danger("[feeder] forces [eater] to eat [parent]!"),
|
|
span_userdanger("[feeder] forces you to eat [parent]!")
|
|
)
|
|
if(eater.is_blind())
|
|
to_chat(eater, span_userdanger("You're forced to eat something!"))
|
|
|
|
TakeBite(eater, feeder)
|
|
|
|
//If we're not force-feeding and there's an eat delay, try take another bite
|
|
if(eater == feeder && eat_time > 0)
|
|
INVOKE_ASYNC(src, PROC_REF(TryToEat), eater, feeder)
|
|
|
|
#undef EAT_TIME_FORCE_FEED
|
|
#undef EAT_TIME_VORACIOUS_MULT
|
|
#undef EAT_TIME_VORACIOUS_FULL_MULT
|
|
|
|
///This function lets the eater take a bite and transfers the reagents to the eater.
|
|
/datum/component/edible/proc/TakeBite(mob/living/eater, mob/living/feeder)
|
|
|
|
var/atom/owner = parent
|
|
|
|
if(!owner.reagents)
|
|
stack_trace("[eater] failed to bite [owner], because [owner] had no reagents.")
|
|
return FALSE
|
|
if(eater.satiety > -200)
|
|
eater.satiety -= junkiness
|
|
playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE)
|
|
if(!owner.reagents.total_volume)
|
|
return
|
|
SEND_SIGNAL(eater, COMSIG_LIVING_EAT_FOOD, owner)
|
|
var/sig_return = SEND_SIGNAL(parent, COMSIG_FOOD_EATEN, eater, feeder, bitecount, bite_consumption)
|
|
if(sig_return & DESTROY_FOOD)
|
|
qdel(owner)
|
|
return
|
|
|
|
//Give a buff when the dish is hand-crafted and unbitten
|
|
if(bitecount == 0)
|
|
apply_buff(eater)
|
|
|
|
var/fraction = 0.3
|
|
fraction = min(bite_consumption / owner.reagents.total_volume, 1)
|
|
owner.reagents.trans_to(eater, bite_consumption, transferred_by = feeder, methods = INGEST)
|
|
var/atom/movable/screen/hunger/hunger_bar = eater.hud_used?.screen_objects[HUD_MOB_HUNGER]
|
|
if (istype(hunger_bar))
|
|
hunger_bar.update_hunger_bar()
|
|
bitecount++
|
|
|
|
checkLiked(fraction, eater)
|
|
|
|
check_materials(eater, fraction)
|
|
|
|
if(!owner.reagents.total_volume)
|
|
On_Consume(eater, feeder)
|
|
|
|
//Invoke our after eat callback if it is valid
|
|
after_eat?.Invoke(eater, feeder, bitecount)
|
|
|
|
//Invoke the eater's stomach's after_eat callback if valid
|
|
if(iscarbon(eater))
|
|
var/mob/living/carbon/carbon_eater = eater
|
|
var/obj/item/organ/stomach/stomach = carbon_eater.get_organ_slot(ORGAN_SLOT_STOMACH)
|
|
if(istype(stomach))
|
|
stomach.after_eat(owner)
|
|
|
|
return TRUE
|
|
|
|
///Perform operations based on materials and/or if the parent object is a stack.
|
|
/datum/component/edible/proc/check_materials(mob/living/carbon/eater, fraction)
|
|
var/atom/owner = parent
|
|
var/is_stack = isstack(owner)
|
|
|
|
//food may also apply golem buffs if it contains certain materials and the mob has the required trait
|
|
if(owner.custom_materials && HAS_TRAIT(eater, TRAIT_ROCK_EATER))
|
|
for(var/datum/material/material as anything in owner.custom_materials)
|
|
var/effect_stack_amount = (owner.custom_materials[material] * fraction) / SHEET_MATERIAL_AMOUNT
|
|
var/datum/golem_food_buff/effect
|
|
effect = GLOB.golem_stack_food_directory[material.sheet_type || material.ore_type]
|
|
if(effect?.can_consume(eater))
|
|
effect.on_consumption(eater, owner, effect_stack_amount)
|
|
|
|
if(fraction >= 1) //don't bother if the item is about to be deleted anyway...
|
|
return
|
|
|
|
if(is_stack) //stacks use up sheets, which recalulates the materials already.
|
|
var/obj/item/stack/stack = owner
|
|
stack.use(CEILING(stack.amount * fraction, 1))
|
|
else if(owner.custom_materials)
|
|
owner.set_custom_materials(owner.custom_materials, 1 - fraction)
|
|
|
|
///Checks whether or not the eater can actually consume the food
|
|
/datum/component/edible/proc/CanConsume(mob/living/carbon/eater, mob/living/feeder)
|
|
if(!iscarbon(eater))
|
|
return FALSE
|
|
if(eater.is_mouth_covered())
|
|
eater.balloon_alert(feeder, "mouth is covered!")
|
|
return FALSE
|
|
|
|
var/atom/food = parent
|
|
|
|
if(food.flags_1 & HOLOGRAM_1)
|
|
if(eater == feeder)
|
|
to_chat(eater, span_notice("You try to take a bite out of [food], but it fades away!"))
|
|
else
|
|
to_chat(feeder, span_notice("You try to feed [eater] [food], but it fades away!"))
|
|
|
|
qdel(food)
|
|
return FALSE
|
|
|
|
if(SEND_SIGNAL(eater, COMSIG_CARBON_ATTEMPT_EAT, food) & BLOCK_EAT_ATTEMPT)
|
|
return FALSE
|
|
if(SEND_SIGNAL(food, COMSIG_FOOD_ATTEMPT_EAT, eater, feeder) & BLOCK_EAT_ATTEMPT)
|
|
return FALSE
|
|
return TRUE
|
|
|
|
///Applies food buffs according to the crafting complexity
|
|
/datum/component/edible/proc/apply_buff(mob/eater)
|
|
var/buff
|
|
var/recipe_complexity = get_recipe_complexity()
|
|
if(recipe_complexity <= 0)
|
|
return
|
|
var/obj/item/food/food = parent
|
|
if(istype(food) && !isnull(food.crafted_food_buff))
|
|
buff = food.crafted_food_buff
|
|
else
|
|
buff = pick_weight(GLOB.food_buffs[min(recipe_complexity, FOOD_COMPLEXITY_5)])
|
|
if(!isnull(buff))
|
|
var/mob/living/living_eater = eater
|
|
var/atom/owner = parent
|
|
var/timeout_mod = owner.reagents.get_average_purity(/datum/reagent/consumable) * 2 // buff duration is 100% at average purity of 50%
|
|
var/strength = recipe_complexity
|
|
living_eater.apply_status_effect(buff, timeout_mod, strength)
|
|
|
|
///Check foodtypes to see if we should send a moodlet
|
|
/datum/component/edible/proc/checkLiked(fraction, mob/eater)
|
|
if(last_check_time + 50 > world.time)
|
|
return FALSE
|
|
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)
|
|
if(food_quality <= FOOD_QUALITY_DANGEROUS && gourmand.check_allergic_reaction(foodtypes, chance = 100, histamine_add = 10))
|
|
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
|
|
|
|
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%
|
|
gourmand.add_mood_event("quality_food", /datum/mood_event/food, food_quality, 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()
|
|
var/list/extra_complexity = list(0)
|
|
SEND_SIGNAL(parent, COMSIG_FOOD_GET_EXTRA_COMPLEXITY, extra_complexity)
|
|
var/complexity_to_add = extra_complexity[1]
|
|
if(!HAS_TRAIT(parent, TRAIT_FOOD_CHEF_MADE) || !istype(parent, /obj/item/food))
|
|
return complexity_to_add // It is factory made. Soulless.
|
|
var/obj/item/food/food = parent
|
|
return food.crafting_complexity + complexity_to_add
|
|
|
|
/// Get food quality adjusted according to eater's preferences
|
|
/datum/component/edible/proc/get_perceived_food_quality(mob/living/eater)
|
|
var/food_quality = get_recipe_complexity()
|
|
var/list/extra_quality = list()
|
|
SEND_SIGNAL(eater, COMSIG_LIVING_GET_PERCEIVED_FOOD_QUALITY, src, extra_quality)
|
|
for(var/quality in extra_quality)
|
|
food_quality += quality
|
|
|
|
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
|
|
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 min(food_quality, FOOD_QUALITY_TOP)
|
|
|
|
/// 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)
|
|
SEND_SIGNAL(parent, COMSIG_FOOD_CONSUMED, eater, feeder)
|
|
SEND_SIGNAL(eater, COMSIG_LIVING_FINISH_EAT, parent, feeder)
|
|
|
|
on_consume?.Invoke(eater, feeder)
|
|
if (QDELETED(parent)) // might be destroyed by the callback
|
|
return
|
|
|
|
to_chat(feeder, span_warning("There is nothing left of [parent], oh no!"))
|
|
if(isturf(parent))
|
|
var/turf/T = parent
|
|
T.ScrapeAway(1, CHANGETURF_INHERIT_AIR)
|
|
else
|
|
qdel(parent)
|
|
|
|
///Ability to feed food to puppers
|
|
/datum/component/edible/proc/UseByAnimal(datum/source, mob/living/basic/pet/dog/doggy)
|
|
SIGNAL_HANDLER
|
|
|
|
if(!isdog(doggy) || (food_flags & FOOD_NO_BITECOUNT)) //this entirely relies on bitecounts alas
|
|
return
|
|
|
|
var/atom/food = parent
|
|
|
|
if(food.flags_1 & HOLOGRAM_1)
|
|
to_chat(doggy, span_notice("You try to take a bite out of [food], but it fades away!"))
|
|
qdel(food)
|
|
return
|
|
|
|
if(bitecount == 0 || prob(50))
|
|
doggy.manual_emote("nibbles away at \the [food].")
|
|
bitecount++
|
|
. = COMPONENT_CANCEL_ATTACK_CHAIN
|
|
|
|
doggy.taste_container(food.reagents) // why should carbons get all the fun?
|
|
if(bitecount >= 5)
|
|
var/satisfaction_text = pick("burps from enjoyment.", "yaps for more!", "woofs twice.", "looks at the area where \the [food] was.")
|
|
doggy.manual_emote(satisfaction_text)
|
|
qdel(food)
|
|
|
|
///Ability to feed food to puppers
|
|
/datum/component/edible/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
|
|
SIGNAL_HANDLER
|
|
if(QDELETED(parent))
|
|
return
|
|
SEND_SIGNAL(parent, COMSIG_FOOD_CROSSED, arrived, bitecount)
|
|
|
|
///Response to being used to customize something
|
|
/datum/component/edible/proc/used_to_customize(datum/source, atom/customized)
|
|
SIGNAL_HANDLER
|
|
|
|
SEND_SIGNAL(customized, COMSIG_FOOD_INGREDIENT_ADDED, src)
|
|
|
|
///Response to an edible ingredient being added to parent.
|
|
/datum/component/edible/proc/edible_ingredient_added(datum/source, datum/component/edible/ingredient)
|
|
SIGNAL_HANDLER
|
|
|
|
InheritComponent(ingredient, TRUE)
|
|
|
|
/// Response to oozes trying to eat something edible
|
|
/datum/component/edible/proc/on_ooze_eat(datum/source, mob/eater, edible_flags)
|
|
SIGNAL_HANDLER
|
|
|
|
var/atom/food = parent
|
|
|
|
if(food.flags_1 & HOLOGRAM_1)
|
|
to_chat(eater, span_notice("You try to take a bite out of [food], but it fades away!"))
|
|
qdel(food)
|
|
return COMPONENT_ATOM_EATEN
|
|
|
|
if(foodtypes & edible_flags)
|
|
food.reagents.trans_to(eater, food.reagents.total_volume, transferred_by = eater)
|
|
eater.visible_message(span_warning("[eater] eats [food]!"), span_notice("You eat [food]."))
|
|
playsound(get_turf(eater),'sound/items/eatfood.ogg', rand(30,50), TRUE)
|
|
qdel(food)
|
|
return COMPONENT_ATOM_EATEN
|
|
|
|
#define REQUIRED_MAT_FLAGS (MATERIAL_EFFECTS|MATERIAL_NO_EDIBILITY)
|
|
|
|
///Calls on_edible_applied() for the main material composing the atom parent
|
|
/datum/component/edible/proc/on_material_effects(atom/source, list/materials, datum/material/main_material)
|
|
SIGNAL_HANDLER
|
|
if((source.material_flags & REQUIRED_MAT_FLAGS) == REQUIRED_MAT_FLAGS)
|
|
main_material.on_edible_applied(source, src)
|
|
|
|
///Calls on_edible_removed() for the main material no longer composing the atom parent
|
|
/datum/component/edible/proc/on_remove_material_effects(atom/source, list/materials, datum/material/main_material)
|
|
SIGNAL_HANDLER
|
|
if((source.material_flags & REQUIRED_MAT_FLAGS) == REQUIRED_MAT_FLAGS)
|
|
main_material.on_edible_removed(source, src)
|
|
|
|
#undef REQUIRED_MAT_FLAGS
|
|
#undef DEFAULT_EDIBLE_VOLUME
|