diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm index 7be079a5ed206..1b47e1696e685 100644 --- a/code/game/objects/items/devices/multitool.dm +++ b/code/game/objects/items/devices/multitool.dm @@ -19,6 +19,7 @@ origin_tech = list(TECH_MAGNET = 1, TECH_ENGINEERING = 1) + var/buffer var/buffer_name var/atom/buffer_object diff --git a/icons/obj/telescience.dmi b/icons/obj/telescience.dmi index 675e8412de2d2..0a95347b6b4d9 100644 Binary files a/icons/obj/telescience.dmi and b/icons/obj/telescience.dmi differ diff --git a/mods/antagonists/_antagonists.dme b/mods/antagonists/_antagonists.dme index 911fe25f76179..21f28a427d881 100644 --- a/mods/antagonists/_antagonists.dme +++ b/mods/antagonists/_antagonists.dme @@ -9,7 +9,6 @@ #include "code/revolutionary.dm" #include "code/operative.dm" #include "code/traitor.dm" -#include "code/teleportation.dm" #include "code/uplink.dm" #endif diff --git a/mods/antagonists/code/teleportation.dm b/mods/antagonists/code/teleportation.dm index 00567d20fd38a..25c87b66c7efd 100644 --- a/mods/antagonists/code/teleportation.dm +++ b/mods/antagonists/code/teleportation.dm @@ -1,15 +1 @@ -/proc/animated_teleportation(atom/movable/target, atom/anchor) - if(ismob(target)) - to_chat(target, SPAN_WARNING("You feel like something pulling you in bluespace.")) - var/obj/temporary/A = new(get_turf(target), 24.5, 'mods/antagonists/icons/effects/bs_silk.dmi', "silc_teleport_back") - target.set_dir(2) - target.forceMove(A) - addtimer(new Callback(GLOBAL_PROC, GLOBAL_PROC_REF(animated_teleportation_ending), target, anchor), 23) - -/proc/animated_teleportation_ending(atom/movable/target, atom/anchor) - target.set_dir(2) - target.forceMove(new /obj/temporary(get_turf(anchor), 26.5, 'mods/antagonists/icons/effects/bs_silk.dmi', "silc_get_hub")) - addtimer(new Callback(GLOBAL_PROC, GLOBAL_PROC_REF(finalize_animated_teleportation), target, anchor), 24) - -/proc/finalize_animated_teleportation(atom/movable/target, atom/anchor) - target.dropInto(get_turf(anchor)) +// delete diff --git a/mods/guns/code/energy.dm b/mods/guns/code/energy.dm index 93a7b4c01b421..f03c80786e033 100644 --- a/mods/guns/code/energy.dm +++ b/mods/guns/code/energy.dm @@ -77,8 +77,13 @@ /obj/item/gun/energy/confuseray/secure name = "disorientator" desc = "The W-T Mk. 6 Disorientator fitted with an NT1017 secure fire chip. It has a NanoTrasen logo on the grip." - icon = 'mods/guns/icons/obj/confuseray_secure.dmi' - icon_state = "confusesecure" + icon = 'icons/obj/guns/confuseray.dmi' + item_icons = list( + slot_l_hand_str = 'mods/guns/icons/mob/lefthand_guns_cadet.dmi', + slot_r_hand_str = 'mods/guns/icons/mob/righthand_guns_cadet.dmi', + ) + icon_state = "confuseray" + item_state = "confuseray" req_access = list(list(access_brig, access_bridge)) /obj/item/gun/energy/stunrevolver/secure diff --git a/mods/guns/icons/mob/lefthand_guns_cadet.dmi b/mods/guns/icons/mob/lefthand_guns_cadet.dmi new file mode 100644 index 0000000000000..f9be770a2ef9e Binary files /dev/null and b/mods/guns/icons/mob/lefthand_guns_cadet.dmi differ diff --git a/mods/guns/icons/mob/righthand_guns_cadet.dmi b/mods/guns/icons/mob/righthand_guns_cadet.dmi new file mode 100644 index 0000000000000..f2abf04d86432 Binary files /dev/null and b/mods/guns/icons/mob/righthand_guns_cadet.dmi differ diff --git a/mods/machinery/_machinery.dme b/mods/machinery/_machinery.dme index a9581b68aa6e5..a8e9646ffa7e0 100644 --- a/mods/machinery/_machinery.dme +++ b/mods/machinery/_machinery.dme @@ -7,6 +7,11 @@ #include "code/gravity_generator/main.dm" #include "code/gravity_generator/wires.dm" #include "code/sealing_generator.dm" -#include "code\telepads.dm" +#include "code/telepads.dm" +#include "code/tele_pads.dm" +#include "code/gps.dm" +#include "code/bcrystal.dm" +#include "code/telesci_computer.dm" +#include "code/tele.dm" #endif diff --git a/mods/machinery/code/bcrystal.dm b/mods/machinery/code/bcrystal.dm new file mode 100644 index 0000000000000..3910fc011090b --- /dev/null +++ b/mods/machinery/code/bcrystal.dm @@ -0,0 +1,57 @@ +// Bluespace crystals, used in telescience and when crushed it will blink you to a random turf. + +/obj/item/bluespace_crystal + name = "bluespace crystal" + desc = "A glowing bluespace crystal, not much is known about how they work. It looks very delicate." + icon = 'icons/obj/telescience.dmi' + icon_state = "bluespace_crystal" + w_class = 1 + origin_tech = list(TECH_BLUESPACE = 4, TECH_MATERIAL = 3) + var/blink_range = 8 // The teleport range when crushed/thrown at someone. + + +/obj/item/bluespace_crystal/New() + ..() + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) +// create_reagents(10) +// reagents.add_reagent("bluespace_dust", blink_range) + +/obj/item/bluespace_crystal/attack_self(mob/user) + user.visible_message("[user] crushes [src]!", "You crush [src]!") + var/datum/effect/spark_spread/sparks = new /datum/effect/spark_spread() + sparks.set_up(5, 0, get_turf(user)) + sparks.start() + playsound(src.loc, "sparks", 50, 1) + playsound(src.loc, 'sound/effects/phasein.ogg', 25, 1) + blink_mob(user) + user.unEquip(src) + qdel(src) + +/obj/item/bluespace_crystal/proc/blink_mob(mob/living/L) + var/turf/T = get_random_turf_in_range(L, blink_range, 1) + L.forceMove(T) + var/datum/effect/spark_spread/sparks = new /datum/effect/spark_spread() + sparks.set_up(5, 0, T) + sparks.start() + +/obj/item/bluespace_crystal/throw_impact(atom/hit_atom) + if(!..()) // not caught in mid-air + visible_message("[src] fizzles and disappears upon impact!") + var/turf/T = get_turf(hit_atom) + var/datum/effect/spark_spread/sparks = new /datum/effect/spark_spread() + sparks.set_up(5, 0, T) + sparks.start() + playsound(src.loc, "sparks", 50, 1) + if(isliving(hit_atom)) + blink_mob(hit_atom) + playsound(T, 'sound/effects/phasein.ogg', 25, 1) + qdel(src) + +// Artifical bluespace crystal, doesn't give you much research. + +/obj/item/bluespace_crystal/artificial + name = "artificial bluespace crystal" + desc = "An artificially made bluespace crystal, it looks delicate." + origin_tech = list(TECH_BLUESPACE = 2) + blink_range = 4 // Not as good as the organic stuff! diff --git a/mods/machinery/code/gps.dm b/mods/machinery/code/gps.dm new file mode 100644 index 0000000000000..14ff9980f368b --- /dev/null +++ b/mods/machinery/code/gps.dm @@ -0,0 +1,95 @@ +GLOBAL_LIST_EMPTY(GPS_list) + +GLOBAL_LIST_EMPTY(gps_by_type) + +/obj/item/device/gps + name = "global positioning system" + desc = "Helping lost spacemen find their way through the planets since 2016." + icon = 'icons/obj/telescience.dmi' + icon_state = "gps-c" + w_class = 2 + slot_flags = SLOT_BELT + origin_tech = list(TECH_ENGINEERING = 2, TECH_DATA = 2, TECH_BLUESPACE = 2) + matter = list(MATERIAL_ALUMINIUM = 250, MATERIAL_STEEL = 250, MATERIAL_GLASS = 50) + var/gps_prefix = "COM" + var/gpstag = "COM0" + emped = 0 + var/turf/locked_location + +/obj/item/device/gps/Initialize() + . = ..() + GLOB.GPS_list += src + LAZYADD(GLOB.gps_by_type["[type]"], src) + gpstag = "[gps_prefix][LAZYLEN(GLOB.gps_by_type["[type]"])]" + name = "global positioning system ([gpstag])" + AddOverlays(image(icon, "working")) + +/obj/item/device/gps/Destroy() + GLOB.GPS_list -= src + var/list/typelist = GLOB.gps_by_type["[type]"] + LAZYREMOVE(typelist, src) + return ..() + +/obj/item/device/gps/emp_act(severity) + emped = 1 + CutOverlays() + AddOverlays(image(icon, "emp")) + addtimer(new Callback(src, .proc/post_emp), 300) + +/obj/item/device/gps/proc/post_emp() + emped = 0 + CutOverlays() + AddOverlays(image(icon, "working")) + +/obj/item/device/gps/attack_self(mob/user) + + var/obj/item/device/gps/t = "" + var/gps_window_height = 110 + LAZYLEN(GLOB.GPS_list) * 20 // Variable window height, depending on how many GPS units there are to show + if(emped) + t += "ERROR" + else + t += "
Set Tag " + t += "
Tag: [gpstag]" + if(locked_location?.loc) + t += "
Bluespace coordinates saved: [locked_location.loc]" + gps_window_height += 20 + + for(var/obj/item/device/gps/G in GLOB.GPS_list) + var/turf/pos = get_turf(G) + var/area/gps_area = get_area(G) + var/tracked_gpstag = G.gpstag + if(G.emped == 1 || !pos) + t += "
[tracked_gpstag]: ERROR" + else + t += "
[tracked_gpstag]: [format_text(gps_area.name)] ([pos.x], [pos.y], [pos.z])" + + var/datum/browser/popup = new(user, "GPS", name, 360, min(gps_window_height, 800)) + popup.set_content(t) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/item/device/gps/Topic(href, href_list) + ..() + if(href_list["tag"] ) + var/a = input("Please enter desired tag.", name, gpstag) as text + a = uppertext(copytext(sanitize(a), 1, 5)) + if(src.loc == usr) + gpstag = a + name = "global positioning system ([gpstag])" + attack_self(usr) + +/obj/item/device/gps/science + icon_state = "gps-s" + gps_prefix = "SCI" + gpstag = "SCI0" + +/obj/item/device/gps/engineering + icon_state = "gps-e" + gps_prefix = "ENG" + gpstag = "ENG0" + +/obj/item/device/gps/mining + icon_state = "gps-m" + gps_prefix = "MIN" + gpstag = "MIN0" + desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life." diff --git a/mods/machinery/code/tele.dm b/mods/machinery/code/tele.dm new file mode 100644 index 0000000000000..00567d20fd38a --- /dev/null +++ b/mods/machinery/code/tele.dm @@ -0,0 +1,15 @@ +/proc/animated_teleportation(atom/movable/target, atom/anchor) + if(ismob(target)) + to_chat(target, SPAN_WARNING("You feel like something pulling you in bluespace.")) + var/obj/temporary/A = new(get_turf(target), 24.5, 'mods/antagonists/icons/effects/bs_silk.dmi', "silc_teleport_back") + target.set_dir(2) + target.forceMove(A) + addtimer(new Callback(GLOBAL_PROC, GLOBAL_PROC_REF(animated_teleportation_ending), target, anchor), 23) + +/proc/animated_teleportation_ending(atom/movable/target, atom/anchor) + target.set_dir(2) + target.forceMove(new /obj/temporary(get_turf(anchor), 26.5, 'mods/antagonists/icons/effects/bs_silk.dmi', "silc_get_hub")) + addtimer(new Callback(GLOBAL_PROC, GLOBAL_PROC_REF(finalize_animated_teleportation), target, anchor), 24) + +/proc/finalize_animated_teleportation(atom/movable/target, atom/anchor) + target.dropInto(get_turf(anchor)) diff --git a/mods/machinery/code/tele_pads.dm b/mods/machinery/code/tele_pads.dm new file mode 100644 index 0000000000000..ffda9771c076f --- /dev/null +++ b/mods/machinery/code/tele_pads.dm @@ -0,0 +1,52 @@ +///SCI TELEPAD/// +/obj/machinery/telepad + name = "telepad" + desc = "A bluespace telepad used for teleporting objects to and from a location." + icon = 'icons/obj/telescience.dmi' + icon_state = "pad-idle" + anchored = TRUE + use_power = 1 + idle_power_usage = 200 + active_power_usage = 5000 + construct_state = /singleton/machine_construction/default/panel_closed + uncreated_component_parts = null + stat_immune = 0 + + var/efficiency + +/obj/machinery/telepad/RefreshParts() + efficiency = total_component_rating_of_type(/obj/item/stock_parts/capacitor) + +/obj/machinery/telepad/components_are_accessible(path) + return panel_open + +/obj/machinery/telepad/use_tool(obj/item/tool, mob/living/user, list/click_params) + if(component_attackby(tool, user)) return TRUE + if(panel_open) + if(istype(tool, /obj/item/device/multitool)) + var/obj/item/device/multitool/M = tool + M.buffer = src + to_chat(user, "You save the data in the [tool.name]'s buffer.") + return + // Алмазная фокусирующая линза. Гы-гы + if(istype(tool, /obj/item/stack/material/diamond)) + var/obj/item/stock_parts/building_material/material = get_component_of_type(/obj/item/stock_parts/building_material, TRUE) + if(material && material.number_of_type(/obj/item/stack/material/diamond)>0) + to_chat(user, "Machine have already installed \an [tool.name]") + return + if(user.drop_from_inventory(tool)) + install_component(tool) + return + + else + if(istype(tool, /obj/item/device/multitool)) + to_chat(user, "You should open [src]'s maintenance panel first.") + return + .=..() + +/obj/machinery/telepad/on_update_icon() + switch (panel_open) + if (1) + icon_state = "pad-idle-o" + if (0) + icon_state = "pad-idle" diff --git a/mods/machinery/code/telepads.dm b/mods/machinery/code/telepads.dm index 0326e4f8e67f7..fe329b6861743 100644 --- a/mods/machinery/code/telepads.dm +++ b/mods/machinery/code/telepads.dm @@ -7,7 +7,7 @@ uncreated_component_parts = null /obj/item/stock_parts/circuitboard/tele_pad - name = "circuit board (telepad)" + name = "circuit board (teleporter pad)" board_type = "machine" origin_tech = list(TECH_DATA = 4, TECH_BLUESPACE = 4) build_path = /obj/machinery/tele_pad @@ -20,7 +20,7 @@ /datum/design/circuit/tele_pad name = "telepad machine" - id = "telepad" + id = "teleporter_pad" req_tech = list(TECH_DATA = 4, TECH_BLUESPACE = 4) build_path = /obj/item/stock_parts/circuitboard/tele_pad sort_string = "MAAAA" @@ -31,3 +31,37 @@ req_tech = list(TECH_DATA = 4, TECH_BLUESPACE = 4) build_path = /obj/item/stock_parts/circuitboard/tele_projector sort_string = "MAAAA" + +/obj/item/stock_parts/circuitboard/telepad + name = "circuit board (telepad)" + board_type = "machine" + origin_tech = list(TECH_DATA = 4, TECH_ENGINEERING = 3, TECH_MATERIAL = 3, TECH_BLUESPACE = 4) + build_path = /obj/machinery/telepad + req_components = list( + /obj/item/bluespace_crystal = 2, + /obj/item/stock_parts/capacitor = 1 + ) + additional_spawn_components = list( + /obj/item/stock_parts/console_screen = 1, + /obj/item/stock_parts/keyboard = 1, + /obj/item/stock_parts/power/apc/buildable = 1 + ) + +/obj/item/stock_parts/circuitboard/telesci_console + name = "circuit board (telescience console)" + build_path = /obj/machinery/computer/telescience + origin_tech = list(TECH_DATA = 3, TECH_BLUESPACE = 2) + +/datum/design/circuit/telepad + name = "telepad" + id = "telepad" + req_tech = list(TECH_DATA = 4, TECH_ENGINEERING = 3, TECH_MATERIAL = 3, TECH_BLUESPACE = 4) + build_path = /obj/item/stock_parts/circuitboard/telepad + sort_string = "HAAAF" + +/datum/design/circuit/telesci_console + name = "telepad control console" + id = "telesci_console" + req_tech = list(TECH_DATA = 3, TECH_BLUESPACE = 2) + build_path = /obj/item/stock_parts/circuitboard/telesci_console + sort_string = "HAAAD" diff --git a/mods/machinery/code/telesci_computer.dm b/mods/machinery/code/telesci_computer.dm new file mode 100644 index 0000000000000..4beaffc5866c6 --- /dev/null +++ b/mods/machinery/code/telesci_computer.dm @@ -0,0 +1,419 @@ +/obj/machinery/computer/telescience + name = "\improper Telepad Control Console" + desc = "Used to teleport objects to and from the telescience telepad." + icon_screen = "telesci" + icon_keyboard = "telesci_key" + light_color = COLOR_BLUE + //circuit = /obj/item/stock_parts/circuitboard/telesci_console + var/sending = 1 + var/obj/machinery/telepad/telepad = null + var/temp_msg = "Telescience control console initialized.
Welcome." + + // VARIABLES // + var/teles_left // How many teleports left until it becomes uncalibrated + var/datum/projectile_data/last_tele_data = null + var/z_co = 1 + var/power_off + var/xlen + var/rotation_off + var/last_target + + var/rotation = 0 + var/angle = 45 + var/power = 5 + + // Based on the power used + var/teleport_cooldown = 0 // every index requires a bluespace crystal + var/list/power_options = list(5, 10, 20, 25, 30, 40, 50, 80, 100) + var/teleporting = 0 + var/starting_crystals = 0 //Edit this on the map, seriously. + var/max_crystals = 5 + var/list/crystals = list() + var/obj/item/device/gps/inserted_gps + +/obj/machinery/computer/telescience/Destroy() + eject() + if(inserted_gps) + inserted_gps.forceMove(loc) + inserted_gps = null + return ..() + +/obj/machinery/computer/telescience/examine(mob/user) + . = ..() + + to_chat(user, "There are [xlen ? xlen : "no"] bluespace crystal\s in the crystal slots.") + +/obj/machinery/computer/telescience/Initialize() + . = ..() + xlen = LAZYLEN(crystals) + recalibrate() + for(var/i = 1; i <= starting_crystals; i++) + crystals += new /obj/item/bluespace_crystal/artificial(null) // starting crystals + +/obj/machinery/computer/telescience/use_tool(obj/item/W, mob/user, params) + if(istype(W, /obj/item/bluespace_crystal)) + if(xlen >= max_crystals) + to_chat(user, "There are not enough crystal slots.") + return + user.drop_item(src) + crystals += W + W.forceMove(null) + user.visible_message("[user] inserts [W] into \the [src]'s crystal slot.", "You insert [W] into \the [src]'s crystal slot.") + updateDialog() + else if(istype(W, /obj/item/device/gps)) + if(!inserted_gps) + inserted_gps = W + user.unEquip(W) + W.forceMove(src) + user.visible_message("[user] inserts [W] into \the [src]'s GPS device slot.", "You insert [W] into \the [src]'s GPS device slot.") + else if(istype(W, /obj/item/device/multitool)) + var/obj/item/device/multitool/M = W + if(M.buffer && istype(M.buffer, /obj/machinery/telepad)) + telepad = M.buffer + M.buffer = null + to_chat(user, "You upload the data from the [W.name]'s buffer.") + else + ..() + +/obj/machinery/computer/telescience/attack_ai(mob/user) + src.physical_attack_hand(user) + +/obj/machinery/computer/telescience/physical_attack_hand(mob/user) + if(..()) + return + if(!user.skill_check(SKILL_SCIENCE, SKILL_EXPERIENCED)) + to_chat(user, "You see a lot of complex variables that you cannot understand.") + return + interact(user) + +/obj/machinery/computer/telescience/interact(mob/user) + var/t + if(!telepad) + in_use = 0 //Yeah so if you deconstruct teleporter while its in the process of shooting it wont disable the console + t += "
No telepad located.
Please add telepad data via use of Multitool.

