/obj/machinery/camera name = "security camera" desc = "A small, high quality camera with thermal, light-amplification, and diffused laser imaging to see through walls. It is tied into a computer system, allowing those with access to watch what occurs around it." icon = 'icons/obj/monitors.dmi' icon_state = "camera" deconstruct_flags = DECON_SCREWDRIVER | DECON_WELDER | DECON_WIRECUTTERS | DECON_MULTITOOL var/network = "SS13" layer = EFFECTS_LAYER_UNDER_1 var/c_tag = null var/c_tag_order = 999 var/camera_status = 1.0 anchored = 1.0 var/invuln = null var/last_paper = 0 //This camera is a node pointing to the other bunch of cameras nearby for AI movement purposes var/obj/machinery/camera/c_north = null var/obj/machinery/camera/c_east = null var/obj/machinery/camera/c_west = null var/obj/machinery/camera/c_south = null //Here's a list of cameras pointing to this camera for reprocessing purposes var/list/obj/machinery/camera/referrers = list() //MBC : Ok so this is a kind of dumb optimization thing. We want to unsubscribe cameras from the machine loop that do not need to process (All wall-mounted stuffs) // But sometimes a camera is created and then placed inside an object after a certain amount of ticks!! // We are gonna give cameras a grace period of a few process cycles before they decide they aren't needed. This is PROBABLY FINE! var/unsubscribe_grace_counter = 0 var/oldx = 0 var/oldy = 0 /obj/machinery/camera/process() .=..() if(!isturf(src.loc)) //This will end up removing coverage if camera is inside a thing. var/turf/T = get_turf(src) if(T && (T.x != oldx || T.y != oldy)) //This will end up removing coverage if camera is inside a thing. src.updateCoverage() //MBC : handles moving cameras! oldx = T.x oldy = T.y //boutput(world,"hewwo there : ) ") src.updateCoverage() //MBC : handles moving cameras! else if (src.type == /obj/machinery/camera) //we actually don't want this check to affect children, so we compare to exact type unsubscribe_grace_counter++ if (unsubscribe_grace_counter >= 5) UnsubscribeProcess() unsubscribe_grace_counter = -1 /obj/machinery/camera/television name = "television camera" desc = "A bulky stationary camera for wireless broadcasting of live feeds." network = "Zeta" // why not. icon_state = "television" anchored = 1 density = 1 var/securedstate = 2 /obj/machinery/camera/television/attackby(obj/item/W as obj, mob/user as mob) ..() if (isscrewingtool(W)) //to move them if (securedstate && src.securedstate >= 1) playsound(src.loc, "sound/items/Screwdriver.ogg", 30, 1, -2) actions.start(new/datum/action/bar/icon/cameraSecure(src, securedstate), user) else if (securedstate) boutput(user, "You need to secure the floor bolts!") else if (iswrenchingtool(W)) if (src.securedstate <= 1) playsound(src.loc, "sound/items/Wrench.ogg", 30, 1, -2) boutput(user, "You [securedstate == 1 ? "un" : ""]secure the floor bolts on the [src].") src.securedstate = (securedstate == 1) ? 0 : 1 if (securedstate == 0) src.anchored = 0 else src.anchored = 1 /datum/action/bar/icon/cameraSecure //This is used when you are securing a non-mobile television camera duration = 150 interrupt_flags = INTERRUPT_MOVE | INTERRUPT_ACT | INTERRUPT_STUNNED | INTERRUPT_ACTION id = "cameraSecure" icon = 'icons/obj/items.dmi' icon_state = "screwdriver" var/obj/machinery/camera/television/cam var/secstate New(Camera, Secstate) cam = Camera secstate = Secstate ..() onStart() ..() for(var/mob/O in AIviewers(owner)) O.show_message(text("[] begins [secstate == 2 ? "un" : ""]securing the camera hookups on the [cam].", owner), 1) onInterrupt(var/flag) ..() boutput(owner, "You were interrupted!") onEnd() ..() owner.visible_message("[owner.name] [secstate == 2 ? "un" : ""]secures the camera hookups on the [cam].") cam.securedstate = (secstate == 2) ? 1 : 2 if (cam.securedstate != 2) machines.Remove(cam) else machines.Add(cam) /obj/machinery/camera/television/mobile name = "mobile television camera" desc = "A bulky mobile camera for wireless broadcasting of live feeds." anchored = 0 icon_state = "mobilevision" securedstate = null //No bugginess thank you /obj/machinery/camera/New() ..() cameras.Add(src) SPAWN_DBG(1 SECOND) // making life easy for mappers since 2013 if (dd_hasprefix(name, "autoname")) var/area/where = get_area(src) if (isarea(where)) name = "security camera" var/count = 1 for(var/obj/machinery/camera/C in where) if (C == src) continue if (dd_hasprefix(C.name, "autoname")) C.name = "security camera" if (C.c_tag == "autotag") count++ C.c_tag = "[where.name] [count]" if (c_tag == "autotag") if (count > 1) c_tag = "[where.name] 1" else c_tag = "[where.name]" addToNetwork() updateCoverage() //Make sure coverage is updated. (must happen in spawn!) add_to_turfs() /obj/machinery/camera/proc/addToNetwork() if(camnets[network]) var/list/net = camnets[network] net.Add(src) else var/list/net = list() net.Add(src) camnets[network] = net /obj/machinery/camera/proc/addToReferrers(var/obj/machinery/camera/C) //Safe addition if(!(C in referrers)) referrers += C /obj/machinery/camera/proc/removeNode(var/obj/machinery/camera/node) //Completely remove a node from this camera for(var/N in list("c_north", "c_east", "c_south", "c_west")) if(node == vars[N]) vars[N] = null node.referrers -= src /obj/machinery/camera/proc/hasNode(var/obj/machinery/camera/node) if(!istype(node)) return 0 . = 0 . = (node == c_north) + (node == c_east) + (node == c_south) + (node == c_west) /obj/machinery/camera/disposing() if (coveredTiles) //ZeWaka: Fix for null.Copy() for(var/turf/O in coveredTiles.Copy()) //Remove all coverage O.removeCameraCoverage(src) src.remove_from_turfs() //needs to happen BEFORE the actual deletion or else it fuckks up if(camnets && camnets[network]) camnets[network].Remove(src) cameras.Remove(src) if (c_north) c_north.referrers -= src if (c_east) c_east.referrers -= src if (c_south) c_south.referrers -= src if (c_west) c_west.referrers -= src for(var/obj/machinery/camera/C in referrers) if (C.c_north == src) C.c_north = null if (C.c_east == src) C.c_east = null if (C.c_south == src) C.c_south = null if (C.c_west == src) C.c_west = null //C.removeNode(src) src.referrers = null ..() dirty_cameras |= referrers camnet_needs_rebuild = 1 //logTheThing("debug", null, null, "SpyGuy/Camnet: Camera destroyed. Camera network needs a rebuild! Number of dirty cameras: [dirty_cameras.len]") //connect_camera_list(referrers) /obj/machinery/camera/ex_act(severity) if(src.invuln) return else ..(severity) return /obj/machinery/camera/emp_act() ..() if(!istype(src, /obj/machinery/camera/television)) //tv cams were getting messed up src.icon_state = "cameraemp" src.network = null //Not the best way but it will do. I think. camera_status-- if (coveredTiles) //ZeWaka: Fix for null.Copy() for(var/turf/O in coveredTiles.Copy()) //Remove all coverage O.removeCameraCoverage(src) src.remove_from_turfs() SPAWN_DBG(90 SECONDS) camera_status++ src.network = initial(src.network) if(!istype(src, /obj/machinery/camera/television)) src.icon_state = initial(src.icon_state) src.add_to_turfs() if (coveredTiles) for(var/turf/O in coveredTiles.Copy()) O.addCameraCoverage(src) updateCoverage() // (must happen in spawn!) src.disconnect_viewers() return /obj/machinery/camera/blob_act(var/power) return /obj/machinery/camera/attack_ai(var/mob/living/silicon/ai/user as mob) if (!istype(user)) //Other silicon mobs shouldn't try to encroach on the AI's "view through all cameras" schtick. Mostly because it generates runtime errors. return if (src.network != user.network || !(src.camera_status)) return //if (istype(user, /mob/living/silicon/ai/hologram)) // return user.current = src user.set_eye(src) // v the fuck is this??? v /*/obj/machinery/camera/attack_hand(mob/user as mob) if(user.jew == 1) src.camera_status = !( src.camera_status ) if (!( src.camera_status )) var/message = pick("OY VEY!","Bupkes!","Feh!","What a mishegas!","Schlocky putz!") user.say(message) src.icon_state = "camera1" else var/message = pick("OY VEY!","Bupkes!","Feh!","What a mishegas!","Schlocky putz!") user.say(message) src.icon_state = "camera" // now disconnect anyone using the camera disconnect_viewers() else return return*/ /obj/machinery/camera/proc/disconnect_viewers() for(var/mob/O in mobs) /* Not needed with new AI cam system if (isAI(O)) var/mob/living/silicon/ai/OAI = O if (OAI && OAI.current == src) if( OAI.camera_overlay_check(src) ) boutput(OAI, "Your regain your connection to the camera.") else boutput(OAI, "Your connection to the camera has been lost.") */ if (istype(O.machine, /obj/machinery/computer/security)) var/obj/machinery/computer/security/S = O.machine if (S.current == src) O.machine = null S.current = null O.set_eye(null) boutput(O, "The screen bursts into static.") /obj/machinery/camera/attackby(obj/item/W as obj, mob/user as mob) if(istype(W,/obj/item/parts/human_parts)) //dumb easter egg incoming user.visible_message("[user] wipes [src] with the bloody end of [W.name]. What the fuck?", "You wipe [src] with the bloody end of [W.name]. What the fuck?") return if (issnippingtool(W)) src.camera_status = !( src.camera_status ) if (!( src.camera_status )) user.visible_message("[user] has deactivated [src]!", "You have deactivated [src].") logTheThing("station", null, null, "[key_name(user)] deactivated a security camera ([showCoords(src.loc.x, src.loc.y, src.loc.z)])") playsound(src.loc, "sound/items/Wirecutter.ogg", 100, 1) src.icon_state = "camera1" add_fingerprint(user) if (coveredTiles) //ZeWaka: Fix for null.Copy() for(var/turf/O in coveredTiles.Copy()) //Remove all coverage O.removeCameraCoverage(src) src.remove_from_turfs() else user.visible_message("[user] has reactivated [src]!", "You have reactivated [src].") playsound(src.loc, "sound/items/Wirecutter.ogg", 100, 1) src.icon_state = "camera" add_fingerprint(user) src.add_to_turfs() if (coveredTiles) for(var/turf/O in coveredTiles.Copy()) O.addCameraCoverage(src) spawn() updateCoverage() //(must happen in spawn!) // now disconnect anyone using the camera src.disconnect_viewers() else if (istype(W, /obj/item/paper)) if (last_paper && ( (last_paper + 80) >= world.time)) return last_paper = world.time var/obj/item/paper/X = W boutput(user, "You hold a paper up to the camera ...") for(var/mob/O in mobs) if (isAI(O)) boutput(O, "[user] holds a paper up to one of your cameras ...") O.Browse(text("[][]", X.name, X.info), text("window=[]", X.name)) logTheThing("station", user, O, "holds up a paper to a camera at [log_loc(src)], forcing %target% to read it. Title: [X.name]. Text: [adminscrub(X.info)]") else if (istype(O.machine, /obj/machinery/computer/security)) var/obj/machinery/computer/security/S = O.machine if (S.current == src) boutput(O, "[user] holds a paper up to one of the cameras ...") O.Browse(text("[][]", X.name, X.info), text("window=[]", X.name)) logTheThing("station", user, O, "holds up a paper to a camera at [log_loc(src)], forcing %target% to read it. Title: [X.name]. Text: [adminscrub(X.info)]") //Return a working camera that can see a given mob //or null if none /proc/seen_by_camera(var/mob/M) .= 0 if (isturf(M.loc)) var/turf/T = M.loc .= (T.cameras && T.cameras.len) /obj/machinery/camera/motion name = "Motion Security Camera" var/list/motionTargets = list() var/detectTime = 0 var/locked = 1 /obj/machinery/camera/motion/process() // motion camera event loop . = ..() if (detectTime > 0) var/elapsed = world.time - detectTime if (elapsed > 300) triggerAlarm() else if (detectTime == -1) for (var/mob/target in motionTargets) if (isdead(target)) lostTarget(target) /obj/machinery/camera/motion/proc/newTarget(var/mob/target) if (isAI(target)) return 0 if (detectTime == 0) detectTime = world.time // start the clock if (!(target in motionTargets)) motionTargets += target return 1 /obj/machinery/camera/motion/proc/lostTarget(var/mob/target) if (target in motionTargets) motionTargets -= target if (motionTargets.len == 0) cancelAlarm() /obj/machinery/camera/motion/proc/cancelAlarm() if (detectTime == -1) for (var/mob/living/silicon/aiPlayer in mobs) if (camera_status) aiPlayer.cancelAlarm("Motion", src.loc.loc) detectTime = 0 return 1 /obj/machinery/camera/motion/proc/triggerAlarm() if (!detectTime) return 0 for (var/mob/living/silicon/aiPlayer in mobs) if (camera_status) aiPlayer.triggerAlarm("Motion", src.loc.loc, src) detectTime = -1 return 1 /obj/machinery/camera/motion/attackby(obj/item/W as obj, mob/user as mob) if (issnippingtool(W) && locked == 1) return if (isscrewingtool(W)) var/turf/T = user.loc boutput(user, text("[]ing the access hatch... (this is a long process)", (locked) ? "Open" : "Clos")) sleep(100) if ((user.loc == T && user.equipped() == W && !( user.stat ))) src.locked ^= 1 boutput(user, text("The access hatch is now [].", (locked) ? "closed" : "open")) ..() // call the parent to (de|re)activate if (issnippingtool(W)) // now handle alarm on/off... if (camera_status) // ok we've just been reconnected... send an alarm! detectTime = world.time - 301 triggerAlarm() else for (var/mob/living/silicon/aiPlayer in mobs) // manually cancel, to not disturb internal state aiPlayer.cancelAlarm("Motion", src.loc.loc) /*------------------------------------ CAMERA NETWORK STUFF ------------------------------------*/ /proc/build_camera_network() connect_camera_list(machines) /proc/rebuild_camera_network() if(defer_camnet_rebuild || !camnet_needs_rebuild) return connect_camera_list(dirty_cameras) dirty_cameras.Cut() camnet_needs_rebuild = 0 /proc/disconnect_camera_network() for(var/obj/machinery/camera/C in cameras) C.c_north = null C.c_east = null C.c_south = null C.c_west = null C.referrers.Cut() /proc/connect_camera_list(var/list/obj/machinery/camera/camlist, var/force_connection=0) if( camlist.len < 1) return 1 logTheThing("debug", null, null, "SpyGuy/Camnet: Starting to connect cameras") var/count = 0 for(var/obj/machinery/camera/C in camlist) if(!isturf(C.loc) || C.disposed || C.qdeled) //This is one of those weird internal cameras, or it's been deleted and hasn't had the decency to go away yet continue connect_camera_neighbours(C, NORTH, force_connection) connect_camera_neighbours(C, EAST, force_connection) connect_camera_neighbours(C, SOUTH, force_connection) connect_camera_neighbours(C, WEST, force_connection) count++ if(!(C.c_north || C.c_east || C.c_south || C.c_west)) logTheThing("debug", null, null, "SpyGuy/Camnet: Camera at [showCoords(C.x, C.y, C.z)] failed to receive cardinal directions during initialization.") logTheThing("debug", null, null, "SpyGuy/Camnet: Done. Connected [count] cameras.") return 0 /proc/connect_camera_neighbours(var/obj/machinery/camera/C, var/direction, var/force_connection=0) var/dir_var = "" //The direction we're trying to fill var/rec_var = "" //The reciprocal of this direction if(direction & NORTH) dir_var = "c_north" rec_var = "c_south" else if(direction & EAST) dir_var ="c_east" rec_var = "c_west" else if(direction & SOUTH) dir_var = "c_south" rec_var = "c_north" else if(direction & WEST) dir_var = "c_west" rec_var = "c_east" if(!dir_var) return if(!C.vars[dir_var] || force_connection) var/obj/machinery/camera/candidate = null candidate = getCameraMove(C, direction) /* if(!candidate) logTheThing("debug", null, null, "SpyGuy/Camnet: Camera at [showCoords(C.x, C.y, C.z)] didn't get a candidate when heading [dir2text(direction)].") return */ if(candidate && C.z == candidate.z && C.network == candidate.network) // && (!camera_network_reciprocity || !candidate.vars[rec_var])) C.vars[dir_var] = candidate candidate.addToReferrers(C) if(camera_network_reciprocity && (!candidate.vars[rec_var])) candidate.vars[rec_var] = C C.addToReferrers(candidate) /* else logTheThing("debug", null, null, "SpyGuy/Camnet: Camera at [showCoords(C.x, C.y, C.z)] rejected. cand z = [candidate.z], C z = [C.z]; cand net = [candidate.network], C net = [C.network]; reciprocity = [camera_network_reciprocity], rec_var:[rec_var] ( [isnull(candidate.vars[rec_var]) ? "null" : "not null"] )") else logTheThing("debug", null, null, "SpyGuy/Camnet: Camera at [showCoords(C.x, C.y, C.z)] rejected because [dir_var] was already set.") */