diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index c4c361c0e71..d0bc71e4827 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -657,7 +657,7 @@ //Food -///from base of obj/item/reagent_containers/food/snacks/attack() & Edible component: (mob/living/eater, mob/feeder) +///from base of obj/item/reagent_containers/food/snacks/attack() & Edible component: (mob/living/eater, mob/feeder, bitecount, bitesize) #define COMSIG_FOOD_EATEN "food_eaten" #define COMSIG_ITEM_FRIED "item_fried" diff --git a/code/datums/components/food/edible.dm b/code/datums/components/food/edible.dm index 10b645b001c..4886c8b4df4 100644 --- a/code/datums/components/food/edible.dm +++ b/code/datums/components/food/edible.dm @@ -214,7 +214,7 @@ Behavior that's still missing from this component that original food items had t eater.satiety -= junkiness playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE) if(owner.reagents.total_volume) - SEND_SIGNAL(parent, COMSIG_FOOD_EATEN, eater, feeder) + SEND_SIGNAL(parent, COMSIG_FOOD_EATEN, eater, feeder, bitecount, bite_consumption) var/fraction = min(bite_consumption / owner.reagents.total_volume, 1) owner.reagents.trans_to(eater, bite_consumption, transfered_by = feeder, methods = INGEST) bitecount++ diff --git a/code/datums/components/storage/food_storage.dm b/code/datums/components/storage/food_storage.dm new file mode 100644 index 00000000000..63c86bdc9dd --- /dev/null +++ b/code/datums/components/storage/food_storage.dm @@ -0,0 +1,189 @@ +/// --Food storage component-- +/// This component lets you slide one item into large foods, such as bread, cheese wheels, or cakes. +/// Consuming food storages with an item inside can cause unique interactions, such as eating glass shards. + +/datum/component/food_storage + /// Reference to what we have in our food. + var/obj/item/stored_item + /// The amount of volume the food has on creation - Used for probabilities + var/initial_volume = 10 + /// Minimum size items that can be inserted + var/minimum_weight_class = WEIGHT_CLASS_SMALL + /// What are the odds we bite into the stored item? + var/bad_chance_of_discovery = 0 + /// What are the odds we see the stored item before we bite it? + var/good_chance_of_discovery = 100 + /// The stored item was found out somehow. + var/discovered = FALSE + +/datum/component/food_storage/Initialize(_minimum_weight_class = WEIGHT_CLASS_SMALL, _bad_chance = 0, _good_chance = 100) + + RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/try_inserting_item) + RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/try_removing_item) + RegisterSignal(parent, COMSIG_FOOD_EATEN, .proc/consume_food_storage) + + var/atom/food = parent + initial_volume = food.reagents.total_volume + + minimum_weight_class = _minimum_weight_class + bad_chance_of_discovery = _bad_chance + good_chance_of_discovery = _good_chance + +/datum/component/food_storage/Destroy(force, silent) + if(stored_item) + stored_item.forceMove(stored_item.drop_location()) + stored_item = null + . = ..() + +/** Begins the process of inserted an item. + * + * Clicking on the food storage with an item on disarm intent will begin a do_after, which if successful inserts the item. + * + * Arguments + * inserted_item - the item being placed into the food + * user - the person inserting the item + */ +/datum/component/food_storage/proc/try_inserting_item(datum/source, obj/item/inserted_item, mob/user, params) + SIGNAL_HANDLER + + // No matryoshka-ing food storage + if(istype(inserted_item, /obj/item/storage) || IS_EDIBLE(inserted_item)) + return + + if(inserted_item.w_class > minimum_weight_class) + to_chat(user, "\The [inserted_item.name] won't fit in \the [parent].") + return + + if(!QDELETED(stored_item)) + to_chat(user, "There's something in \the [parent].") + return + + user.visible_message("[user.name] begins inserting [inserted_item.name] into \the [parent].", \ + "You start to insert the [inserted_item.name] into \the [parent].") + + INVOKE_ASYNC(src, .proc/insert_item, inserted_item, user) + return COMPONENT_ITEM_NO_ATTACK + +/** Begins the process of attempting to remove the stored item. + * + * Clicking on food storage on grab intent will begin a do_after, which if successful removes the stored_item. + * + * Arguments + * user - the person removing the item. + */ +/datum/component/food_storage/proc/try_removing_item(datum/source, mob/user) + SIGNAL_HANDLER + + if(user.a_intent != INTENT_GRAB) + return + + if(QDELETED(stored_item)) + return + + user.visible_message("[user.name] begins tearing at \the [parent].", \ + "You start to rip into \the [parent].") + + INVOKE_ASYNC(src, .proc/begin_remove_item, user) + return COMPONENT_ITEM_NO_ATTACK + +/** Inserts the item into the food, after a do_after. + * + * Arguments + * inserted_item - The item being inserted. + * user - the person inserting the item. + */ +/datum/component/food_storage/proc/insert_item(obj/item/inserted_item, mob/user) + if(do_after(user, 1.5 SECONDS, target = parent)) + var/atom/food = parent + to_chat(user, "You slip [inserted_item.name] inside \the [parent].") + inserted_item.forceMove(food) + user.log_message("[key_name(user)] inserted [inserted_item] into [parent] at [AREACOORD(user)]", LOG_ATTACK) + food.add_fingerprint(user) + inserted_item.add_fingerprint(user) + + stored_item = inserted_item + +/** Removes the item from the food, after a do_after. + * + * Arguments + * user - person removing the item. + */ +/datum/component/food_storage/proc/begin_remove_item(mob/user) + if(do_after(user, 10 SECONDS, target = parent)) + remove_item(user) + +/** + * Removes the stored item, putting it in user's hands or on the ground, then updates the reference. + */ +/datum/component/food_storage/proc/remove_item(mob/user) + if(user.put_in_hands(stored_item)) + user.visible_message("[user.name] slowly pulls [stored_item.name] out of \the [parent].", \ + "You slowly pull [stored_item.name] out of \the [parent].") + else + stored_item.visible_message("[stored_item.name] falls out of \the [parent].") + + update_stored_item() + +/** Checks for stored items when the food is eaten. + * + * If the food is eaten while an item is stored in it, calculates the odds that the item will be found. + * Then, if the item is found before being bitten, the item is removed. + * If the item is found by biting into it, calls on_accidental_consumption on the stored item. + * Afterwards, removes the item from the food if it was discovered. + * + * Arguments + * target - person doing the eating (can be the same as user) + * user - person causing the eating to happen + * bitecount - how many times the current food has been bitten + * bitesize - how large bties are for this food + */ +/datum/component/food_storage/proc/consume_food_storage(datum/source, mob/living/target, mob/living/user, bitecount, bitesize) + SIGNAL_HANDLER + + if(QDELETED(stored_item)) //if the stored item was deleted/null... + if(!update_stored_item()) //check if there's a replacement item + return + + /// Chance of biting the held item = amount of bites / (intitial reagents / reagents per bite) * 100 + bad_chance_of_discovery = (bitecount / (initial_volume / bitesize))*100 + /// Chance of finding the held item = bad chance - 50 + good_chance_of_discovery = bad_chance_of_discovery - 50 + + if(prob(good_chance_of_discovery)) //finding the item, without biting it + discovered = TRUE + to_chat(target, "It feels like there's something in \the [parent]...!") + + else if(prob(bad_chance_of_discovery)) //finding the item, BY biting it + user.log_message("[key_name(user)] just fed [key_name(target)] a/an [stored_item] which was hidden in [parent] at [AREACOORD(target)]", LOG_ATTACK) + discovered = stored_item.on_accidental_consumption(target, user, parent) + update_stored_item() //make sure if the item was changed, the reference changes as well + + if(!QDELETED(stored_item) && discovered) + INVOKE_ASYNC(src, .proc/remove_item, user) + +/** Updates the reference of the stored item. + * + * Checks the food's contents for if an alternate item was placed into the food. + * If there is an alternate item, updates the reference to the new item. + * If there isn't, updates the reference to null. + * + * Returns FALSE if the ref is nulled, or TRUE is another item replaced it. + */ +/datum/component/food_storage/proc/update_stored_item() + var/atom/food = parent + if(!food?.contents.len) //if there's no items in the food or food is deleted somehow + stored_item = null + return FALSE + + for(var/obj/item/i in food.contents) //search the food's contents for a replacement item + if(istype(i, /obj/item/reagent_containers/food/snacks)) + continue + if(QDELETED(i)) + continue + + stored_item = i //we found something to replace it + return TRUE + + //if there's nothing else in the food, or we found nothing valid + stored_item = null + return FALSE diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index f93f7754a35..48dd5f90214 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -1079,18 +1079,16 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb if(custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)] >= total_material_amount * 0.60) if(prob(66)) //66% chance to break it /// The glass shard that is spawned into the source item - var/obj/item/shard/broken_glass = new /obj/item/shard(src) + var/obj/item/shard/broken_glass = new /obj/item/shard(loc) broken_glass.name = "broken [name]" broken_glass.desc = "This used to be \a [name], but it sure isn't anymore." playsound(M, "shatter", 25, TRUE) qdel(src) - if(S) - S.contents += broken_glass //puts the glass back into the source item - else + if(QDELETED(S)) broken_glass.on_accidental_consumption(M, user) else //33% chance to just "crack" it (play a sound) and leave it in the bread playsound(M, "shatter", 15, TRUE) - discover_after = FALSE + discover_after = FALSE M.adjust_disgust(33) M.visible_message("[M] looks like [M.p_theyve()] just bitten into something hard.", \ @@ -1113,10 +1111,6 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb else to_chat(M, "[source_item? "Something strange was in the \the [source_item]..." : "I just bit something strange..."] ") - //Just in case - can't discover something that doesn't exist - if(QDELETED(src)) - return FALSE - return discover_after #undef MAX_BONUS_MATS_PER_BITE diff --git a/code/game/objects/items/food/bread.dm b/code/game/objects/items/food/bread.dm index 0af5510a2c7..3e8c947d5fc 100644 --- a/code/game/objects/items/food/bread.dm +++ b/code/game/objects/items/food/bread.dm @@ -9,6 +9,7 @@ /obj/item/food/bread/Initialize() . = ..() AddElement(/datum/element/dunkable, 10) + AddComponent(/datum/component/food_storage) /obj/item/food/breadslice icon = 'icons/obj/food/burgerbread.dmi' diff --git a/code/modules/food_and_drinks/food/snacks.dm b/code/modules/food_and_drinks/food/snacks.dm index a189ea0f321..c9929b20eb9 100644 --- a/code/modules/food_and_drinks/food/snacks.dm +++ b/code/modules/food_and_drinks/food/snacks.dm @@ -155,7 +155,7 @@ All foods are distributed among various categories. Use common sense. M.satiety -= junkiness playsound(M.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE) if(reagents.total_volume) - SEND_SIGNAL(src, COMSIG_FOOD_EATEN, M, user) + SEND_SIGNAL(src, COMSIG_FOOD_EATEN, M, user, bitecount, bitesize) var/fraction = min(bitesize / reagents.total_volume, 1) reagents.trans_to(M, bitesize, transfered_by = user, methods = INGEST) bitecount++ @@ -340,78 +340,10 @@ All foods are distributed among various categories. Use common sense. /// All the food items that can store an item inside itself, like bread or cake. /obj/item/reagent_containers/food/snacks/store w_class = WEIGHT_CLASS_NORMAL - /// If an item has been stored in the food - var/stored_item = FALSE - /// The amount of volume the food has on creation - var/volume_on_creation = 0 - /// Allows someone to bypass the small weight class requirement, so they can put whatever they want into a slice of bread - var/bypass_weight_limit = FALSE /obj/item/reagent_containers/food/snacks/store/Initialize() . = ..() - if(reagents?.total_volume) - volume_on_creation = reagents.total_volume - -/obj/item/reagent_containers/food/snacks/store/attackby(obj/item/W, mob/user, params) - ..() - if(istype(W, /obj/item/reagent_containers/food/snacks)) //can't slip snacks inside, they're used for custom foods. - return FALSE - - if((bypass_weight_limit || W.w_class <= WEIGHT_CLASS_SMALL)) - if(W.get_sharpness() && user.a_intent != INTENT_DISARM) - return FALSE - if(istype(W, /obj/item/storage)) - return FALSE - if(stored_item) - return FALSE - if(!iscarbon(user)) - return FALSE - if(contents.len >= 20) - to_chat(user, "[src] is full.") - return FALSE - user.visible_message("[user.name] begins inserting [W] into [src].", \ - "You start to insert the [W] into \the [src].") - if(!do_after(user, 1.5 SECONDS, target = src)) - return FALSE - to_chat(user, "You slip [W] inside [src].") - user.transferItemToLoc(W, src) - log_message("[key_name(user)] inserted [W.name] into [src.name] at [AREACOORD(src)]", LOG_ATTACK) - add_fingerprint(user) - contents += W - stored_item = TRUE - return TRUE // no afterattack here - -/obj/item/reagent_containers/food/snacks/store/attack(mob/living/carbon/M, mob/living/carbon/user, def_zone) - if(!..()) - return - /// What are the odds we eat glass? - [Bitecount / Max number of bites] * 100 - var/bad_chance_of_discovery = (bitecount / (volume_on_creation / bitesize))*100 //the closer you get to finishing it, the higher the chance you bite into it - /// What are the odds we see the glass but don't bite it? - ([Bitecount / Max number of bites] * 100) - 50 - var/good_chance_of_discovery = bad_chance_of_discovery - 50 //the closer you get to finishing it, the more likely you can see what is in it - /// We've found the item, and plan on remove it - var/discovered = FALSE - - if(stored_item) - for(var/obj/item/I in contents) - if(istype(I, /obj/item/reagent_containers/food/snacks)) - return FALSE - if(prob(good_chance_of_discovery)) - discovered = TRUE - to_chat(M, "It feels like there's something in this [src.name]...!") - - else if(prob(bad_chance_of_discovery)) - log_message("[key_name(user)] just fed [key_name(M)] a/an [I.name] which was hidden in [src.name] at [AREACOORD(src)]", LOG_ATTACK) - discovered = I.on_accidental_consumption(M, user, src) - - if(!QDELETED(I) && discovered) - contents -= I - stored_item = FALSE - if(M.put_in_hands(I)) //the moment when you slowly pull out whatever you just bit into in your food - to_chat(M, "You slowly pull [I] out of \the [src].") - else - to_chat(M, "[I] falls out of \the [src].") - - return FALSE + AddComponent(/datum/component/food_storage) /obj/item/reagent_containers/food/snacks/MouseDrop(atom/over) var/turf/T = get_turf(src) diff --git a/tgstation.dme b/tgstation.dme index 5ed72b2934a..d0ad423c71e 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -497,6 +497,7 @@ #include "code\datums\components\plumbing\filter.dm" #include "code\datums\components\plumbing\reaction_chamber.dm" #include "code\datums\components\plumbing\splitter.dm" +#include "code\datums\components\storage\food_storage.dm" #include "code\datums\components\storage\storage.dm" #include "code\datums\components\storage\concrete\_concrete.dm" #include "code\datums\components\storage\concrete\bag_of_holding.dm"