diff --git a/code/datums/uplink/tools.dm b/code/datums/uplink/tools.dm index 7cea842778..bf9ddd4ac1 100644 --- a/code/datums/uplink/tools.dm +++ b/code/datums/uplink/tools.dm @@ -66,6 +66,14 @@ When in hacking mode this device will grant full access to any standard airlock within 20 to 40 seconds. \ This device will also be able to immediately access the last 6 to 8 hacked airlocks." +/datum/uplink_item/item/tools/hacking_tool + name = "Anti-Surveillance Tool" + item_cost = 20 + path = /obj/item/device/multitool/ai_detector + desc = "This functions like a normal multitool, but includes an integrated camera network sensor that will warn the holder if they are being \ + watched, by changing color and beeping. It is able to detect both AI visual surveillance and security camera utilization from terminals, and \ + will give different warnings by beeping and changing colors based on what it detects. Only the holder can hear the warnings." + /datum/uplink_item/item/tools/emag name = "Cryptographic Sequencer" item_cost = 30 diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 85d97a2b30..b5eda03a09 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -37,6 +37,8 @@ var/client_huds = list() + var/list/camera_computers_using_this = list() + /obj/machinery/camera/New() wires = new(src) assembly = new(src) diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index 36974b159f..2f4832047f 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -169,6 +169,7 @@ src.current_camera = C if(current_camera) + current_camera.camera_computers_using_this.Add(src) use_power = 2 var/mob/living/L = current_camera.loc if(istype(L)) @@ -176,6 +177,7 @@ /obj/machinery/computer/security/proc/reset_current() if(current_camera) + current_camera.camera_computers_using_this.Remove(src) var/mob/living/L = current_camera.loc if(istype(L)) L.tracking_cancelled() diff --git a/code/game/objects/items/devices/ai_detector.dm b/code/game/objects/items/devices/ai_detector.dm new file mode 100644 index 0000000000..7fa6f74521 --- /dev/null +++ b/code/game/objects/items/devices/ai_detector.dm @@ -0,0 +1,121 @@ +#define PROXIMITY_OFF_CAMERANET "_no_camera" +#define PROXIMITY_NONE "" +#define PROXIMITY_NEAR "_yellow" +#define PROXIMITY_ON_SCREEN "_red" +#define PROXIMITY_TRACKING "_tracking" +#define PROXIMITY_TRACKING_FAIL "_tracking_fail" + +// This is another syndie-multitool, except this one detects when the AI and/or Security is peeping on the holder. + +/obj/item/device/multitool/ai_detector + var/range_alert = 7 // Will turn red if the AI can observe its holder. + var/range_warning = 14 // Will turn yellow if the AI's eye is near the holder. + var/detect_state = PROXIMITY_NONE + origin_tech = list(TECH_MAGNET = 2, TECH_ENGINEERING = 2, TECH_ILLEGAL = 2) + +/obj/item/device/multitool/ai_detector/New() + // It's really really unlikely for the view range to change. But why not be futureproof anyways? + range_alert = world.view + range_warning = world.view * 2 + processing_objects += src + ..() + +/obj/item/device/multitool/ai_detector/Destroy() + processing_objects -= src + return ..() + +/obj/item/device/multitool/ai_detector/process() + var/old_detect_state = detect_state + var/new_detect_state = detect_ai() + detect_state = new_detect_state + update_icon() + update_warning(old_detect_state, new_detect_state) + return + +// This also detects security using cameras. +/obj/item/device/multitool/ai_detector/proc/detect_ai() + var/mob/living/carrier = isliving(loc) ? loc : null + + // First, let's check if any AIs are actively tracking them. + for(var/mob/living/silicon/ai/AI in silicon_mob_list) + if(carrier && AI.cameraFollow == carrier) + if(!carrier.tracking_status()) // Successful tracking returns a 0, so we need to invert it. + return PROXIMITY_TRACKING + else + return PROXIMITY_TRACKING_FAIL + + // If there's no turf then cameras won't do anything anyways. + var/turf/T = get_turf(src) + if(!T) + return PROXIMITY_OFF_CAMERANET + + // Security is also a concern, so we need to see if any cameras are in use. + // Note that this will trigger upon the security console being used, regardless if someone is actually watching, + // because there isn't a nice way to test if someone is actually looking. Probably better that way too. + var/list/our_local_area = range(range_alert, T) + for(var/obj/machinery/camera/C in our_local_area) + if(C.camera_computers_using_this.len) // Only check cameras actively being used. + var/list/their_local_area = C.can_see(range_alert) + if(T in their_local_area) + return PROXIMITY_ON_SCREEN + + // Now for the somewhat harder AI cameranet checks. + + // Check if we are even on the cameranet. + if(!cameranet.checkVis(T)) + return PROXIMITY_OFF_CAMERANET + + var/datum/chunk/chunk = cameranet.getChunk(T.x, T.y, T.z) + if(!chunk) + return PROXIMITY_OFF_CAMERANET + + // Check if the AI eye is able to see us, or if it's almost able to. + if(chunk.seenby.len) + for(var/mob/observer/eye/aiEye/A in chunk.seenby) + var/turf/detect_turf = get_turf(A) + if(get_dist(T, detect_turf) <= range_alert) + return PROXIMITY_ON_SCREEN + if(get_dist(T, detect_turf) <= range_warning) + return PROXIMITY_NEAR + + // If we reach this point, AI or sec isn't near us. + return PROXIMITY_NONE + +/obj/item/device/multitool/ai_detector/update_icon() + icon_state = "[initial(icon_state)][detect_state]" + +/obj/item/device/multitool/ai_detector/proc/update_warning(var/old_state, var/new_state) + var/mob/living/carrier = isliving(loc) ? loc : null + + // Now to warn our holder, if the state changes. + if(!carrier) + return + + if(new_state != old_state) + switch(new_state) + if(PROXIMITY_OFF_CAMERANET) + to_chat(carrier, "\icon[src] Now outside of camera network.") + carrier << 'sound/machines/defib_failed.ogg' + if(PROXIMITY_NONE) + to_chat(carrier, "\icon[src] Now within camera network, AI and cameras unfocused.") + carrier << 'sound/machines/defib_safetyOff.ogg' + if(PROXIMITY_NEAR) + to_chat(carrier, "\icon[src] Warning: AI focus at nearby location.") + carrier << 'sound/machines/defib_SafetyOn.ogg' + if(PROXIMITY_ON_SCREEN) + to_chat(carrier, "\icon[src] Alert: AI or camera focused at current location!") + carrier <<'sound/machines/defib_ready.ogg' + if(PROXIMITY_TRACKING) + to_chat(carrier, "\icon[src] Danger: AI is actively tracking you!") + carrier << 'sound/machines/defib_success.ogg' + if(PROXIMITY_TRACKING_FAIL) + to_chat(carrier, "\icon[src] Danger: AI is attempting to actively track you, but you are outside of the camera network!") + carrier <<'sound/machines/defib_ready.ogg' + + +#undef PROXIMITY_OFF_CAMERANET +#undef PROXIMITY_NONE +#undef PROXIMITY_NEAR +#undef PROXIMITY_ON_SCREEN +#undef PROXIMITY_TRACKING +#undef PROXIMITY_TRACKING_FAIL \ No newline at end of file diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi index 1198dc0314..48ce44ee29 100644 Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ diff --git a/polaris.dme b/polaris.dme index e6b605a99b..462504dd79 100644 --- a/polaris.dme +++ b/polaris.dme @@ -776,6 +776,7 @@ #include "code\game\objects\items\shooting_range.dm" #include "code\game\objects\items\toys.dm" #include "code\game\objects\items\trash.dm" +#include "code\game\objects\items\devices\ai_detector.dm" #include "code\game\objects\items\devices\aicard.dm" #include "code\game\objects\items\devices\binoculars.dm" #include "code\game\objects\items\devices\chameleonproj.dm"