Skip to content

Commit

Permalink
[MIRROR] Adds a new mobility option for wizards, the Telegram Scepter…
Browse files Browse the repository at this point in the history
…, not inspired by any relic at all (#987)

* Adds a new mobility option for wizards, the Telegram Scepter, not inspired by any relic at all (#81413)

## About The Pull Request

This PR adds a new mobility option for wizards, the Telegram Scepter, a
magical rod that teleports you anywhere you point it.

But be warned, its method of teleportation is very volatile, putting you
in a state of flux. While the scepter itself has no cooldown, using it
while in flux will damage you severely and purge some of your chems. In
fact, any form of magical teleportation will damage you while in flux.


https://github.com/tgstation/tgstation/assets/51863163/b1008f6c-d724-4b7d-8e47-b70e2e770183

Non-wizards who attempt to utilize the wand will find themselves in a
state of permanent flux, meaning any and all teleports are met with
damage. It's also less precise.

Also adds an admin-only version which has infinite range and applies no
flux.

Any semblances to any other game is purely coincidental, I assure you.

Misc. Details: 

- I added the ability to pass sound datums to playsound, so it doesn't
override custom sound settings you may want.
- I moved the teleport signals out of the pure-sounding proc `check
teleport`.

## Why It's Good For The Game

I've always thought wizard mobility has been rather lacking, in that you
generally only pick Jaunt.


![image](https://github.com/tgstation/tgstation/assets/51863163/6eb2b136-7ec4-445e-b28d-c8841e5bcbe9)

So when someone threw an offhanded suggestion to make a certain item
from a certain game in discord, my wheel started to turn.

And here we are.

- Teleporting anywhere you can see is very powerful (especially in
conjunction with the Scrying Orb) so this makes it a very high potency
item a wizard can pick up, almost like a targeted blink.
- It is, however, offset by a few things: Namely, the flux mechanic
putting a soft cooldown on teleporting (lest you kill yourself), and it
being an item, meaning you may have to pick up Instant Summons for added
safety.

Overall, I thought it to be a very flavorful mobility option for
wizards. Maybe it will shake up spell selection. Who knows?

## Changelog

:cl: Melbert
add: Wizards have a new mobility option available, the Telegram Scepter.
The ability to travel anywhere you can see at the point of a wand... but
at a price?
/:cl:

* Adds a new mobility option for wizards, the Telegram Scepter, not inspired by any relic at all

---------

Co-authored-by: MrMelbert <[email protected]>
  • Loading branch information
2 people authored and StealsThePRs committed Feb 17, 2024
1 parent 01ddf28 commit 0b79929
Show file tree
Hide file tree
Showing 13 changed files with 279 additions and 8 deletions.
2 changes: 0 additions & 2 deletions code/_globalvars/traits/_traits.dm
Original file line number Diff line number Diff line change
Expand Up @@ -270,13 +270,11 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_LIMBATTACHMENT" = TRAIT_LIMBATTACHMENT,
"TRAIT_LITERATE" = TRAIT_LITERATE,
"TRAIT_LIVERLESS_METABOLISM" = TRAIT_LIVERLESS_METABOLISM,
"TRAIT_LIVERLESS_METABOLISM" = TRAIT_LIVERLESS_METABOLISM,
"TRAIT_MADNESS_IMMUNE" = TRAIT_MADNESS_IMMUNE,
"TRAIT_MAGICALLY_GIFTED" = TRAIT_MAGICALLY_GIFTED,
"TRAIT_MAGICALLY_PHASED" = TRAIT_MAGICALLY_PHASED,
"TRAIT_MARTIAL_ARTS_IMMUNE" = TRAIT_MARTIAL_ARTS_IMMUNE,
"TRAIT_MEDIBOTCOMINGTHROUGH" = TRAIT_MEDIBOTCOMINGTHROUGH,
"TRAIT_MEDIBOTCOMINGTHROUGH" = TRAIT_MEDIBOTCOMINGTHROUGH,
"TRAIT_MEDICAL_HUD" = TRAIT_MEDICAL_HUD,
"TRAIT_MESON_VISION" = TRAIT_MESON_VISION,
"TRAIT_MIME_FAN" = TRAIT_MIME_FAN,
Expand Down
2 changes: 1 addition & 1 deletion code/_globalvars/traits/admin_tooling.dm
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list(
"TRAIT_LIMBATTACHMENT" = TRAIT_LIMBATTACHMENT,
"TRAIT_LITERATE" = TRAIT_LITERATE,
"TRAIT_LIVERLESS_METABOLISM" = TRAIT_LIVERLESS_METABOLISM,
"TRAIT_LIVERLESS_METABOLISM" = TRAIT_LIVERLESS_METABOLISM,
"TRAIT_MAGICALLY_GIFTED" = TRAIT_MAGICALLY_GIFTED,
"TRAIT_MEDICAL_HUD" = TRAIT_MEDICAL_HUD,
"TRAIT_MIME_FAN" = TRAIT_MIME_FAN,
"TRAIT_MIMING" = TRAIT_MIMING,
Expand Down
8 changes: 4 additions & 4 deletions code/datums/helper_datums/teleport.dm
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@
teleatom.balloon_alert(teleatom, "something holds you back!")
return FALSE

SEND_SIGNAL(teleatom, COMSIG_MOVABLE_TELEPORTED, destination, channel)
SEND_SIGNAL(destturf, COMSIG_ATOM_INTERCEPT_TELEPORTED, channel, curturf, destturf)

if(isobserver(teleatom))
teleatom.abstract_move(destturf)
return TRUE
Expand All @@ -85,7 +88,7 @@
teleatom.log_message("teleported from [loc_name(curturf)] to [loc_name(destturf)].", LOG_GAME, log_globally = FALSE)
M.cancel_camera()

SEND_SIGNAL(teleatom, COMSIG_MOVABLE_POST_TELEPORT)
SEND_SIGNAL(teleatom, COMSIG_MOVABLE_POST_TELEPORT, destination, channel)

return TRUE

Expand Down Expand Up @@ -209,7 +212,4 @@
if(SEND_SIGNAL(destination_turf, COMSIG_ATOM_INTERCEPT_TELEPORTING, channel, origin_turf, destination_turf) & COMPONENT_BLOCK_TELEPORT)
return FALSE

SEND_SIGNAL(teleported_atom, COMSIG_MOVABLE_TELEPORTED, destination, channel)
SEND_SIGNAL(destination_turf, COMSIG_ATOM_INTERCEPT_TELEPORTED, channel, origin_turf, destination_turf)

return TRUE
2 changes: 1 addition & 1 deletion code/game/sound.dm
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
//allocate a channel if necessary now so its the same for everyone
channel = channel || SSsounds.random_available_channel()

var/sound/S = sound(get_sfx(soundin))
var/sound/S = isdatum(soundin) ? soundin : sound(get_sfx(soundin))
var/maxdistance = SOUND_RANGE + extrarange
var/source_z = turf_source.z
var/list/listeners = SSmobs.clients_by_zlevel[source_z].Copy()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,10 @@
item_path = /obj/item/gun/magic/staff/door
cost = 1
category = "Mobility"

/datum/spellbook_entry/item/teleport_rod
name = /obj/item/teleport_rod::name
desc = /obj/item/teleport_rod::desc
item_path = /obj/item/teleport_rod
cost = 2 // Puts it at 3 cost if you go for safety instant summons, but teleporting anywhere on screen is pretty good.
category = "Mobility"
246 changes: 246 additions & 0 deletions code/modules/antagonists/wizard/equipment/teleport_rod.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/// Totally NOT a Rod of Discord
/// Teleports you to where you click!
/obj/item/teleport_rod
name = "Telegram Scepter"
desc = "A magical rod that teleports you to the location you point it. \
Using it puts you in a state of flux, removing some of your reagents and \
causing you to take damage from further uses until you stabilize once more."
icon_state = "tele_wand_er"
inhand_icon_state = "tele_wand_er"
icon = 'icons/obj/weapons/guns/magic.dmi'
lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF | UNACIDABLE
item_flags = NOBLUDGEON
light_system = OVERLAY_LIGHT
light_color = COLOR_FADED_PINK
light_power = 1
light_range = 2
light_on = TRUE
/// Whether we apply the teleport flux debuff, damaging people who teleport
var/apply_debuffs = TRUE
/// Max range at which we can teleport, because it operates in view TECHNICALLY can click very very far
var/max_tp_range = 8

/obj/item/teleport_rod/Initialize(mapload)
. = ..()
particles = new /particles/teleport_flux/small()

// Admin only version which just teleports you, so spam it all you want
/obj/item/teleport_rod/admin
name = "Harmonious " + parent_type::name
desc = "A magical rod that teleports you anywhere, no questions asked."
apply_debuffs = FALSE
max_tp_range = INFINITY

/obj/item/teleport_rod/equipped(mob/living/user, slot, initial)
. = ..()
if(!isliving(user))
return
if(HAS_MIND_TRAIT(user, TRAIT_MAGICALLY_GIFTED))
return
if(!(slot & ITEM_SLOT_HANDS))
return
if(!apply_debuffs)
return
user.apply_status_effect(/datum/status_effect/teleport_flux/perma)

/obj/item/teleport_rod/dropped(mob/living/user, silent)
. = ..()
if(!isliving(user))
return
if(HAS_MIND_TRAIT(user, TRAIT_MAGICALLY_GIFTED))
return

var/datum/status_effect/teleport_flux/perma/permaflux = user.has_status_effect(/datum/status_effect/teleport_flux/perma)
permaflux?.delayed_remove(src)

/obj/item/teleport_rod/afterattack(atom/target, mob/living/user, proximity_flag, click_parameters)
. = ..()
if(!isliving(user))
return
if(proximity_flag) // assuming you don't want to teleport 1 tile away
return

. |= AFTERATTACK_PROCESSED_ITEM

var/turf/start_turf = get_turf(user)
var/turf/target_turf = get_turf(target)
if(get_dist(start_turf, target_turf) > max_tp_range)
user.balloon_alert(user, "too far!")
return

if(!(target_turf in view(user, user.client?.view || world.view)))
user.balloon_alert(user, "out of view!")
return

if(target_turf.is_blocked_turf(exclude_mobs = TRUE, source_atom = user))
user.balloon_alert(user, "obstructed!")
return

var/tp_result = do_teleport(
teleatom = user,
destination = target_turf,
precision = (HAS_MIND_TRAIT(user, TRAIT_MAGICALLY_GIFTED) || !apply_debuffs) ? 0 : 2,
no_effects = TRUE,
channel = TELEPORT_CHANNEL_MAGIC,
)

if(!tp_result)
user.balloon_alert(user, "teleport failed!")
return

var/sound/teleport_sound = sound('sound/magic/summonitems_generic.ogg')
teleport_sound.pitch = 0.5
// Handle our own pizzaz rather than doing it in do_teleport
new /obj/effect/temp_visual/teleport_flux(start_turf, user.dir)
new /obj/effect/temp_visual/teleport_flux(get_turf(user), user.dir)
playsound(start_turf, teleport_sound, 90, extrarange = MEDIUM_RANGE_SOUND_EXTRARANGE)
playsound(user, teleport_sound, 90, extrarange = MEDIUM_RANGE_SOUND_EXTRARANGE)
// Some extra delay to prevent accidental double clicks
user.changeNext_move(CLICK_CD_SLOW * 1.2)

if(!apply_debuffs)
return

// Teleporting leaves some of your reagents behind!
// (Primarily a way to prevent cheese with damage healing chem mixes,
// but also serves as a counter-counter to stuff like mute toxin.)
var/obj/item/organ/user_stomach = user.get_organ_slot(ORGAN_SLOT_STOMACH)
user.reagents?.remove_all_direct(0.33)
user_stomach?.reagents?.remove_all_direct(0.33)
if(user.has_status_effect(/datum/status_effect/teleport_flux/perma))
return

if(user.has_status_effect(/datum/status_effect/teleport_flux))
// The status effect handles the damage, but we'll add a special pop up for rod usage specifically
user.balloon_alert(user, "too soon!")

user.apply_status_effect(/datum/status_effect/teleport_flux)

/// Temp visual displayed on both sides of a teleport rod teleport
/obj/effect/temp_visual/teleport_flux
icon_state = "blank_white"
color = COLOR_MAGENTA
alpha = 255
duration = 2 SECONDS
light_color = COLOR_MAGENTA
light_power = 2
light_range = 1
light_on = TRUE
randomdir = FALSE

/obj/effect/temp_visual/teleport_flux/Initialize(mapload, copy_dir = SOUTH)
. = ..()
setDir(copy_dir)
particles = new /particles/teleport_flux()
apply_wibbly_filters(src)
animate(src, alpha = 0, time = duration, flags = ANIMATION_PARALLEL)

/// Status effect applied to users of a Teleport Rod, damages them when they teleport
/datum/status_effect/teleport_flux
id = "teleport_flux"
status_type = STATUS_EFFECT_REFRESH
duration = 6 SECONDS
alert_type = /atom/movable/screen/alert/status_effect/teleport_flux
remove_on_fullheal = TRUE // staff of healing ~synergy~

/// Amount of damage to deal when teleporting in flux
var/tp_damage = 15
/// Damage type to deal when teleporting in flux
var/tp_damage_type = BRUTE

/datum/status_effect/teleport_flux/on_apply()
RegisterSignal(owner, COMSIG_MOVABLE_POST_TELEPORT, PROC_REF(teleported))
return TRUE

/datum/status_effect/teleport_flux/on_remove()
UnregisterSignal(owner, COMSIG_MOVABLE_POST_TELEPORT)

/datum/status_effect/teleport_flux/proc/teleported(mob/living/source, turf/destination, channel)
SIGNAL_HANDLER

if(channel != TELEPORT_CHANNEL_MAGIC)
return

owner.apply_damage(
damage = tp_damage,
damagetype = tp_damage_type,
spread_damage = TRUE,
forced = TRUE,
)
log_combat(owner, owner, "teleported too soon")

/datum/status_effect/teleport_flux/update_particles()
if(isnull(particle_effect))
particle_effect = new(owner, /particles/teleport_flux)

particle_effect.alpha = 200
var/original_duration = initial(duration)
if(original_duration == -1)
return
animate(particle_effect, alpha = 50, time = original_duration)

/datum/status_effect/teleport_flux/refresh(effect, ...)
. = ..()
update_particles()

/datum/status_effect/teleport_flux/perma
id = "perma_teleport_flux"
status_type = STATUS_EFFECT_REPLACE
duration = -1
alert_type = /atom/movable/screen/alert/status_effect/teleport_flux/perma
remove_on_fullheal = FALSE

/datum/status_effect/teleport_flux/perma/on_apply()
. = ..()
RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_MAGICALLY_GIFTED), PROC_REF(gained_gift))

/datum/status_effect/teleport_flux/perma/on_remove()
. = ..()
UnregisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_MAGICALLY_GIFTED))

