diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index 0b535a44c3..8da220bf67 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -327,3 +327,41 @@ proc/isInSight(var/atom/A, var/atom/B)
spawn(delay)
for(var/client/C in group)
C.screen -= O
+
+datum/projectile_data
+ var/src_x
+ var/src_y
+ var/time
+ var/distance
+ var/power_x
+ var/power_y
+ var/dest_x
+ var/dest_y
+
+/datum/projectile_data/New(var/src_x, var/src_y, var/time, var/distance, \
+ var/power_x, var/power_y, var/dest_x, var/dest_y)
+ src.src_x = src_x
+ src.src_y = src_y
+ src.time = time
+ src.distance = distance
+ src.power_x = power_x
+ src.power_y = power_y
+ src.dest_x = dest_x
+ src.dest_y = dest_y
+
+/proc/projectile_trajectory(var/src_x, var/src_y, var/rotation, var/angle, var/power)
+
+ // returns the destination (Vx,y) that a projectile shot at [src_x], [src_y], with an angle of [angle],
+ // rotated at [rotation] and with the power of [power]
+ // Thanks to VistaPOWA for this function
+
+ var/power_x = power * cos(angle)
+ var/power_y = power * sin(angle)
+ var/time = 2* power_y / 10 //10 = g
+
+ var/distance = time * power_x
+
+ var/dest_x = src_x + distance*sin(rotation);
+ var/dest_y = src_y + distance*cos(rotation);
+
+ return new /datum/projectile_data(src_x, src_y, time, distance, power_x, power_y, dest_x, dest_y)
diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm
index e861826e0f..f2bd04a785 100644
--- a/code/modules/research/designs.dm
+++ b/code/modules/research/designs.dm
@@ -1575,6 +1575,16 @@ datum/design/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."
+ id = "bluespace_crystal"
+ req_tech = list("bluespace" = 5, "materials" = 7)
+ build_type = PROTOLATHE
+ materials = list("$gold" = 1500, "$diamond" = 3000, "$plasma" = 1500)
+ reliability_base = 100
+ 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 0000000000..d9e968f4da
--- /dev/null
+++ b/code/modules/telesci/bscrystal.dm
@@ -0,0 +1,38 @@
+// 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(var/mob/user)
+ blink_mob(user)
+ user.drop_item()
+ user.visible_message("[user] crushes the [src]!")
+ del(src)
+
+/obj/item/bluespace_crystal/proc/blink_mob(var/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(isliving(hit_atom))
+ blink_mob(hit_atom)
+ del(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
index 19b107a503..28555a2dce 100644
--- a/code/modules/telesci/gps.dm
+++ b/code/modules/telesci/gps.dm
@@ -19,6 +19,10 @@ var/list/GPS_list = list()
/obj/item/device/gps/Del()
GPS_list.Remove(src)
..()
+/obj/item/device/gps/Del()
+ GPS_list.Remove(src)
+ ..()
+
/obj/item/device/gps/emp_act(severity)
emped = 1
overlays -= "working"
@@ -29,6 +33,7 @@ var/list/GPS_list = list()
overlays += "working"
/obj/item/device/gps/attack_self(mob/user as mob)
+
var/obj/item/device/gps/t = ""
if(emped)
t += "ERROR"
@@ -54,14 +59,11 @@ var/list/GPS_list = list()
..()
if(href_list["tag"] )
var/a = input("Please enter desired tag.", name, gpstag) as text
- a = copytext(sanitize(a), 1, 20)
- if(length(a) != 4)
- usr << " The tag must be four letters long!"
- return
- else
+ a = uppertext(copytext(sanitize(a), 1, 5))
+ if(src.loc == usr)
gpstag = a
name = "global positioning system ([gpstag])"
- return
+ attack_self(usr)
/obj/item/device/gps/science
icon_state = "gps-s"
diff --git a/code/modules/telesci/telesci_computer.dm b/code/modules/telesci/telesci_computer.dm
index a42e617f1c..85232a4106 100644
--- a/code/modules/telesci/telesci_computer.dm
+++ b/code/modules/telesci/telesci_computer.dm
@@ -1,27 +1,51 @@
/obj/machinery/computer/telescience
- name = "telepad control console"
+ name = "\improper Telepad Control Console"
desc = "Used to teleport objects to and from the telescience telepad."
icon_state = "teleport"
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/x_off // X offset
- var/y_off // Y offset
- var/x_co = 1 // X coordinate
- var/y_co = 1 // Y coordinate
- var/z_co = 1 // Z coordinate
+ var/datum/projectile_data/last_tele_data = null
+ var/z_co = 1
+ var/power_off
+ var/rotation_off
+ var/angle_off
+
+ var/rotation = 0
+ var/angle = 45
+ var/power
+
+ // Based on the power used
+ var/teleport_cooldown = 0
+ var/list/power_options = list(5, 10, 20, 25, 30, 40, 50, 80, 100) // every index requires a bluespace crystal
+ var/teleporting = 0
+ var/starting_crystals = 3
+ var/list/crystals = list()
/obj/machinery/computer/telescience/New()
..()
- teles_left = rand(8,12)
- x_off = rand(-10,10)
- y_off = rand(-10,10)
- initialize()
+ link_telepad()
+ recalibrate()
+
+/obj/machinery/computer/telescience/Del()
+ eject()
+ ..()
+
+/obj/machinery/computer/telescience/examine()
+ ..()
+ usr << "There are [crystals.len] bluespace crystals in the crystal ports."
/obj/machinery/computer/telescience/initialize()
..()
+ link_telepad()
+ for(var/i = 1; i <= starting_crystals; i++)
+ crystals += new /obj/item/bluespace_crystal/artificial(null) // starting crystals
+ power = power_options[1]
+
+/obj/machinery/computer/telescience/proc/link_telepad()
telepad = locate() in range(src, 7)
/obj/machinery/computer/telescience/update_icon()
@@ -35,220 +59,251 @@
icon_state = initial(icon_state)
stat &= ~NOPOWER
- /**
- * The ui_interact proc is used to open and update Nano UIs
- * If ui_interact is not used then the UI will not update correctly
- * ui_interact is currently defined for /atom/movable
- *
- * @param user /mob The mob who is interacting with this ui
- * @param ui_key string A string key to use for this ui. Allows for multiple unique uis on one obj/mob (defaut value "main")
- *
- * @return nothing
- */
-/obj/machinery/computer/telescience/ui_interact(mob/user, ui_key = "main")
- if(stat & (BROKEN|NOPOWER)) return
- if(user.stat || user.restrained()) return
-
- // this is the data which will be sent to the ui
- var/data[0]
- data["coordx"] = x_co
- data["coordy"] = y_co
- data["coordz"] = z_co
-
- var/datum/nanoui/ui = nanomanager.get_open_ui(user, src, ui_key)
- if (!ui)
- // the ui does not exist, so we'll create a new one
- ui = new(user, src, ui_key, "telescience_console.tmpl", name, 380, 110)
- // When the UI is first opened this is the data it will use
- ui.set_initial_data(data)
- ui.open()
- else
- // The UI is already open so push the new data to it
- ui.push_data(data)
- return
-
/obj/machinery/computer/telescience/attack_paw(mob/user)
user << "You are too primitive to use this computer."
return
+/obj/machinery/computer/telescience/attackby(obj/item/W, mob/user)
+ if(istype(W, /obj/item/bluespace_crystal))
+ if(crystals.len >= power_options.len)
+ user << "There are not enough crystal ports."
+ return
+ user.drop_item()
+ crystals += W
+ W.loc = null
+ user.visible_message("[user] inserts a [W] into the [src]'s crystal port.")
+ else
+ ..()
+
/obj/machinery/computer/telescience/attack_ai(mob/user)
- return src.attack_hand(user)
+ src.attack_hand(user)
-/obj/machinery/computer/telescience/attack_hand(mob/user as mob)
- if(stat & BROKEN)
+/obj/machinery/computer/telescience/attack_hand(mob/user)
+ if(..())
return
+ interact(user)
- ui_interact(user)
+/obj/machinery/computer/telescience/interact(mob/user)
+
+ var/t = "
[temp_msg]
"
+ t += "Set Bearing"
+ t += "[rotation]°
"
+ t += "Set Elevation"
+ t += "[angle]°
"
+ t += "Set Power"
+ t += ""
+
+ for(var/i = 1; i <= power_options.len; i++)
+ if(crystals.len < 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/L = get_turf(E)
var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
- s.set_up(5, 1, L)
+ s.set_up(5, 1, get_turf(telepad))
s.start()
else
return
/obj/machinery/computer/telescience/proc/telefail()
- if(prob(50))
- sparks()
- for(var/mob/O in hearers(src, null))
- O.show_message("\red The telepad weakly fizzles.", 2)
- return
- if(prob(5))
- // Irradiate everyone in telescience!
- for(var/obj/machinery/telepad/E in machines)
- var/L = get_turf(E)
- sparks()
- for(var/mob/living/carbon/human/M in viewers(L, null))
- M.apply_effect((rand(10, 20)), IRRADIATE, 0)
- M << "\red You feel strange."
- return
- if(prob(1))
- // AI CALL SHUTTLE I SAW RUNE, SUPER LOW CHANCE, CAN HARDLY HAPPEN
- for(var/mob/living/carbon/O in viewers(src, null))
- var/datum/game_mode/cult/temp = new
- O.show_message("\red The telepad flashes with a strange light, and you have a sudden surge of allegiance toward the true dark one!", 2)
- O.mind.make_Cultist()
- temp.grant_runeword(O)
- sparks()
- return
- if(prob(1))
- // VIVA LA FUCKING REVOLUTION BITCHES, SUPER LOW CHANCE, CAN HARDLY HAPPEN
- for(var/mob/living/carbon/O in viewers(src, null))
- O.show_message("\red The telepad flashes with a strange light, and you see all kind of images flash through your mind, of murderous things Nanotrasen has done, and you decide to rebel!", 2)
- O.mind.make_Rev()
- sparks()
- return
- if(prob(1))
- // The OH SHIT FUCK GOD DAMN IT LYNCH THE SCIENTISTS event.
- for(var/mob/living/carbon/O in viewers(src, null))
- O.show_message("\red The telepad changes colors rapidly, and opens a portal, and you see what your mind seems to think is the very threads that hold the pattern of the universe together, and a eerie sense of paranoia creeps into you.", 2)
- spacevine_infestation()
- sparks()
- return
- if(prob(5))
- // HOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONK
- for(var/mob/living/carbon/M in hearers(src, null))
- M << sound('sound/items/AirHorn.ogg')
- if(istype(M, /mob/living/carbon/human))
- var/mob/living/carbon/human/H = M
- if(istype(H.l_ear, /obj/item/clothing/ears/earmuffs) || istype(H.r_ear, /obj/item/clothing/ears/earmuffs))
- continue
- M << "HONK"
- M.sleeping = 0
- M.stuttering += 20
- M.ear_deaf += 30
- M.Weaken(3)
- if(prob(30))
- M.Stun(10)
- M.Paralyse(4)
- else
- M.make_jittery(500)
- sparks()
- return
- if(prob(1))
- // They did the mash! (They did the monster mash!) The monster mash! (It was a graveyard smash!)
- sparks()
- for(var/obj/machinery/telepad/E in machines)
- var/L = get_turf(E)
- var/blocked = list(/mob/living/simple_animal/hostile,
- /mob/living/simple_animal/hostile/alien/queen/large,
- /mob/living/simple_animal/hostile/retaliate,
- /mob/living/simple_animal/hostile/retaliate/clown,
- /mob/living/simple_animal/hostile/giant_spider/nurse)
- var/list/hostiles = typesof(/mob/living/simple_animal/hostile) - blocked
- playsound(L, 'sound/effects/phasein.ogg', 100, 1)
- for(var/mob/living/carbon/human/M in viewers(L, null))
- flick("e_flash", M.flash)
- var/chosen = pick(hostiles)
- var/mob/living/simple_animal/hostile/H = new chosen
- H.loc = L
- return
- return
+ sparks()
+ visible_message("The telepad weakly fizzles.")
return
/obj/machinery/computer/telescience/proc/doteleport(mob/user)
- var/trueX = (x_co + x_off)
- var/trueY = (y_co + y_off)
- trueX = Clamp(trueX, 1, world.maxx)
- trueY = Clamp(trueY, 1, world.maxy)
- if(telepad)
- var/turf/target = locate(trueX, trueY, z_co)
- var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
- s.set_up(5, 1, telepad)
- s.start()
- flick("pad-beam", telepad)
- user << "Teleport successful."
- var/sparks = get_turf(target)
- var/datum/effect/effect/system/spark_spread/y = new /datum/effect/effect/system/spark_spread
- y.set_up(5, 1, sparks)
- y.start()
- var/turf/source = target
- var/turf/dest = get_turf(telepad)
- if(sending)
- source = dest
- dest = target
- for(var/atom/movable/ROI in source)
- if(ROI.anchored) continue
- do_teleport(ROI, dest, 0)
+
+ if(teleport_cooldown > world.time)
+ temp_msg = "Telepad is recharging power.
Please wait [round((teleport_cooldown - world.time) / 10)] seconds."
return
- 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 + angle_off, 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)
+ 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 & NOPOWER)
+ return
+ teleporting = 0
+ teleport_cooldown = world.time + (power * 2)
+ teles_left -= 1
+
+ // use a lot of power
+ use_power(power * 10)
+
+ var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
+ s.set_up(5, 1, get_turf(telepad))
+ s.start()
+
+ temp_msg = "Teleport successful.
"
+ if(teles_left < 10)
+ temp_msg += "
Calibration required soon."
+ else
+ temp_msg += "Data printed below."
+ investigate_log("[key_name(usr)]/[user] has teleported with Telescience at [trueX],[trueY],[z_co], in [A ? A.name : "null area"].","telesci")
+
+ var/sparks = get_turf(target)
+ var/datum/effect/effect/system/spark_spread/y = new /datum/effect/effect/system/spark_spread
+ y.set_up(5, 1, sparks)
+ y.start()
+
+ var/turf/source = target
+ var/turf/dest = get_turf(telepad)
+ if(sending)
+ source = dest
+ dest = target
+
+ flick("pad-beam", telepad)
+ playsound(telepad.loc, 'sound/weapons/emitter2.ogg', 25, 1)
+ 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
+ else
+ continue
+ else if(!isobserver(ROI))
+ continue
+ do_teleport(ROI, dest)
+ updateDialog()
/obj/machinery/computer/telescience/proc/teleport(mob/user)
- if(x_co == null || y_co == null || z_co == null)
- user << "Error: coordinates not set."
+ if(rotation == null || angle == null || z_co == null)
+ 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 == 2 || z_co < 1 || z_co > 6)
+ telefail()
+ temp_msg = "ERROR! Sector is less than 1,
greater than 6, or equal to 2."
+ return
if(teles_left > 0)
- teles_left -= 1
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.loc = src.loc
+ crystals -= I
+ power = 0
+
/obj/machinery/computer/telescience/Topic(href, href_list)
- if(stat & (NOPOWER|BROKEN))
- return 0
+ if(..())
+ return
+ 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["setx"])
- var/new_x = input("Please input desired X coordinate.", name, x_co) as num
- if(new_x < 1 || new_x > 255)
- usr << "Error: Invalid X coordinate."
- else
- x_co = new_x
- return 1
+ 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["sety"])
- var/new_y = input("Please input desired Y coordinate.", name, y_co) as num
- if(new_y < 1 || new_y > 255)
- usr << "Error: Invalid Y coordinate."
- else
- y_co = new_y
- return 1
+ if(href_list["setpower"])
+ var/index = href_list["setpower"]
+ index = text2num(index)
+ if(index != null && power_options[index])
+ if(crystals.len >= index)
+ power = power_options[index]
if(href_list["setz"])
- var/new_z = input("Please input desired Z coordinate.", name, z_co) as num
- if(new_z == 2 || new_z < 1 || new_z > 7)
- usr << "Error: Invalid Z coordinate."
- else
- z_co = new_z
- return 1
+ var/new_z = input("Please input desired sector.", name, z_co) as num
+ if(..())
+ return
+ z_co = Clamp(round(new_z), 1, 10)
if(href_list["send"])
sending = 1
teleport(usr)
- return 1
if(href_list["receive"])
sending = 0
teleport(usr)
- return 1
if(href_list["recal"])
- teles_left = rand(9,12)
- x_off = rand(-10,10)
- y_off = rand(-10,10)
+ recalibrate()
sparks()
- usr << "Calibration successful."
- return 1
- return 0
\ No newline at end of file
+ temp_msg = "NOTICE:
Calibration successful."
+
+ if(href_list["eject"])
+ eject()
+ temp_msg = "NOTICE:
Bluespace crystals ejected."
+
+ updateDialog()
+
+/obj/machinery/computer/telescience/proc/recalibrate()
+ teles_left = rand(30, 40)
+ angle_off = rand(-25, 25)
+ power_off = rand(-4, 0)
+ rotation_off = rand(-10, 10)
\ No newline at end of file
diff --git a/icons/obj/telescience.dmi b/icons/obj/telescience.dmi
index 1f3344f2e4..4e8251beac 100644
Binary files a/icons/obj/telescience.dmi and b/icons/obj/telescience.dmi differ