Files
FenodyreeAvandGitHub 21b9d5861a Fixes bluespace inhibitor interaction with telescience (#22967)
Bluespace inhibitors prevent you from teleporting out of them in a
square.
They prevent you teleporting into them in a circle.

Previously, if you got very unlucky, you could teleport into an area but
not back out again.

This makes both checks a square.

It also drops you one additional tile further out of the teleport zone,
so if you step off your portal in the direction of the inhibitor, it
doesn't destroy the portal.

Telescience portals also didn't care and would form inside the inhibitor
radius.

changes:
- bugfix: "Fixes bluespace inhibitors inconsistantly blocking teleports
in a square, but finding the alternate desination in a circle. Its all
square now."
- bugfix: "Fixes telescience portals not respecting bluespace
inhibitors."
2026-08-02 01:24:10 +00:00

359 lines
11 KiB
Plaintext

//wrapper
/proc/do_teleport(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null)
var/datum/teleport/T = new /datum/teleport/instant/science(arglist(args))
if(T.stable_creation)
return TRUE
return FALSE
/datum/teleport
var/atom/movable/teleatom //atom to teleport
var/atom/destination //destination to teleport to
var/precision = 0 //teleport precision
var/datum/effect_system/effectin //effect to show right before teleportation
var/datum/effect_system/effectout //effect to show right after teleportation
var/soundin //soundfile to play before teleportation
var/soundout //soundfile to play after teleportation
var/force_teleport = 1 //if false, teleport will use Move() proc (dense objects will prevent teleportation)
var/stable_creation = TRUE //This is mostly for portals, which need to delete if the do_teleport fails, due to inhibitors or other reasons we fail to initTeleport properly
/datum/teleport/New(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null)
..()
if(!initTeleport(arglist(args)))
stable_creation = FALSE
return FALSE
return TRUE
/datum/teleport/proc/initTeleport(ateleatom,adestination,aprecision,afteleport,aeffectin,aeffectout,asoundin,asoundout)
if(!setTeleatom(ateleatom))
return FALSE
if(!setDestination(adestination))
return FALSE
if(!setPrecision(aprecision))
return FALSE
setEffects(aeffectin,aeffectout)
setForceTeleport(afteleport)
setSounds(asoundin,asoundout)
return TRUE
//must succeed
/datum/teleport/proc/setPrecision(aprecision)
if(isnum(aprecision))
precision = aprecision
return TRUE
return FALSE
/datum/teleport/proc/checkInhibitors(atom/adestination)
if(istype(adestination))
var/list/turf/good_turfs = list()
var/list/turf/bad_turfs = list()
var/turf/T = get_turf(adestination)
if(!T)
return adestination
var/list/invalid_inhibitors
for(var/found_inhibitor in GLOB.bluespace_inhibitors)
if(!istype(found_inhibitor, /obj/structure/machinery/anti_bluespace))
LAZYADD(invalid_inhibitors, found_inhibitor)
continue
var/obj/structure/machinery/anti_bluespace/AB = found_inhibitor
if(QDELETED(AB))
LAZYADD(invalid_inhibitors, found_inhibitor)
continue
if(T.z != AB.z || get_dist(adestination, AB) > 8 || (AB.stat & (NOPOWER | BROKEN)))
continue
AB.use_power_oneoff(AB.active_power_usage)
bad_turfs += RANGE_TURFS(8, get_turf(AB))
good_turfs += RANGE_TURFS(9, get_turf(AB))
if(invalid_inhibitors)
GLOB.bluespace_inhibitors -= invalid_inhibitors
if(length(good_turfs) && length(bad_turfs))
good_turfs -= bad_turfs
if(length(good_turfs))
return pick(good_turfs)
return adestination
//Check if we're in range of a bluespace inhibitor. We can't be teleported if we are.
/datum/teleport/proc/checkLocalInhibitors(atom/movable/teleportee)
for(var/obj/structure/machinery/anti_bluespace/AB in range(8, teleportee))
if(AB.stat & (NOPOWER | BROKEN))
continue
else
AB.use_power_oneoff(AB.active_power_usage)
return null
return teleportee
//must succeed
/datum/teleport/proc/setDestination(atom/adestination)
if(istype(adestination))
destination = checkInhibitors(adestination)
return TRUE
return FALSE
//must succeed in most cases
/datum/teleport/proc/setTeleatom(atom/movable/ateleatom)
if(!istype(ateleatom))
return FALSE
teleatom = checkLocalInhibitors(ateleatom)
if(isnull(teleatom))
return FALSE
if(istype(ateleatom, /obj/effect) && !istype(ateleatom, /obj/effect/dummy/chameleon))
qdel(ateleatom)
return FALSE
return TRUE
//custom effects must be properly set up first for instant-type teleports
//optional
/datum/teleport/proc/setEffects(datum/effect/effect/system/aeffectin=null,datum/effect/effect/system/aeffectout=null)
effectin = istype(aeffectin) ? aeffectin : null
effectout = istype(aeffectout) ? aeffectout : null
return TRUE
//optional
/datum/teleport/proc/setForceTeleport(afteleport)
force_teleport = afteleport
return TRUE
//optional
/datum/teleport/proc/setSounds(asoundin=null,asoundout=null)
soundin = isfile(asoundin) ? asoundin : null
soundout = isfile(asoundout) ? asoundout : null
return TRUE
//placeholder
/datum/teleport/proc/teleportChecks()
return TRUE
/datum/teleport/proc/playSpecials(atom/location,datum/effect_system/effect,sound)
if(location)
if(effect)
src = null
effect.location = location
effect.queue()
if(sound)
src = null
playsound(location,sound,60,1)
return
//do the monkey dance
/datum/teleport/proc/doTeleport()
var/turf/destturf
var/turf/curturf = get_turf(teleatom)
var/area/destarea = get_area(destination)
if(precision)
var/list/posturfs = circle_range_turfs(destination,precision)
destturf = LAZYPICK(posturfs, null)
else
destturf = get_turf(destination)
if(!destturf || !curturf)
return FALSE
playSpecials(curturf,effectin,soundin)
// we don't want a teleportation loop, if we teleport to a destination that already has a portal
// disable it for just a moment until we get there
var/has_active_destination_portal = FALSE
var/obj/effect/portal/destination_portal = locate() in destturf
if(destination_portal?.does_teleport)
has_active_destination_portal = TRUE
destination_portal.does_teleport = FALSE
var/obj/structure/bed/stool/chair/C = null
if(isliving(teleatom))
var/mob/living/L = teleatom
if(L.buckled_to)
C = L.buckled_to
if(force_teleport)
teleatom.forceMove(destturf)
playSpecials(destturf,effectout,soundout)
var/atom/impediment
var/valid = 0
if(destturf.density && destturf != teleatom)
impediment = destturf
else
for(var/atom/movable/A in destturf)
if(A != teleatom && A.density && A.anchored && !istype(A, /obj/effect/portal))
if(A.atom_flags & ATOM_FLAG_CHECKS_BORDER)
if(prob(10))
impediment = A
break
else
impediment = A
break
if(impediment)
var/turf/newdest
var/boominess = 0
for(var/turf/T in range(1, destturf))
if(!T.density)
for(var/atom/movable/A in T)
var/blocked = 0
if(A.density && A.opacity && A.anchored)
blocked = 1
break
if(!blocked)
newdest = T
break
if(istype(teleatom, /obj) && !istype(teleatom, /obj/effect/portal))
valid = 1
var/obj/O = teleatom
if(newdest)
O.ex_act(3)
boominess += max(0, O.w_class - 1)
if(O.density)
boominess += 5
if(O.opacity)
boominess += 10
if(istype(teleatom, /mob/living))
valid = 1
var/mob/living/L = teleatom
boominess += L.mob_size/4
if(!L.incorporeal_move)
if(istype(L, /mob/living/carbon/human))
var/mob/living/carbon/human/H = L
if(newdest)
var/list/organs_to_gib = list()
for(var/obj/item/organ/external/ext in H.organs)
if(!ext.vital) //ensures someone doesn't instantly die, allowing them to slowly die instead
organs_to_gib.Add(ext)
if(organs_to_gib.len)
var/obj/item/organ/external/E = pick(organs_to_gib)
to_chat(H, SPAN_DANGER("You partially phase into \the [impediment], causing your [E.name] to violently dematerialize!"))
H.apply_damage(35, DAMAGE_BRUTE, E, 0)
else
if(newdest)
to_chat(L, SPAN_DANGER("You partially phase into \the [impediment], causing a chunk of you to violently dematerialize!"))
L.adjustBruteLoss(40)
else
newdest = destturf
valid = 0
if(valid && newdest)
destturf.visible_message("<span class ='danger'>There is a sizable emission of energy as \the [teleatom] phases into \the [impediment]!</span>")
explosion(destturf, ((boominess > 10) ? 1 : 0), ((boominess > 5) ? (boominess/10) : 0), boominess/5, boominess/2)
teleatom.forceMove(newdest)
else if(!newdest)
var/list/turfs_to_teleport = list()
var/turf/target_turf
for(var/turf/T in orange(10, get_turf(teleatom)))
turfs_to_teleport += T
target_turf = pick(turfs_to_teleport)
if(target_turf)
do_teleport(teleatom, target_turf)
else
do_teleport(teleatom, get_turf(teleatom))
else if(istype(teleatom, /mob/living/simple_animal/shade/bluespace))
var/mob/living/simple_animal/shade/bluespace/BS = teleatom
for(var/mob/living/L in destturf)
if(!L.mind && !isvaurca(L))
if(BS.message_countdown >= 200)
to_chat(BS, SPAN_NOTICE("<b>You feel relief wash over you as your harried spirit fills into \the [L] like water into a vase.</b>"))
BS.mind.transfer_to(L)
to_chat(L, "<b>You have been restored to a corporeal form. You retain no memories of your time as a bluespace echo, but regardless of your current form the memories of your time before being a bluespace echo are returned.</b>")
qdel(BS)
else
to_chat(BS, SPAN_WARNING("You lack the strength of echoes necessary to reattain corporeality in \the [L]!"))
break
else
if(teleatom.Move(destturf))
playSpecials(destturf,effectout,soundout)
if(C)
C.forceMove(destturf)
destarea.Entered(teleatom)
if(has_active_destination_portal)
destination_portal.does_teleport = TRUE
return TRUE
/datum/teleport/proc/teleport()
if(teleportChecks())
return doTeleport()
return FALSE
/datum/teleport/instant //teleports when datum is created
/datum/teleport/instant/New(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null)
if(..())
teleport()
return
/datum/teleport/instant/science/setEffects(datum/effect/effect/system/aeffectin,datum/effect/effect/system/aeffectout)
if(!aeffectin || !aeffectout)
var/datum/effect_system/sparks/aeffect = new(null, FALSE, 5, GLOB.alldirs)
effectin = effectin || aeffect
effectout = effectout || aeffect
return TRUE
else
return ..()
/datum/teleport/instant/science/setPrecision(aprecision)
..()
if(istype(teleatom, /obj/item/storage/backpack/holding))
precision = rand(1,100)
var/list/bagholding = teleatom.search_contents_for(/obj/item/storage/backpack/holding)
if(bagholding.len)
precision = max(rand(1,100)*bagholding.len,100)
if(istype(teleatom, /mob/living))
var/mob/living/MM = teleatom
to_chat(MM, SPAN_DANGER("The Bluespace interface on your [teleatom] interferes with the teleport!"))
return TRUE
/datum/teleport/instant/science/teleportChecks()
if(istype(teleatom, /obj/item/disk/nuclear)) // Don't let nuke disks get teleported --NeoFite
teleatom.visible_message(SPAN_DANGER("\The [teleatom] bounces off of the portal!"))
return FALSE
if(isghost(teleatom)) // do not teleport ghosts
return FALSE
if(!isemptylist(teleatom.search_contents_for(/obj/item/disk/nuclear)))
if(istype(teleatom, /mob/living))
var/mob/living/MM = teleatom
MM.visible_message(SPAN_DANGER("\The [MM] bounces off of the portal!"),SPAN_WARNING("Something you are carrying seems to be unable to pass through the portal. Better drop it if you want to go through."))
else
teleatom.visible_message(SPAN_DANGER("\The [teleatom] bounces off of the portal!"))
return FALSE
if(isAdminLevel(destination.z)) //centcomm z-level
if(!isemptylist(teleatom.search_contents_for(/obj/item/storage/backpack/holding)))
teleatom.visible_message(SPAN_DANGER("\The [teleatom] bounces off of the portal!"))
return FALSE
if(destination.z > max_default_z_level()) //Away mission z-levels
return FALSE
return TRUE