" + else + if(inserted_gps) + t += "Eject GPS" + t += "Set GPS memory" + else + t += "Eject GPS" + t += "Set GPS memory" + t += "
[temp_msg]

" + t += "Set Bearing" + t += "
[rotation]°
" + t += "Set Elevation" + t += "
[angle]°
" + t += "Set Power" + t += "
" + + for(var/i = 1; i <= LAZYLEN(power_options); i++) + if(x + telepad.efficiency < i) + t += "[power_options[i]]" + continue + if(power == power_options[i]) + t += "[power_options[i]]" + continue + t += "[power_options[i]]" + t += "
" + + t += "Set Sector" + t += "
[z_co ? z_co : "NULL"]
" + + t += "
Send" + t += " Receive" + t += "
Recalibrate Crystals Eject Crystals" + + // Information about the last teleport + t += "
" + if(!last_tele_data) + t += "No teleport data found." + else + t += "Source Location: ([last_tele_data.src_x], [last_tele_data.src_y])
" + //t += "Distance: [round(last_tele_data.distance, 0.1)]m
" + t += "Time: [round(last_tele_data.time, 0.1)] secs
" + t += "
" + + var/datum/browser/popup = new(user, "telesci", name, 300, 500) + popup.set_content(t) + popup.open() + return + +/obj/machinery/computer/telescience/proc/sparks() + if(telepad) + var/datum/effect/spark_spread/sparks = new /datum/effect/spark_spread() + sparks.set_up(5, 0, get_turf(telepad)) + sparks.start() + else + return + +/obj/machinery/computer/telescience/proc/telefail() + sparks() + visible_message("The telepad weakly fizzles.") + return + +/obj/machinery/computer/telescience/proc/doteleport(mob/user) + + if(teleport_cooldown > world.time) + temp_msg = "Telepad is recharging power.
Please wait [round((teleport_cooldown - world.time) / 10)] seconds." + return + + if(teleporting) + temp_msg = "Telepad is in use.
Please wait." + return + + if(telepad) + + var/truePower = clamp(power + power_off, 1, 1000) + var/trueRotation = rotation + rotation_off + var/trueAngle = clamp(angle, 1, 90) + + var/datum/projectile_data/proj_data = projectile_trajectory(telepad.x, telepad.y, trueRotation, trueAngle, truePower) + last_tele_data = proj_data + + var/trueX = clamp(round(proj_data.dest_x, 1), 1, world.maxx) + var/trueY = clamp(round(proj_data.dest_y, 1), 1, world.maxy) + var/spawn_time = round(proj_data.time) * 10 + + var/turf/target = locate(trueX, trueY, z_co) + last_target = target + var/area/A = get_area(target) + flick("pad-beam", telepad) + + if(spawn_time > 15) // 1.5 seconds + playsound(telepad.loc, 'sound/weapons/flash.ogg', 25, 1) + // Wait depending on the time the projectile took to get there + teleporting = 1 + temp_msg = "Powering up bluespace crystals.
Please wait." + + + spawn(round(proj_data.time) * 10) // in seconds + if(!telepad) + return + if(telepad.stat & src.is_powered()) + return + teleporting = 0 + teleport_cooldown = world.time + (power * 2) + teles_left -= 1 + + // use a lot of power + use_power_oneoff(power * 10) + + temp_msg = "Teleport successful.
" + if(teles_left < 10) + temp_msg += "
Calibration required soon." + else + temp_msg += "Data printed below." + + var/datum/effect/spark_spread/sparks = new /datum/effect/spark_spread() + sparks.set_up(5, 0, telepad) + sparks.start() + + var/turf/source = target + var/turf/dest = get_turf(telepad) + var/log_msg = "" + log_msg += ": [key_name(user)] has teleported " + + if(sending) + source = dest + dest = target + + flick("pad-beam", telepad) + playsound(telepad.loc, 'sound/weapons/emitter2.ogg', 25, 1, extrarange = 3, falloff = 5) + for(var/atom/movable/ROI in source) + // if is anchored, don't let through + if(ROI.anchored) + if(isliving(ROI)) + var/mob/living/L = ROI + if(L.buckled) + // TP people on office chairs + if(L.buckled.anchored) + continue + + log_msg += "[key_name(L)] (on a chair), " + else + continue + else if(!isobserver(ROI)) + continue + if(ismob(ROI)) + var/mob/T = ROI + log_msg += "[key_name(T)], " + else + log_msg += "[ROI.name]" + if (istype(ROI, /obj/structure/closet)) + var/obj/structure/closet/C = ROI + log_msg += " (" + for(var/atom/movable/Q as mob|obj in C) + if(ismob(Q)) + log_msg += "[key_name(Q)], " + else + log_msg += "[Q.name], " + if (dd_hassuffix(log_msg, "(")) + log_msg += "empty)" + else + //log_msg = dd_limittext(log_msg, length(log_msg) - 2) + log_msg += ")" + log_msg += ", " + // ELAR's animation of human's telepoartation + if(ishuman(ROI)) + animated_teleportation(ROI, dest) + else + do_teleport(ROI, dest) + + if (dd_hassuffix(log_msg, ", ")) + //log_msg = dd_limittext(log_msg, length(log_msg) - 2) + else + log_msg += "nothing" + log_msg += " [sending ? "to" : "from"] [trueX], [trueY], [z_co] ([A ? A.name : "null area"])" + investigate_log(log_msg, "telesci") + updateDialog() + +/obj/machinery/computer/telescience/proc/teleport(mob/user) + if(!(rotation || angle || z_co)) + temp_msg = "ERROR!
Set a angle, rotation and sector." + return + if(power <= 0) + telefail() + temp_msg = "ERROR!
No power selected!" + return + if(angle < 1 || angle > 90) + telefail() + temp_msg = "ERROR!
Elevation is less than 1 or greater than 90." + return + if(z_co in GLOB.using_map.admin_levels) + telefail() + temp_msg = "ERROR! Sector is invalid! Valid sectors are [english_list(GLOB.using_map.player_levels)]." + return + if(!user.skill_check(SKILL_SCIENCE, SKILL_MASTER) && !(z_co in GLOB.using_map.station_levels)) + telefail() + temp_msg = "ERROR! Bad configuration provided by the user. Unable to charge the teleporter." + return + // Да если у тебя мастер науки, ты все равно огребешь за телепорты с кораблика на планету в другом конце карты + if(GLOB.using_map.use_overmap && !(z_co in GetConnectedZlevels(telepad.z))) + var/obj/item/stock_parts/building_material/material = telepad.get_component_of_type(/obj/item/stock_parts/building_material) + if(!(material && material.number_of_type(/obj/item/stack/material/diamond)!=0)) + telefail() + temp_msg = "ERROR! Can not reach that sector. It's too far or need an upgrade!" + return + else + telepad.visible_message(SPAN_NOTICE("\The [telepad] shines brighter when it's diamond lens focuses bluespace power!")) + var/obj/overmap/visitable/we = map_sectors["[telepad.z]"] + var/turf/T = get_turf(we) + var/located = FALSE + for(var/obj/overmap/visitable/candidate in T) + if(candidate == we) + continue + if(z_co in candidate.map_z) + located = TRUE + break + // Мы не нашли цель в нашем секторе, попробуем более серьезные варианты. + if(!located) + var/obj/item/stock_parts/power/battery/battery = telepad.get_component_of_type(/obj/item/stock_parts/power/battery) + // Больше чем advanced меньше чем enchanced и да мы тратим энергию независимо от успеха + if(!(battery && battery.can_use_power_oneoff(telepad, (1500 / CELLRATE), LOCAL) && battery.use_power_oneoff(telepad, (1500 / CELLRATE), LOCAL))) + telefail() + temp_msg = "ERROR! Can not reach that sector. Not enough power buffer capacity!" + return + else + playsound(telepad, 'sound/machines/apc_nopower.ogg', 25, 0) + telepad.visible_message(SPAN_NOTICE("\The [telepad] makes an loud sound as it internal capacitor quickly loose it's power!")) + var/list/near_sectors = T.AdjacentTurfs() + for(var/turf/Z in near_sectors) + for(var/obj/overmap/visitable/candidate in Z) + if(candidate == we) + continue + if(z_co in candidate.map_z) + located = TRUE + break + if(located) + break + if(!located) + telefail() + temp_msg = "ERROR! Can not reach that sector. Too far. You did your best..." + return + if(teles_left > 0) + doteleport(user) + else + telefail() + temp_msg = "ERROR!
Calibration required." + return + return + +/obj/machinery/computer/telescience/proc/eject() + for(var/obj/item/I in crystals) + I.forceMove(src.loc) + crystals -= I + power = 0 + +/obj/machinery/computer/telescience/Topic(href, href_list) + if(..()) + return + if(!telepad) + updateDialog() + return + if(telepad.panel_open) + temp_msg = "Telepad undergoing physical maintenance operations." + + if(href_list["setrotation"]) + var/new_rot = input("Please input desired bearing in degrees.", name, rotation) as num + if(..()) // Check after we input a value, as they could've moved after they entered something + return + rotation = clamp(new_rot, -900, 900) + rotation = round(rotation, 0.01) + + if(href_list["setangle"]) + var/new_angle = input("Please input desired elevation in degrees.", name, angle) as num + if(..()) + return + angle = clamp(round(new_angle, 0.1), 1, 9999) + + if(href_list["setpower"]) + var/index = href_list["setpower"] + index = text2num(index) + if(index != null && power_options[index]) + if(xlen + telepad.efficiency >= index) + power = power_options[index] + + if(href_list["setz"]) + var/new_z = input("Please input desired sector.", name, z_co) as num + if(..()) + return + z_co = clamp(round(new_z), 1, 35) + + if(href_list["ejectGPS"]) + if(inserted_gps) + inserted_gps.forceMove(loc) + inserted_gps = null + + if(href_list["setMemory"]) + if(last_target && inserted_gps) + inserted_gps.locked_location = last_target + temp_msg = "Location saved." + else + temp_msg = "ERROR!
No data was stored." + + if(href_list["send"]) + sending = 1 + teleport(usr) + + if(href_list["receive"]) + sending = 0 + teleport(usr) + + if(href_list["recal"]) + recalibrate(usr) + sparks() + temp_msg = "NOTICE:
Calibration successful." + + if(href_list["eject"]) + eject() + temp_msg = "NOTICE:
Bluespace crystals ejected." + + updateDialog() + +/obj/machinery/computer/telescience/proc/recalibrate(mob/user) + var/mult = 1 + if(user?.skill_check(SKILL_SCIENCE, SKILL_MASTER)) + mult = 2 + teles_left = rand(30, 40) * mult + power_off = rand(-4, 0) / mult + rotation_off = rand(-10, 10) / mult diff --git a/mods/utility_items/_utility_items.dme b/mods/utility_items/_utility_items.dme index 2fd8896a51132..f21bac280307f 100644 --- a/mods/utility_items/_utility_items.dme +++ b/mods/utility_items/_utility_items.dme @@ -8,5 +8,6 @@ #include "code/multimeter.dm" #include "code/noose.dm" #include "code/wires.dm" +#include "code/reagents.dm" #endif diff --git a/mods/utility_items/code/reagents.dm b/mods/utility_items/code/reagents.dm new file mode 100644 index 0000000000000..8d98d70ce9cfa --- /dev/null +++ b/mods/utility_items/code/reagents.dm @@ -0,0 +1,660 @@ +#define IC_SMOKE_REAGENTS_MINIMUM_UNITS 10 +#define IC_REAGENTS_DRAW 0 +#define IC_REAGENTS_INJECT 1 +#define IC_HEATER_MODE_HEAT "heat" +#define IC_HEATER_MODE_COOL "cool" + +/obj/item/integrated_circuit/reagent + category_text = "Reagent" + unacidable = TRUE + cooldown_per_use = 10 + var/volume = 0 + +/obj/item/integrated_circuit/reagent/Initialize() + . = ..() + if(volume) + create_reagents(volume) + push_vol() + +/obj/item/integrated_circuit/reagent/proc/push_vol() + set_pin_data(IC_OUTPUT, 1, reagents.total_volume) + push_data() + +/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 the smoke is made." + ext_cooldown = 1 + atom_flags = ATOM_FLAG_OPEN_CONTAINER + 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, + "push ref" = IC_PINTYPE_PULSE_IN + ) + spawn_flags = IC_SPAWN_RESEARCH + power_draw_per_use = 20 + var/smoke_radius = 5 + var/notified = FALSE + +/obj/item/integrated_circuit/reagent/smoke/on_reagent_change() + push_vol() + +/obj/item/integrated_circuit/reagent/smoke/do_work(ord) + switch(ord) + if(1) + if(!reagents || (reagents.total_volume < IC_SMOKE_REAGENTS_MINIMUM_UNITS)) + return + var/location = get_turf(src) + var/datum/effect/smoke_spread/chem/S = new + S.attach(location) + playsound(location, 'sound/effects/smoke.ogg', 50, 1, -3) + if(S) + S.set_up(reagents, smoke_radius, 0, location) + if(!notified) + notified = TRUE + S.start() + reagents.clear_reagents() + activate_pin(2) + if(3) + set_pin_data(IC_OUTPUT, 2, weakref(src)) + push_data() + +/obj/item/integrated_circuit/reagent/injector + name = "integrated hypo-injector" + desc = "This scary looking thing is able to pump liquids into, or suck liquids out of, whatever it's pointed at." + icon_state = "injector" + extended_desc = "This autoinjector can push up to 30 units of 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 who is not carrying it in their pockets or belt, they cannot be wearing thick clothing. Negative given amounts makes the injector suck out reagents instead." + + atom_flags = ATOM_FLAG_OPEN_CONTAINER + 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, + "push ref" = IC_PINTYPE_PULSE_IN + + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + power_draw_per_use = 15 + var/direction_mode = IC_REAGENTS_INJECT + var/transfer_amount = 10 + var/busy = FALSE + +/obj/item/integrated_circuit/reagent/injector/on_reagent_change(changetype) + push_vol() + +/obj/item/integrated_circuit/reagent/injector/on_data_written() + var/new_amount = get_pin_data(IC_INPUT, 2) + if(new_amount < 0) + new_amount = -new_amount + direction_mode = IC_REAGENTS_DRAW + else + direction_mode = IC_REAGENTS_INJECT + if(isnum(new_amount)) + new_amount = clamp(new_amount, 0, volume) + transfer_amount = new_amount + + +/obj/item/integrated_circuit/reagent/injector/do_work(ord) + switch(ord) + if(1) + inject() + if(4) + set_pin_data(IC_OUTPUT, 2, weakref(src)) + push_data() + +/obj/item/integrated_circuit/reagent/injector/proc/target_nearby(weakref/target) + var/mob/living/L = target.resolve() + if(!L || get_dist(src,L) > 1) + return + return L + +/obj/item/integrated_circuit/reagent/injector/proc/inject_after(weakref/target) + busy = FALSE + var/mob/living/L = target_nearby(target) + if(!L) + activate_pin(3) + return + var/atom/movable/acting_object = get_object() + log_admin("[key_name(L)] was successfully injected with " + reagents.get_reagents() + " by \the [acting_object]") + L.visible_message(SPAN_WARNING("\The [acting_object] injects [L] with its needle!"), \ + SPAN_WARNING("\The [acting_object] injects you with its needle!")) + reagents.trans_to_mob(L, transfer_amount, CHEM_BLOOD) + activate_pin(2) + +/obj/item/integrated_circuit/reagent/injector/proc/draw_after(weakref/target, amount) + busy = FALSE + var/mob/living/carbon/C = target_nearby(target) + if(!C) + activate_pin(3) + return + var/atom/movable/acting_object = get_object() + + C.visible_message(SPAN_WARNING("\The [acting_object] draws blood from \the [C]"), + SPAN_WARNING("\The [acting_object] draws blood from you.") + ) + C.take_blood(src, amount) + activate_pin(2) + + +/obj/item/integrated_circuit/reagent/injector/proc/inject() + 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) + var/atom/movable/acting_object = get_object() + + if(busy || !check_target(AM)) + activate_pin(3) + return + + if(!AM.reagents) + activate_pin(3) + return + + if(direction_mode == IC_REAGENTS_INJECT) + if(!reagents.total_volume || !AM.reagents || !AM.reagents.get_free_space()) + activate_pin(3) + return + + if(isliving(AM)) + var/mob/living/L = AM + var/injection_status = L.can_inject(null, BP_CHEST) + if(L.isEquipped(assembly, slot_l_hand)) + injection_status = L.can_inject(null, BP_L_HAND) + if(L.isEquipped(assembly, slot_r_hand)) + injection_status = L.can_inject(null, BP_R_HAND) + if(L.isEquipped(assembly, slot_l_store) || L.isEquipped(assembly, slot_r_store) || L.isEquipped(assembly, slot_belt)) + injection_status = L.can_inject(null, BP_CHEST, ignore_thick_clothing = TRUE) //The injector has been put under the thick layer + log_world("Injection status? [injection_status]") + var/injection_delay = 3 SECONDS + if(injection_status == INJECTION_PORT) + injection_delay += INJECTION_PORT_DELAY + if(!injection_status) + activate_pin(3) + return + //Always log attemped injections for admins + log_admin("[key_name(L)] is getting injected with " + reagents.get_reagents() + " by \the [acting_object]") + L.visible_message(SPAN_DANGER("\The [acting_object] is trying to inject [L]!"), \ + SPAN_DANGER("\The [acting_object] is trying to inject you!")) + busy = TRUE + addtimer(new Callback(src, .proc/inject_after, weakref(L)), injection_delay) + return + else + if(!AM.is_open_container()) + activate_pin(3) + return + + + reagents.trans_to(AM, transfer_amount) + + else if(direction_mode == IC_REAGENTS_DRAW) + if(reagents.total_volume >= reagents.maximum_volume) + acting_object.visible_message("\The [acting_object] tries to draw from [AM], but the injector is full.") + activate_pin(3) + return + + var/tramount = abs(transfer_amount) + + if(istype(AM, /mob/living/carbon)) + var/mob/living/carbon/C = AM + var/injection_status = C.can_inject(null, BP_CHEST) + if(C.isEquipped(assembly, slot_l_hand)) + injection_status = C.can_inject(null, BP_L_HAND) + if(C.isEquipped(assembly, slot_r_hand)) + injection_status = C.can_inject(null, BP_R_HAND) + if(C.isEquipped(assembly, slot_l_store) || C.isEquipped(assembly, slot_r_store) || C.isEquipped(assembly, slot_belt)) + injection_status = C.can_inject(null, BP_CHEST, ignore_thick_clothing = TRUE) + var/injection_delay = 3 SECONDS + if(injection_status == INJECTION_PORT) + injection_delay += INJECTION_PORT_DELAY + if(istype(C, /mob/living/carbon/slime) || !C.dna || !injection_status) + activate_pin(3) + return + C.visible_message(SPAN_DANGER("\The [acting_object] is trying to take a blood sample from [C]!"), \ + SPAN_DANGER("\The [acting_object] is trying to take a blood sample from you!")) + busy = TRUE + addtimer(new Callback(src, .proc/draw_after, weakref(C), tramount), injection_delay) + return + + else + if(!AM.reagents.total_volume) + acting_object.visible_message(SPAN_NOTICE("\The [acting_object] tries to draw from [AM], but it is empty!")) + activate_pin(3) + return + + if(!AM.is_open_container()) + activate_pin(3) + return + tramount = min(tramount, AM.reagents.total_volume) + 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 adjacent 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) + outputs = list() + activators = list("transfer reagents" = IC_PINTYPE_PULSE_IN, "on transfer" = IC_PINTYPE_PULSE_OUT) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + var/transfer_amount = 10 + var/direction_mode = IC_REAGENTS_INJECT + power_draw_per_use = 10 + +/obj/item/integrated_circuit/reagent/pump/on_data_written() + var/new_amount = get_pin_data(IC_INPUT, 3) + if(new_amount < 0) + new_amount = -new_amount + direction_mode = IC_REAGENTS_DRAW + else + direction_mode = IC_REAGENTS_INJECT + if(isnum(new_amount)) + new_amount = clamp(new_amount, 0, 50) + transfer_amount = new_amount + +/obj/item/integrated_circuit/reagent/pump/do_work() + 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) + + // Check for invalid input. + if(!check_target(source) || !check_target(target)) + return + + // 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(!source.is_open_container()) + return + + source.reagents.trans_to(target, transfer_amount) + activate_pin(2) + +/obj/item/integrated_circuit/reagent/storage + cooldown_per_use = 1 + name = "reagent storage" + desc = "Stores liquid inside the device away from electrical components. It can store up to 60u." + icon_state = "reagent_storage" + extended_desc = "This is effectively an internal beaker." + + atom_flags = ATOM_FLAG_OPEN_CONTAINER + volume = 60 + + complexity = 4 + inputs = list() + outputs = list( + "volume used" = IC_PINTYPE_NUMBER, + "self reference" = IC_PINTYPE_REF + ) + activators = list("push ref" = IC_PINTYPE_PULSE_IN) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + + +/obj/item/integrated_circuit/reagent/storage/do_work() + set_pin_data(IC_OUTPUT, 2, weakref(src)) + push_data() + +/obj/item/integrated_circuit/reagent/storage/on_reagent_change(changetype) + push_vol() + +/obj/item/integrated_circuit/reagent/storage/big + name = "big reagent storage" + icon_state = "reagent_storage_big" + desc = "Stores liquid inside the device away from electrical components. Can store up to 180u." + + volume = 180 + + complexity = 16 + spawn_flags = IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/reagent/storage/cryo + name = "cryo reagent storage" + desc = "Stores liquid inside the device away from electrical components. It can store up to 60u. This will also prevent reactions." + icon_state = "reagent_storage_cryo" + extended_desc = "This is effectively an internal cryo beaker." + + atom_flags = ATOM_FLAG_NO_TEMP_CHANGE | ATOM_FLAG_OPEN_CONTAINER | ATOM_FLAG_NO_REACT + complexity = 8 + spawn_flags = IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/reagent/storage/grinder + name = "reagent grinder" + desc = "This is a reagent grinder. It accepts a ref to something, and refines it into reagents. It cannot grind materials. It can store up to 100u." + icon_state = "blender" + extended_desc = "" + inputs = list( + "target" = IC_PINTYPE_REF, + ) + outputs = list( + "volume used" = IC_PINTYPE_NUMBER, + "self reference" = IC_PINTYPE_REF + ) + activators = list( + "grind" = IC_PINTYPE_PULSE_IN, + "on grind" = IC_PINTYPE_PULSE_OUT, + "on fail" = IC_PINTYPE_PULSE_OUT, + "push ref" = IC_PINTYPE_PULSE_IN + ) + volume = 100 + power_draw_per_use = 150 + complexity = 16 + spawn_flags = IC_SPAWN_RESEARCH + + +/obj/item/integrated_circuit/reagent/storage/grinder/do_work(ord) + switch(ord) + if(1) + grind() + if(4) + set_pin_data(IC_OUTPUT, 2, weakref(src)) + push_data() + +/obj/item/integrated_circuit/reagent/storage/grinder/proc/grind() + if(reagents.total_volume >= reagents.maximum_volume) + activate_pin(3) + return FALSE + var/obj/item/I = get_pin_data_as_type(IC_INPUT, 1, /obj/item) + + if(isnull(I)) + return FALSE + + if(!I.reagents || !I.reagents.total_volume) + activate_pin(3) + return FALSE + + I.reagents.trans_to(src,I.reagents.total_volume) + if(!I.reagents.total_volume) + qdel(I) + + activate_pin(2) + return FALSE + + + +/obj/item/integrated_circuit/reagent/storage/scan + name = "reagent scanner" + desc = "Stores liquid inside the device away from electrical components. It can store up to 60u. On pulse this beaker will send list of contained reagents." + icon_state = "reagent_scan" + extended_desc = "Mostly useful for filtering reagents." + + 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, + "push ref" = IC_PINTYPE_PULSE_IN + ) + spawn_flags = IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/reagent/storage/scan/do_work(ord) + switch(ord) + if(1) + var/cont[0] + for(var/datum/reagent/RE in reagents.reagent_list) + cont += RE.name + set_pin_data(IC_OUTPUT, 3, cont) + push_data() + if(2) + set_pin_data(IC_OUTPUT, 2, weakref(src)) + push_data() + +/obj/item/integrated_circuit/reagent/filter + name = "reagent filter" + desc = "Filters 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 to its target. \ + If the amount in the fourth pin is positive, it will move all reagents except those in the unwanted list. \ + If the amount in the fourth pin is negative, it will only move the reagents in the wanted list. \ + The third pin determines how many reagents are moved per pulse, between 0 and 50. Amount is given for each separate reagent." + + 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 + ) + outputs = list() + activators = list( + "transfer reagents" = IC_PINTYPE_PULSE_IN, + "on transfer" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + var/transfer_amount = 10 + var/direction_mode = IC_REAGENTS_INJECT + power_draw_per_use = 10 + +/obj/item/integrated_circuit/reagent/filter/on_data_written() + var/new_amount = get_pin_data(IC_INPUT, 3) + if(new_amount < 0) + new_amount = -new_amount + direction_mode = IC_REAGENTS_DRAW + else + direction_mode = IC_REAGENTS_INJECT + if(isnum(new_amount)) + new_amount = clamp(new_amount, 0, 50) + transfer_amount = new_amount + +/obj/item/integrated_circuit/reagent/filter/do_work() + 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) + + // Check for invalid input. + if(!check_target(source) || !check_target(target)) + return + + if(!source.reagents || !target.reagents) + return + + if(!source.is_open_container() || istype(source, /mob)) + 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.name in demand) + source.reagents.trans_type_to(target, G.type, transfer_amount) + else + if(!(G.name in demand)) + source.reagents.trans_type_to(target, G.type, transfer_amount) + activate_pin(2) + push_data() + +// This is an input circuit because attackby_react is only called for input circuits +/obj/item/integrated_circuit/input/funnel + category_text = "Reagent" + name = "reagent funnel" + desc = "A funnel with a small pump that lets you refill an internal reagent storage." + icon_state = "reagent_funnel" + + inputs = list( + "target" = IC_PINTYPE_REF + ) + activators = list( + "on transfer" = IC_PINTYPE_PULSE_OUT + ) + + unacidable = TRUE + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + complexity = 4 + power_draw_per_use = 5 + +/obj/item/integrated_circuit/input/funnel/attackby_react(obj/item/I, mob/living/user, intent) + var/atom/movable/target = get_pin_data_as_type(IC_INPUT, 1, /atom/movable) + var/obj/item/reagent_containers/container = I + + if(!check_target(target)) + return FALSE + + if(!istype(container)) + return FALSE + + // Messages are provided by standard_pour_into + if(container.standard_pour_into(user, target)) + activate_pin(1) + return TRUE + + return FALSE + +// Most of this is just chemical heater code refitted for ICs +/obj/item/integrated_circuit/reagent/temp + inputs = list( + "target temperature" = IC_PINTYPE_NUMBER + ) + outputs = list( + "volume used" = IC_PINTYPE_NUMBER, + "temperature" = IC_PINTYPE_NUMBER, + "enabled" = IC_PINTYPE_BOOLEAN, + "self reference" = IC_PINTYPE_REF + ) + activators = list( + "toggle" = IC_PINTYPE_PULSE_IN, + "on toggle" = IC_PINTYPE_PULSE_OUT, + "push ref" = IC_PINTYPE_PULSE_IN + ) + + atom_flags = ATOM_FLAG_OPEN_CONTAINER + complexity = 12 + cooldown_per_use = 1 + power_draw_per_use = 50 + volume = 30 + + var/active = 0 + var/min_temp = 40 CELSIUS + var/max_temp = 200 CELSIUS + var/heating_power = 5 + var/target_temp = T20C + var/last_temperature = 0 + var/mode = IC_HEATER_MODE_HEAT + +/obj/item/integrated_circuit/reagent/temp/Initialize() + . = ..() + + set_pin_data(IC_OUTPUT, 2, temperature - T0C) + push_data() + +/obj/item/integrated_circuit/reagent/temp/do_work(ord) + switch(ord) + if(1) + target_temp = get_pin_data(IC_INPUT, 1) + if(isnull(target_temp)) + return + + // +/- T0C to convert to/from kelvin + target_temp = clamp(target_temp + T0C, min_temp, max_temp) + set_pin_data(IC_INPUT, 1, target_temp - T0C) + + active = !active + set_pin_data(IC_OUTPUT, 3, active) + push_data() + activate_pin(2) + + // begin processing temperature + if(active) + QUEUE_TEMPERATURE_ATOMS(src) + if(3) + set_pin_data(IC_OUTPUT, 4, weakref(src)) + push_data() + +/obj/item/integrated_circuit/reagent/temp/on_reagent_change() + push_vol() + +/obj/item/integrated_circuit/reagent/temp/power_fail() + active = 0 + +/obj/item/integrated_circuit/reagent/temp/ProcessAtomTemperature() + if(!active) + return PROCESS_KILL + + last_temperature = temperature + + if(mode == IC_HEATER_MODE_HEAT && temperature < target_temp) + temperature = min(temperature + heating_power, max_temp) + else if(mode == IC_HEATER_MODE_COOL && temperature > target_temp) + temperature = max(temperature - heating_power, min_temp) + + if(temperature != last_temperature) + // Lost power + if(!check_power()) + power_fail() + return ..() + + set_pin_data(IC_OUTPUT, 2, temperature - T0C) + push_data() + + return TRUE + +/obj/item/integrated_circuit/reagent/temp/heater + name = "reagent heater" + desc = "A small reagent container capable of heating reagents. It can hold up to 30u." + icon_state = "reagent_heater" + extended_desc = "This is effectively an internal beaker. It has a heating coil wrapped around it, which allows it to heat the contents of the beaker. Temperature is given in celsius." + + spawn_flags = IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/reagent/temp/cooler + name = "reagent cooler" + desc = "A small reagent container capable of cooling reagents. It can hold up to 30u." + icon_state = "reagent_cooler" + extended_desc = "This is effectively an internal beaker. It has a cooling mechanism wrapped around it, which allows it to cool the contents of the beaker. Temperature is given in celsius." + + spawn_flags = IC_SPAWN_RESEARCH + + min_temp = -80 CELSIUS + max_temp = 30 CELSIUS + mode = IC_HEATER_MODE_COOL + +//undefs + +#undef IC_HEATER_MODE_HEAT +#undef IC_HEATER_MODE_COOL +#undef IC_REAGENTS_DRAW +#undef IC_REAGENTS_INJECT