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..cf1c1ea8092 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,25 @@ 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) + 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 +86,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 +112,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 +197,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 +220,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 +230,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 +238,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 +276,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 +329,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 +337,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 +353,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/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 05cf2458f2d..1e4bf4348ff 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("") @@ -1911,3 +1911,15 @@ else 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 ..() \ 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..6306cc4b646 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,30 @@ 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) + +/mob/aiEye/experience_pressure_difference() + return 0 + +// 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 +80,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/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/html/changelogs/Tigercat2000-tgcams.yml b/html/changelogs/Tigercat2000-tgcams.yml new file mode 100644 index 00000000000..77192547bc4 --- /dev/null +++ b/html/changelogs/Tigercat2000-tgcams.yml @@ -0,0 +1,41 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. Remove the quotation mark and put in your name when copy+pasting the example changelog. +author: Tigercat2000 + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit. +# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog. +changes: + - bugfix: "Fixed a bug where the AI could not delete photos it takes." + - rscadd: "Ported -tg- cameranets. This means that all of our camera based systems are more or less up to date with -tg-; 510 features are missing." + - rscadd: "An AI with the 'camera lights' mode on will now, instead of the button toggling the closest camera's light, toggle camera lights on/off as the AI moves." + - rscadd: "It now takes a minimum of 30 deciseconds between attempting to track someone, and locking on. This number is increased by their distance away from the AI eye." + - tweak: "The camera activate/deactivate proc is now called 'toggle_cam'." + - tweak: "The 'trackable' proc has been replaced by a more object-oriented 'mob.can_track()' proc." + - tweak: "The 'networks' section of the security camera console is now above the camera list." \ No newline at end of file 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("