diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm
index 10ab63c9259..8e6118f6f46 100644
--- a/code/__HELPERS/lists.dm
+++ b/code/__HELPERS/lists.dm
@@ -396,4 +396,190 @@ proc/listclearnulls(list/list)
/proc/find_record(field, value, list/L)
for(var/datum/data/record/R in L)
if(R.fields[field] == value)
- return R
\ No newline at end of file
+ return R
+
+
+/proc/dd_sortedObjectList(var/list/L, var/cache=list())
+ if(L.len < 2)
+ return L
+ var/middle = L.len / 2 + 1 // Copy is first,second-1
+ return dd_mergeObjectList(dd_sortedObjectList(L.Copy(0,middle), cache), dd_sortedObjectList(L.Copy(middle), cache), cache) //second parameter null = to end of list
+
+/proc/dd_mergeObjectList(var/list/L, var/list/R, var/list/cache)
+ var/Li=1
+ var/Ri=1
+ var/list/result = new()
+ while(Li <= L.len && Ri <= R.len)
+ var/LLi = L[Li]
+ var/RRi = R[Ri]
+ var/LLiV = cache[LLi]
+ var/RRiV = cache[RRi]
+ if(!LLiV)
+ LLiV = LLi:dd_SortValue()
+ cache[LLi] = LLiV
+ if(!RRiV)
+ RRiV = RRi:dd_SortValue()
+ cache[RRi] = RRiV
+ if(LLiV < RRiV)
+ result += L[Li++]
+ else
+ result += R[Ri++]
+
+ if(Li <= L.len)
+ return (result + L.Copy(Li, 0))
+ return (result + R.Copy(Ri, 0))
+
+// Insert an object into a sorted list, preserving sortedness
+/proc/dd_insertObjectList(var/list/L, var/O)
+ var/min = 1
+ var/max = L.len
+ var/Oval = O:dd_SortValue()
+
+ while(1)
+ var/mid = min+round((max-min)/2)
+
+ if(mid == max)
+ L.Insert(mid, O)
+ return
+
+ var/Lmid = L[mid]
+ var/midval = Lmid:dd_SortValue()
+ if(Oval == midval)
+ L.Insert(mid, O)
+ return
+ else if(Oval < midval)
+ max = mid
+ else
+ min = mid+1
+
+/*
+proc/dd_sortedObjectList(list/incoming)
+ /*
+ Use binary search to order by dd_SortValue().
+ This works by going to the half-point of the list, seeing if the node in
+ question is higher or lower cost, then going halfway up or down the list
+ and checking again. This is a very fast way to sort an item into a list.
+ */
+ var/list/sorted_list = new()
+ var/low_index
+ var/high_index
+ var/insert_index
+ var/midway_calc
+ var/current_index
+ var/current_item
+ var/current_item_value
+ var/current_sort_object_value
+ var/list/list_bottom
+
+ var/current_sort_object
+ for (current_sort_object in incoming)
+ low_index = 1
+ high_index = sorted_list.len
+ while (low_index <= high_index)
+ // Figure out the midpoint, rounding up for fractions. (BYOND rounds down, so add 1 if necessary.)
+ midway_calc = (low_index + high_index) / 2
+ current_index = round(midway_calc)
+ if (midway_calc > current_index)
+ current_index++
+ current_item = sorted_list[current_index]
+
+ current_item_value = current_item:dd_SortValue()
+ current_sort_object_value = current_sort_object:dd_SortValue()
+ if (current_sort_object_value < current_item_value)
+ high_index = current_index - 1
+ else if (current_sort_object_value > current_item_value)
+ low_index = current_index + 1
+ else
+ // current_sort_object == current_item
+ low_index = current_index
+ break
+
+ // Insert before low_index.
+ insert_index = low_index
+
+ // Special case adding to end of list.
+ if (insert_index > sorted_list.len)
+ sorted_list += current_sort_object
+ continue
+
+ // Because BYOND lists don't support insert, have to do it by:
+ // 1) taking out bottom of list, 2) adding item, 3) putting back bottom of list.
+ list_bottom = sorted_list.Copy(insert_index)
+ sorted_list.Cut(insert_index)
+ sorted_list += current_sort_object
+ sorted_list += list_bottom
+ return sorted_list
+*/
+
+proc/dd_sortedtextlist(list/incoming, case_sensitive = 0)
+ // Returns a new list with the text values sorted.
+ // Use binary search to order by sortValue.
+ // This works by going to the half-point of the list, seeing if the node in question is higher or lower cost,
+ // then going halfway up or down the list and checking again.
+ // This is a very fast way to sort an item into a list.
+ var/list/sorted_text = new()
+ var/low_index
+ var/high_index
+ var/insert_index
+ var/midway_calc
+ var/current_index
+ var/current_item
+ var/list/list_bottom
+ var/sort_result
+
+ var/current_sort_text
+ for (current_sort_text in incoming)
+ low_index = 1
+ high_index = sorted_text.len
+ while (low_index <= high_index)
+ // Figure out the midpoint, rounding up for fractions. (BYOND rounds down, so add 1 if necessary.)
+ midway_calc = (low_index + high_index) / 2
+ current_index = round(midway_calc)
+ if (midway_calc > current_index)
+ current_index++
+ current_item = sorted_text[current_index]
+
+ if (case_sensitive)
+ sort_result = sorttextEx(current_sort_text, current_item)
+ else
+ sort_result = sorttext(current_sort_text, current_item)
+
+ switch(sort_result)
+ if (1)
+ high_index = current_index - 1 // current_sort_text < current_item
+ if (-1)
+ low_index = current_index + 1 // current_sort_text > current_item
+ if (0)
+ low_index = current_index // current_sort_text == current_item
+ break
+
+ // Insert before low_index.
+ insert_index = low_index
+
+ // Special case adding to end of list.
+ if (insert_index > sorted_text.len)
+ sorted_text += current_sort_text
+ continue
+
+ // Because BYOND lists don't support insert, have to do it by:
+ // 1) taking out bottom of list, 2) adding item, 3) putting back bottom of list.
+ list_bottom = sorted_text.Copy(insert_index)
+ sorted_text.Cut(insert_index)
+ sorted_text += current_sort_text
+ sorted_text += list_bottom
+ return sorted_text
+
+
+proc/dd_sortedTextList(list/incoming)
+ var/case_sensitive = 1
+ return dd_sortedtextlist(incoming, case_sensitive)
+
+
+datum/proc/dd_SortValue()
+ return "[src]"
+
+/obj/machinery/dd_SortValue()
+ return "[sanitize(name)]"
+
+/obj/machinery/camera/dd_SortValue()
+ return "[c_tag]"
\ No newline at end of file
diff --git a/code/datums/visibility_networks/visibility_network.dm b/code/datums/visibility_networks/visibility_network.dm
index f1bc24e771a..9c9ce550a51 100644
--- a/code/datums/visibility_networks/visibility_network.dm
+++ b/code/datums/visibility_networks/visibility_network.dm
@@ -1,6 +1,6 @@
/datum/visibility_network
var/list/viewpoints = list()
-
+ var/cameras_unsorted = 1
// the type of chunk used by this network
var/datum/visibility_chunk/ChunkType = /datum/visibility_chunk
@@ -8,6 +8,12 @@
var/list/chunks = list()
var/ready = 0
+
+
+/datum/visibility_network/proc/process_sort()
+ if(cameras_unsorted)
+ viewpoints = dd_sortedObjectList(viewpoints)
+ cameras_unsorted = 0
// Creates a chunk key string from x,y,z coordinates
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index 977c1bd3d3e..9c7085c581d 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -8,17 +8,20 @@
active_power_usage = 10
layer = 5
- var/datum/wires/camera/wires = null // Wires datum
var/list/network = list("SS13")
var/c_tag = null
var/c_tag_order = 999
- var/status = 1.0
+ var/status = 1
anchored = 1.0
- var/invuln = null
+ panel_open = 0 // 0 = Closed / 1 = Open
+ var/indestructible = 0
var/bugged = 0
var/obj/item/weapon/camera_assembly/assembly = null
- var/watcherslist = list()
- var/obj/item/device/camera_bug/hasbug = null
+
+ var/toughness = 5 //sorta fragile
+
+ // WIRES
+ var/datum/wires/camera/wires = null // Wires datum
//OTHER
@@ -28,15 +31,18 @@
var/light_disabled = 0
var/alarm_on = 0
var/busy = 0
- var/indestructible = 0 // If set, prevents aliens from destroying it
+
+ var/obj/item/device/camera_bug/hasbug = null
/obj/machinery/camera/New()
wires = new(src)
-
assembly = new(src)
assembly.state = 4
+
+ invalidateCameraCache()
+
/* // Use this to look for cameras that have the same c_tag.
- for(var/obj/machinery/camera/C in cameranet.viewpoints)
+ 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)
world.log << "[src.c_tag] [src.x] [src.y] [src.z] conflicts with [C.c_tag] [C.x] [C.y] [C.z]"
@@ -50,51 +56,48 @@
ASSERT(src.network.len > 0)
..()
+/obj/machinery/camera/Del()
+ if(!alarm_on)
+ triggerCameraAlarm()
+
+ cancelCameraAlarm()
+ ..()
+
/obj/machinery/camera/emp_act(severity)
if(!isEmpProof())
if(prob(100/severity))
- icon_state = "[initial(icon_state)]emp"
- var/list/previous_network = network
- network = list()
- cameranet.removeCamera(src)
+ invalidateCameraCache()
stat |= EMPED
SetLuminosity(0)
+ kick_viewers()
triggerCameraAlarm()
+ update_icon()
+
spawn(900)
- network = previous_network
- icon_state = initial(icon_state)
stat &= ~EMPED
cancelCameraAlarm()
- if(can_use())
- cameranet.addCamera(src)
- for(var/mob/O in mob_list)
- if(O.client && O.client.eye == src)
- O.unset_machine()
- O.reset_view(null)
- O << "The screen bursts into static."
+ update_icon()
+ invalidateCameraCache()
..()
+/obj/machinery/camera/bullet_act(var/obj/item/projectile/P)
+ if(P.damage_type == BRUTE || P.damage_type == BURN)
+ take_damage(P.damage)
/obj/machinery/camera/ex_act(severity)
- if(src.invuln)
+ if(indestructible)
return
- else
- ..(severity)
- return
+
+ //camera dies if an explosion touches it!
+ if(severity <= 2 || prob(50))
+ destroy()
+
+ ..() //and give it the regular chance of being deleted outright
+
/obj/machinery/camera/blob_act()
- del(src)
return
-
-/obj/machinery/camera/proc/setViewRange(var/num = 7)
- src.view_range = num
- cameranet.updateVisibility(src, 0)
-
-/obj/machinery/camera/proc/shock(var/mob/living/user)
- if(!istype(user))
- return
- user.electrocute_act(10, src)
-
+
/obj/machinery/camera/attack_paw(mob/living/carbon/alien/humanoid/user as mob)
if(!istype(user))
return
@@ -107,10 +110,22 @@
add_hiddenprint(user)
deactivate(user,0)
-/obj/machinery/camera/attackby(W as obj, mob/living/user as mob)
+/obj/machinery/camera/hitby(AM as mob|obj)
+ ..()
+ if (istype(AM, /obj))
+ var/obj/O = AM
+ if (O.throwforce >= src.toughness)
+ visible_message("[src] was hit by [O].")
+ take_damage(O.throwforce)
+/obj/machinery/camera/proc/setViewRange(var/num = 7)
+ src.view_range = num
+ cameranet.updateVisibility(src, 0)
+
+/obj/machinery/camera/attackby(obj/W as obj, mob/living/user as mob)
+ invalidateCameraCache()
// DECONSTRUCTION
- if(istype(W, /obj/item/weapon/screwdriver))
+ if(isscrewdriver(W))
//user << "You start to [panel_open ? "close" : "open"] the camera's panel."
//if(toggle_panel(user)) // No delay because no one likes screwdrivers trying to be hip and have a duration cooldown
panel_open = !panel_open
@@ -118,19 +133,21 @@
"You screw the camera's panel [panel_open ? "open" : "closed"].")
playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
- else if((istype(W, /obj/item/weapon/wirecutters) || istype(W, /obj/item/device/multitool)) && panel_open)
- wires.Interact(user)
+ else if((iswirecutter(W) || ismultitool(W)) && panel_open)
+ interact(user)
- else if(istype(W, /obj/item/weapon/weldingtool) && wires.CanDeconstruct())
+ else if(iswelder(W) && (wires.CanDeconstruct() || (stat & BROKEN)))
if(weld(W, user))
- if(assembly)
+ if (stat & BROKEN)
+ new /obj/item/stack/cable_coil(src.loc, length=2)
+ else if(assembly)
assembly.loc = src.loc
assembly.state = 1
+ new /obj/item/stack/cable_coil(src.loc, length=2)
del(src)
-
// OTHER
- else if ((istype(W, /obj/item/weapon/paper) || istype(W, /obj/item/device/pda)) && isliving(user))
+ else if (can_use() && (istype(W, /obj/item/weapon/paper) || istype(W, /obj/item/device/pda)) && isliving(user))
var/mob/living/U = user
var/obj/item/weapon/paper/X = null
var/obj/item/device/pda/P = null
@@ -152,11 +169,12 @@
else O << "[U] holds \a [itemname] up to one of your cameras ..."
O << browse(text("
[][]", itemname, info), text("window=[]", itemname))
for(var/mob/O in player_list)
- if(O.client && O.client.eye == src)
- O.unset_machine()
- O.reset_view(null)
- O << "[U] holds \a [itemname] up to the camera..."
- O << browse("[itemname][info]","window=[itemname]")
+ if (istype(O.machine, /obj/machinery/computer/security))
+ var/obj/machinery/computer/security/S = O.machine
+ if (S.current == src)
+ O << "[U] holds \a [itemname] up to one of the cameras ..."
+ O << browse(text("[][]", itemname, info), text("window=[]", itemname))
+
else if (istype(W, /obj/item/device/camera_bug) && panel_open)
if (!src.can_use())
user << "\blue Camera non-functional"
@@ -165,32 +183,24 @@
user << "\blue Camera bugged."
user.drop_item(W)
hasbug = W
- contents += W
- if(prob(15))
- spawn(30)
- if(src.can_use() && hasbug)
- desc += "
The power light on the camera is blinking"
- triggerCameraAlarm()
+ src.bugged = 1
else if (iscrowbar(W) && panel_open && src.hasbug)
user << "\blue You retrieve \the [hasbug]"
user.put_in_hands(hasbug)
hasbug = null
- deactivatebug(user)
- 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.
- var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread()
- spark_system.set_up(5, 0, loc)
- spark_system.start()
- playsound(loc, 'sound/weapons/blade1.ogg', 50, 1)
- playsound(loc, "sparks", 50, 1)
- visible_message("\blue The camera has been sliced apart by [] with an energy blade!")
- del(src)
- else if(istype(W, /obj/item/device/laser_pointer))
- var/obj/item/device/laser_pointer/L = W
- L.laser_act(src, user)
+ deactivatebug(user)
+ else if(W.damtype == BRUTE || W.damtype == BURN) //bashing cameras
+ if (W.force >= src.toughness)
+ visible_message("[src] has been [pick(W.attack_verb)] with [W] by [user]!")
+ if (istype(W, /obj/item)) //is it even possible to get into attackby() with non-items?
+ var/obj/item/I = W
+ if (I.hitsound)
+ playsound(loc, I.hitsound, 50, 1, -1)
+ take_damage(W.force)
+
else
..()
- return
+
/obj/machinery/camera/proc/deactivatebug(user as mob)
for(var/mob/O in player_list)
if(istype(O.machine, /obj/item/device/handtv))
@@ -201,50 +211,91 @@
O << "The screen bursts into static."
/obj/machinery/camera/proc/deactivate(user as mob, var/choice = 1)
- if(choice==1)
- status = !( src.status )
+ if(choice != 1)
+ //legacy support, if choice is != 1 then just kick viewers without changing status
+ kick_viewers()
+ else
+ invalidateCameraCache()
+ set_status( !src.status )
if (!(src.status))
- if(user)
- visible_message("\red [user] has deactivated [src]!")
- add_hiddenprint(user)
- else
- visible_message("\red \The [src] deactivates!")
+ visible_message("\red [user] has deactivated [src]!")
playsound(src.loc, 'sound/items/Wirecutter.ogg', 100, 1)
icon_state = "[initial(icon_state)]1"
add_hiddenprint(user)
else
- if(user)
- visible_message("\red [user] has reactivated [src]!")
- add_hiddenprint(user)
- else
- visible_message("\red \the [src] reactivates!")
+ visible_message("\red [user] has reactivated [src]!")
playsound(src.loc, 'sound/items/Wirecutter.ogg', 100, 1)
icon_state = initial(icon_state)
add_hiddenprint(user)
- // now disconnect anyone using the camera
- //Apparently, this will disconnect anyone even if the camera was re-activated.
- //I guess that doesn't matter since they can't use it anyway?
+
+/obj/machinery/camera/proc/take_damage(var/force, var/message)
+ //prob(25) gives an average of 3-4 hits
+ if (force >= toughness && (force > toughness*4 || prob(25)))
+ destroy()
+
+//Used when someone breaks a camera
+/obj/machinery/camera/proc/destroy()
+ invalidateCameraCache()
+ stat |= BROKEN
+ kick_viewers()
+ triggerCameraAlarm()
+ update_icon()
+
+ //sparks
+ var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread()
+ spark_system.set_up(5, 0, loc)
+ spark_system.start()
+ playsound(loc, "sparks", 50, 1)
+
+/obj/machinery/camera/proc/set_status(var/newstatus)
+ if (status != newstatus)
+ status = newstatus
+ invalidateCameraCache()
+ // now disconnect anyone using the camera
+ //Apparently, this will disconnect anyone even if the camera was re-activated.
+ //I guess that doesn't matter since they couldn't use it anyway?
+ kick_viewers()
+
+//This might be redundant, because of check_eye()
+/obj/machinery/camera/proc/kick_viewers()
for(var/mob/O in player_list)
- if(O.client && O.client.eye == src)
- O.unset_machine()
- O.reset_view(null)
- O << "The screen bursts into static."
+ if (istype(O.machine, /obj/machinery/computer/security))
+ var/obj/machinery/computer/security/S = O.machine
+ if (S.current == src)
+ O.unset_machine()
+ O.reset_view(null)
+ O << "The screen bursts into static."
+
+/obj/machinery/camera/update_icon()
+ if (!status || (stat & BROKEN))
+ icon_state = "[initial(icon_state)]1"
+ else if (stat & EMPED)
+ icon_state = "[initial(icon_state)]emp"
+ else
+ icon_state = initial(icon_state)
/obj/machinery/camera/proc/triggerCameraAlarm()
alarm_on = 1
+ if(!get_area(src))
+ return
+
for(var/mob/living/silicon/S in mob_list)
S.triggerAlarm("Camera", get_area(src), list(src), src)
/obj/machinery/camera/proc/cancelCameraAlarm()
alarm_on = 0
+ if(!get_area(src))
+ return
+
for(var/mob/living/silicon/S in mob_list)
- S.cancelAlarm("Camera", get_area(src), list(src), src)
+ S.cancelAlarm("Camera", get_area(src), src)
+//if false, then the camera is listed as DEACTIVATED and cannot be used
/obj/machinery/camera/proc/can_use()
if(!status)
return 0
- if(stat & EMPED)
+ if(stat & (EMPED|BROKEN))
return 0
return 1
@@ -266,13 +317,13 @@
//If someone knows a better way to do this, let me know. -Giacom
switch(i)
if(NORTH)
- src.dir = SOUTH
+ dir = SOUTH
if(SOUTH)
- src.dir = NORTH
+ dir = NORTH
if(WEST)
- src.dir = EAST
+ dir = EAST
if(EAST)
- src.dir = WEST
+ dir = WEST
break
//Return a working camera that can see a given mob
@@ -312,3 +363,14 @@
return 1
busy = 0
return 0
+
+/obj/machinery/camera/interact(mob/living/user as mob)
+ if(!panel_open || istype(user, /mob/living/silicon/ai))
+ return
+
+ if(stat & BROKEN)
+ user << "\The [src] is broken."
+ return
+
+ user.set_machine(src)
+ wires.Interact(user)
diff --git a/code/game/machinery/camera/camera_assembly.dm b/code/game/machinery/camera/camera_assembly.dm
index 9f6eb2210af..8e3238ba675 100644
--- a/code/game/machinery/camera/camera_assembly.dm
+++ b/code/game/machinery/camera/camera_assembly.dm
@@ -1,16 +1,15 @@
/obj/item/weapon/camera_assembly
name = "camera assembly"
- desc = "The basic construction for Nanotrasen-Always-Watching-You cameras."
+ desc = "A pre-fabricated security camera kit, ready to be assembled and mounted to a surface."
icon = 'icons/obj/monitors.dmi'
icon_state = "cameracase"
w_class = 2
anchored = 0
-
m_amt = 700
g_amt = 300
// Motion, EMP-Proof, X-Ray
- var/list/obj/item/possible_upgrades = list(/obj/item/device/assembly/prox_sensor, /obj/item/stack/sheet/mineral/plasma, /obj/item/weapon/reagent_containers/food/snacks/grown/carrot)
+ var/list/obj/item/possible_upgrades = list(/obj/item/device/assembly/prox_sensor, /obj/item/stack/sheet/mineral/osmium, /obj/item/weapon/stock_parts/scanning_module)
var/list/upgrades = list()
var/state = 0
var/busy = 0
@@ -59,8 +58,10 @@
if(iscoil(W))
var/obj/item/stack/cable_coil/C = W
if(C.use(2))
- user << "You add wires to the assembly."
+ user << "You add wires to the assembly."
state = 3
+ else
+ user << "You need 2 coils of wire to wire the assembly."
return
else if(iswelder(W))
@@ -87,7 +88,8 @@
usr << "No network found please hang up and try your call again."
return
- var/temptag = "[get_area(src)] ([rand(1, 999)])"
+ var/area/camera_area = get_area(src)
+ var/temptag = "[sanitize(camera_area.name)] ([rand(1, 999)])"
input = strip_html(input(usr, "How would you like to name the camera?", "Set Camera Name", temptag))
state = 4
@@ -124,7 +126,7 @@
// Upgrades!
if(is_type_in_list(W, possible_upgrades) && !is_type_in_list(W, upgrades)) // Is a possible upgrade and isn't in the camera already.
- user << "You attach the [W] into the assembly inner circuits."
+ user << "You attach \the [W] into the assembly inner circuits."
upgrades += W
user.drop_item(W)
W.loc = src
@@ -169,4 +171,4 @@
return 0
return 1
busy = 0
- return 0
\ No newline at end of file
+ return 0
diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm
index 588ab5005ce..0c6f7d95a7f 100644
--- a/code/game/machinery/camera/motion.dm
+++ b/code/game/machinery/camera/motion.dm
@@ -8,6 +8,8 @@
/obj/machinery/camera/process()
// motion camera event loop
+ if (stat & (EMPED|NOPOWER))
+ return
if(!isMotion())
. = PROCESS_KILL
return
@@ -40,16 +42,20 @@
cancelAlarm()
/obj/machinery/camera/proc/cancelAlarm()
+ if (!status || (stat & NOPOWER))
+ return 0
if (detectTime == -1)
for (var/mob/living/silicon/aiPlayer in player_list)
- if (status) aiPlayer.cancelAlarm("Motion", src.loc.loc)
+ aiPlayer.cancelAlarm("Motion", get_area(src), src)
detectTime = 0
return 1
/obj/machinery/camera/proc/triggerAlarm()
+ if (!status || (stat & NOPOWER))
+ return 0
if (!detectTime) return 0
for (var/mob/living/silicon/aiPlayer in player_list)
- if (status) aiPlayer.triggerAlarm("Motion", src.loc.loc, src)
+ aiPlayer.triggerAlarm("Motion", get_area(src), list(src), src)
detectTime = -1
return 1
diff --git a/code/game/machinery/camera/presets.dm b/code/game/machinery/camera/presets.dm
index 0ee0e622b8b..21a9dc10616 100644
--- a/code/game/machinery/camera/presets.dm
+++ b/code/game/machinery/camera/presets.dm
@@ -53,12 +53,14 @@
// CHECKS
/obj/machinery/camera/proc/isEmpProof()
- var/O = locate(/obj/item/stack/sheet/mineral/plasma) in assembly.upgrades
+ var/O = locate(/obj/item/stack/sheet/mineral/osmium) in assembly.upgrades
return O
/obj/machinery/camera/proc/isXRay()
- var/O = locate(/obj/item/weapon/reagent_containers/food/snacks/grown/carrot) in assembly.upgrades
- return O
+ var/obj/item/weapon/stock_parts/scanning_module/O = locate(/obj/item/weapon/stock_parts/scanning_module) in assembly.upgrades
+ if (O && O.rating >= 2)
+ return O
+ return null
/obj/machinery/camera/proc/isMotion()
var/O = locate(/obj/item/device/assembly/prox_sensor) in assembly.upgrades
@@ -67,11 +69,22 @@
// UPGRADE PROCS
/obj/machinery/camera/proc/upgradeEmpProof()
- assembly.upgrades.Add(new /obj/item/stack/sheet/mineral/plasma(assembly))
+ assembly.upgrades.Add(new /obj/item/stack/sheet/mineral/osmium(assembly))
+ setPowerUsage()
/obj/machinery/camera/proc/upgradeXRay()
- assembly.upgrades.Add(new /obj/item/weapon/reagent_containers/food/snacks/grown/carrot(assembly))
+ assembly.upgrades.Add(new /obj/item/weapon/stock_parts/scanning_module/adv(assembly))
+ setPowerUsage()
// If you are upgrading Motion, and it isn't in the camera's New(), add it to the machines list.
/obj/machinery/camera/proc/upgradeMotion()
- assembly.upgrades.Add(new /obj/item/device/assembly/prox_sensor(assembly))
\ No newline at end of file
+ assembly.upgrades.Add(new /obj/item/device/assembly/prox_sensor(assembly))
+ setPowerUsage()
+
+/obj/machinery/camera/proc/setPowerUsage()
+ var/mult = 1
+ if (isXRay())
+ mult++
+ if (isMotion())
+ mult++
+ active_power_usage = mult*initial(active_power_usage)
diff --git a/code/game/machinery/camera/tracking.dm b/code/game/machinery/camera/tracking.dm
index 47b2edf9fac..aafb582e902 100644
--- a/code/game/machinery/camera/tracking.dm
+++ b/code/game/machinery/camera/tracking.dm
@@ -1,16 +1,25 @@
+/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 == 2)
+ return 1
+ if(T.z > 6)
+ return 1
+ return 0
+
/mob/living/silicon/ai/proc/get_camera_list()
+
if(src.stat == 2)
return
- var/list/L = list()
- for (var/obj/machinery/camera/C in cameranet.viewpoints)
- L.Add(C)
-
- camera_sort(L)
+ cameranet.process_sort()
var/list/T = list()
T["Cancel"] = "Cancel"
- for (var/obj/machinery/camera/C in L)
+ for (var/obj/machinery/camera/C in cameranet.viewpoints)
var/list/tempnetwork = C.network&src.network
if (tempnetwork.len)
T[text("[][]", C.c_tag, (C.can_use() ? null : " (Deactivated)"))] = C
@@ -19,20 +28,76 @@
track.cameras = T
return T
+
/mob/living/silicon/ai/proc/ai_camera_list(var/camera in get_camera_list())
+ set category = "AI Commands"
+ set name = "Show Camera List"
+
if(src.stat == 2)
src << "You can't list the cameras because you are dead!"
return
if (!camera || camera == "Cancel")
return 0
-
+
var/obj/machinery/camera/C = track.cameras[camera]
- track = null
src.eyeobj.setLoc(C)
return
+/mob/living/silicon/ai/proc/ai_store_location(loc as text)
+ set category = "AI Commands"
+ set name = "Store Camera Location"
+ set desc = "Stores your current camera location by the given name"
+
+ loc = sanitize(copytext(loc, 1, MAX_MESSAGE_LEN))
+ if(!loc)
+ src << "\red Must supply a location name"
+ return
+
+ if(stored_locations.len >= max_locations)
+ src << "\red Cannot store additional locations. Remove one first"
+ return
+
+ if(loc in stored_locations)
+ src << "\red There is already a stored location by this name"
+ return
+
+ var/L = src.eyeobj.getLoc()
+ if (InvalidTurf(get_turf(L)))
+ src << "\red Unable to store this location"
+ return
+
+ stored_locations[loc] = L
+ src << "Location '[loc]' stored"
+
+/mob/living/silicon/ai/proc/sorted_stored_locations()
+ return sortList(stored_locations)
+
+/mob/living/silicon/ai/proc/ai_goto_location(loc in sorted_stored_locations())
+ set category = "AI Commands"
+ set name = "Goto Camera Location"
+ set desc = "Returns to the selected camera location"
+
+ if (!(loc in stored_locations))
+ src << "\red Location [loc] not found"
+ return
+
+ var/L = stored_locations[loc]
+ src.eyeobj.setLoc(L)
+
+/mob/living/silicon/ai/proc/ai_remove_location(loc in sorted_stored_locations())
+ set category = "AI Commands"
+ set name = "Delete Camera Location"
+ set desc = "Deletes the selected camera location"
+
+ if (!(loc in stored_locations))
+ src << "\red Location [loc] not found"
+ return
+
+ stored_locations.Remove(loc)
+ src << "Location [loc] removed"
+
// Used to allow the AI is write in mob names/camera name from the CMD line.
/datum/trackable
var/list/names = list()
@@ -50,12 +115,7 @@
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.
- var/turf/T = get_turf(M)
- if(!T)
- continue
- if(T.z == 2)
- continue
- if(T.z > 6)
+ if(InvalidTurf(get_turf(M)))
continue
if(M == usr)
continue
@@ -72,10 +132,6 @@
//Cameras can't track people wearing an agent card or a ninja hood.
if(H.wear_id && istype(H.wear_id.GetID(), /obj/item/weapon/card/id/syndicate))
continue
- if(istype(H.head, /obj/item/clothing/head/helmet/space/space_ninja))
- var/obj/item/clothing/head/helmet/space/space_ninja/hood = H.head
- if(!hood.canremove)
- continue
// Now, are they viewable by a camera? (This is last because it's the most intensive check)
if(!near_camera(M))
@@ -98,6 +154,10 @@
return targets
/mob/living/silicon/ai/proc/ai_camera_track(var/target_name in trackable_mobs())
+ set category = "AI Commands"
+ set name = "Track With Camera"
+ set desc = "Select who you would like to track."
+
if(src.stat == 2)
src << "You can't track with camera because you are dead!"
return
@@ -108,61 +168,18 @@
src.track = null
ai_actual_track(target)
-/mob/living/silicon/ai/proc/open_nearest_door(mob/living/target as mob)
- if(!istype(target)) return
- spawn(0)
- if(istype(target, /mob/living/carbon/human))
- var/mob/living/carbon/human/H = target
- if(H.wear_id && istype(H.wear_id.GetID(), /obj/item/weapon/card/id/syndicate))
- src << "Unable to locate an airlock"
- return
- if(H.wear_id && istype(H.wear_id.GetID(), /obj/item/weapon/card/id/syndicate))
- src << "Unable to locate an airlock"
- return
- if(istype(H.head, /obj/item/clothing/head/helmet/space/space_ninja) && !H.head.canremove)
- src << "Unable to locate an airlock"
- return
- if(H.digitalcamo)
- src << "Unable to locate an airlock"
- return
- if (!near_camera(target))
- src << "Target is not near any active cameras."
- return
- var/obj/machinery/door/airlock/tobeopened
- var/dist = -1
- for(var/obj/machinery/door/airlock/D in range(3,target))
- if(!D.density) continue
- if(dist < 0)
- dist = get_dist(D, target)
- //world << dist
- tobeopened = D
- else
- if(dist > get_dist(D, target))
- dist = get_dist(D, target)
- //world << dist
- tobeopened = D
- //world << "found [tobeopened.name] closer"
- else
- //world << "[D.name] not close enough | [get_dist(D, target)] | [dist]"
- if(tobeopened)
- switch(alert(src, "Do you want to open \the [tobeopened] for [target]?","Doorknob_v2a.exe","Yes","No"))
- if("Yes")
- var/nhref = "src=\ref[tobeopened];aiEnable=7"
- tobeopened.Topic(nhref, params2list(nhref), tobeopened, 1)
- src << "\blue You've opened \the [tobeopened] for [target]."
- if("No")
- src << "\red You deny the request."
- else
- src << "\red You've failed to open an airlock for [target]"
+/mob/living/silicon/ai/proc/ai_cancel_tracking(var/forced = 0)
+ if(!cameraFollow)
return
-/mob/living/silicon/ai/proc/ai_actual_track(atom/target as mob|obj)
+
+ src << "Follow camera mode [forced ? "terminated" : "ended"]."
+ cameraFollow = null
+
+/mob/living/silicon/ai/proc/ai_actual_track(mob/living/target as mob)
if(!istype(target)) return
var/mob/living/silicon/ai/U = usr
U.cameraFollow = target
- //U << text("Now tracking [] on camera.", target.name)
- //if (U.machine == null)
- // U.machine = U
U << "Now tracking [target.name] on camera."
spawn (0)
@@ -172,30 +189,23 @@
if (istype(target, /mob/living/carbon/human))
var/mob/living/carbon/human/H = target
if(H.wear_id && istype(H.wear_id.GetID(), /obj/item/weapon/card/id/syndicate))
- U << "Follow camera mode terminated."
- U.cameraFollow = null
+ U.ai_cancel_tracking(1)
return
-/* if(istype(H.head, /obj/item/clothing/head/helmet/space/space_ninja) && !H.head.canremove)
- U << "Follow camera mode terminated."
- U.cameraFollow = null
- return*/
if(H.digitalcamo)
- U << "Follow camera mode terminated."
- U.cameraFollow = null
+ U.ai_cancel_tracking(1)
return
if(istype(target.loc,/obj/effect/dummy))
- U << "Follow camera mode ended."
- U.cameraFollow = null
+ U.ai_cancel_tracking()
return
- if (!near_camera(target))
+ if (!trackable(target))
U << "Target is not near any active cameras."
sleep(100)
continue
if(U.eyeobj)
- U.eyeobj.setLoc(get_turf(target))
+ U.eyeobj.setLoc(get_turf(target), 0)
else
view_core()
return
@@ -212,6 +222,12 @@
return 0
return 1
+/proc/trackable(var/mob/living/M)
+ var/turf/T = get_turf(M)
+ if(T && (T.z == 1) && hassensorlevel(M, SUIT_SENSOR_TRACKING))
+ return 1
+
+ return near_camera(M)
/obj/machinery/camera/attack_ai(var/mob/living/silicon/ai/user as mob)
if (!istype(user))
@@ -223,19 +239,3 @@
/mob/living/silicon/ai/attack_ai(var/mob/user as mob)
ai_camera_list()
-
-/proc/camera_sort(list/L)
- var/obj/machinery/camera/a
- var/obj/machinery/camera/b
-
- 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
diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index 7bc376580b8..59cd90cb17b 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -1,3 +1,7 @@
+/proc/invalidateCameraCache()
+ for(var/obj/machinery/computer/security/s in world)
+ s.camera_cache = null
+
/obj/machinery/computer/security
name = "Camera Monitor"
desc = "Used to access the various cameras networks on the station."
@@ -12,6 +16,7 @@
var/list/tempnets[0]
var/list/data[0]
var/list/access[0]
+ var/camera_cache = null
New() // Lists existing networks and their required access. Format: networks[] = list()
networks["SS13"] = list(access_hos,access_captain)
@@ -78,29 +83,38 @@
data["current"] = null
- var/list/L = list()
- for (var/obj/machinery/camera/C in cameranet.viewpoints)
- if(can_access_camera(C))
- L.Add(C)
+ if(isnull(camera_cache))
+ cameranet.process_sort()
- camera_sort(L)
+ var/cameras[0]
+ for(var/obj/machinery/camera/C in cameranet.viewpoints)
+ var/cam[0]
+ cam["name"] = C.c_tag
+ cam["deact"] = !C.can_use()
+ cam["camera"] = "\ref[C]"
+ cam["x"] = C.x
+ cam["y"] = C.y
+ cam["z"] = C.z
- var/cameras[0]
- for(var/obj/machinery/camera/C in L)
- var/cam[0]
- cam["name"] = C.c_tag
- cam["deact"] = !C.can_use()
- cam["camera"] = "\ref[C]"
- cam["x"] = C.x
- cam["y"] = C.y
- cam["z"] = C.z
+ cameras[++cameras.len] = cam
- cameras[++cameras.len] = cam
+ if(C == current)
+ data["current"] = cam
+
+ var/list/camera_list = list("cameras" = cameras)
+ camera_cache=list2json(camera_list)
+
+ else
+ if(current)
+ var/cam[0]
+ cam["name"] = current.c_tag
+ cam["deact"] = !current.can_use()
+ cam["camera"] = "\ref[current]"
+ cam["x"] = current.x
+ cam["y"] = current.y
+ cam["z"] = current.z
- if(C == current)
data["current"] = cam
-
- data["cameras"] = cameras
tempnets.Cut()
if(emagged)
@@ -119,6 +133,9 @@
tempnets.Add(list(list("name" = l, "active" = 0)))
break
data["networks"] = tempnets
+
+ if(ui)
+ ui.load_cached_data(camera_cache)
ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
@@ -128,7 +145,8 @@
ui.add_template("mapContent", "sec_camera_map_content.tmpl")
// adding a template with the key "mapHeader" replaces the map header content
ui.add_template("mapHeader", "sec_camera_map_header.tmpl")
-
+
+ ui.load_cached_data(camera_cache)
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
diff --git a/code/modules/computer3/computers/camera.dm b/code/modules/computer3/computers/camera.dm
index e0d485e1da1..ed387513a5f 100644
--- a/code/modules/computer3/computers/camera.dm
+++ b/code/modules/computer3/computers/camera.dm
@@ -190,7 +190,7 @@
if(temp.len)
L.Add(C)
- camera_sort(L)
+ cameranet.process_sort()
return L
verify_machine(var/obj/machinery/camera/C,var/datum/file/camnet_key/key = null)
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 1fabedb239c..a6583841231 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -112,7 +112,7 @@ var/list/ai_list = list()
if (istype(loc, /turf))
verbs.Add(/mob/living/silicon/ai/proc/ai_network_change, \
/mob/living/silicon/ai/proc/ai_statuschange, /mob/living/silicon/ai/proc/ai_hologram_change, \
- /mob/living/silicon/ai/proc/toggle_camera_light, /mob/living/silicon/ai/proc/botcall, /mob/living/silicon/ai/proc/control_integrated_radio, /mob/living/silicon/ai/proc/control_hud, /mob/living/silicon/ai/proc/change_arrival_message)
+ /mob/living/silicon/ai/proc/toggle_camera_light, /mob/living/silicon/ai/proc/botcall, /mob/living/silicon/ai/proc/control_integrated_radio, /mob/living/silicon/ai/proc/control_hud, /mob/living/silicon/ai/proc/change_arrival_message, /mob/living/silicon/ai/proc/ai_store_location, /mob/living/silicon/ai/proc/ai_goto_location, /mob/living/silicon/ai/proc/ai_remove_location)
if(!safety)//Only used by AIize() to successfully spawn an AI.
if (!B)//If there is no player/brain inside.
@@ -127,7 +127,7 @@ var/list/ai_list = list()
verbs.Remove(,/mob/living/silicon/ai/proc/ai_call_shuttle,/mob/living/silicon/ai/proc/ai_camera_track, \
/mob/living/silicon/ai/proc/ai_camera_list, /mob/living/silicon/ai/proc/ai_network_change, \
/mob/living/silicon/ai/proc/ai_statuschange, /mob/living/silicon/ai/proc/ai_hologram_change, \
- /mob/living/silicon/ai/proc/toggle_camera_light,/mob/living/silicon/ai/verb/pick_icon,/mob/living/silicon/ai/proc/control_hud)
+ /mob/living/silicon/ai/proc/toggle_camera_light,/mob/living/silicon/ai/verb/pick_icon,/mob/living/silicon/ai/proc/control_hud, /mob/living/silicon/ai/proc/change_arrival_message)
laws = new /datum/ai_laws/alienmov
else
B.brainmob.mind.transfer_to(src)
@@ -631,8 +631,6 @@ var/list/ai_list = list()
/mob/living/silicon/ai/proc/switchCamera(var/obj/machinery/camera/C)
- src.cameraFollow = null
-
if (!C || stat == 2) //C.can_use())
return 0
@@ -706,7 +704,6 @@ var/list/ai_list = list()
set category = "AI Commands"
set name = "Jump To Network"
unset_machine()
- src.cameraFollow = null
var/cameralist[0]
if(usr.stat == 2)
@@ -912,3 +909,52 @@ var/list/ai_list = list()
src << "Accessing Subspace Transceiver control..."
if (src.aiRadio)
src.aiRadio.interact(src)
+
+
+/mob/living/silicon/ai/proc/open_nearest_door(mob/living/target as mob)
+ if(!istype(target)) return
+ spawn(0)
+ if(istype(target, /mob/living/carbon/human))
+ var/mob/living/carbon/human/H = target
+ if(H.wear_id && istype(H.wear_id.GetID(), /obj/item/weapon/card/id/syndicate))
+ src << "Unable to locate an airlock"
+ return
+ if(H.wear_id && istype(H.wear_id.GetID(), /obj/item/weapon/card/id/syndicate))
+ src << "Unable to locate an airlock"
+ return
+ if(istype(H.head, /obj/item/clothing/head/helmet/space/space_ninja) && !H.head.canremove)
+ src << "Unable to locate an airlock"
+ return
+ if(H.digitalcamo)
+ src << "Unable to locate an airlock"
+ return
+ if (!near_camera(target))
+ src << "Target is not near any active cameras."
+ return
+ var/obj/machinery/door/airlock/tobeopened
+ var/dist = -1
+ for(var/obj/machinery/door/airlock/D in range(3,target))
+ if(!D.density) continue
+ if(dist < 0)
+ dist = get_dist(D, target)
+ //world << dist
+ tobeopened = D
+ else
+ if(dist > get_dist(D, target))
+ dist = get_dist(D, target)
+ //world << dist
+ tobeopened = D
+ //world << "found [tobeopened.name] closer"
+ else
+ //world << "[D.name] not close enough | [get_dist(D, target)] | [dist]"
+ if(tobeopened)
+ switch(alert(src, "Do you want to open \the [tobeopened] for [target]?","Doorknob_v2a.exe","Yes","No"))
+ if("Yes")
+ var/nhref = "src=\ref[tobeopened];aiEnable=7"
+ tobeopened.Topic(nhref, params2list(nhref), tobeopened, 1)
+ src << "\blue You've opened \the [tobeopened] for [target]."
+ if("No")
+ src << "\red You deny the request."
+ else
+ src << "\red You've failed to open an airlock for [target]"
+ return
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm
index c2c23b02188..afe319fc501 100644
--- a/code/modules/mob/living/silicon/ai/freelook/eye.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm
@@ -54,6 +54,14 @@
//Holopad
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/Move()
diff --git a/code/modules/mob/living/silicon/ai/freelook/update_triggers.dm b/code/modules/mob/living/silicon/ai/freelook/update_triggers.dm
index 775d8864751..d6799548bff 100644
--- a/code/modules/mob/living/silicon/ai/freelook/update_triggers.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/update_triggers.dm
@@ -12,12 +12,19 @@
/obj/machinery/camera/New()
..()
- cameranet.viewpoints += src //Camera must be added to global list of all cameras no matter what...
+ //Camera must be added to global list of all cameras no matter what...
+ if(cameranet.cameras_unsorted || !ticker)
+ cameranet.viewpoints += src
+ cameranet.cameras_unsorted = 1
+ else
+ dd_insertObjectList(cameranet.viewpoints, 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.addViewpoint(src)
/obj/machinery/camera/Del()
+
cameranet.viewpoints -= src
var/list/open_networks = difflist(network,RESTRICTED_CAMERA_NETWORKS)
if(open_networks.len)
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 0432e4baef6..b652a106824 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -161,6 +161,14 @@ proc/hasorgans(A)
/proc/hsl2rgb(h, s, l)
return
+
+proc/hassensorlevel(A, var/level)
+ var/mob/living/carbon/human/H = A
+ if(istype(H) && istype(H.w_uniform, /obj/item/clothing/under))
+ var/obj/item/clothing/under/U = H.w_uniform
+ return U.sensor_mode >= level
+ return 0
+
/proc/check_zone(zone)
diff --git a/code/modules/nano/JSON Writer.dm b/code/modules/nano/JSON Writer.dm
index 3cd3520f177..f4c74d35ebb 100644
--- a/code/modules/nano/JSON Writer.dm
+++ b/code/modules/nano/JSON Writer.dm
@@ -1,7 +1,7 @@
json_writer
proc
- WriteObject(list/L)
+ WriteObject(list/L, cached_data = null)
. = "{"
var/i = 1
for(var/k in L)
@@ -9,6 +9,8 @@ json_writer
. += {"\"[k]\":[write(val)]"}
if(i++ < L.len)
. += ","
+ if(cached_data)
+ . = copytext(., 1, lentext(.)) + ",\"cached\":[cached_data]}"
.+= "}"
write(val)
diff --git a/code/modules/nano/_JSON.dm b/code/modules/nano/_JSON.dm
index 5692e643baa..4f70e6e664a 100644
--- a/code/modules/nano/_JSON.dm
+++ b/code/modules/nano/_JSON.dm
@@ -7,6 +7,6 @@ proc
var/static/json_reader/_jsonr = new()
return _jsonr.ReadObject(_jsonr.ScanJson(json))
- list2json(list/L)
+ list2json(list/L, var/cached_data = null)
var/static/json_writer/_jsonw = new()
- return _jsonw.WriteObject(L)
+ return _jsonw.WriteObject(L, cached_data)
diff --git a/code/modules/nano/nanoui.dm b/code/modules/nano/nanoui.dm
index 54ff6b70b01..01792ad0c3d 100644
--- a/code/modules/nano/nanoui.dm
+++ b/code/modules/nano/nanoui.dm
@@ -58,6 +58,9 @@ nanoui is used to open and update nano browser uis
var/is_auto_updating = 0
// the current status/visibility of the ui
var/status = STATUS_INTERACTIVE
+
+ var/cached_data = null
+
// Only allow users with a certain user.stat to get updates. Defaults to 0 (concious)
var/allowed_user_stat = 0 // -1 = ignore, 0 = alive, 1 = unconcious or alive, 2 = dead concious or alive
@@ -370,7 +373,7 @@ nanoui is used to open and update nano browser uis
template_data_json = list2json(templates)
var/list/send_data = get_send_data(initial_data)
- var/initial_data_json = list2json(send_data)
+ var/initial_data_json = list2json(send_data, cached_data)
var/url_parameters_json = list2json(list("src" = "\ref[src]"))
@@ -450,6 +453,17 @@ nanoui is used to open and update nano browser uis
var/params = "\ref[src]"
winset(user, window_id, "on-close=\"nanoclose [params]\"")
+
+/**
+ * Appends already processed json txt to the list2json proc when setting initial-data and data pushes
+ * Used for data that is fucking huge like manifests and camera lists that doesn't change often.
+ * And we only want to process them when they change.
+ *
+ * @return nothing
+ */
+/datum/nanoui/proc/load_cached_data(var/data)
+ cached_data = data
+ return
/**
* Push data to an already open UI window
@@ -464,7 +478,7 @@ nanoui is used to open and update nano browser uis
var/list/send_data = get_send_data(data)
//user << list2json(data) // used for debugging
- user << output(list2params(list(list2json(send_data))),"[window_id].browser:receiveUpdateData")
+ user << output(list2params(list(list2json(send_data,cached_data))),"[window_id].browser:receiveUpdateData")
/**
* This Topic() proc is called whenever a user clicks on a link within a Nano UI
diff --git a/code/modules/organs/organ_internal.dm b/code/modules/organs/organ_internal.dm
index 54aac02d952..e0872804ce3 100644
--- a/code/modules/organs/organ_internal.dm
+++ b/code/modules/organs/organ_internal.dm
@@ -96,6 +96,7 @@
P.internal_organs = list()
P.internal_organs += src
H.internal_organs_by_name[name] = src
+ H.internal_organs |= src
owner = H
return
@@ -196,7 +197,7 @@
src.damage += 0.2 * process_accuracy
//Damaged one shares the fun
else
- var/datum/organ/internal/O = pick(owner.internal_organs_by_name)
+ var/datum/organ/internal/O = pick(owner.internal_organs)
if(O)
O.damage += 0.2 * process_accuracy
diff --git a/code/setup.dm b/code/setup.dm
index 157b5a47d04..925f72c9536 100644
--- a/code/setup.dm
+++ b/code/setup.dm
@@ -806,6 +806,8 @@ var/list/RESTRICTED_CAMERA_NETWORKS = list( //Those networks can only be accesse
"ERT",
"NukeOps",
"Thunderdome",
+ "UO45",
+ "UO45R",
"Xeno"
)
@@ -943,4 +945,10 @@ var/list/hit_appends = list("-OOF", "-ACK", "-UGH", "-HRNK", "-HURGH", "-GLORF")
#define AUTOLATHE 4 //Uses glass/metal only.
#define CRAFTLATHE 8 //Uses fuck if I know. For use eventually.
#define MECHFAB 16 //Remember, objects utilising this flag should have construction_time and construction_cost vars.
-//Note: More then one of these can be added to a design but imprinter and lathe designs are incompatable.
\ No newline at end of file
+//Note: More then one of these can be added to a design but imprinter and lathe designs are incompatable.
+
+// Suit sensor levels
+#define SUIT_SENSOR_OFF 0
+#define SUIT_SENSOR_BINARY 1
+#define SUIT_SENSOR_VITAL 2
+#define SUIT_SENSOR_TRACKING 3
\ No newline at end of file
diff --git a/nano/templates/sec_camera.tmpl b/nano/templates/sec_camera.tmpl
index b9764555cd6..5aad1e29298 100644
--- a/nano/templates/sec_camera.tmpl
+++ b/nano/templates/sec_camera.tmpl
@@ -12,7 +12,7 @@ Used In File(s): \code\game\machinery\computer\camera.dm
None
{{/if}}
-{{for data.cameras}}
+{{for data.cached.cameras}}
{{if data.current && value.name == data.current.name}}
{{:helper.link(value.name, '', {'switchTo' : value.camera}, 'selected')}}
{{else value.deact}}
diff --git a/nano/templates/sec_camera_map_content.tmpl b/nano/templates/sec_camera_map_content.tmpl
index 52dc8aeadd6..d0abff84285 100644
--- a/nano/templates/sec_camera_map_content.tmpl
+++ b/nano/templates/sec_camera_map_content.tmpl
@@ -2,7 +2,7 @@
Title: Security Camera Console (Map content)
Used In File(s): \code\game\machinery\computer\camera.dm
-->
-{{for data.cameras}}
+{{for data.cached.cameras}}
{{if value.z == 1}}
{{if data.current && value.name == data.current.name}}