diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index acf0ab0809a..436e542dfda 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -1,4 +1,9 @@ -//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31 +//supposedly the fastest way to do this according to https://gist.github.com/Giacom/be635398926bb463b42a +#define RANGE_TURFS(RADIUS, CENTER) \ + block( \ + locate(max(CENTER.x-(RADIUS),1), max(CENTER.y-(RADIUS),1), CENTER.z), \ + locate(min(CENTER.x+(RADIUS),world.maxx), min(CENTER.y+(RADIUS),world.maxy), CENTER.z) \ + ) /proc/dopage(src,target) var/href_list diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 899e8caa98a..6d3ad3f365e 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1664,6 +1664,25 @@ var/mob/dview/dview_mob = new return I +//ultra range (no limitations on distance, faster than range for distances > 8); including areas drastically decreases performance +/proc/urange(dist=0, atom/center=usr, orange=0, areas=0) + if(!dist) + if(!orange) + return list(center) + else + return list() + + var/list/turfs = RANGE_TURFS(dist, center) + if(orange) + turfs -= get_turf(center) + . = list() + for(var/V in turfs) + var/turf/T = V + . += T + . += T.contents + if(areas) + . |= T.loc + /proc/turf_clear(turf/T) for(var/atom/A in T) if(A.simulated) diff --git a/code/_onclick/hud/ai.dm b/code/_onclick/hud/ai.dm index 0049537e9b4..d98851fc443 100644 --- a/code/_onclick/hud/ai.dm +++ b/code/_onclick/hud/ai.dm @@ -15,10 +15,9 @@ icon_state = "camera" /obj/screen/ai/camera_list/Click() - if(isAI(usr)) - var/mob/living/silicon/ai/AI = usr - var/camera = input(AI) in AI.get_camera_list() - AI.ai_camera_list(camera) + var/mob/living/silicon/ai/AI = usr + var/camera = input(AI, "Choose which camera you want to view", "Cameras") as null|anything in AI.get_camera_list() + AI.ai_camera_list(camera) /obj/screen/ai/camera_track name = "Track With Camera" diff --git a/code/datums/wires/camera.dm b/code/datums/wires/camera.dm index f91d3a9f419..d52b410b3dd 100644 --- a/code/datums/wires/camera.dm +++ b/code/datums/wires/camera.dm @@ -38,7 +38,7 @@ var/const/CAMERA_WIRE_NOTHING2 = 32 if(CAMERA_WIRE_POWER) if(C.status && !mended || !C.status && mended) - C.deactivate(usr, 1) + C.toggle_cam(usr, 1) if(CAMERA_WIRE_LIGHT) C.light_disabled = !mended @@ -60,7 +60,7 @@ var/const/CAMERA_WIRE_NOTHING2 = 32 C.setViewRange(new_range) if(CAMERA_WIRE_POWER) - C.deactivate(null) // Deactivate the camera + C.toggle_cam(null) // Deactivate the camera if(CAMERA_WIRE_LIGHT) C.light_disabled = !C.light_disabled diff --git a/code/datums/wires/robot.dm b/code/datums/wires/robot.dm index 6d839d77770..9e894fb9fec 100644 --- a/code/datums/wires/robot.dm +++ b/code/datums/wires/robot.dm @@ -49,7 +49,7 @@ var/const/BORG_WIRE_LAWCHECK = 16 // Not used on MoMMIs if (BORG_WIRE_CAMERA) if(!isnull(R.camera) && !R.scrambledcodes) R.camera.status = mended - R.camera.deactivate(usr, 0) // Will kick anyone who is watching the Cyborg's camera. + R.camera.toggle_cam(usr, 0) // Will kick anyone who is watching the Cyborg's camera. if(BORG_WIRE_LAWCHECK) //Forces a law update if the borg is set to receive them. Since an update would happen when the borg checks its laws anyway, not much use, but eh if (R.lawupdate) @@ -70,7 +70,7 @@ var/const/BORG_WIRE_LAWCHECK = 16 // Not used on MoMMIs if (BORG_WIRE_CAMERA) if(!isnull(R.camera) && R.camera.can_use() && !R.scrambledcodes) - R.camera.deactivate(usr, 0) // Kick anyone watching the Cyborg's camera, doesn't display you disconnecting the camera. + R.camera.toggle_cam(usr, 0) // Kick anyone watching the Cyborg's camera, doesn't display you disconnecting the camera. R.visible_message("[R]'s camera lense focuses loudly.") R << "Your camera lense focuses loudly." diff --git a/code/game/gamemodes/malfunction/Malf_Modules.dm b/code/game/gamemodes/malfunction/Malf_Modules.dm index 86f921da146..66f530c35ab 100644 --- a/code/game/gamemodes/malfunction/Malf_Modules.dm +++ b/code/game/gamemodes/malfunction/Malf_Modules.dm @@ -386,7 +386,7 @@ rcd light flash thingy on matter drain var/initial_range = initial(C.view_range) //To prevent calling the proc twice if(camera.uses > 0) if(!C.status) - C.deactivate(src, 1) //Reactivates the camera based on status. Badly named proc. + C.toggle_cam(src, 1) //Reactivates the camera based on status. Badly named proc. fixedcams++ camera.uses-- if(C.view_range != initial_range) diff --git a/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm b/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm index 7b056987953..1a6e687e206 100644 --- a/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm +++ b/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm @@ -161,7 +161,7 @@ /obj/machinery/camera/swarmer_act(mob/living/simple_animal/hostile/swarmer/S) S.DisIntegrate(src) - deactivate(S, 0) + toggle_cam(S, 0) /obj/machinery/particle_accelerator/swarmer_act(mob/living/simple_animal/hostile/swarmer/S) S << "Disrupting the power grid would bring no benefit to us. Aborting." diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 6e9c41ff983..77616cbb1b6 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -12,8 +12,8 @@ var/list/network = list("SS13") var/c_tag = null var/c_tag_order = 999 - var/status = 1.0 - anchored = 1.0 + var/status = 1 + anchored = 1 var/start_active = 0 //If it ignores the random chance to start broken on round start var/invuln = null var/obj/item/device/camera_bug/bug = null @@ -38,32 +38,15 @@ assembly.anchored = 1 assembly.update_icon() - invalidateCameraCache() - - if(cameranet.cameras_unsorted || !ticker) - cameranet.cameras += src - cameranet.cameras_unsorted = 1 - else - dd_insertObjectList(cameranet.cameras, src) - - var/list/open_networks = difflist(network,restricted_camera_networks) //...but if all of camera's networks are restricted, it only works for specific camera consoles. - if(open_networks.len) //If there is at least one open network, chunk is available for AI usage. - cameranet.addCamera(src) - - /* // Use this to look for cameras that have the same c_tag. - for(var/obj/machinery/camera/C in cameranet.cameras) - var/list/tempnetwork = C.network&src.network - if(C != src && C.c_tag == src.c_tag && tempnetwork.len) - log_to_dd("[src.c_tag] [src.x] [src.y] [src.z] conflicts with [C.c_tag] [C.x] [C.y] [C.z]") - */ + cameranet.cameras += src + cameranet.addCamera(src) /obj/machinery/camera/initialize() if(z == ZLEVEL_STATION && prob(3) && !start_active) - deactivate() + toggle_cam() /obj/machinery/camera/Destroy() - invalidateCameraCache() - deactivate(null, 0) //kick anyone viewing out + toggle_cam(null, 0) //kick anyone viewing out if(assembly) qdel(assembly) assembly = null @@ -76,26 +59,24 @@ wires = null cameranet.removeCamera(src) //Will handle removal from the camera network and the chunks, so we don't need to worry about that cameranet.cameras -= src - var/list/open_networks = difflist(network,restricted_camera_networks) - if(open_networks.len) - cameranet.removeCamera(src) return ..() /obj/machinery/camera/emp_act(severity) + if(!status) + return if(!isEmpProof()) - if(prob(100/severity)) - invalidateCameraCache() + if(prob(150/severity)) icon_state = "[initial(icon_state)]emp" var/list/previous_network = network network = list() cameranet.removeCamera(src) stat |= EMPED set_light(0) - triggerCameraAlarm(10 * severity) emped = emped+1 //Increase the number of consecutive EMP's var/thisemp = emped //Take note of which EMP this proc is for spawn(900) if(loc) //qdel limbo + triggerCameraAlarm() //camera alarm triggers even if multiple EMPs are in effect. if(emped == thisemp) //Only fix it if the camera hasn't been EMP'd again network = previous_network icon_state = initial(icon_state) @@ -104,7 +85,9 @@ if(can_use()) cameranet.addCamera(src) emped = 0 //Resets the consecutive EMP count - invalidateCameraCache() + spawn(100) + if(!qdeleted(src)) + cancelCameraAlarm() for(var/mob/O in mob_list) if (O.client && O.client.eye == src) O.unset_machine() @@ -128,19 +111,18 @@ if(!istype(user)) return user.do_attack_animation(src) + add_hiddenprint(user) status = 0 visible_message("\The [user] slashes at [src]!") playsound(src.loc, 'sound/weapons/slash.ogg', 100, 1) - icon_state = "[initial(icon_state)]1" - add_hiddenprint(user) - deactivate(user,0) + toggle_cam(user, 0) -/obj/machinery/camera/proc/setViewRange(var/num = 7) + +/obj/machinery/camera/proc/setViewRange(num = 7) src.view_range = num cameranet.updateVisibility(src, 0) /obj/machinery/camera/attackby(W as obj, mob/living/user as mob, params) - invalidateCameraCache() var/msg = "You attach [W] into the assembly inner circuits." var/msg2 = "The camera already has that upgrade!" @@ -214,12 +196,17 @@ for(var/mob/O in player_list) if(istype(O, /mob/living/silicon/ai)) var/mob/living/silicon/ai/AI = O - if(U.name == "Unknown") AI << "[U] holds \a [itemname] up to one of your cameras ..." - else AI << "[U] holds \a [itemname] up to one of your cameras ..." + if(AI.control_disabled || (AI.stat == DEAD)) + return + if(U.name == "Unknown") + AI << "[U] holds \a [itemname] up to one of your cameras ..." + else + AI << "[U] holds \a [itemname] up to one of your cameras ..." AI.last_paper_seen = "[itemname][info]" else if (O.client && O.client.eye == src) O << "[U] holds \a [itemname] up to one of the cameras ..." O << browse(text("[][]", itemname, info), text("window=[]", itemname)) + else if (istype(W, /obj/item/device/camera_bug)) if (!src.can_use()) user << "Camera non-functional." @@ -232,8 +219,9 @@ user << "Camera bugged." src.bug = W src.bug.bugged_cameras[src.c_tag] = src + else if(istype(W, /obj/item/weapon/melee/energy/blade))//Putting it here last since it's a special case. I wonder if there is a better way to do these than type casting. - deactivate(user,2)//Here so that you can disconnect anyone viewing the camera, regardless if it's on or off. + toggle_cam(user, 1) var/datum/effect/system/spark_spread/spark_system = new /datum/effect/system/spark_spread() spark_system.set_up(5, 0, loc) spark_system.start() @@ -241,6 +229,7 @@ playsound(loc, "sparks", 50, 1) visible_message("[user] has sliced the camera apart with an energy blade!") qdel(src) + else if(istype(W, /obj/item/device/laser_pointer)) var/obj/item/device/laser_pointer/L = W L.laser_act(src, user) @@ -248,32 +237,32 @@ ..() return -/obj/machinery/camera/proc/deactivate(user as mob, var/choice = 1) +/obj/machinery/camera/proc/toggle_cam(mob/user, displaymessage = 1) + status = !status if(can_use()) cameranet.addCamera(src) else set_light(0) cameranet.removeCamera(src) - if(choice==1) - invalidateCameraCache() - status = !( src.status ) - if (!(src.status)) - if(user) - visible_message("[user] deactivates [src]!") - add_hiddenprint(user) - else - visible_message("\The [src] deactivates!") - playsound(src.loc, 'sound/items/Wirecutter.ogg', 100, 1) - icon_state = "[initial(icon_state)]1" - + cameranet.updateChunk(x, y, z) + var/change_msg = "deactivates" + if(!status) + icon_state = "[initial(icon_state)]1" + else + icon_state = initial(icon_state) + change_msg = "reactivates" + triggerCameraAlarm() + spawn(100) + if(!qdeleted(src)) + cancelCameraAlarm() + if(displaymessage) + if(user) + visible_message("[user] [change_msg] [src]!") + add_hiddenprint(user) else - if(user) - visible_message("[user] reactivates [src]!") - add_hiddenprint(user) - else - visible_message("\The [src] reactivates!") - playsound(src.loc, 'sound/items/Wirecutter.ogg', 100, 1) - icon_state = initial(icon_state) + visible_message("\The [src] [change_msg]!") + + playsound(src.loc, 'sound/items/Wirecutter.ogg', 100, 1) // now disconnect anyone using the camera //Apparently, this will disconnect anyone even if the camera was re-activated. @@ -286,14 +275,14 @@ /obj/machinery/camera/proc/triggerCameraAlarm(var/duration = 0) alarm_on = 1 - camera_alarm.triggerAlarm(loc, src, duration) + motion_alarm.triggerAlarm(loc, src) /obj/machinery/camera/proc/cancelCameraAlarm() if(wires.IsIndexCut(CAMERA_WIRE_ALARM)) return alarm_on = 0 - camera_alarm.clearAlarm(loc, src) + motion_alarm.clearAlarm(loc, src) /obj/machinery/camera/proc/can_use() if(!status) @@ -339,7 +328,6 @@ return null /proc/near_range_camera(var/mob/M) - for(var/obj/machinery/camera/C in range(4, M)) if(C.can_use()) // check if camera disabled return C @@ -348,16 +336,13 @@ return null /obj/machinery/camera/proc/weld(var/obj/item/weapon/weldingtool/WT, var/mob/user) - if(busy) return 0 - if(!WT.isOn()) + if(!WT.remove_fuel(0, user)) return 0 - // Do after stuff here - user << "You start to weld [src]." + user << "You start to weld [src]..." playsound(src.loc, 'sound/items/Welder.ogg', 50, 1) - WT.eyecheck(user) busy = 1 if(do_after(user, 100, target = src)) busy = 0 @@ -367,12 +352,37 @@ busy = 0 return 0 +/obj/machinery/camera/proc/Togglelight(on=0) + for(var/mob/living/silicon/ai/A in ai_list) + for(var/obj/machinery/camera/cam in A.lit_cameras) + if(cam == src) + return + if(on) + src.set_light(AI_CAMERA_LUMINOSITY) + else + src.set_light(0) + /obj/machinery/camera/proc/nano_structure() var/cam[0] + var/turf/T = get_turf(src) cam["name"] = sanitize(c_tag) cam["deact"] = !can_use() cam["camera"] = "\ref[src]" - cam["x"] = x - cam["y"] = y - cam["z"] = z - return cam \ No newline at end of file + cam["x"] = T.x + cam["y"] = T.y + cam["z"] = T.z + return cam + +/obj/machinery/camera/portable //Cameras which are placed inside of things, such as helmets. + var/turf/prev_turf + +/obj/machinery/camera/portable/New() + ..() + assembly.state = 0 //These cameras are portable, and so shall be in the portable state if removed. + assembly.anchored = 0 + assembly.update_icon() + +/obj/machinery/camera/portable/process() //Updates whenever the camera is moved. + if(cameranet && get_turf(src) != prev_turf) + cameranet.updatePortableCamera(src) + prev_turf = get_turf(src) \ No newline at end of file diff --git a/code/game/machinery/camera/tracking.dm b/code/game/machinery/camera/tracking.dm index 7317450e02d..f33db20eb67 100644 --- a/code/game/machinery/camera/tracking.dm +++ b/code/game/machinery/camera/tracking.dm @@ -1,30 +1,36 @@ -/mob/living/silicon/ai/var/max_locations = 10 -/mob/living/silicon/ai/var/stored_locations[0] - /mob/living/silicon/ai/proc/InvalidTurf(turf/T as turf) if(!T) return 1 if((T.z in config.admin_levels)) return 1 - if(T.z > 6) + if(T.z > MAX_Z) return 1 return 0 + +/mob/living/silicon/ai/var/max_locations = 10 +/mob/living/silicon/ai/var/stored_locations[0] + /mob/living/silicon/ai/proc/get_camera_list() + track.cameras.Cut() + if(src.stat == 2) return - cameranet.process_sort() + var/list/L = list() + for (var/obj/machinery/camera/C in cameranet.cameras) + L.Add(C) + + camera_sort(L) var/list/T = list() - T["Cancel"] = "Cancel" - for (var/obj/machinery/camera/C in cameranet.cameras) - var/list/tempnetwork = C.network&src.network + + for (var/obj/machinery/camera/C in L) + var/list/tempnetwork = C.network & src.network if (tempnetwork.len) T[text("[][]", C.c_tag, (C.can_use() ? null : " (Deactivated)"))] = C - track = new() track.cameras = T return T @@ -63,7 +69,7 @@ src << "\red There is already a stored location by this name" return - var/L = src.eyeobj.getLoc() + var/L = get_turf(eyeobj) if (InvalidTurf(get_turf(L))) src << "\red Unable to store this location" return @@ -108,53 +114,37 @@ /mob/living/silicon/ai/proc/trackable_mobs() + track.names.Cut() + track.namecounts.Cut() + track.humans.Cut() + track.others.Cut() + if(usr.stat == 2) return list() - var/datum/trackable/TB = new() for(var/mob/living/M in mob_list) - // Easy checks first. - // Don't detect mobs on Centcom. Since the wizard den is on Centcomm, we only need this. - if(InvalidTurf(get_turf(M))) - continue - if(M == usr) - continue - if(M.invisibility)//cloaked - continue - if(M.digitalcamo) + if(!M.can_track(usr)) continue // Human check var/human = 0 if(istype(M, /mob/living/carbon/human)) human = 1 - var/mob/living/carbon/human/H = M - //Cameras can't track people wearing an untrackable card - var/obj/item/weapon/card/id/id = H.wear_id - if(istype(id) && id.is_untrackable()) - continue - if(istype(H.head, /obj/item/clothing/head)) - var/obj/item/clothing/head/hat = H.head - if(hat.blockTracking) - continue - // Now, are they trackable? (This is last because it's the most intensive check) - if(!trackable(M)) - continue var/name = M.name - if (name in TB.names) - TB.namecounts[name]++ - name = text("[] ([])", name, TB.namecounts[name]) + if (name in track.names) + track.namecounts[name]++ + name = text("[] ([])", name, track.namecounts[name]) else - TB.names.Add(name) - TB.namecounts[name] = 1 + track.names.Add(name) + track.namecounts[name] = 1 if(human) - TB.humans[name] = M + track.humans[name] = M else - TB.others[name] = M + track.others[name] = M + + var/list/targets = sortList(track.humans) + sortList(track.others) - var/list/targets = sortList(TB.humans) + sortList(TB.others) - src.track = TB return targets /mob/living/silicon/ai/proc/ai_camera_track(target_name in trackable_mobs()) @@ -162,14 +152,14 @@ set name = "Track With Camera" set desc = "Select who you would like to track." - if(src.stat == 2) + if(src.stat == DEAD) src << "You can't track with camera because you are dead!" return if(!target_name) - src.cameraFollow = null + return var/mob/target = (isnull(track.humans[target_name]) ? track.others[target_name] : track.humans[target_name]) - src.track = null + ai_actual_track(target) /mob/living/silicon/ai/proc/ai_cancel_tracking(var/forced = 0) @@ -177,65 +167,74 @@ return src << "Follow camera mode [forced ? "terminated" : "ended"]." - cameraFollow.tracking_cancelled() cameraFollow = null -/mob/living/silicon/ai/proc/ai_actual_track(atom/movable/target as mob|obj) - if(!istype(target)) return +/mob/living/silicon/ai/proc/ai_actual_track(mob/living/target) + if(!istype(target)) + return var/mob/living/silicon/ai/U = usr U.cameraFollow = target - U << "Now tracking [target.name] on camera." - target.tracking_initiated() + U.tracking = 1 - spawn (0) - while (U.cameraFollow == target) - if (U.cameraFollow == null) + U << "Attempting to track [target.get_visible_name()]..." + sleep(min(30, get_dist(target, U.eyeobj) / 4)) + spawn(15) //give the AI a grace period to stop moving. + U.tracking = 0 + + if(!target || !target.can_track(usr)) + U << "Target is not near any active cameras." + U.cameraFollow = null + return + + U << "Now tracking [target.get_visible_name()] on camera." + + var/cameraticks = 0 + spawn(0) + while(U.cameraFollow == target) + if(U.cameraFollow == null) return - if (!trackable(target)) - U << "Target is not on or near any active cameras on the station." - sleep(100) - continue + if(!target.can_track(usr)) + U.tracking = 1 + if(!cameraticks) + U << "Target is not near any active cameras. Attempting to reacquire..." + cameraticks++ + if(cameraticks > 9) + U.cameraFollow = null + U << "Unable to reacquire, cancelling track..." + U.tracking = 0 + return + else + sleep(10) + continue + + else + cameraticks = 0 + U.tracking = 0 if(U.eyeobj) - U.eyeobj.setLoc(get_turf(target), 0) + U.eyeobj.setLoc(get_turf(target)) + else view_core() + U.cameraFollow = null return + sleep(10) -/proc/near_camera(var/mob/living/M) +/proc/near_camera(mob/living/M) + if (!isturf(M.loc)) + return 0 if(isrobot(M)) var/mob/living/silicon/robot/R = M if(!(R.camera && R.camera.can_use()) && !cameranet.checkCameraVis(M)) return 0 - else if(!isturf(M.loc) || !cameranet.checkCameraVis(M)) + else if(!cameranet.checkCameraVis(M)) return 0 return 1 -/proc/trackable(atom/movable/M) - var/turf/T = get_turf(M) - if (istype(M, /mob/living/carbon/human)) - var/mob/living/carbon/human/H = M - var/obj/item/weapon/card/id/id = H.wear_id - if(istype(id) && id.is_untrackable()) - return 0 - if(H.digitalcamo) - return 0 - if(istype(H.head, /obj/item/clothing/head)) - var/obj/item/clothing/head/hat = H.head - if (hat.blockTracking) - return 0 - if(istype(M.loc,/obj/effect/dummy)) - return 0 - - if((T && (T.z in config.contact_levels)) && ((hassensorlevel(M, SUIT_SENSOR_TRACKING) || M in aibots))) - return 1 - - return near_camera(M) - -/obj/machinery/camera/attack_ai(var/mob/living/silicon/ai/user as mob) +/obj/machinery/camera/attack_ai(mob/living/silicon/ai/user) if (!istype(user)) return if (!src.can_use()) @@ -243,19 +242,21 @@ user.eyeobj.setLoc(get_turf(src)) -/mob/living/silicon/ai/attack_ai(var/mob/user as mob) +/mob/living/silicon/ai/attack_ai(mob/user) ai_camera_list() -/atom/movable/proc/tracking_initiated() +/proc/camera_sort(list/L) + var/obj/machinery/camera/a + var/obj/machinery/camera/b -/mob/living/silicon/robot/tracking_initiated() - tracking_entities++ - if(tracking_entities == 1 && has_zeroth_law()) - src << "Internal camera is currently being accessed." - -/atom/movable/proc/tracking_cancelled() - -/mob/living/silicon/robot/tracking_initiated() - tracking_entities-- - if(!tracking_entities && has_zeroth_law()) - src << "Internal camera is no longer being accessed." + for (var/i = L.len, i > 0, i--) + for (var/j = 1 to i - 1) + a = L[j] + b = L[j + 1] + if (a.c_tag_order != b.c_tag_order) + if (a.c_tag_order > b.c_tag_order) + L.Swap(j, j + 1) + else + if (sorttext(a.c_tag, b.c_tag) < 0) + L.Swap(j, j + 1) + return L diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index bcbafc5ffe5..187e7d28f85 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -1,8 +1,3 @@ -/var/camera_cache_id = 1 - -/proc/invalidateCameraCache() - camera_cache_id = (++camera_cache_id % 999999) - /obj/machinery/computer/security name = "Camera Monitor" desc = "Used to access the various cameras networks on the station." @@ -11,38 +6,35 @@ circuit = /obj/item/weapon/circuitboard/camera var/obj/machinery/camera/current = null var/list/network = list("") - var/last_pic = 1.0 + var/last_pic = 1 light_color = LIGHT_COLOR_RED var/mapping = 0 - var/cache_id = 0 var/list/networks[0] - var/list/tempnets[0] var/list/data[0] var/list/access[0] - var/camera_cache = null /obj/machinery/computer/security/New() // Lists existing networks and their required access. Format: networks[] = list() - networks["SS13"] = list(access_hos,access_captain) - networks["Telecomms"] = list(access_hos,access_captain) - networks["Research Outpost"] = list(access_rd,access_hos,access_captain) - networks["Mining Outpost"] = list(access_qm,access_hop,access_hos,access_captain) - networks["Research"] = list(access_rd,access_hos,access_captain) - networks["Prison"] = list(access_hos,access_captain) - networks["Labor"] = list(access_hos,access_captain) - networks["Interrogation"] = list(access_hos,access_captain) + networks["SS13"] = list(access_hos,access_captain) + networks["Telecomms"] = list(access_hos,access_captain) + networks["Research Outpost"] = list(access_rd,access_hos,access_captain) + networks["Mining Outpost"] = list(access_qm,access_hop,access_hos,access_captain) + networks["Research"] = list(access_rd,access_hos,access_captain) + networks["Prison"] = list(access_hos,access_captain) + networks["Labor"] = list(access_hos,access_captain) + networks["Interrogation"] = list(access_hos,access_captain) networks["Atmosphere Alarms"] = list(access_ce,access_hos,access_captain) - networks["Fire Alarms"] = list(access_ce,access_hos,access_captain) - networks["Power Alarms"] = list(access_ce,access_hos,access_captain) - networks["Supermatter"] = list(access_ce,access_hos,access_captain) - networks["MiniSat"] = list(access_rd,access_hos,access_captain) - networks["Singularity"] = list(access_ce,access_hos,access_captain) + networks["Fire Alarms"] = list(access_ce,access_hos,access_captain) + networks["Power Alarms"] = list(access_ce,access_hos,access_captain) + networks["Supermatter"] = list(access_ce,access_hos,access_captain) + networks["MiniSat"] = list(access_rd,access_hos,access_captain) + networks["Singularity"] = list(access_ce,access_hos,access_captain) networks["Anomaly Isolation"] = list(access_rd,access_hos,access_captain) - networks["Toxins"] = list(access_rd,access_hos,access_captain) - networks["Telepad"] = list(access_rd,access_hos,access_captain) - networks["TestChamber"] = list(access_rd,access_hos,access_captain) - networks["ERT"] = list(access_cent_specops_commander,access_cent_commander) - networks["CentComm"] = list(access_cent_security,access_cent_commander) - networks["Thunderdome"] = list(access_cent_thunder,access_cent_commander) + networks["Toxins"] = list(access_rd,access_hos,access_captain) + networks["Telepad"] = list(access_rd,access_hos,access_captain) + networks["TestChamber"] = list(access_rd,access_hos,access_captain) + networks["ERT"] = list(access_cent_specops_commander,access_cent_commander) + networks["CentComm"] = list(access_cent_security,access_cent_commander) + networks["Thunderdome"] = list(access_cent_thunder,access_cent_commander) ..() @@ -75,50 +67,55 @@ ui_interact(user) /obj/machinery/computer/security/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - if(src.z > 6) return - if(stat & (NOPOWER|BROKEN)) return - if(user.stat) return + if(stat & (NOPOWER|BROKEN)) + return + if(user.stat) + return var/data[0] - data["current"] = null - if(camera_cache_id != cache_id) - cache_id = camera_cache_id - cameranet.process_sort() + var/list/cameras = list() + for(var/obj/machinery/camera/C in cameranet.cameras) + if((z > MAX_Z || C.z > MAX_Z) && (C.z != z)) //can only recieve away mission cameras on away missions + continue + if(!can_access_camera(C)) + continue - var/cameras[0] - for(var/obj/machinery/camera/C in cameranet.cameras) - if(!can_access_camera(C)) - continue + cameras[++cameras.len] = C.nano_structure() - var/cam = C.nano_structure() - cameras[++cameras.len] = cam + for(var/i = cameras.len, i > 0, i--) //based off /proc/camera_sort, sorts cameras alphabetically for the UI + for(var/j = 1 to i - 1) + var/a = cameras[j] + var/b = cameras[j + 1] + if(sorttext(a["name"], b["name"]) < 0) + cameras.Swap(j, j + 1) - camera_cache=list2json(cameras) + data["cameras"] = cameras - tempnets.Cut() if(emagged) access = list(access_captain) // Assume captain level access when emagged data["emagged"] = 1 + if(isAI(user) || isrobot(user)) access = list(access_captain) // Assume captain level access when AI + var/tempnets[0] // Loop through the ID's permission, and check which networks the ID has access to. - for(var/l in networks) // Loop through networks. - for(var/m in networks[l]) // Loop through access levels of the networks. - if(m in access) - if(l in network) // Checks if the network is currently active. - tempnets.Add(list(list("name" = l, "active" = 1))) + for(var/net in networks) // Loop through networks. + for(var/req in networks[net]) // Loop through access levels of the networks. + if(req in access) + if(net in network) // Checks if the network is currently active. + tempnets.Add(list(list("name" = net, "active" = 1))) else - tempnets.Add(list(list("name" = l, "active" = 0))) + tempnets.Add(list(list("name" = net, "active" = 0))) break + if(tempnets.len) data["networks"] = tempnets if(current) data["current"] = current.nano_structure() - data["cameras"] = list("__json_cache" = camera_cache) ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open) if (!ui) @@ -134,20 +131,24 @@ ui.set_auto_update(1) /obj/machinery/computer/security/Topic(href, href_list) + if(..()) + return 1 + if(href_list["switchTo"]) - if(src.z>6 || stat&(NOPOWER|BROKEN)) return - if(usr.stat || ((get_dist(usr, src) > 1 || !( usr.canmove ) || usr.blinded) && !istype(usr, /mob/living/silicon))) return + if(stat & (NOPOWER|BROKEN)) + return 1 var/obj/machinery/camera/C = locate(href_list["switchTo"]) in cameranet.cameras - if(!C) return + if(!C) + return 1 switch_to_camera(usr, C) - return 1 + else if(href_list["reset"]) - if(src.z>6 || stat&(NOPOWER|BROKEN)) return - if(usr.stat || ((get_dist(usr, src) > 1 || !( usr.canmove ) || usr.blinded) && !istype(usr, /mob/living/silicon))) return + if(stat & (NOPOWER|BROKEN)) + return 1 reset_current() usr.check_eye(current) - return 1 + else if(href_list["activate"]) // Activate: enable or disable networks var/net = href_list["activate"] // Network to be enabled or disabled. var/active = href_list["active"] // Is the network currently active. @@ -159,17 +160,13 @@ else src.network += net break - invalidateCameraCache() nanomanager.update_uis(src) - else - . = ..() -/obj/machinery/computer/security/attack_hand(var/mob/user as mob) + +/obj/machinery/computer/security/attack_hand(mob/user) access = list() - if (src.z > 6) - user << "\red Unable to establish a connection: \black You're too far away from the station!" + if(stat & (NOPOWER|BROKEN)) return - if(stat & (NOPOWER|BROKEN)) return if(!isAI(user)) user.set_machine(src) @@ -191,9 +188,9 @@ // Switching to cameras /obj/machinery/computer/security/proc/switch_to_camera(var/mob/user, var/obj/machinery/camera/C) - if ((get_dist(user, src) > 1 || user.machine != src || user.blinded || !( user.canmove ) || !( C.can_use() )) && (!istype(user, /mob/living/silicon/ai))) + if((get_dist(user, src) > 1 || user.machine != src || user.blinded || !(user.canmove) || !C.can_use()) && !(istype(user, /mob/living/silicon/ai))) if(!C.can_use() && !isAI(user)) - src.current = null + current = null return 0 else if(isAI(user)) @@ -215,18 +212,11 @@ if(current) reset_current() - src.current = C + current = C if(current) use_power = 2 - var/mob/living/L = current.loc - if(istype(L)) - L.tracking_initiated() /obj/machinery/computer/security/proc/reset_current() - if(current) - var/mob/living/L = current.loc - if(istype(L)) - L.tracking_cancelled() current = null use_power = 1 diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm index 4b08503b317..9efa958caca 100644 --- a/code/game/objects/items/devices/multitool.dm +++ b/code/game/objects/items/devices/multitool.dm @@ -46,7 +46,7 @@ var/found_eye = 0 - for(var/mob/aiEye/A in living_mob_list) + for(var/mob/camera/aiEye/A in living_mob_list) var/turf/our_turf = get_turf(src) var/turf/eye_turf = get_turf(A) diff --git a/code/modules/alarm/alarm_handler.dm b/code/modules/alarm/alarm_handler.dm index 74144ac153c..bfa2b5f44ed 100644 --- a/code/modules/alarm/alarm_handler.dm +++ b/code/modules/alarm/alarm_handler.dm @@ -65,7 +65,6 @@ for(var/obj/machinery/camera/C in alarm.cameras()) if(was_raised) C.network.Add(category) - invalidateCameraCache() else C.network.Remove(category) notify_listeners(alarm, was_raised) diff --git a/code/modules/computer3/computers/camera.dm b/code/modules/computer3/computers/camera.dm index d2180c174a8..6dce48393db 100644 --- a/code/modules/computer3/computers/camera.dm +++ b/code/modules/computer3/computers/camera.dm @@ -190,7 +190,6 @@ if(temp.len) L.Add(C) - cameranet.process_sort() return L verify_machine(var/obj/machinery/camera/C,var/datum/file/camnet_key/key = null) diff --git a/code/modules/mob/camera/camera.dm b/code/modules/mob/camera/camera.dm index 18025f89b1e..18a066d03a9 100644 --- a/code/modules/mob/camera/camera.dm +++ b/code/modules/mob/camera/camera.dm @@ -8,7 +8,7 @@ mouse_opacity = 0 see_in_dark = 7 invisibility = 101 // No one can see us - + sight = SEE_SELF move_on_shuttle = 0 /mob/camera/experience_pressure_difference() @@ -17,7 +17,7 @@ /mob/camera/Destroy() ..() return QDEL_HINT_HARDDEL_NOW - + /mob/camera/Login() ..() update_interface() diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index faa648ce31f..787d1e51b44 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -694,10 +694,10 @@ return //repurposed proc. Now it combines get_id_name() and get_face_name() to determine a mob's name variable. Made into a seperate proc as it'll be useful elsewhere -/mob/living/carbon/human/proc/get_visible_name() - if( wear_mask && (wear_mask.flags_inv&HIDEFACE) ) //Wearing a mask which hides our face, use id-name if possible +/mob/living/carbon/human/get_visible_name() + if(wear_mask && (wear_mask.flags_inv & HIDEFACE)) //Wearing a mask which hides our face, use id-name if possible return get_id_name("Unknown") - if( head && (head.flags_inv&HIDEFACE) ) + if(head && (head.flags_inv & HIDEFACE)) return get_id_name("Unknown") //Likewise for hats var/face_name = get_face_name() var/id_name = get_id_name("") @@ -1913,5 +1913,17 @@ src << "You swallow a gulp of [toDrink]." return 1 +/mob/living/carbon/human/can_track(mob/living/user) + if(wear_id) + var/obj/item/weapon/card/id/id = wear_id + if(istype(id) && id.is_untrackable()) + return 0 + if(istype(head, /obj/item/clothing/head)) + var/obj/item/clothing/head/hat = head + if(hat.blockTracking) + return 0 + + return ..() + /mob/living/carbon/human/proc/get_age_pitch() return 1.0 + 0.5*(30 - age)/80 \ No newline at end of file diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index dd37b374fd1..1dceb6a20e8 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -10,6 +10,28 @@ if(rig) SetupStat(rig) +/mob/living/proc/can_track(mob/living/user) + //basic fast checks go first. When overriding this proc, I recommend calling ..() at the end. + var/turf/T = get_turf(src) + if(!T) + return 0 + if(T.z == ZLEVEL_CENTCOMM) //dont detect mobs on centcomm + return 0 + if(T.z >= MAX_Z) + return 0 + if(user != null && src == user) + return 0 + if(invisibility || alpha == 0)//cloaked + return 0 + if(digitalcamo) + return 0 + + // Now, are they viewable by a camera? (This is last because it's the most intensive check) + if(!near_camera(src)) + return 0 + + return 1 + //mob verbs are a lot faster than object verbs //for more info on why this is not atom/pull, see examinate() in mob.dm /mob/living/verb/pulled(atom/movable/AM as mob|obj in oview(1)) @@ -760,6 +782,9 @@ src << "You're too exhausted to keep going..." Weaken(5) +/mob/living/proc/get_visible_name() + return name + /mob/living/update_gravity(has_gravity) if(!ticker) return diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 8509d59e547..049fec7805a 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -46,7 +46,7 @@ var/list/ai_verbs_default = list( var/list/connected_robots = list() var/aiRestorePowerRoutine = 0 //var/list/laws = list() - var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list()) + var/alarms = list("Motion" = list(), "Fire" = list(), "Atmosphere" = list(), "Power" = list(), "Camera" = list()) var/viewalerts = 0 var/icon/holo_icon//Default is assigned when AI is created. var/obj/mecha/controlled_mech //For controlled_mech a mech, to determine whether to relaymove or use the AI eye. @@ -69,10 +69,13 @@ var/list/ai_verbs_default = list( var/obj/machinery/power/apc/malfhack = null var/explosive = 0 //does the AI explode when it dies? - var/mob/living/silicon/ai/parent = null - var/camera_light_on = 0 //Defines if the AI toggled the light on the camera it's looking through. - var/datum/trackable/track = null + var/mob/living/silicon/ai/parent = null + var/camera_light_on = 0 + var/list/obj/machinery/camera/lit_cameras = list() + + var/datum/trackable/track = new() + var/last_paper_seen = null var/can_shunt = 1 var/last_announcement = "" @@ -81,6 +84,15 @@ var/list/ai_verbs_default = list( var/turf/waypoint //Holds the turf of the currently selected waypoint. var/waypoint_mode = 0 //Waypoint mode is for selecting a turf via clicking. + var/obj/machinery/hologram/holopad/holo = null + var/mob/camera/aiEye/eyeobj = new() + var/sprint = 10 + var/cooldown = 0 + var/acceleration = 1 + var/tracking = 0 //this is 1 if the AI is currently tracking somebody, but the track has not yet been completed. + + var/obj/machinery/camera/portable/builtInCamera + //var/obj/item/borg/sight/hud/sec/sechud = null //var/obj/item/borg/sight/hud/med/healthhud = null @@ -175,6 +187,13 @@ var/list/ai_verbs_default = list( spawn(5) new /obj/machinery/ai_powersupply(src) + eyeobj.ai = src + eyeobj.name = "[src.name] (AI Eye)" // Give it a name + eyeobj.loc = src.loc + + builtInCamera = new /obj/machinery/camera/portable(src) + builtInCamera.c_tag = name + builtInCamera.network = list("SS13") ai_list += src shuttle_caller_list += src @@ -486,8 +505,8 @@ var/list/ai_verbs_default = list( return if (href_list["track"]) - var/mob/target = locate(href_list["track"]) in mob_list - if(target && trackable(target)) + var/mob/living/target = locate(href_list["track"]) in mob_list + if(target && target.can_track()) ai_actual_track(target) else src << "Target is not on or near any active cameras on the station." @@ -495,7 +514,7 @@ var/list/ai_verbs_default = list( if (href_list["trackbot"]) var/obj/machinery/bot/target = locate(href_list["trackbot"]) in aibots - if(target && trackable(target)) + if(target) ai_actual_track(target) else src << "Target is not on or near any active cameras on the station." @@ -633,7 +652,6 @@ var/list/ai_verbs_default = list( d += "[bot_area.name]" d += "Interface" d += "Call" - d += "Track" d += "" d = format_text(d) @@ -664,7 +682,10 @@ var/list/ai_verbs_default = list( /mob/living/silicon/ai/proc/switchCamera(var/obj/machinery/camera/C) - if (!C || stat == 2) //C.can_use()) + if(!tracking) + cameraFollow = null + + if (!C || stat == DEAD) //C.can_use()) return 0 if(!src.eyeobj) @@ -825,17 +846,20 @@ var/list/ai_verbs_default = list( if(stat != CONSCIOUS) return - if(check_unable()) + camera_light_on = !camera_light_on + + if (!camera_light_on) + src << "Camera lights deactivated." + + for (var/obj/machinery/camera/C in lit_cameras) + C.set_light(0) + lit_cameras = list() + return - camera_light_on = !camera_light_on - src << "Camera lights [camera_light_on ? "activated" : "deactivated"]." - if(!camera_light_on) - if(current) - current.set_light(0) - current = null - else - lightNearbyCamera() + light_cameras() + + src << "Camera lights activated." /mob/living/silicon/ai/proc/sensor_mode() set name = "Set Sensor Augmentation" @@ -856,26 +880,25 @@ var/list/ai_verbs_default = list( // Handled camera lighting, when toggled. // It will get the nearest camera from the eyeobj, lighting it. -/mob/living/silicon/ai/proc/lightNearbyCamera() - if(camera_light_on && camera_light_on < world.timeofday) - if(src.current) - var/obj/machinery/camera/camera = near_range_camera(src.eyeobj) - if(camera && src.current != camera) - src.current.set_light(0) - if(!camera.light_disabled) - src.current = camera - src.current.set_light(AI_CAMERA_LUMINOSITY) - else - src.current = null - else if(isnull(camera)) - src.current.set_light(0) - src.current = null - else - var/obj/machinery/camera/camera = near_range_camera(src.eyeobj) - if(camera && !camera.light_disabled) - src.current = camera - src.current.set_light(AI_CAMERA_LUMINOSITY) - camera_light_on = world.timeofday + 1 * 20 // Update the light every 2 seconds. +/mob/living/silicon/ai/proc/light_cameras() + var/list/obj/machinery/camera/add = list() + var/list/obj/machinery/camera/remove = list() + var/list/obj/machinery/camera/visible = list() + for (var/datum/camerachunk/CC in eyeobj.visibleCameraChunks) + for (var/obj/machinery/camera/C in CC.cameras) + if (!C.can_use() || get_dist(C, eyeobj) > 7) + continue + visible |= C + + add = visible - lit_cameras + remove = lit_cameras - visible + + for (var/obj/machinery/camera/C in remove) + lit_cameras -= C //Removed from list before turning off the light so that it doesn't check the AI looking away. + C.Togglelight(0) + for (var/obj/machinery/camera/C in add) + C.Togglelight(1) + lit_cameras |= C /mob/living/silicon/ai/attackby(obj/item/weapon/W as obj, mob/user as mob, params) diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm index d2b23c93eff..9c4ab44a5ca 100644 --- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm +++ b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm @@ -2,33 +2,34 @@ // // The datum containing all the chunks. +var/const/CHUNK_SIZE = 16 // Only chunk sizes that are to the power of 2. E.g: 2, 4, 8, 16, etc.. + var/datum/cameranet/cameranet = new() /datum/cameranet + var/name = "Camera Net" // Name to show for VV and stat() + // The cameras on the map, no matter if they work or not. Updated in obj/machinery/camera.dm by New() and Destroy(). var/list/cameras = list() - var/cameras_unsorted = 1 // The chunks of the map, mapping the areas that the cameras can see. var/list/chunks = list() var/ready = 0 -/datum/cameranet/proc/process_sort() - if(cameras_unsorted) - cameras = dd_sortedObjectList(cameras) - cameras_unsorted = 0 + // The object used for the clickable stat() button. + var/obj/effect/statclick/statclick // Checks if a chunk has been Generated in x, y, z. /datum/cameranet/proc/chunkGenerated(x, y, z) - x &= ~0xf - y &= ~0xf + x &= ~(CHUNK_SIZE - 1) + y &= ~(CHUNK_SIZE - 1) var/key = "[x],[y],[z]" return (chunks[key]) // Returns the chunk in the x, y, z. // If there is no chunk, it creates a new chunk and returns that. /datum/cameranet/proc/getCameraChunk(x, y, z) - x &= ~0xf - y &= ~0xf + x &= ~(CHUNK_SIZE - 1) + y &= ~(CHUNK_SIZE - 1) var/key = "[x],[y],[z]" if(!chunks[key]) chunks[key] = new /datum/camerachunk(null, x, y, z) @@ -37,12 +38,12 @@ var/datum/cameranet/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(mob/aiEye/ai) +/datum/cameranet/proc/visibility(mob/camera/aiEye/ai) // 0xf = 15 - var/x1 = max(0, ai.x - 16) & ~0xf - var/y1 = max(0, ai.y - 16) & ~0xf - var/x2 = min(world.maxx, ai.x + 16) & ~0xf - var/y2 = min(world.maxy, ai.y + 16) & ~0xf + var/x1 = max(0, ai.x - 16) & ~(CHUNK_SIZE - 1) + var/y1 = max(0, ai.y - 16) & ~(CHUNK_SIZE - 1) + var/x2 = min(world.maxx, ai.x + 16) & ~(CHUNK_SIZE - 1) + var/y2 = min(world.maxy, ai.y + 16) & ~(CHUNK_SIZE - 1) var/list/visibleChunks = list() @@ -63,7 +64,7 @@ var/datum/cameranet/cameranet = new() // 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, var/opacity_check = 1) +/datum/cameranet/proc/updateVisibility(atom/A, opacity_check = 1) if(!ticker || (opacity_check && !A.opacity)) return @@ -109,15 +110,15 @@ var/datum/cameranet/cameranet = new() var/turf/T = get_turf(c) if(T) - var/x1 = max(0, T.x - 8) & ~0xf - var/y1 = max(0, T.y - 8) & ~0xf - var/x2 = min(world.maxx, T.x + 8) & ~0xf - var/y2 = min(world.maxy, T.y + 8) & ~0xf + var/x1 = max(0, T.x - (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1) + var/y1 = max(0, T.y - (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1) + var/x2 = min(world.maxx, T.x + (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1) + var/y2 = min(world.maxy, T.y + (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1) //world << "X1: [x1] - Y1: [y1] - X2: [x2] - Y2: [y2]" - for(var/x = x1; x <= x2; x += 16) - for(var/y = y1; y <= y2; y += 16) + for(var/x = x1; x <= x2; x += CHUNK_SIZE) + for(var/y = y1; y <= y2; y += CHUNK_SIZE) if(chunkGenerated(x, y, T.z)) var/datum/camerachunk/chunk = getCameraChunk(x, y, T.z) if(choice == 0) @@ -145,6 +146,14 @@ var/datum/cameranet/cameranet = new() return 1 return 0 +/* +/datum/cameranet/proc/stat_entry() + if(!statclick) + statclick = new/obj/effect/statclick/debug("Initializing...", src) + + stat(name, statclick.update("Cameras: [cameranet.cameras.len] | Chunks: [cameranet.chunks.len]")) +*/ + // Debug verb for VVing the chunk that the turf is in. /* /turf/verb/view_chunk() diff --git a/code/modules/mob/living/silicon/ai/freelook/chunk.dm b/code/modules/mob/living/silicon/ai/freelook/chunk.dm index fda4ef2f78e..e4b7f983b9d 100644 --- a/code/modules/mob/living/silicon/ai/freelook/chunk.dm +++ b/code/modules/mob/living/silicon/ai/freelook/chunk.dm @@ -21,26 +21,24 @@ // Add an AI eye to the chunk, then update if changed. -/datum/camerachunk/proc/add(mob/aiEye/ai) - if(!ai.ai) - return - ai.visibleCameraChunks += src - if(ai.ai.client) - ai.ai.client.images += obscured +/datum/camerachunk/proc/add(mob/camera/aiEye/eye) + var/client/client = eye.GetViewerClient() + if(client) + client.images += obscured + eye.visibleCameraChunks += src visible++ - seenby += ai + seenby += eye if(changed && !updating) update() // Remove an AI eye from the chunk, then update if changed. -/datum/camerachunk/proc/remove(mob/aiEye/ai) - if(!ai.ai) - return - ai.visibleCameraChunks -= src - if(ai.ai.client) - ai.ai.client.images -= obscured - seenby -= ai +/datum/camerachunk/proc/remove(mob/camera/aiEye/eye) + var/client/client = eye.GetViewerClient() + if(client) + client.images -= obscured + eye.visibleCameraChunks -= src + seenby -= eye if(visible > 0) visible-- @@ -54,7 +52,7 @@ // Updates the chunk, makes sure that it doesn't update too much. If the chunk isn't being watched it will // instead be flagged to update the next time an AI Eye moves near it. -/datum/camerachunk/proc/hasChanged(var/update_now = 0) +/datum/camerachunk/proc/hasChanged(update_now = 0) if(visible || update_now) if(!updating) updating = 1 @@ -68,7 +66,7 @@ /datum/camerachunk/proc/update() - set background = 1 + set background = BACKGROUND_ENABLED var/list/newVisibleTurfs = list() @@ -81,11 +79,14 @@ if(!c.can_use()) continue - var/turf/point = locate(src.x + 8, src.y + 8, src.z) - if(get_dist(point, c) > 24) + var/turf/point = locate(src.x + (CHUNK_SIZE / 2), src.y + (CHUNK_SIZE / 2), src.z) + if(get_dist(point, c) > CHUNK_SIZE + (CHUNK_SIZE / 2)) continue for(var/turf/t in c.can_see()) + // Possible optimization: if(turfs[t]) here, rather than &= turfs afterwards. + // List associations use a tree or hashmap of some sort (alongside the list itself) + // so are surprisingly fast. (significantly faster than var/thingy/x in list, in testing) newVisibleTurfs[t] = t // Removes turf that isn't in turfs. @@ -102,11 +103,12 @@ if(t.obscured) obscured -= t.obscured for(var/eye in seenby) - var/mob/aiEye/m = eye - if(!m || !m.ai) + var/mob/camera/aiEye/m = eye + if(!m) continue - if(m.ai.client) - m.ai.client.images -= t.obscured + var/client/client = m.GetViewerClient() + if(client) + client.images -= t.obscured for(var/turf in visRemoved) var/turf/t = turf @@ -116,32 +118,32 @@ obscured += t.obscured for(var/eye in seenby) - var/mob/aiEye/m = eye - if(!m || !m.ai) + var/mob/camera/aiEye/m = eye + if(!m) seenby -= m continue - if(m.ai.client) - m.ai.client.images += t.obscured + var/client/client = m.GetViewerClient() + if(client) + client.images += t.obscured // Create a new camera chunk, since the chunks are made as they are needed. /datum/camerachunk/New(loc, x, y, z) // 0xf = 15 - x &= ~0xf - y &= ~0xf + x &= ~(CHUNK_SIZE - 1) + y &= ~(CHUNK_SIZE - 1) src.x = x src.y = y src.z = z - for(var/obj/machinery/camera/c in range(16, locate(x + 8, y + 8, z))) + for(var/obj/machinery/camera/c in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z))) if(c.can_use()) cameras += c - for(var/turf/t in range(10, locate(x + 8, y + 8, z))) - if(t.x >= x && t.y >= y && t.x < x + 16 && t.y < y + 16) - turfs[t] = t + for(var/turf/t in block(locate(x, y, z), locate(min(x + CHUNK_SIZE - 1, world.maxx), min(y + CHUNK_SIZE - 1, world.maxy), z))) + turfs[t] = t for(var/camera in cameras) var/obj/machinery/camera/c = camera @@ -152,6 +154,9 @@ continue for(var/turf/t in c.can_see()) + // Possible optimization: if(turfs[t]) here, rather than &= turfs afterwards. + // List associations use a tree or hashmap of some sort (alongside the list itself) + // so are surprisingly fast. (significantly faster than var/thingy/x in list, in testing) visibleTurfs[t] = t // Removes turf that isn't in turfs. diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index 6162ca31b33..b8d27787247 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -3,57 +3,27 @@ // An invisible (no icon) mob that the AI controls to look around the station with. // It streams chunks as it moves around, which will show it what the AI can and cannot see. -/mob/aiEye +/mob/camera/aiEye name = "Inactive AI Eye" - icon = 'icons/mob/AI.dmi' + + icon = 'icons/mob/AI.dmi' //Allows ghosts to see what the AI is looking at. icon_state = "eye" alpha = 127 + invisibility = SEE_INVISIBLE_OBSERVER + var/list/visibleCameraChunks = list() var/mob/living/silicon/ai/ai = null - density = 0 - status_flags = GODMODE // You can't damage it. - mouse_opacity = 0 - see_in_dark = 7 - invisibility = SEE_INVISIBLE_OBSERVER - var/ghostimage = null - -/mob/aiEye/New() - ghostimage = image(src.icon,src,src.icon_state) - ghost_darkness_images |= ghostimage //so ghosts can see the blob cursor when they disable darkness - updateallghostimages() - ..() - -/mob/aiEye/Destroy() - if (ghostimage) - ghost_darkness_images -= ghostimage - qdel(ghostimage) - ghostimage = null; - updateallghostimages() - ai = null - return ..() - -// Movement code. Returns 0 to stop air movement from moving it. -/mob/aiEye/Move() - return 0 - -// Hide popout menu verbs -/mob/aiEye/examinate(atom/A as mob|obj|turf in view()) - set popup_menu = 0 - set src = usr.contents - return 0 + var/relay_speech = FALSE // Use this when setting the aiEye's location. // It will also stream the chunk that the new loc is in. -/mob/aiEye/setLoc(var/T, var/cancel_tracking = 1) + +/mob/camera/aiEye/setLoc(T) if(ai) if(!isturf(ai.loc)) return - - if(cancel_tracking) - ai.ai_cancel_tracking() - T = get_turf(T) loc = T cameranet.visibility(src) @@ -63,40 +33,27 @@ if(ai.holo) ai.holo.move_hologram() -/mob/aiEye/proc/getLoc() - - if(ai) - if(!isturf(ai.loc) || !ai.client) - return - return ai.eyeobj.loc - -/mob/aiEye/experience_pressure_difference() +/mob/camera/aiEye/Move() return 0 -// AI MOVEMENT +/mob/camera/aiEye/proc/GetViewerClient() + if(ai) + return ai.client + return null -// The AI's "eye". Described on the top of the page. - -/mob/living/silicon/ai - var/mob/aiEye/eyeobj = new() - var/sprint = 10 - var/cooldown = 0 - var/acceleration = 1 - var/obj/machinery/hologram/holopad/holo = null - -// Intiliaze the eye by assigning it's "ai" variable to us. Then set it's loc to us. -/mob/living/silicon/ai/New() - ..() - eyeobj.ai = src - eyeobj.name = "[src.name] (AI Eye)" // Give it a name - spawn(5) - eyeobj.loc = src.loc +/mob/camera/aiEye/Destroy() + ai = null + return ..() /atom/proc/move_camera_by_click() if(istype(usr, /mob/living/silicon/ai)) var/mob/living/silicon/ai/AI = usr if(AI.eyeobj && AI.client.eye == AI.eyeobj) - AI.eyeobj.setLoc(src) + AI.cameraFollow = null + if (isturf(src.loc) || isturf(src)) + AI.eyeobj.setLoc(src) + +// AI MOVEMENT // This will move the AIEye. It will also cause lights near the eye to light up, if toggled. // This is handled in the proc below this one. @@ -120,38 +77,42 @@ else user.sprint = initial + if(!user.tracking) + user.cameraFollow = null + //user.unset_machine() //Uncomment this if it causes problems. //user.lightNearbyCamera() - + if (user.camera_light_on) + user.light_cameras() // Return to the Core. - /mob/living/silicon/ai/proc/core() set category = "AI Commands" set name = "AI Core" view_core() - /mob/living/silicon/ai/proc/view_core() + current = null + cameraFollow = null unset_machine() - if(!src.eyeobj) + if(src.eyeobj && src.loc) + src.eyeobj.loc = src.loc + else src << "ERROR: Eyeobj not found. Creating new eye..." - src.eyeobj = new(loc) + src.eyeobj = new(src.loc) src.eyeobj.ai = src - src.rename_character(null, real_name) + src.eyeobj.name = "[src.name] (AI Eye)" // Give it a name - if(client && client.eye) - client.eye = src - for(var/datum/camerachunk/c in eyeobj.visibleCameraChunks) - c.remove(eyeobj) - src.eyeobj.setLoc(src) + eyeobj.setLoc(loc) /mob/living/silicon/ai/proc/toggle_acceleration() set category = "AI Commands" set name = "Toggle Camera Acceleration" + if(usr.stat == 2) + return //won't work if dead acceleration = !acceleration usr << "Camera acceleration has been toggled [acceleration ? "on" : "off"]." diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 49a52de9113..ff22f315867 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -108,7 +108,7 @@ proc/isembryo(A) return 1 /proc/isAIEye(A) - if(istype(A, /mob/aiEye)) + if(istype(A, /mob/camera/aiEye)) return 1 return 0 @@ -379,7 +379,7 @@ proc/Gibberish(t, p)//t is the inputted message, and any value higher than 70 fo var/atom/oldeye=M.client.eye var/aiEyeFlag = 0 - if(istype(oldeye, /mob/aiEye)) + if(istype(oldeye, /mob/camera/aiEye)) aiEyeFlag = 1 var/x diff --git a/code/modules/paperwork/silicon_photography.dm b/code/modules/paperwork/silicon_photography.dm index 0826090c59c..ec4d920eaaa 100644 --- a/code/modules/paperwork/silicon_photography.dm +++ b/code/modules/paperwork/silicon_photography.dm @@ -122,7 +122,7 @@ set desc = "Delete image" set src in usr - deletepicture() + deletepicture(src) /obj/item/device/camera/siliconcam/robot_camera/verb/take_image() set category ="Robot Commands" diff --git a/nano/assets/libraries.min.js b/nano/assets/libraries.min.js index 047902609a2..ba3441604d1 100644 --- a/nano/assets/libraries.min.js +++ b/nano/assets/libraries.min.js @@ -1,5 +1,5 @@ -!function(e,t){"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){function n(e){var t="length"in e&&e.length,n=oe.type(e);return"function"===n||oe.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e}function i(e,t,n){if(oe.isFunction(t))return oe.grep(e,function(e,i){return!!t.call(e,i,e)!==n});if(t.nodeType)return oe.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(pe.test(t))return oe.filter(t,e,n);t=oe.filter(t,e)}return oe.grep(e,function(e){return oe.inArray(e,t)>=0!==n})}function o(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function r(e){var t=xe[e]={};return oe.each(e.match(be)||[],function(e,n){t[n]=!0}),t}function s(){he.addEventListener?(he.removeEventListener("DOMContentLoaded",a,!1),e.removeEventListener("load",a,!1)):(he.detachEvent("onreadystatechange",a),e.detachEvent("onload",a))}function a(){(he.addEventListener||"load"===event.type||"complete"===he.readyState)&&(s(),oe.ready())}function l(e,t,n){if(void 0===n&&1===e.nodeType){var i="data-"+t.replace(Ne,"-$1").toLowerCase();if(n=e.getAttribute(i),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:Ce.test(n)?oe.parseJSON(n):n}catch(o){}oe.data(e,t,n)}else n=void 0}return n}function u(e){var t;for(t in e)if(("data"!==t||!oe.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function c(e,t,n,i){if(oe.acceptData(e)){var o,r,s=oe.expando,a=e.nodeType,l=a?oe.cache:e,u=a?e[s]:e[s]&&s;if(u&&l[u]&&(i||l[u].data)||void 0!==n||"string"!=typeof t)return u||(u=a?e[s]=Q.pop()||oe.guid++:s),l[u]||(l[u]=a?{}:{toJSON:oe.noop}),("object"==typeof t||"function"==typeof t)&&(i?l[u]=oe.extend(l[u],t):l[u].data=oe.extend(l[u].data,t)),r=l[u],i||(r.data||(r.data={}),r=r.data),void 0!==n&&(r[oe.camelCase(t)]=n),"string"==typeof t?(o=r[t],null==o&&(o=r[oe.camelCase(t)])):o=r,o}}function f(e,t,n){if(oe.acceptData(e)){var i,o,r=e.nodeType,s=r?oe.cache:e,a=r?e[oe.expando]:oe.expando;if(s[a]){if(t&&(i=n?s[a]:s[a].data)){oe.isArray(t)?t=t.concat(oe.map(t,oe.camelCase)):t in i?t=[t]:(t=oe.camelCase(t),t=t in i?[t]:t.split(" ")),o=t.length;for(;o--;)delete i[t[o]];if(n?!u(i):!oe.isEmptyObject(i))return}(n||(delete s[a].data,u(s[a])))&&(r?oe.cleanData([e],!0):ne.deleteExpando||s!=s.window?delete s[a]:s[a]=null)}}}function p(){return!0}function d(){return!1}function h(){try{return he.activeElement}catch(e){}}function g(e){var t=We.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function m(e,t){var n,i,o=0,r=typeof e.getElementsByTagName!==_e?e.getElementsByTagName(t||"*"):typeof e.querySelectorAll!==_e?e.querySelectorAll(t||"*"):void 0;if(!r)for(r=[],n=e.childNodes||e;null!=(i=n[o]);o++)!t||oe.nodeName(i,t)?r.push(i):oe.merge(r,m(i,t));return void 0===t||t&&oe.nodeName(e,t)?oe.merge([e],r):r}function v(e){Pe.test(e.type)&&(e.defaultChecked=e.checked)}function y(e,t){return oe.nodeName(e,"table")&&oe.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function b(e){return e.type=(null!==oe.find.attr(e,"type"))+"/"+e.type,e}function x(e){var t=Ye.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function w(e,t){for(var n,i=0;null!=(n=e[i]);i++)oe._data(n,"globalEval",!t||oe._data(t[i],"globalEval"))}function T(e,t){if(1===t.nodeType&&oe.hasData(e)){var n,i,o,r=oe._data(e),s=oe._data(t,r),a=r.events;if(a){delete s.handle,s.events={};for(n in a)for(i=0,o=a[n].length;o>i;i++)oe.event.add(t,n,a[n][i])}s.data&&(s.data=oe.extend({},s.data))}}function _(e,t){var n,i,o;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!ne.noCloneEvent&&t[oe.expando]){o=oe._data(t);for(i in o.events)oe.removeEvent(t,i,o.handle);t.removeAttribute(oe.expando)}"script"===n&&t.text!==e.text?(b(t).text=e.text,x(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),ne.html5Clone&&e.innerHTML&&!oe.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Pe.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}function C(t,n){var i,o=oe(n.createElement(t)).appendTo(n.body),r=e.getDefaultComputedStyle&&(i=e.getDefaultComputedStyle(o[0]))?i.display:oe.css(o[0],"display");return o.detach(),r}function N(e){var t=he,n=Ze[e];return n||(n=C(e,t),"none"!==n&&n||(Je=(Je||oe("