diff --git a/code/modules/integrated_electronics/core/assemblies.dm b/code/modules/integrated_electronics/core/assemblies.dm index a7ed8e7c11..135461a12f 100644 --- a/code/modules/integrated_electronics/core/assemblies.dm +++ b/code/modules/integrated_electronics/core/assemblies.dm @@ -122,9 +122,8 @@ if(!battery) to_chat(usr, "There's no power cell to remove from \the [src].") else - var/turf/T = get_turf(src) - battery.forceMove(T) - playsound(T, 'sound/items/Crowbar.ogg', 50, 1) + battery.forceMove(drop_location()) + playsound(src, 'sound/items/Crowbar.ogg', 50, 1) to_chat(usr, "You pull \the [battery] out of \the [src]'s power supplier.") battery = null @@ -393,6 +392,17 @@ return src +// Returns the location to be used for dropping items. +// Same as the regular drop_location(), but with checks being run on acting_object if necessary. +/obj/item/integrated_circuit/drop_location() + var/atom/movable/acting_object = get_object() + + // plz no infinite loops + if(acting_object == src) + return ..() + + return acting_object.drop_location() + /obj/item/device/electronic_assembly/medium diff --git a/code/modules/integrated_electronics/core/integrated_circuit.dm b/code/modules/integrated_electronics/core/integrated_circuit.dm index 748acfa5e4..069ca4245c 100644 --- a/code/modules/integrated_electronics/core/integrated_circuit.dm +++ b/code/modules/integrated_electronics/core/integrated_circuit.dm @@ -330,3 +330,43 @@ a creative player the means to solve many problems. Circuits are held inside an return assembly.get_object() else return src // If not, the component is acting on its own. + + +// Returns the location to be used for dropping items. +// Same as the regular drop_location(), but with proc being run on assembly if there is any. +/obj/item/integrated_circuit/drop_location() + // If the component is located in an assembly, let the assembly figure that one out. + if(assembly) + return assembly.drop_location() + else + return ..() // If not, the component is acting on its own. + + +// Checks if the target object is reachable. Useful for various manipulators and manipulator-like objects. +/obj/item/integrated_circuit/proc/check_target(atom/target, exclude_contents = FALSE, exclude_components = FALSE, exclude_self = FALSE) + if(!target) + return FALSE + + var/atom/movable/acting_object = get_object() + + if(exclude_self && target == acting_object) + return FALSE + + if(exclude_components && assembly) + if(target in assembly.assembly_components) + return FALSE + + if(target == assembly.battery) + return FALSE + + if(target.Adjacent(acting_object) && isturf(target.loc)) + return TRUE + + if(!exclude_contents && (target in acting_object.GetAllContents())) + return TRUE + + if(target in acting_object.loc) + return TRUE + + return FALSE + diff --git a/code/modules/integrated_electronics/subtypes/manipulation.dm b/code/modules/integrated_electronics/subtypes/manipulation.dm index 62f15d927b..cd43cfae19 100644 --- a/code/modules/integrated_electronics/subtypes/manipulation.dm +++ b/code/modules/integrated_electronics/subtypes/manipulation.dm @@ -309,7 +309,7 @@ var/mode = get_pin_data(IC_INPUT, 2) if(mode == 1) - if(AM.Adjacent(acting_object) && isturf(AM.loc)) + if(check_target(AM, exclude_contents = TRUE)) if((contents.len < max_items) && (!max_w_class || AM.w_class <= max_w_class)) AM.forceMove(src) if(mode == 0) @@ -378,26 +378,20 @@ if(max_w_class && (A.w_class > max_w_class)) return - var/atom/movable/acting_object = get_object() - if(!(A.Adjacent(acting_object) && isturf(A.loc)) && !(A in acting_object.GetAllContents())) + // Is the target inside the assembly or close to it? + if(!check_target(A, exclude_components = TRUE)) return - var/turf/T = get_turf(acting_object) + var/turf/T = get_turf(get_object()) if(!T) return - // No ejecting assembly components or power cells - if(assembly) - if((A in assembly.assembly_components) || A == assembly.battery) - return - // If the item is in mob's inventory, try to remove it from there. if(ismob(A.loc)) var/mob/living/M = A.loc if(!M.temporarilyRemoveItemFromInventory(A)) return - var/x_abs = Clamp(T.x + target_x_rel, 0, world.maxx) var/y_abs = Clamp(T.y + target_y_rel, 0, world.maxy) var/range = round(Clamp(sqrt(target_x_rel*target_x_rel+target_y_rel*target_y_rel),0,8),1) diff --git a/code/modules/integrated_electronics/subtypes/reagents.dm b/code/modules/integrated_electronics/subtypes/reagents.dm index 1793869237..4fad53e746 100644 --- a/code/modules/integrated_electronics/subtypes/reagents.dm +++ b/code/modules/integrated_electronics/subtypes/reagents.dm @@ -1,29 +1,39 @@ /obj/item/integrated_circuit/reagent category_text = "Reagent" - var/volume = 0 resistance_flags = UNACIDABLE | FIRE_PROOF origin_tech = list(TECH_ENGINEERING = 2, TECH_DATA = 2, TECH_BIO = 2) + var/volume = 0 -/obj/item/integrated_circuit/reagent/New() - ..() +/obj/item/integrated_circuit/reagent/Initialize() + . = ..() if(volume) create_reagents(volume) + + /obj/item/integrated_circuit/reagent/smoke name = "smoke generator" desc = "Unlike most electronics, creating smoke is completely intentional." icon_state = "smoke" extended_desc = "This smoke generator creates clouds of smoke on command. It can also hold liquids inside, which will go \ into the smoke clouds when activated. The reagents are consumed when smoke is made." + container_type = OPENCONTAINER_1 + volume = 100 + complexity = 20 cooldown_per_use = 1 SECONDS inputs = list() - outputs = list("volume used" = IC_PINTYPE_NUMBER,"self reference" = IC_PINTYPE_REF) - activators = list("create smoke" = IC_PINTYPE_PULSE_IN,"on smoked" = IC_PINTYPE_PULSE_OUT) + outputs = list( + "volume used" = IC_PINTYPE_NUMBER, + "self reference" = IC_PINTYPE_REF + ) + activators = list( + "create smoke" = IC_PINTYPE_PULSE_IN, + "on smoked" = IC_PINTYPE_PULSE_OUT + ) spawn_flags = IC_SPAWN_RESEARCH origin_tech = list(TECH_ENGINEERING = 3, TECH_DATA = 3, TECH_BIO = 3) - volume = 100 power_draw_per_use = 20 var/smoke_radius = 5 var/notified = FALSE @@ -35,12 +45,6 @@ set_pin_data(IC_OUTPUT, 1, reagents.total_volume) push_data() - -/obj/item/integrated_circuit/reagent/smoke/interact(mob/user) - set_pin_data(IC_OUTPUT, 2, WEAKREF(src)) - push_data() - ..() - /obj/item/integrated_circuit/reagent/smoke/do_work() var/location = get_turf(src) var/datum/effect_system/smoke_spread/chem/S = new @@ -63,15 +67,29 @@ icon_state = "injector" extended_desc = "This autoinjector can push reagents into another container or someone else outside of the machine. The target \ must be adjacent to the machine, and if it is a person, they cannot be wearing thick clothing. Negative given amount makes injector suck out reagents." + container_type = OPENCONTAINER_1 + volume = 30 + complexity = 20 cooldown_per_use = 6 SECONDS - inputs = list("target" = IC_PINTYPE_REF, "injection amount" = IC_PINTYPE_NUMBER) - inputs_default = list("2" = 5) - outputs = list("volume used" = IC_PINTYPE_NUMBER,"self reference" = IC_PINTYPE_REF) - activators = list("inject" = IC_PINTYPE_PULSE_IN, "on injected" = IC_PINTYPE_PULSE_OUT, "on fail" = IC_PINTYPE_PULSE_OUT) + inputs = list( + "target" = IC_PINTYPE_REF, + "injection amount" = IC_PINTYPE_NUMBER + ) + inputs_default = list( + "2" = 5 + ) + outputs = list( + "volume used" = IC_PINTYPE_NUMBER, + "self reference" = IC_PINTYPE_REF + ) + activators = list( + "inject" = IC_PINTYPE_PULSE_IN, + "on injected" = IC_PINTYPE_PULSE_OUT, + "on fail" = IC_PINTYPE_PULSE_OUT + ) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH - volume = 30 power_draw_per_use = 15 var/direction_mode = SYRINGE_INJECT var/transfer_amount = 10 @@ -98,101 +116,109 @@ new_amount = Clamp(new_amount, 0, volume) transfer_amount = new_amount -/obj/item/integrated_circuit/reagent/proc/inject_tray(var/obj/machinery/hydroponics/H,var/atom/movable/SO,var/A) - var/datum/reagents/S = new /datum/reagents() //This is a strange way, but I don't know of a better one so I can't fix it at the moment... - S.my_atom = H - SO.reagents.trans_to(S,A) - H.applyChemicals(S) - S.clear_reagents() - qdel(S) +// Hydroponics trays have no reagents holder and handle reagents in their own snowflakey way. +// This is a dirty hack to make injecting reagents into them work. +// TODO: refactor that. +/obj/item/integrated_circuit/reagent/proc/inject_tray(obj/machinery/hydroponics/tray, atom/movable/source, amount) + var/datum/reagents/temp_reagents = new /datum/reagents() + temp_reagents.my_atom = tray + + source.reagents.trans_to(temp_reagents, amount) + tray.applyChemicals(temp_reagents) + + temp_reagents.clear_reagents() + qdel(temp_reagents) /obj/item/integrated_circuit/reagent/injector/do_work() set waitfor = FALSE // Don't sleep in a proc that is called by a processor without this set, otherwise it'll delay the entire thing var/atom/movable/AM = get_pin_data_as_type(IC_INPUT, 1, /atom/movable) - if(!istype(AM)||!Adjacent(AM)||busy) + var/atom/movable/acting_object = get_object() + + if(busy || !check_target(AM)) activate_pin(3) return - if(istype(AM,/obj/machinery/hydroponics)&&(direction_mode==SYRINGE_INJECT)&&(reagents.total_volume))//injection into tray. - inject_tray(AM,src,transfer_amount) - activate_pin(2) - return + if(!AM.reagents) + if(istype(AM, /obj/machinery/hydroponics) && direction_mode == SYRINGE_INJECT && reagents.total_volume)//injection into tray. + inject_tray(AM, src, transfer_amount) + activate_pin(2) + return activate_pin(3) return + if(direction_mode == SYRINGE_INJECT) - if(!reagents.total_volume) // Empty + if(!reagents.total_volume || !AM.is_injectable() || AM.reagents.total_volume >= AM.reagents.maximum_volume) activate_pin(3) return - if(AM.is_injectable()) - if(AM.reagents.total_volume>=AM.reagents.maximum_volume) - activate_pin(3) - return - if(isliving(AM)) - var/mob/living/L = AM - if(!L.can_inject(null, 0)) - activate_pin(3) - return - //Always log attemped injections for admins - var/list/rinject = list() - for(var/datum/reagent/R in reagents.reagent_list) - rinject += R.name - var/contained = english_list(rinject) - add_logs(src, L, "attemped to inject", addition="which had [contained]") //TODO: proper logging (maybe last touched and assembled) - L.visible_message("[src] is trying to inject [L]!", \ - "[src] is trying to inject you!") - busy = TRUE - if(do_atom(src, L, extra_checks=CALLBACK(L, /mob/living/proc/can_inject,null,0))) - var/fraction = min(transfer_amount/reagents.total_volume, 1) - reagents.reaction(L, INJECT, fraction) - reagents.trans_to(L, transfer_amount) - L.visible_message("[src] injects [L] with its needle!", \ - "[src] injects you with its needle!") - else - busy = FALSE - activate_pin(3) - return - busy = FALSE - else - reagents.trans_to(AM, transfer_amount) - else - activate_pin(3) - return - else - if(reagents.total_volume >= reagents.maximum_volume) // Full - activate_pin(3) - return - var/tramount = Clamp(min(transfer_amount, reagents.maximum_volume - reagents.total_volume), 0, reagents.maximum_volume) + if(isliving(AM)) var/mob/living/L = AM - L.visible_message("[src] is trying to take a blood sample from [L]!", \ - "[src] is trying to take a blood sample from you!") + if(!L.can_inject(null, 0)) + activate_pin(3) + return + + //Always log attemped injections for admins + var/list/rinject = list() + for(var/datum/reagent/R in reagents.reagent_list) + rinject += R.name + var/contained = english_list(rinject) + + add_logs(src, L, "attemped to inject", addition="which had [contained]") //TODO: proper logging (maybe last touched and assembled) + L.visible_message("[acting_object] is trying to inject [L]!", \ + "[acting_object] is trying to inject you!") + busy = TRUE + if(do_atom(src, L, extra_checks=CALLBACK(L, /mob/living/proc/can_inject,null,0))) + var/fraction = min(transfer_amount/reagents.total_volume, 1) + reagents.reaction(L, INJECT, fraction) + reagents.trans_to(L, transfer_amount) + L.visible_message("[acting_object] injects [L] with its needle!", \ + "[acting_object] injects you with its needle!") + else + busy = FALSE + activate_pin(3) + return + busy = FALSE + else + reagents.trans_to(AM, transfer_amount) + + else + if(!AM.is_drawable() || reagents.total_volume >= reagents.maximum_volume) + activate_pin(3) + return + + var/tramount = Clamp(transfer_amount, 0, reagents.total_volume) + + if(isliving(AM)) + var/mob/living/L = AM + L.visible_message("[acting_object] is trying to take a blood sample from [L]!", \ + "[acting_object] is trying to take a blood sample from you!") busy = TRUE if(do_atom(src, L, extra_checks=CALLBACK(L, /mob/living/proc/can_inject,null,0))) if(L.transfer_blood_to(src, tramount)) - L.visible_message("[src] takes a blood sample from [L].") + L.visible_message("[acting_object] takes a blood sample from [L].") else busy = FALSE activate_pin(3) return busy = FALSE else - if(!AM.reagents.total_volume || !AM.is_drawable()) + if(!AM.reagents.total_volume) activate_pin(3) return + AM.reagents.trans_to(src, tramount) activate_pin(2) - /obj/item/integrated_circuit/reagent/pump name = "reagent pump" desc = "Moves liquids safely inside a machine, or even nearby it." icon_state = "reagent_pump" - extended_desc = "This is a pump, which will move liquids from the source ref to the target ref. The third pin determines \ - how much liquid is moved per pulse, between 0 and 50. The pump can move reagents to any open container inside the machine, or \ - outside the machine if it is next to the machine. Note that this cannot be used on entities." - container_type = OPENCONTAINER_1 + extended_desc = "This is a pump, which will move liquids from the source ref to the target ref. The third pin determines \ + how much liquid is moved per pulse, between 0 and 50. The pump can move reagents to any open container inside the machine, or \ + outside the machine if it is next to the machine." + complexity = 8 inputs = list("source" = IC_PINTYPE_REF, "target" = IC_PINTYPE_REF, "injection amount" = IC_PINTYPE_NUMBER) inputs_default = list("3" = 5) @@ -219,43 +245,52 @@ var/atom/movable/source = get_pin_data_as_type(IC_INPUT, 1, /atom/movable) var/atom/movable/target = get_pin_data_as_type(IC_INPUT, 2, /atom/movable) - if(!istype(source) || !istype(target) || !source.reagents) //Invalid input + // Check for invalid input. + if(!check_target(source) || !check_target(target)) return - if(Adjacent(source) && Adjacent(target)) - if(istype(target,/obj/machinery/hydroponics)&&(source.reagents.total_volume))//injection into tray. - inject_tray(target,source,transfer_amount) - activate_pin(2) - return - if(!target.reagents) - return - if(ismob(source) || ismob(target)) - return - if(!source.is_open_container() || !target.is_open_container()) - return - if(direction_mode) - if(target.reagents.maximum_volume - target.reagents.total_volume <= 0) //full - return - source.reagents.trans_to(target, transfer_amount) - else - if(source.reagents.maximum_volume - source.reagents.total_volume <= 0) - return - target.reagents.trans_to(source, transfer_amount) - activate_pin(2) + // If the pump is pumping backwards, swap target and source. + if(!direction_mode) + var/temp_source = source + source = target + target = temp_source + + if(!source.reagents) + return + + if(!target.reagents) + // Hydroponics trays have no reagents holder and handle reagents in their own snowflakey way. + // This is a dirty hack to make injecting reagents into them work. + if(istype(target, /obj/machinery/hydroponics) && source.reagents.total_volume) + inject_tray(target, source, transfer_amount) + activate_pin(2) + return + + // FALSE in those procs makes mobs invalid targets. + if(!source.is_drawable(FALSE) || !target.is_injectable(FALSE)) + return + + source.reagents.trans_to(target, transfer_amount) + activate_pin(2) /obj/item/integrated_circuit/reagent/storage name = "reagent storage" - desc = "Stores liquid inside, and away from electrical components. Can store up to 60u." + desc = "Stores liquid inside, and away from electrical components. Can store up to 60u." icon_state = "reagent_storage" extended_desc = "This is effectively an internal beaker." + container_type = OPENCONTAINER_1 + volume = 60 + complexity = 4 inputs = list() - outputs = list("volume used" = IC_PINTYPE_NUMBER,"self reference" = IC_PINTYPE_REF) + outputs = list( + "volume used" = IC_PINTYPE_NUMBER, + "self reference" = IC_PINTYPE_REF + ) activators = list() spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH origin_tech = list(TECH_ENGINEERING = 2, TECH_DATA = 2, TECH_BIO = 2) - volume = 60 /obj/item/integrated_circuit/reagent/storage/interact(mob/user) @@ -269,38 +304,45 @@ /obj/item/integrated_circuit/reagent/storage/cryo name = "cryo reagent storage" - desc = "Stores liquid inside, and away from electrical components. Can store up to 60u. This will also suppress reactions." + desc = "Stores liquid inside, and away from electrical components. Can store up to 60u. This will also suppress reactions." icon_state = "reagent_storage_cryo" extended_desc = "This is effectively an internal cryo beaker." - container_type = OPENCONTAINER_1 + complexity = 8 spawn_flags = IC_SPAWN_RESEARCH origin_tech = list(TECH_MATERIALS = 4, TECH_ENGINEERING = 2, TECH_DATA = 2, TECH_BIO = 2) -/obj/item/integrated_circuit/reagent/storage/cryo/New() +/obj/item/integrated_circuit/reagent/storage/cryo/Initialize() . = ..() reagents.set_reacting(FALSE) /obj/item/integrated_circuit/reagent/storage/big name = "big reagent storage" - desc = "Stores liquid inside, and away from electrical components. Can store up to 180u." + desc = "Stores liquid inside, and away from electrical components. Can store up to 180u." icon_state = "reagent_storage_big" extended_desc = "This is effectively an internal beaker." - container_type = OPENCONTAINER_1 - complexity = 16 + volume = 180 + + complexity = 16 spawn_flags = IC_SPAWN_RESEARCH origin_tech = list(TECH_MATERIALS = 3, TECH_ENGINEERING = 2, TECH_DATA = 2, TECH_BIO = 2) /obj/item/integrated_circuit/reagent/storage/scan name = "reagent scanner" - desc = "Stores liquid inside, and away from electrical components. Can store up to 60u. On pulse this beaker will send list of contained reagents." + desc = "Stores liquid inside, and away from electrical components. Can store up to 60u. On pulse this beaker will send list of contained reagents." icon_state = "reagent_scan" extended_desc = "Mostly useful for reagent filter." - container_type = OPENCONTAINER_1 + complexity = 8 - outputs = list("volume used" = IC_PINTYPE_NUMBER,"self reference" = IC_PINTYPE_REF,"list of reagents" = IC_PINTYPE_LIST) - activators = list("scan" = IC_PINTYPE_PULSE_IN) + outputs = list( + "volume used" = IC_PINTYPE_NUMBER, + "self reference" = IC_PINTYPE_REF, + "list of reagents" = IC_PINTYPE_LIST + ) + activators = list( + "scan" = IC_PINTYPE_PULSE_IN + ) spawn_flags = IC_SPAWN_RESEARCH origin_tech = list(TECH_MATERIALS = 3, TECH_ENGINEERING = 2, TECH_DATA = 2, TECH_BIO = 2) @@ -316,16 +358,26 @@ name = "reagent filter" desc = "Filtering liquids by list of desired or unwanted reagents." icon_state = "reagent_filter" - extended_desc = "This is a filter, which will move liquids from the source ref to the target ref. \ + extended_desc = "This is a filter, which will move liquids from the source to the target. \ It will move all reagents, except list, given in fourth pin if amount value is positive.\ Or it will move only desired reagents if amount is negative, The third pin determines \ how much reagent is moved per pulse, between 0 and 50. Amount is given for each separate reagent." - container_type = OPENCONTAINER_1 + complexity = 8 - inputs = list("source" = IC_PINTYPE_REF, "target" = IC_PINTYPE_REF, "injection amount" = IC_PINTYPE_NUMBER, "list of reagents" = IC_PINTYPE_LIST) - inputs_default = list("3" = 5) + inputs = list( + "source" = IC_PINTYPE_REF, + "target" = IC_PINTYPE_REF, + "injection amount" = IC_PINTYPE_NUMBER, + "list of reagents" = IC_PINTYPE_LIST + ) + inputs_default = list( + "3" = 5 + ) outputs = list() - activators = list("transfer reagents" = IC_PINTYPE_PULSE_IN, "on transfer" = IC_PINTYPE_PULSE_OUT) + activators = list( + "transfer reagents" = IC_PINTYPE_PULSE_IN, + "on transfer" = IC_PINTYPE_PULSE_OUT + ) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH origin_tech = list(TECH_ENGINEERING = 2, TECH_DATA = 2, TECH_BIO = 2) var/transfer_amount = 10 @@ -347,27 +399,28 @@ var/atom/movable/source = get_pin_data_as_type(IC_INPUT, 1, /atom/movable) var/atom/movable/target = get_pin_data_as_type(IC_INPUT, 2, /atom/movable) var/list/demand = get_pin_data(IC_INPUT, 4) - if(!istype(source) || !istype(target)) //Invalid input + + // Check for invalid input. + if(!check_target(source) || !check_target(target)) return - var/turf/T = get_turf(src) - if(source.Adjacent(T) && target.Adjacent(T)) - if(!source.reagents || !target.reagents) - return - if(ismob(source) || ismob(target)) - return - if(!source.is_open_container() || !target.is_open_container()) - return - if(target.reagents.maximum_volume - target.reagents.total_volume <= 0) - return - for(var/datum/reagent/G in source.reagents.reagent_list) - if (!direction_mode) - if(G.id in demand) - source.reagents.trans_id_to(target, G.id, transfer_amount) - else - if(!(G.id in demand)) - source.reagents.trans_id_to(target, G.id, transfer_amount) - activate_pin(2) - push_data() + if(!source.reagents || !target.reagents) + return + // FALSE in those procs makes mobs invalid targets. + if(!source.is_drawable(FALSE) || !target.is_injectable(FALSE)) + return + + if(target.reagents.maximum_volume - target.reagents.total_volume <= 0) + return + + for(var/datum/reagent/G in source.reagents.reagent_list) + if(!direction_mode) + if(G.id in demand) + source.reagents.trans_id_to(target, G.id, transfer_amount) + else + if(!(G.id in demand)) + source.reagents.trans_id_to(target, G.id, transfer_amount) + activate_pin(2) + push_data()