Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 80 additions & 5 deletions code/datums/helper_datums/teleport.dm
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
/**
* Returns FALSE if we SHOULDN'T do_teleport() with the given arguments
*
* Arguments:
* * teleatom: The atom to teleport
* * dest_turf: The destination turf for the atom to go
* * channel: Which teleport channel/type should we try to use (for blocking checks), defaults to TELEPORT_CHANNEL_BLUESPACE
*/
/proc/check_teleport(atom/movable/teleatom, turf/dest_turf, channel = TELEPORT_CHANNEL_BLUESPACE)
var/turf/cur_turf = get_turf(teleatom)

if(!istype(dest_turf))
stack_trace("Destination [dest_turf] is not a turf.")
return FALSE
if(!istype(cur_turf) || dest_turf.is_transition_turf())
return FALSE

return TRUE

// teleatom: atom to teleport
// destination: destination to teleport to
// precision: teleport precision (0 is most precise, the default)
Expand Down Expand Up @@ -63,11 +82,6 @@
if(!destturf || !curturf)
return FALSE

var/area/A = get_area(curturf)
var/area/B = get_area(destturf)
if(!forced && (HAS_TRAIT(teleatom, TRAIT_NO_TELEPORT) || (A.area_flags & NOTELEPORT) || (B.area_flags & NOTELEPORT)))
return FALSE

if(SEND_SIGNAL(destturf, COMSIG_ATOM_INTERCEPT_TELEPORT, channel, curturf, destturf))
return FALSE

Expand Down Expand Up @@ -184,3 +198,64 @@
var/list/turfs = get_teleport_turfs(current, destination, precision)
if (length(turfs))
return pick(turfs)

/**
* attempts to take AM through all turfs in a straight line between ``current_turf`` and ``target_turf``,
* applying ``on_turf_cross`` for each turf and ``obj_damage`` to each structure encountered
*
* player-facing warnings and EMP/BoH effects should be handled externally from this proc
*
* required arguments:
* * ``AM`` - movable atom to be dashed
* * ``current_turf`` - source turf for the dash, not necessarily ``AM``'s
* * ``target_turf`` - destination turf for the dash
* optional parameters:
* * ``obj_damage`` - damage applied to structures in its path (not mobs)
* * ``phase`` - whether to go through structures or be impeded by them until they're broken
* * ``teleport_channel`` - allows overriding of teleport channel used
* * ``on_turf_cross`` - optional callback proc to call on each of the crossed turfs;
* takes ``turf/T`` and returns ``TRUE`` if dash should continue, otherwise ``FALSE`` when it should be interrupted -
* this however does not cause the dash to return a null value;
* if the proc you wrap in a callback has multiple parameters, ``turf/T`` should be last, and will be passed from here
*
* returns: ``turf/landing_turf``, which represents where the dash ended, or ``null`` if the jaunt's teleport check failed
*/
/proc/do_dash(atom/movable/AM, turf/current_turf, turf/target_turf, obj_damage=0, phase=TRUE, teleport_channel=TELEPORT_CHANNEL_BLUESPACE, datum/callback/on_turf_cross=null)
// current loc
if(!istype(current_turf))
return

// getline path
var/turf/landing_turf = current_turf
var/list/path = get_line(current_turf, target_turf)
path -= current_turf
// iterate
for (var/turf/checked_turf in path)
// Step forward

// Check if we can move here
if(!check_teleport(AM, checked_turf, channel = teleport_channel))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if AM is not located on a turf
break // stop moving forward
// If it contains objects, try to break it
if (obj_damage > 0) // should skip this if not needed
for (var/obj/object in checked_turf.contents)
if (object.density)
object.take_damage(obj_damage)

// check if we should stop due to obstacles, crack apart a wall if one is hit
if (!phase && checked_turf.is_blocked_turf(TRUE))
if (istype(checked_turf, /turf/closed))
var/turf/closed/impact_wall = checked_turf
playsound(impact_wall, impact_wall.attack_hitsound, 100, TRUE)
impact_wall.alter_integrity(-obj_damage)
break // stop moving forward
// call on_turf_cross(checked_turf)
if (on_turf_cross) // optional callback should be optional
if (!on_turf_cross.Invoke(checked_turf))
break // stop moving forward

// increment our landing turf
landing_turf = checked_turf

do_teleport(AM, landing_turf, channel = teleport_channel)
return landing_turf
122 changes: 122 additions & 0 deletions code/game/objects/items/teleportation.dm
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,125 @@
if(active_portal_pairs[i] == P)
return DESTINATION_PORTAL
return FALSE

/*
* Gravitational Jaunter
*/

/obj/item/gravitational_jaunter
name = "gravitational jaunter"
desc = "A device that allows its users to bend space around themselves into a 'bubble'. The lensing of this bubble is set to send its user directly ahead of the direction they're facing. Upon contact with matter, the bubble loses form, but not without causing damage to structures -- or people -- in its way. Created as a result of research into gravitational phenomena."
icon = 'icons/obj/device.dmi'
icon_state = "gravitational_jaunter"
item_state = "electronic"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
throwforce = 5
w_class = WEIGHT_CLASS_SMALL
throw_speed = 4
throw_range = 10

