Ports BS12 fixes / refactor of visualnets. (#7572)

Ports (and adjusts) Baystation12/Baystation12#12521 et al. Essentially brings our visualnet code up to the standard of three years ago, rather than five.

Side effects of merging may include but are not limited to loss of AI vision, helmet camera failure, garbage collection implosion, infinite turf loops, and unintended layer shenanigans.

High-level changes:

    Overall, visualnet/chunk procs have been renamed and refactored to be clear as to their function.
    Fix some layering issues related to magic numbers, these have been set to constants.
    Visualnets now track source movement rather than overriding individual mob/obj procs.
    Adds obfuscation underlay to complicate memory fudging to remove camera static.
    Cameras now only add themselves to the cameranet if they're on open (non-restricted) networks.
    Helmet cameras now use cameras themselves rather than network defines.
    Adds a sorted object list insertion helper.
    The Camera MIU (presently unused) should now function properly.
    Unused cultnet / cult mask deleted because refactoring that would likely kill the few brain cells I have left
This commit is contained in:
JohnWildkins
2019-12-07 18:37:40 +02:00
committed by Erki
parent abe1153bec
commit a03f170f16
53 changed files with 634 additions and 620 deletions
@@ -3,42 +3,37 @@
// The datum containing all the chunks.
/datum/visualnet/camera
// 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 cameras on the map, no matter if they work or not.
var/list/cameras
chunk_type = /datum/chunk/camera
valid_source_types = list(/obj/machinery/camera, /mob/living/silicon/ai)
/datum/visualnet/camera/proc/process_sort()
if(cameras_unsorted)
sortTim(cameras, /proc/cmp_camera, FALSE)
cameras_unsorted = 0
/datum/visualnet/camera/New()
cameras = list()
..()
// Removes a camera from a chunk.
/datum/visualnet/camera/Destroy()
cameras.Cut()
. = ..()
/datum/visualnet/camera/proc/removeCamera(obj/machinery/camera/c)
if(c.can_use())
majorChunkChange(c, 0)
/datum/visualnet/camera/add_source(obj/machinery/camera/c)
if(istype(c))
if(c in cameras)
return FALSE
. = ..(c, c.can_use())
if(.)
dd_binaryInsertSorted(cameras, c)
else if(isAI(c))
var/mob/living/silicon/AI = c
return ..(AI, AI.stat != DEAD)
else
..()
// Add a camera to a chunk.
/datum/visualnet/camera/proc/addCamera(obj/machinery/camera/c)
if(c.can_use())
majorChunkChange(c, 1)
// Used for Cyborg cameras. Since portable cameras can be in ANY chunk.
/datum/visualnet/camera/proc/updatePortableCamera(obj/machinery/camera/c)
if(c.can_use())
majorChunkChange(c, 1)
//else
// majorChunkChange(c, 0)
/datum/visualnet/camera/onMajorChunkChange(atom/c, var/choice, var/datum/chunk/camera/chunk)
// Only add actual cameras to the list of cameras
if(istype(c, /obj/machinery/camera))
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
/datum/visualnet/camera/remove_source(obj/machinery/camera/c)
if(istype(c) && cameras.Remove(c))
. = ..(c, c.can_use())
if(isAI(c))
var/mob/living/silicon/AI = c
return ..(AI, AI.stat != DEAD)
else
..()
+18 -47
View File
@@ -3,50 +3,21 @@
// A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed.
// Allows the Eye to stream these chunks and know what it can and cannot see.
/datum/chunk/camera
var/list/cameras = list()
/datum/chunk/camera/acquireVisibleTurfs(var/list/visible)
var/turf/point = locate(src.x + 8, src.y + 8, src.z)
for(var/camera in cameras)
var/obj/machinery/camera/c = camera
if(!istype(c))
cameras -= c
continue
if(!c.can_use())
continue
if(get_dist(point, c) > 24)
cameras -= c
continue
for(var/turf/t in c.can_see())
visible[t] = t
for(var/mob/living/silicon/ai/AI in living_mob_list)
if (get_dist(point, AI) > world.view)
continue
for(var/turf/t in AI.seen_camera_turfs())
visible[t] = t
// Create a new camera chunk, since the chunks are made as they are needed.
/datum/chunk/camera/New(loc, x, y, z)
for(var/obj/machinery/camera/c in range(16, locate(x + 8, y + 8, z)))
if(c.can_use())
cameras += c
..()
/mob/living/silicon/proc/provides_camera_vision()
return 0
/mob/living/silicon/ai/provides_camera_vision()
return stat != DEAD
/mob/living/silicon/robot/provides_camera_vision()
return src.camera && src.camera.network.len
/mob/living/silicon/ai/proc/seen_camera_turfs()
return seen_turfs_in_range(src, world.view)
/datum/chunk/camera/acquire_visible_turfs(var/list/visible)
for(var/source in sources)
if(istype(source, /obj/machinery/camera))
var/obj/machinery/camera/c = source
if(!c.can_use())
continue
for(var/turf/t in c.can_see())
visible[t] = t
else if(isAI(source))
var/mob/living/silicon/ai/AI = source
if (AI.stat == DEAD)
continue
for(var/turf/t in seen_turfs_in_range(AI, world.view))
visible[t] = t
else
log_visualnet("Contained an unhandled source", source)
sources -= source
+18 -20
View File
@@ -3,16 +3,27 @@
// A 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/abstract/eye/cameranet
// Generic version of the AI eye without the AI-specific handling, for things like the Camera MIU mask.
name = "Inactive Camera Eye"
name_suffix = "Camera Eye"
/mob/abstract/eye/cameranet/Initialize()
. = ..()
visualnet = cameranet
/mob/abstract/eye/aiEye
name = "Inactive AI Eye"
name_suffix = "AI Eye"
icon_state = "AI-eye"
/mob/abstract/eye/aiEye/New()
..()
/mob/abstract/eye/aiEye/Initialize()
. = ..()
visualnet = cameranet
/mob/abstract/eye/aiEye/setLoc(var/T, var/cancel_tracking = 1)
if(..())
. = ..()
if(. && isAI(owner))
var/mob/living/silicon/ai/ai = owner
if(cancel_tracking)
ai.ai_cancel_tracking()
@@ -33,7 +44,6 @@
if(!eyeobj) return
if(!new_eye)
new_eye = src
eyeobj.owner = null
qdel(eyeobj) // No AI, no Eye
eyeobj = null
if(client)
@@ -41,26 +51,18 @@
/mob/living/silicon/ai/proc/create_eyeobj(var/newloc)
if(eyeobj) destroy_eyeobj()
if(!newloc) newloc = src.loc
if(!newloc) newloc = get_turf(src)
eyeobj = new /mob/abstract/eye/aiEye(newloc)
eyeobj.owner = src
eyeobj.name = "[src.name] (AI Eye)" // Give it a name
if(client) client.eye = eyeobj
SetName(src.name)
eyeobj.possess(src)
// Intiliaze the eye by assigning it's "ai" variable to us. Then set it's loc to us.
/mob/living/silicon/ai/Initialize()
. = ..()
create_eyeobj()
addtimer(CALLBACK(src, .proc/init_move_eyeobj), 5)
/mob/living/silicon/ai/proc/init_move_eyeobj()
if (eyeobj)
eyeobj.forceMove(loc)
/mob/living/silicon/ai/Destroy()
destroy_eyeobj()
return ..()
. = ..()
/atom/proc/move_camera_by_click()
if(istype(usr, /mob/living/silicon/ai))
@@ -82,11 +84,7 @@
if(!src.eyeobj)
return
if(client && client.eye)
client.eye = src
for(var/datum/chunk/c in eyeobj.visibleChunks)
c.remove(eyeobj)
src.eyeobj.setLoc(src)
eyeobj.possess(src)
/mob/living/silicon/ai/proc/toggle_acceleration()
set category = "AI Commands"
@@ -1,39 +1,3 @@
#define BORG_CAMERA_BUFFER 30
// ROBOT MOVEMENT
// Update the portable camera everytime the Robot moves.
// This might be laggy, comment it out if there are problems.
/mob/living/silicon/var/updating = 0
/mob/living/silicon/robot/Move()
var/oldLoc = src.loc
. = ..()
if (.)
if(provides_camera_vision())
if(!updating)
updating = 1
addtimer(CALLBACK(src, .proc/camera_post_move, oldLoc), BORG_CAMERA_BUFFER)
/mob/living/silicon/robot/proc/camera_post_move(oldLoc)
if (oldLoc != loc)
cameranet.updatePortableCamera(camera)
updating = 0
/mob/living/silicon/ai/Move()
var/oldLoc = src.loc
. = ..()
if (.)
if(provides_camera_vision())
addtimer(CALLBACK(src, .proc/camera_post_move, oldLoc), BORG_CAMERA_BUFFER, TIMER_UNIQUE)
/mob/living/silicon/ai/proc/camera_post_move(oldLoc)
if(oldLoc != src.loc)
cameranet.updateVisibility(oldLoc, 0)
cameranet.updateVisibility(loc, 0)
#undef BORG_CAMERA_BUFFER
// CAMERA
// An addition to deactivate which removes/adds the camera from the chunk list based on if it works or not.
@@ -41,33 +5,55 @@
/obj/machinery/camera/deactivate(user as mob, var/choice = 1)
..(user, choice)
invalidateCameraCache()
if(src.can_use())
cameranet.addCamera(src)
else
src.set_light(0)
cameranet.removeCamera(src)
if(!can_use())
set_light(0)
cameranet.update_visibility(src)
/obj/machinery/camera/Initialize()
. = ..()
//Camera must be added to global list of all cameras no matter what...
cameranet.cameras += src
cameranet.cameras_unsorted = TRUE
update_coverage(1)
var/list/open_networks = difflist(network, restricted_camera_networks)
on_open_network = open_networks.len
if(on_open_network)
cameranet.add_source(src)
/obj/machinery/camera/Destroy()
clear_all_networks()
cameranet.cameras -= src
return ..()
if(on_open_network)
cameranet.remove_source(src)
. = ..()
/obj/machinery/camera/proc/update_coverage(var/network_change = 0)
if(network_change)
var/list/open_networks = difflist(network, restricted_camera_networks)
// Add or remove camera from the camera net as necessary
if(on_open_network && !open_networks.len)
on_open_network = FALSE
cameranet.remove_source(src)
else if(!on_open_network && open_networks.len)
on_open_network = TRUE
cameranet.add_source(src)
else
cameranet.update_visibility(src)
invalidateCameraCache()
// Mobs
/mob/living/silicon/ai/Initialize()
. = ..()
cameranet.add_source(src)
/mob/living/silicon/ai/Destroy()
cameranet.remove_source(src)
. = ..()
/mob/living/silicon/ai/rejuvenate()
var/was_dead = stat == DEAD
..()
if(was_dead && stat != DEAD)
// Arise!
cameranet.updateVisibility(src, 0)
cameranet.update_visibility(src, FALSE)
/mob/living/silicon/ai/death(gibbed)
if(..())
. = ..()
if(.)
// If true, the mob went from living to dead (assuming everyone has been overriding as they should...)
cameranet.updateVisibility(src, 0)
cameranet.update_visibility(src, FALSE)
+144 -108
View File
@@ -8,120 +8,51 @@
/datum/obfuscation
var/icon = 'icons/effects/cameravis.dmi'
var/icon_state = "black"
var/list/obfuscation_images = list()
var/static/icon/obfuscation_underlay
// There is an exploit were clients can memory-edit their local version of the static images, allowing them to see everything. This is a minor attempt to make that more difficult.
/datum/obfuscation/Destroy()
obfuscation_images.Cut()
. = ..()
/datum/obfuscation/proc/has_obfuscation(var/turf/T)
return !isnull(obfuscation_images[T])
/datum/obfuscation/proc/get_obfuscation(var/turf/T)
var/image/obfuscation = obfuscation_images[T]
if(!obfuscation)
obfuscation = image(icon, T, icon_state)
obfuscation.layer = OBFUSCATION_LAYER
if(!obfuscation_underlay)
// Creating a new icon of a fairly common icon state, adding some random color to prevent address searching, and hoping being static kills memory locality
var/turf/floor = /turf/simulated/floor/tiled
obfuscation_underlay = icon(initial(floor.icon), initial(floor.icon_state))
obfuscation_underlay.Blend(rgb(rand(0,255),rand(0,255),rand(0,255)))
obfuscation.underlays += obfuscation_underlay
obfuscation_images[T] = obfuscation
return obfuscation
/datum/chunk
var/datum/visualnet/visualnet
var/list/obscuredTurfs = list()
var/list/visibleTurfs = list()
var/list/obscured = list()
var/list/turfs = list()
var/list/seenby = list()
var/visible = 0
var/changed = 0
var/updating = 0
var/list/sources = list()
var/dirty = FALSE
var/updating = FALSE
var/x = 0
var/y = 0
var/z = 0
var/datum/obfuscation/obfuscation = new()
// Add an eye to the chunk, then update if changed.
/datum/chunk/proc/add(mob/abstract/eye/eye)
if(!eye.owner)
return
eye.visibleChunks += src
if(eye.owner.client)
eye.owner.client.images += obscured
visible++
seenby += eye
if(changed && !updating)
update()
// Remove an eye from the chunk, then update if changed.
/datum/chunk/proc/remove(mob/abstract/eye/eye)
if(!eye.owner)
return
eye.visibleChunks -= src
if(eye.owner.client)
eye.owner.client.images -= obscured
seenby -= eye
if(visible > 0)
visible--
// Called when a chunk has changed. I.E: A wall was deleted.
/datum/chunk/proc/visibilityChanged(turf/loc)
if(!visibleTurfs[loc])
return
hasChanged()
// 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/chunk/proc/hasChanged(var/update_now = 0)
if(visible || update_now)
if(!updating)
updating = 1
addtimer(CALLBACK(src, .proc/doUpdate), UPDATE_BUFFER)
else
changed = 1
/datum/chunk/proc/doUpdate()
update()
updating = 0
// The actual updating.
/datum/chunk/proc/update()
set waitfor = FALSE
var/list/newVisibleTurfs = new()
acquireVisibleTurfs(newVisibleTurfs)
// Removes turf that isn't in turfs.
newVisibleTurfs &= turfs
var/list/visAdded = newVisibleTurfs - visibleTurfs
var/list/visRemoved = visibleTurfs - newVisibleTurfs
visibleTurfs = newVisibleTurfs
obscuredTurfs = turfs - newVisibleTurfs
for(var/turf in visAdded)
var/turf/t = turf
if(LAZYACCESS(t.obfuscations, obfuscation.type))
obscured -= t.obfuscations[obfuscation.type]
for(var/eye in seenby)
var/mob/abstract/eye/m = eye
if(!m || !m.owner)
continue
if(m.owner.client)
m.owner.client.images -= t.obfuscations[obfuscation.type]
for(var/turf in visRemoved)
var/turf/t = turf
if(obscuredTurfs[t])
if(!LAZYACCESS(t.obfuscations, obfuscation.type))
LAZYINITLIST(t.obfuscations)
var/image/obfuscation_static = image(obfuscation.icon, t, obfuscation.icon_state, OBFUSCATION_LAYER)
obfuscation_static.plane = 0
t.obfuscations[obfuscation.type] = obfuscation_static
obscured += LAZYACCESS(t.obfuscations, obfuscation.type)
for(var/eye in seenby)
var/mob/abstract/eye/m = eye
if(!m || !m.owner)
seenby -= m
continue
if(m.owner.client)
m.owner.client.images += LAZYACCESS(t.obfuscations, obfuscation.type)
/datum/chunk/proc/acquireVisibleTurfs(var/list/visible)
// Create a new camera chunk, since the chunks are made as they are needed.
/datum/chunk/New(loc, x, y, z)
/datum/chunk/New(var/datum/visualnet/visualnet, x, y, z)
..()
src.visualnet = visualnet
// 0xf = 15
x &= ~0xf
y &= ~0xf
@@ -136,7 +67,8 @@
if(t.x >= x && t.y >= y && t.x < x + 16 && t.y < y + 16)
turfs[t] = t
acquireVisibleTurfs(visibleTurfs)
add_sources(visualnet.sources)
acquire_visible_turfs(visibleTurfs)
// Removes turf that isn't in turfs.
visibleTurfs &= turfs
@@ -145,12 +77,116 @@
for(var/turf in obscuredTurfs)
var/turf/t = turf
if(!LAZYACCESS(t.obfuscations, obfuscation.type))
LAZYINITLIST(t.obfuscations)
var/image/obfuscation_static = image(obfuscation.icon, t, obfuscation.icon_state, OBFUSCATION_LAYER)
obfuscation_static.plane = 0
t.obfuscations[obfuscation.type] = obfuscation_static
obscured += obfuscation.get_obfuscation(t)
obscured += LAZYACCESS(t.obfuscations, obfuscation.type)
#undef UPDATE_BUFFER
/datum/chunk/Destroy()
visualnet = null
. = ..()
/datum/chunk/proc/add_sources(var/list/sources)
var/turf/center = locate(x + 8, y + 8, z)
for(var/entry in sources)
var/atom/A = entry
if(get_dist(get_turf(A), center) > 16)
continue
add_source(A)
// The visualnet checks if a source already exists or not, as appropriate, before calling add/remove_source on the chunk
/datum/chunk/proc/add_source(var/atom/source)
if(source in sources)
return FALSE
sources += source
visibility_changed()
return TRUE
/datum/chunk/proc/remove_source(var/atom/source)
if(sources.Remove(source))
visibility_changed()
return TRUE
return FALSE
// Visualnet adds and removes eyes.
/datum/chunk/proc/add_eye(mob/abstract/eye/eye)
seenby += eye
eye.visibleChunks += src
if(eye.owner && eye.owner.client)
eye.owner.client.images += obscured
/datum/chunk/proc/remove_eye(mob/abstract/eye/eye)
seenby -= eye
eye.visibleChunks -= src
if(eye.owner && eye.owner.client)
eye.owner.client.images -= obscured
// 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/chunk/proc/visibility_changed(var/update_now = FALSE)
if(update_now)
update(TRUE)
return
if(updating)
return
if(seenby.len)
updating = TRUE
addtimer(CALLBACK(src, .proc/update), UPDATE_BUFFER)
else
dirty = TRUE
// The actual updating.
/datum/chunk/proc/update(var/forced = FALSE)
if(!updating && !forced)
return
var/list/newVisibleTurfs = new()
acquire_visible_turfs(newVisibleTurfs)
// Removes turf that isn't in turfs.
newVisibleTurfs &= turfs
var/list/visAdded = newVisibleTurfs - visibleTurfs
var/list/visRemoved = visibleTurfs - newVisibleTurfs
visibleTurfs = newVisibleTurfs
obscuredTurfs = turfs - newVisibleTurfs
for(var/turf in visAdded)
var/turf/t = turf
if(obfuscation.has_obfuscation(t))
var/image/obfuscation_image = obfuscation.get_obfuscation(t)
obscured -= obfuscation_image
for(var/eye in seenby)
var/mob/abstract/eye/m = eye
if (m && m.owner && m.owner.client)
m.owner.client.images -= obfuscation_image
for(var/turf in visRemoved)
var/turf/t = turf
if(obscuredTurfs[t])
var/image/obfuscation_image = obfuscation.get_obfuscation(t)
obscured += obfuscation_image
for(var/eye in seenby)
var/mob/abstract/eye/m = eye
if (m && m.owner && m.owner.client)
m.owner.client.images += obfuscation_image
dirty = FALSE
updating = FALSE
/datum/chunk/proc/acquire_visible_turfs(var/list/visible)
return
/proc/seen_turfs_in_range(var/source, var/range)
var/turf/pos = get_turf(source)
if(pos)
. = hear(range, pos)
else
. = list()
#undef UPDATE_BUFFER
+45 -14
View File
@@ -5,6 +5,7 @@
/mob/abstract/eye
name = "Eye"
var/name_suffix = "Eye"
icon = 'icons/mob/eye.dmi'
icon_state = "default-eye"
alpha = 127
@@ -39,6 +40,10 @@
qdel(ghostimage)
ghostimage = null
updateallghostimages()
release(owner)
owner = null
visualnet = null
return ..()
/mob/abstract/eye/Move(n, direct)
@@ -61,32 +66,58 @@
return 0
/mob/abstract/eye/examine(mob/user)
return
/mob/abstract/eye/proc/possess(var/mob/user)
if(owner && owner != user)
return
if(owner && owner.eyeobj != src)
return
owner = user
owner.eyeobj = src
name = "[owner.name] ([name_suffix])"
if(owner.client)
owner.client.eye = src
setLoc(owner)
visualnet.update_eye_chunks(src, TRUE)
/mob/abstract/eye/proc/release(var/mob/user)
if(owner != user || !user)
return
if(owner.eyeobj != src)
return
visualnet.remove_eye(src)
owner.eyeobj = null
owner = null
name = initial(name)
// Use this when setting the eye's location.
// It will also stream the chunk that the new loc is in.
/mob/abstract/eye/proc/setLoc(var/T)
if(owner)
T = get_turf(T)
if(T != loc)
forceMove(T)
if(!owner)
return FALSE
T = get_turf(T)
if(!T || T == loc)
return FALSE
if(owner.client)
owner.client.eye = src
forceMove(T)
if(owner_follows_eye)
visualnet.updateVisibility(owner, 0)
owner.forceMove(loc)
visualnet.updateVisibility(owner, 0)
visualnet.visibility(src)
return 1
return 0
if(owner.client)
owner.client.eye = src
if(owner_follows_eye)
owner.forceMove(loc)
visualnet.update_eye_chunks(src)
return TRUE
/mob/abstract/eye/proc/getLoc()
if(owner)
if(!isturf(owner.loc) || !owner.client)
return
return loc
/mob
var/mob/abstract/eye/eyeobj
@@ -0,0 +1,5 @@
/datum/chunk/proc/log_visualnet(var/message, var/datum/source)
visualnet.log_visualnet("[src] ([x]-[y]-[z]) - [message]", source)
/datum/visualnet/proc/log_visualnet(var/message, var/datum/source)
log_debug("[src] - [message]: [istype(source) ? "[source] ([source.type])" : (source ? source : "NULL") ]")
@@ -1,36 +0,0 @@
// CULT CHUNK
//
// A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed.
// Allows the Eye to stream these chunks and know what it can and cannot see.
/datum/obfuscation/cult
icon_state = "white"
/datum/chunk/cult
obfuscation = new /datum/obfuscation/cult()
/datum/chunk/cult/acquireVisibleTurfs(var/list/visible)
for(var/mob/living/L in living_mob_list)
for(var/turf/t in L.seen_cult_turfs())
visible[t] = t
/mob/living/proc/seen_cult_turfs()
return seen_turfs_in_range(src, 3)
/mob/living/carbon/human/seen_cult_turfs()
if(mind in cult.current_antagonists)
return seen_turfs_in_range(src, world.view)
return ..()
/mob/living/silicon/seen_cult_turfs()
return list()
/mob/living/simple_animal/seen_cult_turfs()
return seen_turfs_in_range(src, 1)
/mob/living/simple_animal/shade/narsie/seen_cult_turfs()
return view(2, src)
/proc/seen_turfs_in_range(var/source, var/range)
var/turf/pos = get_turf(source)
return hear(range, pos)
@@ -1,15 +0,0 @@
// CULT NET
//
// The datum containing all the chunks.
/datum/visualnet/cult
chunk_type = /datum/chunk/cult
/datum/visualnet/cult/proc/provides_vision(var/mob/living/L)
return L.provides_cult_vision()
/mob/living/proc/provides_cult_vision()
return 1
/mob/living/silicon/provides_cult_vision()
return 0
@@ -1,13 +0,0 @@
// MASK EYE
//
// A mob that a cultists controls to look around the station with.
// It streams chunks as it moves around, which will show it what the cultist can and cannot see.
/mob/abstract/eye/maskEye
name = "Eye of Nar-Sie"
acceleration = 0
owner_follows_eye = 1
/mob/abstract/eye/maskEye/New()
..()
visualnet = cultnet
@@ -1,51 +0,0 @@
//UPDATE TRIGGERS, when the chunk (and the surrounding chunks) should update.
#define CULT_UPDATE_BUFFER 30
/mob/living/var/updating_cult_vision = 0
/mob/living/Move()
var/oldLoc = src.loc
. = ..()
if(.)
if(cultnet.provides_vision(src))
if(!updating_cult_vision)
updating_cult_vision = 1
spawn(CULT_UPDATE_BUFFER)
if(oldLoc != src.loc)
cultnet.updateVisibility(oldLoc, 0)
cultnet.updateVisibility(loc, 0)
updating_cult_vision = 0
#undef CULT_UPDATE_BUFFER
/mob/living/New()
..()
cultnet.updateVisibility(src, 0)
/mob/living/Destroy()
cultnet.updateVisibility(src, 0)
return ..()
/mob/living/rejuvenate()
var/was_dead = stat == DEAD
..()
if(was_dead && stat != DEAD)
// Arise!
cultnet.updateVisibility(src, 0)
/mob/living/death(gibbed, deathmessage="seizes up and falls limp...")
. = ..()
if(.)
// If true, the mob went from living to dead (assuming everyone has been overriding as they should...)
cultnet.updateVisibility(src)
/datum/antagonist/add_antagonist(var/datum/mind/player)
. = ..()
if(src == cult)
cultnet.updateVisibility(player.current, 0)
/datum/antagonist/remove_antagonist(var/datum/mind/player, var/show_message, var/implanted)
..()
if(src == cult)
cultnet.updateVisibility(player.current, 0)
@@ -2,35 +2,29 @@
// TURFS
/proc/updateVisibility(atom/A, var/opacity_check = 1)
for(var/datum/visualnet/VN in visual_nets)
VN.updateVisibility(A, opacity_check)
/turf
var/list/image/obfuscations
/proc/updateVisibility(atom/A, var/opacity_check = TRUE)
if(SSticker)
for(var/datum/visualnet/VN in visual_nets)
VN.update_visibility(A, opacity_check)
/turf/drain_power()
return -1
/turf/simulated/Destroy()
updateVisibility(src)
return ..()
// STRUCTURES
/obj/structure/Destroy()
updateVisibility(src)
return ..()
// EFFECTS
/obj/effect/Destroy()
updateVisibility(src)
return ..()
/obj/effect/Initialize()
/atom/Destroy()
if(opacity)
updateVisibility(src)
. = ..()
updateVisibility(src)
/atom/movable/Move()
. = ..()
if(opacity && .)
updateVisibility(src)
/atom/movable/forceMove()
. = ..()
if(opacity && .)
updateVisibility(src)
// DOORS
@@ -40,4 +34,9 @@
// Glass door glass = 1
// don't check then?
if(!glass)
updateVisibility(src, 0)
updateVisibility(src, FALSE)
/turf/ChangeTurf()
. = ..()
if(.)
updateVisibility(src, FALSE)
+138 -82
View File
@@ -5,125 +5,181 @@
/datum/visualnet
// The chunks of the map, mapping the areas that an object can see.
var/list/chunks = list()
var/ready = 0
var/list/sources = list()
var/chunk_type = /datum/chunk
var/list/valid_source_types
/datum/visualnet/New()
..()
visual_nets += src
if(!valid_source_types)
valid_source_types = list()
/datum/visualnet/Destroy()
visual_nets -= src
for(var/source in sources)
remove_source(source, FALSE)
sources.Cut()
for(var/chunk in chunks)
var/chunk_datum = chunks[chunk]
qdel(chunk_datum)
chunks.Cut()
return ..()
// Checks if a chunk has been Generated in x, y, z.
/datum/visualnet/proc/chunkGenerated(x, y, z)
/datum/visualnet/proc/is_chunk_generated(x, y, z)
x &= ~0xf
y &= ~0xf
var/key = "[x],[y],[z]"
return (chunks[key])
return !isnull(chunks[key])
// Returns the chunk in the x, y, z.
// If there is no chunk, it creates a new chunk and returns that.
/datum/visualnet/proc/getChunk(x, y, z)
/datum/visualnet/proc/get_chunk(x, y, z)
x &= ~0xf
y &= ~0xf
var/key = "[x],[y],[z]"
if(!chunks[key])
chunks[key] = new chunk_type(null, x, y, z)
chunks[key] = new chunk_type(src, x, y, z)
return chunks[key]
// Updates what the aiEye can see. It is recommended you use this when the aiEye moves or it's location is set.
// Updates what the eye can see. It is recommended you use this when the eye moves or it's location is set.
/datum/visualnet/proc/visibility(mob/abstract/eye/eye)
// 0xf = 15
var/x1 = max(0, eye.x - 16) & ~0xf
var/y1 = max(0, eye.y - 16) & ~0xf
var/x2 = min(world.maxx, eye.x + 16) & ~0xf
var/y2 = min(world.maxy, eye.y + 16) & ~0xf
var/list/visibleChunks = list()
for(var/x = x1; x <= x2; x += 16)
for(var/y = y1; y <= y2; y += 16)
visibleChunks += getChunk(x, y, eye.z)
var/list/remove = eye.visibleChunks - visibleChunks
var/list/add = visibleChunks - eye.visibleChunks
for(var/chunk in remove)
var/datum/chunk/c = chunk
c.remove(eye)
for(var/chunk in add)
var/datum/chunk/c = chunk
c.add(eye)
// Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open.
/datum/visualnet/proc/updateVisibility(atom/A, var/opacity_check = 1)
if((opacity_check && !A.opacity))
return
majorChunkChange(A, 2)
/datum/visualnet/proc/updateChunk(x, y, z)
// 0xf = 15
if(!chunkGenerated(x, y, z))
return
var/datum/chunk/chunk = getChunk(x, y, z)
chunk.hasChanged()
// Never access this proc directly!!!!
// This will update the chunk and all the surrounding chunks.
// 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.
/datum/visualnet/proc/majorChunkChange(atom/c, var/choice)
// 0xf = 15
if(!c)
return
var/turf/T = get_turf(c)
/datum/visualnet/proc/update_eye_chunks(mob/abstract/eye/eye, var/full_update = FALSE)
. = list()
var/turf/T = get_turf(eye)
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
// 0xf = 15
var/x1 = max(0, T.x - 16) & ~0xf
var/y1 = max(0, T.y - 16) & ~0xf
var/x2 = min(world.maxx, T.x + 16) & ~0xf
var/y2 = min(world.maxy, T.y + 16) & ~0xf
for(var/x = x1; x <= x2; x += 16)
for(var/y = y1; y <= y2; y += 16)
if(chunkGenerated(x, y, T.z))
var/datum/chunk/chunk = getChunk(x, y, T.z)
onMajorChunkChange(c, choice, chunk)
chunk.hasChanged()
. += get_chunk(x, y, T.z)
/datum/visualnet/proc/onMajorChunkChange(atom/c, var/choice, var/datum/chunk/chunk)
if(full_update)
eye.visibleChunks.Cut()
// Will check if a mob is on a viewable turf. Returns 1 if it is, otherwise returns 0.
var/list/remove = eye.visibleChunks - .
var/list/add = . - eye.visibleChunks
/datum/visualnet/proc/checkVis(mob/living/target as mob)
// 0xf = 15
for(var/chunk in remove)
var/datum/chunk/c = chunk
c.remove_eye(eye)
for(var/chunk in add)
var/datum/chunk/c = chunk
c.add_eye(eye)
/datum/visualnet/proc/remove_eye(mob/abstract/eye/eye)
for(var/chunk in eye.visibleChunks)
var/datum/chunk/c = chunk
c.remove_eye(eye)
// Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open.
/datum/visualnet/proc/update_visibility(atom/A, var/opacity_check = TRUE)
if((opacity_check && !A.opacity))
return
major_chunk_change(A)
/datum/visualnet/proc/update_visibility_nocheck(atom/A)
update_visibility(A, FALSE)
// Will check if an atom is on a viewable turf. Returns TRUE if it is, otherwise FALSE.
/datum/visualnet/proc/is_visible(var/atom/target)
var/turf/position = get_turf(target)
return checkTurfVis(position)
return position && is_turf_visible(position)
/datum/visualnet/proc/checkTurfVis(var/turf/position)
var/datum/chunk/chunk = getChunk(position.x, position.y, position.z)
/datum/visualnet/proc/is_turf_visible(var/turf/position)
if(!position)
return FALSE
var/datum/chunk/chunk = get_chunk(position.x, position.y, position.z)
if(chunk)
if(chunk.changed)
chunk.hasChanged(1) // Update now, no matter if it's visible or not.
if(chunk.visibleTurfs[position])
return 1
return 0
if(chunk.dirty)
chunk.update(TRUE) // Update if it's visible or not
if(position in chunk.visibleTurfs)
return TRUE
return FALSE
// Never access this proc directly!!!!
// This will update the chunk and all the surrounding chunks.
/datum/visualnet/proc/major_chunk_change(var/atom/source)
for_all_chunks_in_range(source, /datum/chunk/proc/visibility_changed, list())
/datum/visualnet/proc/add_source(var/atom/source, var/update_visibility = TRUE, var/opacity_check = FALSE)
if(!(source && is_type_in_list(source, valid_source_types)))
log_visualnet("Was given an unhandled source", source)
return FALSE
if(source in sources)
return FALSE
sources += source
moved_event.register(source, src, /datum/visualnet/proc/source_moved)
destroyed_event.register(source, src, /datum/visualnet/proc/remove_source)
for_all_chunks_in_range(source, /datum/chunk/proc/add_source, list(source))
if(update_visibility)
update_visibility(source, opacity_check)
return TRUE
/datum/visualnet/proc/remove_source(var/atom/source, var/update_visibility = TRUE, var/opacity_check = FALSE)
if(!sources.Remove(source))
return FALSE
moved_event.unregister(source, src)
destroyed_event.unregister(source, src)
for_all_chunks_in_range(source, /datum/chunk/proc/remove_source, list(source))
if(update_visibility)
update_visibility(source, opacity_check)
return TRUE
/datum/visualnet/proc/source_moved(var/atom/movable/source, var/old_loc, var/new_loc)
var/turf/old_turf = get_turf(old_loc)
var/turf/new_turf = get_turf(new_loc)
if(old_turf == new_turf)
return
// A more proper way would be to figure out which chunks have gone out of range, and which have come into range
// and only remove/add to those.
if(old_turf)
for_all_chunks_in_range(source, /datum/chunk/proc/remove_source, list(source), old_turf)
if(new_turf)
for_all_chunks_in_range(source, /datum/chunk/proc/add_source, list(source), new_turf)
/datum/visualnet/proc/for_all_chunks_in_range(var/atom/source, var/proc_call, var/list/proc_args, var/turf/T)
T = T ? T : get_turf(source)
if(!T)
return
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
for(var/x = x1; x <= x2; x += 16)
for(var/y = y1; y <= y2; y += 16)
if(is_chunk_generated(x, y, T.z))
var/datum/chunk/c = get_chunk(x, y, T.z)
call(c, proc_call)(arglist(proc_args))
// Debug verb for VVing the chunk that the turf is in.
/*
/turf/verb/view_chunk()
/turf/proc/view_chunk()
set name = "View Chunk"
set category = "Debug"
set src in world
if(cameranet.chunkGenerated(x, y, z))
var/datum/chunk/chunk = cameranet.getCameraChunk(x, y, z)
if(cameranet.is_chunk_generated(x, y, z))
var/datum/chunk/chunk = cameranet.get_chunk(x, y, z)
usr.client.debug_variables(chunk)
*/
/turf/proc/update_chunk()
set name = "Update Chunk"
set category = "Debug"
set src in world
if(cameranet.is_chunk_generated(x, y, z))
var/datum/chunk/chunk = cameranet.get_chunk(x, y, z)
chunk.visibility_changed(TRUE)
@@ -234,7 +234,7 @@ This saves us from having to call add_fingerprint() any time something is put in
var/obj/item/clothing/ears/offear/O = new(W)
O.forceMove(src)
src.r_ear = O
O.layer = 20
O.layer = SCREEN_LAYER+0.01
W.equipped(src, slot)
update_inv_ears(redraw_mob)
if(slot_r_ear)
@@ -243,7 +243,7 @@ This saves us from having to call add_fingerprint() any time something is put in
var/obj/item/clothing/ears/offear/O = new(W)
O.forceMove(src)
src.l_ear = O
O.layer = 20
O.layer = SCREEN_LAYER+0.01
W.equipped(src, slot)
update_inv_ears(redraw_mob)
if(slot_glasses)
@@ -311,7 +311,7 @@ This saves us from having to call add_fingerprint() any time something is put in
src.r_hand = null
update_inv_r_hand()
W.layer = 20
W.layer = SCREEN_LAYER+0.01
if(W.action_button_name)
update_action_buttons()
+1 -1
View File
@@ -173,7 +173,7 @@
reset_view(null)
/mob/living/proc/update_sight()
if(stat == DEAD)
if(stat == DEAD || eyeobj)
update_dead_sight()
else
sight &= ~(SEE_TURFS|SEE_MOBS|SEE_OBJS)
+1
View File
@@ -216,6 +216,7 @@ var/list/ai_verbs_default = list(
job = "AI"
setup_icon()
eyeobj.possess(src)
/mob/living/silicon/ai/Destroy()
qdel(aiPDA)
@@ -20,5 +20,3 @@
for(var/obj/machinery/ai_status_display/O in SSmachinery.all_status_displays) //change status
O.mode = 1
O.emotion = "Neutral"
src.view_core()
return
@@ -228,21 +228,21 @@
return
if(!module_state_1)
module_state_1 = O
O.layer = 20
O.layer = SCREEN_LAYER
O.screen_loc = inv1.screen_loc
contents += O
if(istype(module_state_1,/obj/item/borg/sight))
sight_mode |= module_state_1:sight_mode
else if(!module_state_2)
module_state_2 = O
O.layer = 20
O.layer = SCREEN_LAYER
O.screen_loc = inv2.screen_loc
contents += O
if(istype(module_state_2,/obj/item/borg/sight))
sight_mode |= module_state_2:sight_mode
else if(!module_state_3)
module_state_3 = O
O.layer = 20
O.layer = SCREEN_LAYER
O.screen_loc = inv3.screen_loc
contents += O
if(istype(module_state_3,/obj/item/borg/sight))
@@ -127,7 +127,7 @@
robot_modules_background = new()
robot_modules_background.icon_state = "block"
robot_modules_background.layer = 19 //Objects that appear on screen are on layer 20, UI should be just below it.
robot_modules_background.layer = SCREEN_LAYER //Objects that appear on screen are on layer 20, UI should be just below it.
ident = rand(1, 999)
module_sprites["Basic"] = "robot"
icontype = "Basic"
@@ -917,19 +917,19 @@
return 1
if(!module_state_1)
module_state_1 = O
O.layer = 20
O.layer = SCREEN_LAYER
contents += O
if(istype(module_state_1,/obj/item/borg/sight))
sight_mode |= module_state_1:sight_mode
else if(!module_state_2)
module_state_2 = O
O.layer = 20
O.layer = SCREEN_LAYER
contents += O
if(istype(module_state_2,/obj/item/borg/sight))
sight_mode |= module_state_2:sight_mode
else if(!module_state_3)
module_state_3 = O
O.layer = 20
O.layer = SCREEN_LAYER
contents += O
if(istype(module_state_3,/obj/item/borg/sight))
sight_mode |= module_state_3:sight_mode
+3
View File
@@ -51,6 +51,9 @@
client.eye = src
client.perspective = MOB_PERSPECTIVE
if(eyeobj)
eyeobj.possess(src)
//set macro to normal incase it was overriden (like cyborg currently does)
winset(src, null, "mainwindow.macro=macro hotkey_toggle.is-checked=false input.focus=true input.background-color=#D3B5B5")
MOB_STOP_THINKING(src)