[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
🆑
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
/🆑

---------

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>
This commit is contained in:
SkyratBot
2023-09-17 15:38:53 -04:00
committed by GitHub
co-authored by Jeremiah SyncIt21
parent dc4e1f184c
commit f9fabdf1b6
6 changed files with 117 additions and 67 deletions
+8 -8
View File
@@ -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()
+37 -6
View File
@@ -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)
@@ -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)
+1 -3
View File
@@ -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)
@@ -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)
@@ -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