Fucks up the camera net + chunk

why
This commit is contained in:
cowbot92
2023-12-01 19:36:45 -05:00
parent 77738b5887
commit 0e421ebf5f
2 changed files with 158 additions and 104 deletions

View File

@@ -1,9 +1,14 @@
/// We only want chunk sizes that are to the power of 2. E.g: 2, 4, 8, 16, etc..
#define CHUNK_SIZE 16
/// Takes a position, transforms it into a chunk bounded position. Indexes at 1 so it'll land on actual turfs always
#define GET_CHUNK_COORD(v) (max((FLOOR(v, CHUNK_SIZE)), 1))
// 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
@@ -11,38 +16,54 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
var/name = "Camera Net"
/// The cameras on the map, no matter if they work or not. Updated in obj/machinery/camera.dm by New() and Del().
var/list/cameras = list()
var/list/obj/machinery/camera/cameras = list()
/// The chunks of the map, mapping the areas that the cameras can see.
var/list/chunks = list()
var/ready = 0
// The object used for the clickable stat() button.
var/obj/effect/statclick/statclick
///The image given to the effect in vis_contents on AI clients
var/image/obscured
/// List of images cloned by all chunk static images put onto turfs cameras cant see
/// Indexed by the plane offset to use
var/list/image/obscured_images
/datum/cameranet/New()
obscured = new('icons/effects/cameravis.dmi')
obscured.plane = CAMERA_STATIC_PLANE
obscured.appearance_flags = RESET_TRANSFORM | RESET_ALPHA | RESET_COLOR | KEEP_APART
obscured.override = TRUE
obscured_images = list()
update_offsets(SSmapping.max_plane_offset)
RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, PROC_REF(on_offset_growth))
/datum/cameranet/proc/update_offsets(new_offset)
for(var/i in length(obscured_images) to new_offset)
var/image/obscured = new('icons/effects/cameravis.dmi')
SET_PLANE_W_SCALAR(obscured, CAMERA_STATIC_PLANE, i)
obscured.appearance_flags = RESET_TRANSFORM | RESET_ALPHA | RESET_COLOR | KEEP_APART
obscured.override = TRUE
obscured_images += obscured
/datum/cameranet/proc/on_offset_growth(datum/source, old_offset, new_offset)
SIGNAL_HANDLER
update_offsets(new_offset)
/// 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 = GET_CHUNK_COORD(x)
y = GET_CHUNK_COORD(y)
if(GET_LOWEST_STACK_OFFSET(z) != 0)
var/turf/lowest = get_lowest_turf(locate(x, y, z))
return chunks["[x],[y],[lowest.z]"]
return chunks["[x],[y],[z]"]
// 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)
var/key = "[x],[y],[z]"
x = GET_CHUNK_COORD(x)
y = GET_CHUNK_COORD(y)
var/turf/lowest = get_lowest_turf(locate(x, y, z))
var/key = "[x],[y],[lowest.z]"
. = chunks[key]
if(!.)
chunks[key] = . = new /datum/camerachunk(x, y, z)
chunks[key] = . = new /datum/camerachunk(x, y, lowest.z)
/// Updates what the ai_eye can see. It is recommended you use this when the ai_eye moves or it's location is set.
/// 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(list/moved_eyes, client/C, list/other_eyes, use_static = TRUE)
if(!islist(moved_eyes))
moved_eyes = moved_eyes ? list(moved_eyes) : list()
@@ -53,21 +74,18 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
for(var/mob/camera/aiEye/eye as anything in moved_eyes)
var/list/visibleChunks = list()
//Get the eye's turf in case it's located in an object like a mecha
var/turf/eye_turf = get_turf(eye)
if(eye.loc)
var/x_value = eye.x
var/y_value = eye.y
var/z_value = eye.z
// 0xf = 15
var/static_range = eye.static_visibility_range
var/x1 = max(0, x_value - static_range) & ~(CHUNK_SIZE - 1)
var/y1 = max(0, y_value - static_range) & ~(CHUNK_SIZE - 1)
var/x2 = min(world.maxx, x_value + static_range) & ~(CHUNK_SIZE - 1)
var/y2 = min(world.maxy, y_value + static_range) & ~(CHUNK_SIZE - 1)
var/x1 = max(1, eye_turf.x - static_range)
var/y1 = max(1, eye_turf.y - static_range)
var/x2 = min(world.maxx, eye_turf.x + static_range)
var/y2 = min(world.maxy, eye_turf.y + static_range)
for(var/x = x1; x <= x2; x += CHUNK_SIZE)
for(var/y = y1; y <= y2; y += CHUNK_SIZE)
visibleChunks |= getCameraChunk(x, y, z_value)
visibleChunks |= getCameraChunk(x, y, eye_turf.z)
var/list/remove = eye.visibleCameraChunks - visibleChunks
var/list/add = visibleChunks - eye.visibleCameraChunks
@@ -78,7 +96,7 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
for(var/datum/camerachunk/chunk as anything in add)
chunk.add(eye)
/// Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open.
/// 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, opacity_check = 1)
if(!SSticker || (opacity_check && !A.opacity))
return
@@ -99,10 +117,14 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
if(c.can_use())
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)
/**
* Used for Cyborg/mecha cameras. Since portable cameras can be in ANY chunk.
* update_delay_buffer is passed all the way to hasChanged() from their camera updates on movement
* to change the time between static updates.
*/
/datum/cameranet/proc/updatePortableCamera(obj/machinery/camera/updating_camera, update_delay_buffer)
if(updating_camera.can_use())
majorChunkChange(updating_camera, 1, update_delay_buffer)
/**
* Never access this proc directly!!!!
@@ -110,37 +132,52 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
* It will also add the atom to the cameras list if you set the choice to 1.
* 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.
* update_delay_buffer is passed all the way to hasChanged() from portable camera updates on movement
* to change the time between static updates.
*/
/datum/cameranet/proc/majorChunkChange(atom/c, choice)
if(!c)
return
/datum/cameranet/proc/majorChunkChange(atom/c, choice, update_delay_buffer)
if(QDELETED(c) && choice == 1)
CRASH("Tried to add a qdeleting camera to the net")
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)
var/x1 = max(1, T.x - (CHUNK_SIZE / 2))
var/y1 = max(1, T.y - (CHUNK_SIZE / 2))
var/x2 = min(world.maxx, T.x + (CHUNK_SIZE / 2))
var/y2 = min(world.maxy, T.y + (CHUNK_SIZE / 2))
for(var/x = x1; x <= x2; x += CHUNK_SIZE)
for(var/y = y1; y <= y2; y += CHUNK_SIZE)
var/datum/camerachunk/chunk = chunkGenerated(x, y, T.z)
if(chunk)
if(choice == 0)
// Remove the camera.
chunk.cameras -= c
chunk.cameras["[T.z]"] -= c
else if(choice == 1)
// You can't have the same camera in the list twice.
chunk.cameras |= c
chunk.hasChanged()
chunk.cameras["[T.z]"] |= c
chunk.hasChanged(update_delay_buffer = update_delay_buffer)
/// A faster, turf only version of [/datum/cameranet/proc/majorChunkChange]
/// For use in sensitive code, be careful with it
/datum/cameranet/proc/bareMajorChunkChange(turf/changed)
var/x1 = max(1, changed.x - (CHUNK_SIZE / 2))
var/y1 = max(1, changed.y - (CHUNK_SIZE / 2))
var/x2 = min(world.maxx, changed.x + (CHUNK_SIZE / 2))
var/y2 = min(world.maxy, changed.y + (CHUNK_SIZE / 2))
for(var/x = x1; x <= x2; x += CHUNK_SIZE)
for(var/y = y1; y <= y2; y += CHUNK_SIZE)
var/datum/camerachunk/chunk = chunkGenerated(x, y, changed.z)
chunk?.hasChanged()
/// Will check if a mob is on a viewable turf. Returns 1 if it is, otherwise returns 0.
/datum/cameranet/proc/checkCameraVis(mob/living/target)
var/turf/position = get_turf(target)
if(!position)
return
return checkTurfVis(position)
/datum/cameranet/proc/checkTurfVis(turf/position)
var/datum/camerachunk/chunk = chunkGenerated(position.x, position.y, position.z)
var/datum/camerachunk/chunk = getCameraChunk(position.x, position.y, position.z)
if(chunk)
if(chunk.changed)
chunk.hasChanged(1) // Update now, no matter if it's visible or not.
@@ -148,22 +185,25 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
return TRUE
return FALSE
/datum/cameranet/proc/stat_entry()
if(!statclick)
statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
stat(name, statclick.update("Cameras: [GLOB.cameranet.cameras.len] | Chunks: [GLOB.cameranet.chunks.len]"))
/datum/cameranet/proc/getTurfVis(turf/position)
RETURN_TYPE(/datum/camerachunk)
var/datum/camerachunk/chunk = getCameraChunk(position.x, position.y, position.z)
if(!chunk)
return FALSE
if(chunk.changed)
chunk.hasChanged(1) // Update now, no matter if it's visible or not.
if(chunk.visibleTurfs[position])
return chunk
/obj/effect/overlay/camera_static
name = "static"
icon = null
icon_state = null
anchored = TRUE // should only appear in vis_contents, but to be safe
appearance_flags = RESET_TRANSFORM | TILE_BOUND | RESET_COLOR
appearance_flags = RESET_TRANSFORM | TILE_BOUND | LONG_GLIDE
// this combination makes the static block clicks to everything below it,
// without appearing in the right-click menu for non-AI clients
mouse_opacity = MOUSE_OPACITY_ICON
invisibility = INVISIBILITY_ABSTRACT
layer = CAMERA_STATIC_LAYER
plane = CAMERA_STATIC_PLANE

