mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-15 20:52:41 +00:00
Links many map-specific details such as the station name, z-level information, and allowed jobs from global vars to map datum vars, which should help us maintain multiple maps at once in the future, which will be needed for the future Southern Cross. Note that a config change will be needed to change GENERATE_ASTEROID to GENERATE_MAP, otherwise no changes should be required to continue normal map usage. To change to a different map, it's suggested to tick the file that ticks all the other needed files, which for the Northern Star is called northern_star.dm.
476 lines
14 KiB
Plaintext
476 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_DEFAULT)
|
|
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)
|
|
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
|
|
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_DEFAULT, ",", ",")
|
|
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()
|