//Uses of the device left
var/charges = 4
//The maximum number of stored uses
var/max_charges = 4
var/minimum_teleport_distance = 6
var/maximum_teleport_distance = 8
//How far the emergency teleport checks for a safe position
var/parallel_teleport_distance = 3
//How long it takes to replenish a charge
var/recharge_time = 15 SECONDS

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it could probably be good to make it battery operated instead,maybe?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having it be anomaly tech is cool enough on its own. I'm unsure it needs to be cell based too.

@zimon9 zimon9 May 27, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could potentially do both? In that it could use an internal cell and also make use of the current system where an internal capacitor charges up charges -- giving 20 to 30 total jaunts with a maximum jaunt quantity of 4 at a time. I could potentially reduce the charge rate as well, if we take this avenue. My thinking here is that this implementation can imply that a link between gravity and the other three fundamental forces, much like how gravity generators currently do (since they require electricity to operate). I'm good with anything though and, I'm happy to let you both discuss it

//If the device is recharging, prevents timers stacking
var/recharging = FALSE
//stores the recharge timer id
var/recharge_timer

/obj/item/gravitational_jaunter/examine(mob/user)
. = ..()
. += span_notice("[src] has [charges] out of [max_charges] charges left.")
if(recharging)
. += span_notice("<b>A small display on the back reads:</b>")
var/timeleft = timeleft(recharge_timer)
var/loadingbar = num2loadingbar(timeleft/recharge_time, reverse=TRUE)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

had no idea this proc even existed.... this is quite cool we should probably use it more lol.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right? It's pretty neat

. += span_notice("<b>CHARGING: [loadingbar] ([timeleft*0.1]s)</b>")

/obj/item/gravitational_jaunter/attack_self(mob/user)
..()
attempt_teleport(user, FALSE)

/obj/item/gravitational_jaunter/proc/check_charges()
if(recharging)
return
if(charges < max_charges)
recharge_timer = addtimer(CALLBACK(src, PROC_REF(recharge)), recharge_time, TIMER_STOPPABLE)
recharging = TRUE

/obj/item/gravitational_jaunter/proc/recharge()
charges++
playsound(src,'sound/machines/twobeep.ogg',10,TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_distance = 0)
recharging = FALSE
check_charges()

/obj/item/gravitational_jaunter/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
if(!prob(50 / severity))
return
if(istype(loc, /mob/living/carbon/human))
var/mob/living/carbon/human/user = loc
to_chat(user, span_danger("[src] buzzes and activates!"))
attempt_teleport(user, TRUE) //EMP Activates a teleport with no safety.
else
visible_message(span_warning("[src] activates and blinks out of existence!"))
do_sparks(2, 1, src)
qdel(src)

/obj/item/gravitational_jaunter/proc/attempt_teleport(mob/user, EMP_D = FALSE)
if(!charges)
to_chat(user, span_warning("[src] is still recharging."))
return

var/turf/original_location = get_turf(user)

var/teleport_distance = rand(minimum_teleport_distance,maximum_teleport_distance)
var/list/bagholding = user.GetAllContents(/obj/item/storage/backpack/holding)
var/direction = (EMP_D || length(bagholding)) ? pick(GLOB.cardinals) : user.dir
var/turf/destination = get_ranged_target_turf(user, direction, teleport_distance)

var/turf/new_location = do_dash(user, original_location, destination, obj_damage=150, phase=FALSE, on_turf_cross=CALLBACK(src, PROC_REF(telefrag), user))
if(isnull(new_location))
to_chat(user, span_notice("\The [src] is malfunctioning."))
return

new /obj/effect/temp_visual/teleport_abductor/gravitational_jaunter(original_location)
new /obj/effect/temp_visual/teleport_abductor/gravitational_jaunter(new_location)
charges--
check_charges()
playsound(new_location, 'sound/effects/phasein.ogg', 25, 1)
playsound(new_location, "sparks", 50, 1)

/obj/item/gravitational_jaunter/proc/telefrag(mob/user, turf/fragging_location)
for(var/mob/living/target in fragging_location)//Hit everything in the turf
// Skip any mobs that aren't standing, or aren't dense
if ((target.body_position == LYING_DOWN) || !target.density || user == target)
continue
// Run armour checks and apply damage
var/armor_block = target.run_armor_check(BODY_ZONE_CHEST, MELEE)
if(istype(target, /mob/living/simple_animal))
target.apply_damage(60, BRUTE, blocked = armor_block) //because simplemobs can't be stunned or paralyzed, the total brute damage is increased
else
target.apply_damage(25, BRUTE, blocked = armor_block)
target.Paralyze(10 * (100 - armor_block) / 100)
target.Knockdown(40 * (100 - armor_block) / 100)
// Check if we successfully knocked them down
if (target.body_position == LYING_DOWN)
to_chat(target, span_userdanger("[user] teleports into you, knocking you to the floor with the bluespace wave!"))
else
to_chat(user, span_userdanger("[target] resists the force of your jaunt's wake, bringing you to stop!"))
to_chat(target, span_userdanger("[user] slams into you, falling out of their bluespace jaunt tunnel!"))
Comment on lines +325 to +328

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gravity-ify your fluff a bit

return FALSE
return TRUE

/obj/effect/temp_visual/teleport_abductor/gravitational_jaunter
duration = 5
Binary file modified icons/obj/device.dmi
Binary file not shown.
Loading