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