From c507158b8f06df3a43c7942e4c01fb475172e9c9 Mon Sep 17 00:00:00 2001 From: Markolie Date: Tue, 10 Feb 2015 15:42:47 +0100 Subject: [PATCH] Camera code update, fix runtime --- code/__HELPERS/lists.dm | 188 ++++++++++++- .../visibility_networks/visibility_network.dm | 8 +- code/game/machinery/camera/camera.dm | 250 +++++++++++------- code/game/machinery/camera/camera_assembly.dm | 16 +- code/game/machinery/camera/motion.dm | 10 +- code/game/machinery/camera/presets.dm | 25 +- code/game/machinery/camera/tracking.dm | 192 +++++++------- code/game/machinery/computer/camera.dm | 56 ++-- code/modules/computer3/computers/camera.dm | 2 +- code/modules/mob/living/silicon/ai/ai.dm | 56 +++- .../mob/living/silicon/ai/freelook/eye.dm | 8 + .../silicon/ai/freelook/update_triggers.dm | 9 +- code/modules/mob/mob_helpers.dm | 8 + code/modules/nano/JSON Writer.dm | 4 +- code/modules/nano/_JSON.dm | 4 +- code/modules/nano/nanoui.dm | 18 +- code/modules/organs/organ_internal.dm | 3 +- code/setup.dm | 10 +- nano/templates/sec_camera.tmpl | 2 +- nano/templates/sec_camera_map_content.tmpl | 2 +- 20 files changed, 629 insertions(+), 242 deletions(-) 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}}