/datum/status_effect/teleport_flux/perma/proc/gained_gift(mob/living/source, trait)
SIGNAL_HANDLER

delayed_remove()

/// Used to fade out the effect and remove it after a delay
/// This cannot be interrupted, but if a new permaflux effect is applied,
/// this one will be deleted instantly anyways, making it moot
/datum/status_effect/teleport_flux/perma/proc/delayed_remove()
var/del_duration = /datum/status_effect/teleport_flux::duration
QDEL_IN(src, del_duration)
animate(particle_effect, alpha = 50, del_duration)

/// Alert for the Teleport Flux status effect
/atom/movable/screen/alert/status_effect/teleport_flux
name = "Teleport Flux"
desc = "Your body exists in a state of flux, making further teleportation dangerous."
icon_state = "flux"

/atom/movable/screen/alert/status_effect/teleport_flux/perma
name = "Permanent " + parent_type::name
desc = "Your lack of magical talent has left you in a state of flux, making further teleportation dangerous."

/// Particles for Teleport Flux and other similar effects
/particles/teleport_flux
icon = 'icons/effects/particles/echo.dmi'
icon_state = list("echo1" = 3, "echo2" = 1, "echo3" = 1)
width = 40
height = 80
count = 1000
spawning = 3
lifespan = 1 SECONDS
fade = 1 SECONDS
friction = 0.5
position = generator(GEN_SPHERE, 12, 12, NORMAL_RAND)
drift = generator(GEN_VECTOR, list(-1, 1), list(1, 1), NORMAL_RAND)
color = COLOR_MAGENTA

