mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-02-07 23:00:09 +00:00
'chu heard me. S'what it does. When you're on a call, there's a "Start Video" button next to each person. When you click it, you actually look through their thing, like a ghost might if they ghostcalled. You have to stay within 1 tile of a video showing communicator to see it. Multiple people can share one communicator video stream by setting it on a table or whatever, and examining it. There's a message like "It appears to be showing a video: [view]" and you can click view and you can all look at the video. COLLABORATION. SYNERGY. OTHER BUZZWORDS. I also added the feature of DECLINING requests from people. Also communicators show up on a special camera list on security consoles, but the same EPv2 network visibility turns this off as well if you wanna be all hidey. This does mean that people with the visiblity on serve as sort of roaming AI cameras for the AI as well. So the AI can watch you repair the outside of the station or whatever if you want.
475 lines
14 KiB
Plaintext
475 lines
14 KiB
Plaintext
/obj/machinery/camera
|
|
name = "security camera"
|
|
desc = "It's used to monitor rooms."
|
|
icon = 'icons/obj/monitors.dmi'
|
|
icon_state = "camera"
|
|
use_power = 2
|
|
idle_power_usage = 5
|
|
active_power_usage = 10
|
|
layer = 5
|
|
|
|
var/list/network = list(NETWORK_EXODUS)
|
|
var/c_tag = null
|
|
var/c_tag_order = 999
|
|
var/status = 1
|
|
anchored = 1.0
|
|
var/invuln = null
|
|
var/bugged = 0
|
|
var/obj/item/weapon/camera_assembly/assembly = null
|
|
|
|
var/toughness = 5 //sorta fragile
|
|
|
|
// WIRES
|
|
var/datum/wires/camera/wires = null // Wires datum
|
|
|
|
//OTHER
|
|
|
|
var/view_range = 7
|
|
var/short_range = 2
|
|
|
|
var/light_disabled = 0
|
|
var/alarm_on = 0
|
|
var/busy = 0
|
|
|
|
var/on_open_network = 0
|
|
|
|
var/affected_by_emp_until = 0
|
|
|
|
var/client_huds = list()
|
|
|
|
/obj/machinery/camera/New()
|
|
wires = new(src)
|
|
assembly = new(src)
|
|
assembly.state = 4
|
|
client_huds |= global_hud.whitense
|
|
|
|
/* // Use this to look for cameras that have the same c_tag.
|
|
for(var/obj/machinery/camera/C in cameranet.cameras)
|
|
var/list/tempnetwork = C.network&src.network
|
|
if(C != src && C.c_tag == src.c_tag && tempnetwork.len)
|
|
world.log << "[src.c_tag] [src.x] [src.y] [src.z] conflicts with [C.c_tag] [C.x] [C.y] [C.z]"
|
|
*/
|
|
if(!src.network || src.network.len < 1)
|
|
if(loc)
|
|
error("[src.name] in [get_area(src)] (x:[src.x] y:[src.y] z:[src.z] has errored. [src.network?"Empty network list":"Null network list"]")
|
|
else
|
|
error("[src.name] in [get_area(src)]has errored. [src.network?"Empty network list":"Null network list"]")
|
|
ASSERT(src.network)
|
|
ASSERT(src.network.len > 0)
|
|
..()
|
|
|
|
/obj/machinery/camera/Destroy()
|
|
deactivate(null, 0) //kick anyone viewing out
|
|
if(assembly)
|
|
qdel(assembly)
|
|
assembly = null
|
|
qdel(wires)
|
|
wires = null
|
|
return ..()
|
|
|
|
/obj/machinery/camera/process()
|
|
if((stat & EMPED) && world.time >= affected_by_emp_until)
|
|
stat &= ~EMPED
|
|
cancelCameraAlarm()
|
|
update_icon()
|
|
update_coverage()
|
|
return internal_process()
|
|
|
|
/obj/machinery/camera/proc/internal_process()
|
|
return
|
|
|
|
/obj/machinery/camera/emp_act(severity)
|
|
if(!isEmpProof() && prob(100/severity))
|
|
if(!affected_by_emp_until || (world.time < affected_by_emp_until))
|
|
affected_by_emp_until = max(affected_by_emp_until, world.time + (90 SECONDS / severity))
|
|
else
|
|
stat |= EMPED
|
|
set_light(0)
|
|
triggerCameraAlarm()
|
|
kick_viewers()
|
|
update_icon()
|
|
update_coverage()
|
|
processing_objects |= src
|
|
|
|
/obj/machinery/camera/bullet_act(var/obj/item/projectile/P)
|
|
take_damage(P.get_structure_damage())
|
|
|
|
/obj/machinery/camera/ex_act(severity)
|
|
if(src.invuln)
|
|
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/hitby(AM as mob|obj)
|
|
..()
|
|
if (istype(AM, /obj))
|
|
var/obj/O = AM
|
|
if (O.throwforce >= src.toughness)
|
|
visible_message("<span class='warning'><B>[src] was hit by [O].</B></span>")
|
|
take_damage(O.throwforce)
|
|
|
|
/obj/machinery/camera/proc/setViewRange(var/num = 7)
|
|
src.view_range = num
|
|
cameranet.updateVisibility(src, 0)
|
|
|
|
/obj/machinery/camera/attack_hand(mob/living/carbon/human/user as mob)
|
|
if(!istype(user))
|
|
return
|
|
|
|
if(user.species.can_shred(user))
|
|
set_status(0)
|
|
user.do_attack_animation(src)
|
|
visible_message("<span class='warning'>\The [user] slashes at [src]!</span>")
|
|
playsound(src.loc, 'sound/weapons/slash.ogg', 100, 1)
|
|
add_hiddenprint(user)
|
|
destroy()
|
|
|
|
/obj/machinery/camera/attackby(obj/item/W as obj, mob/living/user as mob)
|
|
update_coverage()
|
|
// DECONSTRUCTION
|
|
if(isscrewdriver(W))
|
|
//user << "<span class='notice'>You start to [panel_open ? "close" : "open"] the camera's panel.</span>"
|
|
//if(toggle_panel(user)) // No delay because no one likes screwdrivers trying to be hip and have a duration cooldown
|
|
panel_open = !panel_open
|
|
user.visible_message("<span class='warning'>[user] screws the camera's panel [panel_open ? "open" : "closed"]!</span>",
|
|
"<span class='notice'>You screw the camera's panel [panel_open ? "open" : "closed"].</span>")
|
|
playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
|
|
|
|
else if((iswirecutter(W) || ismultitool(W)) && panel_open)
|
|
interact(user)
|
|
|
|
else if(iswelder(W) && (wires.CanDeconstruct() || (stat & BROKEN)))
|
|
if(weld(W, user))
|
|
if(assembly)
|
|
assembly.loc = src.loc
|
|
assembly.anchored = 1
|
|
assembly.camera_name = c_tag
|
|
assembly.camera_network = english_list(network, NETWORK_EXODUS, ",", ",")
|
|
assembly.update_icon()
|
|
assembly.dir = src.dir
|
|
if(stat & BROKEN)
|
|
assembly.state = 2
|
|
user << "<span class='notice'>You repaired \the [src] frame.</span>"
|
|
else
|
|
assembly.state = 1
|
|
user << "<span class='notice'>You cut \the [src] free from the wall.</span>"
|
|
new /obj/item/stack/cable_coil(src.loc, length=2)
|
|
assembly = null //so qdel doesn't eat it.
|
|
qdel(src)
|
|
|
|
// OTHER
|
|
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
|
|
|
|
var/itemname = ""
|
|
var/info = ""
|
|
if(istype(W, /obj/item/weapon/paper))
|
|
X = W
|
|
itemname = X.name
|
|
info = X.info
|
|
else
|
|
P = W
|
|
itemname = P.name
|
|
info = P.notehtml
|
|
U << "You hold \a [itemname] up to the camera ..."
|
|
for(var/mob/living/silicon/ai/O in living_mob_list)
|
|
if(!O.client) continue
|
|
if(U.name == "Unknown") O << "<b>[U]</b> holds \a [itemname] up to one of your cameras ..."
|
|
else O << "<b><a href='byond://?src=\ref[O];track2=\ref[O];track=\ref[U];trackname=[U.name]'>[U]</a></b> holds \a [itemname] up to one of your cameras ..."
|
|
O << browse(text("<HTML><HEAD><TITLE>[]</TITLE></HEAD><BODY><TT>[]</TT></BODY></HTML>", itemname, info), text("window=[]", itemname))
|
|
for(var/mob/O in player_list)
|
|
if (istype(O.machine, /obj/machinery/computer/security))
|
|
var/obj/machinery/computer/security/S = O.machine
|
|
if (S.current_camera == src)
|
|
O << "[U] holds \a [itemname] up to one of the cameras ..."
|
|
O << browse(text("<HTML><HEAD><TITLE>[]</TITLE></HEAD><BODY><TT>[]</TT></BODY></HTML>", itemname, info), text("window=[]", itemname))
|
|
|
|
else if (istype(W, /obj/item/weapon/camera_bug))
|
|
if (!src.can_use())
|
|
user << "<span class='warning'>Camera non-functional.</span>"
|
|
return
|
|
if (src.bugged)
|
|
user << "<span class='notice'>Camera bug removed.</span>"
|
|
src.bugged = 0
|
|
else
|
|
user << "<span class='notice'>Camera bugged.</span>"
|
|
src.bugged = 1
|
|
|
|
else if(W.damtype == BRUTE || W.damtype == BURN) //bashing cameras
|
|
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
|
|
if (W.force >= src.toughness)
|
|
user.do_attack_animation(src)
|
|
visible_message("<span class='warning'><b>[src] has been [pick(W.attack_verb)] with [W] by [user]!</b></span>")
|
|
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
|
|
..()
|
|
|
|
/obj/machinery/camera/proc/deactivate(user as mob, var/choice = 1)
|
|
// The only way for AI to reactivate cameras are malf abilities, this gives them different messages.
|
|
if(istype(user, /mob/living/silicon/ai))
|
|
user = null
|
|
|
|
if(choice != 1)
|
|
//legacy support, if choice is != 1 then just kick viewers without changing status
|
|
kick_viewers()
|
|
else
|
|
set_status(!src.status)
|
|
if (!(src.status))
|
|
if(user)
|
|
visible_message("<span class='notice'> [user] has deactivated [src]!</span>")
|
|
else
|
|
visible_message("<span class='notice'> [src] clicks and shuts down. </span>")
|
|
playsound(src.loc, 'sound/items/Wirecutter.ogg', 100, 1)
|
|
icon_state = "[initial(icon_state)]1"
|
|
add_hiddenprint(user)
|
|
else
|
|
if(user)
|
|
visible_message("<span class='notice'> [user] has reactivated [src]!</span>")
|
|
else
|
|
visible_message("<span class='notice'> [src] clicks and reactivates itself. </span>")
|
|
playsound(src.loc, 'sound/items/Wirecutter.ogg', 100, 1)
|
|
icon_state = initial(icon_state)
|
|
add_hiddenprint(user)
|
|
|
|
/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()
|
|
stat |= BROKEN
|
|
wires.RandomCutAll()
|
|
|
|
kick_viewers()
|
|
triggerCameraAlarm()
|
|
update_icon()
|
|
update_coverage()
|
|
|
|
//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
|
|
update_coverage()
|
|
// 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()
|
|
|
|
/obj/machinery/camera/check_eye(mob/user)
|
|
if(!can_use()) return -1
|
|
if(isXRay()) return SEE_TURFS|SEE_MOBS|SEE_OBJS
|
|
return 0
|
|
|
|
//This might be redundant, because of check_eye()
|
|
/obj/machinery/camera/proc/kick_viewers()
|
|
for(var/mob/O in player_list)
|
|
if (istype(O.machine, /obj/machinery/computer/security))
|
|
var/obj/machinery/computer/security/S = O.machine
|
|
if (S.current_camera == 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(var/duration = 0)
|
|
alarm_on = 1
|
|
camera_alarm.triggerAlarm(loc, src, duration)
|
|
|
|
/obj/machinery/camera/proc/cancelCameraAlarm()
|
|
if(wires.IsIndexCut(CAMERA_WIRE_ALARM))
|
|
return
|
|
|
|
alarm_on = 0
|
|
camera_alarm.clearAlarm(loc, 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|BROKEN))
|
|
return 0
|
|
return 1
|
|
|
|
/obj/machinery/camera/proc/can_see()
|
|
var/list/see = null
|
|
var/turf/pos = get_turf(src)
|
|
if(!pos)
|
|
return list()
|
|
|
|
if(isXRay())
|
|
see = range(view_range, pos)
|
|
else
|
|
see = hear(view_range, pos)
|
|
return see
|
|
|
|
/atom/proc/auto_turn()
|
|
//Automatically turns based on nearby walls.
|
|
var/turf/simulated/wall/T = null
|
|
for(var/i = 1, i <= 8; i += i)
|
|
T = get_ranged_target_turf(src, i, 1)
|
|
if(istype(T))
|
|
//If someone knows a better way to do this, let me know. -Giacom
|
|
switch(i)
|
|
if(NORTH)
|
|
src.set_dir(SOUTH)
|
|
if(SOUTH)
|
|
src.set_dir(NORTH)
|
|
if(WEST)
|
|
src.set_dir(EAST)
|
|
if(EAST)
|
|
src.set_dir(WEST)
|
|
break
|
|
|
|
//Return a working camera that can see a given mob
|
|
//or null if none
|
|
/proc/seen_by_camera(var/mob/M)
|
|
for(var/obj/machinery/camera/C in oview(4, M))
|
|
if(C.can_use()) // check if camera disabled
|
|
return C
|
|
break
|
|
return null
|
|
|
|
/proc/near_range_camera(var/mob/M)
|
|
|
|
for(var/obj/machinery/camera/C in range(4, M))
|
|
if(C.can_use()) // check if camera disabled
|
|
return C
|
|
break
|
|
|
|
return null
|
|
|
|
/obj/machinery/camera/proc/weld(var/obj/item/weapon/weldingtool/WT, var/mob/user)
|
|
|
|
if(busy)
|
|
return 0
|
|
if(!WT.isOn())
|
|
return 0
|
|
|
|
// Do after stuff here
|
|
user << "<span class='notice'>You start to weld the [src]..</span>"
|
|
playsound(src.loc, 'sound/items/Welder.ogg', 50, 1)
|
|
WT.eyecheck(user)
|
|
busy = 1
|
|
if(do_after(user, 100))
|
|
busy = 0
|
|
if(!WT.isOn())
|
|
return 0
|
|
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 << "<span class='warning'>\The [src] is broken.</span>"
|
|
return
|
|
|
|
user.set_machine(src)
|
|
wires.Interact(user)
|
|
|
|
/obj/machinery/camera/proc/add_network(var/network_name)
|
|
add_networks(list(network_name))
|
|
|
|
/obj/machinery/camera/proc/remove_network(var/network_name)
|
|
remove_networks(list(network_name))
|
|
|
|
/obj/machinery/camera/proc/add_networks(var/list/networks)
|
|
var/network_added
|
|
network_added = 0
|
|
for(var/network_name in networks)
|
|
if(!(network_name in src.network))
|
|
network += network_name
|
|
network_added = 1
|
|
|
|
if(network_added)
|
|
update_coverage(1)
|
|
|
|
/obj/machinery/camera/proc/remove_networks(var/list/networks)
|
|
var/network_removed
|
|
network_removed = 0
|
|
for(var/network_name in networks)
|
|
if(network_name in src.network)
|
|
network -= network_name
|
|
network_removed = 1
|
|
|
|
if(network_removed)
|
|
update_coverage(1)
|
|
|
|
/obj/machinery/camera/proc/replace_networks(var/list/networks)
|
|
if(networks.len != network.len)
|
|
network = networks
|
|
update_coverage(1)
|
|
return
|
|
|
|
for(var/new_network in networks)
|
|
if(!(new_network in network))
|
|
network = networks
|
|
update_coverage(1)
|
|
return
|
|
|
|
/obj/machinery/camera/proc/clear_all_networks()
|
|
if(network.len)
|
|
network.Cut()
|
|
update_coverage(1)
|
|
|
|
/obj/machinery/camera/proc/nano_structure()
|
|
var/cam[0]
|
|
cam["name"] = sanitize(c_tag)
|
|
cam["deact"] = !can_use()
|
|
cam["camera"] = "\ref[src]"
|
|
cam["x"] = x
|
|
cam["y"] = y
|
|
cam["z"] = z
|
|
return cam
|
|
|
|
/obj/machinery/camera/proc/update_coverage(var/network_change = 0)
|
|
if(network_change)
|
|
var/list/open_networks = difflist(network, restricted_camera_networks)
|
|
// Add or remove camera from the camera net as necessary
|
|
if(on_open_network && !open_networks.len)
|
|
cameranet.removeCamera(src)
|
|
else if(!on_open_network && open_networks.len)
|
|
on_open_network = 1
|
|
cameranet.addCamera(src)
|
|
else
|
|
cameranet.updateVisibility(src, 0)
|
|
|
|
invalidateCameraCache()
|
|
|
|
// Resets the camera's wires to fully operational state. Used by one of Malfunction abilities.
|
|
/obj/machinery/camera/proc/reset_wires()
|
|
if(!wires)
|
|
return
|
|
if (stat & BROKEN) // Fix the camera
|
|
stat &= ~BROKEN
|
|
wires.CutAll()
|
|
wires.MendAll()
|
|
update_icon()
|
|
update_coverage()
|