diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index acc8cd6ffbe..259bcf07500 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -504,6 +504,11 @@
///from base power_change() when power is restored
#define COMSIG_MACHINERY_POWER_RESTORED "machinery_power_restored"
+// /obj/machinery/camera signals
+#define COMSIG_CAMERA_ON "camera_on"
+#define COMSIG_CAMERA_OFF "camera_off"
+#define COMSIG_CAMERA_MOVED "camera_moved"
+
// /obj/item signals
///from base of obj/item/attack(): (/mob/living/target, /mob/living/user)
diff --git a/code/__DEFINES/misc_defines.dm b/code/__DEFINES/misc_defines.dm
index 8f1dc6098f5..fd6abbb1f44 100644
--- a/code/__DEFINES/misc_defines.dm
+++ b/code/__DEFINES/misc_defines.dm
@@ -153,6 +153,8 @@
#define MIDNIGHT_ROLLOVER 864000 //number of deciseconds in a day
+#define CAMERA_VIEW_DISTANCE 7
+#define CAMERA_CHUNK_SIZE 16 // Only chunk sizes that are to the power of 2. E.g: 2, 4, 8, 16, etc..
//Turf wet states
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 56b05d60411..8b2ceed2ef8 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -97,6 +97,7 @@
#define FIRE_PRIORITY_GARBAGE 15
#define FIRE_PRIORITY_AIR 20
#define FIRE_PRIORITY_NPC 20
+#define FIRE_PRIORITY_CAMERA 20
#define FIRE_PRIORITY_PROCESS 25
#define FIRE_PRIORITY_THROWING 25
#define FIRE_PRIORITY_SPACEDRIFT 30
diff --git a/code/controllers/subsystem/camera.dm b/code/controllers/subsystem/camera.dm
new file mode 100644
index 00000000000..5369c9b9c37
--- /dev/null
+++ b/code/controllers/subsystem/camera.dm
@@ -0,0 +1,24 @@
+// Subsystem that controls the camera vision tiles
+SUBSYSTEM_DEF(camera)
+ name = "Camera"
+ flags = SS_BACKGROUND | SS_NO_INIT
+ priority = FIRE_PRIORITY_CAMERA
+ runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+ wait = 2.5 SECONDS
+ offline_implications = "AI camera view won't update. No immediate action is needed."
+ var/list/chunk_queue = list()
+
+/datum/controller/subsystem/camera/fire(resumed)
+ for(var/datum/camerachunk/chunk as anything in chunk_queue)
+ chunk.update()
+ chunk_queue -= chunk
+ if(MC_TICK_CHECK)
+ return
+
+
+/datum/controller/subsystem/camera/proc/queue(datum/camerachunk/chunk)
+ if(!chunk_queue[chunk])
+ chunk_queue[chunk] = chunk
+
+/datum/controller/subsystem/camera/proc/remove_from_queue(datum/camerachunk/chunk)
+ chunk_queue -= chunk
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index 52d13d45ca0..2953fbf5133 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -14,6 +14,7 @@
max_integrity = 100
integrity_failure = 50
var/list/network = list("SS13")
+ var/list/previous_network
var/c_tag = null
var/c_tag_order = 999
var/status = TRUE
@@ -21,10 +22,12 @@
var/start_active = FALSE //If it ignores the random chance to start broken on round start
var/invuln = null
var/obj/item/camera_assembly/assembly = null
+ /// If this camera should be added to the camera network and update the camera network when it moves around
+ var/part_of_camera_network
//OTHER
- var/view_range = 7
+ var/view_range = CAMERA_VIEW_DISTANCE
var/short_range = 2
var/alarm_on = FALSE
@@ -34,7 +37,7 @@
var/toggle_sound = 'sound/items/wirecutter.ogg'
blocks_emissive = EMISSIVE_BLOCK_GENERIC
-/obj/machinery/camera/Initialize(mapload)
+/obj/machinery/camera/Initialize(mapload, should_add_to_cameranet = TRUE)
. = ..()
wires = new(src)
assembly = new(src)
@@ -43,22 +46,27 @@
assembly.update_icon()
GLOB.cameranet.cameras += src
- GLOB.cameranet.addCamera(src)
+ part_of_camera_network = should_add_to_cameranet
+ if(part_of_camera_network)
+ GLOB.cameranet.addCamera(src)
if(isturf(loc))
LAZYADD(get_area(src).cameras, UID())
if(is_station_level(z) && prob(3) && !start_active)
- toggle_cam(null, FALSE)
+ turn_off(null, FALSE)
wires.cut_all()
/obj/machinery/camera/proc/set_area_motion(area/A)
area_motion = A
+/obj/machinery/camera/Moved(atom/OldLoc, Dir, Forced)
+ . = ..()
+ SEND_SIGNAL(src, COMSIG_CAMERA_MOVED, OldLoc)
+
/obj/machinery/camera/Destroy()
SStgui.close_uis(wires)
- toggle_cam(null, FALSE) //kick anyone viewing out
+ kick_out_watchers()
QDEL_NULL(assembly)
QDEL_NULL(wires)
- GLOB.cameranet.removeCamera(src) //Will handle removal from the camera network and the chunks, so we don't need to worry about that
GLOB.cameranet.cameras -= src
if(isarea(get_area(src)))
LAZYREMOVE(get_area(src).cameras, UID())
@@ -85,36 +93,26 @@
return
if(!isEmpProof())
if(prob(150/severity))
- update_icon(UPDATE_ICON_STATE)
- var/list/previous_network = network
- network = list()
- GLOB.cameranet.removeCamera(src)
- stat |= EMPED
- set_light(0)
- emped = emped+1 //Increase the number of consecutive EMP's
- update_icon(UPDATE_ICON_STATE)
- var/thisemp = emped //Take note of which EMP this proc is for
- spawn(900)
- if(!QDELETED(src))
- if(emped == thisemp) //Only fix it if the camera hasn't been EMP'd again
- network = previous_network
- stat &= ~EMPED
- update_icon(UPDATE_ICON_STATE)
- if(can_use())
- GLOB.cameranet.addCamera(src)
- emped = 0 //Resets the consecutive EMP count
- for(var/mob/M in GLOB.player_list)
- if(M.client && M.client.eye == src)
- M.reset_perspective(null)
- to_chat(M, "The screen bursts into static.")
+ if(!(stat & EMPED))
+ previous_network = network
+ network = list()
+ stat |= EMPED
+ turn_off(null, FALSE, TRUE)
+ addtimer(CALLBACK(src, PROC_REF(reactivate_after_emp)), 90 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE)
..()
+/obj/machinery/camera/proc/reactivate_after_emp()
+ network = previous_network
+ previous_network = null
+ stat &= ~EMPED
+ turn_on(null, FALSE, TRUE)
+
/obj/machinery/camera/ex_act(severity)
if(invuln)
return
..()
-/obj/machinery/camera/proc/setViewRange(num = 7)
+/obj/machinery/camera/proc/setViewRange(num = CAMERA_VIEW_DISTANCE)
view_range = num
GLOB.cameranet.updateVisibility(src, 0)
@@ -261,34 +259,56 @@
else
icon_state = "[initial(icon_state)]"
-/obj/machinery/camera/proc/toggle_cam(mob/user, displaymessage = TRUE)
- status = !status
- if(can_use())
- GLOB.cameranet.addCamera(src)
- if(isturf(loc))
- LAZYADD(get_area(src).cameras, UID())
- else
- set_light(0)
- GLOB.cameranet.removeCamera(src)
- if(isarea(get_area(src)))
- LAZYREMOVE(get_area(src).cameras, UID())
- GLOB.cameranet.updateChunk(x, y, z)
- var/change_msg = "deactivates"
+/obj/machinery/camera/proc/toggle_cam(mob/user, display_message = TRUE)
if(status)
- change_msg = "reactivates"
- if(displaymessage)
+ turn_off(user, display_message)
+ return
+
+ turn_on(user, display_message)
+
+/obj/machinery/camera/proc/turn_on(mob/user, display_message = TRUE, emp_recover = FALSE)
+ if(status && !emp_recover)
+ return
+ status = TRUE
+ if(!emp_recover && isturf(loc))
+ LAZYADD(get_area(src).cameras, UID())
+
+ if(display_message)
if(user)
- visible_message("[user] [change_msg] [src]!")
+ visible_message("[user] reactivates [src]!")
add_hiddenprint(user)
else
- visible_message("\The [src] [change_msg]!")
+ visible_message("\The [src] reactivates!")
+ playsound(loc, toggle_sound, 100, TRUE)
+ update_icon(UPDATE_ICON_STATE)
+ SEND_SIGNAL(src, COMSIG_CAMERA_ON, user, display_message)
+/obj/machinery/camera/proc/turn_off(mob/user, display_message = TRUE, emped = FALSE)
+ if(!status && !emped)
+ return
+
+ if(!emped)
+ status = FALSE
+ if(isarea(get_area(src)))
+ LAZYREMOVE(get_area(src).cameras, UID())
+
+ set_light(0)
+
+ if(display_message)
+ if(user)
+ visible_message("[user] deactivates [src]!")
+ add_hiddenprint(user)
+ else
+ visible_message("\The [src] deactivates!")
playsound(loc, toggle_sound, 100, 1)
+
update_icon(UPDATE_ICON_STATE)
- // now disconnect anyone using the camera
- //Apparently, this will disconnect anyone even if the camera was re-activated.
- //I guess that doesn't matter since they can't use it anyway?
+ kick_out_watchers()
+
+ SEND_SIGNAL(src, COMSIG_CAMERA_OFF, user, display_message, emped)
+
+/obj/machinery/camera/proc/kick_out_watchers()
for(var/mob/O in GLOB.player_list)
if(O.client && O.client.eye == src)
O.reset_perspective(null)
@@ -395,6 +415,12 @@
assembly.update_icon()
/obj/machinery/camera/portable/process() //Updates whenever the camera is moved.
- if(GLOB.cameranet && get_turf(src) != prev_turf)
- GLOB.cameranet.updatePortableCamera(src)
- prev_turf = get_turf(src)
+ if(!part_of_camera_network)
+ return PROCESS_KILL // Stop wasting performance
+
+ if(get_turf(src) == prev_turf)
+ return
+
+ SEND_SIGNAL(src, COMSIG_CAMERA_MOVED, prev_turf)
+ GLOB.cameranet.updatePortableCamera(src, prev_turf)
+ prev_turf = get_turf(src)
diff --git a/code/game/machinery/camera/camera_assembly.dm b/code/game/machinery/camera/camera_assembly.dm
index a6b3faff944..64e88550121 100644
--- a/code/game/machinery/camera/camera_assembly.dm
+++ b/code/game/machinery/camera/camera_assembly.dm
@@ -96,16 +96,15 @@
var/temptag = "[sanitize(camera_area.name)] ([rand(1, 999)])"
input = strip_html(input(usr, "How would you like to name the camera?", "Set Camera Name", temptag))
state = ASSEMBLY_BUILT
- var/obj/machinery/camera/C = new(loc)
+ var/list/network_list = uniquelist(tempnetwork)
+ var/list/visible_networks = difflist(network_list, GLOB.restricted_camera_networks)
+ var/obj/machinery/camera/C = new(loc, length(visible_networks) > 0)
loc = C
C.assembly = src
C.auto_turn()
- C.network = uniquelist(tempnetwork)
- tempnetwork = difflist(C.network,GLOB.restricted_camera_networks)
- if(!tempnetwork.len) // Camera isn't on any open network - remove its chunk from AI visibility.
- GLOB.cameranet.removeCamera(C)
+ C.network = network_list
C.c_tag = input
diff --git a/code/modules/clothing/spacesuits/ert_hardsuits.dm b/code/modules/clothing/spacesuits/ert_hardsuits.dm
index d40357dbb62..15ecefa978c 100644
--- a/code/modules/clothing/spacesuits/ert_hardsuits.dm
+++ b/code/modules/clothing/spacesuits/ert_hardsuits.dm
@@ -37,9 +37,8 @@
/obj/item/clothing/head/helmet/space/hardsuit/ert/proc/register_camera(mob/wearer)
if(camera || !has_camera)
return
- camera = new /obj/machinery/camera/portable(src)
+ camera = new /obj/machinery/camera/portable(src, FALSE)
camera.network = list("ERT")
- GLOB.cameranet.removeCamera(camera)
camera.c_tag = wearer.name
to_chat(wearer, "User scanned as [camera.c_tag]. Camera activated.")
diff --git a/code/modules/mob/living/silicon/ai/ai_mob.dm b/code/modules/mob/living/silicon/ai/ai_mob.dm
index a1c531f25b4..581742834ec 100644
--- a/code/modules/mob/living/silicon/ai/ai_mob.dm
+++ b/code/modules/mob/living/silicon/ai/ai_mob.dm
@@ -1222,7 +1222,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, 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)
+ for(var/obj/machinery/camera/C in CC.active_cameras)
if(!C.can_use() || get_dist(C, eyeobj) > 7)
continue
visible |= C
diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm
index e1056df48d2..9e5341f93ee 100644
--- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm
@@ -1,9 +1,6 @@
// CAMERA NET
//
// The datum containing all the chunks.
-
-#define CHUNK_SIZE 16 // Only chunk sizes that are to the power of 2. E.g: 2, 4, 8, 16, etc..
-
GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new())
/datum/cameranet
@@ -20,16 +17,16 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new())
// Checks if a chunk has been Generated in x, y, z.
/datum/cameranet/proc/chunkGenerated(x, y, z)
- x &= ~(CHUNK_SIZE - 1)
- y &= ~(CHUNK_SIZE - 1)
+ x &= ~(CAMERA_CHUNK_SIZE - 1)
+ y &= ~(CAMERA_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 &= ~(CHUNK_SIZE - 1)
- y &= ~(CHUNK_SIZE - 1)
+ x &= ~(CAMERA_CHUNK_SIZE - 1)
+ y &= ~(CAMERA_CHUNK_SIZE - 1)
var/key = "[x],[y],[z]"
if(!chunks[key])
chunks[key] = new /datum/camerachunk(null, x, y, z)
@@ -55,15 +52,15 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new())
chunks_pre_seen |= eye.visibleCameraChunks
// 0xf = 15
var/static_range = eye.static_visibility_range
- var/x1 = max(0, eye.x - static_range) & ~(CHUNK_SIZE - 1)
- var/y1 = max(0, eye.y - static_range) & ~(CHUNK_SIZE - 1)
- var/x2 = min(world.maxx, eye.x + static_range) & ~(CHUNK_SIZE - 1)
- var/y2 = min(world.maxy, eye.y + static_range) & ~(CHUNK_SIZE - 1)
+ var/x1 = max(0, eye.x - static_range) & ~(CAMERA_CHUNK_SIZE - 1)
+ var/y1 = max(0, eye.y - static_range) & ~(CAMERA_CHUNK_SIZE - 1)
+ var/x2 = min(world.maxx, eye.x + static_range) & ~(CAMERA_CHUNK_SIZE - 1)
+ var/y2 = min(world.maxy, eye.y + static_range) & ~(CAMERA_CHUNK_SIZE - 1)
var/list/visibleChunks = list()
- for(var/x = x1; x <= x2; x += CHUNK_SIZE)
- for(var/y = y1; y <= y2; y += CHUNK_SIZE)
+ for(var/x = x1; x <= x2; x += CAMERA_CHUNK_SIZE)
+ for(var/y = y1; y <= y2; y += CAMERA_CHUNK_SIZE)
visibleChunks |= getCameraChunk(x, y, eye.z)
var/list/remove = eye.visibleCameraChunks - visibleChunks
@@ -115,22 +112,17 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new())
// Removes a camera from a chunk.
/datum/cameranet/proc/removeCamera(obj/machinery/camera/c)
- if(c.can_use())
- majorChunkChange(c, 0)
+ majorChunkChange(c, 0)
// Add a camera to a chunk.
/datum/cameranet/proc/addCamera(obj/machinery/camera/c)
- if(c.can_use())
- majorChunkChange(c, 1)
+ majorChunkChange(c, 1)
// Used for Cyborg cameras. Since portable cameras can be in ANY chunk.
-/datum/cameranet/proc/updatePortableCamera(obj/machinery/camera/c)
- if(c.can_use())
- majorChunkChange(c, 1)
- //else
- // majorChunkChange(c, 0)
+/datum/cameranet/proc/updatePortableCamera(obj/machinery/camera/c, turf/old_loc)
+ majorChunkChange(c, 1, old_loc)
// Never access this proc directly!!!!
// This will update the chunk and all the surrounding chunks.
@@ -138,31 +130,37 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new())
// Setting the choice to 0 will remove the camera from the chunks.
// If you want to update the chunks around an object, without adding/removing a camera, use choice 2.
-/datum/cameranet/proc/majorChunkChange(atom/c, choice)
+/datum/cameranet/proc/majorChunkChange(atom/c, choice, turf/old_loc = null)
// 0xf = 15
if(!c)
return
var/turf/T = get_turf(c)
- if(T)
- 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)
+ if(!T)
+ return
-// to_chat(world, "X1: [x1] - Y1: [y1] - X2: [x2] - Y2: [y2]")
+ if(old_loc)
+ // Check if the current turf falls in the same chunka as the old_loc. If so, don't do anything
+ if(T.x & ~(CAMERA_CHUNK_SIZE - 1) == old_loc.x & ~(CAMERA_CHUNK_SIZE - 1) && T.y & ~(CAMERA_CHUNK_SIZE - 1) == old_loc.y & ~(CAMERA_CHUNK_SIZE - 1))
+ return
- 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)
- // Remove the camera.
- chunk.cameras -= c
- else if(choice == 1)
- // You can't have the same camera in the list twice.
- chunk.cameras |= c
- chunk.hasChanged()
+ // Use camera view distance here to actually know how far a camera can max watch
+ var/x1 = max(0, T.x - CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
+ var/y1 = max(0, T.y - CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
+ var/x2 = min(world.maxx, T.x + CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
+ var/y2 = min(world.maxy, T.y + CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
+
+ for(var/x = x1; x <= x2; x += CAMERA_CHUNK_SIZE)
+ for(var/y = y1; y <= y2; y += CAMERA_CHUNK_SIZE)
+ if(chunkGenerated(x, y, T.z))
+ var/datum/camerachunk/chunk = getCameraChunk(x, y, T.z)
+ if(choice == 0)
+ // Remove the camera.
+ chunk.remove_camera(c)
+ else if(choice == 1)
+ // You can't have the same camera in the list twice.
+ chunk.add_camera(c)
+ chunk.hasChanged()
// Will check if a mob is on a viewable turf. Returns 1 if it is, otherwise returns 0.
diff --git a/code/modules/mob/living/silicon/ai/freelook/chunk.dm b/code/modules/mob/living/silicon/ai/freelook/chunk.dm
index a72ddf20067..651f09b718d 100644
--- a/code/modules/mob/living/silicon/ai/freelook/chunk.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/chunk.dm
@@ -1,5 +1,3 @@
-#define UPDATE_BUFFER 25 // 2.5 seconds
-
// CAMERA CHUNK
//
// A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed.
@@ -9,31 +7,68 @@
var/list/obscuredTurfs = list()
var/list/visibleTurfs = list()
var/list/obscured = list()
- var/list/cameras = list()
+ var/list/active_cameras = list()
+ var/list/inactive_cameras = list()
var/list/turfs = list()
var/list/seenby = list()
- var/visible = FALSE
var/changed = FALSE
var/updating = FALSE
var/x = 0
var/y = 0
var/z = 0
-// Add an AI eye to the chunk, then update if changed.
+/datum/camerachunk/proc/add_camera(obj/machinery/camera/cam)
+ if(active_cameras[cam] || inactive_cameras[cam])
+ return
+ // Register all even though it is active/inactive. Won't get called incorrectly
+ RegisterSignal(cam, COMSIG_CAMERA_OFF, PROC_REF(deactivate_camera))
+ RegisterSignal(cam, COMSIG_CAMERA_ON, PROC_REF(activate_camera))
+ RegisterSignal(cam, COMSIG_PARENT_QDELETING, PROC_REF(remove_camera))
+ RegisterSignal(cam, COMSIG_CAMERA_MOVED, PROC_REF(camera_moved))
+ if(cam.can_use())
+ active_cameras[cam] = cam
+ else
+ inactive_cameras[cam] = cam
+/datum/camerachunk/proc/camera_moved(obj/machinery/camera/cam, atom/old_loc)
+ var/turf/T = get_turf(cam)
+
+ // Falls outside of the chunk view distance
+ if(T.x + CAMERA_VIEW_DISTANCE < x || T.x - CAMERA_VIEW_DISTANCE >= x + CAMERA_CHUNK_SIZE || T.y + CAMERA_VIEW_DISTANCE < y || T.y - CAMERA_VIEW_DISTANCE >= y + CAMERA_CHUNK_SIZE || T.z != z)
+ remove_camera(cam)
+
+/datum/camerachunk/proc/remove_camera(obj/machinery/camera/cam)
+ UnregisterSignal(cam, list(COMSIG_CAMERA_OFF, COMSIG_CAMERA_ON, COMSIG_PARENT_QDELETING, COMSIG_CAMERA_MOVED))
+ active_cameras -= cam
+ inactive_cameras -= cam
+ SScamera.queue(src)
+
+/datum/camerachunk/proc/activate_camera(obj/machinery/camera/cam)
+ inactive_cameras -= cam
+ active_cameras += cam
+ SScamera.queue(src)
+
+/datum/camerachunk/proc/deactivate_camera(obj/machinery/camera/cam)
+ inactive_cameras += cam
+ active_cameras -= cam
+ SScamera.queue(src)
+
+// Add an AI eye to the chunk, then update if changed.
/datum/camerachunk/proc/add(mob/camera/aiEye/eye, add_images = TRUE)
if(add_images)
var/client/client = eye.GetViewerClient()
if(client)
client.images += obscured
eye.visibleCameraChunks += src
- visible++
seenby += eye
+ RegisterSignal(eye, COMSIG_PARENT_QDELETING, PROC_REF(aiEye_destroyed))
if(changed)
- update()
+ SScamera.queue(src)
+
+/datum/camerachunk/proc/aiEye_destroyed(mob/camera/aiEye/eye)
+ remove(eye, FALSE)
// Remove an AI eye from the chunk, then update if changed.
-
/datum/camerachunk/proc/remove(mob/camera/aiEye/eye, remove_images = TRUE)
if(remove_images)
var/client/client = eye.GetViewerClient()
@@ -41,8 +76,7 @@
client.images -= obscured
eye.visibleCameraChunks -= src
seenby -= eye
- if(visible > 0)
- visible--
+ UnregisterSignal(eye, COMSIG_PARENT_QDELETING)
// Called when a chunk has changed. I.E: A wall was deleted.
@@ -55,12 +89,12 @@
// instead be flagged to update the next time an AI Eye moves near it.
/datum/camerachunk/proc/hasChanged(update_now = 0)
- if(visible || update_now)
- if(!updating)
- updating = TRUE
- spawn(UPDATE_BUFFER) // Batch large changes, such as many doors opening or closing at once
- update()
- updating = FALSE
+ if(update_now)
+ update()
+ SScamera.remove_from_queue(src)
+
+ if(length(seenby))
+ SScamera.queue(src)
else
changed = TRUE
@@ -69,106 +103,69 @@
/datum/camerachunk/proc/update()
var/list/newVisibleTurfs = list()
- for(var/camera in cameras)
- var/obj/machinery/camera/c = camera
-
- if(!c)
- continue
-
- if(!c.can_use())
- continue
-
- 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/obj/machinery/camera/c as anything in active_cameras)
+ var/turf/point = locate(src.x + (CAMERA_CHUNK_SIZE / 2), src.y + (CAMERA_CHUNK_SIZE / 2), src.z)
+ var/turf/T = get_turf(c)
+ if(get_dist(point, T) > CAMERA_VIEW_DISTANCE + (CAMERA_CHUNK_SIZE / 2))
+ continue // Still needed for Ais who get created on Z level 1 on the spot of the new player
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.
- newVisibleTurfs &= turfs
+ if(turfs[t])
+ newVisibleTurfs[t] = t
var/list/visAdded = newVisibleTurfs - visibleTurfs
var/list/visRemoved = visibleTurfs - newVisibleTurfs
visibleTurfs = newVisibleTurfs
obscuredTurfs = turfs - newVisibleTurfs
-
- for(var/turf in visAdded)
- var/turf/t = turf
+ var/list/images_to_remove = list()
+ var/list/images_to_add = list()
+ for(var/turf/t as anything in visAdded)
if(t.obscured)
obscured -= t.obscured
- for(var/eye in seenby)
- var/mob/camera/aiEye/m = eye
- if(!m)
- continue
- var/client/client = m.GetViewerClient()
- if(client)
- client.images -= t.obscured
+ images_to_remove += t.obscured
- for(var/turf in visRemoved)
- var/turf/t = turf
- if(obscuredTurfs[t])
- if(!t.obscured)
- t.obscured = image('icons/effects/cameravis.dmi', t, null, BYOND_LIGHTING_LAYER + 0.1)
- t.obscured.plane = BYOND_LIGHTING_PLANE + 1
+ for(var/turf/t as anything in visRemoved)
+ if(!t.obscured)
+ t.obscured = image('icons/effects/cameravis.dmi', t, null, BYOND_LIGHTING_LAYER + 0.1)
+ t.obscured.plane = BYOND_LIGHTING_PLANE + 1
+ obscured += t.obscured
+ images_to_add += t.obscured
- obscured += t.obscured
- for(var/eye in seenby)
- var/mob/camera/aiEye/m = eye
- if(!m)
- seenby -= m
- continue
- var/client/client = m.GetViewerClient()
- if(client)
- client.images += t.obscured
+ for(var/mob/camera/aiEye/eye as anything in seenby)
+ var/client/client = eye.GetViewerClient()
+ if(client)
+ client.images -= images_to_remove
+ client.images += images_to_add
+ changed = FALSE
// Create a new camera chunk, since the chunks are made as they are needed.
-
/datum/camerachunk/New(loc, x, y, z)
// 0xf = 15
- x &= ~(CHUNK_SIZE - 1)
- y &= ~(CHUNK_SIZE - 1)
+ x &= ~(CAMERA_CHUNK_SIZE - 1)
+ y &= ~(CAMERA_CHUNK_SIZE - 1)
src.x = x
src.y = y
src.z = 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
+ var/half_chunk = CAMERA_CHUNK_SIZE / 2
+ for(var/obj/machinery/camera/c in urange(half_chunk + CAMERA_VIEW_DISTANCE, locate(x + half_chunk, y + half_chunk, z)))
+ add_camera(c)
- for(var/turf/t in block(locate(max(x, 1), max(y, 1), max(z, 1)), locate(min(x + CHUNK_SIZE - 1, world.maxx), min(y + CHUNK_SIZE - 1, world.maxy), z)))
+ for(var/turf/t in block(locate(max(x, 1), max(y, 1), max(z, 1)), locate(min(x + CAMERA_CHUNK_SIZE - 1, world.maxx), min(y + CAMERA_CHUNK_SIZE - 1, world.maxy), z)))
turfs[t] = t
- for(var/camera in cameras)
- var/obj/machinery/camera/c = camera
- if(!c)
- continue
-
- if(!c.can_use())
- continue
-
+ for(var/obj/machinery/camera/c as anything in active_cameras)
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.
- visibleTurfs &= turfs
+ if(turfs[t])
+ visibleTurfs[t] = t
obscuredTurfs = turfs - visibleTurfs
- for(var/turf in obscuredTurfs)
- var/turf/t = turf
+ for(var/turf/t as anything in obscuredTurfs)
if(!t.obscured)
t.obscured = image('icons/effects/cameravis.dmi', t, "black", BYOND_LIGHTING_LAYER + 0.1)
t.obscured.plane = BYOND_LIGHTING_PLANE + 1
obscured += t.obscured
-
-#undef UPDATE_BUFFER
diff --git a/code/modules/mob/living/silicon/robot/robot_mob.dm b/code/modules/mob/living/silicon/robot/robot_mob.dm
index 268fac6e099..4072ced048d 100644
--- a/code/modules/mob/living/silicon/robot/robot_mob.dm
+++ b/code/modules/mob/living/silicon/robot/robot_mob.dm
@@ -99,7 +99,8 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
var/lamp_intensity = 0 //Luminosity of the headlamp. 0 is off. Higher settings than the minimum require power.
var/lamp_recharging = FALSE //Flag for if the lamp is on cooldown after being forcibly disabled.
- var/updating = FALSE //portable camera camerachunk update
+ /// When the camera moved signal was send last. Avoid overdoing it
+ var/last_camera_update
hud_possible = list(SPECIALROLE_HUD, DIAG_STAT_HUD, DIAG_HUD, DIAG_BATT_HUD)
@@ -1225,19 +1226,16 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
cell = null
qdel(src)
-#define BORG_CAMERA_BUFFER 3 SECONDS
+#define CAMERA_UPDATE_COOLDOWN 2.5 SECONDS
-/mob/living/silicon/robot/Move(atom/newloc, direct, movetime)
- var/oldLoc = loc
+/mob/living/silicon/robot/Moved(atom/OldLoc, Dir, Forced)
. = ..()
- if(. && !updating && camera)
- updating = TRUE
- spawn(BORG_CAMERA_BUFFER)
- if(camera && oldLoc != loc)
- GLOB.cameranet.updatePortableCamera(camera)
- updating = FALSE
+ if(camera && last_camera_update + CAMERA_UPDATE_COOLDOWN < world.time)
+ last_camera_update = world.time
+ GLOB.cameranet.updatePortableCamera(camera, OldLoc)
+ SEND_SIGNAL(camera, COMSIG_CAMERA_MOVED, OldLoc)
-#undef BORG_CAMERA_BUFFER
+#undef CAMERA_UPDATE_COOLDOWN
/mob/living/silicon/robot/proc/self_destruct()
if(emagged)
diff --git a/paradise.dme b/paradise.dme
index 3646c398a74..c4344b48316 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -240,6 +240,7 @@
#include "code\controllers\subsystem\air.dm"
#include "code\controllers\subsystem\ambience.dm"
#include "code\controllers\subsystem\blackbox.dm"
+#include "code\controllers\subsystem\camera.dm"
#include "code\controllers\subsystem\chat_pings.dm"
#include "code\controllers\subsystem\cleanup.dm"
#include "code\controllers\subsystem\dbcore.dm"