View File

@@ -1,5 +1,8 @@
#define UPDATE_BUFFER_TIME (2.5 SECONDS)
/// Returns a list of turfs in the rectangle specified by BOTTOM LEFT corner and height/width, checks for being outside the world border for you
#define CORNER_BLOCK(corner, width, height) CORNER_BLOCK_OFFSET(corner, width, height, 0, 0)
// CAMERA CHUNK
//
// A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed.
@@ -11,20 +14,21 @@
///turfs our cameras can see inside our grid
var/list/visibleTurfs = list()
///cameras that can see into our grid
///indexed by the z level of the camera
var/list/cameras = list()
///list of all turfs
///list of all turfs, associative with that turf's static image
///turf -> /image
var/list/turfs = list()
///camera mobs that can see turfs in our grid
var/list/seenby = list()
///images created to represent obscured turfs
var/list/inactive_static_images = list()
///images currently in use on obscured turfs.
var/list/active_static_images = list()
var/changed = FALSE
var/x = 0
var/y = 0
var/z = 0
var/lower_z
var/upper_z
/// Add an AI eye to the chunk, then update if changed.
/datum/camerachunk/proc/add(mob/camera/aiEye/eye)
@@ -55,28 +59,33 @@
/**
* 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.
* update_delay_buffer is used for cameras that are moving around, which are cyborg inbuilt cameras and
* mecha onboard cameras. This buffer should be usually lower than UPDATE_BUFFER_TIME because
* otherwise a moving camera can run out of its own view before updating static.
*/
/datum/camerachunk/proc/hasChanged(update_now = 0)
/datum/camerachunk/proc/hasChanged(update_now = 0, update_delay_buffer = UPDATE_BUFFER_TIME)
if(seenby.len || update_now)
addtimer(CALLBACK(src, PROC_REF(update)), UPDATE_BUFFER_TIME, TIMER_UNIQUE)
addtimer(CALLBACK(src, PROC_REF(update)), update_delay_buffer, TIMER_UNIQUE)
else
changed = TRUE
/// The actual updating. It gathers the visible turfs from cameras and puts them into the appropiate lists.
/datum/camerachunk/proc/update()
/// Accepts an optional partial_update argument, that blocks any calls out to chunks that could affect us, like above or below
/datum/camerachunk/proc/update(partial_update = FALSE)
var/list/updated_visible_turfs = list()
for(var/obj/machinery/camera/current_camera as anything in cameras)
if(!current_camera || !current_camera.can_use())
continue
for(var/z_level in lower_z to upper_z)
for(var/obj/machinery/camera/current_camera as anything in cameras["[z_level]"])
if(!current_camera || !current_camera.can_use())
continue
var/turf/point = locate(src.x + (CHUNK_SIZE / 2), src.y + (CHUNK_SIZE / 2), src.z)
if(get_dist(point, current_camera) > CHUNK_SIZE + (CHUNK_SIZE / 2))
continue
var/turf/point = locate(src.x + (CHUNK_SIZE / 2), src.y + (CHUNK_SIZE / 2), z_level)
if(get_dist(point, current_camera) > CHUNK_SIZE + (CHUNK_SIZE / 2))
continue
for(var/turf/vis_turf in current_camera.can_see())
if(turfs[vis_turf])
updated_visible_turfs[vis_turf] = vis_turf
for(var/turf/vis_turf in current_camera.can_see())
if(turfs[vis_turf])
updated_visible_turfs[vis_turf] = vis_turf
///new turfs that we couldnt see last update but can now
var/list/newly_visible_turfs = updated_visible_turfs - visibleTurfs
@@ -91,31 +100,24 @@
client.images -= active_static_images
for(var/turf/visible_turf as anything in newly_visible_turfs)
var/image/static_image_to_deallocate = obscuredTurfs[visible_turf]
if(!static_image_to_deallocate)
var/image/static_image = obscuredTurfs[visible_turf]
if(!static_image)
continue
static_image_to_deallocate.loc = null
active_static_images -= static_image_to_deallocate
inactive_static_images += static_image_to_deallocate
active_static_images -= static_image
obscuredTurfs -= visible_turf
for(var/turf/obscured_turf as anything in newly_obscured_turfs)
if(obscuredTurfs[obscured_turf] || istype(obscured_turf, /turf/open/ai_visible))
continue
var/image/static_image_to_allocate = inactive_static_images[length(inactive_static_images)]
if(!static_image_to_allocate)
stack_trace("somehow a camera chunk ran out of static images!")
var/image/static_image = turfs[obscured_turf]
if(!static_image)
stack_trace("somehow a camera chunk used a turf it didn't contain!!")
break
obscuredTurfs[obscured_turf] = static_image_to_allocate
static_image_to_allocate.loc = obscured_turf
inactive_static_images -= static_image_to_allocate
active_static_images += static_image_to_allocate
obscuredTurfs[obscured_turf] = static_image
active_static_images += static_image
visibleTurfs = updated_visible_turfs
changed = FALSE
@@ -127,39 +129,51 @@
client.images += active_static_images
/// Create a new camera chunk, since the chunks are made as they are needed.
/datum/camerachunk/New(x, y, z)
x &= ~(CHUNK_SIZE - 1)
y &= ~(CHUNK_SIZE - 1)
/datum/camerachunk/New(x, y, lower_z)
x = GET_CHUNK_COORD(x)
y = GET_CHUNK_COORD(y)
src.x = x
src.y = y
src.z = z
src.lower_z = lower_z
var/turf/upper_turf = get_highest_turf(locate(x, y, lower_z))
src.upper_z = upper_turf.z
for(var/obj/machinery/camera/camera in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z)))
if(camera.can_use())
cameras += camera
for(var/z_level in lower_z to upper_z)
var/list/local_cameras = list()
for(var/obj/machinery/camera/camera in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z_level)))
if(camera.can_use())
local_cameras += camera
for(var/turf/t as anything in block(locate(max(x, 1), max(y, 1), z), locate(min(x + CHUNK_SIZE - 1, world.maxx), min(y + CHUNK_SIZE - 1, world.maxy), z)))
turfs[t] = t
for(var/mob/living/silicon/sillycone in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z_level)))
if(sillycone.builtInCamera?.can_use())
local_cameras += sillycone.builtInCamera
for(var/turf in turfs)//one for each 16x16 = 256 turfs this camera chunk encompasses
inactive_static_images += new/image(GLOB.cameranet.obscured)
cameras["[z_level]"] = local_cameras
for(var/obj/machinery/camera/camera as anything in cameras)
if(!camera || !camera.can_use())
continue
var/image/mirror_from = GLOB.cameranet.obscured_images[GET_Z_PLANE_OFFSET(z_level)]
var/turf/chunk_corner = locate(x, y, z_level)
for(var/turf/lad as anything in CORNER_BLOCK(chunk_corner, CHUNK_SIZE, CHUNK_SIZE)) //we use CHUNK_SIZE for width and height here as it handles subtracting 1 from those two parameters by itself
var/image/our_image = new /image(mirror_from)
our_image.loc = lad
turfs[lad] = our_image
for(var/turf/vis_turf in camera.can_see())
if(turfs[vis_turf])
visibleTurfs[vis_turf] = vis_turf
for(var/obj/machinery/camera/camera as anything in local_cameras)
if(!camera)
continue
if(!camera.can_use())
continue
for(var/turf/vis_turf in camera.can_see())
if(turfs[vis_turf])
visibleTurfs[vis_turf] = vis_turf
for(var/turf/obscured_turf as anything in turfs - visibleTurfs)
var/image/new_static = inactive_static_images[inactive_static_images.len]
new_static.loc = obscured_turf
var/image/new_static = turfs[obscured_turf]
active_static_images += new_static
inactive_static_images -= new_static
obscuredTurfs[obscured_turf] = new_static
#undef UPDATE_BUFFER_TIME
#undef CHUNK_SIZE