diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 9c9fa30559a..7b07f950888 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -114,6 +114,8 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list( #define isrogueobserver(A) (istype(A, /mob/dead/observer/rogue)) +#define isscryeye(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 e8388daee89..d16530567f3 100644 --- a/code/__DEFINES/traits/definitions.dm +++ b/code/__DEFINES/traits/definitions.dm @@ -361,9 +361,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Given to a mob that can throw to make them not able to throw #define TRAIT_NO_THROWING "no_throwing" - -/// 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 b62ca22297b..a8f47f3cce6 100644 --- a/code/_onclick/hud/ghost.dm +++ b/code/_onclick/hud/ghost.dm @@ -67,9 +67,6 @@ if(!istype(ghost)) return - if(istype(ghost, /mob/dead/observer/rogue/arcaneeye)) - return - if(ghost.isinhell) return diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm index 6fe1270ba13..6a5858332cd 100644 --- a/code/_onclick/observer.dm +++ b/code/_onclick/observer.dm @@ -18,9 +18,6 @@ /mob/dead/observer/profane/DblClickOn(atom/clicked_atom, params) // Souls trapped by the dagger should not be jumping around. return -/mob/dead/observer/rogue/arcaneeye/DblClickOn(atom/clicked_atom, params) - return - /mob/dead/observer/ClickOn(atom/clicked_atom, params) var/list/modifiers = params2list(params) 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/datums/components/scrying.dm b/code/datums/components/scrying.dm new file mode 100644 index 00000000000..6c7f341ef7f --- /dev/null +++ b/code/datums/components/scrying.dm @@ -0,0 +1,277 @@ +/datum/component/scrying + 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 + + var/interact_method = "attack_self" + COOLDOWN_DECLARE(scry_cooldown) + +/datum/component/scrying/Initialize() + . = ..() + if(!isobj(parent)) + return COMPONENT_INCOMPATIBLE + text_cooldown_fail = replacetext(text_cooldown_fail, "NAME_HERE", "\the [name]") + +/datum/component/scrying/RegisterWithParent() + switch(interact_method) + if("attack_self") + RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, PROC_REF(activate)) + if("attack_hand") + RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(activate)) + +/datum/component/scrying/UnregisterFromParent() + switch(interact_method) + if("attack_self") + UnregisterSignal(parent, COMSIG_ITEM_ATTACK_SELF) + if("attack_hand") + UnregisterSignal(parent, COMSIG_ATOM_ATTACK_HAND) + +/datum/component/scrying/Destroy(force) + QDEL_NULL(scrying_eye) + held_user = null + return ..() + +/datum/component/scrying/proc/activate(datum/source, mob/user) + if(!pass_extra_checks(user)) + return FALSE + + if(!COOLDOWN_FINISHED(src, scry_cooldown)) + to_chat(user, span_warning(text_cooldown_fail)) + return FALSE + + var/search_name = tgui_input_text(user, "Who are you looking for?", name) + if(!search_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 + + 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 + + var/mob/living/carbon/human/found_target + for(var/mob/living/carbon/human/human_target as anything in GLOB.human_list) + if(lowertext(human_target.real_name) == lowertext(search_name)) + var/turf/target_turf = get_turf(human_target) + if(!target_turf) + continue + found_target = human_target + break + + if(!found_target) + return + + 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.")) + return + + if(needs_to_live && found_target.stat) + to_chat(user, span_warning("I peer into \the [name], but can't find [search_name].")) + return FALSE + + held_user = user + create_eye() + if(!scrying_eye) + remove_eye(TRUE) + return + + log_game("SCRYING: [user.real_name] ([user.ckey]) has used the [name] to scry [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) + var/target_perception = GET_MOB_ATTRIBUTE_VALUE(found_target, STAT_PERCEPTION) + if(target_perception >= 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(target_perception >= 11) + to_chat(found_target, span_warning("I feel a pair of unknown eyes on me.")) + return TRUE + +/datum/component/scrying/proc/create_eye() + if(!held_user) + return FALSE + scrying_eye = new + scrying_eye.user_mob = held_user + held_user.reset_perspective(scrying_eye) + held_user.Immobilize(vision_duration) + held_user.overlay_fullscreen("scrying", /atom/movable/screen/backhudl/obscured) + addtimer(CALLBACK(src, PROC_REF(remove_eye)), vision_duration) + +/datum/component/scrying/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/component/scrying/proc/pass_extra_checks(mob/living/user) + return TRUE + +/datum/component/scrying/orb + name = "Scrying Orb" + +/datum/component/scrying/orb/pass_extra_checks(mob/living/user) + if(GET_MOB_SKILL_VALUE_OLD(user, /datum/attribute/skill/magic/arcane) < 1) + to_chat(user, span_warning("I do not know what to do with this...")) + return FALSE + return TRUE + +/datum/component/scrying/eye + name = "Accursed Eye" + cooldown_duration = 5 MINUTES + +/datum/component/scrying/vampire + name = "Night's Eye" + needs_to_know = FALSE + needs_to_live = FALSE + vision_duration = 12 SECONDS + cooldown_duration = 3 SECONDS + +/datum/component/scrying/vampire/pass_extra_checks(mob/living/user) + if(!user?.mind.has_antag_datum(/datum/antagonist/vampire/lord)) + to_chat(user, span_warning("I don't have the power to use this!")) + return FALSE + return TRUE + +/datum/component/scrying/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." + interact_method = "attack_hand" + +/datum/component/scrying/telescope/pass_extra_checks(mob/living/user) + var/mob/living/carbon/human/human_user = user + if(!ishuman(human_user) || !HAS_TRAIT(human_user, TRAIT_VIRGIN)) + to_chat(human_user, span_warning("Noc looks angry with me...")) + return FALSE + return TRUE + +/datum/component/scrying/mirror + name = "Black Mirror" + vision_duration = 6 SECONDS + needs_to_know = FALSE + needs_to_live = FALSE + var/obj/item/inqarticles/bmirror/parent_mirror + var/mob/stored_target + var/atom/movable/screen/alert/blackmirror/effect + +/datum/component/scrying/mirror/Initialize(obj/item/scrying/parent) + . = ..() + parent_mirror = parent + if(!istype(parent_mirror)) + return INITIALIZE_HINT_QDEL + +/datum/component/scrying/mirror/Destroy(force) + parent_mirror = null + stored_target = null + QDEL_NULL(effect) + . = ..() + +/datum/component/scrying/mirror/activate(mob/living/user) + if(!pass_extra_checks(user)) + message_admins("SCRY DEBUG: EXTRA CHECKS FAIL") + 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/component/scrying/mirror/create_eye() + if(!held_user) + return FALSE + scrying_eye = new + scrying_eye.user_mob = held_user + held_user.reset_perspective(scrying_eye) + held_user.Immobilize(vision_duration) + held_user.overlay_fullscreen("scrying", /atom/movable/screen/backhudl/obscured) + playsound(held_user, 'sound/items/blackmirror_use.ogg', 100, FALSE) + addtimer(CALLBACK(src, PROC_REF(remove_eye)), vision_duration) + +/datum/component/scrying/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() + effect = null + +/datum/component/scrying/mirror/proc/apply_black_eye() + scrying_eye.orbit(stored_target) + effect = stored_target.throw_alert("blackmirror", /atom/movable/screen/alert/blackmirror, override = TRUE) + effect.source = parent_mirror + playsound(stored_target, 'sound/items/blackeye_warn.ogg', 100, FALSE) diff --git a/code/game/objects/items/inquisition_relics.dm b/code/game/objects/items/inquisition_relics.dm index 33c32bb81f6..90949a4a365 100644 --- a/code/game/objects/items/inquisition_relics.dm +++ b/code/game/objects/items/inquisition_relics.dm @@ -1179,24 +1179,16 @@ 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 /obj/item/inqarticles/bmirror/Initialize() . = ..() soundloop = new(src, FALSE) + AddComponent(/datum/component/scrying/mirror) /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) @@ -1211,13 +1203,6 @@ 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.")) @@ -1264,69 +1249,9 @@ to_chat(user, span_warning("It looks like it needs blood to work properly.")) return - if(!active) - var/mob/living/carbon/human/target = fixation?.resolve() - var/input - if(!target) - input = "FIXATION" //skips through the tgui alert if target isn't set - else - input = tgui_alert(user, "THE MIRROR IS FIXATED ON [uppertext(target.real_name)]. WILL YOU REVEAL YOUR GAZE?", "THE PRICE IS PAID", list("STALK BLOOD", "FIXATION")) - if(!input || QDELETED(user) || QDELETED(src)) - return - if(input == "FIXATION") - var/name = html_decode(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(LOWER_TEXT(HL.real_name) == LOWER_TEXT(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 - to_chat(user, span_warning("The mirror makes no sound... It could not locate a person of such name.")) - 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) - - if(target.real_name == user.real_name) //prevents bugging the timer through looking at yourself - to_chat(user, span_danger("I see my reflection in the mirror... It is quite distorted, but what am I trying to achieve?")) - return - - 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...")) - - 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) + //add_client_colour(/datum/client_colour/nocshaded) + var/datum/component/scrying/mirror/scry_comp = GetComponent(/datum/component/scrying/mirror) + scry_comp.activate(user) /obj/item/inqarticles/bmirror/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(!isliving(interacting_with)) @@ -1338,7 +1263,7 @@ to_chat(user, span_warning("I need to open it first.")) return ITEM_INTERACT_BLOCKING - if(feeder) + if(fedblood) to_chat(user, span_warning("It's already been fed.")) return ITEM_INTERACT_BLOCKING @@ -1365,7 +1290,6 @@ attacked.flash_fullscreen("redflash3") attacked.adjustBruteLoss(40, damage_type = BCLASS_PIERCE, can_crit = FALSE) attacked.adjust_bloodpool(-240) - feeder = WEAKREF(attacked) openstate = "bloody" fedblood = TRUE update_appearance(UPDATE_ICON_STATE) @@ -1391,31 +1315,17 @@ /obj/item/inqarticles/bmirror/attack_hand_secondary(mob/user, list/modifiers) . = ..() openorshut(user) + /obj/item/inqarticles/bmirror/proc/openorshut(mob/user) if(active) to_chat(user, span_warning("I cannot close the mirror while it's active.")) return - var/mob/living/fixated = fixation?.resolve() + opened = !opened if(opened) - if(fixated) - fixated.clear_alert("blackmirror", TRUE) - fixated.playsound_local(src, 'sound/items/blackeye.ogg', 40, FALSE) - else if(effect) - QDEL_NULL(effect) + playsound(src, 'sound/items/blackmirror_open.ogg', 100, FALSE) + else playsound(src, 'sound/items/blackmirror_shut.ogg', 100, FALSE) - opened = FALSE - update_appearance(UPDATE_ICON_STATE) - return - - playsound(src, 'sound/items/blackmirror_open.ogg', 100, FALSE) - - 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() @@ -1431,35 +1341,39 @@ 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 ..() + var/mob/scry_eye/looking_eye /atom/movable/screen/alert/blackmirror/Click() - var/mob/living/L = usr - if(!istype(L)) - return - var/mob/living/target = null - var/input = tgui_alert(L, "YOU FEEL UNFAMILIAR GAZE. WILL YOU STARE BACK AT ABYSS?", "PRESENCE WATCHING OVER", list("TRACE BLOOD", "LOOK BACK")) - if(input == "TRACE BLOOD") - target = source.feeder?.resolve() - else if(input == "LOOK BACK") - target = source - playsound(L, 'sound/items/blackmirror_use.ogg', 100, FALSE) - ADD_TRAIT(L, TRAIT_NOSSDINDICATOR, "blackmirror") - if(!target) + var/mob/living/user = usr + if(!istype(user)) return - var/mob/dead/observer/screye/blackmirror/S = L.scry_ghost() - if(!S) + var/input = tgui_alert(user, "YOU FEEL AN UNFAMILIAR GAZE. WILL YOU STARE BACK AT THE ABYSS?", "PRESENCE WATCHING OVER", list("NO", "LOOK BACK")) + if(input != "LOOK BACK") 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) + looking_eye = new + looking_eye.user_mob = user + looking_eye.orbit(source) + user.reset_perspective(looking_eye) + user.Immobilize(4 SECONDS) + user.overlay_fullscreen("scrying", /atom/movable/screen/backhudl/obscured) + playsound(user, 'sound/items/blackmirror_use.ogg', 100, FALSE) + addtimer(CALLBACK(src, PROC_REF(remove_eye)), 4 SECONDS) + + user.visible_message(span_warning("[user] looks inward as their eyes glaze over...")) + +/atom/movable/screen/alert/blackmirror/Destroy() + remove_eye() + source = null + return ..() + +/atom/movable/screen/alert/blackmirror/proc/remove_eye() + if(mob_viewer) + mob_viewer.reset_perspective(mob_viewer) + mob_viewer.clear_fullscreen("scrying") + playsound(mob_viewer, 'sound/items/blackeye.ogg', 100, FALSE) + QDEL_NULL(looking_eye) + mob_viewer = null // 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 e06218234cf..00000000000 --- a/code/game/objects/items/magic.dm +++ /dev/null @@ -1,147 +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 - item_weight = 400 GRAMS - - 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 - item_weight = 200 GRAMS - -/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 = SANITIZE_HEAR_MESSAGE(html_decode(tgui_input_text(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 [HL.real_name].")) - 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(GET_MOB_ATTRIBUTE_VALUE(HL, STAT_PERCEPTION) >= 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(GET_MOB_ATTRIBUTE_VALUE(HL, STAT_PERCEPTION) >= 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" - layer = 4.2 - var/last_scry - -/obj/structure/nocdevice/attack_hand(mob/user) - . = ..() - var/mob/living/carbon/human/H = user - if(HAS_TRAIT(H, TRAIT_VIRGIN)) - 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(GET_MOB_ATTRIBUTE_VALUE(HL, STAT_PERCEPTION) >= 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(GET_MOB_ATTRIBUTE_VALUE(HL, STAT_PERCEPTION) >= 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..4fc04aed943 --- /dev/null +++ b/code/game/objects/items/scrying.dm @@ -0,0 +1,180 @@ +/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/scry_comp_path = /datum/component/scrying/orb + +/obj/item/scrying/Initialize(mapload) + . = ..() + AddComponent(scry_comp_path) + +/obj/item/scrying/eye + name = "accursed eye" + desc = "It is pulsating." + icon = 'icons/roguetown/items/misc.dmi' + icon_state ="scryeye" + + scry_comp_path = /datum/component/scrying/eye + + +/* .................. 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" + layer = 4.2 + +/obj/structure/nocdevice/Initialize() + . = ..() + AddComponent(/datum/component/scrying/telescope) + +/* .................. THE EYE ................... */ +/mob/scry_eye + sight = SEE_TURFS | SEE_MOBS | SEE_OBJS + see_in_dark = 100 + hud_type = /datum/hud/obscured + invisibility = INVISIBILITY_GHOST + see_invisible = SEE_INVISIBLE_LIVING + var/mob/living/user_mob + 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(!user_mob) + qdel(src) + return + user_mob.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(!isscryeye(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/cross_human = L + var/holyskill = GET_MOB_SKILL_VALUE_OLD(cross_human, /datum/attribute/skill/magic/holy) + var/magicskill = GET_MOB_SKILL_VALUE_OLD(cross_human, /datum/attribute/skill/magic/arcane) + if(magicskill >= 2) + to_chat(cross_human, "An ancient and unusual magic looms in the air around you.") + return + if(holyskill >= 2) + to_chat(cross_human, "An ancient and unholy magic looms in the air around you.") + return + if(prob(20)) + to_chat(cross_human, "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 1bc9235c8df..c8958319600 100644 --- a/code/modules/antagonists/villain/vampire/objects/scrying.dm +++ b/code/modules/antagonists/villain/vampire/objects/scrying.dm @@ -2,136 +2,18 @@ name = "Eye of Night" icon_state = "scrying" -/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) - else - to_chat(user, span_warning("I don't have the power to use this!")) - -/mob/dead/observer/rogue/arcaneeye - name = "Arcane Eye" - icon_state = "arcaneeye" - sight = 0 - see_in_dark = 2 - invisibility = INVISIBILITY_GHOST - see_invisible = SEE_INVISIBLE_GHOST - - draw_icon = FALSE - hud_type = /datum/hud/eye - - var/mob/living/carbon/human/vampirelord = null - -/mob/dead/observer/rogue/arcaneeye/Initialize(mapload) +/obj/structure/vampire/scryingorb/Initialize() . = ..() - set_invisibility(GLOB.observer_default_invisibility) - add_movespeed_modifier(MOVESPEED_ID_GHOST, override = TRUE, multiplicative_slowdown = 3) - 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) - -/mob/dead/observer/rogue/arcaneeye/Destroy() - vampirelord = null - return ..() - -/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 = GET_MOB_SKILL_VALUE_OLD(V, /datum/attribute/skill/magic/holy) - var/magicskill = GET_MOB_SKILL_VALUE_OLD(V, /datum/attribute/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/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/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, z_move_flags = ZMOVE_FEEDBACK)) - 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, z_move_flags = ZMOVE_FEEDBACK)) - to_chat(src, span_notice("I move downwards.")) - -/mob/dead/observer/rogue/arcaneeye/proc/scry_tele() - set category = "RoleUnique.Arcane Eye" - set name = "Teleport" - set desc= "Teleport to a location" - - if(!isobserver(src)) - to_chat(src, 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 = browser_input_list(src, "Area to jump to", "VANDERLIN", filtered) - - if(!thearea) - return - - var/list/L = list() - for(var/turf/T as anything in get_area_turfs(thearea.type)) - L += T - - if(!length(L)) - to_chat(src, span_warning("No area available.")) - return - - forceMove(pick(L)) + AddComponent(/datum/component/scrying/vampire) //Temporary alternative -/mob/proc/scry(can_reenter_corpse = 1, force_respawn = FALSE, drawskip) +/* +/mob/proc/enter_night_eye() 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 qdel(eye.language_holder) eye.language_holder = language_holder.copy(eye) return eye +*/ diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 6f4cf3b1eb6..fb0dca4bd2d 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -96,21 +96,6 @@ GLOBAL_LIST_INIT(ghost_verbs, list( draw_icon = FALSE alpha = 100 -/mob/dead/observer/screye - sight = 0 - see_in_dark = 0 - hud_type = /datum/hud/obscured - 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 @@ -192,10 +177,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.")) if(grant_all_languages) grant_all_languages() @@ -293,24 +276,7 @@ Works together with spawning an observer, noted above. SEND_SIGNAL(src, COMSIG_MOB_GHOSTIZED) return ghost -/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 - RegisterSignal(ghost, COMSIG_MOB_LOGOUT, PROC_REF(break_scry)) - return ghost - -/mob/proc/break_scry() - return -/mob/dead/observer/break_scry() - client.view_size.setDefault(client.view_size.getScreenSize()) - mind.current_ghost = null - mind.current.ckey = ckey(key) /* This is the proc mobs get to turn into a ghost. Forked from ghostize due to compatibility issues. @@ -787,8 +753,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(tgui_alert(src, "You have been summoned to destroy Vanderlin!", "Join the Horde", list("Yes", "No")) == "Yes") diff --git a/code/modules/mob/dead/observer/observer_say.dm b/code/modules/mob/dead/observer/observer_say.dm index a672cf7a15c..f9e2df93d9b 100644 --- a/code/modules/mob/dead/observer/observer_say.dm +++ b/code/modules/mob/dead/observer/observer_say.dm @@ -29,18 +29,6 @@ . = say_dead(message) -/mob/dead/observer/rogue/arcaneeye/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) - return - -/mob/dead/observer/rogue/arcaneeye/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 7e22f5f940b..7b5fa06b23c 100644 --- a/vanderlin.dme +++ b/vanderlin.dme @@ -1148,6 +1148,7 @@ #include "code\datums\components\rope.dm" #include "code\datums\components\rotation.dm" #include "code\datums\components\rotting.dm" +#include "code\datums\components\scrying.dm" #include "code\datums\components\shrapnel.dm" #include "code\datums\components\slippery.dm" #include "code\datums\components\slowing_field.dm" @@ -2064,7 +2065,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" @@ -2078,6 +2078,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"