From c2bf86c572e03474a8941d09fe3cfb57ba31fa5e Mon Sep 17 00:00:00 2001 From: Critawakets Date: Wed, 12 Jun 2024 14:29:17 -0400 Subject: [PATCH] preliminary addition --- code/_onclick/item_attack.dm | 4 +- code/datums/components/style/style_meter.dm | 7 +- code/datums/elements/ridable.dm | 12 + code/game/atom/atom_tool_acts.dm | 34 +- code/game/machinery/_machinery.dm | 11 +- .../game/machinery/computer/arcade/_arcade.dm | 100 ++++ .../game/machinery/computer/buildandrepair.dm | 7 +- code/game/machinery/machine_frame.dm | 472 ++++++++++++++++++ code/game/machinery/slotmachine.dm | 73 +-- code/game/objects/buckling.dm | 12 - .../objects/items/devices/laserpointer.dm | 9 +- .../items/devices/scanners/autopsy_scanner.dm | 2 +- .../items/devices/scanners/health_analyzer.dm | 6 +- .../devices/scanners/sequence_scanner.dm | 4 +- .../items/devices/scanners/slime_scanner.dm | 2 +- .../objects/items/devices/traitordevices.dm | 2 +- code/game/objects/items/emags.dm | 4 +- code/game/objects/items/stacks/medical.dm | 2 +- code/game/objects/items/stacks/telecrystal.dm | 2 +- code/game/objects/items/tools/weldingtool.dm | 2 +- code/game/objects/structures/false_walls.dm | 4 +- code/game/objects/structures/reflector.dm | 4 +- code/game/objects/structures/tables_racks.dm | 19 +- code/game/objects/structures/window.dm | 4 +- code/game/turfs/open/floor/plating.dm | 5 +- .../abductor/equipment/gear/abductor_items.dm | 4 +- .../food_and_drinks/machinery/griddle.dm | 37 ++ .../food_and_drinks/machinery/grill.dm | 134 ++++- .../food_and_drinks/machinery/microwave.dm | 14 +- .../modules/food_and_drinks/machinery/oven.dm | 51 ++ .../food_and_drinks/machinery/stove.dm | 6 + code/modules/holodeck/turfs.dm | 5 +- .../boulder_processing/_boulder_processing.dm | 423 ++++++++++++++++ code/modules/paperwork/handlabeler.dm | 9 +- code/modules/power/apc/apc_tool_act.dm | 234 +++++++++ .../chemistry/machinery/chem_dispenser.dm | 6 +- .../chemistry/machinery/chem_heater.dm | 17 + .../chemistry/machinery/chem_mass_spec.dm | 19 +- .../chemistry/machinery/chem_master.dm | 13 + .../machinery/portable_chem_mixer.dm | 16 + .../chemistry/machinery/reagentgrinder.dm | 105 ++++ code/modules/research/destructive_analyzer.dm | 6 + code/modules/research/server.dm | 17 +- 43 files changed, 1771 insertions(+), 148 deletions(-) create mode 100644 code/game/machinery/computer/arcade/_arcade.dm create mode 100644 code/game/machinery/machine_frame.dm create mode 100644 code/modules/mining/boulder_processing/_boulder_processing.dm diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index f17ce8ef9132..fea80e2439e0 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -11,7 +11,7 @@ var/list/modifiers = params2list(params) var/is_right_clicking = LAZYACCESS(modifiers, RIGHT_CLICK) - var/item_interact_result = target.item_interaction(user, src, modifiers, is_right_clicking) + var/item_interact_result = target.base_item_interaction(user, src, modifiers) if(item_interact_result & ITEM_INTERACT_SUCCESS) return TRUE if(item_interact_result & ITEM_INTERACT_BLOCKING) @@ -159,7 +159,7 @@ return FALSE return attacking_item.attack_atom(src, user, params) -/mob/living/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) +/mob/living/item_interaction(mob/living/user, obj/item/tool, list/modifiers) // Surgery and such happens very high up in the interaction chain, before parent call var/attempt_tending = item_tending(user, tool, modifiers) if(attempt_tending & ITEM_INTERACT_ANY_BLOCKER) diff --git a/code/datums/components/style/style_meter.dm b/code/datums/components/style/style_meter.dm index 8c5b5ea87df6..67571a0ca726 100644 --- a/code/datums/components/style/style_meter.dm +++ b/code/datums/components/style/style_meter.dm @@ -28,10 +28,9 @@ . = ..() . += span_notice("You feel like a multitool could be used on this.") -/obj/item/style_meter/afterattack(atom/movable/attacked_atom, mob/user, proximity_flag, click_parameters) - . = ..() - if(!istype(attacked_atom, /obj/item/clothing/glasses)) - return +/obj/item/style_meter/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + if(!istype(interacting_with, /obj/item/clothing/glasses)) + return NONE forceMove(attacked_atom) attacked_atom.add_overlay(meter_appearance) diff --git a/code/datums/elements/ridable.dm b/code/datums/elements/ridable.dm index 15b81e73f241..1d60c584668b 100644 --- a/code/datums/elements/ridable.dm +++ b/code/datums/elements/ridable.dm @@ -197,3 +197,15 @@ to_chat(user, span_notice("You gently let go of [rider].")) return return rider + +/obj/item/riding_offhand/interact_with_atom(atom/movable/interacting_with, mob/living/user, list/modifiers) + if(!istype(interacting_with) || !interacting_with.can_buckle) + return NONE + if(rider == user) // Piggyback user + return ITEM_INTERACT_BLOCKING + + // Handles de-fireman carrying a mob and buckling them onto something (tables, etc) + var/mob/living/former_rider = rider + user.unbuckle_mob(former_rider) + former_rider.forceMove(get_turf(interacting_with)) + return interacting_with.mouse_buckle_handling(former_rider, user) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING diff --git a/code/game/atom/atom_tool_acts.dm b/code/game/atom/atom_tool_acts.dm index 22aef1a54ae0..d012611fda64 100644 --- a/code/game/atom/atom_tool_acts.dm +++ b/code/game/atom/atom_tool_acts.dm @@ -4,15 +4,12 @@ * Handles non-combat iteractions of a tool on this atom, * such as using a tool on a wall to deconstruct it, * or scanning someone with a health analyzer - * - * This can be overridden to add custom item interactions to this atom - * - * Do not call this directly */ -/atom/proc/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) +/atom/proc/base_item_interaction(mob/living/user, obj/item/tool, list/modifiers) SHOULD_CALL_PARENT(TRUE) PROTECTED_PROC(TRUE) + var/is_right_clicking = LAZYACCESS(modifiers, RIGHT_CLICK) var/is_left_clicking = !is_right_clicking var/early_sig_return = NONE if(is_left_clicking) @@ -24,6 +21,12 @@ if(early_sig_return) return early_sig_return + var/self_interaction = is_left_clicking \ + ? item_interaction(user, tool, modifiers) \ + : item_interaction_secondary(user, tool, modifiers) + if(self_interaction) + return self_interaction + var/interact_return = is_left_clicking \ ? tool.interact_with_atom(src, user) \ : tool.interact_with_atom_secondary(src, user) @@ -75,6 +78,27 @@ SEND_SIGNAL(tool, COMSIG_TOOL_ATOM_ACTED_SECONDARY(tool_type), src) return act_result +/** + * Called when this atom has an item used on it. + * IE, a mob is clicking on this atom with an item. + * + * Return an ITEM_INTERACT_ flag in the event the interaction was handled, to cancel further interaction code. + * Return NONE to allow default interaction / tool handling. + */ +/atom/proc/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + return NONE + +/** + * Called when this atom has an item used on it WITH RIGHT CLICK, + * IE, a mob is right clicking on this atom with an item. + * Default behavior has it run the same code as left click. + * + * Return an ITEM_INTERACT_ flag in the event the interaction was handled, to cancel further interaction code. + * Return NONE to allow default interaction / tool handling. + */ +/atom/proc/item_interaction_secondary(mob/living/user, obj/item/tool, list/modifiers) + return item_interaction(user, tool, modifiers) + /** * Called when this item is being used to interact with an atom, * IE, a mob is clicking on an atom with this item. diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index 6411a12be425..03c9399f3711 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -758,13 +758,14 @@ return update_last_used(user) -/obj/machinery/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) +/obj/machinery/base_item_interaction(mob/living/user, obj/item/tool, list/modifiers) if(SEND_SIGNAL(user, COMSIG_TRY_USE_MACHINE, src) & COMPONENT_CANT_USE_MACHINE_TOOLS) - return ITEM_INTERACT_ANY_BLOCKER + return ITEM_INTERACT_BLOCKING + . = ..() - if(. & ITEM_INTERACT_BLOCKING) - return - update_last_used(user) + if(.) + update_last_used(user) + return . /obj/machinery/_try_interact(mob/user) if((interaction_flags_machine & INTERACT_MACHINE_WIRES_IF_OPEN) && panel_open && (attempt_wire_interaction(user) == WIRE_INTERACTION_BLOCK)) diff --git a/code/game/machinery/computer/arcade/_arcade.dm b/code/game/machinery/computer/arcade/_arcade.dm new file mode 100644 index 000000000000..69994634fc3b --- /dev/null +++ b/code/game/machinery/computer/arcade/_arcade.dm @@ -0,0 +1,100 @@ +/obj/machinery/computer/arcade + name = "\proper the arcade cabinet which shouldn't exist" + desc = "This arcade cabinet has no games installed, and in fact, should not exist. \ + Report the location of this machine to your local diety." + icon_state = "arcade" + icon_keyboard = null + icon_screen = "invaders" + light_color = LIGHT_COLOR_GREEN + interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_REQUIRES_LITERACY + + ///If set, will dispense these as prizes instead of the default GLOB.arcade_prize_pool + ///Like prize pool, it must be a list of the prize and the weight of being selected. + var/list/prize_override + +/obj/machinery/computer/arcade/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(istype(tool, /obj/item/stack/arcadeticket)) + var/obj/item/stack/arcadeticket/tickets = tool + if(!tickets.use(2)) + balloon_alert(user, "need 2 tickets!") + return ITEM_INTERACT_BLOCKING + + prizevend(user) + balloon_alert(user, "prize claimed") + return ITEM_INTERACT_SUCCESS + + if(istype(tool, /obj/item/key/displaycase) || istype(tool, /obj/item/access_key)) + var/static/list/radial_menu_options + if(!radial_menu_options) + radial_menu_options = list( + "Reset Cabinet" = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_reset"), + "Cancel" = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_close"), + ) + var/radial_reset_menu = show_radial_menu(user, src, radial_menu_options, require_near = TRUE) + if(radial_reset_menu != "Reset Cabinet") + return ITEM_INTERACT_BLOCKING + playsound(loc, 'sound/items/rattling_keys.ogg', 25, TRUE) + if(!do_after(user, 10 SECONDS, src)) + return ITEM_INTERACT_BLOCKING + balloon_alert(user, "cabinet reset") + reset_cabinet(user) + return ITEM_INTERACT_SUCCESS + + return NONE + +/obj/machinery/computer/arcade/screwdriver_act(mob/living/user, obj/item/I) + //you can't stop playing when you start. + if(obj_flags & EMAGGED) + return ITEM_INTERACT_BLOCKING + return ..() + +///Performs a factory reset of the cabinet and wipes all its stats. +/obj/machinery/computer/arcade/proc/reset_cabinet(mob/living/user) + SHOULD_CALL_PARENT(TRUE) + obj_flags &= ~EMAGGED + SStgui.update_uis(src) + +/obj/machinery/computer/arcade/emp_act(severity) + . = ..() + if((machine_stat & (NOPOWER|BROKEN)) || (. & EMP_PROTECT_SELF)) + return + + var/empprize = null + var/num_of_prizes = 0 + switch(severity) + if(1) + num_of_prizes = rand(1,4) + if(2) + num_of_prizes = rand(0,2) + for(var/i = num_of_prizes; i > 0; i--) + if(prize_override) + empprize = pick_weight(prize_override) + else + empprize = pick_weight(GLOB.arcade_prize_pool) + new empprize(loc) + explosion(src, devastation_range = -1, light_impact_range = 1 + num_of_prizes, flame_range = 1 + num_of_prizes) + +///Dispenses the proper prizes and gives them a positive mood event. If valid, has a small chance to give a pulse rifle. +/obj/machinery/computer/arcade/proc/prizevend(mob/living/user, prizes = 1) + SEND_SIGNAL(src, COMSIG_ARCADE_PRIZEVEND, user, prizes) + if(user.mind?.get_skill_level(/datum/skill/gaming) >= SKILL_LEVEL_LEGENDARY && HAS_TRAIT(user, TRAIT_GAMERGOD)) + visible_message("[user] inputs an intense cheat code!",\ + span_notice("You hear a flurry of buttons being pressed.")) + say("CODE ACTIVATED: EXTRA PRIZES.") + prizes *= 2 + for(var/i in 1 to prizes) + user.add_mood_event("arcade", /datum/mood_event/arcade) + if(prob(0.0001)) //1 in a million + new /obj/item/gun/energy/pulse/prize(src) + visible_message(span_notice("[src] dispenses.. woah, a gun! Way past cool."), span_notice("You hear a chime and a shot.")) + user.client.give_award(/datum/award/achievement/misc/pulse, user) + continue + + var/prizeselect + if(prize_override) + prizeselect = pick_weight(prize_override) + else + prizeselect = pick_weight(GLOB.arcade_prize_pool) + var/atom/movable/the_prize = new prizeselect(get_turf(src)) + playsound(src, 'sound/machines/machine_vend.ogg', 50, TRUE, extrarange = -3) + visible_message(span_notice("[src] dispenses [the_prize]!"), span_notice("You hear a chime and a clunk.")) diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm index 907548d0028f..c02b2d33b42a 100644 --- a/code/game/machinery/computer/buildandrepair.dm +++ b/code/game/machinery/computer/buildandrepair.dm @@ -23,8 +23,11 @@ return TRUE -/obj/structure/frame/computer/attackby(obj/item/P, mob/living/user, params) - add_fingerprint(user) +/obj/structure/frame/computer/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + . = ..() + if(. & ITEM_INTERACT_ANY_BLOCKER) + return . + switch(state) if(0) if(P.tool_behaviour == TOOL_WRENCH) diff --git a/code/game/machinery/machine_frame.dm b/code/game/machinery/machine_frame.dm new file mode 100644 index 000000000000..ccdcddc87052 --- /dev/null +++ b/code/game/machinery/machine_frame.dm @@ -0,0 +1,472 @@ +/obj/structure/frame/machine + name = "machine frame" + desc = "The standard frame for most station appliances. Its appearance and function is controlled by the inserted board." + board_type = /obj/item/circuitboard/machine + /// List of all compnents inside the frame contributing to its construction + var/list/components + /// List of all components required to construct the frame + var/list/req_components + /// User-friendly list of names of required components + var/list/req_component_names + +/obj/structure/frame/machine/Initialize(mapload) + . = ..() + register_context() + +/obj/structure/frame/machine/Destroy() + QDEL_LIST(components) + return ..() + +/obj/structure/frame/machine/atom_deconstruct(disassembled = TRUE) + if(state >= FRAME_STATE_WIRED) + new /obj/item/stack/cable_coil(drop_location(), 5) + dump_contents() + return ..() + +/obj/structure/frame/machine/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = NONE + if(isnull(held_item)) + return + + if(held_item.tool_behaviour == TOOL_WRENCH && !circuit?.needs_anchored) + context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]anchor" + return CONTEXTUAL_SCREENTIP_SET + + switch(state) + if(FRAME_STATE_EMPTY) + if(istype(held_item, /obj/item/stack/cable_coil)) + context[SCREENTIP_CONTEXT_LMB] = "Wire Frame" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_WELDER) + context[SCREENTIP_CONTEXT_LMB] = "Unweld frame" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "Disassemble frame" + return CONTEXTUAL_SCREENTIP_SET + if(FRAME_STATE_WIRED) + if(held_item.tool_behaviour == TOOL_WIRECUTTER) + context[SCREENTIP_CONTEXT_LMB] = "Cut wires" + return CONTEXTUAL_SCREENTIP_SET + else if(istype(held_item, board_type)) + context[SCREENTIP_CONTEXT_LMB] = "Insert board" + return CONTEXTUAL_SCREENTIP_SET + if(FRAME_STATE_BOARD_INSTALLED) + if(held_item.tool_behaviour == TOOL_CROWBAR) + context[SCREENTIP_CONTEXT_LMB] = "Pry out components" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + var/needs_components = FALSE + for(var/component in req_components) + if(!req_components[component]) + continue + needs_components = TRUE + break + if(!needs_components) + context[SCREENTIP_CONTEXT_LMB] = "Complete frame" + return CONTEXTUAL_SCREENTIP_SET + else if(!istype(held_item, /obj/item/storage/part_replacer)) + for(var/component in req_components) + if(!req_components[component]) + continue + var/stock_part_path + if(ispath(component, /obj/item)) + stock_part_path = component + else if(ispath(component, /datum/stock_part)) + var/datum/stock_part/stock_part_datum_type = component + stock_part_path = initial(stock_part_datum_type.physical_object_type) + if(istype(held_item, stock_part_path)) + context[SCREENTIP_CONTEXT_LMB] = "Insert part" + return CONTEXTUAL_SCREENTIP_SET + +/obj/structure/frame/machine/examine(user) + . = ..() + if(!circuit?.needs_anchored) + . += span_notice("It can be [EXAMINE_HINT("anchored")] [anchored ? "loose" : "in place"]") + if(state == FRAME_STATE_EMPTY) + if(!anchored) + . += span_notice("It can be [EXAMINE_HINT("welded")] or [EXAMINE_HINT("screwed")] apart.") + . += span_warning("It needs [EXAMINE_HINT("5 cable")] pieces to wire it.") + return + if(state == FRAME_STATE_WIRED) + . += span_info("Its wires can be cut with a [EXAMINE_HINT("wirecutter")].") + if(state != FRAME_STATE_BOARD_INSTALLED) + . += span_warning("Its missing a circuit board..") + return + if(!length(req_components)) + . += span_info("It requires no components.") + return + + var/list/nice_list = list() + for(var/component in req_components) + if(!req_components[component]) + continue + nice_list += list("[req_components[component]] [req_component_names[component]]\s") + . += span_info("It requires [english_list(nice_list, "no more components")].") + + . += span_info("All the components can be [EXAMINE_HINT("pried")] out.") + if(!length(nice_list)) + . += span_info("The frame can be [EXAMINE_HINT("screwed")] to complete it.") + +/obj/structure/frame/machine/dump_contents() + var/atom/drop_loc = drop_location() + + // We need a snowflake check for stack items since they don't exist anymore + for(var/component in circuit?.req_components) + if(!ispath(component, /obj/item/stack)) + continue + var/obj/item/stack/stack_path = component + var/stack_amount = circuit.req_components[component] - req_components[component] + if(stack_amount > 0) + new stack_path(drop_loc, stack_amount) + + // Rest of the stuff can just be spat out (this includes the circuitboard0) + for(var/component in components) + if(ismovable(component)) + var/atom/movable/atom_component = component + atom_component.forceMove(drop_loc) + + else if(istype(component, /datum/stock_part)) + var/datum/stock_part/stock_part_datum = component + var/physical_object_type = initial(stock_part_datum.physical_object_type) + new physical_object_type(drop_loc) + + else + stack_trace("Invalid component [component] was found in constructable frame") + + components = null + req_components = null + req_component_names = null + +/obj/structure/frame/machine/install_board(mob/living/user, obj/item/circuitboard/machine/board, by_hand = TRUE) + if(state == FRAME_STATE_EMPTY) + balloon_alert(user, "needs wiring!") + return FALSE + if(state == FRAME_STATE_BOARD_INSTALLED) + balloon_alert(user, "circuit already installed!") + return FALSE + if(!anchored && istype(board) && board.needs_anchored) + balloon_alert(user, "frame must be anchored!") + return FALSE + + return ..() + +/obj/structure/frame/machine/circuit_added(obj/item/circuitboard/machine/added) + state = FRAME_STATE_BOARD_INSTALLED + update_appearance(UPDATE_ICON_STATE) + + //add circuit board as the first component to the list of components + //required for part_replacer to locate it while exchanging parts + //so it does not early return in /obj/machinery/proc/exchange_parts + components = list(circuit) + req_components = added.req_components.Copy() + if(!req_components) + return + + //creates a list of names from all the required parts + req_component_names = list() + for(var/component_path in req_components) + if(!ispath(component_path)) + continue + + if(ispath(component_path, /obj/item/stack)) + var/obj/item/stack/stack_path = component_path + if(initial(stack_path.singular_name)) + req_component_names[component_path] = initial(stack_path.singular_name) + else + req_component_names[component_path] = initial(stack_path.name) + else if(ispath(component_path, /datum/stock_part)) + var/datum/stock_part/stock_part = component_path + var/obj/item/physical_object_type = initial(stock_part.physical_object_type) + + req_component_names[component_path] = initial(physical_object_type.name) + else if(ispath(component_path, /obj/item/stock_parts)) + var/obj/item/stock_parts/stock_part = component_path + + if(!added.specific_parts && initial(stock_part.base_name)) + req_component_names[component_path] = initial(stock_part.base_name) + else + req_component_names[component_path] = initial(stock_part.name) + else if(ispath(component_path, /obj/item)) + var/obj/item/part = component_path + + req_component_names[component_path] = initial(part.name) + else + stack_trace("Invalid component part [component_path] in [type], couldn't get its name") + req_component_names[component_path] = "[component_path] (this is a bug)" + +/obj/structure/frame/machine/circuit_removed(obj/item/circuitboard/machine/removed) + components -= removed + state = FRAME_STATE_WIRED + update_appearance(UPDATE_ICON_STATE) + +/** + * Returns the instance of path1 in list, else path2 in list + * + * Arguments + * * parts - the list of parts to search + * * path1 - the first path to search for + * * path2 - the second path to search for, if path1 is not found + */ +/obj/structure/frame/machine/proc/look_for(list/parts, path1, path2) + PRIVATE_PROC(TRUE) + + return (locate(path1) in parts) || (path2 ? (locate(path2) in parts) : null) + +/obj/structure/frame/machine/install_parts_from_part_replacer(mob/living/user, obj/item/storage/part_replacer/replacer, no_sound = FALSE) + if(!length(replacer.contents)) + return FALSE + var/amt = 0 + for(var/path in req_components) + amt += req_components[path] + if(!amt) + return FALSE + + var/play_sound = FALSE + var/list/part_list = replacer.get_sorted_parts() //parts sorted in order of tier + for(var/path in req_components) + var/target_path + if(ispath(path, /datum/stock_part)) + var/datum/stock_part/datum_part = path + target_path = initial(datum_part.physical_object_base_type) + else + target_path = path + + var/obj/item/part + while(req_components[path] > 0 && (part = look_for(part_list, target_path, ispath(path, /obj/item/stack/ore/bluespace_crystal) ? /obj/item/stack/sheet/bluespace_crystal : null))) + part_list -= part + if(istype(part, /obj/item/stack)) + var/obj/item/stack/S = part + var/used_amt = min(round(S.get_amount()), req_components[path]) + var/stack_name = S.singular_name + if(!used_amt || !S.use(used_amt)) + continue + req_components[path] -= used_amt + // No balloon alert here so they can look back and see what they added + to_chat(user, span_notice("You add [used_amt] [stack_name] to [src].")) + play_sound = TRUE + else if(replacer.atom_storage.attempt_remove(part, src)) + var/stock_part_datum = GLOB.stock_part_datums_per_object[part.type] + if (!isnull(stock_part_datum)) + components += stock_part_datum + qdel(part) + else + components += part + part.forceMove(src) + req_components[path]-- + // No balloon alert here so they can look back and see what they added + to_chat(user, span_notice("You add [part] to [src].")) + play_sound = TRUE + + if(play_sound && !no_sound) + replacer.play_rped_sound() + if(replacer.works_from_distance) + user.Beam(src, icon_state = "rped_upgrade", time = 0.5 SECONDS) + return TRUE + +/obj/structure/frame/machine/can_be_unfasten_wrench(mob/user, silent) + . = ..() + if(. != SUCCESSFUL_UNFASTEN) + return . + + if(circuit?.needs_anchored) + balloon_alert(user, "frame must be anchored!") + return FAILED_UNFASTEN + + return . + +/obj/structure/frame/machine/screwdriver_act(mob/living/user, obj/item/tool) + . = ..() + if(. & ITEM_INTERACT_ANY_BLOCKER) + return . + if(state != FRAME_STATE_BOARD_INSTALLED) + return . + + if(finalize_construction(user, tool)) + return ITEM_INTERACT_SUCCESS + + return ITEM_INTERACT_BLOCKING + +/obj/structure/frame/machine/wirecutter_act(mob/living/user, obj/item/tool) + if(user.combat_mode) + return NONE + if(state != FRAME_STATE_WIRED) + return ITEM_INTERACT_BLOCKING + + balloon_alert(user, "removing cables...") + if(!tool.use_tool(src, user, 2 SECONDS, volume = 50) || state != FRAME_STATE_WIRED) + return ITEM_INTERACT_BLOCKING + + state = FRAME_STATE_EMPTY + update_appearance(UPDATE_ICON_STATE) + new /obj/item/stack/cable_coil(drop_location(), 5) + return ITEM_INTERACT_SUCCESS + +/obj/structure/frame/machine/crowbar_act(mob/living/user, obj/item/tool) + if(user.combat_mode) + return NONE + if(state != FRAME_STATE_BOARD_INSTALLED) + return ITEM_INTERACT_BLOCKING + + tool.play_tool_sound(src) + var/list/leftover_components = components.Copy() - circuit + dump_contents() + balloon_alert(user, "circuit board[length(leftover_components) ? " and components" : ""] removed") + // Circuit exited handles updating state + return ITEM_INTERACT_SUCCESS + +/** + * Attempts to add the passed part to the frame + * + * Requires no sanity check that the passed part is a stock part + * + * Arguments + * * user - the player + * * tool - the part to add + */ +/obj/structure/frame/machine/proc/add_part(mob/living/user, obj/item/tool) + PRIVATE_PROC(TRUE) + + for(var/stock_part_base in req_components) + if (req_components[stock_part_base] == 0) + continue + + var/stock_part_path + + if(ispath(stock_part_base, /obj/item)) + stock_part_path = stock_part_base + else if(ispath(stock_part_base, /datum/stock_part)) + var/datum/stock_part/stock_part_datum_type = stock_part_base + stock_part_path = initial(stock_part_datum_type.physical_object_type) + else + stack_trace("Bad stock part in req_components: [stock_part_base]") + continue + + //if we require an bluespace crystall and we have an full sheet of them we can allow that + if(ispath(stock_part_path, /obj/item/stack/ore/bluespace_crystal) && istype(tool, /obj/item/stack/sheet/bluespace_crystal)) + pass() //allow it + else if(!istype(tool, stock_part_path)) + continue + + if(isstack(tool)) + var/obj/item/stack/S = tool + var/used_amt = min(round(S.get_amount()), req_components[stock_part_path]) + if(used_amt && S.use(used_amt)) + req_components[stock_part_path] -= used_amt + // No balloon alert here so they can look back and see what they added + to_chat(user, span_notice("You add [tool] to [src].")) + return + + // We might end up qdel'ing the part if it's a stock part datum. + // In practice, this doesn't have side effects to the name, + // but academically we should not be using an object after it's deleted. + var/part_name = "[tool]" + + if (ispath(stock_part_base, /datum/stock_part)) + // We can't just reuse stock_part_path here or its singleton, + // or else putting in a tier 2 part will deconstruct to a tier 1 part. + var/stock_part_datum = GLOB.stock_part_datums_per_object[tool.type] + if (isnull(stock_part_datum)) + stack_trace("tool.type] does not have an associated stock part datum!") + continue + + components += stock_part_datum + + // We regenerate the stock parts on deconstruct. + // This technically means we lose unique qualities of the stock part, but + // it's worth it for how dramatically this simplifies the code. + // The only place I can see it affecting anything is like...RPG qualities. :P + qdel(tool) + else if(user.transferItemToLoc(tool, src)) + components += tool + else + break + + // No balloon alert here so they can look back and see what they added + to_chat(user, span_notice("You add [part_name] to [src].")) + req_components[stock_part_base]-- + return TRUE + + balloon_alert(user, "can't add that!") + return FALSE + +/obj/structure/frame/machine/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + . = ..() + if(. & ITEM_INTERACT_ANY_BLOCKER) + return . + + switch(state) + if(FRAME_STATE_EMPTY) + if(istype(tool, /obj/item/stack/cable_coil)) + if(!tool.tool_start_check(user, amount = 5)) + return ITEM_INTERACT_BLOCKING + + balloon_alert(user, "adding cables...") + if(!tool.use_tool(src, user, 2 SECONDS, volume = 50, amount = 5) || state != FRAME_STATE_EMPTY) + return ITEM_INTERACT_BLOCKING + + state = FRAME_STATE_WIRED + update_appearance(UPDATE_ICON_STATE) + return ITEM_INTERACT_SUCCESS + + if(FRAME_STATE_WIRED) + if(isnull(circuit) && istype(tool, /obj/item/storage/part_replacer)) + return install_circuit_from_part_replacer(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING + + if(FRAME_STATE_BOARD_INSTALLED) + if(istype(tool, /obj/item/storage/part_replacer)) + return install_parts_from_part_replacer(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING + + return . + +// Override of base_item_interaction so we only try to add parts to the frame AFTER running item_interaction and all the tool_acts +/obj/structure/frame/machine/base_item_interaction(mob/living/user, obj/item/tool, list/modifiers) + . = ..() + if(. & ITEM_INTERACT_ANY_BLOCKER) + return . + if(user.combat_mode) + return NONE + + return add_part(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING + +/** + * Attempt to finalize the construction of the frame into a machine + * as according to our circuit and parts + * + * If successful, results in qdel'ing the frame and newing of a machine + * + * Arguments + * * user - the player + * * tool - the tool used to finalize the construction + */ +/obj/structure/frame/machine/finalize_construction(mob/living/user, obj/item/tool) + for(var/component in req_components) + if(req_components[component] > 0) + user.balloon_alert(user, "missing components!") + return FALSE + + tool.play_tool_sound(src) + var/obj/machinery/new_machine = new circuit.build_path(loc) + if(istype(new_machine)) + new_machine.clear_components() + // Set anchor state + new_machine.set_anchored(anchored) + // Prevent us from dropping stuff thanks to /Exited + var/obj/item/circuitboard/machine/leaving_circuit = circuit + circuit = null + // Assign the circuit & parts & move them all at once into the machine + // no need to seperatly move circuit board as its already part of the components list + new_machine.circuit = leaving_circuit + new_machine.component_parts = components + for (var/obj/new_part in components) + new_part.forceMove(new_machine) + //Inform machine that its finished & cleanup + new_machine.RefreshParts() + new_machine.on_construction(user) + components = null + qdel(src) + return TRUE + +/obj/structure/frame/machine/secured + icon_state = "box_1" + state = FRAME_STATE_WIRED + anchored = TRUE diff --git a/code/game/machinery/slotmachine.dm b/code/game/machinery/slotmachine.dm index ddfe76f56af1..77f666b3ba8c 100644 --- a/code/game/machinery/slotmachine.dm +++ b/code/game/machinery/slotmachine.dm @@ -84,48 +84,57 @@ icon_screen = "slots_screen" return ..() -/obj/machinery/computer/slot_machine/attackby(obj/item/I, mob/living/user, params) - if(istype(I, /obj/item/coin)) - var/obj/item/coin/C = I + +/obj/machinery/computer/slot_machine/item_interaction(mob/living/user, obj/item/inserted, list/modifiers) + if(istype(inserted, /obj/item/coin)) + var/obj/item/coin/inserted_coin = inserted if(paymode == COIN) if(prob(2)) - if(!user.transferItemToLoc(C, drop_location(), silent = FALSE)) - return - C.throw_at(user, 3, 10) + if(!user.transferItemToLoc(inserted_coin, drop_location(), silent = FALSE)) + return ITEM_INTERACT_BLOCKING + inserted_coin.throw_at(user, 3, 10) if(prob(10)) balance = max(balance - SPIN_PRICE, 0) to_chat(user, span_warning("[src] spits your coin back out!")) - + return ITEM_INTERACT_BLOCKING else - if(!user.temporarilyRemoveItemFromInventory(C)) - return - to_chat(user, span_notice("You insert [C] into [src]'s slot!")) - balance += C.value - qdel(C) + if(!user.temporarilyRemoveItemFromInventory(inserted_coin)) + return ITEM_INTERACT_BLOCKING + balloon_alert(user, "coin insterted") + balance += inserted_coin.value + qdel(inserted_coin) + return ITEM_INTERACT_SUCCESS else - to_chat(user, span_warning("This machine is only accepting holochips!")) - else if(istype(I, /obj/item/holochip)) + balloon_alert(user, "holochips only!") + return ITEM_INTERACT_BLOCKING + + if(istype(inserted, /obj/item/holochip)) if(paymode == HOLOCHIP) - var/obj/item/holochip/H = I - if(!user.temporarilyRemoveItemFromInventory(H)) - return - to_chat(user, span_notice("You insert [H.credits] holocredits into [src]'s slot!")) - balance += H.credits - qdel(H) - else - to_chat(user, span_warning("This machine is only accepting coins!")) - else if(I.tool_behaviour == TOOL_MULTITOOL) - if(balance > 0) - visible_message("[src] says, 'ERROR! Please empty the machine balance before altering paymode'") //Prevents converting coins into holocredits and vice versa + var/obj/item/holochip/inserted_chip = inserted + if(!user.temporarilyRemoveItemFromInventory(inserted_chip)) + return ITEM_INTERACT_BLOCKING + balloon_alert(user, "[inserted_chip.credits] credit[inserted_chip.credits == 1 ? "" : "s"] inserted") + balance += inserted_chip.credits + qdel(inserted_chip) + return ITEM_INTERACT_SUCCESS else - if(paymode == HOLOCHIP) - paymode = COIN - visible_message("[src] says, 'This machine now works with COINS!'") - else - paymode = HOLOCHIP - visible_message("[src] says, 'This machine now works with HOLOCHIPS!'") + balloon_alert(user, "coins only!") + return ITEM_INTERACT_BLOCKING + + return NONE + +/obj/machinery/computer/slot_machine/multitool_act(mob/living/user, obj/item/tool) + if(balance > 0) + visible_message("[src] says, 'ERROR! Please empty the machine balance before altering paymode'") //Prevents converting coins into holocredits and vice versa + return ITEM_INTERACT_BLOCKING + + if(paymode == HOLOCHIP) + paymode = COIN + balloon_alert(user, "now using coins") else - return ..() + paymode = HOLOCHIP + balloon_alert(user, "now using holochips") + return ITEM_INTERACT_SUCCESS /obj/machinery/computer/slot_machine/emag_act(mob/user, obj/item/card/emag/emag_card) if(obj_flags & EMAGGED) diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm index 2ad1096ac582..ad878e6529a3 100644 --- a/code/game/objects/buckling.dm +++ b/code/game/objects/buckling.dm @@ -28,18 +28,6 @@ if(user_unbuckle_mob(buckled_mobs[1],user)) return TRUE -/atom/movable/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) - if(!can_buckle || !istype(tool, /obj/item/riding_offhand) || !user.Adjacent(src)) - return ..() - - var/obj/item/riding_offhand/riding_item = tool - var/mob/living/carried_mob = riding_item.rider - if(carried_mob == user) //Piggyback user. - return ITEM_INTERACT_BLOCKING - user.unbuckle_mob(carried_mob) - carried_mob.forceMove(get_turf(src)) - return mouse_buckle_handling(carried_mob, user) ? ITEM_INTERACT_SUCCESS: ITEM_INTERACT_BLOCKING - //literally just the above extension of attack_hand(), but for silicons instead (with an adjacency check, since attack_robot() being called doesn't mean that you're adjacent to something) /atom/movable/attack_robot(mob/living/user) . = ..() diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm index 6bfca96bf271..c5a86332007b 100644 --- a/code/game/objects/items/devices/laserpointer.dm +++ b/code/game/objects/items/devices/laserpointer.dm @@ -71,14 +71,11 @@ diode = null return TRUE -/obj/item/laser_pointer/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) - . = ..() - if(. & ITEM_INTERACT_ANY_BLOCKER) - return . +/obj/item/laser_pointer/item_interaction(mob/living/user, obj/item/tool, list/modifiers) if(isnull(crystal_lens)) - return . + return NONE if(tool_behaviour != TOOL_WIRECUTTER && tool_behaviour != TOOL_HEMOSTAT) - return . + return NONE tool.play_tool_sound(src) balloon_alert(user, "removed crystal lens") crystal_lens.forceMove(drop_location()) diff --git a/code/game/objects/items/devices/scanners/autopsy_scanner.dm b/code/game/objects/items/devices/scanners/autopsy_scanner.dm index c0ae78b5278f..8cbc9da1c266 100644 --- a/code/game/objects/items/devices/scanners/autopsy_scanner.dm +++ b/code/game/objects/items/devices/scanners/autopsy_scanner.dm @@ -13,7 +13,7 @@ custom_materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*2) custom_price = PAYCHECK_COMMAND -/obj/item/autopsy_scanner/interact_with_atom(atom/interacting_with, mob/living/user) +/obj/item/autopsy_scanner/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(!isliving(interacting_with)) return NONE if(!user.can_read(src) || user.is_blind()) diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm index 22654ff72946..8ed743064c8a 100644 --- a/code/game/objects/items/devices/scanners/health_analyzer.dm +++ b/code/game/objects/items/devices/scanners/health_analyzer.dm @@ -54,7 +54,7 @@ if(SCANMODE_WOUND) to_chat(user, span_notice("You switch the health analyzer to report extra info on wounds.")) -/obj/item/healthanalyzer/interact_with_atom(atom/interacting_with, mob/living/user) +/obj/item/healthanalyzer/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(!isliving(interacting_with)) return NONE if(!user.can_read(src) || user.is_blind()) @@ -92,7 +92,7 @@ add_fingerprint(user) -/obj/item/healthanalyzer/interact_with_atom_secondary(atom/interacting_with, mob/living/user) +/obj/item/healthanalyzer/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers) if(!isliving(interacting_with)) return NONE if(!user.can_read(src) || user.is_blind()) @@ -563,7 +563,7 @@ /obj/item/healthanalyzer/simple/proc/violence_damage(mob/living/user) user.adjustBruteLoss(4) -/obj/item/healthanalyzer/simple/interact_with_atom(atom/interacting_with, mob/living/user) +/obj/item/healthanalyzer/simple/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(!isliving(interacting_with)) return NONE if(!user.can_read(src) || user.is_blind()) diff --git a/code/game/objects/items/devices/scanners/sequence_scanner.dm b/code/game/objects/items/devices/scanners/sequence_scanner.dm index c8dfa5b87250..319584ee0807 100644 --- a/code/game/objects/items/devices/scanners/sequence_scanner.dm +++ b/code/game/objects/items/devices/scanners/sequence_scanner.dm @@ -29,7 +29,7 @@ if(LAZYLEN(genetic_makeup_buffer) > 0) . += span_notice("It has the genetic makeup of \"[genetic_makeup_buffer["name"]]\" stored inside its buffer") -/obj/item/sequence_scanner/interact_with_atom(atom/interacting_with, mob/living/user) +/obj/item/sequence_scanner/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(!isliving(interacting_with)) return NONE @@ -46,7 +46,7 @@ user.visible_message(span_notice("[user] fails to analyze [interacting_with]'s genetic sequence."), span_warning("[interacting_with] has no readable genetic sequence!")) return ITEM_INTERACT_BLOCKING -/obj/item/sequence_scanner/interact_with_atom_secondary(atom/interacting_with, mob/living/user) +/obj/item/sequence_scanner/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers) if(!isliving(interacting_with)) return NONE diff --git a/code/game/objects/items/devices/scanners/slime_scanner.dm b/code/game/objects/items/devices/scanners/slime_scanner.dm index 05b11b3be3a6..ae0061398a96 100644 --- a/code/game/objects/items/devices/scanners/slime_scanner.dm +++ b/code/game/objects/items/devices/scanners/slime_scanner.dm @@ -13,7 +13,7 @@ throw_range = 7 custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*0.30, /datum/material/glass=SMALL_MATERIAL_AMOUNT * 0.20) -/obj/item/slime_scanner/interact_with_atom(atom/interacting_with, mob/living/user) +/obj/item/slime_scanner/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(!isliving(interacting_with)) return NONE if(!user.can_read(src) || user.is_blind()) diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm index 99322597b4b5..967ee1576b7b 100644 --- a/code/game/objects/items/devices/traitordevices.dm +++ b/code/game/objects/items/devices/traitordevices.dm @@ -76,7 +76,7 @@ effective or pretty fucking useless. var/intensity = 10 // how much damage the radiation does var/wavelength = 10 // time it takes for the radiation to kick in, in seconds -/obj/item/healthanalyzer/rad_laser/interact_with_atom(atom/interacting_with, mob/living/user) +/obj/item/healthanalyzer/rad_laser/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(!stealth || !irradiate) . = ..() diff --git a/code/game/objects/items/emags.dm b/code/game/objects/items/emags.dm index c404cda26592..7882018701e4 100644 --- a/code/game/objects/items/emags.dm +++ b/code/game/objects/items/emags.dm @@ -45,7 +45,7 @@ user.visible_message(span_notice("[user] shows you: [icon2html(src, viewers(user))] [name]."), span_notice("You show [src].")) add_fingerprint(user) -/obj/item/card/emagfake/interact_with_atom(atom/interacting_with, mob/living/user) +/obj/item/card/emagfake/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE) return ITEM_INTERACT_SKIP_TO_ATTACK // So it does the attack animation. @@ -53,7 +53,7 @@ . = ..() type_blacklist = list(typesof(/obj/machinery/door/airlock) + typesof(/obj/machinery/door/window/) + typesof(/obj/machinery/door/firedoor) - typesof(/obj/machinery/door/airlock/tram)) //list of all typepaths that require a specialized emag to hack. -/obj/item/card/emag/interact_with_atom(atom/interacting_with, mob/living/user) +/obj/item/card/emag/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(!can_emag(interacting_with, user)) return ITEM_INTERACT_BLOCKING log_combat(user, interacting_with, "attempted to emag") diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm index 306bc0fa9f94..83f7128d8463 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -35,7 +35,7 @@ /// Time it takes to assess injuries when looping healing var/assessing_injury_delay = 1 SECONDS -/obj/item/stack/medical/interact_with_atom(atom/interacting_with, mob/living/user) +/obj/item/stack/medical/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(!isliving(interacting_with)) return NONE if(!begin_heal_loop(interacting_with, user)) diff --git a/code/game/objects/items/stacks/telecrystal.dm b/code/game/objects/items/stacks/telecrystal.dm index 51d21d6ef6cc..cff39ba5e90f 100644 --- a/code/game/objects/items/stacks/telecrystal.dm +++ b/code/game/objects/items/stacks/telecrystal.dm @@ -11,7 +11,7 @@ merge_type = /obj/item/stack/telecrystal novariants = FALSE -/obj/item/stack/telecrystal/interact_with_atom(atom/interacting_with, mob/living/user) +/obj/item/stack/telecrystal/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(interacting_with != user) //You can't go around smacking people with crystals to find out if they have an uplink or not. return NONE diff --git a/code/game/objects/items/tools/weldingtool.dm b/code/game/objects/items/tools/weldingtool.dm index f6ace35c2ce7..6a3b40b2dd1d 100644 --- a/code/game/objects/items/tools/weldingtool.dm +++ b/code/game/objects/items/tools/weldingtool.dm @@ -134,7 +134,7 @@ LAZYREMOVE(update_overlays_on_z, sparks) target.cut_overlay(sparks) -/obj/item/weldingtool/interact_with_atom(atom/interacting_with, mob/living/user) +/obj/item/weldingtool/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(!ishuman(interacting_with)) return NONE if(user.combat_mode) diff --git a/code/game/objects/structures/false_walls.dm b/code/game/objects/structures/false_walls.dm index 9f4f4fdd73f4..8eceb090e759 100644 --- a/code/game/objects/structures/false_walls.dm +++ b/code/game/objects/structures/false_walls.dm @@ -91,9 +91,9 @@ qdel(src) return T -/obj/structure/falsewall/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) +/obj/structure/falsewall/item_interaction(mob/living/user, obj/item/tool, list/modifiers) if(!opening || !tool.tool_behaviour) - return ..() + return NONE to_chat(user, span_warning("You must wait until the door has stopped moving!")) return ITEM_INTERACT_BLOCKING diff --git a/code/game/objects/structures/reflector.dm b/code/game/objects/structures/reflector.dm index 0d6799b05835..0700f19818a3 100644 --- a/code/game/objects/structures/reflector.dm +++ b/code/game/objects/structures/reflector.dm @@ -79,10 +79,10 @@ P.decayedRange = max(P.decayedRange--, 0) return BULLET_ACT_FORCE_PIERCE -/obj/structure/reflector/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) +/obj/structure/reflector/item_interaction(mob/living/user, obj/item/tool, list/modifiers) if(admin && tool.tool_behaviour) return ITEM_INTERACT_BLOCKING - return ..() + return NONE /obj/structure/reflector/screwdriver_act(mob/living/user, obj/item/tool) can_rotate = !can_rotate diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 9131607c6c42..c2049cf6b37e 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -836,14 +836,17 @@ if(istype(mover) && (mover.pass_flags & PASSTABLE)) return TRUE -/obj/structure/rack/MouseDrop_T(obj/O, mob/user) - . = ..() - if ((!( isitem(O) ) || user.get_active_held_item() != O)) - return - if(!user.dropItemToGround(O)) - return - if(O.loc != src.loc) - step(O, get_dir(O, src)) +/obj/structure/rack/wrench_act_secondary(mob/living/user, obj/item/tool) + tool.play_tool_sound(src) + deconstruct(TRUE) + return ITEM_INTERACT_SUCCESS + +/obj/structure/rack/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if((tool.item_flags & ABSTRACT) || user.combat_mode) + return NONE + if(user.transferItemToLoc(tool, drop_location(), silent = FALSE)) + return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_BLOCKING /obj/structure/rack/attackby(obj/item/W, mob/living/user, params) var/list/modifiers = params2list(params) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 52e694d4aa44..bd017a858618 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -190,11 +190,11 @@ return return ..() -/obj/structure/window/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) +/obj/structure/window/item_interaction(mob/living/user, obj/item/tool, list/modifiers) if(!can_be_reached(user)) return ITEM_INTERACT_SKIP_TO_ATTACK // Guess you get to hit it add_fingerprint(user) - return ..() + return NONE /obj/structure/window/welder_act(mob/living/user, obj/item/tool) if(atom_integrity >= max_integrity) diff --git a/code/game/turfs/open/floor/plating.dm b/code/game/turfs/open/floor/plating.dm index 3e58d647023e..46bb91df9dee 100644 --- a/code/game/turfs/open/floor/plating.dm +++ b/code/game/turfs/open/floor/plating.dm @@ -177,9 +177,8 @@ ScrapeAway(flags = CHANGETURF_INHERIT_AIR) return TRUE -/turf/open/floor/plating/foam/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) - SHOULD_CALL_PARENT(FALSE) - return NONE // Fuck you +/turf/open/floor/plating/foam/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + return user.combat_mode ? ITEM_INTERACT_SKIP_TO_ATTACK : ITEM_INTERACT_BLOCKING // Fuck you //reinforced plating deconstruction states #define PLATE_INTACT 0 diff --git a/code/modules/antagonists/abductor/equipment/gear/abductor_items.dm b/code/modules/antagonists/abductor/equipment/gear/abductor_items.dm index 66004140e592..2d08defea3f3 100644 --- a/code/modules/antagonists/abductor/equipment/gear/abductor_items.dm +++ b/code/modules/antagonists/abductor/equipment/gear/abductor_items.dm @@ -48,7 +48,7 @@ icon_state = "gizmo_scan" to_chat(user, span_notice("You switch the device to [mode == GIZMO_SCAN? "SCAN": "MARK"] MODE")) -/obj/item/abductor/gizmo/interact_with_atom(atom/interacting_with, mob/living/user) +/obj/item/abductor/gizmo/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(!ScientistCheck(user)) return ITEM_INTERACT_SKIP_TO_ATTACK // So you slap them with it if(!console) @@ -110,7 +110,7 @@ icon_state = "silencer" inhand_icon_state = "gizmo" -/obj/item/abductor/silencer/interact_with_atom(atom/interacting_with, mob/living/user) +/obj/item/abductor/silencer/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(!AbductorCheck(user)) return ITEM_INTERACT_SKIP_TO_ATTACK // So you slap them with it diff --git a/code/modules/food_and_drinks/machinery/griddle.dm b/code/modules/food_and_drinks/machinery/griddle.dm index 5fe48cca50ae..29ce99df50d4 100644 --- a/code/modules/food_and_drinks/machinery/griddle.dm +++ b/code/modules/food_and_drinks/machinery/griddle.dm @@ -81,6 +81,43 @@ else return ..() +/obj/machinery/griddle/item_interaction_secondary(mob/living/user, obj/item/item, list/modifiers) + if(isnull(item.atom_storage)) + return NONE + + for(var/obj/tray_item in griddled_objects) + item.atom_storage.attempt_insert(tray_item, user, TRUE) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/griddle/item_interaction(mob/living/user, obj/item/item, list/modifiers) + if(isnull(item.atom_storage)) + return NONE + + if(length(contents) >= max_items) + balloon_alert(user, "it's full!") + return ITEM_INTERACT_BLOCKING + + if(!istype(item, /obj/item/storage/bag/tray)) + // Non-tray dumping requires a do_after + to_chat(user, span_notice("You start dumping out the contents of [item] into [src]...")) + if(!do_after(user, 2 SECONDS, target = item)) + return ITEM_INTERACT_BLOCKING + + var/loaded = 0 + for(var/obj/tray_item in item) + if(!IS_EDIBLE(tray_item)) + continue + if(length(contents) >= max_items) + break + if(item.atom_storage.attempt_remove(tray_item, src)) + loaded++ + AddToGrill(tray_item, user) + if(loaded) + to_chat(user, span_notice("You insert [loaded] item\s into [src].")) + update_appearance() + return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_BLOCKING + /obj/machinery/griddle/attack_hand(mob/user, list/modifiers) . = ..() toggle_mode() diff --git a/code/modules/food_and_drinks/machinery/grill.dm b/code/modules/food_and_drinks/machinery/grill.dm index 695c70b75dae..c643e030b32f 100644 --- a/code/modules/food_and_drinks/machinery/grill.dm +++ b/code/modules/food_and_drinks/machinery/grill.dm @@ -117,13 +117,133 @@ /obj/machinery/grill/attack_ai(mob/user) return -/obj/machinery/grill/attack_hand(mob/user, list/modifiers) - if(grilled_item) - to_chat(user, span_notice("You take out [grilled_item] from [src].")) - grilled_item.forceMove(drop_location()) - update_appearance() - return - return ..() + +/// Makes grill fuel from a unit of stack +/obj/machinery/grill/proc/burn_stack() + PRIVATE_PROC(TRUE) + + //compute boost from wood or coal + var/boost + for(var/obj/item/stack in contents) + boost = 5 * (GRILL_FUELUSAGE_IDLE + GRILL_FUELUSAGE_ACTIVE) + if(istype(stack, /obj/item/stack/sheet/mineral/coal)) + boost *= 2 + if(stack.use(1)) + grill_fuel += boost + update_appearance(UPDATE_ICON_STATE) + +/obj/machinery/grill/item_interaction(mob/living/user, obj/item/weapon, list/modifiers) + if(user.combat_mode || (weapon.item_flags & ABSTRACT) || (weapon.flags_1 & HOLOGRAM_1) || (weapon.resistance_flags & INDESTRUCTIBLE)) + return NONE + + if(istype(weapon, /obj/item/stack/sheet/mineral/coal) || istype(weapon, /obj/item/stack/sheet/mineral/wood)) + if(!QDELETED(grilled_item)) + return NONE + if(!anchored) + balloon_alert(user, "anchor it first!") + return ITEM_INTERACT_BLOCKING + + //required for amount subtypes + var/target_type + if(istype(weapon, /obj/item/stack/sheet/mineral/coal)) + target_type = /obj/item/stack/sheet/mineral/coal + else + target_type = /obj/item/stack/sheet/mineral/wood + + //transfer or merge stacks if we have enough space + var/merged = FALSE + var/obj/item/stack/target = weapon + for(var/obj/item/stack/stored in contents) + if(!istype(stored, target_type)) + continue + if(stored.amount == MAX_STACK_SIZE) + balloon_alert(user, "no space!") + return ITEM_INTERACT_BLOCKING + target.merge(stored) + merged = TRUE + break + if(!merged) + weapon.forceMove(src) + + to_chat(user, span_notice("You add [src] to the fuel stack.")) + if(!grill_fuel) + burn_stack() + begin_processing() + return ITEM_INTERACT_SUCCESS + + if(is_reagent_container(weapon) && weapon.is_open_container()) + var/obj/item/reagent_containers/container = weapon + if(!QDELETED(grilled_item)) + return NONE + if(!anchored) + balloon_alert(user, "anchor it first!") + return ITEM_INTERACT_BLOCKING + + var/transfered_amount = weapon.reagents.trans_to(src, container.amount_per_transfer_from_this) + if(transfered_amount) + //reagents & their effects on fuel + var/static/list/fuel_map = list( + /datum/reagent/consumable/monkey_energy = 4, + /datum/reagent/fuel/oil = 3, + /datum/reagent/fuel = 2, + /datum/reagent/consumable/ethanol = 1 + ) + + //compute extra fuel to be obtained from everything transfered + var/boost + var/additional_fuel = 0 + for(var/datum/reagent/stored as anything in reagents.reagent_list) + boost = fuel_map[stored.type] + if(!boost) //anything we don't recognize as fuel has inverse effects + boost = -1 + boost = boost * stored.volume * (GRILL_FUELUSAGE_IDLE + GRILL_FUELUSAGE_ACTIVE) + additional_fuel += boost + + //add to fuel source + reagents.clear_reagents() + grill_fuel += additional_fuel + if(grill_fuel <= 0) //can happen if you put water or something + grill_fuel = 0 + else + begin_processing() + update_appearance(UPDATE_ICON_STATE) + + //feedback + to_chat(user, span_notice("You transfer [transfered_amount]u to the fuel source.")) + return ITEM_INTERACT_SUCCESS + + balloon_alert(user, "no fuel transfered!") + return ITEM_INTERACT_BLOCKING + + if(IS_EDIBLE(weapon)) + //sanity checks + if(!anchored) + balloon_alert(user, "anchor first!") + return ITEM_INTERACT_BLOCKING + if(HAS_TRAIT(weapon, TRAIT_NODROP)) + return ..() + if(!QDELETED(grilled_item)) + balloon_alert(user, "remove item first!") + return ITEM_INTERACT_BLOCKING + if(grill_fuel <= 0) + balloon_alert(user, "no fuel!") + return ITEM_INTERACT_BLOCKING + if(!user.transferItemToLoc(weapon, src)) + balloon_alert(user, "[weapon] is stuck in your hand!") + return ITEM_INTERACT_BLOCKING + + //add the item on the grill + grill_time = 0 + grilled_item = weapon + var/datum/component/sizzle/sizzle = grilled_item.GetComponent(/datum/component/sizzle) + if(!isnull(sizzle)) + grill_time = sizzle.time_elapsed() + to_chat(user, span_notice("You put the [grilled_item] on [src].")) + update_appearance(UPDATE_ICON_STATE) + grill_loop.start() + return ITEM_INTERACT_SUCCESS + + return NONE /obj/machinery/grill/proc/finish_grill() if(!QDELETED(grilled_item)) diff --git a/code/modules/food_and_drinks/machinery/microwave.dm b/code/modules/food_and_drinks/machinery/microwave.dm index c4cc6d378e35..0134fba01371 100644 --- a/code/modules/food_and_drinks/machinery/microwave.dm +++ b/code/modules/food_and_drinks/machinery/microwave.dm @@ -364,21 +364,15 @@ update_appearance() return ITEM_INTERACT_SUCCESS -/obj/machinery/microwave/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) +/obj/machinery/microwave/item_interaction(mob/living/user, obj/item/tool, list/modifiers) if(operating) - return + return ITEM_INTERACT_SKIP_TO_ATTACK // Don't use tools if we're dirty if(dirty >= MAX_MICROWAVE_DIRTINESS) - return - - . = ..() - if(. & ITEM_INTERACT_ANY_BLOCKER) - return . - + return ITEM_INTERACT_SKIP_TO_ATTACK // Don't insert items if we're dirty if(panel_open && is_wire_tool(tool)) wires.interact(user) return ITEM_INTERACT_SUCCESS - - return . + return NONE /obj/machinery/microwave/attackby(obj/item/item, mob/living/user, params) if(operating) diff --git a/code/modules/food_and_drinks/machinery/oven.dm b/code/modules/food_and_drinks/machinery/oven.dm index 87cffebc8aec..7d89c7c9ad62 100644 --- a/code/modules/food_and_drinks/machinery/oven.dm +++ b/code/modules/food_and_drinks/machinery/oven.dm @@ -104,6 +104,20 @@ else return ..() + if(user.transferItemToLoc(item, src, silent = FALSE)) + to_chat(user, span_notice("You put [item] in [src].")) + add_tray_to_oven(item, user) + +/obj/machinery/oven/item_interaction(mob/living/user, obj/item/item, list/modifiers) + if(open && used_tray && item.atom_storage) + return used_tray.item_interaction(user, item, modifiers) + return NONE + +/obj/machinery/oven/item_interaction_secondary(mob/living/user, obj/item/tool, list/modifiers) + if(open && used_tray && tool.atom_storage) + return used_tray.item_interaction_secondary(user, tool, modifiers) + return NONE + ///Adds a tray to the oven, making sure the shit can get baked. /obj/machinery/oven/proc/add_tray_to_oven(obj/item/plate/oven_tray, mob/baker) used_tray = oven_tray @@ -239,6 +253,43 @@ max_items = 6 biggest_w_class = WEIGHT_CLASS_BULKY +/obj/item/plate/oven_tray/item_interaction_secondary(mob/living/user, obj/item/item, list/modifiers) + if(isnull(item.atom_storage)) + return NONE + + for(var/obj/tray_item in src) + item.atom_storage.attempt_insert(tray_item, user, TRUE) + return ITEM_INTERACT_SUCCESS + +/obj/item/plate/oven_tray/item_interaction(mob/living/user, obj/item/item, list/modifiers) + if(isnull(item.atom_storage)) + return NONE + + if(length(contents >= max_items)) + balloon_alert(user, "it's full!") + return ITEM_INTERACT_BLOCKING + + if(!istype(item, /obj/item/storage/bag/tray)) + // Non-tray dumping requires a do_after + to_chat(user, span_notice("You start dumping out the contents of [item] into [src]...")) + if(!do_after(user, 2 SECONDS, target = item)) + return ITEM_INTERACT_BLOCKING + + var/loaded = 0 + for(var/obj/tray_item in item) + if(!IS_EDIBLE(tray_item)) + continue + if(length(contents) >= max_items) + break + if(item.atom_storage.attempt_remove(tray_item, src)) + loaded++ + AddToPlate(tray_item, user) + if(loaded) + to_chat(user, span_notice("You insert [loaded] item\s into [src].")) + update_appearance() + return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_BLOCKING + #undef OVEN_SMOKE_STATE_NONE #undef OVEN_SMOKE_STATE_GOOD #undef OVEN_SMOKE_STATE_NEUTRAL diff --git a/code/modules/food_and_drinks/machinery/stove.dm b/code/modules/food_and_drinks/machinery/stove.dm index 38f98cfa8a8b..24af831d8ca7 100644 --- a/code/modules/food_and_drinks/machinery/stove.dm +++ b/code/modules/food_and_drinks/machinery/stove.dm @@ -140,6 +140,12 @@ update_appearance(UPDATE_OVERLAYS) return TRUE +/obj/item/reagent_containers/cup/soup_pot/item_interaction(mob/living/user, obj/item/item, list/modifiers) + if(LAZYACCESS(modifiers, RIGHT_CLICK)) + return NONE + + return transfer_from_container_to_pot(item, user) + /obj/item/reagent_containers/cup/soup_pot/attack_hand_secondary(mob/user, list/modifiers) if(!LAZYLEN(added_ingredients)) return SECONDARY_ATTACK_CALL_NORMAL diff --git a/code/modules/holodeck/turfs.dm b/code/modules/holodeck/turfs.dm index 15a82c5aa557..3e2a149be644 100644 --- a/code/modules/holodeck/turfs.dm +++ b/code/modules/holodeck/turfs.dm @@ -8,9 +8,8 @@ /turf/open/floor/holofloor/attackby(obj/item/I, mob/living/user) return // HOLOFLOOR DOES NOT GIVE A FUCK -/turf/open/floor/holofloor/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) - SHOULD_CALL_PARENT(FALSE) - return NONE // Fuck you +/turf/open/floor/holofloor/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + return ITEM_INTERACT_BLOCKING // Fuck you /turf/open/floor/holofloor/burn_tile() return //you can't burn a hologram! diff --git a/code/modules/mining/boulder_processing/_boulder_processing.dm b/code/modules/mining/boulder_processing/_boulder_processing.dm new file mode 100644 index 000000000000..5795eed1d749 --- /dev/null +++ b/code/modules/mining/boulder_processing/_boulder_processing.dm @@ -0,0 +1,423 @@ +/obj/machinery/bouldertech + name = "bouldertech brand refining machine" + desc = "You shouldn't be seeing this! And bouldertech isn't even a real company!" + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "ore_redemption" + active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.5 + anchored = TRUE + density = TRUE + + /// What is the efficiency of minerals produced by the machine? + var/refining_efficiency = 1 + /// How much durability of an boulder can we reduce + var/boulders_processing_count = 2 + /// How many boulders can we hold maximum? + var/boulders_held_max = 1 + /// What sound plays when a thing operates? + var/usage_sound = 'sound/machines/mining/wooping_teleport.ogg' + /// Silo link to it's materials list. + var/datum/component/remote_materials/silo_materials + /// Mining points held by the machine for miners. + var/points_held = 0 + ///The action verb to display to players + var/action = "processing" + + /// Cooldown associated with the sound played for collecting mining points. + COOLDOWN_DECLARE(sound_cooldown) + /// Cooldown associated with taking in boulds. + COOLDOWN_DECLARE(accept_cooldown) + +/obj/machinery/bouldertech/Initialize(mapload) + . = ..() + + silo_materials = AddComponent( + /datum/component/remote_materials, \ + mapload, \ + mat_container_flags = MATCONTAINER_NO_INSERT \ + ) + + register_context() + +/obj/machinery/bouldertech/post_machine_initialize() + . = ..() + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_entered), + ) + AddElement(/datum/element/connect_loc, loc_connections) + +/obj/machinery/bouldertech/Destroy() + silo_materials = null + return ..() + +/obj/machinery/bouldertech/on_deconstruction(disassembled) + if(length(contents)) + for(var/obj/item/boulder/boulder in contents) + remove_boulder(boulder) + +/obj/machinery/bouldertech/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = CONTEXTUAL_SCREENTIP_SET + + if(isnull(held_item)) + context[SCREENTIP_CONTEXT_RMB] = "Remove Boulder" + return + + if(istype(held_item, /obj/item/boulder)) + context[SCREENTIP_CONTEXT_LMB] = "Insert boulder" + else if(istype(held_item, /obj/item/card/id) && points_held > 0) + context[SCREENTIP_CONTEXT_LMB] = "Claim mining points" + else if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] panel" + else if(held_item.tool_behaviour == TOOL_WRENCH) + context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]Anchor" + else if(panel_open && held_item.tool_behaviour == TOOL_CROWBAR) + context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" + +/obj/machinery/bouldertech/examine(mob/user) + . = ..() + . += span_notice("The machine reads that it has [span_bold("[points_held] mining points")] stored. Swipe an ID to claim them.") + . += span_notice("Click to remove a stored boulder.") + + var/boulder_count = 0 + for(var/obj/item/boulder/potential_boulder in contents) + boulder_count += 1 + . += span_notice("Storage capacity = [boulder_count]/[boulders_held_max] boulders.") + . += span_notice("Can process upto [boulders_processing_count] boulders at a time.") + + if(anchored) + . += span_notice("Its [EXAMINE_HINT("anchored")] in place.") + else + . += span_warning("It needs to be [EXAMINE_HINT("anchored")] to start operations.") + + . += span_notice("Its maintainence panel can be [EXAMINE_HINT("screwed")] [panel_open ? "closed" : "open"].") + + if(panel_open) + . += span_notice("The whole machine can be [EXAMINE_HINT("pried")] apart.") + +/obj/machinery/bouldertech/update_icon_state() + . = ..() + var/suffix = "" + if(!anchored || panel_open || !is_operational || (machine_stat & (BROKEN | NOPOWER))) + suffix = "-off" + icon_state ="[initial(icon_state)][suffix]" + +/obj/machinery/bouldertech/CanAllowThrough(atom/movable/mover, border_dir) + if(!anchored) + return FALSE + if(istype(mover, /obj/item/boulder)) + return can_process_boulder(mover) + if(isgolem(mover)) + return can_process_golem(mover) + return ..() + +/** + * Can we process the boulder, checks only the boulders state & machines capacity + * Arguments + * + * * obj/item/boulder/new_boulder - the boulder we are checking + */ +/obj/machinery/bouldertech/proc/can_process_boulder(obj/item/boulder/new_boulder) + PRIVATE_PROC(TRUE) + SHOULD_BE_PURE(TRUE) + + //machine not operational + if(!anchored || panel_open || !is_operational || (machine_stat & (BROKEN | NOPOWER))) + return FALSE + + //not a valid boulder + if(!istype(new_boulder) || QDELETED(new_boulder)) + return FALSE + + //someone just processed this + if(new_boulder.processed_by) + return FALSE + + //no space to hold boulders + var/boulder_count = 0 + for(var/obj/item/boulder/potential_boulder in contents) + boulder_count += 1 + if(boulder_count >= boulders_held_max) + return FALSE + + //did we cooldown enough to accept a boulder + return COOLDOWN_FINISHED(src, accept_cooldown) + +/** + * Accepts a boulder into the machine. Used when a boulder is first placed into the machine. + * Arguments + * + * * obj/item/boulder/new_boulder - the boulder to accept + */ +/obj/machinery/bouldertech/proc/accept_boulder(obj/item/boulder/new_boulder) + PRIVATE_PROC(TRUE) + + if(!can_process_boulder(new_boulder)) + return FALSE + + new_boulder.forceMove(src) + + COOLDOWN_START(src, accept_cooldown, 1.5 SECONDS) + + return TRUE + +/** + * Can we maim this golem + * Arguments + * + * * [rockman][mob/living/carbon/human] - the golem we are trying to main + */ +/obj/machinery/bouldertech/proc/can_process_golem(mob/living/carbon/human/rockman) + PRIVATE_PROC(TRUE) + SHOULD_BE_PURE(TRUE) + + //not operatinal + if(!anchored || panel_open || !is_operational || (machine_stat & (BROKEN | NOPOWER))) + return FALSE + + //still in cooldown + if(!COOLDOWN_FINISHED(src, accept_cooldown)) + return FALSE + + //not processable + if(!istype(rockman) || QDELETED(rockman) || rockman.body_position != LYING_DOWN) + return FALSE + + return TRUE + +/** + * Accepts a golem to be processed, mainly for memes + * Arguments + * + * * [rockman][mob/living/carbon/human] - the golem we are trying to main + */ +/obj/machinery/bouldertech/proc/accept_golem(mob/living/carbon/human/rockman) + PRIVATE_PROC(TRUE) + + if(!can_process_golem(rockman)) + return + + if(!use_energy(active_power_usage * 1.5, force = FALSE)) + say("Not enough energy!") + return + + maim_golem(rockman) + playsound(src, usage_sound, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + + COOLDOWN_START(src, accept_cooldown, 3 SECONDS) + +/// What effects actually happens to a golem when it is "processed" +/obj/machinery/bouldertech/proc/maim_golem(mob/living/carbon/human/rockman) + PROTECTED_PROC(TRUE) + + Shake(duration = 1 SECONDS) + rockman.visible_message(span_warning("[rockman] is processed by [src]!"), span_userdanger("You get processed into bits by [src]!")) + rockman.investigate_log("was gibbed by [src] for being a golem", INVESTIGATE_DEATHS) + rockman.gib(DROP_ALL_REMAINS) + +/obj/machinery/bouldertech/proc/on_entered(datum/source, atom/movable/atom_movable) + SIGNAL_HANDLER + + if(istype(atom_movable, /obj/item/boulder)) + INVOKE_ASYNC(src, PROC_REF(accept_boulder), atom_movable) + return + + if(isgolem(atom_movable)) + INVOKE_ASYNC(src, PROC_REF(accept_golem), atom_movable) + return + +/** + * Looks for a boost to the machine's efficiency, and applies it if found. + * Applied more on the chemistry integration but can be used for other things if desired. + */ +/obj/machinery/bouldertech/proc/check_for_boosts() + PROTECTED_PROC(TRUE) + + refining_efficiency = initial(refining_efficiency) //Reset refining efficiency to 100%. + +/** + * Checks if this machine can process this material + * Arguments + * + * * datum/material/mat - the material to process + */ +/obj/machinery/bouldertech/proc/can_process_material(datum/material/mat) + PROTECTED_PROC(TRUE) + + return FALSE + +/obj/machinery/bouldertech/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(panel_open || user.combat_mode) + return NONE + + if(istype(tool, /obj/item/boulder)) + var/obj/item/boulder/my_boulder = tool + if(!accept_boulder(my_boulder)) + balloon_alert_to_viewers("cannot accept!") + return ITEM_INTERACT_BLOCKING + balloon_alert_to_viewers("accepted") + return ITEM_INTERACT_SUCCESS + + if(istype(tool, /obj/item/card/id)) + if(points_held <= 0) + balloon_alert_to_viewers("no points to claim!") + if(!COOLDOWN_FINISHED(src, sound_cooldown)) + return ITEM_INTERACT_BLOCKING + COOLDOWN_START(src, sound_cooldown, 1.5 SECONDS) + playsound(src, 'sound/machines/buzz-sigh.ogg', 30, FALSE) + return ITEM_INTERACT_BLOCKING + + var/obj/item/card/id/id_card = tool + var/amount = tgui_input_number(user, "How many mining points do you wish to claim? ID Balance: [id_card.registered_account.mining_points], stored mining points: [points_held]", "Transfer Points", max_value = points_held, min_value = 0, round_value = 1) + if(!amount) + return ITEM_INTERACT_BLOCKING + if(amount > points_held) + amount = points_held + id_card.registered_account.mining_points += amount + points_held = round(points_held - amount) + to_chat(user, span_notice("You claim [amount] mining points from \the [src] to [id_card].")) + return ITEM_INTERACT_SUCCESS + + return NONE + +/obj/machinery/bouldertech/wrench_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING + if(default_unfasten_wrench(user, tool, time = 1.5 SECONDS) == SUCCESSFUL_UNFASTEN) + if(anchored) + begin_processing() + else + end_processing() + update_appearance(UPDATE_ICON_STATE) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/bouldertech/screwdriver_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING + if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-off", initial(icon_state), tool)) + update_appearance(UPDATE_ICON_STATE) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/bouldertech/crowbar_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING + if(default_deconstruction_crowbar(tool)) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/bouldertech/attack_hand_secondary(mob/user, list/modifiers) + . = ..() + if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN || panel_open) + return + if(!anchored) + balloon_alert(user, "anchor first!") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + if(panel_open) + balloon_alert(user, "close panel!") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + var/obj/item/boulder/boulder = locate(/obj/item/boulder) in src + if(!boulder) + balloon_alert_to_viewers("no boulders to remove!") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + if(!remove_boulder(boulder)) + balloon_alert_to_viewers("no space to remove!") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/** + * Accepts a boulder into the machinery, then converts it into minerals. + * If the boulder can be fully processed by this machine, we take the materials, insert it into the silo, and destroy the boulder. + * If the boulder has materials left, we make a copy of the boulder to hold the processable materials, take the processable parts, and eject the original boulder. + * Arguments + * + * * obj/item/boulder/chosen_boulder - The boulder to being breaking down into minerals. + */ +/obj/machinery/bouldertech/proc/breakdown_boulder(obj/item/boulder/chosen_boulder) + PRIVATE_PROC(TRUE) + + if(QDELETED(chosen_boulder)) + return + if(chosen_boulder.loc != src) + return + if(!use_energy(active_power_usage, force = FALSE)) + say("Not enough energy!") + return + + //if boulders are kept inside because there is no space to eject them, then they could be reprocessed, lets avoid that + if(!chosen_boulder.processed_by) + check_for_boosts() + + //here we loop through the boulder's ores + var/list/rejected_mats = list() + for(var/datum/material/possible_mat as anything in chosen_boulder.custom_materials) + var/quantity = chosen_boulder.custom_materials[possible_mat] * refining_efficiency + if(!can_process_material(possible_mat)) + rejected_mats[possible_mat] = quantity + continue + points_held = round(points_held + (quantity * possible_mat.points_per_unit * MINING_POINT_MACHINE_MULTIPLIER)) // put point total here into machine + if(!silo_materials.mat_container.insert_amount_mat(quantity, possible_mat)) + rejected_mats[possible_mat] = quantity + + //puts back materials that couldn't be processed + chosen_boulder.set_custom_materials(rejected_mats, refining_efficiency) + + //break the boulder down if we have processed all its materials + if(!length(chosen_boulder.custom_materials)) + playsound(loc, usage_sound, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + if(istype(chosen_boulder, /obj/item/boulder/artifact)) + points_held = round((points_held + MINER_POINT_MULTIPLIER) * MINING_POINT_MACHINE_MULTIPLIER) /// Artifacts give bonus points! + chosen_boulder.break_apart() + return//We've processed all the materials in the boulder, so we can just destroy it in break_apart. + + chosen_boulder.processed_by = src + + //eject the boulder since we are done with it + remove_boulder(chosen_boulder) + +/obj/machinery/bouldertech/process() + if(!anchored || panel_open || !is_operational || (machine_stat & (BROKEN | NOPOWER))) + return + + var/boulders_found = FALSE + var/boulders_processed = boulders_processing_count + for(var/obj/item/boulder/potential_boulder in contents) + boulders_found = TRUE + if(boulders_processed <= 0) + break //Try again next time + boulders_processed-- + + if(potential_boulder.durability > 0) + potential_boulder.durability -= 1 + if(potential_boulder.durability > 0) + continue + + breakdown_boulder(potential_boulder) + boulders_found = FALSE + + //when the boulder is removed it plays sound and displays a balloon alert. don't overlap when that happens + if(boulders_found) + playsound(loc, usage_sound, 29, FALSE, SHORT_RANGE_SOUND_EXTRARANGE) + balloon_alert_to_viewers(action) + +/** + * Ejects a boulder from the machine. Used when a boulder is finished processing, or when a boulder can't be processed. + * Arguments + * + * * obj/item/boulder/specific_boulder - the boulder to remove + */ +/obj/machinery/bouldertech/proc/remove_boulder(obj/item/boulder/specific_boulder) + PRIVATE_PROC(TRUE) + + if(QDELETED(specific_boulder)) + return TRUE + if(locate(/obj/item/boulder) in loc) //There is an boulder in our loc. it has be removed so we don't clog up our loc with even more boulders + return FALSE + + //Reset durability to little random lower value cause we have crushed it so many times + var/size = specific_boulder.boulder_size + if(size == BOULDER_SIZE_SMALL) + specific_boulder.durability = rand(2, BOULDER_SIZE_SMALL - 1) + else + specific_boulder.durability = rand(BOULDER_SIZE_SMALL, size - 1) + specific_boulder.processed_by = src //so we don't take in the boulder again after we just ejected it + specific_boulder.forceMove(drop_location()) + specific_boulder.processed_by = null //now since move is done we can safely clear the reference + playsound(loc, 'sound/machines/ping.ogg', 50, FALSE) + + return TRUE diff --git a/code/modules/paperwork/handlabeler.dm b/code/modules/paperwork/handlabeler.dm index e1516f3914c8..b855a5dcd5ea 100644 --- a/code/modules/paperwork/handlabeler.dm +++ b/code/modules/paperwork/handlabeler.dm @@ -84,12 +84,9 @@ else to_chat(user, span_notice("You turn off [src].")) -/obj/item/hand_labeler/attackby(obj/item/I, mob/user, params) - ..() - if(istype(I, /obj/item/hand_labeler_refill)) - to_chat(user, span_notice("You insert [I] into [src].")) - qdel(I) - labels_left = initial(labels_left) //Yes, it's capped at its initial value +/obj/item/hand_labeler/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/hand_labeler_refill)) + return NONE /obj/item/hand_labeler/attackby_storage_insert(datum/storage, atom/storage_holder, mob/user) return !mode diff --git a/code/modules/power/apc/apc_tool_act.dm b/code/modules/power/apc/apc_tool_act.dm index b35f4de3fba1..667b03d5cad7 100644 --- a/code/modules/power/apc/apc_tool_act.dm +++ b/code/modules/power/apc/apc_tool_act.dm @@ -1,4 +1,238 @@ //attack with an item - open/close cover, insert cell, or (un)lock interface + +/obj/machinery/power/apc/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + . = NONE + if(HAS_TRAIT(tool, TRAIT_APC_SHOCKING)) + . = fork_outlet_act(user, tool) + if(.) + return . + + if(tool.GetID()) + togglelock(user) + return ITEM_INTERACT_SUCCESS + + if(istype(tool, /obj/item/stock_parts/cell)) + . = cell_act(user, tool) + else if(istype(tool, /obj/item/stack/cable_coil)) + . = cable_act(user, tool, LAZYACCESS(modifiers, RIGHT_CLICK)) + else if(istype(tool, /obj/item/electronics/apc)) + . = electronics_act(user, tool) + else if(istype(tool, /obj/item/electroadaptive_pseudocircuit)) + . = pseudocircuit_act(user, tool) + else if(istype(tool, /obj/item/wallframe/apc)) + . = wallframe_act(user, tool) + if(.) + return . + + if(panel_open && !opened && is_wire_tool(tool)) + wires.interact(user) + return ITEM_INTERACT_SUCCESS + + return . + +/// Called when we interact with the APC with an item with which we can get shocked when we stuff it into an APC +/obj/machinery/power/apc/proc/fork_outlet_act(mob/living/user, obj/item/tool) + var/metal = 0 + var/shock_source = null + metal += LAZYACCESS(tool.custom_materials, GET_MATERIAL_REF(/datum/material/iron))//This prevents wooden rolling pins from shocking the user + + if(cell || terminal) //The mob gets shocked by whichever powersource has the most electricity + if(cell && terminal) + shock_source = cell.charge > terminal.powernet.avail ? cell : terminal.powernet + else + shock_source = terminal?.powernet || cell + + if(shock_source && metal && (panel_open || opened)) //Now you're cooking with electricity + if(!electrocute_mob(user, shock_source, src, siemens_coeff = 1, dist_check = TRUE))//People with insulated gloves just attack the APC normally. They're just short of magical anyway + return NONE + do_sparks(5, TRUE, src) + user.visible_message(span_notice("[user.name] shoves [tool] into the internal components of [src], erupting into a cascade of sparks!")) + if(shock_source == cell)//If the shock is coming from the cell just fully discharge it, because it's funny + cell.use(cell.charge) + return ITEM_INTERACT_SUCCESS + +/// Called when we interact with the APC with a cell, attempts to insert it +/obj/machinery/power/apc/proc/cell_act(mob/living/user, obj/item/stock_parts/cell/new_cell) + if(!opened) + return NONE + + if(cell) + balloon_alert(user, "cell already installed!") + return ITEM_INTERACT_BLOCKING + if(machine_stat & MAINT) + balloon_alert(user, "no connector for a cell!") + return ITEM_INTERACT_BLOCKING + if(!user.transferItemToLoc(new_cell, src)) + return ITEM_INTERACT_BLOCKING + cell = new_cell + user.visible_message(span_notice("[user.name] inserts the power cell to [src.name]!")) + balloon_alert(user, "cell inserted") + update_appearance() + return ITEM_INTERACT_SUCCESS + +/// Checks if we can place a terminal on the APC +/obj/machinery/power/apc/proc/can_place_terminal(mob/living/user, obj/item/stack/cable_coil/installing_cable, silent = TRUE) + if(!opened) + return FALSE + var/turf/host_turf = get_turf(src) + if(host_turf.underfloor_accessibility < UNDERFLOOR_INTERACTABLE) + if(!silent && user) + balloon_alert(user, "remove the floor plating!") + return FALSE + if(!isnull(terminal)) + if(!silent && user) + balloon_alert(user, "already wired!") + return FALSE + if(!has_electronics) + if(!silent && user) + balloon_alert(user, "no board to wire!") + return FALSE + if(panel_open) + if(!silent && user) + balloon_alert(user, "wires prevent placing a terminal!") + return FALSE + if(installing_cable.get_amount() < 10) + if(!silent && user) + balloon_alert(user, "need ten lengths of cable!") + return FALSE + return TRUE + +/// Called when we interact with the APC with a cable, attempts to wire the APC and create a terminal +/obj/machinery/power/apc/proc/cable_act(mob/living/user, obj/item/stack/cable_coil/installing_cable, is_right_clicking) + if(!opened) + return NONE + if(!can_place_terminal(user, installing_cable, silent = FALSE)) + return ITEM_INTERACT_BLOCKING + + var/terminal_cable_layer = cable_layer // Default to machine's cable layer + if(is_right_clicking) + var/choice = tgui_input_list(user, "Select Power Input Cable Layer", "Select Cable Layer", GLOB.cable_name_to_layer) + if(isnull(choice) \ + || !user.is_holding(installing_cable) \ + || !user.Adjacent(src) \ + || user.incapacitated() \ + || !can_place_terminal(user, installing_cable, silent = TRUE) \ + ) + return ITEM_INTERACT_BLOCKING + terminal_cable_layer = GLOB.cable_name_to_layer[choice] + + user.visible_message(span_notice("[user.name] starts addding cables to the APC frame.")) + balloon_alert(user, "adding cables...") + playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) + + if(!do_after(user, 2 SECONDS, target = src)) + return ITEM_INTERACT_BLOCKING + if(!can_place_terminal(user, installing_cable, silent = TRUE)) + return ITEM_INTERACT_BLOCKING + var/turf/our_turf = get_turf(src) + var/obj/structure/cable/cable_node = our_turf.get_cable_node(terminal_cable_layer) + if(prob(50) && electrocute_mob(usr, cable_node, cable_node, 1, TRUE)) + do_sparks(5, TRUE, src) + return ITEM_INTERACT_BLOCKING + installing_cable.use(10) + user.visible_message(span_notice("[user.name] adds cables to the APC frame.")) + balloon_alert(user, "cables added") + make_terminal(terminal_cable_layer) + terminal.connect_to_network() + return ITEM_INTERACT_SUCCESS + +/// Called when we interact with the APC with APC electronics, attempts to install the board +/obj/machinery/power/apc/proc/electronics_act(mob/living/user, obj/item/electronics/apc/installing_board) + if(!opened) + return NONE + + if(has_electronics) + balloon_alert(user, "there is already a board!") + return ITEM_INTERACT_BLOCKING + + if(machine_stat & BROKEN) + balloon_alert(user, "the frame is damaged!") + return ITEM_INTERACT_BLOCKING + + user.visible_message(span_notice("[user.name] inserts the power control board into [src].")) + balloon_alert(user, "inserting the board...") + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) + + if(!do_after(user, 1 SECONDS, target = src) || has_electronics) + return ITEM_INTERACT_BLOCKING + + has_electronics = APC_ELECTRONICS_INSTALLED + locked = FALSE + balloon_alert(user, "board installed") + qdel(installing_board) + return ITEM_INTERACT_SUCCESS + +/// Called when we interact with the APC with an electroadaptive pseudocircuit, used by cyborgs to install a board or weak cell +/obj/machinery/power/apc/proc/pseudocircuit_act(mob/living/user, obj/item/electroadaptive_pseudocircuit/pseudocircuit) + if(!has_electronics) + if(machine_stat & BROKEN) + balloon_alert(user, "frame is too damaged!") + return ITEM_INTERACT_BLOCKING + if(!pseudocircuit.adapt_circuit(user, circuit_cost = 50 KILO JOULES)) + return ITEM_INTERACT_BLOCKING + user.visible_message( + span_notice("[user] fabricates a circuit and places it into [src]."), + span_notice("You adapt a power control board and click it into place in [src]'s guts."), + ) + has_electronics = APC_ELECTRONICS_INSTALLED + locked = FALSE + return ITEM_INTERACT_SUCCESS + + if(!cell) + if(machine_stat & MAINT) + balloon_alert(user, "no board for a cell!") + return ITEM_INTERACT_BLOCKING + if(!pseudocircuit.adapt_circuit(user, circuit_cost = 500 KILO JOULES)) + return ITEM_INTERACT_BLOCKING + var/obj/item/stock_parts/cell/crap/empty/bad_cell = new(src) + bad_cell.forceMove(src) + cell = bad_cell + user.visible_message( + span_notice("[user] fabricates a weak power cell and places it into [src]."), + span_warning("Your [pseudocircuit.name] whirrs with strain as you create a weak power cell and place it into [src]!"), + ) + update_appearance() + return ITEM_INTERACT_SUCCESS + + balloon_alert(user, "has both board and cell!") + return ITEM_INTERACT_BLOCKING + +/// Called when we interact with the APC with and APC frame, used for replacing a damaged cover/frame +/obj/machinery/power/apc/proc/wallframe_act(mob/living/user, obj/item/wallframe/apc/wallframe) + if(!opened) + return NONE + + if(!(machine_stat & BROKEN || opened == APC_COVER_REMOVED || atom_integrity < max_integrity)) // There is nothing to repair + balloon_alert(user, "no reason for repairs!") + return ITEM_INTERACT_BLOCKING + if((machine_stat & BROKEN) && opened == APC_COVER_REMOVED && has_electronics && terminal) // Cover is the only thing broken, we do not need to remove elctronicks to replace cover + user.visible_message(span_notice("[user.name] replaces missing APC's cover.")) + balloon_alert(user, "replacing APC's cover...") + if(!do_after(user, 2 SECONDS, target = src)) // replacing cover is quicker than replacing whole frame + return ITEM_INTERACT_BLOCKING + balloon_alert(user, "cover replaced") + qdel(wallframe) + update_integrity(30) //needs to be welded to fully repair but can work without + set_machine_stat(machine_stat & ~(BROKEN|MAINT)) + opened = APC_COVER_OPENED + update_appearance() + return ITEM_INTERACT_SUCCESS + if(has_electronics) + balloon_alert(user, "remove the board inside!") + return ITEM_INTERACT_BLOCKING + user.visible_message(span_notice("[user.name] replaces the damaged APC frame with a new one.")) + balloon_alert(user, "replacing damaged frame...") + if(!do_after(user, 5 SECONDS, target = src)) + return ITEM_INTERACT_BLOCKING + balloon_alert(user, "replaced frame") + qdel(wallframe) + set_machine_stat(machine_stat & ~BROKEN) + atom_integrity = max_integrity + if(opened == APC_COVER_REMOVED) + opened = APC_COVER_OPENED + update_appearance() + return ITEM_INTERACT_SUCCESS + /obj/machinery/power/apc/crowbar_act(mob/user, obj/item/crowbar) . = TRUE diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm index 4f58591a3ffa..73728083491d 100644 --- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm +++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm @@ -399,15 +399,15 @@ return ITEM_INTERACT_SUCCESS return ITEM_INTERACT_BLOCKING -/obj/machinery/chem_dispenser/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) +/obj/machinery/chem_dispenser/item_interaction(mob/living/user, obj/item/tool, list/modifiers) if(is_reagent_container(tool) && !(tool.item_flags & ABSTRACT) && tool.is_open_container()) if(!user.transferItemToLoc(tool, src)) - return ..() + return ITEM_INTERACT_BLOCKING replace_beaker(user, tool) ui_interact(user) return ITEM_INTERACT_SUCCESS - return ..() + return NONE /obj/machinery/chem_dispenser/get_cell() return cell diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm index 722c3f5b1b49..a472e9f2e0e3 100644 --- a/code/modules/reagents/chemistry/machinery/chem_heater.dm +++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm @@ -41,6 +41,23 @@ beaker = null update_appearance() +/obj/machinery/chem_heater/item_interaction(mob/living/user, obj/item/held_item, list/modifiers) + if((held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1)) + return NONE + + if(QDELETED(beaker)) + if(istype(held_item, /obj/item/reagent_containers/dropper) || istype(held_item, /obj/item/reagent_containers/syringe)) + var/obj/item/reagent_containers/injector = held_item + injector.afterattack(beaker, user, proximity_flag = TRUE) + return ITEM_INTERACT_SUCCESS + + if(is_reagent_container(held_item) && held_item.is_open_container()) + if(replace_beaker(user, held_item)) + ui_interact(user) + balloon_alert(user, "beaker added") + return ITEM_INTERACT_SUCCESS + + return NONE /obj/machinery/chem_heater/update_icon_state() icon_state = "[base_icon_state][beaker ? 1 : 0]b" return ..() diff --git a/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm b/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm index e49bc3ec5d6b..67efc61ccb3f 100644 --- a/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm +++ b/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm @@ -77,11 +77,9 @@ This will not clean any inverted reagents. Inverted reagents will still be corre default_unfasten_wrench(user, tool) return ITEM_INTERACT_SUCCESS -/* beaker swapping/attack code */ -/obj/machinery/chem_mass_spec/attackby(obj/item/item, mob/user, params) - if(processing_reagents) - to_chat(user, " The [src] is currently processing a batch!") - return ..() +/obj/machinery/chem_mass_spec/item_interaction(mob/living/user, obj/item/item, list/modifiers) + if((item.item_flags & ABSTRACT) || (item.flags_1 & HOLOGRAM_1) || !can_interact(user) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH)) + return NONE if(default_deconstruction_screwdriver(user, icon_state, icon_state, item)) update_appearance() @@ -91,16 +89,17 @@ This will not clean any inverted reagents. Inverted reagents will still be corre var/obj/item/reagent_containers/beaker = item . = TRUE //no afterattack if(!user.transferItemToLoc(beaker, src)) - return - replace_beaker(user, BEAKER1, beaker) - to_chat(user, span_notice("You add [beaker] to [src].")) + return ITEM_INTERACT_BLOCKING + + var/is_right_clicking = LAZYACCESS(modifiers, RIGHT_CLICK) + replace_beaker(user, !is_right_clicking, beaker) + to_chat(user, span_notice("You add [beaker] to [is_right_clicking ? "output" : "input"] slot.")) update_appearance() ui_interact(user) return ..() -/obj/machinery/chem_mass_spec/attackby_secondary(obj/item/item, mob/user, params) - . = ..() + return ..() if(processing_reagents) to_chat(user, " The [src] is currently processing a batch!") diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm index bbc06437b84c..ad7e66bec7ee 100644 --- a/code/modules/reagents/chemistry/machinery/chem_master.dm +++ b/code/modules/reagents/chemistry/machinery/chem_master.dm @@ -123,6 +123,19 @@ filling.color = mix_color_from_reagents(reagents.reagent_list) . += filling +/obj/machinery/chem_master/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + return NONE + + if(!panel_open) + if(is_reagent_container(tool) && tool.is_open_container()) + replace_beaker(user, tool) + return ITEM_INTERACT_SUCCESS + ui_interact(user) + else + return ITEM_INTERACT_BLOCKING + + return NONE + /obj/machinery/chem_master/wrench_act(mob/living/user, obj/item/tool) if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN) return ITEM_INTERACT_SUCCESS diff --git a/code/modules/reagents/chemistry/machinery/portable_chem_mixer.dm b/code/modules/reagents/chemistry/machinery/portable_chem_mixer.dm index 9ad8e3ecfc5a..73695ea607e8 100644 --- a/code/modules/reagents/chemistry/machinery/portable_chem_mixer.dm +++ b/code/modules/reagents/chemistry/machinery/portable_chem_mixer.dm @@ -136,6 +136,22 @@ if(!atom_storage.locked) update_contents() +/obj/item/storage/portable_chem_mixer/ex_act(severity, target) + return severity > EXPLODE_LIGHT ? ..() : FALSE + +/obj/item/storage/portable_chem_mixer/item_interaction(mob/living/user, obj/item/weapon, list/modifiers) + if (!atom_storage.locked || \ + (weapon.item_flags & ABSTRACT) || \ + (weapon.flags_1 & HOLOGRAM_1) || \ + !is_reagent_container(weapon) || \ + !weapon.is_open_container() \ + ) + return NONE + + replace_beaker(user, weapon) + update_appearance() + return ITEM_INTERACT_SUCCESS + /** * Replaces the beaker of the portable chemical mixer with another beaker, or simply adds the new beaker if none is in currently * diff --git a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm index f4be905a0738..4537828ed2a5 100644 --- a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm +++ b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm @@ -139,6 +139,111 @@ 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.reagents.total_volume) + to_chat(user, span_warning("You cannot grind/juice [ingredient] into reagents!")) + continue + + //Error messages should be in the objects' definitions + if(!ingredient.blend_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) + 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 NONE + + //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(length(tool.grind_results) || tool.reagents?.total_volume) + 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 NONE + /obj/machinery/reagentgrinder/wrench_act(mob/living/user, obj/item/tool) . = ..() default_unfasten_wrench(user, tool) diff --git a/code/modules/research/destructive_analyzer.dm b/code/modules/research/destructive_analyzer.dm index 72fd12ced704..e757ce5a0fa6 100644 --- a/code/modules/research/destructive_analyzer.dm +++ b/code/modules/research/destructive_analyzer.dm @@ -102,6 +102,12 @@ say("Destructive analysis failed!") return TRUE +/obj/machinery/rnd/destructive_analyzer/item_interaction_secondary(mob/living/user, obj/item/tool, list/modifiers) + // Cringe way to let emags insert on RMB because we still use attackby to insert + if(istype(tool, /obj/item/card/emag)) + return ITEM_INTERACT_SKIP_TO_ATTACK + return NONE + //This allows people to put syndicate screwdrivers in the machine. Secondary act still passes. /obj/machinery/rnd/destructive_analyzer/screwdriver_act(mob/living/user, obj/item/tool) return FALSE diff --git a/code/modules/research/server.dm b/code/modules/research/server.dm index b3c10eaac93b..908516a1f6cd 100644 --- a/code/modules/research/server.dm +++ b/code/modules/research/server.dm @@ -154,17 +154,16 @@ if(HDD_OVERLOADED) . += "The front panel is dangling open. The hdd inside is destroyed and the wires are all burned." -/obj/machinery/rnd/server/master/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) +/obj/machinery/rnd/server/master/item_interaction(mob/living/user, obj/item/tool, list/modifiers) if(!tool.tool_behaviour) - return ..() - // Only antags are given the training and knowledge to disassemble this thing. - if(is_special_character(user)) - return ..() - if(user.combat_mode) return NONE - - balloon_alert(user, "you can't find an obvious maintenance hatch!") - return ITEM_INTERACT_BLOCKING + // Only antags are given the training and knowledge to disassemble this thing. + if(!is_special_character(user)) + if(user.combat_mode) + return ITEM_INTERACT_SKIP_TO_ATTACK + balloon_alert(user, "you can't find an obvious maintenance hatch!") + return ITEM_INTERACT_BLOCKING + return NONE /obj/machinery/rnd/server/master/attackby(obj/item/attacking_item, mob/user, params) if(istype(attacking_item, /obj/item/computer_disk/hdd_theft))