-
-
Notifications
You must be signed in to change notification settings - Fork 444
/
Copy pathcameranet.dm
209 lines (179 loc) · 7.69 KB
/
cameranet.dm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// CAMERA NET
//
// The datum containing all the chunks.
GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
/datum/cameranet
/// Name to show for VV and stat()
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/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
/// 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
///If defined, only cameras with matching network flags will be used by chunks
///The cameras list is only used for updating chunks, not for actual vision
var/list/networks
//worst 6 hours of my life i spent trying to figure out how to best split a cameranet, this is what i've settled on
/datum/cameranet/darkspawn
networks = list(ROLE_DARKSPAWN)
/datum/cameranet/New()
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 = 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 = 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, lowest.z, src)
/// 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()
if(islist(other_eyes))
other_eyes = (other_eyes - moved_eyes)
else
other_eyes = list()
for(var/mob/camera/ai_eye/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/static_range = eye.static_visibility_range
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, eye_turf.z)
var/list/remove = eye.visibleCameraChunks - visibleChunks
var/list/add = visibleChunks - eye.visibleCameraChunks
for(var/datum/camerachunk/chunk as anything in remove)
chunk.remove(eye, FALSE)
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.
/datum/cameranet/proc/updateVisibility(atom/A, opacity_check = 1)
if(!SSticker || (opacity_check && !A.opacity))
return
majorChunkChange(A, 2)
/datum/cameranet/proc/updateChunk(x, y, z)
var/datum/camerachunk/chunk = chunkGenerated(x, y, z)
if (!chunk)
return
chunk.hasChanged()
/// Removes a camera from a chunk.
/datum/cameranet/proc/removeCamera(obj/machinery/camera/c)
majorChunkChange(c, 0)
/// Add a camera to a chunk.
/datum/cameranet/proc/addCamera(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!!!!
* 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.
* 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, 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(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["[T.z]"] -= c
else if(choice == 1)
// You can't have the same camera in the list twice.
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 = 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.
if(chunk.visibleTurfs[position])
return TRUE
return FALSE
/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 | 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
plane = CAMERA_STATIC_PLANE