Port -tg-station cameranets

This should make it much easier to port abductors, if we ever want to do
that.

Changes:
  - Fixed a bug where the AI could not delete photos it takes.
  - 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.
  - 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.
  - 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.
  - The camera activate/deactivate proc is now called 'toggle_cam'.
  - The 'trackable' proc has been replaced by a more object-oriented 'mob.can_track()' proc.
  - The 'networks' section of the security camera console is now above the camera list.

In laymans terms: This refactors how security cameras calculate what
viewers can see, and adds a few neat features on top.
This commit is contained in:
Tigercat2000
2016-03-13 15:06:24 -07:00
parent db8b472fc7
commit bc78146178
25 changed files with 562 additions and 455 deletions
+6 -1
View File
@@ -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
+19
View File
@@ -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)
+3 -4
View File
@@ -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"
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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."
@@ -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)
@@ -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 << "<span class='warning'>Disrupting the power grid would bring no benefit to us. Aborting.</span>"
+81 -70
View File
@@ -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("<span class='warning'>\The [user] slashes at [src]!</span>")
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 = "<span class='notice'>You attach [W] into the assembly inner circuits.</span>"
var/msg2 = "<span class='notice'>The camera already has that upgrade!</span>"
@@ -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 << "<b>[U]</b> holds <a href='?_src_=usr;show_paper=1;'>\a [itemname]</a> up to one of your cameras ..."
else AI << "<b><a href='byond://?src=\ref[O];track=\ref[U]'>[U]</a></b> holds <a href='?_src_=usr;show_paper=1;'>\a [itemname]</a> up to one of your cameras ..."
if(AI.control_disabled || (AI.stat == DEAD))
return
if(U.name == "Unknown")
AI << "<b>[U]</b> holds <a href='?_src_=usr;show_paper=1;'>\a [itemname]</a> up to one of your cameras ..."
else
AI << "<b><a href='?src=\ref[AI];track=[html_encode(U.name)]'>[U]</a></b> holds <a href='?_src_=usr;show_paper=1;'>\a [itemname]</a> up to one of your cameras ..."
AI.last_paper_seen = "<HTML><HEAD><TITLE>[itemname]</TITLE></HEAD><BODY><TT>[info]</TT></BODY></HTML>"
else if (O.client && O.client.eye == src)
O << "[U] holds \a [itemname] up to one of the cameras ..."
O << browse(text("<HTML><HEAD><TITLE>[]</TITLE></HEAD><BODY><TT>[]</TT></BODY></HTML>", itemname, info), text("window=[]", itemname))
else if (istype(W, /obj/item/device/camera_bug))
if (!src.can_use())
user << "<span class='notice'>Camera non-functional.</span>"
@@ -232,8 +220,9 @@
user << "<span class='notice'>Camera bugged.</span>"
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("<span class='notice'>[user] has sliced the camera apart with an energy blade!</span>")
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("<span class='danger'>[user] deactivates [src]!</span>")
add_hiddenprint(user)
else
visible_message("<span class='danger'>\The [src] deactivates!</span>")
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("<span class='danger'>[user] [change_msg] [src]!</span>")
add_hiddenprint(user)
else
if(user)
visible_message("<span class='danger'>[user] reactivates [src]!</span>")
add_hiddenprint(user)
else
visible_message("<span class='danger'>\The [src] reactivates!</span>")
playsound(src.loc, 'sound/items/Wirecutter.ogg', 100, 1)
icon_state = initial(icon_state)
visible_message("<span class='danger'>\The [src] [change_msg]!</span>")
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 << "<span class='notice'>You start to weld [src].</span>"
user << "<span class='notice'>You start to weld [src]...</span>"
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
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)
+95 -94
View File
@@ -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 << "<span class='notice'>Attempting to track [target.get_visible_name()]...</span>"
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 << "<span class='warning'>Target is not near any active cameras.</span>"
U.cameraFollow = null
return
U << "<span class='notice'>Now tracking [target.get_visible_name()] on camera.</span>"
var/cameraticks = 0
spawn(0)
while(U.cameraFollow == target)
if(U.cameraFollow == null)
return
if (!trackable(target))
U << "<span class='warning'>Target is not on or near any active cameras on the station.</span>"
sleep(100)
continue
if(!target.can_track(usr))
U.tracking = 1
if(!cameraticks)
U << "<span class='warning'>Target is not near any active cameras. Attempting to reacquire...</span>"
cameraticks++
if(cameraticks > 9)
U.cameraFollow = null
U << "<span class='warning'>Unable to reacquire, cancelling track...</span>"
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 << "<span class='warning'>Internal camera is currently being accessed.</span>"
/atom/movable/proc/tracking_cancelled()
/mob/living/silicon/robot/tracking_initiated()
tracking_entities--
if(!tracking_entities && has_zeroth_law())
src << "<span class='notice'>Internal camera is no longer being accessed.</span>"
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
+64 -74
View File
@@ -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[<name>] = list(<access>)
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 <b>Unable to establish a connection</b>: \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
-1
View File
@@ -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)
@@ -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)
+2 -2
View File
@@ -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()
+15 -3
View File
@@ -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 << "<span class='notice'>You swallow a gulp of [toDrink].</span>"
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 ..()
+25
View File
@@ -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 << "<span class='notice'>You're too exhausted to keep going...</span>"
Weaken(5)
/mob/living/proc/get_visible_name()
return name
/mob/living/update_gravity(has_gravity)
if(!ticker)
return
+61 -38
View File
@@ -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 << "<span class='warning'>Target is not on or near any active cameras on the station.</span>"
@@ -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 << "<span class='warning'>Target is not on or near any active cameras on the station.</span>"
@@ -633,7 +652,6 @@ var/list/ai_verbs_default = list(
d += "<td width='30%'>[bot_area.name]</td>"
d += "<td width='10%'><A HREF=?src=\ref[src];interface=\ref[Bot]>Interface</A></td>"
d += "<td width='10%'><A HREF=?src=\ref[src];callbot=\ref[Bot]>Call</A></td>"
d += "<td width='10%'><a href='byond://?src=\ref[src];track2=\ref[src];trackbot=\ref[Bot]'>Track</A></td>"
d += "</tr>"
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)
@@ -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()
@@ -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.
@@ -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"]."
@@ -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"
+41
View File
@@ -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."
+5 -5
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+30 -25
View File
@@ -5,33 +5,38 @@ Used In File(s): \code\game\machinery\computer\camera.dm
{{:helper.link('Show Map', 'thumb-tack', {'showMap' : 1})}}
{{:helper.link('Reset', 'refresh', {'reset' : 1})}}
<div class='item'>
<div class='itemLabel'>Current Camera: </div>
{{if data.current}}
<div class='itemContent'><b>{{:data.current.name}}</b></div>
{{else}}
<div class='itemContent'>None</div>
{{/if}}
<div class='itemLabel'>Current Camera: </div>
{{if data.current}}
<div class='itemContent'><b>{{:data.current.name}}</b></div>
{{else}}
<div class='itemContent'>None</div>
{{/if}}
</div>
{{for data.cameras}}
{{if data.current && value.name == data.current.name}}
{{:helper.link(value.name, '', {'switchTo' : value.camera}, 'selected')}}
{{else value.deact}}
{{:helper.link(value.name + " (deactivated)", '', {}, 'inactive')}}
{{else}}
{{:helper.link(value.name, '', {'switchTo' : value.camera})}}
{{/if}}
{{/for}}
{{if data.networks}}
<div class="item">
<h2>Networks</h2>
Please select the networks you'd like this console to monitor.
{{if data.emagged}}
<div class="bad">WARNING: Unauthorized access detected.</div>
{{/if}}
<div class="item">
{{for data.networks}}
{{:helper.link(value.name, value.active ? 'minus' : 'plus', { 'activate' : value.name, 'active' : value.active }, '', value.active ? 'linkOn' : '')}}
{{/for}}
</div>
<h2>Networks</h2>
Please select the networks you'd like this console to monitor.
{{if data.emagged}}
<div class="bad">WARNING: Unauthorized access detected.</div>
{{/if}}
<div class="item">
{{for data.networks}}
{{:helper.link(value.name, value.active ? 'minus' : 'plus', { 'activate' : value.name, 'active' : value.active }, '', value.active ? 'linkOn' : '')}}
{{/for}}
</div>
</div>
{{/if}}
<div class="item">
<h2>Cameras</h2>
{{for data.cameras}}
{{if data.current && value.name == data.current.name}}
{{:helper.link(value.name, '', {'switchTo' : value.camera}, 'selected')}}
{{else value.deact}}
{{:helper.link(value.name + " (deactivated)", '', {}, 'inactive')}}
{{else}}
{{:helper.link(value.name, '', {'switchTo' : value.camera})}}
{{/if}}
{{/for}}
</div>