/obj/item/ghost_catcher name = "proton rifle" desc = "A two handed device, used for 'catching ghosts'. Weakens the entity initially grabbed." icon = 'icons/obj/guns/ghost_beam.dmi' icon_state = "ghost_beam" w_class = ITEMSIZE_NORMAL force = 0 slot_flags = SLOT_BELT var/grab_range = 5 // How many tiles away it can grab. Changing this also changes the box size. /// Stops multiple grabbs if set to TRUE var/busy = FALSE /// The entity we are currently grabbing. var/datum/weakref/grabbed_entity /// How far we can move an entity in one go! var/max_move_distance = 1 /// If we're held in two hands or not...Used until we get two handed component. var/wielded = FALSE COOLDOWN_DECLARE(ghost_cooldown) COOLDOWN_DECLARE(click_cooldown) //TODO: // Make ghosts/phased entities slow when grabbed // Make it so it searches in an AOE and grabs thing. /obj/item/ghost_catcher/Destroy() grabbed_entity = null return ..() /obj/item/ghost_catcher/update_icon() if(busy) icon_state = "ghost_beam_active" else icon_state = initial(icon_state) /obj/item/ghost_catcher/update_held_icon() var/mob/living/M = loc if(istype(M) && M.can_wield_item(src) && is_held_twohanded(M)) wielded = TRUE else wielded = FALSE ..() /obj/item/ghost_catcher/afterattack(atom/target, mob/user, proximity_flag) update_held_icon() if(!wielded) to_chat(user, span_warning("You need to hold \the [src] in two hands to use it!")) return if(!COOLDOWN_FINISHED(src, ghost_cooldown)) to_chat(user, span_warning("The [src] is recharging!")) return if(!COOLDOWN_FINISHED(src, click_cooldown)) to_chat(user, span_warning("The [src] needs time between movements!")) return if(isturf(target) && (!target.incorporeal_grab())) var/turf/T = target if(!busy) for(var/mob/entity in range(1, T)) //We'll let you grab things ON the tile or AROUND the tile you click on. if(entity.incorporeal_grab()) target = entity break if(!ismob(target)) to_chat(user, span_warning("No valid target in range! Beginning cooldown.")) COOLDOWN_START(src, ghost_cooldown, 3 SECONDS) //Prevent from spamclicking to find ghosts. return else if(grabbed_entity) var/atom/movable/entity = grabbed_entity.resolve() if(get_dist(T, entity) > max_move_distance) to_chat(user, span_warning("\The [src] is unable to pull the entity that far!")) return entity.forceMove(T) COOLDOWN_START(src, click_cooldown, 1 SECOND) return if(istype(target, /obj/item/ghost_trap)) //Special handling for traps, since traps are full sized objects and not turf. var/obj/item/ghost_trap/trap = target var/atom/movable/entity = grabbed_entity.resolve() if(!trap.deployed) to_chat(user, span_warning("The trap isn't deployed!")) return if(!entity) to_chat(user, span_warning("No valid target!")) return if(get_dist(trap, entity) > max_move_distance) to_chat(user, span_warning("\The [src] is unable to pull the entity that far!")) return entity.forceMove(get_turf(trap)) COOLDOWN_START(src, click_cooldown, 1 SECOND) return // Things that invalidate the scan immediately. if(busy) to_chat(user, span_warning("\The [src] is already grabbing an entity!")) return if(!target.incorporeal_grab(user)) // This will tell the user what is wrong. return if(get_dist(target, user) > grab_range) to_chat(user, span_warning("You are too far away from \the [target] to catalogue it. Get closer.")) return // Start the special effects. busy = TRUE update_icon() var/datum/beam/scan_beam = user.Beam(target, icon_state = "curse1", time = 60 SECONDS) var/filter = filter(type = "outline", size = 1, color = "#330099") target.filters += filter var/list/box_segments = list() if(user.client) box_segments = draw_box(target, grab_range, user.client) color_box(box_segments, "#330099", 60 SECONDS) playsound(src, 'sound/machines/beep.ogg', 50) grabbed_entity = WEAKREF(target) if(isliving(target)) var/mob/living/target_mob = target target_mob.Weaken(3) target_mob.Stun(3) to_chat(target, span_danger("You feel yourself weakened from the [src]'s beam!")) // The delay, and test for if the scan succeeds or not. if(do_after(user, 60 SECONDS, target, timed_action_flags = IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE, max_distance = grab_range)) to_chat(user, span_warning("With a buzz, \the [src] flashes red, the beam on \the [target] has broken!")) playsound(src, 'sound/machines/buzz-two.ogg', 50) color_box(box_segments, "#330099", 3) busy = FALSE // Now clean up the effects. update_icon() QDEL_NULL(scan_beam) if(target) target.filters -= filter if(user.client) // If for some reason they logged out mid-scan the box will be gone anyways. delete_box(box_segments, user.client) grabbed_entity = null COOLDOWN_START(src, ghost_cooldown, 10 SECONDS) // Arbitrary cooldown to prevent spam. Adjust as needed. /atom/proc/incorporeal_grab(mob/user) if(is_incorporeal()) return TRUE return FALSE /mob/observer/dead/incorporeal_grab(mob/user) // Dead mobs can't be scanned. if(admin_ghosted || !interact_with_world) to_chat(user, span_notice("No entity detected!")) //The goggles ARE listed as unreliable. You could just be seeing static. return FALSE return ..() //The backpack the gun is (typically) attached to! /obj/item/proton_pack name = "proton pack" desc = "A complex backpack that houses a miniature antimatter reactor to power a spectral capture device." icon = 'icons/obj/technomancer.dmi' default_worn_icon = 'icons/inventory/back/mob.dmi' icon_state = "technomancer_core" item_state = "proton_pack" slot_flags = SLOT_BACK preserve_item = 1 w_class = ITEMSIZE_LARGE unacidable = TRUE var/obj/item/ghost_catcher/who_ya_gunna_call = /obj/item/ghost_catcher /obj/item/proton_pack/Initialize(mapload) AddComponent(/datum/component/tethered_item, who_ya_gunna_call) . = ..() /obj/item/proton_pack/attack_hand(mob/living/user) // See important note in tethered_item.dm if(SEND_SIGNAL(src,COMSIG_ITEM_ATTACK_SELF,user) & COMPONENT_CANCEL_ATTACK_CHAIN) return TRUE . = ..() /obj/item/proton_pack/MouseDrop() if(ismob(src.loc)) if(!CanMouseDrop(src)) return var/mob/M = src.loc if(!M.unEquip(src)) return src.add_fingerprint(usr) M.put_in_any_hand_if_possible(src)