/particles/teleport_flux/small
spawning = 1.5
scale = 0.75
lifespan = 0.5 SECONDS
position = generator(GEN_SPHERE, 4, 12, NORMAL_RAND)
drift = generator(GEN_VECTOR, list(-1, 1), list(1, 2), NORMAL_RAND)
19 changes: 19 additions & 0 deletions code/modules/reagents/chemistry/holder/holder.dm
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,25 @@
handle_reactions()
return round(total_removed_amount, CHEMICAL_VOLUME_ROUNDING)

/**
* Like remove_all but removes a percentage of every reagent directly rather than by volume
*
* Arguments
* * percentage - the percentage of each reagent to remove
*/
/datum/reagents/proc/remove_all_direct(percentage = 0.5)
if(!total_volume)
return 0
if(percentage <= 0)
stack_trace("non positive percentage passed to remove all reagents direct [percentage]")
return 0
var/total_removed_amount = 0
for(var/datum/reagent/reagent as anything in reagent_list)
total_removed_amount += remove_reagent(reagent.type, reagent.volume * percentage)

handle_reactions()
return round(total_removed_amount, CHEMICAL_VOLUME_ROUNDING)

/**
* Removes an specific reagent from this holder
* Arguments
Expand Down
Binary file modified icons/effects/effects.dmi
Binary file not shown.
Binary file modified icons/hud/screen_alert.dmi
Binary file not shown.
Binary file modified icons/mob/inhands/weapons/staves_lefthand.dmi
Binary file not shown.
Binary file modified icons/mob/inhands/weapons/staves_righthand.dmi
Binary file not shown.
Binary file modified icons/obj/weapons/guns/magic.dmi
Binary file not shown.
1 change: 1 addition & 0 deletions tgstation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -3296,6 +3296,7 @@
#include "code\modules\antagonists\wizard\equipment\chuunibyou_spell.dm"
#include "code\modules\antagonists\wizard\equipment\enchanted_clown_suit.dm"
#include "code\modules\antagonists\wizard\equipment\soulstone.dm"
#include "code\modules\antagonists\wizard\equipment\teleport_rod.dm"
#include "code\modules\antagonists\wizard\equipment\wizard_spellbook.dm"
#include "code\modules\antagonists\wizard\equipment\spellbook_entries\_entry.dm"
#include "code\modules\antagonists\wizard\equipment\spellbook_entries\assistance.dm"
Expand Down

0 comments on commit 0b79929

Please sign in to comment.