From 4c932542ef78c2126f7cdb2c9a63306e2652297c Mon Sep 17 00:00:00 2001
From: Fluffy <65877598+FluffyGhoster@users.noreply.github.com>
Date: Wed, 17 Jul 2024 23:17:42 +0200
Subject: [PATCH] Telescience unapologetic buff (#18854)
Refactored portals, improved handling.
Telescience console can now target overmap zlevels, provided deep scan
is not enabled and the contact is completely scanned.
Telescience portals now properly delete when the timer expires.
Greatly increased power usage for the telescience portals, especially if
aiming outside targets.
Refactored telescience console backend.
Added various DMDocs.
---
code/__DEFINES/_macros.dm | 1 +
code/game/gamemodes/events/wormholes.dm | 2 +-
code/game/objects/effects/portals.dm | 77 +++-
code/modules/hydroponics/seed.dm | 2 +-
code/modules/mining/mine_items.dm | 2 +-
code/modules/shuttles/shuttle_specops.dm | 2 +-
code/modules/telesci/telesci_computer.dm | 397 ++++++++++++++----
.../fluffyghost-teleporterscience.yml | 46 ++
8 files changed, 437 insertions(+), 92 deletions(-)
create mode 100644 html/changelogs/fluffyghost-teleporterscience.yml
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 += "