diff --git a/_maps/RandomRuins/SpaceRuins/nova/port_tarkon.dmm b/_maps/RandomRuins/SpaceRuins/nova/port_tarkon.dmm index be2c41b1eb8..fed8e5b80ca 100644 --- a/_maps/RandomRuins/SpaceRuins/nova/port_tarkon.dmm +++ b/_maps/RandomRuins/SpaceRuins/nova/port_tarkon.dmm @@ -4396,7 +4396,7 @@ /area/ruin/space/has_grav/port_tarkon/secoff) "DG" = ( /obj/structure/table/reinforced, -/obj/machinery/reagentgrinder/constructed, +/obj/machinery/reagentgrinder, /obj/machinery/light/small/directional/west, /turf/open/floor/iron/cafeteria, /area/ruin/space/has_grav/port_tarkon/kitchen) @@ -7204,7 +7204,7 @@ dir = 1 }, /obj/structure/table/reinforced, -/obj/machinery/reagentgrinder/constructed, +/obj/machinery/reagentgrinder, /turf/open/floor/iron/dark, /area/ruin/space/has_grav/port_tarkon/developement) "Wy" = ( diff --git a/_maps/shuttles/pirate_ex_interdyne.dmm b/_maps/shuttles/pirate_ex_interdyne.dmm index c84250b13d9..94fdb5be35e 100644 --- a/_maps/shuttles/pirate_ex_interdyne.dmm +++ b/_maps/shuttles/pirate_ex_interdyne.dmm @@ -67,7 +67,7 @@ dir = 8 }, /obj/structure/table/reinforced/plastitaniumglass, -/obj/machinery/reagentgrinder/constructed, +/obj/machinery/reagentgrinder, /turf/open/floor/iron/dark, /area/shuttle/pirate) "al" = ( @@ -625,7 +625,7 @@ /obj/item/stack/sheet/mineral/plasma, /obj/item/stack/sheet/mineral/plasma, /obj/item/stack/sheet/mineral/plasma, -/obj/machinery/reagentgrinder/constructed, +/obj/machinery/reagentgrinder, /obj/item/storage/box/monkeycubes/syndicate, /obj/item/storage/box/monkeycubes/syndicate, /obj/item/reagent_containers/cup/bottle, diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index bfec16eb732..19d9a6ac045 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -803,9 +803,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_VENTCRAWLER_ALWAYS "ventcrawler_always" #define TRAIT_VENTCRAWLER_NUDE "ventcrawler_nude" -/// Minor trait used for beakers, or beaker-ishes. [/obj/item/reagent_containers], to show that they've been used in a reagent grinder. -#define TRAIT_MAY_CONTAIN_BLENDED_DUST "may_contain_blended_dust" - /// Trait put on [/mob/living/carbon/human]. If that mob has a crystal core, also known as an ethereal heart, it will not try to revive them if the mob dies. #define TRAIT_CANNOT_CRYSTALIZE "cannot_crystalize" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index f002703942a..c3d5751188e 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -582,9 +582,6 @@ GLOBAL_LIST_INIT(traits_by_type, list( /obj/item/organ/internal/lungs = list( "TRAIT_SPACEBREATHING" = TRAIT_SPACEBREATHING, ), - /obj/item/reagent_containers = list( - "TRAIT_MAY_CONTAIN_BLENDED_DUST" = TRAIT_MAY_CONTAIN_BLENDED_DUST, - ), /obj/machinery/modular_computer = list( "TRAIT_MODPC_INTERACTING_WITH_FRAME" = TRAIT_MODPC_INTERACTING_WITH_FRAME, ), diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm index 1480ac23c5a..1f8b01f41da 100644 --- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm @@ -525,9 +525,11 @@ /obj/item/circuitboard/machine/reagentgrinder name = "All-In-One Grinder" greyscale_colors = CIRCUIT_COLOR_GENERIC - build_path = /obj/machinery/reagentgrinder/constructed + build_path = /obj/machinery/reagentgrinder req_components = list( - /datum/stock_part/servo = 1) + /datum/stock_part/servo = 1, + /datum/stock_part/matter_bin = 1, + ) needs_anchored = FALSE /obj/item/circuitboard/machine/smartfridge diff --git a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm index c3ec9bbddb4..708be786404 100644 --- a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm +++ b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm @@ -3,57 +3,41 @@ name = "\improper All-In-One Grinder" desc = "From BlenderTech. Will It Blend? Let's test it out!" icon = 'icons/obj/machines/kitchen.dmi' - icon_state = "juicer1" + icon_state = "juicer" base_icon_state = "juicer" - layer = BELOW_OBJ_LAYER + active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.0025 circuit = /obj/item/circuitboard/machine/reagentgrinder pass_flags = PASSTABLE resistance_flags = ACID_PROOF + interaction_flags_machine = parent_type::interaction_flags_machine | INTERACT_MACHINE_OFFLINE anchored_tabletop_offset = 8 + + /// The maximum weight of items this grinder can hold + var/maximum_weight = WEIGHT_CLASS_BULKY + /// Is the grinder currently performing work var/operating = FALSE + /// The beaker to hold the final products var/obj/item/reagent_containers/beaker = null - var/limit = 10 + /// How fast operations take place var/speed = 1 - var/list/holdingitems - - var/static/radial_examine = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_examine") - var/static/radial_eject = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_eject") - var/static/radial_grind = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_grind") - var/static/radial_juice = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_juice") - var/static/radial_mix = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_mix") /obj/machinery/reagentgrinder/Initialize(mapload) . = ..() - holdingitems = list() - beaker = new /obj/item/reagent_containers/cup/beaker/large(src) - warn_of_dust() - RegisterSignal(src, COMSIG_STORAGE_DUMP_CONTENT, PROC_REF(on_storage_dump)) -/// Add a description to the current beaker warning of blended dust, if it doesn't already have that warning. -/obj/machinery/reagentgrinder/proc/warn_of_dust() - if(HAS_TRAIT(beaker, TRAIT_MAY_CONTAIN_BLENDED_DUST)) - return - - beaker.desc += " May contain blended dust. Don't breathe this!" - ADD_TRAIT(beaker, TRAIT_MAY_CONTAIN_BLENDED_DUST, TRAIT_GENERIC) + if(mapload) + beaker = new /obj/item/reagent_containers/cup/beaker/large(src) -/obj/machinery/reagentgrinder/constructed/Initialize(mapload) - . = ..() - holdingitems = list() - QDEL_NULL(beaker) - update_appearance() + register_context() + update_appearance(UPDATE_OVERLAYS) -/obj/machinery/reagentgrinder/on_deconstruction(disassmbled) - drop_all_items() - beaker?.forceMove(drop_location()) - beaker = null + RegisterSignal(src, COMSIG_STORAGE_DUMP_CONTENT, PROC_REF(on_storage_dump)) /obj/machinery/reagentgrinder/Destroy() QDEL_NULL(beaker) return ..() /obj/machinery/reagentgrinder/contents_explosion(severity, target) - if(!beaker) + if(!QDELETED(beaker)) return switch(severity) @@ -64,11 +48,36 @@ if(EXPLODE_LIGHT) SSexplosions.low_mov_atom += beaker -/obj/machinery/reagentgrinder/RefreshParts() - . = ..() - speed = 1 - for(var/datum/stock_part/servo/servo in component_parts) - speed = servo.tier +/obj/machinery/reagentgrinder/add_context(atom/source, list/context, obj/item/held_item, mob/user) + var/result = NONE + if(isnull(held_item)) + if(!QDELETED(beaker) && !operating) + context[SCREENTIP_CONTEXT_RMB] = "Remove beaker" + result = CONTEXTUAL_SCREENTIP_SET + return result + + if(is_reagent_container(held_item) && held_item.is_open_container() && !operating) + if(QDELETED(beaker)) + context[SCREENTIP_CONTEXT_LMB] = "Insert beaker" + else + context[SCREENTIP_CONTEXT_LMB] = "Replace beaker" + return CONTEXTUAL_SCREENTIP_SET + + if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] panel" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_CROWBAR && panel_open) + context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_WRENCH) + context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Una" : "A"]nchor" + return CONTEXTUAL_SCREENTIP_SET + + if(istype(held_item, /obj/item/storage/bag)) + context[SCREENTIP_CONTEXT_LMB] = "Transfer contents" + else + context[SCREENTIP_CONTEXT_LMB] = "Insert item" + return CONTEXTUAL_SCREENTIP_SET /obj/machinery/reagentgrinder/examine(mob/user) . = ..() @@ -76,312 +85,426 @@ . += span_warning("You're too far away to examine [src]'s contents and display!") return - if(operating) - . += span_warning("\The [src] is operating.") - return + var/total_weight = 0 + var/list/obj/item/to_process = list() + for(var/obj/item/target in src) + if((target in component_parts) || target == beaker) + continue + to_process["[target.name]"] += 1 + total_weight += target.w_class + if(to_process.len) + . += span_notice("Currently holding.") + for(var/target_name as anything in to_process) + . += span_notice("[to_process[target_name]] [target_name]") + . += span_notice("Filled to [round((total_weight / maximum_weight) * 100)]% capacity.") + + if(!QDELETED(beaker)) + . += span_notice("A beaker of [beaker.reagents.maximum_volume]u capacity is present.") + . += span_notice("[EXAMINE_HINT("Right click")] with empty hand to remove beaker.") + else + . += span_warning("It's missing a beaker.") - if(beaker || length(holdingitems)) - . += span_notice("\The [src] contains:") - if(beaker) - . += span_notice("- \A [beaker].") - for(var/i in holdingitems) - var/obj/item/O = i - . += span_notice("- \A [O.name].") - - if(!(machine_stat & (NOPOWER|BROKEN))) - . += "[span_notice("The status display reads:")]\n"+\ - span_notice("- Grinding reagents at [speed*100]%.") - if(beaker) - for(var/datum/reagent/R in beaker.reagents.reagent_list) - . += span_notice("- [R.volume] units of [R.name].") + . += span_notice("You can drag a storage item to dump its contents in the grinder.") + if(anchored) + . += span_notice("It can be [EXAMINE_HINT("wrenched")] loose.") + else + . += span_warning("Needs to be [EXAMINE_HINT("wrenched")] in place to work.") + . += span_notice("Its maintainence panel can be [EXAMINE_HINT("screwed")] [panel_open ? "closed" : "open"].") + if(panel_open) + . += span_notice("It can be [EXAMINE_HINT("pried")] apart.") -/obj/machinery/reagentgrinder/attack_hand_secondary(mob/user, list/modifiers) +/obj/machinery/reagentgrinder/update_overlays() . = ..() - if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) - return - if(operating || !user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH)) - return - eject(user) - return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN -/obj/machinery/reagentgrinder/attack_robot_secondary(mob/user, list/modifiers) - return attack_hand_secondary(user, modifiers) + if(!QDELETED(beaker)) + . += "[base_icon_state]-beaker" -/obj/machinery/reagentgrinder/attack_ai_secondary(mob/user, list/modifiers) - return attack_hand_secondary(user, modifiers) + if(anchored && !panel_open && is_operational) + . += "[base_icon_state]-on" /obj/machinery/reagentgrinder/Exited(atom/movable/gone, direction) . = ..() if(gone == beaker) beaker = null - update_appearance() - if(holdingitems[gone]) - holdingitems -= gone - -/obj/machinery/reagentgrinder/proc/drop_all_items() - for(var/i in holdingitems) - var/atom/movable/AM = i - AM.forceMove(drop_location()) - holdingitems = list() - -/obj/machinery/reagentgrinder/update_icon_state() - icon_state = "[base_icon_state][beaker ? 1 : 0]" - return ..() + update_appearance(UPDATE_OVERLAYS) +/obj/machinery/reagentgrinder/RefreshParts() + . = ..() + + for(var/datum/stock_part/part in component_parts) + if(istype(part, /datum/stock_part/servo)) + speed = part.tier + else if(istype(part, /datum/stock_part/matter_bin)) + maximum_weight = WEIGHT_CLASS_GIGANTIC * part.tier +/** + * Inserts, removes or replaces the beaker present + * Arguments + * + * * mob/living/user - the player performing the action + * * obj/item/reagent_containers/new_beaker - the new beaker to replace the old, null to do nothing + */ /obj/machinery/reagentgrinder/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker) - if(!user) - return FALSE - if(beaker) + PRIVATE_PROC(TRUE) + + if(!QDELETED(beaker)) try_put_in_hand(beaker, user) - beaker = null - if(new_beaker) + + if(!QDELETED(new_beaker)) + if(!user.transferItemToLoc(new_beaker, src)) + return beaker = new_beaker - update_appearance() - return TRUE + + update_appearance(UPDATE_OVERLAYS) + +/** + * Transfer this item or items inside if its a bag into the grinder + * Arguments + * + * * mob/user - the player who is inserting these items + * * list/obj/item/to_add - list of items to add + */ +/obj/machinery/reagentgrinder/proc/load_items(mob/user, list/obj/item/to_add) + PRIVATE_PROC(TRUE) + + //surface level checks to filter out items that can be grinded/juice + var/list/obj/item/filtered_list = list() + for(var/obj/item/ingredient as anything in to_add) + //what are we trying to grind exactly? + if((ingredient.item_flags & ABSTRACT) || (ingredient.flags_1 & HOLOGRAM_1)) + continue + + //Nothing would come from grinding or juicing + if(!length(ingredient.grind_results) && !ingredient.juice_typepath) + to_chat(user, span_warning("You cannot grind/juice [ingredient] into reagents!")) + continue + + //Error messages should be in the objects' definitions + if(!ingredient.grind_requirements(src)) + continue + + filtered_list += ingredient + if(!filtered_list.len) + return FALSE + + //find total weight of all items already in grinder + var/total_weight + for(var/obj/item/to_process in src) + if((to_process in component_parts) || to_process == beaker) + continue + total_weight += to_process.w_class + + //Now transfer the items 1 at a time while ensuring we don't go above the maximum allowed weight + var/items_transfered = 0 + for(var/obj/item/weapon as anything in filtered_list) + if(weapon.w_class + total_weight > maximum_weight) + to_chat(user, span_warning("[weapon] is too big to fit into [src].")) + continue + weapon.forceMove(src) + total_weight += weapon.w_class + items_transfered += 1 + to_chat(user, span_notice("[weapon] was loaded into [src].")) + + return items_transfered + +/obj/machinery/reagentgrinder/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) + if(user.combat_mode || (tool.item_flags & ABSTRACT) || (tool.flags_1 & HOLOGRAM_1) || !can_interact(user) || !user.can_perform_action(src, ALLOW_SILICON_REACH)) + return ..() + + //add the beaker + if (is_reagent_container(tool) && tool.is_open_container()) + replace_beaker(user, tool) + to_chat(user, span_notice("You add [tool] to [src].")) + return ITEM_INTERACT_SUCCESS + + //add items from bag + else if(istype(tool, /obj/item/storage/bag)) + var/list/obj/item/to_add = list() + //list of acceptable items from the bag + var/static/list/accepted_items = list( + /obj/item/grown, + /obj/item/food/grown, + /obj/item/food/honeycomb, + ) + + //add to list of items to check for + for(var/obj/item/ingredient in tool) + if(!is_type_in_list(ingredient, accepted_items)) + continue + to_add += ingredient + + //add the items + var/items_added = load_items(user, to_add) + if(!items_added) + to_chat(user, span_warning("No items were added.")) + return ITEM_INTERACT_BLOCKING + to_chat(user, span_notice("[items_added] items were added from [tool] to [src].")) + return ITEM_INTERACT_SUCCESS + + //add item directly + else if(tool.grind_results || tool.juice_typepath) + if(tool.atom_storage) //anything that has internal storage would be too much recursion for us to handle + to_chat(user, span_notice("Drag this item onto [src] to dump its contents.")) + return ITEM_INTERACT_BLOCKING + + //add the items + if(!load_items(user, list(tool))) + return ITEM_INTERACT_BLOCKING + to_chat(user, span_notice("[tool] was added to [src].")) + return ITEM_INTERACT_SUCCESS + + //ask player to drag stuff into grinder + else if(tool.atom_storage) + to_chat(user, span_warning("You must drag & dump contents of [tool] into [src].")) + return ITEM_INTERACT_BLOCKING + + return ..() /obj/machinery/reagentgrinder/wrench_act(mob/living/user, obj/item/tool) - . = ..() - default_unfasten_wrench(user, tool) - return ITEM_INTERACT_SUCCESS + if(user.combat_mode) + return NONE -/obj/machinery/reagentgrinder/screwdriver_act(mob/living/user, obj/item/tool) - . = ITEM_INTERACT_SUCCESS - if(!beaker && !length(holdingitems)) - return default_deconstruction_screwdriver(user, icon_state, icon_state, tool) + var/tool_result = ITEM_INTERACT_BLOCKING + if(operating) + balloon_alert(user, "still operating!") + return tool_result -/obj/machinery/reagentgrinder/crowbar_act(mob/living/user, obj/item/tool) - return default_deconstruction_crowbar(tool) + if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN) + update_appearance(UPDATE_OVERLAYS) + tool_result = ITEM_INTERACT_SUCCESS + return tool_result -/obj/machinery/reagentgrinder/attackby(obj/item/weapon, mob/living/user, params) - if(panel_open) //Can't insert objects when its screwed open - return TRUE +/obj/machinery/reagentgrinder/screwdriver_act(mob/living/user, obj/item/tool) + if(user.combat_mode) + return NONE - if(!weapon.grind_requirements(src)) //Error messages should be in the objects' definitions - return + var/tool_result = ITEM_INTERACT_BLOCKING + if(operating) + balloon_alert(user, "still operating!") + return tool_result - 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(container, src)) - return - replace_beaker(user, container) - to_chat(user, span_notice("You add [container] to [src].")) - update_appearance() - return TRUE //no afterattack - - if(holdingitems.len >= limit) - to_chat(user, span_warning("[src] is filled to capacity!")) - return TRUE - - //Fill machine with a bag! - 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(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(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(!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(default_deconstruction_screwdriver(user, icon_state, icon_state, tool)) + update_appearance(UPDATE_OVERLAYS) + tool_result = ITEM_INTERACT_SUCCESS + return tool_result - if(!weapon.grind_results && !weapon.juice_typepath) - if(user.combat_mode) - return ..() - else - to_chat(user, span_warning("You cannot grind/juice [weapon] into reagents!")) - return TRUE +/obj/machinery/reagentgrinder/crowbar_act(mob/living/user, obj/item/tool) + if(user.combat_mode) + return NONE - if(user.transferItemToLoc(weapon, src)) - to_chat(user, span_notice("You add [weapon] to [src].")) - holdingitems[weapon] = TRUE - return FALSE + var/tool_result = ITEM_INTERACT_BLOCKING + if(operating) + balloon_alert(user, "still operating!") + return tool_result + + if(default_deconstruction_crowbar(tool)) + tool_result = ITEM_INTERACT_SUCCESS + return tool_result /obj/machinery/reagentgrinder/proc/on_storage_dump(datum/source, datum/storage/storage, mob/user) SIGNAL_HANDLER + var/list/obj/item/contents_to_dump = list() for(var/obj/item/to_dump in storage.real_location) - if(holdingitems.len >= limit) - break - - if(!to_dump.grind_results && !to_dump.juice_typepath) + if(to_dump.atom_storage) //No recursive handling of contents please continue + contents_to_dump += to_dump - if(!storage.attempt_remove(to_dump, src, silent = TRUE)) - continue + to_chat(user, span_notice("You dumped [load_items(user, contents_to_dump)] items from [storage.parent] into [src].")) - holdingitems[to_dump] = TRUE - - to_chat(user, span_notice("You dump [storage.parent] into [src].")) return STORAGE_DUMP_HANDLED -/obj/machinery/reagentgrinder/ui_interact(mob/user) // The microwave Menu //I am reasonably certain that this is not a microwave +/obj/machinery/reagentgrinder/attack_hand_secondary(mob/user, list/modifiers) + . = ..() + if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) + return + if(operating || !can_interact(user) || !user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH)) + return + replace_beaker(user) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/machinery/reagentgrinder/attack_robot_secondary(mob/user, list/modifiers) + return attack_hand_secondary(user, modifiers) + +/obj/machinery/reagentgrinder/attack_ai_secondary(mob/user, list/modifiers) + return attack_hand_secondary(user, modifiers) + +/obj/machinery/reagentgrinder/ui_interact(mob/user) . = ..() - if(operating || !user.can_perform_action(src, ALLOW_SILICON_REACH)) + //some interaction sanity checks + if(!anchored || operating || !can_interact(user) || !user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH)) return + var/static/radial_eject = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_eject") + var/static/radial_mix = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_mix") + //create list of options available var/list/options = list() + //actions to be performed on the items stored inside + for(var/obj/item/to_process in src) + if((to_process in component_parts) || to_process == beaker) + continue - if(beaker || length(holdingitems)) - options["eject"] = radial_eject + if(!QDELETED(beaker) && !beaker.reagents.holder_full() && is_operational && anchored) + var/static/radial_grind = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_grind") + options["grind"] = radial_grind + var/static/radial_juice = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_juice") + options["juice"] = radial_juice + + options["eject"] = radial_eject + break + //eject action if we have a beaker + if(!QDELETED(beaker)) + options["eject"] = radial_eject + //mix reagents present inside + if(beaker?.reagents.total_volume && is_operational && anchored) + options["mix"] = radial_mix + //examine action if Ai is trying to see whats up if(HAS_AI_ACCESS(user)) if(machine_stat & NOPOWER) return + var/static/radial_examine = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_examine") options["examine"] = radial_examine - // if there is no power or it's broken, the procs will fail but the buttons will still show - if(length(holdingitems)) - options["grind"] = radial_grind - options["juice"] = radial_juice - else if(beaker?.reagents.total_volume) - options["mix"] = radial_mix - - var/choice - - if(length(options) < 1) - return - if(length(options) == 1) - for(var/key in options) - choice = key - else - choice = show_radial_menu(user, src, options, require_near = !HAS_SILICON_ACCESS(user)) - - // post choice verification - if(operating || (HAS_AI_ACCESS(user) && machine_stat & NOPOWER) || !user.can_perform_action(src, ALLOW_SILICON_REACH)) + //display choices & perform action + var/choice = show_radial_menu( + user, + src, + options, + custom_check = CALLBACK(src, PROC_REF(check_interactable), user), + require_near = !HAS_SILICON_ACCESS(user), + ) + if(!choice) return - switch(choice) if("eject") - eject(user) - if("grind") - grind(user) - if("juice") - juice(user) + replace_beaker(user) + dump_inventory_contents() + if("grind", "juice") + operate_for(60 DECISECONDS, choice == "juice", user) if("mix") - mix(user) + mix(50 DECISECONDS, user) if("examine") - examine(user) - -/obj/machinery/reagentgrinder/proc/eject(mob/user) - drop_all_items() - if(beaker) - replace_beaker(user) - -/obj/machinery/reagentgrinder/proc/remove_object(obj/item/weapon) - holdingitems -= weapon - qdel(weapon) - -/obj/machinery/reagentgrinder/proc/start_shaking() - var/static/list/transforms - if(!transforms) - var/matrix/M1 = matrix() - var/matrix/M2 = matrix() - var/matrix/M3 = matrix() - var/matrix/M4 = matrix() - M1.Translate(-1, 0) - M2.Translate(0, 1) - M3.Translate(1, 0) - M4.Translate(0, -1) - transforms = list(M1, M2, M3, M4) - animate(src, transform=transforms[1], time=0.4, loop=-1) - animate(transform=transforms[2], time=0.2) - animate(transform=transforms[3], time=0.4) - animate(transform=transforms[4], time=0.6) - -/obj/machinery/reagentgrinder/proc/shake_for(duration) - start_shaking() //start shaking - addtimer(CALLBACK(src, PROC_REF(stop_shaking)), duration) - -/obj/machinery/reagentgrinder/proc/stop_shaking() - update_appearance() - animate(src, transform = matrix()) - -/obj/machinery/reagentgrinder/proc/operate_for(time, silent = FALSE, juicing = FALSE) - shake_for(time / speed) - operating = TRUE - if(!silent) - if(!juicing) - playsound(src, 'sound/machines/blender.ogg', 50, TRUE) - else - playsound(src, 'sound/machines/juicer.ogg', 20, TRUE) - use_energy(active_power_usage * time / (1 SECONDS)) - addtimer(CALLBACK(src, PROC_REF(stop_operating)), time / speed) + to_chat(user, examine_block("[examine(user)]")) + +/** + * Checks if the radial menu can interact with this machine + * Arguments + * + * * mob/user - the player interacting with this machine + */ +/obj/machinery/reagentgrinder/proc/check_interactable(mob/user) + PRIVATE_PROC(TRUE) + + if(!can_interact(user)) + return FALSE -/obj/machinery/reagentgrinder/proc/stop_operating() - operating = FALSE + if(!anchored || operating || !user.can_perform_action(src, ALLOW_SILICON_REACH)) + return FALSE -/obj/machinery/reagentgrinder/proc/juice(mob/user) - power_change() - if(!beaker || machine_stat & (NOPOWER|BROKEN) || beaker.reagents.holder_full()) - return - operate_for(50, juicing = TRUE) - for(var/obj/item/ingredient in holdingitems) - if(beaker.reagents.holder_full()) - break + return TRUE - if(ingredient.flags_1 & HOLOGRAM_1) - to_chat(user, span_notice("You try to juice [ingredient], but it fades away!")) - qdel(ingredient) - continue +/** + * Grinds/Juices all contents inside the grinder + * Arguments + * + * * time - the duration in deciseconds to perform the operation + * * juicing - FALSE to grind, TRUE to juice + * * mob/user - the player who initiated this process + */ +/obj/machinery/reagentgrinder/proc/operate_for(time, juicing = FALSE, mob/user) + PRIVATE_PROC(TRUE) - if(ingredient.juice_typepath) - juice_item(ingredient, user) + var/duration = time / speed -/obj/machinery/reagentgrinder/proc/juice_item(obj/item/ingredient, mob/user) //Juicing results can be found in respective object definitions - if(!ingredient.juice(beaker.reagents, user)) - to_chat(user, span_danger("[src] shorts out as it tries to juice up [ingredient], and transfers it back to storage.")) - return - remove_object(ingredient) + Shake(duration = duration) + operating = TRUE + if(!juicing) + playsound(src, 'sound/machines/blender.ogg', 50, TRUE) + else + playsound(src, 'sound/machines/juicer.ogg', 20, TRUE) -/obj/machinery/reagentgrinder/proc/grind(mob/user) - power_change() - if(!beaker || machine_stat & (NOPOWER|BROKEN) || beaker.reagents.holder_full()) - return - operate_for(60) - warn_of_dust() // don't breathe this. - for(var/obj/item/ingredient in holdingitems) + var/total_weight + for(var/obj/item/weapon in src) + if((weapon in component_parts) || weapon == beaker) + continue if(beaker.reagents.holder_full()) break - if(ingredient.flags_1 & HOLOGRAM_1) - to_chat(user, span_notice("You try to grind [ingredient], but it fades away!")) - qdel(ingredient) - continue + //recursively process everything inside this atom + var/item_processed = FALSE + var/item_weight = weapon.w_class + for(var/obj/item/ingredient as anything in weapon.get_all_contents_type(/obj/item)) + if(beaker.reagents.holder_full()) + break + + if(juicing) + if(ingredient.juice_typepath) + if(!ingredient.juice(beaker.reagents, user)) + to_chat(user, span_danger("[src] shorts out as it tries to juice up [ingredient], and transfers it back to storage.")) + continue + item_processed = TRUE + else if(ingredient.grind_results) + if(!ingredient.grind(beaker.reagents, user)) + if(isstack(ingredient)) + to_chat(user, span_notice("[src] attempts to grind as many pieces of [ingredient] as possible.")) + else + to_chat(user, span_danger("[src] shorts out as it tries to grind up [ingredient], and transfers it back to storage.")) + continue + item_processed = TRUE + + //happens only for stacks where some of the sheets were grinded so we roughly compute the weight grinded + if(item_weight != weapon.w_class) + total_weight += item_weight - weapon.w_class + else + total_weight += item_weight - if(ingredient.grind_results) - grind_item(ingredient, user) + //delete only if operation was successfull for atleast 1 item(also delete atoms for whom only some of its contents were processed as they are non functional now) + if(item_processed) + qdel(weapon) -/obj/machinery/reagentgrinder/proc/grind_item(obj/item/ingredient, mob/user) //Grind results can be found in respective object definitions - if(!ingredient.grind(beaker.reagents, user)) - if(isstack(ingredient)) - to_chat(user, span_notice("[src] attempts to grind as many pieces of [ingredient] as possible.")) - else - to_chat(user, span_danger("[src] shorts out as it tries to grind up [ingredient], and transfers it back to storage.")) - return - remove_object(ingredient) + //use power according to the total weight of items grinded + use_energy((active_power_usage * (duration / 1 SECONDS)) * (total_weight / maximum_weight)) -/obj/machinery/reagentgrinder/proc/mix(mob/user) - //For butter and other things that would change upon shaking or mixing - power_change() - if(!beaker || machine_stat & (NOPOWER|BROKEN)) - return - operate_for(50, juicing = TRUE) - addtimer(CALLBACK(src, PROC_REF(mix_complete)), 50 / speed) + addtimer(CALLBACK(src, PROC_REF(stop_operating)), duration) + +///Reset the operating status of the machine +/obj/machinery/reagentgrinder/proc/stop_operating() + PRIVATE_PROC(TRUE) + + operating = FALSE + +/** + * Mixes the reagents inside the beaker + * Arguments + * + * * time - the length of time in deciseconds to operate + * * mob/user - the player who started the mixing process + */ +/obj/machinery/reagentgrinder/proc/mix(time, mob/user) + PRIVATE_PROC(TRUE) + + var/duration = time / speed + + Shake(duration = duration) + operating = TRUE + playsound(src, 'sound/machines/juicer.ogg', 20, TRUE) + + addtimer(CALLBACK(src, PROC_REF(mix_complete), duration), duration) -/obj/machinery/reagentgrinder/proc/mix_complete() - if(beaker?.reagents.total_volume <= 0) +/** + * Mix the reagents + * Arguments + * + * * duration - the time spent in mixing + */ +/obj/machinery/reagentgrinder/proc/mix_complete(duration) + PRIVATE_PROC(TRUE) + + if(QDELETED(beaker) || beaker.reagents.total_volume <= 0) + operating = FALSE return + //Recipe to make Butter var/butter_amt = FLOOR(beaker.reagents.get_reagent_amount(/datum/reagent/consumable/milk) / MILK_TO_BUTTER_COEFF, 1) var/purity = beaker.reagents.get_reagent_purity(/datum/reagent/consumable/milk) @@ -389,9 +512,15 @@ for(var/i in 1 to butter_amt) var/obj/item/food/butter/tasty_butter = new(drop_location()) tasty_butter.reagents.set_all_reagents_purity(purity) + //Recipe to make Mayonnaise if (beaker.reagents.has_reagent(/datum/reagent/consumable/eggyolk)) beaker.reagents.convert_reagent(/datum/reagent/consumable/eggyolk, /datum/reagent/consumable/mayonnaise) + //Recipe to make whipped cream if (beaker.reagents.has_reagent(/datum/reagent/consumable/cream)) beaker.reagents.convert_reagent(/datum/reagent/consumable/cream, /datum/reagent/consumable/whipped_cream) + + //power consumed based on the ratio of total reagents mixed + use_energy((active_power_usage * (duration / 1 SECONDS)) * (beaker.reagents.total_volume / beaker.reagents.maximum_volume)) + operating = FALSE diff --git a/icons/obj/machines/kitchen.dmi b/icons/obj/machines/kitchen.dmi index 5bd820e554a..c94afb8d78a 100644 Binary files a/icons/obj/machines/kitchen.dmi and b/icons/obj/machines/kitchen.dmi differ