diff --git a/code/__DEFINES/_macros.dm b/code/__DEFINES/_macros.dm
index 050551b3368..81192d6784a 100644
--- a/code/__DEFINES/_macros.dm
+++ b/code/__DEFINES/_macros.dm
@@ -21,6 +21,7 @@
#define SPAN_HEAR(X) ("" + X + "")
#define SPAN_STYLE(style, X) "[X]"
#define SPAN_COLOR(color, text) SPAN_STYLE("color: [color]", "[text]")
+#define SPAN_CAUTION(X) ("" + X + "")
#define SPAN_RED(x) "[x]"
#define SPAN_YELLOW(x) "[x]"
diff --git a/code/game/gamemodes/events/wormholes.dm b/code/game/gamemodes/events/wormholes.dm
index 77ed3b137c8..8451d2d3021 100644
--- a/code/game/gamemodes/events/wormholes.dm
+++ b/code/game/gamemodes/events/wormholes.dm
@@ -49,7 +49,7 @@
//maybe this proc can even be used as an admin tool for teleporting players without ruining immulsions?
/proc/create_wormhole(var/turf/enter as turf, var/turf/exit as turf)
var/obj/effect/portal/P = new /obj/effect/portal( enter )
- P.target = exit
+ P.set_target(exit)
P.creator = null
P.icon = 'icons/obj/objects.dmi'
P.failchance = 0
diff --git a/code/game/objects/effects/portals.dm b/code/game/objects/effects/portals.dm
index 4728d4d680b..ce46e6f5ffb 100644
--- a/code/game/objects/effects/portals.dm
+++ b/code/game/objects/effects/portals.dm
@@ -8,28 +8,55 @@
mouse_opacity = MOUSE_OPACITY_ICON
anchored = TRUE
- var/does_teleport = TRUE // Some portals might just be visual
+ ///Boolean, if the portal can actually teleport, or not (aka it's just visual)
+ var/does_teleport = TRUE
+
var/has_lifespan = TRUE // Whether we want to directly control the lifespan or not
+
+ ///Chance that the portal will fail
var/failchance = 5
+
+ ///Boolean, if the portal has failed (aka it will teleport you somewhere else)
var/has_failed = FALSE
- var/obj/target
+
+ /**
+ * The target the teleport aims to, an `/obj` or `/turf`
+ *
+ * *Not* to be set directly, use `set_target()` instead
+ */
+ var/atom/target
+
+ ///Who created the portal
var/creator
+
+ ///How precise (turf range) is the teleportation
var/precision = 1
/obj/effect/portal/Initialize(mapload, turf/set_target, set_creator, lifespan = 300, precise = 1)
. = ..()
if(set_target)
- target = set_target
+ set_target(set_target)
+
if(set_creator)
creator = set_creator
+ if(istype(creator, /atom))
+ RegisterSignal(creator, COMSIG_QDELETING, PROC_REF(handle_creator_qdel))
+
if(has_lifespan && lifespan > 0)
QDEL_IN(src, lifespan)
+
if(prob(failchance))
has_failed = TRUE
precision = precise
+/obj/effect/portal/Destroy()
+ if(istype(creator, /obj/item/hand_tele))
+ var/obj/item/hand_tele/HT = creator
+ HT.remove_portal(src)
+ . = ..()
+
/obj/effect/portal/CollidedWith(atom/bumped_atom)
. = ..()
@@ -37,8 +64,6 @@
teleport(bumped_atom)
/obj/effect/portal/Crossed(AM)
- set waitfor = FALSE
-
if(does_teleport)
teleport(AM)
@@ -50,8 +75,6 @@
return ..()
/obj/effect/portal/attack_hand(mob/user)
- set waitfor = FALSE
-
if(does_teleport)
teleport(user)
@@ -86,11 +109,41 @@
visible_message(SPAN_WARNING("\The [src] oscillates violently as \the [movable] comes into contact with it, and collapses! Seems like the rift was unstable..."))
qdel(src)
-/obj/effect/portal/Destroy()
- if(istype(creator, /obj/item/hand_tele))
- var/obj/item/hand_tele/HT = creator
- HT.remove_portal(src)
- return ..()
+/**
+ * Sets the target of the teleporter
+ */
+/obj/effect/portal/proc/set_target(atom/new_target)
+ if(!is_type_in_list(new_target, list(/obj, /turf, /mob)))
+ stack_trace("Portal was tried to be targeted at something that it is not supposed to!")
+ return
+
+ if(target)
+ UnregisterSignal(target, COMSIG_QDELETING)
+
+ target = new_target
+ RegisterSignal(target, COMSIG_QDELETING, PROC_REF(handle_target_qdel))
+
+/**
+ * Handles the teleporting target being deleted
+ */
+/obj/effect/portal/proc/handle_target_qdel()
+ SIGNAL_HANDLER
+
+ if(!QDELETED(src))
+ qdel(src)
+
+/**
+ * Handles the creator being deleted
+ */
+/obj/effect/portal/proc/handle_creator_qdel()
+ SIGNAL_HANDLER
+
+ creator = null
+
+
+/*##############
+ SUBTYPES
+##############*/
/obj/effect/portal/spawner
name = "portal"
diff --git a/code/modules/hydroponics/seed.dm b/code/modules/hydroponics/seed.dm
index a9f68e6661b..caef049a2be 100644
--- a/code/modules/hydroponics/seed.dm
+++ b/code/modules/hydroponics/seed.dm
@@ -277,7 +277,7 @@
if(turfs.len)
var/turf/picked = get_turf(pick(turfs))
var/obj/effect/portal/P = new /obj/effect/portal(get_turf(target))
- P.target = picked
+ P.set_target(picked)
P.creator = null
/datum/seed/proc/handle_environment(var/turf/current_turf, var/datum/gas_mixture/environment, var/light_supplied, var/check_only)
diff --git a/code/modules/mining/mine_items.dm b/code/modules/mining/mine_items.dm
index 263a8eab90b..a80ae41fa1f 100644
--- a/code/modules/mining/mine_items.dm
+++ b/code/modules/mining/mine_items.dm
@@ -725,7 +725,7 @@
return
var/chosen_beacon = pick(L)
var/obj/effect/portal/wormhole/jaunt_tunnel/J = new /obj/effect/portal/wormhole/jaunt_tunnel(get_turf(src), chosen_beacon, null, 100)
- J.target = chosen_beacon
+ J.set_target(chosen_beacon)
playsound(src,'sound/effects/sparks4.ogg', 50, 1)
qdel(src)
diff --git a/code/modules/shuttles/shuttle_specops.dm b/code/modules/shuttles/shuttle_specops.dm
index e37b4eb82f4..cdd5af58f89 100644
--- a/code/modules/shuttles/shuttle_specops.dm
+++ b/code/modules/shuttles/shuttle_specops.dm
@@ -158,7 +158,7 @@
var/obj/effect/portal/P = new(L.loc)
P.set_invisibility(101)//So it is not seen by anyone.
P.failchance = 0//So it has no fail chance when teleporting.
- P.target = pick(spawn_marauder)//Where the marauder will arrive.
+ P.set_target(pick(spawn_marauder))//Where the marauder will arrive.
spawn_marauder.Remove(P.target)
sleep(10)
diff --git a/code/modules/telesci/telesci_computer.dm b/code/modules/telesci/telesci_computer.dm
index a09a97babe4..06fc1617a62 100644
--- a/code/modules/telesci/telesci_computer.dm
+++ b/code/modules/telesci/telesci_computer.dm
@@ -7,70 +7,178 @@
light_color = LIGHT_COLOR_BLUE
circuit = /obj/item/circuitboard/telesci_console
var/sending = 1
+
+ /**
+ * The telepad that this console controls
+ */
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
+
+ ///How many teleports left until it becomes uncalibrated
+ var/teles_left
+
+ ///Projectile data, info about the latest teleport/aim that was done
var/datum/projectile_data/last_tele_data = null
- var/z_co = 4
+
+ ///The zlevel aim offset, that was inputted by the user as a target
+ var/zlevel_offset = 0
+
+ ///The Zlevel that is being targeted, aka the real one
+ var/target_zlevel
+
+ ///The rotation that was inputted by the user as a target
+ var/rotation = 0
+
+ ///The angle that was inputted by the user as a target
+ var/angle = 45
+
+ ///The power that was inputted by the user as a target
+ var/power = 5
+
+ ///The power offset
var/power_off
+
+ ///The rotation offset
var/rotation_off
//var/angle_off
- var/last_target
- var/rotation = 0
- var/angle = 45
- var/power = 5
+ ///The target of the last teleportation, a `/turf`
+ var/turf/last_target
// Based on the power used
var/teleport_cooldown = 0 // every index requires a bluespace crystal
+
+ /**
+ * A `/list` of power options that this console provides, for the projectile portal calculation
+ *
+ * Each added telecrystal (in `crystals`) unlocks an additional option, up to `max_crystals`
+ */
var/list/power_options = list(5, 10, 20, 25, 30, 40, 50, 80, 100)
- var/teleporting = 0
+
+ ///Boolean, if we're teleporting (aka opening a portal) right now
+ var/teleporting = FALSE
+
var/starting_crystals = 0 //Edit this on the map, seriously.
var/max_crystals = 5
- var/list/crystals = list()
+
+ /**
+ * A `/list` of `/obj/item/bluespace_crystal` that this console contains
+ */
+ var/list/obj/item/bluespace_crystal/crystals = list()
+
+ ///A GPS that this console contains
var/obj/item/device/gps/inserted_gps
+ ///A list of Zlevels that belong to the visitable (generally a ship) we are in
+ var/list/our_zlevels = list()
+
+ /**
+ * A list of currently known Zlevels translations below our ship/visitable
+ *
+ * K -> String, the fake zlevel that can be aimed at, it's an absolute number (the console will show it as negative),
+ * starts at the offset of the lowest zlevel of the visitable the console is in
+ * (eg: console at the second zlevel of a ship, with one zlevel below it of the same ship, this will start with an index of 2, the console will show it as -2)
+ *
+ * V -> Number, the actual zlevel
+ */
+ var/list/overmap_contacts_zlevels = list()
+
+ /**
+ * The portal that is the origin, above the telepad
+ *
+ * Do *not* set this directly, use `set_origin_portal()`
+ */
+ var/obj/effect/portal/origin_portal
+
+ /**
+ * The portal that is the destination
+ *
+ * Do *not* set this directly, use `set_destination_portal()`
+ */
+ var/obj/effect/portal/destination_portal
+
+/obj/machinery/computer/telescience/Initialize()
+ . = ..()
+
+ recalibrate()
+
+ for(var/i = 1; i <= starting_crystals; i++)
+ crystals += new /obj/item/bluespace_crystal/artificial(null) // starting crystals
+
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/machinery/computer/telescience/LateInitialize()
+ . = ..()
+
+ if(SSatlas.current_map.use_overmap && !linked)
+ var/my_sector = GLOB.map_sectors["[z]"]
+ if(istype(my_sector, /obj/effect/overmap/visitable))
+ attempt_hook_up(my_sector)
+
+ //If we got hooked up correctly, populate the list of our zlevels
+ if(linked)
+ for(var/zlevel in GLOB.map_sectors)
+ if(GLOB.map_sectors["[zlevel]"] == linked)
+ our_zlevels += text2num(zlevel)
+
+
/obj/machinery/computer/telescience/Destroy()
eject()
if(inserted_gps)
inserted_gps.forceMove(loc)
inserted_gps = null
+
+ if(!QDELETED(origin_portal))
+ QDEL_NULL(origin_portal)
+
+ if(!QDELETED(destination_portal))
+ QDEL_NULL(destination_portal)
+
+ QDEL_LIST(crystals)
+
+ telepad = null
+
return ..()
/obj/machinery/computer/telescience/get_examine_text(mob/user, distance, is_adjacent, infix, suffix)
. = ..()
- . += "There are [crystals.len ? crystals.len : "no"] bluespace crystal\s in the crystal slots."
+ . += "There are [length(crystals) ? length(crystals) : "no"] bluespace crystal\s in the crystal slots."
-/obj/machinery/computer/telescience/Initialize()
- . = ..()
- recalibrate()
- 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/attacking_item, mob/user, params)
+
if(istype(attacking_item, /obj/item/bluespace_crystal))
- if(crystals.len >= max_crystals)
+ if(length(crystals) >= max_crystals)
to_chat(user, SPAN_WARNING("There are not enough crystal slots."))
return
+
user.drop_item(src)
crystals += attacking_item
attacking_item.forceMove(null)
- user.visible_message("[user] inserts [attacking_item] into \the [src]'s crystal slot.", SPAN_NOTICE("You insert [attacking_item] into \the [src]'s crystal slot."))
+ user.visible_message("[user] inserts [attacking_item] into \the [src]'s crystal slot.",
+ SPAN_NOTICE("You insert [attacking_item] into \the [src]'s crystal slot."))
updateDialog()
+
else if(istype(attacking_item, /obj/item/device/gps))
+
if(!inserted_gps)
inserted_gps = attacking_item
user.unEquip(attacking_item)
attacking_item.forceMove(src)
- user.visible_message("[user] inserts [attacking_item] into \the [src]'s GPS device slot.", SPAN_NOTICE("You insert [attacking_item] into \the [src]'s GPS device slot."))
+ user.visible_message("[user] inserts [attacking_item] into \the [src]'s GPS device slot.",
+ SPAN_NOTICE("You insert [attacking_item] into \the [src]'s GPS device slot."))
+
else if(attacking_item.ismultitool())
+
var/obj/item/device/multitool/M = attacking_item
if(M.buffer && istype(M.buffer, /obj/machinery/telepad))
telepad = M.buffer
M.buffer = null
- to_chat(user, "You upload the data from the [attacking_item.name]'s buffer.")
+ to_chat(user, SPAN_CAUTION("You upload the data from the [attacking_item.name]'s buffer."))
+
else
..()
@@ -104,8 +212,8 @@
t += "Set Power"
t += "
"
- for(var/i = 1; i <= power_options.len; i++)
- if(crystals.len + telepad.efficiency < i)
+ for(var/i = 1; i <= length(power_options); i++)
+ if(length(crystals) + telepad.efficiency < i)
t += "
[power_options[i]]"
continue
if(power == power_options[i])
@@ -114,8 +222,8 @@
t += "
[power_options[i]]"
t += "
"
- t += "Set Sector"
- t += "[z_co ? z_co : "NULL"]
"
+ t += "Set Vertical Offset"
+ t += "[!isnull(zlevel_offset) ? zlevel_offset : "No Data"]
"
t += "
Open Portal"
t += "
Recalibrate Crystals Eject Crystals"
@@ -135,18 +243,29 @@
popup.open()
return
+/**
+ * Causes sparks to be emitted from the linked telepad
+ */
/obj/machinery/computer/telescience/proc/sparks()
+ SHOULD_NOT_SLEEP(TRUE)
+
if(telepad)
spark(telepad, 5, GLOB.alldirs)
else
return
+/**
+ * Called when the portal fails to be created
+ */
/obj/machinery/computer/telescience/proc/telefail()
+ SHOULD_NOT_SLEEP(TRUE)
+
sparks()
visible_message(SPAN_WARNING("The telepad weakly fizzles."))
return
/obj/machinery/computer/telescience/proc/doteleport(mob/user)
+ SHOULD_NOT_SLEEP(TRUE)
if(teleport_cooldown > world.time)
temp_msg = "Telepad is recharging power.
Please wait [round((teleport_cooldown - world.time) / 10)] seconds."
@@ -167,76 +286,82 @@
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/spawn_time = round(proj_data.time)
- var/turf/target = locate(trueX, trueY, z_co)
+ var/turf/target = locate(trueX, trueY, target_zlevel)
last_target = target
- var/area/A = get_area(target)
flick("pad-beam", telepad)
- if(spawn_time > 15 ) // 1.5 seconds
+ if(spawn_time > 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."
+ addtimer(CALLBACK(src, PROC_REF(complete_doteleport), user, target), spawn_time SECONDS)
- 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
+/**
+ * Completes the teleportation process, after the timer has expired, to be called only by
+ * the timer set by `doteleport()`
+ */
+/obj/machinery/computer/telescience/proc/complete_doteleport(mob/user, turf/target)
+ SHOULD_NOT_SLEEP(TRUE)
- // use a lot of power
- use_power_oneoff(power * 10)
+ if(!telepad)
+ return
+ if(telepad.stat & NOPOWER)
+ return
+ teleporting = 0
+ teleport_cooldown = world.time + (power * 2)
+ teles_left -= 1
- spark(telepad, 5, GLOB.alldirs)
+ // use a lot of power
+ var/power_to_use = (power KILO WATTS)
+ if(overmap_contacts_zlevels["[abs(zlevel_offset)]"])
+ power_to_use += (300 KILO WATTS * abs(zlevel_offset))
- temp_msg = "Bluespace portal creation successful.
"
- if(teles_left < 10)
- temp_msg += "
Calibration required soon."
- else
- temp_msg += "Data printed below."
+ use_power_oneoff(power_to_use)
- spark(telepad, 5, GLOB.alldirs)
+ spark(telepad, 5, GLOB.alldirs)
- var/turf/source = target
- var/turf/dest = get_turf(telepad)
- var/log_msg = ""
- log_msg += ": [key_name(user)] has teleported "
+ temp_msg = "Bluespace portal creation successful.
"
+ if(teles_left < 10)
+ temp_msg += "
Calibration required soon."
+ else
+ temp_msg += "Data printed below."
- if(sending)
- source = dest
- dest = target
+ spark(telepad, 5, GLOB.alldirs)
- flick("pad-beam", telepad)
- playsound(telepad.loc, 'sound/weapons/emitter2.ogg', 25, 1, extrarange = 3, falloff_distance = 5)
+ var/turf/source = target
+ var/turf/dest = get_turf(telepad)
+ var/log_msg = ""
+ log_msg += ": [key_name(user)] has teleported "
- var/total_lifespawn = 25 * crystals.len
+ if(sending)
+ source = dest
+ dest = target
- var/obj/effect/portal/origin = new /obj/effect/portal(dest, null, null, total_lifespawn, 0)
- var/obj/effect/portal/destination = new /obj/effect/portal(source, null, null, total_lifespawn, 0)
+ flick("pad-beam", telepad)
+ playsound(telepad.loc, 'sound/weapons/emitter2.ogg', 25, 1, extrarange = 3, falloff_distance = 5)
+
+ var/total_lifespawn = 25 * length(crystals)
+
+ var/obj/effect/portal/origin = new /obj/effect/portal(dest, null, null, total_lifespawn, 0)
+ set_origin_portal(origin)
+
+ var/obj/effect/portal/destination = new /obj/effect/portal(source, null, null, total_lifespawn, 0)
+ set_destination_portal(destination)
- origin.target = destination
- destination.target = origin
- origin.has_failed = FALSE
- destination.has_failed = FALSE
+ origin.set_target(destination)
+ destination.set_target(origin)
+ origin.has_failed = FALSE
+ destination.has_failed = FALSE
-
- 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()
+ updateDialog()
/obj/machinery/computer/telescience/proc/teleport(mob/user)
- if(rotation == null || angle == null || z_co == null)
+ if(rotation == null || angle == null || target_zlevel == null)
temp_msg = "ERROR!
Set a angle, rotation and sector."
return
if(power <= 0)
@@ -251,19 +376,19 @@
telefail()
temp_msg = "ERROR!
Elevation is less than 1 or greater than 90."
return
- if(isNotStationLevel(z_co))
- telefail()
- temp_msg = "ERROR! Sector is invalid! Valid sectors are [english_list(SSatlas.current_map.station_levels)]."
- return
if(teles_left > 0)
doteleport(user)
else
telefail()
temp_msg = "ERROR!
Calibration required."
return
- return
+/**
+ * Ejects the telecrystals from the console
+ */
/obj/machinery/computer/telescience/proc/eject()
+ SHOULD_NOT_SLEEP(TRUE)
+
for(var/obj/item/I in crystals)
I.forceMove(src.loc)
crystals -= I
@@ -295,14 +420,94 @@
var/index = href_list["setpower"]
index = text2num(index)
if(index != null && power_options[index])
- if(crystals.len + telepad.efficiency >= index)
+ if(length(crystals) + telepad.efficiency >= index)
power = power_options[index]
if(href_list["setz"])
- var/new_z = input("Please input desired sector.", name, z_co) as num
+ var/inputed_new_z = tgui_input_number(usr, "Please input desired height offset", "Section Selection", 0, max_value = world.maxz, min_value = world.maxz*-1, round_value = TRUE)
+
if(..())
return
- z_co = Clamp(round(new_z), 1, 10)
+
+ //Use a holder var to do the calculations, detached from the user input, as we vary it
+ var/new_z = inputed_new_z
+ //If we crossed the 0 mark, get a free additional zlevel (so we don't have to deal with a zlevel 0)
+ if(new_z < 0 && abs(new_z) >= src.z)
+ new_z--
+
+ //Prevent people from targeting admin levels
+ if(isAdminLevel(src.z + new_z))
+ to_chat(usr, SPAN_WARNING("Bluespace forces prevent this offset from being used."))
+ return
+
+ //If we are going into the negatives, those are "fake" zlevels that we use to refer
+ //to outside areas (like other ships, planets, sites, you name it)
+ //Same goes for not connected zlevels, in case we ever add them above us
+ if((src.z + new_z) < 0 || !AreConnectedZLevels(src.z, (src.z + new_z)))
+
+ overmap_contacts_zlevels = list()
+
+ var/list/obj/effect/overmap/visitable/already_added_visitables = list()
+
+ //Get the minimum zlevel that we need to offset to be extraneous of our ship
+ //eg. if we are on Zlevel 4, and our ship covers Z 1 to 5, we have 3 Zlevels
+ //that are ours, so we need to offset by 4 to start being outside our zlevels
+ //(of course those zlevels would be virtual ones, to place the translation to the real ones for overmap visitables)
+ var/our_zlevel = src.z
+ var/min_zlevel_below_us = 1
+ while((our_zlevel-=1) in our_zlevels)
+ min_zlevel_below_us++
+
+ for(var/obj/machinery/computer/ship/sensors/S in SSmachinery.machinery)
+ //If we have ownership of this
+ if(linked.check_ownership(S))
+
+ //If the deep scan is toggled, we do not allow it, as the range is too large
+ //print a message to the user and break the loop
+ var/obj/machinery/shipsensors/ship_sensors = S.sensor_ref?.resolve()
+ if((ship_sensors?.deep_scan_toggled))
+ to_chat(usr, SPAN_WARNING("The deep scan is currently enabled and interferes with the teleporter lock ability."))
+ break
+
+ for(var/obj/effect/overmap/visitable/known_visitable in S.objects_in_view)
+ //If not fully scanned and identified, skip
+ if(S.objects_in_view[known_visitable] < 100)
+ continue
+
+ //If it's us or some BS, skip
+ if(known_visitable.map_z == our_zlevels)
+ continue
+
+ //If we added this already, skip
+ if(known_visitable in already_added_visitables)
+ continue
+
+
+ //Ok, we found what would be the offset below us to start adding shit, let's add shit
+ for(var/contact_zlevel in known_visitable.map_z)
+ overmap_contacts_zlevels["[min_zlevel_below_us+length(overmap_contacts_zlevels)]"] = contact_zlevel
+
+ //Mark it as added
+ already_added_visitables += known_visitable
+
+ //One console gave us the zlevels, we can break the loop
+ if(length(overmap_contacts_zlevels))
+ break
+
+ if((src.z + new_z) < 0 && (num2text(abs(new_z)-1) in overmap_contacts_zlevels))
+ target_zlevel = overmap_contacts_zlevels["[abs(new_z)-1]"]
+ zlevel_offset = inputed_new_z
+ else
+ to_chat(usr, SPAN_WARNING("There is nothing targetable at this height."))
+ return
+
+ //Same area as us
+ else
+ if((src.z+new_z) in our_zlevels)
+ target_zlevel = (src.z+new_z)
+ zlevel_offset = inputed_new_z
+ else
+ to_chat(usr, SPAN_WARNING("There is nothing targetable at this height."))
if(href_list["ejectGPS"])
if(inserted_gps)
@@ -331,8 +536,48 @@
updateDialog()
+/obj/machinery/computer/telescience/process(seconds_per_tick)
+ if((stat & NOPOWER) || (telepad?.stat & NOPOWER))
+ QDEL_NULL(origin_portal)
+
+/**
+ * Recomputes the parameters used for aiming
+ */
/obj/machinery/computer/telescience/proc/recalibrate()
+ SHOULD_NOT_SLEEP(TRUE)
+
teles_left = rand(30, 40)
//angle_off = rand(-25, 25)
power_off = rand(-4, 0)
rotation_off = rand(-10, 10)
+
+/**
+ * Sets the origin portal
+ *
+ * * orig_portal - An `/obj/effect/portal` that will act as the "origin"
+ */
+/obj/machinery/computer/telescience/proc/set_origin_portal(obj/effect/portal/orig_portal)
+ SHOULD_NOT_SLEEP(TRUE)
+
+ origin_portal = orig_portal
+ RegisterSignal(origin_portal, COMSIG_QDELETING, PROC_REF(handle_portal_qdel))
+
+/**
+ * Sets the origin portal
+ *
+ * * dest_portal - An `/obj/effect/portal` that will act as the "destination"
+ */
+/obj/machinery/computer/telescience/proc/set_destination_portal(obj/effect/portal/dest_portal)
+ SHOULD_NOT_SLEEP(TRUE)
+
+ destination_portal = dest_portal
+ RegisterSignal(destination_portal, COMSIG_QDELETING, PROC_REF(handle_portal_qdel))
+
+/**
+ * Handles a portal being deleted
+ */
+/obj/machinery/computer/telescience/proc/handle_portal_qdel()
+ SIGNAL_HANDLER
+
+ destination_portal = null
+ origin_portal = null
diff --git a/html/changelogs/fluffyghost-teleporterscience.yml b/html/changelogs/fluffyghost-teleporterscience.yml
new file mode 100644
index 00000000000..bd39f882815
--- /dev/null
+++ b/html/changelogs/fluffyghost-teleporterscience.yml
@@ -0,0 +1,46 @@
+################################
+# Example Changelog File
+#
+# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
+#
+# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
+# When it is, any changes listed below will disappear.
+#
+# Valid Prefixes:
+# bugfix
+# wip (For works in progress)
+# tweak
+# soundadd
+# sounddel
+# rscadd (general adding of nice things)
+# rscdel (general deleting of nice things)
+# imageadd
+# imagedel
+# maptweak
+# spellcheck (typo fixes)
+# experiment
+# balance
+# admin
+# backend
+# security
+# refactor
+#################################
+
+# Your name.
+author: FluffyGhost
+
+# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
+delete-after: True
+
+# Any changes you've made. See valid prefix list above.
+# INDENT WITH TWO SPACES. NOT TABS. SPACES.
+# SCREW THIS UP AND IT WON'T WORK.
+# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
+# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
+changes:
+ - refactor: "Refactored portals, improved handling."
+ - rscadd: "Telescience console can now target overmap zlevels, provided deep scan is not enabled and the contact is completely scanned."
+ - bugfix: "Telescience portals now properly delete when the timer expires."
+ - rscadd: "Greatly increased power usage for the telescience portals, especially if aiming outside targets."
+ - refactor: "Refactored telescience console backend."
+ - refactor: "Added various DMDocs."