From f9fabdf1b6b52462ca64aabe338fa1655ff7cccb Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Sun, 17 Sep 2023 21:38:53 +0200 Subject: [PATCH] [MIRROR] Refactors & patches for grinding & juicing [MDB IGNORE] (#23760) * Refactors & patches for grinding & juicing (#78268) ## About The Pull Request This deals with grinding & juicing in 4 stages **1. General grinding & juicing** Nothing player facing, just added some extra null checks to ensure we use `reagents` and `target_holder` only when they are not null. The current way it was setup did not check for this **2. Grinding Stacks** - Fixes #48387 - Fixes #78180 - Fixes #77878 This changes the way stacks are grinded. Rather than grinding the whole stack and have reagents wasted from grinding because there isn't enough space in the beaker we do the reverse. We calculate how many pieces of cable(or sheets of material) can be grinded "based" on the available volume inside the `target_holder`(i.e. beaker for all in 1 grinder, or internal buffer for chemical plumbing grinder, mortar & pedestal) **For example** Say you have a beaker of 100 ml capacity and you have a stack of 50 iron sheets where each sheet of iron when grinded yields 20 units of iron reagent. Doing some simple math we should only be able to grind 5 sheets of iron(20 units of iron per sheet x 5 = 100 ml capacity). This means the remaining 45 sheets of iron should be left untouched and we should be able to eject and regrind them in a different beaker if we don't have the space for it. This PR does exactly that. It computes how many pieces/sheets can be grinded based on the available volume for grinding (e.g. based on the available volume in your beaker for the all in 1 grinder) and grinds exactly that many pieces, leaving the rest of stack untouched so you can reuse them This way you avoid wasting stacks when your beaker doesn't have the required space to hold its reagents **3. Plumbing Chemical Grinder** - Not sure how nobody noticed but the plumbing chemical grinder completely stopped working because wrong arguments were passed to the items grind & juice procs https://github.com/tgstation/tgstation/blob/2ddbdca1b7fb5cb85cbdcd566a489cbc4794edcf/code/modules/plumbing/plumbers/grinder_chemical.dm#L47 When it fact it should have been `I.grind(reagents, usr)` So yeah the plumbing chemical grinder works again - Fixes #75429 The plumbing chemical grinder now blocks anything that isn't an `obj/item` from entering inside it. The `grind` proc is set up to accept only items anyway so allowing mobs to enter is just a waste of processing power. That way nobody gets stuck inside, - Fixes #62822 The chemical grinder now accepts items coming at it from any direction. if you don't want to throw stuff at it you can manually put stuff in it with hand. If you try to use a storage item like a bag(plant bag or any bag) it dumps all its contents in the grinder. **4. All in 1 Grinder** - Fixes #76983 You can now remove the beaker when the blender is unpowered with right click. The left click does not work when power is off because `ui_interact()` proc is disabled which was responsible for ejecting the beaker & its contents. The right click was meant to compensate for this but it also checked if power was available and it also failed. Now that check has been removed meaning you can eject the beaker & its contents via right click - Fixes #54813 The delay and shake animation is already handled by the `operate()` proc but the mix settings would add an additional timer on top of that with a fixed delay of 50 deciseconds. That timer is adjusted so its reduced from upgraded parts ## Changelog :cl: code: added some null checks for general juicing & grinding items fix: grinding stacks now grinds as many pieces/sheets from the stack as possible that can fit in a beaker/container without wasting the whole stack fix: plumbing chemical grinder now actually works again fix: the plumbing chemical grinder allows stuff to enter from any direction but not mobs and also accepts items put inside it via hand including bags fix: You can remove the beaker from the all in 1 grinder when power is off via right click fix: All in 1 grinder now mixes faster with upgraded parts refactor: you can no longer walk into a plumbing chemical grinder /:cl: --------- Co-authored-by: Jeremiah <42397676+jlsnow301@ users.noreply.github.com> * Refactors & patches for grinding & juicing --------- Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> Co-authored-by: Jeremiah <42397676+jlsnow301@ users.noreply.github.com> --- code/game/objects/items.dm | 16 ++-- code/game/objects/items/stacks/stack.dm | 43 ++++++++-- .../plumbing/plumbers/grinder_chemical.dm | 36 ++++++--- code/modules/reagents/chemistry/holder.dm | 4 +- .../chemistry/machinery/reagentgrinder.dm | 80 ++++++++++--------- .../reagents/reagent_containers/cups/_cup.dm | 5 +- 6 files changed, 117 insertions(+), 67 deletions(-) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 15d8af848c0..c10c04a9fa1 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -984,11 +984,10 @@ /obj/item/proc/grind(datum/reagents/target_holder, mob/user) if(on_grind() == -1) return FALSE - if(!reagents) - reagents = new() - target_holder.add_reagent_list(grind_results) - if(reagents && target_holder) - reagents.trans_to(target_holder, reagents.total_volume, transferred_by = user) + if(target_holder) + target_holder.add_reagent_list(grind_results) + if(reagents) + reagents.trans_to(target_holder, reagents.total_volume, transferred_by = user) return TRUE ///Called BEFORE the object is ground up - use this to change grind results based on conditions. Return "-1" to prevent the grinding from occurring @@ -1001,9 +1000,10 @@ /obj/item/proc/juice(datum/reagents/target_holder, mob/user) if(on_juice() == -1) return FALSE - reagents.convert_reagent(/datum/reagent/consumable, juice_typepath, include_source_subtypes = TRUE) - if(reagents && target_holder) - reagents.trans_to(target_holder, reagents.total_volume, transferred_by = user) + if(reagents) + reagents.convert_reagent(/datum/reagent/consumable, juice_typepath, include_source_subtypes = TRUE) + if(target_holder) + reagents.trans_to(target_holder, reagents.total_volume, transferred_by = user) return TRUE /obj/item/proc/set_force_string() diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 5c616fa4803..7050309268c 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -145,18 +145,49 @@ /obj/item/stack/set_custom_materials(list/materials, multiplier=1, is_update=FALSE) return is_update ? ..() : set_mats_per_unit(materials, multiplier/(amount || 1)) - -/obj/item/stack/on_grind() - . = ..() - for(var/i in 1 to length(grind_results)) //This should only call if it's ground, so no need to check if grind_results exists - grind_results[grind_results[i]] *= get_amount() //Gets the key at position i, then the reagent amount of that key, then multiplies it by stack size - /obj/item/stack/grind_requirements() if(is_cyborg) to_chat(usr, span_warning("[src] is too integrated into your chassis and can't be ground up!")) return return TRUE +/obj/item/stack/grind(datum/reagents/target_holder, mob/user) + var/current_amount = get_amount() + if(current_amount <= 0 || QDELETED(src)) //just to get rid of this 0 amount/deleted stack we return success + return TRUE + if(on_grind() == -1) + return FALSE + if(isnull(target_holder)) + return TRUE + + if(reagents) + reagents.trans_to(target_holder, reagents.total_volume, transferred_by = user) + var/available_volume = target_holder.maximum_volume - target_holder.total_volume + + //compute total volume of reagents that will be occupied by grind_results + var/total_volume = 0 + for(var/reagent in grind_results) + total_volume += grind_results[reagent] + + //compute number of pieces(or sheets) from available_volume + var/available_amount = min(current_amount, round(available_volume / total_volume)) + if(available_amount <= 0) + return FALSE + + //Now transfer the grind results scaled by available_amount + var/list/grind_reagents = grind_results.Copy() + for(var/reagent in grind_reagents) + grind_reagents[reagent] *= available_amount + target_holder.add_reagent_list(grind_reagents) + + /** + * use available_amount of sheets/pieces, return TRUE only if all sheets/pieces of this stack were used + * we don't delete this stack when it reaches 0 because we expect the all in one grinder, etc to delete + * this stack if grinding was successfull + */ + use(available_amount, check = FALSE) + return available_amount == current_amount + /obj/item/stack/proc/get_main_recipes() RETURN_TYPE(/list) SHOULD_CALL_PARENT(TRUE) diff --git a/code/modules/plumbing/plumbers/grinder_chemical.dm b/code/modules/plumbing/plumbers/grinder_chemical.dm index 5e3c3b0f5d3..e70977bc1f4 100644 --- a/code/modules/plumbing/plumbers/grinder_chemical.dm +++ b/code/modules/plumbing/plumbers/grinder_chemical.dm @@ -1,6 +1,6 @@ /obj/machinery/plumbing/grinder_chemical name = "chemical grinder" - desc = "chemical grinder." + desc = "Chemical grinder. Can either grind or juice stuff you put in." icon_state = "grinder_chemical" layer = ABOVE_ALL_MOB_LAYER plane = ABOVE_GAME_PLANE @@ -8,7 +8,6 @@ reagent_flags = TRANSPARENT | DRAINABLE buffer = 400 active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 2 - var/eat_dir = SOUTH /obj/machinery/plumbing/grinder_chemical/Initialize(mapload, bolt, layer) . = ..() @@ -18,21 +17,35 @@ ) AddElement(/datum/element/connect_loc, loc_connections) -/obj/machinery/plumbing/grinder_chemical/setDir(newdir) - . = ..() - eat_dir = newdir +/obj/machinery/plumbing/grinder_chemical/attackby(obj/item/weapon, mob/user, params) + if(istype(weapon, /obj/item/storage/bag)) + to_chat(user, span_notice("You dump items from [weapon] into the grinder.")) + for(var/obj/item/obj_item in weapon.contents) + grind(obj_item) + else + to_chat(user, span_notice("You attempt to grind [weapon].")) + grind(weapon) + + return TRUE /obj/machinery/plumbing/grinder_chemical/CanAllowThrough(atom/movable/mover, border_dir) . = ..() if(!anchored) return - if(border_dir == eat_dir) - return TRUE + if(!istype(mover, /obj/item)) + return FALSE + return TRUE /obj/machinery/plumbing/grinder_chemical/proc/on_entered(datum/source, atom/movable/AM) SIGNAL_HANDLER + grind(AM) +/** + * Grinds/Juices the atom + * Arguments + * * [AM][atom] - the atom to grind or juice + */ /obj/machinery/plumbing/grinder_chemical/proc/grind(atom/AM) if(machine_stat & NOPOWER) return @@ -40,11 +53,14 @@ return if(!isitem(AM)) return + var/obj/item/I = AM + var/result if(I.grind_results || I.juice_typepath) use_power(active_power_usage) if(I.grind_results) - I.grind(src, src) + result = I.grind(reagents, usr) else if (I.juice_typepath) - I.juice(src, src) - qdel(I) + result = I.juice(reagents, usr) + if(result) + qdel(I) diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm index 4e5930b96df..902ccf35e6a 100644 --- a/code/modules/reagents/chemistry/holder.dm +++ b/code/modules/reagents/chemistry/holder.dm @@ -1391,9 +1391,7 @@ /// Is this holder full or not /datum/reagents/proc/holder_full() - if(total_volume >= maximum_volume) - return TRUE - return FALSE + return total_volume >= maximum_volume /// Get the amount of this reagent /datum/reagents/proc/get_reagent_amount(reagent, include_subtypes = FALSE) diff --git a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm index 35be2dc1865..26ebfa1d18d 100644 --- a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm +++ b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm @@ -99,11 +99,9 @@ . = ..() if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) return - if(!can_interact(user) || !user.can_perform_action(src, ALLOW_SILICON_REACH|FORBID_TELEKINESIS_REACH)) + if(operating || !user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH)) return - if(operating) - return - replace_beaker(user) + eject(user) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN /obj/machinery/reagentgrinder/attack_robot_secondary(mob/user, list/modifiers) @@ -146,24 +144,28 @@ default_unfasten_wrench(user, tool) return TOOL_ACT_TOOLTYPE_SUCCESS -/obj/machinery/reagentgrinder/attackby(obj/item/I, mob/living/user, params) - //You can only screw open empty grinder - if(!beaker && !length(holdingitems) && default_deconstruction_screwdriver(user, icon_state, icon_state, I)) - return +/obj/machinery/reagentgrinder/screwdriver_act(mob/living/user, obj/item/tool) + . = TOOL_ACT_TOOLTYPE_SUCCESS + if(!beaker && !length(holdingitems)) + return default_deconstruction_screwdriver(user, icon_state, icon_state, tool) - if(default_deconstruction_crowbar(I)) - return +/obj/machinery/reagentgrinder/crowbar_act(mob/living/user, obj/item/tool) + return default_deconstruction_crowbar(tool) +/obj/machinery/reagentgrinder/attackby(obj/item/weapon, mob/living/user, params) if(panel_open) //Can't insert objects when its screwed open return TRUE - if (is_reagent_container(I) && !(I.item_flags & ABSTRACT) && I.is_open_container()) - var/obj/item/reagent_containers/B = I + if(!weapon.grind_requirements(src)) //Error messages should be in the objects' definitions + return + + if (is_reagent_container(weapon) && !(weapon.item_flags & ABSTRACT) && weapon.is_open_container()) + var/obj/item/reagent_containers/container = weapon . = TRUE //no afterattack - if(!user.transferItemToLoc(B, src)) + if(!user.transferItemToLoc(container, src)) return - replace_beaker(user, B) - to_chat(user, span_notice("You add [B] to [src].")) + replace_beaker(user, container) + to_chat(user, span_notice("You add [container] to [src].")) update_appearance() return TRUE //no afterattack @@ -172,39 +174,36 @@ return TRUE //Fill machine with a bag! - if(istype(I, /obj/item/storage/bag)) - if(!I.contents.len) - to_chat(user, span_notice("[I] is empty!")) + if(istype(weapon, /obj/item/storage/bag)) + if(!weapon.contents.len) + to_chat(user, span_notice("[weapon] is empty!")) return TRUE var/list/inserted = list() - if(I.atom_storage.remove_type(/obj/item/food/grown, src, limit - length(holdingitems), TRUE, FALSE, user, inserted)) + if(weapon.atom_storage.remove_type(/obj/item/food/grown, src, limit - length(holdingitems), TRUE, FALSE, user, inserted)) for(var/i in inserted) holdingitems[i] = TRUE inserted = list() - if(I.atom_storage.remove_type(/obj/item/food/honeycomb, src, limit - length(holdingitems), TRUE, FALSE, user, inserted)) + if(weapon.atom_storage.remove_type(/obj/item/food/honeycomb, src, limit - length(holdingitems), TRUE, FALSE, user, inserted)) for(var/i in inserted) holdingitems[i] = TRUE - if(!I.contents.len) - to_chat(user, span_notice("You empty [I] into [src].")) + if(!weapon.contents.len) + to_chat(user, span_notice("You empty [weapon] into [src].")) else to_chat(user, span_notice("You fill [src] to the brim.")) return TRUE - if(!I.grind_results && !I.juice_typepath) + if(!weapon.grind_results && !weapon.juice_typepath) if(user.combat_mode) return ..() else - to_chat(user, span_warning("You cannot grind [I] into reagents!")) + to_chat(user, span_warning("You cannot grind/juice [weapon] into reagents!")) return TRUE - if(!I.grind_requirements(src)) //Error messages should be in the objects' definitions - return - - if(user.transferItemToLoc(I, src)) - to_chat(user, span_notice("You add [I] to [src].")) - holdingitems[I] = TRUE + if(user.transferItemToLoc(weapon, src)) + to_chat(user, span_notice("You add [weapon] to [src].")) + holdingitems[weapon] = TRUE return FALSE /obj/machinery/reagentgrinder/ui_interact(mob/user) // The microwave Menu //I am reasonably certain that this is not a microwave @@ -261,9 +260,9 @@ if(beaker) replace_beaker(user) -/obj/machinery/reagentgrinder/proc/remove_object(obj/item/O) - holdingitems -= O - qdel(O) +/obj/machinery/reagentgrinder/proc/remove_object(obj/item/weapon) + holdingitems -= weapon + qdel(weapon) /obj/machinery/reagentgrinder/proc/start_shaking() var/static/list/transforms @@ -306,11 +305,11 @@ /obj/machinery/reagentgrinder/proc/juice(mob/user) power_change() - if(!beaker || machine_stat & (NOPOWER|BROKEN) || beaker.reagents.total_volume >= beaker.reagents.maximum_volume) + if(!beaker || machine_stat & (NOPOWER|BROKEN) || beaker.reagents.holder_full()) return operate_for(50, juicing = TRUE) for(var/obj/item/i in holdingitems) - if(beaker.reagents.total_volume >= beaker.reagents.maximum_volume) + if(beaker.reagents.holder_full()) break var/obj/item/I = i if(I.juice_typepath) @@ -324,12 +323,12 @@ /obj/machinery/reagentgrinder/proc/grind(mob/user) power_change() - if(!beaker || machine_stat & (NOPOWER|BROKEN) || beaker.reagents.total_volume >= beaker.reagents.maximum_volume) + if(!beaker || machine_stat & (NOPOWER|BROKEN) || beaker.reagents.holder_full()) return operate_for(60) warn_of_dust() // don't breathe this. for(var/i in holdingitems) - if(beaker.reagents.total_volume >= beaker.reagents.maximum_volume) + if(beaker.reagents.holder_full()) break var/obj/item/I = i if(I.grind_results) @@ -337,7 +336,10 @@ /obj/machinery/reagentgrinder/proc/grind_item(obj/item/I, mob/user) //Grind results can be found in respective object definitions if(!I.grind(beaker.reagents, user)) - to_chat(usr, span_danger("[src] shorts out as it tries to grind up [I], and transfers it back to storage.")) + if(isstack(I)) + to_chat(usr, span_notice("[src] attempts to grind as many pieces of [I] as possible.")) + else + to_chat(usr, span_danger("[src] shorts out as it tries to grind up [I], and transfers it back to storage.")) return remove_object(I) @@ -347,7 +349,7 @@ if(!beaker || machine_stat & (NOPOWER|BROKEN)) return operate_for(50, juicing = TRUE) - addtimer(CALLBACK(src, TYPE_PROC_REF(/obj/machinery/reagentgrinder, mix_complete)), 50) + addtimer(CALLBACK(src, PROC_REF(mix_complete)), 50 / speed) /obj/machinery/reagentgrinder/proc/mix_complete() if(beaker?.reagents.total_volume <= 0) diff --git a/code/modules/reagents/reagent_containers/cups/_cup.dm b/code/modules/reagents/reagent_containers/cups/_cup.dm index 069c770f009..b5930955a31 100644 --- a/code/modules/reagents/reagent_containers/cups/_cup.dm +++ b/code/modules/reagents/reagent_containers/cups/_cup.dm @@ -537,7 +537,10 @@ /obj/item/reagent_containers/cup/mortar/proc/grind_item(obj/item/item, mob/living/carbon/human/user) if(!item.grind(reagents, user)) - to_chat(user, span_notice("You fail to grind [item].")) + if(isstack(item)) + to_chat(usr, span_notice("[src] attempts to grind as many pieces of [item] as possible.")) + else + to_chat(user, span_danger("You fail to grind [item].")) return to_chat(user, span_notice("You grind [item] into a nice powder.")) grinded = null