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