From 275aa2bfa26678bd2fae7e8797458ba7aeca41d1 Mon Sep 17 00:00:00 2001 From: Cruix Date: Sat, 7 Jul 2018 22:19:55 -0700 Subject: [PATCH] AI detection multitools can show camera static and AI eyes (#38255) The old color-change functionality of the multitool remains unchanged. The hud is toggled with an action button, because multitools already have an attack_self() that does something. When toggled on, you get the hud as long as the multitool is in any slot on your body. The only way I could find to reliably show exactly where an AI is looking was to place an image on every turf they are looking at. Because AI eyes move around a lot and I do not want them placing images all over the place when they do not have to, if there is nobody in the world currently using an ai detection multitool, they will not update the images until someone is. Only AI eyes and multicamera zones currently appear. Advanced camera consoles do not appear on the hud, and no longer trigger the proximity color change. Also fixed multitools not appearing in-hands if their icon state changed. --- code/__DEFINES/atom_hud.dm | 32 +++---- code/__DEFINES/layers.dm | 28 +++--- code/__DEFINES/sight.dm | 5 ++ code/_globalvars/lists/mobs.dm | 1 + code/_onclick/hud/picture_in_picture.dm | 14 +-- code/_onclick/hud/plane_master.dm | 6 ++ code/datums/holocall.dm | 1 - code/datums/hud.dm | 4 +- code/game/data_huds.dm | 10 +++ .../machinery/computer/camera_advanced.dm | 22 ++--- code/game/objects/items/devices/multitool.dm | 90 +++++++++++++++++-- code/game/turfs/turf.dm | 4 +- code/modules/mob/living/silicon/ai/ai.dm | 2 +- .../living/silicon/ai/freelook/cameranet.dm | 58 +++++++----- .../mob/living/silicon/ai/freelook/chunk.dm | 14 ++- .../mob/living/silicon/ai/freelook/eye.dm | 81 +++++++++++++++-- .../modules/mob/living/silicon/ai/multicam.dm | 5 ++ code/modules/shuttle/navigation_computer.dm | 2 +- code/modules/uplink/uplink_items.dm | 6 +- 19 files changed, 292 insertions(+), 93 deletions(-) diff --git a/code/__DEFINES/atom_hud.dm b/code/__DEFINES/atom_hud.dm index 393ffce4fde..d690dcd6274 100644 --- a/code/__DEFINES/atom_hud.dm +++ b/code/__DEFINES/atom_hud.dm @@ -20,8 +20,9 @@ #define DIAG_PATH_HUD "16"//Bot path indicators #define GLAND_HUD "17"//Gland indicators for abductors #define SENTIENT_DISEASE_HUD "18" +#define AI_DETECT_HUD "19" //for antag huds. these are used at the /mob level -#define ANTAG_HUD "19" +#define ANTAG_HUD "20" //by default everything in the hud_list of an atom is an image //a value in hud_list with one of these will change that behavior @@ -37,22 +38,23 @@ #define DATA_HUD_DIAGNOSTIC_ADVANCED 6 #define DATA_HUD_ABDUCTOR 7 #define DATA_HUD_SENTIENT_DISEASE 8 +#define DATA_HUD_AI_DETECT 9 //antag HUD defines -#define ANTAG_HUD_CULT 9 -#define ANTAG_HUD_REV 10 -#define ANTAG_HUD_OPS 11 -#define ANTAG_HUD_WIZ 12 -#define ANTAG_HUD_SHADOW 13 -#define ANTAG_HUD_TRAITOR 14 -#define ANTAG_HUD_NINJA 15 -#define ANTAG_HUD_CHANGELING 16 -#define ANTAG_HUD_ABDUCTOR 17 -#define ANTAG_HUD_DEVIL 18 -#define ANTAG_HUD_SINTOUCHED 19 -#define ANTAG_HUD_SOULLESS 20 -#define ANTAG_HUD_CLOCKWORK 21 -#define ANTAG_HUD_BROTHER 22 +#define ANTAG_HUD_CULT 10 +#define ANTAG_HUD_REV 11 +#define ANTAG_HUD_OPS 12 +#define ANTAG_HUD_WIZ 13 +#define ANTAG_HUD_SHADOW 14 +#define ANTAG_HUD_TRAITOR 15 +#define ANTAG_HUD_NINJA 16 +#define ANTAG_HUD_CHANGELING 17 +#define ANTAG_HUD_ABDUCTOR 18 +#define ANTAG_HUD_DEVIL 19 +#define ANTAG_HUD_SINTOUCHED 20 +#define ANTAG_HUD_SOULLESS 21 +#define ANTAG_HUD_CLOCKWORK 22 +#define ANTAG_HUD_BROTHER 23 // Notification action types #define NOTIFY_JUMP "jump" diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index 9f0fdc27b22..8891c06f750 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -85,20 +85,22 @@ #define BYOND_LIGHTING_PLANE 17 #define BYOND_LIGHTING_LAYER 17 +#define CAMERA_STATIC_PLANE 18 +#define CAMERA_STATIC_LAYER 18 //HUD layer defines -#define FULLSCREEN_PLANE 18 -#define FLASH_LAYER 18 -#define FULLSCREEN_LAYER 18.1 -#define UI_DAMAGE_LAYER 18.2 -#define BLIND_LAYER 18.3 -#define CRIT_LAYER 18.4 -#define CURSE_LAYER 18.5 +#define FULLSCREEN_PLANE 19 +#define FLASH_LAYER 19 +#define FULLSCREEN_LAYER 19.1 +#define UI_DAMAGE_LAYER 19.2 +#define BLIND_LAYER 19.3 +#define CRIT_LAYER 19.4 +#define CURSE_LAYER 19.5 -#define HUD_PLANE 19 -#define HUD_LAYER 19 -#define ABOVE_HUD_PLANE 20 -#define ABOVE_HUD_LAYER 20 +#define HUD_PLANE 20 +#define HUD_LAYER 20 +#define ABOVE_HUD_PLANE 21 +#define ABOVE_HUD_LAYER 21 -#define SPLASHSCREEN_LAYER 21 -#define SPLASHSCREEN_PLANE 21 +#define SPLASHSCREEN_LAYER 22 +#define SPLASHSCREEN_PLANE 22 diff --git a/code/__DEFINES/sight.dm b/code/__DEFINES/sight.dm index bf00ed0cf32..6ab3092d1d0 100644 --- a/code/__DEFINES/sight.dm +++ b/code/__DEFINES/sight.dm @@ -28,3 +28,8 @@ #define VISOR_VISIONFLAGS (1<<2) //all following flags only matter for glasses #define VISOR_DARKNESSVIEW (1<<3) #define VISOR_INVISVIEW (1<<4) + +//for whether AI eyes see static, and whether it is mouse-opaque or not +#define USE_STATIC_NONE 0 +#define USE_STATIC_TRANSPARENT 1 +#define USE_STATIC_OPAQUE 2 diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index 07513105ae3..1010aa7676d 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -26,6 +26,7 @@ GLOBAL_LIST_INIT(simple_animals, list(list(),list(),list(),list())) // One for e GLOBAL_LIST_EMPTY(spidermobs) //all sentient spider mobs GLOBAL_LIST_EMPTY(bots_list) GLOBAL_LIST_EMPTY(living_cameras) +GLOBAL_LIST_EMPTY(aiEyes) GLOBAL_LIST_EMPTY(language_datum_instances) GLOBAL_LIST_EMPTY(all_languages) diff --git a/code/_onclick/hud/picture_in_picture.dm b/code/_onclick/hud/picture_in_picture.dm index aea87827a6a..e028212e96b 100644 --- a/code/_onclick/hud/picture_in_picture.dm +++ b/code/_onclick/hud/picture_in_picture.dm @@ -124,14 +124,16 @@ vis_contents -= viewing_turfs if(!width || !height) return - var/turf/T = get_turf(center) - if(!T) - return - var/turf/lowerleft = locate(max(1, T.x - round(width/2)), max(1, T.y - round(height/2)), T.z) - var/turf/upperright = locate(min(world.maxx, lowerleft.x + width - 1), min(world.maxy, lowerleft.y + height - 1), lowerleft.z) - viewing_turfs = block(lowerleft, upperright) + viewing_turfs = get_visible_turfs() vis_contents += viewing_turfs +/obj/screen/movable/pic_in_pic/proc/get_visible_turfs() + var/turf/T = get_turf(center) + if(!T) + return list() + var/turf/lowerleft = locate(max(1, T.x - round(width/2)), max(1, T.y - round(height/2)), T.z) + var/turf/upperright = locate(min(world.maxx, lowerleft.x + width - 1), min(world.maxy, lowerleft.y + height - 1), lowerleft.z) + return block(lowerleft, upperright) /obj/screen/movable/pic_in_pic/proc/show_to(client/C) if(C) diff --git a/code/_onclick/hud/plane_master.dm b/code/_onclick/hud/plane_master.dm index d4559ed66dc..5bdf61f75d3 100644 --- a/code/_onclick/hud/plane_master.dm +++ b/code/_onclick/hud/plane_master.dm @@ -52,3 +52,9 @@ /obj/screen/plane_master/lighting/backdrop(mob/mymob) mymob.overlay_fullscreen("lighting_backdrop_lit", /obj/screen/fullscreen/lighting_backdrop/lit) mymob.overlay_fullscreen("lighting_backdrop_unlit", /obj/screen/fullscreen/lighting_backdrop/unlit) + +/obj/screen/plane_master/camera_static + name = "camera static plane master" + plane = CAMERA_STATIC_PLANE + appearance_flags = PLANE_MASTER + blend_mode = BLEND_OVERLAY diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm index 9d4b86b5ef3..4923609db80 100644 --- a/code/datums/holocall.dm +++ b/code/datums/holocall.dm @@ -60,7 +60,6 @@ user.remote_control = null if(!QDELETED(eye)) - eye.RemoveImages() QDEL_NULL(eye) if(connected_holopad && !QDELETED(hologram)) diff --git a/code/datums/hud.dm b/code/datums/hud.dm index 54b28deeee8..32bce89f4d1 100644 --- a/code/datums/hud.dm +++ b/code/datums/hud.dm @@ -12,6 +12,7 @@ GLOBAL_LIST_INIT(huds, list( DATA_HUD_DIAGNOSTIC_ADVANCED = new/datum/atom_hud/data/diagnostic/advanced(), DATA_HUD_ABDUCTOR = new/datum/atom_hud/abductor(), DATA_HUD_SENTIENT_DISEASE = new/datum/atom_hud/sentient_disease(), + DATA_HUD_AI_DETECT = new/datum/atom_hud/ai_detector(), ANTAG_HUD_CULT = new/datum/atom_hud/antag(), ANTAG_HUD_REV = new/datum/atom_hud/antag(), ANTAG_HUD_OPS = new/datum/atom_hud/antag(), @@ -100,7 +101,8 @@ GLOBAL_LIST_INIT(huds, list( return FALSE hudatoms |= A for(var/mob/M in hudusers) - add_to_single_hud(M, A) + if(!queued_to_see[M]) + add_to_single_hud(M, A) return TRUE /datum/atom_hud/proc/add_to_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm index 9a60fdef31e..bc40dd8d424 100644 --- a/code/game/data_huds.dm +++ b/code/game/data_huds.dm @@ -66,6 +66,16 @@ /datum/atom_hud/sentient_disease hud_icons = list(SENTIENT_DISEASE_HUD) +/datum/atom_hud/ai_detector + hud_icons = list(AI_DETECT_HUD) + +/datum/atom_hud/ai_detector/add_hud_to(mob/M) + ..() + if(M && (hudusers.len == 1)) + for(var/V in GLOB.aiEyes) + var/mob/camera/aiEye/E = V + E.update_ai_detect_hud() + /* MED/SEC/DIAG HUD HOOKS */ /* diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm index 772bfc16043..86fcefb3407 100644 --- a/code/game/machinery/computer/camera_advanced.dm +++ b/code/game/machinery/computer/camera_advanced.dm @@ -59,7 +59,8 @@ C.remove(eyeobj) if(user.client) user.reset_perspective(null) - eyeobj.RemoveImages() + if(eyeobj.visible_icon && user.client) + user.client.images -= eyeobj.user_image eyeobj.eye_user = null user.remote_control = null @@ -108,7 +109,7 @@ if(!eyeobj.eye_initialized) var/camera_location var/turf/myturf = get_turf(src) - if(eyeobj.use_static) + if(eyeobj.use_static != USE_STATIC_NONE) if((!z_lock.len || (myturf.z in z_lock)) && GLOB.cameranet.checkTurfVis(myturf)) camera_location = myturf else @@ -151,6 +152,7 @@ /mob/camera/aiEye/remote name = "Inactive Camera Eye" + ai_detector_visible = FALSE var/sprint = 10 var/cooldown = 0 var/acceleration = 1 @@ -166,14 +168,9 @@ user.see_in_dark = 2 return 1 -/mob/camera/aiEye/remote/RemoveImages() - ..() - if(visible_icon) - var/client/C = GetViewerClient() - if(C) - C.images -= user_image - /mob/camera/aiEye/remote/Destroy() + if(origin && eye_user) + origin.remove_eye_control(eye_user) origin = null . = ..() eye_user = null @@ -185,15 +182,14 @@ /mob/camera/aiEye/remote/setLoc(T) if(eye_user) - if(!isturf(eye_user.loc)) - return T = get_turf(T) if (T) forceMove(T) else moveToNullspace() - if(use_static) - GLOB.cameranet.visibility(src, GetViewerClient()) + update_ai_detect_hud() + if(use_static != USE_STATIC_NONE) + GLOB.cameranet.visibility(src, GetViewerClient(), null, use_static) if(visible_icon) if(eye_user.client) eye_user.client.images -= user_image diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm index 43c2b06d440..23749d4481e 100644 --- a/code/game/objects/items/devices/multitool.dm +++ b/code/game/objects/items/devices/multitool.dm @@ -15,6 +15,7 @@ desc = "Used for pulsing wires to test which to cut. Not recommended by doctors." icon = 'icons/obj/device.dmi' icon_state = "multitool" + item_state = "multitool" lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' force = 5 @@ -40,8 +41,6 @@ if(selected_io) selected_io = null to_chat(user, "You clear the wired connection from the multitool.") - else - ..() update_icon() /obj/item/multitool/update_icon() @@ -98,30 +97,89 @@ // Syndicate device disguised as a multitool; it will turn red when an AI camera is nearby. - /obj/item/multitool/ai_detect var/track_cooldown = 0 var/track_delay = 10 //How often it checks for proximity var/detect_state = PROXIMITY_NONE var/rangealert = 8 //Glows red when inside var/rangewarning = 20 //Glows yellow when inside + var/hud_type = DATA_HUD_AI_DETECT + var/hud_on = FALSE + var/mob/camera/aiEye/remote/ai_detector/eye + var/datum/action/item_action/toggle_multitool/toggle_action -/obj/item/multitool/ai_detect/New() - ..() +/obj/item/multitool/ai_detect/Initialize() + . = ..() START_PROCESSING(SSobj, src) + eye = new /mob/camera/aiEye/remote/ai_detector() + toggle_action = new /datum/action/item_action/toggle_multitool(src) /obj/item/multitool/ai_detect/Destroy() STOP_PROCESSING(SSobj, src) + if(hud_on && ismob(loc)) + remove_hud(loc) + QDEL_NULL(toggle_action) + QDEL_NULL(eye) return ..() +/obj/item/multitool/ai_detect/ui_action_click() + return + +/obj/item/multitool/ai_detect/update_icon() + if(selected_io) + icon_state = "multitool_red" + else + icon_state = "[initial(icon_state)][detect_state]" + +/obj/item/multitool/ai_detect/equipped(mob/living/carbon/human/user, slot) + ..() + if(hud_on) + show_hud(user) + +/obj/item/multitool/ai_detect/dropped(mob/living/carbon/human/user) + ..() + if(hud_on) + remove_hud(user) + /obj/item/multitool/ai_detect/process() if(track_cooldown > world.time) return detect_state = PROXIMITY_NONE + if(eye.eye_user) + eye.setLoc(get_turf(src)) multitool_detect() - icon_state = "[initial(icon_state)][detect_state]" + update_icon() track_cooldown = world.time + track_delay +/obj/item/multitool/ai_detect/proc/toggle_hud(mob/user) + hud_on = !hud_on + if(user) + to_chat(user, "You toggle the ai detection HUD on [src] [hud_on ? "on" : "off"].") + if(hud_on) + show_hud(user) + else + remove_hud(user) + +/obj/item/multitool/ai_detect/proc/show_hud(mob/user) + if(user && hud_type) + var/obj/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"] + PM.alpha = 150 + var/datum/atom_hud/H = GLOB.huds[hud_type] + if(!H.hudusers[user]) + H.add_hud_to(user) + eye.eye_user = user + eye.setLoc(get_turf(src)) + +/obj/item/multitool/ai_detect/proc/remove_hud(mob/user) + if(user && hud_type) + var/obj/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"] + PM.alpha = 255 + var/datum/atom_hud/H = GLOB.huds[hud_type] + H.remove_hud_from(user) + if(eye) + eye.setLoc(null) + eye.eye_user = null + /obj/item/multitool/ai_detect/proc/multitool_detect() var/turf/our_turf = get_turf(src) for(var/mob/living/silicon/ai/AI in GLOB.ai_list) @@ -134,6 +192,8 @@ var/datum/camerachunk/chunk = GLOB.cameranet.chunkGenerated(our_turf.x, our_turf.y, our_turf.z) if(chunk && chunk.seenby.len) for(var/mob/camera/aiEye/A in chunk.seenby) + if(!A.ai_detector_visible) + continue var/turf/detect_turf = get_turf(A) if(get_dist(our_turf, detect_turf) < rangealert) detect_state = PROXIMITY_ON_SCREEN @@ -142,6 +202,24 @@ detect_state = PROXIMITY_NEAR break +/mob/camera/aiEye/remote/ai_detector + name = "AI detector eye" + ai_detector_visible = FALSE + use_static = USE_STATIC_TRANSPARENT + visible_icon = FALSE + +/datum/action/item_action/toggle_multitool + name = "Toggle AI detector HUD" + check_flags = NONE + +/datum/action/item_action/toggle_multitool/Trigger() + if(!..()) + return 0 + if(target) + var/obj/item/multitool/ai_detect/M = target + M.toggle_hud(owner) + return 1 + /obj/item/multitool/cyborg name = "multitool" desc = "Optimised and stripped-down version of a regular multitool." diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 490a6dcaf72..c223cbfcc86 100755 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -331,9 +331,9 @@ var/datum/camerachunk/C = GLOB.cameranet.chunkGenerated(x, y, z) if(C) if(C.obscuredTurfs[src]) - vis_contents += GLOB.cameranet.vis_contents + vis_contents += GLOB.cameranet.vis_contents_objects else - vis_contents -= GLOB.cameranet.vis_contents + vis_contents -= GLOB.cameranet.vis_contents_objects /turf/proc/burn_tile() diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index c1ec315d92a..d9095ce462d 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -1003,7 +1003,7 @@ target_ai = src //cheat! just give... ourselves as the spawned AI, because that's technically correct /mob/living/silicon/ai/proc/camera_visibility(mob/camera/aiEye/moved_eye) - GLOB.cameranet.visibility(moved_eye, client, all_eyes) + GLOB.cameranet.visibility(moved_eye, client, all_eyes, USE_STATIC_OPAQUE) /mob/living/silicon/ai/forceMove(atom/destination) . = ..() diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm index 36ae19b478f..8d5590def65 100644 --- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm +++ b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm @@ -18,15 +18,24 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new) // The object used for the clickable stat() button. var/obj/effect/statclick/statclick - // The object used in vis_contents of obscured turfs - var/vis_contents + // The objects used in vis_contents of obscured turfs + var/list/vis_contents_objects + var/obj/effect/overlay/camera_static/vis_contents_opaque + var/obj/effect/overlay/camera_static/vis_contents_transparent // The image given to the effect in vis_contents on AI clients var/image/obscured + var/image/obscured_transparent /datum/cameranet/New() - vis_contents = new /obj/effect/overlay/camera_static() - obscured = new('icons/effects/cameravis.dmi', vis_contents, null, BYOND_LIGHTING_LAYER + 0.1) - obscured.plane = BYOND_LIGHTING_PLANE + 1 + vis_contents_opaque = new /obj/effect/overlay/camera_static() + vis_contents_transparent = new /obj/effect/overlay/camera_static/transparent() + vis_contents_objects = list(vis_contents_opaque, vis_contents_transparent) + + obscured = new('icons/effects/cameravis.dmi', vis_contents_opaque, null, CAMERA_STATIC_LAYER) + obscured.plane = CAMERA_STATIC_PLANE + + obscured_transparent = new('icons/effects/cameravis.dmi', vis_contents_transparent, null, CAMERA_STATIC_LAYER) + obscured_transparent.plane = CAMERA_STATIC_PLANE // Checks if a chunk has been Generated in x, y, z. /datum/cameranet/proc/chunkGenerated(x, y, z) @@ -46,7 +55,7 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new) // Updates what the aiEye can see. It is recommended you use this when the aiEye moves or it's location is set. -/datum/cameranet/proc/visibility(list/moved_eyes, client/C, list/other_eyes) +/datum/cameranet/proc/visibility(list/moved_eyes, client/C, list/other_eyes, use_static = USE_STATIC_OPAQUE) if(!islist(moved_eyes)) moved_eyes = moved_eyes ? list(moved_eyes) : list() if(islist(other_eyes)) @@ -54,19 +63,28 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new) else other_eyes = list() + if(C) + switch(use_static) + if(USE_STATIC_TRANSPARENT) + C.images += obscured_transparent + if(USE_STATIC_OPAQUE) + C.images += obscured + for(var/V in moved_eyes) var/mob/camera/aiEye/eye = V - var/static_range = eye.static_visibility_range - var/x1 = max(0, eye.x - static_range) & ~(CHUNK_SIZE - 1) - var/y1 = max(0, eye.y - static_range) & ~(CHUNK_SIZE - 1) - var/x2 = min(world.maxx, eye.x + static_range) & ~(CHUNK_SIZE - 1) - var/y2 = min(world.maxy, eye.y + static_range) & ~(CHUNK_SIZE - 1) - var/list/visibleChunks = list() + if(eye.loc) + // 0xf = 15 + var/static_range = eye.static_visibility_range + var/x1 = max(0, eye.x - static_range) & ~(CHUNK_SIZE - 1) + var/y1 = max(0, eye.y - static_range) & ~(CHUNK_SIZE - 1) + var/x2 = min(world.maxx, eye.x + static_range) & ~(CHUNK_SIZE - 1) + var/y2 = min(world.maxy, eye.y + static_range) & ~(CHUNK_SIZE - 1) - for(var/x = x1; x <= x2; x += CHUNK_SIZE) - for(var/y = y1; y <= y2; y += CHUNK_SIZE) - visibleChunks |= getCameraChunk(x, y, eye.z) + + for(var/x = x1; x <= x2; x += CHUNK_SIZE) + for(var/y = y1; y <= y2; y += CHUNK_SIZE) + visibleChunks |= getCameraChunk(x, y, eye.z) var/list/remove = eye.visibleCameraChunks - visibleChunks var/list/add = visibleChunks - eye.visibleCameraChunks @@ -79,9 +97,6 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new) var/datum/camerachunk/c = chunk c.add(eye) - if(C) - C.images += obscured - // Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open. /datum/cameranet/proc/updateVisibility(atom/A, opacity_check = 1) @@ -173,5 +188,8 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new) mouse_opacity = MOUSE_OPACITY_ICON invisibility = INVISIBILITY_ABSTRACT - layer = BYOND_LIGHTING_LAYER + 0.1 - plane = BYOND_LIGHTING_PLANE + 1 + layer = CAMERA_STATIC_LAYER + plane = CAMERA_STATIC_PLANE + +/obj/effect/overlay/camera_static/transparent + mouse_opacity = MOUSE_OPACITY_TRANSPARENT diff --git a/code/modules/mob/living/silicon/ai/freelook/chunk.dm b/code/modules/mob/living/silicon/ai/freelook/chunk.dm index 9a84616fbf5..d83292f7670 100644 --- a/code/modules/mob/living/silicon/ai/freelook/chunk.dm +++ b/code/modules/mob/living/silicon/ai/freelook/chunk.dm @@ -29,6 +29,14 @@ /datum/camerachunk/proc/remove(mob/camera/aiEye/eye) eye.visibleCameraChunks -= src seenby -= eye + if(!eye.visibleCameraChunks.len) + var/client/client = eye.GetViewerClient() + if(client) + switch(eye.use_static) + if(USE_STATIC_TRANSPARENT) + client.images -= GLOB.cameranet.obscured_transparent + if(USE_STATIC_OPAQUE) + client.images -= GLOB.cameranet.obscured // Called when a chunk has changed. I.E: A wall was deleted. @@ -81,12 +89,12 @@ for(var/turf in visAdded) var/turf/t = turf - t.vis_contents -= GLOB.cameranet.vis_contents + t.vis_contents -= GLOB.cameranet.vis_contents_objects for(var/turf in visRemoved) var/turf/t = turf if(obscuredTurfs[t] && !istype(t, /turf/open/ai_visible)) - t.vis_contents += GLOB.cameranet.vis_contents + t.vis_contents += GLOB.cameranet.vis_contents_objects changed = 0 @@ -128,7 +136,7 @@ for(var/turf in obscuredTurfs) var/turf/t = turf - t.vis_contents += GLOB.cameranet.vis_contents + t.vis_contents += GLOB.cameranet.vis_contents_objects #undef UPDATE_BUFFER #undef CHUNK_SIZE diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index c2c395cabbb..bedb9638edb 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -7,28 +7,81 @@ name = "Inactive AI Eye" invisibility = INVISIBILITY_MAXIMUM + hud_possible = list(ANTAG_HUD, AI_DETECT_HUD = HUD_LIST_LIST) var/list/visibleCameraChunks = list() var/mob/living/silicon/ai/ai = null var/relay_speech = FALSE - var/use_static = TRUE + var/use_static = USE_STATIC_OPAQUE var/static_visibility_range = 16 + var/ai_detector_visible = TRUE + var/ai_detector_color = COLOR_RED + +/mob/camera/aiEye/Initialize() + . = ..() + GLOB.aiEyes += src + update_ai_detect_hud() + setLoc(loc, TRUE) + +/mob/camera/aiEye/proc/update_ai_detect_hud() + var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] + var/list/old_images = hud_list[AI_DETECT_HUD] + if(!ai_detector_visible) + hud.remove_from_hud(src) + QDEL_LIST(old_images) + return + + if(!hud.hudusers.len) + //no one is watching, do not bother updating anything + return + hud.remove_from_hud(src) + + var/static/list/vis_contents_objects = list() + var/obj/effect/overlay/ai_detect_hud/hud_obj = vis_contents_objects[ai_detector_color] + if(!hud_obj) + hud_obj = new /obj/effect/overlay/ai_detect_hud() + hud_obj.color = ai_detector_color + vis_contents_objects[ai_detector_color] = hud_obj + + var/list/new_images = list() + var/list/turfs = get_visible_turfs() + for(var/T in turfs) + var/image/I = (old_images.len > new_images.len) ? old_images[new_images.len + 1] : image(null, T) + I.loc = T + I.vis_contents += hud_obj + new_images += I + for(var/i in (new_images.len + 1) to old_images.len) + qdel(old_images[i]) + hud_list[AI_DETECT_HUD] = new_images + hud.add_to_hud(src) + +/mob/camera/aiEye/proc/get_visible_turfs() + if(!isturf(loc)) + return list() + var/client/C = GetViewerClient() + var/view = C ? getviewsize(C.view) : getviewsize(world.view) + var/turf/lowerleft = locate(max(1, x - (view[1] - 1)/2), max(1, y - (view[2] - 1)/2), z) + var/turf/upperright = locate(min(world.maxx, lowerleft.x + (view[1] - 1)), min(world.maxy, lowerleft.y + (view[2] - 1)), lowerleft.z) + return block(lowerleft, upperright) // Use this when setting the aiEye's location. // It will also stream the chunk that the new loc is in. -/mob/camera/aiEye/proc/setLoc(T) +/mob/camera/aiEye/proc/setLoc(T, force_update = FALSE) if(ai) if(!isturf(ai.loc)) return T = get_turf(T) + if(!force_update && (T == get_turf(src)) ) + return //we are already here! if (T) forceMove(T) else - moveToNullspace() // ???? - if(use_static) + moveToNullspace() + if(use_static != USE_STATIC_NONE) ai.camera_visibility(src) if(ai.client && !ai.multicam_on) ai.client.eye = src + update_ai_detect_hud() update_parallax_contents() //Holopad if(istype(ai.current, /obj/machinery/holopad)) @@ -47,11 +100,6 @@ return ai.client return null -/mob/camera/aiEye/proc/RemoveImages() - var/client/C = GetViewerClient() - if(C && use_static) - C.images -= GLOB.cameranet.obscured - /mob/camera/aiEye/Destroy() if(ai) ai.all_eyes -= src @@ -59,6 +107,12 @@ for(var/V in visibleCameraChunks) var/datum/camerachunk/c = V c.remove(src) + GLOB.aiEyes -= src + if(ai_detector_visible) + var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] + hud.remove_from_hud(src) + var/list/L = hud_list[AI_DETECT_HUD] + QDEL_LIST(L) return ..() /atom/proc/move_camera_by_click() @@ -131,3 +185,12 @@ /mob/camera/aiEye/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) if(relay_speech && speaker && ai && !radio_freq && speaker != ai && near_camera(speaker)) ai.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) + +/obj/effect/overlay/ai_detect_hud + name = "" + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + icon = 'icons/effects/alphacolors.dmi' + icon_state = "" + alpha = 100 + layer = ABOVE_ALL_MOB_LAYER + plane = GAME_PLANE diff --git a/code/modules/mob/living/silicon/ai/multicam.dm b/code/modules/mob/living/silicon/ai/multicam.dm index d77e7f8a408..20b5f962429 100644 --- a/code/modules/mob/living/silicon/ai/multicam.dm +++ b/code/modules/mob/living/silicon/ai/multicam.dm @@ -124,6 +124,7 @@ GLOBAL_DATUM(ai_camera_room_landmark, /obj/effect/landmark/ai_multicam_room) var/list/cameras_telegraphed = list() var/telegraph_cameras = TRUE var/telegraph_range = 7 + ai_detector_color = COLOR_ORANGE /mob/camera/aiEye/pic_in_pic/GetViewerClient() if(screen && screen.ai) @@ -139,6 +140,10 @@ GLOBAL_DATUM(ai_camera_room_landmark, /obj/effect/landmark/ai_multicam_room) else GLOB.cameranet.visibility(src) update_camera_telegraphing() + update_ai_detect_hud() + +/mob/camera/aiEye/pic_in_pic/get_visible_turfs() + return screen ? screen.get_visible_turfs() : list() /mob/camera/aiEye/pic_in_pic/proc/update_camera_telegraphing() if(!telegraph_cameras) diff --git a/code/modules/shuttle/navigation_computer.dm b/code/modules/shuttle/navigation_computer.dm index c58b3eee7be..e8106766cd8 100644 --- a/code/modules/shuttle/navigation_computer.dm +++ b/code/modules/shuttle/navigation_computer.dm @@ -249,7 +249,7 @@ /mob/camera/aiEye/remote/shuttle_docker visible_icon = FALSE - use_static = FALSE + use_static = USE_STATIC_NONE var/list/placement_images = list() var/list/placed_images = list() diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm index a9f2c63e133..8f63efb2933 100644 --- a/code/modules/uplink/uplink_items.dm +++ b/code/modules/uplink/uplink_items.dm @@ -1002,8 +1002,10 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item)) /datum/uplink_item/device_tools/ai_detector name = "Artificial Intelligence Detector" - desc = "A functional multitool that turns red when it detects an artificial intelligence watching it or its \ - holder. Knowing when an artificial intelligence is watching you is useful for knowing when to maintain cover." + desc = "A functional multitool that turns red when it detects an artificial intelligence watching it, and can be \ + activated to display their exact viewing location and nearby security camera blind spots. Knowing when \ + an artificial intelligence is watching you is useful for knowing when to maintain cover, and finding nearby \ + blind spots can help you identify escape routes." item = /obj/item/multitool/ai_detect cost = 1