diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 23f5a5c51f7..9a10f4b6230 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -113,6 +113,8 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define isrogueobserver(A) (istype(A, /mob/dead/observer/rogue))
+#define is_scryeye(A) (istype(A, /mob/scry_eye))
+
#define isdead(A) (istype(A, /mob/dead))
#define isnewplayer(A) (istype(A, /mob/dead/new_player))
diff --git a/code/__DEFINES/traits/definitions.dm b/code/__DEFINES/traits/definitions.dm
index e1d9a1e0f70..85a19ffed28 100644
--- a/code/__DEFINES/traits/definitions.dm
+++ b/code/__DEFINES/traits/definitions.dm
@@ -311,8 +311,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_ANTIMAGIC_NO_SELFBLOCK "anti_magic_no_selfblock"
/// makes your footsteps completely silent
#define TRAIT_SILENT_FOOTSTEPS "silent_footsteps"
-/// Hides the SSD indicator. Used with scrying.
-#define TRAIT_NOSSDINDICATOR "nossdindicator"
/// Instant grabs on someone else.
#define TRAIT_NOSTRUGGLE "nostruggle"
/// Black-bagged. More snowflaking.
diff --git a/code/_onclick/hud/ghost.dm b/code/_onclick/hud/ghost.dm
index 92d5e97767c..fcf8a0abade 100644
--- a/code/_onclick/hud/ghost.dm
+++ b/code/_onclick/hud/ghost.dm
@@ -24,8 +24,6 @@
return
if(ghost.client)
if(ghost.client.holder)
- if(istype(ghost, /mob/dead/observer/rogue/arcaneeye))
- return
if(istype(ghost, /mob/dead/observer/profane)) // Souls trapped by a dagger can return to lobby if they want
if(alert("Return to the lobby?", "", "Yes", "No") == "Yes")
ghost.returntolobby()
diff --git a/code/datums/components/orbiter.dm b/code/datums/components/orbiter.dm
index 00732b29525..01e1efd4f9c 100644
--- a/code/datums/components/orbiter.dm
+++ b/code/datums/components/orbiter.dm
@@ -102,8 +102,7 @@
orbiter.glide_size = movable_parent.glide_size
orbiter.abstract_move(get_turf(parent))
- if(!istype(orbiter, /mob/dead/observer/screye))
- to_chat(orbiter, span_notice("Now orbiting [parent]."))
+ to_chat(orbiter, span_notice("Now orbiting [parent]."))
/datum/component/orbiter/proc/end_orbit(atom/movable/orbiter, refreshing=FALSE)
if(!orbiter_list[orbiter])
diff --git a/code/game/objects/items/inquisition_relics.dm b/code/game/objects/items/inquisition_relics.dm
index 30bce46fce7..6338d94e633 100644
--- a/code/game/objects/items/inquisition_relics.dm
+++ b/code/game/objects/items/inquisition_relics.dm
@@ -1128,24 +1128,20 @@
var/usesleft = 3
var/active = FALSE
var/broken = FALSE
- /// Target name
- var/datum/weakref/fixation
- /// One with the bleed in the mirror
- var/datum/weakref/feeder
var/atom/movable/screen/alert/blackmirror/effect
var/datum/looping_sound/blackmirror/soundloop
+ var/datum/scrying_component/mirror/scry_comp
/obj/item/inqarticles/bmirror/Initialize()
. = ..()
soundloop = new(src, FALSE)
+ scry_comp = new(src)
/obj/item/inqarticles/bmirror/Destroy()
if(soundloop)
QDEL_NULL(soundloop)
if(effect)
QDEL_NULL(effect)
- fixation = null
- feeder = null
return ..()
/obj/item/inqarticles/bmirror/examine(mob/user)
@@ -1160,13 +1156,7 @@
active = FALSE
fedblood = FALSE
openstate = "bloody"
- feeder = null
- var/mob/living/fixated = fixation?.resolve()
- if(fixated)
- fixated.clear_alert("blackmirror", TRUE)
- fixated.playsound_local(src, 'sound/items/blackeye.ogg', 40, FALSE)
effect = null
- fixation = null
usesleft--
soundloop.stop()
visible_message(span_info("[src] clouds itself with a chilling fog."))
@@ -1213,67 +1203,9 @@
to_chat(user, span_warning("It looks like it needs blood to work properly."))
return
- if(!active)
- var/input = browser_alert(user, "WHAT DO YOU SEEK?", "THE PRICE IS PAID", list("BLOOD", "FIXATION"))
- if(!input || QDELETED(user) || QDELETED(src))
- return
-
- var/mob/living/carbon/human/target
-
- if(input == "FIXATION")
- var/name = browser_input_text(user, "WHO DO YOU SEEK?", "THE PRICE IS PAID")
- if(!name)
- return
- for(var/mob/living/carbon/human/HL as anything in GLOB.player_list)
- if(HL.real_name == name)
- fixation = WEAKREF(HL)
- target = HL
- playsound(src, 'sound/items/blackmirror_no.ogg', 100, FALSE)
- to_chat(user, span_warning("[src] makes a grating sound."))
- return
- else if(input == "BLOOD")
- target = feeder?.resolve()
-
- if(!target)
- return
-
- active = TRUE
- openstate = "active"
- update_appearance(UPDATE_ICON_STATE)
- soundloop.start()
-
- effect = target.throw_alert("blackmirror", /atom/movable/screen/alert/blackmirror, override = TRUE)
- effect.source = src
-
- target.playsound_local(target, 'sound/items/blackeye_warn.ogg', 100, FALSE)
-
- playsound(src, 'sound/items/blackmirror_active.ogg', 100, FALSE)
- addtimer(CALLBACK(src, PROC_REF(donefixating)), 2 MINUTES, TIMER_UNIQUE)
-
- message_admins("SCRYING: [user.real_name] ([user.ckey]) has fixated on [target.real_name] ([target.ckey]) via black mirror.")
- log_game("SCRYING: [user.real_name] ([user.ckey]) has fixated on [target.real_name] ([target.ckey]) via black mirror.")
- return
-
- var/datum/weakref/lookat = fixation ? fixation : feeder
- var/mob/living/target = lookat?.resolve()
- if(!target)
- to_chat(user, span_notice("The mirror remains clear..."))
- return
-
- playsound(src, 'sound/items/blackmirror_use.ogg', 100, FALSE)
-
- ADD_TRAIT(user, TRAIT_NOSSDINDICATOR, "blackmirror")
-
- var/mob/dead/observer/screye/blackmirror/S = user.scry_ghost()
- if(!S)
- return
- S.ManualFollow(target)
- S.add_client_colour(/datum/client_colour/nocshaded)
- user.visible_message(span_warning("[user] stares into [src], their eyes glazing over..."))
+ //add_client_colour(/datum/client_colour/nocshaded)
- addtimer(CALLBACK(S, TYPE_PROC_REF(/mob/dead/observer, reenter_corpse)), 4 SECONDS)
- addtimer(CALLBACK(user, GLOBAL_PROC_REF(playsound), user, 'sound/items/blackeye.ogg', 100, FALSE), 4 SECONDS)
- addtimer(TRAIT_CALLBACK_REMOVE(user, TRAIT_NOSSDINDICATOR, "blackmirror"), 4 SECONDS)
+ scry_comp.activate(user)
/obj/item/inqarticles/bmirror/attack(mob/living/carbon/human/attacked, mob/living/carbon/human/user, list/modifiers)
if(!istype(attacked) || !istype(user))
@@ -1283,7 +1215,7 @@
to_chat(user, span_warning("I need to open it first."))
return
- if(feeder)
+ if(fedblood)
to_chat(user, span_warning("It's already been fed."))
return
@@ -1309,7 +1241,6 @@
attacked.adjustBruteLoss(40)
attacked.blood_volume = max(attacked.blood_volume - 240, 0)
attacked.handle_blood()
- feeder = WEAKREF(attacked)
openstate = "bloody"
fedblood = TRUE
update_appearance(UPDATE_ICON_STATE)
@@ -1339,6 +1270,14 @@
to_chat(user, span_warning("I cannot close the mirror while it's active."))
return
+ opened = !opened
+ if(opened)
+ playsound(src, 'sound/items/blackmirror_open.ogg', 100, FALSE)
+ else
+ playsound(src, 'sound/items/blackmirror_shut.ogg', 100, FALSE)
+ update_appearance(UPDATE_ICON_STATE)
+
+/*
var/mob/living/fixated = fixation?.resolve()
if(opened)
if(fixated)
@@ -1356,10 +1295,10 @@
if(fixated)
fixated.playsound_local(src, 'sound/items/blackeye_warn.ogg', 100, FALSE)
effect = fixated.throw_alert("blackmirror", /atom/movable/screen/alert/blackmirror, override = TRUE)
- effect.source = src
opened = TRUE
update_appearance(UPDATE_ICON_STATE)
+*/
/obj/item/inqarticles/bmirror/update_icon_state()
. = ..()
@@ -1373,37 +1312,6 @@
name = "BLACK EYE"
desc = "LOOK AT ME. I SEE YOU."
icon_state = "blackeye"
- var/obj/item/inqarticles/bmirror/source
-
-/atom/movable/screen/alert/blackmirror/Destroy()
- source = null
- return ..()
-
-/atom/movable/screen/alert/blackmirror/Click()
- var/mob/living/L = usr
- if(!istype(L))
- return
-
- var/datum/weakref/lookat = null
- if(alert(L, "KEEP LOOKING, WHAT WILL YOU FIND?", "BLACK EYED GAZE", "BLOOD", "MIRROR") != "BLOOD")
- lookat = source
- else
- lookat = source.feeder
- playsound(L, 'sound/items/blackmirror_use.ogg', 100, FALSE)
- ADD_TRAIT(L, TRAIT_NOSSDINDICATOR, "blackmirror")
- var/mob/living/target = lookat?.resolve()
- if(!target)
- return
- var/mob/dead/observer/screye/blackmirror/S = L.scry_ghost()
- if(!S)
- return
- S.ManualFollow(target)
- S.add_client_colour(/datum/client_colour/nocshaded)
- L.visible_message(span_warning("[L] looks inward as their eyes glaze over..."))
-
- addtimer(CALLBACK(S, TYPE_PROC_REF(/mob/dead/observer, reenter_corpse)), 4 SECONDS)
- addtimer(CALLBACK(L, GLOBAL_PROC_REF(playsound), L, 'sound/items/blackeye.ogg', 100, FALSE), 4 SECONDS)
- addtimer(TRAIT_CALLBACK_REMOVE(L, TRAIT_NOSSDINDICATOR, "blackmirror"), 4 SECONDS)
// FINISH THIS AT YOUR LEISURE. I'M JUST LEAVING IT HERE UNIMPLEMENTED. IT'S INTENDED TO WORK AS A COMBINATION OF THE NOC FAR-SIGHT AND THE NOCSHADES. HAVE FUN! - YISCHE
/obj/item/inqarticles/spyglass
diff --git a/code/game/objects/items/magic.dm b/code/game/objects/items/magic.dm
deleted file mode 100644
index ec76e5797f6..00000000000
--- a/code/game/objects/items/magic.dm
+++ /dev/null
@@ -1,145 +0,0 @@
-/////////////////////////////////////////Scrying///////////////////
-
-/obj/item/scrying
- name = "scrying orb"
- desc = "On its glass depths, you can scry on many unsuspecting beings..."
- icon = 'icons/roguetown/items/misc.dmi'
- icon_state ="scrying"
- throw_speed = 3
- throw_range = 7
- throwforce = 15
- damtype = BURN
- force = 15
- hitsound = 'sound/blank.ogg'
- sellprice = 30
- dropshrink = 0.6
-
- grid_height = 32
- grid_width = 32
-
- var/mob/current_owner
- var/last_scry
- var/cooldown = 30 SECONDS
-
-/obj/item/scrying/eye
- name = "accursed eye"
- desc = "It is pulsating."
- icon = 'icons/roguetown/items/misc.dmi'
- icon_state ="scryeye"
- cooldown = 5 MINUTES
-
-/obj/item/scrying/attack_self(mob/user, list/modifiers)
- . = ..()
- if(world.time < last_scry + cooldown)
- to_chat(user, span_warning("I look into [src] but only see inky smoke. Maybe I should wait."))
- return
- var/input = stripped_input(user, "Who are you looking for?", "Scrying Orb")
- if(!input)
- return
- if(!user.key)
- return
- if(!user.mind || !user.mind.do_i_know(name=input))
- to_chat(user, span_warning("I don't know anyone by that name."))
- return
- //check is applied twice to prevent someone from bypassing the cooldown
- if(world.time < last_scry + cooldown)
- to_chat(user, span_warning("I look into [src] but only see inky smoke. Maybe I should wait."))
- return
- for(var/mob/living/carbon/human/HL in GLOB.human_list)
- if(HL.real_name == input)
- var/turf/T = get_turf(HL)
- if(!T)
- continue
- if(HAS_TRAIT(HL, TRAIT_ANTISCRYING))
- to_chat(user, span_warning("I peer into [src], but an impenetrable fog shrouds [input]."))
- to_chat(HL, span_warning("My magical shrouding reacted to something."))
- return
- log_game("SCRYING: [user.real_name] ([user.ckey]) has used the scrying orb to leer at [HL.real_name] ([HL.ckey])")
- ADD_TRAIT(user, TRAIT_NOSSDINDICATOR, "scryingorb")
- var/mob/dead/observer/screye/S = user.scry_ghost()
- if(!S)
- return
- S.ManualFollow(HL)
- last_scry = world.time
- user.visible_message(span_danger("[user] stares into [src], [p_their()] eyes rolling back into [p_their()] head."))
- addtimer(CALLBACK(S, TYPE_PROC_REF(/mob/dead/observer, reenter_corpse)), 8 SECONDS)
- if(!HL.stat)
- if(HL.STAPER >= 15)
- if(HL.mind)
- if(HL.mind.do_i_know(name=user.real_name))
- to_chat(HL, span_warning("I can clearly see the face of [user.real_name] staring at me!"))
- to_chat(user, span_warning("[HL.real_name] stares back at me!"))
- return
- to_chat(HL, span_warning("I can clearly see the face of an unknown [user.gender == FEMALE ? "woman" : "man"] staring at me!"))
- return
- if(HL.STAPER >= 11)
- to_chat(HL, span_warning("I feel a pair of unknown eyes on me."))
- REMOVE_TRAIT(user, TRAIT_NOSSDINDICATOR, "scryingorb")
- return
- to_chat(user, span_warning("I peer into [src], but can't find [input]."))
- return
-
-//23.08.2025
-//crystallball and nocdevice are depreciated?
-
-/////////////////////////////////////////Crystal ball ghsot vision///////////////////
-
-/obj/item/crystalball/attack_self(mob/user, list/modifiers)
- user.visible_message("[user] stares into [src], their eyes rolling back into their head.")
- user.ghostize(1)
-
-/* .................. NOC Device (Fixed scrying ball) ................... */
-/obj/structure/nocdevice
- name = "NOC Device"
- desc = "An intricate lunar observation machine, that allows its user to study the face of Noc in the sky, reflecting the true whereabouts of hidden beings..."
- icon = 'icons/roguetown/misc/96x96.dmi'
- icon_state = "nocdevice"
- plane = -1
- layer = 4.2
- var/last_scry
-
-/obj/structure/nocdevice/attack_hand(mob/user)
- . = ..()
- var/mob/living/carbon/human/H = user
- if(H.virginity)
- if(world.time < last_scry + 30 SECONDS)
- to_chat(user, "I peer into the sky but cannot focus the lens on the face of Noc. Maybe I should wait.")
- return
- var/input = stripped_input(user, "Who are you looking for?", "Scrying Orb")
- if(!input)
- return
- if(!user.key)
- return
- if(world.time < last_scry + 30 SECONDS)
- to_chat(user, "I peer into the sky but cannot focus the lens on the face of Noc. Maybe I should wait.")
- return
- if(!user.mind || !user.mind.do_i_know(name=input))
- to_chat(user, "I don't know anyone by that name.")
- return
- for(var/mob/living/carbon/human/HL in GLOB.human_list)
- if(HL.real_name == input)
- var/turf/T = get_turf(HL)
- if(!T)
- continue
- var/mob/dead/observer/screye/S = user.scry_ghost()
- if(!S)
- return
- S.ManualFollow(HL)
- last_scry = world.time
- user.visible_message("[user] stares into [src], [p_their()] squinting and concentrating...")
- addtimer(CALLBACK(S, TYPE_PROC_REF(/mob/dead/observer, reenter_corpse)), 8 SECONDS)
- if(!HL.stat)
- if(HL.STAPER >= 15)
- if(HL.mind)
- if(HL.mind.do_i_know(name=user.real_name))
- to_chat(HL, "I can clearly see the face of [user.real_name] staring at me!.")
- return
- to_chat(HL, "I can clearly see the face of an unknown [user.gender == FEMALE ? "woman" : "man"] staring at me!")
- return
- if(HL.STAPER >= 11)
- to_chat(HL, "I feel a pair of unknown eyes on me.")
- return
- to_chat(user, "I peer into the viewpiece, but Noc does not reveal where [input] is.")
- return
- else
- to_chat(user, "Noc looks angry with me...")
diff --git a/code/game/objects/items/scrying.dm b/code/game/objects/items/scrying.dm
new file mode 100644
index 00000000000..923b30fa85f
--- /dev/null
+++ b/code/game/objects/items/scrying.dm
@@ -0,0 +1,433 @@
+/datum/scrying_component
+ var/name = "scrying component"
+
+ var/text_cooldown_fail = "I look into NAME_HERE but only see inky smoke. Maybe I should wait."
+
+ var/vision_duration = 8 SECONDS
+ var/cooldown_duration = 30 SECONDS
+
+ /// Whether or not the user of the scrying device needs to personally know the identity of their target.
+ var/needs_to_know = TRUE
+ /// Whether or not the target needs to be alive
+ var/needs_to_live = TRUE
+
+ var/mob/scry_eye/scrying_eye
+ var/mob/living/carbon/held_user
+ COOLDOWN_DECLARE(scry_cooldown)
+
+/datum/scrying_component/New(obj/item/scrying/parent)
+ . = ..()
+ text_cooldown_fail = replacetext(text_cooldown_fail, "NAME_HERE", "\the [name]")
+ if(!parent)
+ return
+ RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(handle_parent_del), parent)
+
+/datum/scrying_component/proc/handle_parent_del(var/obj/item/scrying/parent)
+ SIGNAL_HANDLER
+ parent?.scry_comp = null
+ qdel(src)
+
+/datum/scrying_component/proc/activate(mob/living/user)
+ if(!pass_extra_checks())
+ return FALSE
+
+ if(!COOLDOWN_FINISHED(src, scry_cooldown))
+ to_chat(user, span_warning(text_cooldown_fail))
+ return FALSE
+
+ var/search_name = stripped_input(user, "Who are you looking for?", name)
+ if(!search_name)
+ return FALSE
+
+ if(!user.mind || (needs_to_know && !user.mind.do_i_know(name = search_name)))
+ to_chat(user, span_warning("I don't know anyone by that name."))
+ return FALSE
+
+ //check is applied twice to prevent someone from bypassing the cooldown
+ if(!COOLDOWN_FINISHED(src, scry_cooldown))
+ to_chat(user, span_warning(text_cooldown_fail))
+ return FALSE
+
+ var/mob/living/carbon/human/found_target
+ for(var/mob/living/carbon/human/human_target in GLOB.human_list)
+ if(human_target.real_name == search_name)
+ var/turf/target_turf = get_turf(human_target)
+ if(!target_turf)
+ continue
+ found_target = human_target
+ break
+
+ held_user = user
+ if(HAS_TRAIT(found_target, TRAIT_ANTISCRYING))
+ to_chat(user, span_warning("I peer into \the [name], but an impenetrable fog shrouds [search_name]."))
+ to_chat(found_target, span_warning("My magical shrouding reacted to something."))
+ held_user = null
+ return
+
+ create_eye()
+ if(!scrying_eye)
+ remove_eye(TRUE)
+ return
+
+ if(needs_to_live && found_target.stat)
+ to_chat(user, span_warning("I peer into \the [name], but can't find [search_name]."))
+ remove_eye(TRUE)
+ return FALSE
+
+ log_game("SCRYING: [user.real_name] ([user.ckey]) has used the [name] to leer at [found_target.real_name] ([found_target.ckey])")
+
+ var/real_cooldown = cooldown_duration + vision_duration
+ COOLDOWN_START(src, scry_cooldown, real_cooldown)
+ user.visible_message(span_danger("[user] stares into \the [name], [user.p_their()] eyes rolling back into [user.p_their()] head."), span_warning("My eyes roll into the back of my head as I'm lost in the depths of the orb."))
+ scrying_eye.orbit(found_target)
+ if(found_target.STAPER >= 15)
+ if(found_target.mind)
+ if(found_target.mind.do_i_know(name = user.real_name))
+ to_chat(found_target, span_warning("I can clearly see the face of [user.real_name] staring at me!"))
+ to_chat(user, span_warning("[found_target.real_name] stares back at me!"))
+ return TRUE
+ to_chat(found_target, span_warning("I can clearly see the face of an unknown [user.gender == FEMALE ? "woman" : "man"] staring at me!"))
+ return TRUE
+ if(found_target.STAPER >= 11)
+ to_chat(found_target, span_warning("I feel a pair of unknown eyes on me."))
+ return TRUE
+
+/datum/scrying_component/proc/create_eye()
+ if(!held_user)
+ return FALSE
+ scrying_eye = new
+ scrying_eye.component = src
+ held_user.reset_perspective(scrying_eye)
+ held_user.Immobilize(vision_duration)
+ held_user.overlay_fullscreen("scrying", /atom/movable/screen/backhudl/obs)
+ addtimer(CALLBACK(src, PROC_REF(remove_eye)), vision_duration)
+
+/datum/scrying_component/proc/remove_eye(early = FALSE)
+ if(!held_user)
+ return FALSE
+ held_user.reset_perspective(held_user)
+ held_user.clear_fullscreen("scrying")
+ if(early)
+ held_user.SetImmobilized(2 SECONDS)
+ QDEL_NULL(scrying_eye)
+ held_user = null
+
+
+/datum/scrying_component/proc/pass_extra_checks(mob/living/user)
+ return TRUE
+
+/datum/scrying_component/orb
+ name = "Scrying Orb"
+
+/datum/scrying_component/eye
+ name = "Accursed Eye"
+ cooldown_duration = 5 MINUTES
+
+/datum/scrying_component/vampire
+ name = "Night's Eye"
+ needs_to_know = FALSE
+ needs_to_live = FALSE
+
+/datum/scrying_component/telescope
+ name = "NOC Device"
+ text_cooldown_fail = "I peer into the sky but cannot focus the lens on the face of Noc. Maybe I should wait."
+
+/datum/scrying_component/telescope/pass_extra_checks(mob/living/user)
+ var/mob/living/carbon/human/human_user = user
+ if(!ishuman(human_user) || !human_user.virginity)
+ to_chat(human_user, span_notice("Noc looks angry with me..."))
+ return FALSE
+ return TRUE
+
+/datum/scrying_component/mirror
+ name = "Black Mirror"
+ vision_duration = 4 SECONDS
+ needs_to_know = FALSE
+ needs_to_live = FALSE
+ var/obj/item/inqarticles/bmirror/parent_mirror
+ var/mob/stored_target
+
+/datum/scrying_component/mirror/New(obj/item/scrying/parent)
+ . = ..()
+ parent_mirror = parent
+ if(!istype(parent_mirror))
+ UnregisterSignal(parent, COMSIG_PARENT_QDELETING)
+ qdel(src)
+
+/datum/scrying_component/mirror/activate(mob/living/user)
+ if(!pass_extra_checks())
+ return FALSE
+
+ if(!COOLDOWN_FINISHED(src, scry_cooldown))
+ to_chat(user, span_warning(text_cooldown_fail))
+ return FALSE
+
+ var/search_name = stripped_input(user, "Who are you looking for?", name)
+ if(!search_name)
+ return FALSE
+
+ if(!user.mind)
+ to_chat(user, span_warning("I don't know of anyone by that name."))
+ return FALSE
+
+ //check is applied twice to prevent someone from bypassing the cooldown
+ if(!COOLDOWN_FINISHED(src, scry_cooldown))
+ to_chat(user, span_warning(text_cooldown_fail))
+ return FALSE
+
+ for(var/mob/living/carbon/human/human_target in GLOB.human_list)
+ if(human_target.real_name == search_name)
+ var/turf/target_turf = get_turf(human_target)
+ if(!target_turf)
+ continue
+ stored_target = human_target
+ break
+
+ held_user = user
+ if(HAS_TRAIT(stored_target, TRAIT_ANTISCRYING))
+ to_chat(user, span_warning("I peer into \the [name], but an impenetrable fog shrouds [search_name]."))
+ to_chat(stored_target, span_warning("My magical shrouding reacted to something."))
+ held_user = null
+ return
+
+ create_eye()
+ if(!scrying_eye)
+ remove_eye(TRUE)
+ return
+
+ log_game("SCRYING: [user.real_name] ([user.ckey]) has used the [name] to leer at [stored_target.real_name] ([stored_target.ckey])")
+
+ var/real_cooldown = cooldown_duration + vision_duration
+ COOLDOWN_START(src, scry_cooldown, real_cooldown)
+ user.visible_message(span_danger("[user] stares into \the [name], [user.p_their()] eyes rolling back into [user.p_their()] head."), span_warning("My eyes roll into the back of my head as I'm lost in the depths of the orb."))
+ apply_black_eye()
+ return TRUE
+
+/datum/scrying_component/mirror/create_eye()
+ if(!held_user)
+ return FALSE
+ scrying_eye = new
+ scrying_eye.component = src
+ held_user.reset_perspective(scrying_eye)
+ held_user.Immobilize(vision_duration)
+ held_user.overlay_fullscreen("scrying", /atom/movable/screen/backhudl/obs)
+ playsound(held_user, 'sound/items/blackmirror_use.ogg', 100, FALSE)
+ addtimer(CALLBACK(src, PROC_REF(remove_eye)), vision_duration)
+
+/datum/scrying_component/mirror/remove_eye(early = FALSE)
+ if(!held_user)
+ return FALSE
+ held_user.reset_perspective(held_user)
+ held_user.clear_fullscreen("scrying")
+ playsound(held_user, 'sound/items/blackeye.ogg', 100, FALSE)
+ if(early)
+ held_user.SetImmobilized(2 SECONDS)
+ QDEL_NULL(scrying_eye)
+ held_user = null
+ if(stored_target)
+ stored_target.clear_alert("blackmirror", TRUE)
+ stored_target.playsound_local(src, 'sound/items/blackeye.ogg', 40, FALSE)
+ stored_target = null
+ parent_mirror.donefixating()
+
+/datum/scrying_component/mirror/proc/apply_black_eye()
+ scrying_eye.orbit(stored_target)
+ parent_mirror.effect = stored_target.throw_alert("blackmirror", /atom/movable/screen/alert/blackmirror, override = TRUE)
+ playsound(stored_target, 'sound/items/blackeye_warn.ogg', 100, FALSE)
+
+
+/////////////////////////////////////////Scrying///////////////////
+
+/obj/item/scrying
+ name = "scrying orb"
+ desc = "On its glass depths, you can scry on many unsuspecting beings..."
+ icon = 'icons/roguetown/items/misc.dmi'
+ icon_state ="scrying"
+ throw_speed = 3
+ throw_range = 7
+ throwforce = 15
+ damtype = BURN
+ force = 15
+ hitsound = 'sound/blank.ogg'
+ sellprice = 30
+ dropshrink = 0.6
+
+ grid_height = 32
+ grid_width = 32
+
+ var/datum/scrying_component/scry_comp
+ var/scry_comp_path = /datum/scrying_component/orb
+
+/obj/item/scrying/Initialize(mapload)
+ . = ..()
+ scry_comp = new scry_comp_path(src)
+
+/obj/item/scrying/eye
+ name = "accursed eye"
+ desc = "It is pulsating."
+ icon = 'icons/roguetown/items/misc.dmi'
+ icon_state ="scryeye"
+
+ scry_comp_path = /datum/scrying_component/eye
+
+/obj/item/scrying/attack_self(mob/user, list/modifiers)
+ . = ..()
+ scry_comp.activate(user)
+
+//23.08.2025
+//nocdevice depreciated?
+
+/* .................. NOC Device (Fixed scrying ball) ................... */
+/obj/structure/nocdevice
+ name = "NOC Device"
+ desc = "An intricate lunar observation machine, that allows its user to study the face of Noc in the sky, reflecting the true whereabouts of hidden beings..."
+ icon = 'icons/roguetown/misc/96x96.dmi'
+ icon_state = "nocdevice"
+ plane = -1
+ layer = 4.2
+ var/datum/scrying_component/telescope/scry_comp
+
+/obj/structure/nocdevice/Initialize()
+ . = ..()
+ scry_comp = new(src)
+
+/obj/structure/nocdevice/attack_hand(mob/user)
+ . = ..()
+ scry_comp.activate(user)
+
+/* .................. THE EYE ................... */
+/mob/scry_eye
+ sight = SEE_TURFS | SEE_MOBS | SEE_OBJS
+ see_in_dark = 100
+ hud_type = /datum/hud/obs
+ invisibility = INVISIBILITY_GHOST
+ see_invisible = SEE_INVISIBLE_LIVING
+ var/datum/scrying_component/component
+ var/moving_eye = FALSE
+
+/mob/scry_eye/blackmirror
+
+/mob/scry_eye/Move(n, direct)
+ if(!moving_eye)
+ return
+ ..()
+
+/mob/scry_eye/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, list/message_mods = list(), original_message)
+ if(!component)
+ qdel(src)
+ return
+ component.held_user.Hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mods, original_message)
+ return
+
+
+
+
+
+
+/* VAMPIRE EYE */
+/mob/scry_eye/eye_of_night
+ sight = 0
+ see_in_dark = 2
+ invisibility = INVISIBILITY_GHOST
+ see_invisible = SEE_INVISIBLE_GHOST
+
+ var/mob/living/carbon/human/vampirelord = null
+ icon_state = "arcaneeye"
+ hud_type = /datum/hud/eye
+ moving_eye = TRUE
+
+/mob/scry_eye/eye_of_night/proc/scry_tele()
+ set category = "RoleUnique.Arcane Eye"
+ set name = "Teleport"
+ set desc= "Teleport to a location"
+ set hidden = 0
+
+ if(!is_scryeye(usr))
+ to_chat(usr, span_warning("You're not an Eye!"))
+ return
+ var/list/filtered = list()
+ for(var/area/A as anything in get_sorted_areas())
+ if(A.area_flags & (HIDDEN_AREA|NO_TELEPORT))
+ continue
+ filtered += A
+ var/area/thearea = input("Area to jump to", "VANDERLIN") as null|anything in filtered
+
+ if(!thearea)
+ return
+
+ var/list/L = list()
+ for(var/turf/T in get_area_turfs(thearea.type))
+ L+=T
+
+ if(!L || !L.len)
+ to_chat(usr, span_warning("No area available."))
+ return
+
+ usr.forceMove(pick(L))
+
+/mob/scry_eye/eye_of_night/Initialize()
+ . = ..()
+ var/list/verbs = list(
+ /mob/scry_eye/eye_of_night/proc/scry_tele,
+ /mob/scry_eye/eye_of_night/proc/cancel_scry,
+ /mob/scry_eye/eye_of_night/proc/eye_down,
+ /mob/scry_eye/eye_of_night/proc/eye_up,
+ /mob/scry_eye/eye_of_night/proc/vampire_telepathy
+ )
+ add_verb(src, verbs)
+ name = "Arcane Eye"
+ grant_all_languages()
+
+/mob/scry_eye/eye_of_night/proc/cancel_scry()
+ set category = "RoleUnique.Arcane Eye"
+ set name = "Cancel Eye"
+ set desc= "Return to Body"
+
+ if(vampirelord)
+ vampirelord.ckey = ckey
+ qdel(src)
+ else
+ to_chat(src, "My body has been destroyed! I'm trapped!")
+
+/mob/scry_eye/eye_of_night/Crossed(mob/living/L)
+ if(istype(L, /mob/living/carbon/human))
+ var/mob/living/carbon/human/V = L
+ var/holyskill = V.get_skill_level(/datum/skill/magic/holy)
+ var/magicskill = V.get_skill_level(/datum/skill/magic/arcane)
+ if(magicskill >= 2)
+ to_chat(V, "An ancient and unusual magic looms in the air around you.")
+ return
+ if(holyskill >= 2)
+ to_chat(V, "An ancient and unholy magic looms in the air around you.")
+ return
+ if(prob(20))
+ to_chat(V, "You feel like someone is watching you, or something.")
+ return
+
+/mob/scry_eye/eye_of_night/proc/vampire_telepathy()
+ set name = "Telepathy"
+ set category = "RoleUnique.Arcane Eye"
+
+ var/msg = input("Send a message.", "Command") as text|null
+ if(!msg)
+ return
+ for(var/datum/mind/V in SSmapping.retainer.vampires)
+ to_chat(V, span_boldnotice("A message from [src.real_name]:[msg]"))
+ for(var/datum/mind/D in SSmapping.retainer.death_knights)
+ to_chat(D, span_boldnotice("A message from [src.real_name]:[msg]"))
+ for(var/mob/scry_eye/eye_of_night/A in GLOB.mob_list)
+ to_chat(A, span_boldnotice("A message from [src.real_name]:[msg]"))
+
+/mob/scry_eye/eye_of_night/proc/eye_up()
+ set category = "RoleUnique.Arcane Eye"
+ set name = "Move Up"
+
+ if(zMove(UP, TRUE))
+ to_chat(src, span_notice("I move upwards."))
+
+/mob/scry_eye/eye_of_night/proc/eye_down()
+ set category = "RoleUnique.Arcane Eye"
+ set name = "Move Down"
+
+ if(zMove(DOWN, TRUE))
+ to_chat(src, span_notice("I move down."))
diff --git a/code/modules/antagonists/villain/vampire/objects/scrying.dm b/code/modules/antagonists/villain/vampire/objects/scrying.dm
index dcf76366c51..eee24b86c5f 100644
--- a/code/modules/antagonists/villain/vampire/objects/scrying.dm
+++ b/code/modules/antagonists/villain/vampire/objects/scrying.dm
@@ -1,157 +1,27 @@
/obj/structure/vampire/scryingorb // Method of spying on the town
name = "Eye of Night"
icon_state = "scrying"
+ var/datum/scrying_component/vampire/scry_comp //Temporary alternative
+
+/obj/structure/vampire/scryingorb/Initialize()
+ . = ..()
+ scry_comp = new(src)
/obj/structure/vampire/scryingorb/attack_hand(mob/living/carbon/human/user)
if(user?.mind.has_antag_datum(/datum/antagonist/vampire/lord))
user.visible_message("[user]'s eyes turn dark red, as they channel the [src]", "I begin to channel my consciousness into a Predator's Eye.")
if(do_after(user, 6 SECONDS, src))
- user.scry(can_reenter_corpse = 1, force_respawn = FALSE)
+ //user.scry(can_reenter_corpse = 1, force_respawn = FALSE)
+ scry_comp.activate(user)
else
to_chat(user, span_warning("I don't have the power to use this!"))
-/mob/dead/observer/rogue/arcaneeye
- sight = 0
- see_in_dark = 2
- invisibility = INVISIBILITY_GHOST
- see_invisible = SEE_INVISIBLE_GHOST
-
- misting = 0
- var/mob/living/carbon/human/vampirelord = null
- icon_state = "arcaneeye"
- draw_icon = FALSE
- hud_type = /datum/hud/eye
-
-/mob/dead/observer/rogue/arcaneeye/proc/scry_tele()
- set category = "RoleUnique.Arcane Eye"
- set name = "Teleport"
- set desc= "Teleport to a location"
- set hidden = 0
-
- if(!isobserver(usr))
- to_chat(usr, span_warning("You're not an Eye!"))
- return
- var/list/filtered = list()
- for(var/area/A as anything in get_sorted_areas())
- if(A.area_flags & (HIDDEN_AREA|NO_TELEPORT))
- continue
- filtered += A
- var/area/thearea = input("Area to jump to", "VANDERLIN") as null|anything in filtered
-
- if(!thearea)
- return
-
- var/list/L = list()
- for(var/turf/T in get_area_turfs(thearea.type))
- L+=T
-
- if(!L || !L.len)
- to_chat(usr, span_warning("No area available."))
- return
-
- usr.forceMove(pick(L))
-
-/mob/dead/observer/rogue/arcaneeye/Initialize()
- . = ..()
- set_invisibility(GLOB.observer_default_invisibility)
- var/list/verbs = list(
- /mob/dead/observer/rogue/arcaneeye/proc/scry_tele,
- /mob/dead/observer/rogue/arcaneeye/proc/cancel_scry,
- /mob/dead/observer/rogue/arcaneeye/proc/eye_down,
- /mob/dead/observer/rogue/arcaneeye/proc/eye_up,
- /mob/dead/observer/rogue/arcaneeye/proc/vampire_telepathy
- )
- add_verb(src, verbs)
- name = "Arcane Eye"
- grant_all_languages()
-
-/mob/dead/observer/rogue/arcaneeye/proc/cancel_scry()
- set category = "RoleUnique.Arcane Eye"
- set name = "Cancel Eye"
- set desc= "Return to Body"
-
- if(vampirelord)
- vampirelord.ckey = ckey
- qdel(src)
- else
- to_chat(src, "My body has been destroyed! I'm trapped!")
-
-/mob/dead/observer/rogue/arcaneeye/Crossed(mob/living/L)
- if(istype(L, /mob/living/carbon/human))
- var/mob/living/carbon/human/V = L
- var/holyskill = V.get_skill_level(/datum/skill/magic/holy)
- var/magicskill = V.get_skill_level(/datum/skill/magic/arcane)
- if(magicskill >= 2)
- to_chat(V, "An ancient and unusual magic looms in the air around you.")
- return
- if(holyskill >= 2)
- to_chat(V, "An ancient and unholy magic looms in the air around you.")
- return
- if(prob(20))
- to_chat(V, "You feel like someone is watching you, or something.")
- return
-
-/mob/dead/observer/rogue/arcaneeye/proc/vampire_telepathy()
- set name = "Telepathy"
- set category = "RoleUnique.Arcane Eye"
-
- var/msg = input("Send a message.", "Command") as text|null
- if(!msg)
- return
- for(var/datum/mind/V in SSmapping.retainer.vampires)
- to_chat(V, span_boldnotice("A message from [src.real_name]:[msg]"))
- for(var/datum/mind/D in SSmapping.retainer.death_knights)
- to_chat(D, span_boldnotice("A message from [src.real_name]:[msg]"))
- for(var/mob/dead/observer/rogue/arcaneeye/A in GLOB.mob_list)
- to_chat(A, span_boldnotice("A message from [src.real_name]:[msg]"))
-
-/mob/dead/observer/rogue/arcaneeye/proc/eye_up()
- set category = "RoleUnique.Arcane Eye"
- set name = "Move Up"
-
- if(zMove(UP, TRUE))
- to_chat(src, span_notice("I move upwards."))
-
-/mob/dead/observer/rogue/arcaneeye/proc/eye_down()
- set category = "RoleUnique.Arcane Eye"
- set name = "Move Down"
-
- if(zMove(DOWN, TRUE))
- to_chat(src, span_notice("I move down."))
-
-/mob/dead/observer/rogue/arcaneeye/Move(NewLoc, direct)
- if(world.time < next_gmove)
- return
- next_gmove = world.time + 3
-
- if(updatedir)
- setDir(direct)//only update dir if we actually need it, so overlays won't spin on base sprites that don't have directions of their own
- var/oldloc = loc
-
- if(NewLoc)
- var/NewLocTurf = get_turf(NewLoc)
- if(istype(NewLocTurf, /turf/closed/mineral/bedrock)) // prevent going out of bounds.
- return
- forceMove(NewLoc)
- else
- forceMove(get_turf(src)) //Get out of closets and such as a ghost
- if((direct & NORTH) && y < world.maxy)
- y++
- else if((direct & SOUTH) && y > 1)
- y--
- if((direct & EAST) && x < world.maxx)
- x++
- else if((direct & WEST) && x > 1)
- x--
- Moved(oldloc, direct)
/mob/proc/scry(can_reenter_corpse = 1, force_respawn = FALSE, drawskip)
stop_sound_channel(CHANNEL_HEARTBEAT) //Stop heartbeat sounds because You Are A Ghost Now
- var/mob/dead/observer/rogue/arcaneeye/eye = new(src) // Transfer safety to observer spawning proc.
+ var/mob/scry_eye/eye_of_night/eye = new(src) // Transfer safety to observer spawning proc.
SStgui.on_transfer(src, eye) // Transfer NanoUIs.
- eye.can_reenter_corpse = can_reenter_corpse
eye.vampirelord = src
- eye.ghostize_time = world.time
eye.key = key
return eye
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index c5993fa4c31..32208c0d467 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -107,22 +107,6 @@ GLOBAL_LIST_INIT(ghost_verbs, list(
return
. = ..()
-/mob/dead/observer/screye
-// see_invisible = SEE_INVISIBLE_LIVING
- sight = 0
- see_in_dark = 0
- hud_type = /datum/hud/obs
- can_reenter_corpse = FALSE
- invisibility = INVISIBILITY_GHOST
- see_invisible = SEE_INVISIBLE_GHOST
-
-/mob/dead/observer/screye/blackmirror
- sight = SEE_TURFS | SEE_MOBS | SEE_OBJS
- see_in_dark = 100
-
-/mob/dead/observer/screye/Move(n, direct)
- return
-
/mob/dead/observer/profane // Ghost type for souls trapped by the profane dagger. They can't move, but can talk to the dagger's wielder and other trapped souls.
sight = 0
invisibility = INVISIBILITY_GHOST
@@ -212,10 +196,8 @@ GLOBAL_LIST_INIT(ghost_verbs, list(
. = ..()
- if(!istype(src, /mob/dead/observer/rogue/arcaneeye))
- if(!istype(src, /mob/dead/observer/screye))
- add_verb(src, GLOB.ghost_verbs)
- to_chat(src, span_danger("Click the SKULL on the left of your HUD to respawn."))
+ add_verb(src, GLOB.ghost_verbs)
+ to_chat(src, span_danger("Click the SKULL on the left of your HUD to respawn."))
grant_all_languages()
@@ -327,16 +309,6 @@ Works together with spawning an observer, noted above.
// return
return ..()
-/mob/proc/scry_ghost()
- if(key)
- stop_sound_channel(CHANNEL_HEARTBEAT) //Stop heartbeat sounds because You Are A Ghost Now
- var/mob/dead/observer/screye/ghost = new(src) // Transfer safety to observer spawning proc.
- ghost.ghostize_time = world.time
- SStgui.on_transfer(src, ghost) // Transfer NanoUIs.
- ghost.can_reenter_corpse = TRUE
- ghost.key = key
- return ghost
-
/*
This is the proc mobs get to turn into a ghost. Forked from ghostize due to compatibility issues.
*/
@@ -757,8 +729,6 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
O.updateghostimages()
/mob/dead/observer/proc/horde_respawn()
- if(istype(src, /mob/dead/observer/rogue/arcaneeye))
- return
var/bt = world.time
SEND_SOUND(src, sound('sound/misc/notice (2).ogg'))
if(alert(src, "You have been summoned to destroy Vanderlin!", "Join the Horde", "Yes", "No") == "Yes")
diff --git a/code/modules/mob/dead/observer/observer_say.dm b/code/modules/mob/dead/observer/observer_say.dm
index cce7351e5e0..57a2baf5964 100644
--- a/code/modules/mob/dead/observer/observer_say.dm
+++ b/code/modules/mob/dead/observer/observer_say.dm
@@ -37,12 +37,6 @@
/mob/dead/observer/rogue/say_dead(message)
return
-/mob/dead/observer/screye/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
- return
-
-/mob/dead/observer/screye/say_dead(message)
- return
-
/mob/dead/observer/profane/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
if (!message)
return
diff --git a/vanderlin.dme b/vanderlin.dme
index 0f79769aeb8..289e029bfdd 100644
--- a/vanderlin.dme
+++ b/vanderlin.dme
@@ -1744,7 +1744,6 @@
#include "code\game\objects\items\literary.dm"
#include "code\game\objects\items\locks.dm"
#include "code\game\objects\items\mageitems.dm"
-#include "code\game\objects\items\magic.dm"
#include "code\game\objects\items\natural.dm"
#include "code\game\objects\items\needle.dm"
#include "code\game\objects\items\ore.dm"
@@ -1757,6 +1756,7 @@
#include "code\game\objects\items\quicksilver.dm"
#include "code\game\objects\items\ropechainbola.dm"
#include "code\game\objects\items\scrolls.dm"
+#include "code\game\objects\items\scrying.dm"
#include "code\game\objects\items\servant_bell.dm"
#include "code\game\objects\items\signal_horn.dm"
#include "code\game\objects\items\soap.dm"