diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index dcc231295d4..dad2492e4d1 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -138,6 +138,14 @@
/proc/sanitize_old(var/t,var/list/repl_chars = list("\n"="#","\t"="#"))
return html_encode(replace_characters(t,repl_chars))
+// Truncates text to limit if necessary.
+/proc/dd_limittext(message, length)
+ var/size = length(message)
+ if (size <= length)
+ return message
+ else
+ return copytext(message, 1, length + 1)
+
/*
* Text searches
*/
diff --git a/code/game/objects/items/weapons/circuitboards/computer/computer.dm b/code/game/objects/items/weapons/circuitboards/computer/computer.dm
index d71e2dcb074..bbb9d42730b 100644
--- a/code/game/objects/items/weapons/circuitboards/computer/computer.dm
+++ b/code/game/objects/items/weapons/circuitboards/computer/computer.dm
@@ -1,5 +1,5 @@
#ifndef T_BOARD
-#error T_BOARD macro is not defined but we need it!
+#error T_BOARD macro is not defined but we need it!
#endif
/obj/item/weapon/circuitboard/message_monitor
@@ -60,11 +60,11 @@
/obj/item/weapon/circuitboard/stationalert_engineering
name = T_BOARD("station alert console (engineering)")
build_path = /obj/machinery/computer/station_alert
-
+
/obj/item/weapon/circuitboard/stationalert_security
name = T_BOARD("station alert console (security)")
build_path = /obj/machinery/computer/station_alert
-
+
/obj/item/weapon/circuitboard/stationalert_all
name = T_BOARD("station alert console (all)")
build_path = /obj/machinery/computer/station_alert/all
@@ -196,3 +196,8 @@
name = T_BOARD("RCON remote control console")
build_path = /obj/machinery/computer/rcon
origin_tech = "programming=4;engineering=3;powerstorage=5"
+
+/obj/item/weapon/circuitboard/telesci_console
+ name = T_BOARD("Telescience Console")
+ build_path = /obj/machinery/computer/telescience
+ origin_tech = "programming=3;bluespace=2"
\ No newline at end of file
diff --git a/code/game/objects/items/weapons/circuitboards/machinery/research.dm b/code/game/objects/items/weapons/circuitboards/machinery/research.dm
index 0ab0822e4bb..2faeafa1b79 100644
--- a/code/game/objects/items/weapons/circuitboards/machinery/research.dm
+++ b/code/game/objects/items/weapons/circuitboards/machinery/research.dm
@@ -1,5 +1,5 @@
#ifndef T_BOARD
-#error T_BOARD macro is not defined but we need it!
+#error T_BOARD macro is not defined but we need it!
#endif
obj/item/weapon/circuitboard/rdserver
@@ -62,3 +62,14 @@ obj/item/weapon/circuitboard/rdserver
"/obj/item/weapon/stock_parts/manipulator" = 1,
"/obj/item/weapon/stock_parts/micro_laser" = 1,
"/obj/item/weapon/stock_parts/console_screen" = 1)
+
+/obj/item/weapon/circuitboard/telesci_pad
+ name = T_BOARD("telepad")
+ build_path = "/obj/machinery/telepad"
+ board_type = "machine"
+ origin_tech = "programming=4;engineering=3;materials=3;bluespace=4"
+ req_components = list(
+ "/obj/item/bluespace_crystal" = 2,
+ "/obj/item/weapon/stock_parts/capacitor" = 1,
+ "/obj/item/stack/cable_coil" = 1,
+ "/obj/item/weapon/stock_parts/console_screen" = 1)
\ No newline at end of file
diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm
index 3ddb262453a..fdcd0e67ed5 100644
--- a/code/modules/research/designs.dm
+++ b/code/modules/research/designs.dm
@@ -309,6 +309,18 @@ datum/design/circuit/aifixer
req_tech = list("programming" = 3, "biotech" = 2)
build_path = /obj/item/weapon/circuitboard/aifixer
+datum/design/circuit/telesci_console
+ name = "telepad control console"
+ id = "telesci_console"
+ req_tech = list("programming" = 3, "bluespace" = 2)
+ build_path = /obj/item/weapon/circuitboard/telesci_console
+
+datum/design/circuit/telepad
+ name = "telepad board"
+ id = "telepad"
+ req_tech = list("programming" = 4, "bluespace" = 4, "materials" = 3, "engineering" = 3)
+ build_path = /obj/item/weapon/circuitboard/telesci_pad
+
///////////////////////////////////
/////////Shield Generators/////////
///////////////////////////////////
@@ -1434,7 +1446,6 @@ datum/design/item/bag_holding
reliability_base = 80
build_path = /obj/item/weapon/storage/backpack/holding
-/*
datum/design/bluespace_crystal
name = "Artificial bluespace crystal"
desc = "A small blue crystal with mystical properties."
@@ -1443,8 +1454,7 @@ datum/design/bluespace_crystal
build_type = PROTOLATHE
materials = list("$gold" = 1500, "$diamond" = 3000, "$phoron" = 1500)
reliability_base = 100
- build_path = /obj/item/bluespace_crystal/artificial"
-*/
+ build_path = /obj/item/bluespace_crystal/artificial
/////////////////////////////////////////
/////////////////HUDs////////////////////
diff --git a/code/modules/telesci/bscrystal.dm b/code/modules/telesci/bscrystal.dm
new file mode 100644
index 00000000000..14eb5569590
--- /dev/null
+++ b/code/modules/telesci/bscrystal.dm
@@ -0,0 +1,46 @@
+// 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 = "bluespace=4;materials=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)
+
+
+/obj/item/bluespace_crystal/attack_self(mob/user)
+ user.visible_message("[user] crushes [src]!", "You crush [src]!")
+ PoolOrNew(/obj/effect/effect/sparks, loc)
+ playsound(src.loc, "sparks", 50, 1)
+ blink_mob(user)
+ user.unEquip(src)
+ qdel(src)
+
+/obj/item/bluespace_crystal/proc/blink_mob(mob/living/L)
+ do_teleport(L, get_turf(L), blink_range, asoundin = 'sound/effects/phasein.ogg')
+
+/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)
+ PoolOrNew(/obj/effect/effect/sparks, T)
+ playsound(src.loc, "sparks", 50, 1)
+ if(isliving(hit_atom))
+ blink_mob(hit_atom)
+ 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 = "bluespace=2"
+ blink_range = 4 // Not as good as the organic stuff!
\ No newline at end of file
diff --git a/code/modules/telesci/gps.dm b/code/modules/telesci/gps.dm
new file mode 100644
index 00000000000..6271a79a6dd
--- /dev/null
+++ b/code/modules/telesci/gps.dm
@@ -0,0 +1,81 @@
+var/list/GPS_list = list()
+/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 = "programming=2;engineering=2"
+ var/gpstag = "COM0"
+ var/emped = 0
+ var/turf/locked_location
+
+/obj/item/device/gps/New()
+ ..()
+ GPS_list.Add(src)
+ name = "global positioning system ([gpstag])"
+ overlays += "working"
+
+/obj/item/device/gps/Destroy()
+ GPS_list.Remove(src)
+ return ..()
+
+/obj/item/device/gps/emp_act(severity)
+ emped = 1
+ overlays -= "working"
+ overlays += "emp"
+ spawn(300)
+ emped = 0
+ overlays -= "emp"
+ overlays += "working"
+
+/obj/item/device/gps/attack_self(mob/user)
+
+ var/obj/item/device/gps/t = ""
+ var/gps_window_height = 110 + GPS_list.len * 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 && locked_location.loc)
+ t += "
Bluespace coordinates saved: [locked_location.loc]"
+ gps_window_height += 20
+
+ for(var/obj/item/device/gps/G in GPS_list)
+ var/turf/pos = get_turf(G)
+ var/area/gps_area = get_area(G)
+ var/tracked_gpstag = G.gpstag
+ if(G.emped == 1)
+ 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"
+ gpstag = "SCI0"
+
+/obj/item/device/gps/engineering
+ icon_state = "gps-e"
+ gpstag = "ENG0"
+
+/obj/item/device/gps/mining
+ icon_state = "gps-m"
+ gpstag = "MINE0"
+ 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/code/modules/telesci/telepad.dm b/code/modules/telesci/telepad.dm
new file mode 100644
index 00000000000..5e921a6466d
--- /dev/null
+++ b/code/modules/telesci/telepad.dm
@@ -0,0 +1,148 @@
+///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 = 1
+ use_power = 1
+ idle_power_usage = 200
+ active_power_usage = 5000
+ var/efficiency
+
+/obj/machinery/telepad/New()
+ ..()
+ component_parts = list()
+ component_parts += new /obj/item/weapon/circuitboard/telesci_pad(null)
+ component_parts += new /obj/item/bluespace_crystal/artificial(null)
+ component_parts += new /obj/item/bluespace_crystal/artificial(null)
+ component_parts += new /obj/item/weapon/stock_parts/capacitor(null)
+ component_parts += new /obj/item/weapon/stock_parts/console_screen(null)
+ component_parts += new /obj/item/stack/cable_coil(null, 1)
+ RefreshParts()
+
+/obj/machinery/telepad/RefreshParts()
+ var/E
+ for(var/obj/item/weapon/stock_parts/capacitor/C in component_parts)
+ E += C.rating
+ efficiency = E
+
+/obj/machinery/telepad/attackby(obj/item/I, mob/user, params)
+ if(default_deconstruction_screwdriver(user, "pad-idle-o", "pad-idle", I))
+ return
+
+ if(panel_open)
+ if(istype(I, /obj/item/device/multitool))
+ var/obj/item/device/multitool/M = I
+ M.buffer = src
+ user << "You save the data in the [I.name]'s buffer."
+
+ default_deconstruction_crowbar(I)
+
+
+//CARGO TELEPAD//
+/obj/machinery/telepad_cargo
+ name = "cargo telepad"
+ desc = "A telepad used by the Rapid Crate Sender."
+ icon = 'icons/obj/telescience.dmi'
+ icon_state = "pad-idle"
+ anchored = 1
+ use_power = 1
+ idle_power_usage = 20
+ active_power_usage = 500
+ var/stage = 0
+
+/obj/machinery/telepad_cargo/attackby(obj/item/weapon/W, mob/user, params)
+ if(istype(W, /obj/item/weapon/wrench))
+ anchored = 0
+ playsound(src, 'sound/items/Ratchet.ogg', 50, 1)
+ if(anchored)
+ anchored = 0
+ user << "\The [src] can now be moved."
+ else if(!anchored)
+ anchored = 1
+ user << "\The [src] is now secured."
+ if(istype(W, /obj/item/weapon/screwdriver))
+ if(stage == 0)
+ playsound(src, 'sound/items/Screwdriver.ogg', 50, 1)
+ user << "You unscrew the telepad's tracking beacon."
+ stage = 1
+ else if(stage == 1)
+ playsound(src, 'sound/items/Screwdriver.ogg', 50, 1)
+ user << "You screw in the telepad's tracking beacon."
+ stage = 0
+ if(istype(W, /obj/item/weapon/weldingtool) && stage == 1)
+ playsound(src, 'sound/items/Welder.ogg', 50, 1)
+ user << "You disassemble the telepad."
+ new /obj/item/stack/material/steel(get_turf(src))
+ new /obj/item/stack/material/glass(get_turf(src))
+ qdel(src)
+
+///TELEPAD CALLER///
+/obj/item/device/telepad_beacon
+ name = "telepad beacon"
+ desc = "Use to warp in a cargo telepad."
+ icon = 'icons/obj/radio.dmi'
+ icon_state = "beacon"
+ item_state = "signaler"
+ origin_tech = "bluespace=3"
+
+/obj/item/device/telepad_beacon/attack_self(mob/user)
+ if(user)
+ user << "Locked In"
+ new /obj/machinery/telepad_cargo(user.loc)
+ playsound(src, 'sound/effects/pop.ogg', 100, 1, 1)
+ qdel(src)
+ return
+
+///HANDHELD TELEPAD USER///
+/obj/item/weapon/rcs
+ name = "rapid-crate-sender (RCS)"
+ desc = "Use this to send crates and closets to cargo telepads."
+ icon = 'icons/obj/telescience.dmi'
+ icon_state = "rcs"
+ flags = CONDUCT
+ force = 10
+ throwforce = 10
+ throw_speed = 2
+ throw_range = 5
+ var/rcharges = 10
+ var/obj/machinery/pad = null
+ var/last_charge = 30
+ var/mode = 0
+ var/rand_x = 0
+ var/rand_y = 0
+ var/emagged = 0
+ var/teleporting = 0
+
+/obj/item/weapon/rcs/examine(mob/user)
+ ..()
+ user << "There are [rcharges] charge\s left."
+
+/obj/item/weapon/rcs/process()
+ if(rcharges > 10)
+ rcharges = 10
+ if(last_charge == 0)
+ rcharges++
+ last_charge = 30
+ else
+ last_charge--
+
+/obj/item/weapon/rcs/attack_self(mob/user)
+ if(emagged)
+ if(mode == 0)
+ mode = 1
+ playsound(src.loc, 'sound/effects/pop.ogg', 50, 0)
+ user << "The telepad locator has become uncalibrated."
+ else
+ mode = 0
+ playsound(src.loc, 'sound/effects/pop.ogg', 50, 0)
+ user << "You calibrate the telepad locator."
+
+/obj/item/weapon/rcs/attackby(var/obj/item/O, var/mob/user)
+ if (istype(O, /obj/item/weapon/card/emag) && !emagged)
+ emagged = 1
+ var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
+ s.set_up(5, 1, src)
+ s.start()
+ user << "You emag the RCS. Click on it to toggle between modes."
diff --git a/code/modules/telesci/telesci_computer.dm b/code/modules/telesci/telesci_computer.dm
new file mode 100644
index 00000000000..db1c7b335a8
--- /dev/null
+++ b/code/modules/telesci/telesci_computer.dm
@@ -0,0 +1,368 @@
+/obj/machinery/computer/telescience
+ name = "\improper Telepad Control Console"
+ desc = "Used to teleport objects to and from the telescience telepad."
+ icon_state = "teleport"
+ circuit = /obj/item/weapon/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/rotation_off
+ //var/angle_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 = 3
+ var/max_crystals = 4
+ var/list/crystals = list()
+ var/obj/item/device/gps/inserted_gps
+
+/obj/machinery/computer/telescience/New()
+ ..()
+ recalibrate()
+
+/obj/machinery/computer/telescience/Destroy()
+ eject()
+ if(inserted_gps)
+ inserted_gps.loc = loc
+ inserted_gps = null
+ return ..()
+
+/obj/machinery/computer/telescience/examine(mob/user)
+ ..()
+ user << "There are [crystals.len ? crystals.len : "no"] bluespace crystal\s in the crystal slots."
+
+/obj/machinery/computer/telescience/initialize()
+ ..()
+ for(var/i = 1; i <= starting_crystals; i++)
+ crystals += new /obj/item/bluespace_crystal/artificial(null) // starting crystals
+
+/obj/machinery/computer/telescience/attackby(obj/item/W, mob/user, params)
+ if(istype(W, /obj/item/bluespace_crystal))
+ if(crystals.len >= max_crystals)
+ user << "There are not enough crystal slots."
+ return
+ if(!user.drop_item())
+ return
+ crystals += W
+ W.loc = 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.loc = 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
+ user << "You upload the data from the [W.name]'s buffer."
+ else
+ ..()
+
+/obj/machinery/computer/telescience/attack_ai(mob/user)
+ src.attack_hand(user)
+
+/obj/machinery/computer/telescience/attack_hand(mob/user)
+ if(..())
+ 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 += "