Files
Skyrat-tg/code/game/machinery/camera/camera.dm
T
san7890andGitHub ccef887efe Lints Against Unmanaged Local Defines (#74333)
# MAINTAINER - USE THE BUTTON THAT SAYS "MERGE MASTER" THEN SET THE PR
TO AUTO-MERGE! IT'S MUCH EASIER FOR ME TO FIX THINGS BEFORE THEY SKEW
RATHER THAN AFTER THE FACT.

## About The Pull Request

Hey there,

This took a while to do, but here's the gist:

Python file now regexes every file in `/code` except for those that have
some valid reason to be tacking on more global defines. Some of those
reasons are simply just that I don't have the time right now (doing what
you see in this PR took a few hours) to refactor and parse what should
belong and what should be thrown out. For the time being though, this PR
will at least _halt_ people making the mistake of not `#undef`ing any
files they `#define` "locally", or within the scope of a file.

Most people forget to do this and this leads to a lot of mess later on
due to how many variables can be unmanaged on the global level. I've
made this mistake, you've made this mistake, it's a common thing. Let's
automatically check for it so it can be fixed no-stress.

Scenarios this PR corrects:

* Forgetting to undef a define but undeffing others.
* Not undeffing any defines in your file.
* Earmarking a define as a "file local" define, but not defining it.
* Having a define be a "file local" define, but having it be used
elsewhere.
* Having a "local" define not even be in the file that it only shows up
in.
* Having a completely unused define*

(* I kept some of these because they seemed important... Others were
junked.)
## Why It's Good For The Game

If you wanna use it across multiple files, no reason to not make it a
global define (maybe there's a few reasons but let's assume that this is
the 95% case).

Let me know if you don't like how I re-arranged some of the defines and
how you'd rather see it be implemented, and I'd be happy to do that.
This was mostly just "eh does it need it or not" sorta stuff.

I used a pretty cool way to detect if we should use the standardized
GitHub "error" output, you can see the results of that here
https://github.com/san7890/bruhstation/actions/runs/4549766579/jobs/8022186846#step:7:792
## Changelog
Nothing that really concerns players.

(I fixed up all this stuff using vscode, no regexes beyond what you see
in the python script. sorry downstreams)
2023-03-29 10:17:03 -07:00

588 lines
20 KiB
Plaintext

/obj/machinery/camera
name = "security camera"
desc = "It's used to monitor rooms."
icon = 'icons/obj/machines/camera.dmi'
icon_state = "camera" //mapping icon to represent upgrade states. if you want a different base icon, update default_camera_icon as well as this.
use_power = ACTIVE_POWER_USE
active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.02
layer = WALL_OBJ_LAYER
plane = GAME_PLANE_UPPER
resistance_flags = FIRE_PROOF
damage_deflection = 12
armor_type = /datum/armor/machinery_camera
max_integrity = 100
integrity_failure = 0.5
var/default_camera_icon = "camera" //the camera's base icon used by update_appearance - icon_state is primarily used for mapping display purposes.
var/list/network = list("ss13")
var/c_tag = null
var/status = TRUE
var/start_active = FALSE //If it ignores the random chance to start broken on round start
var/invuln = null
var/obj/item/camera_bug/bug = null
var/datum/weakref/assembly_ref = null
var/area/myarea = null
//OTHER
var/view_range = 7
var/short_range = 2
var/alarm_on = FALSE
var/busy = FALSE
var/emped = FALSE //Number of consecutive EMP's on this camera
var/in_use_lights = 0
// Upgrades bitflag
var/upgrades = 0
var/internal_light = TRUE //Whether it can light up when an AI views it
///Represents a signel source of camera alarms about movement or camera tampering
var/datum/alarm_handler/alarm_manager
///Proximity monitor associated with this atom, for motion sensitive cameras.
var/datum/proximity_monitor/proximity_monitor
/// A copy of the last paper object that was shown to this camera.
var/obj/item/paper/last_shown_paper
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera, 0)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/autoname, 0)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/emp_proof, 0)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/motion, 0)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/xray, 0)
/datum/armor/machinery_camera
melee = 50
bullet = 20
laser = 20
energy = 20
fire = 90
acid = 50
/obj/machinery/camera/preset/ordnance //Bomb test site in space
name = "Hardened Bomb-Test Camera"
desc = "A specially-reinforced camera with a long lasting battery, used to monitor the bomb testing site. An external light is attached to the top."
c_tag = "Bomb Testing Site"
network = list("rd","ordnance")
use_power = NO_POWER_USE //Test site is an unpowered area
invuln = TRUE
light_range = 10
start_active = TRUE
/obj/machinery/camera/Initialize(mapload, obj/structure/camera_assembly/old_assembly)
. = ..()
for(var/i in network)
network -= i
network += lowertext(i)
var/obj/structure/camera_assembly/assembly
if(old_assembly) //check to see if the camera assembly was upgraded at all.
assembly = old_assembly
assembly_ref = WEAKREF(assembly) //important to do this now since upgrades call back to the assembly_ref
if(assembly.xray_module)
upgradeXRay()
else if(assembly.malf_xray_firmware_present) //if it was secretly upgraded via the MALF AI Upgrade Camera Network ability
upgradeXRay(TRUE)
if(assembly.emp_module)
upgradeEmpProof()
else if(assembly.malf_xray_firmware_present) //if it was secretly upgraded via the MALF AI Upgrade Camera Network ability
upgradeEmpProof(TRUE)
if(assembly.proxy_module)
upgradeMotion()
else
assembly = new(src)
assembly.state = 4 //STATE_FINISHED
assembly_ref = WEAKREF(assembly)
GLOB.cameranet.cameras += src
GLOB.cameranet.addCamera(src)
myarea = get_room_area()
LAZYADD(myarea.cameras, src)
if(mapload && is_station_level(z) && prob(3) && !start_active)
toggle_cam()
else //this is handled by toggle_camera, so no need to update it twice.
update_appearance()
alarm_manager = new(src)
/obj/machinery/camera/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
for(var/i in network)
network -= i
network += "[port.shuttle_id]_[i]"
/obj/machinery/camera/proc/create_prox_monitor()
if(!proximity_monitor)
proximity_monitor = new(src, 1)
/obj/machinery/camera/proc/set_area_motion(area/A)
area_motion = A
create_prox_monitor()
/obj/machinery/camera/Destroy()
if(can_use())
toggle_cam(null, 0) //kick anyone viewing out and remove from the camera chunks
GLOB.cameranet.removeCamera(src)
GLOB.cameranet.cameras -= src
cancelCameraAlarm()
if(isarea(myarea))
LAZYREMOVE(myarea.cameras, src)
QDEL_NULL(alarm_manager)
QDEL_NULL(assembly_ref)
if(bug)
bug.bugged_cameras -= c_tag
if(bug.current == src)
bug.current = null
bug = null
QDEL_NULL(last_shown_paper)
return ..()
/obj/machinery/camera/examine(mob/user)
. = ..()
if(isEmpProof(TRUE)) //don't reveal it's upgraded if was done via MALF AI Upgrade Camera Network ability
. += "It has electromagnetic interference shielding installed."
else
. += span_info("It can be shielded against electromagnetic interference with some <b>plasma</b>.")
if(isXRay(TRUE)) //don't reveal it's upgraded if was done via MALF AI Upgrade Camera Network ability
. += "It has an X-ray photodiode installed."
else
. += span_info("It can be upgraded with an X-ray photodiode with an <b>analyzer</b>.")
if(isMotion())
. += "It has a proximity sensor installed."
else
. += span_info("It can be upgraded with a <b>proximity sensor</b>.")
if(!status)
. += span_info("It's currently deactivated.")
if(!panel_open && powered())
. += span_notice("You'll need to open its maintenance panel with a <b>screwdriver</b> to turn it back on.")
if(panel_open)
. += span_info("Its maintenance panel is currently open.")
if(!status && powered())
. += span_info("It can reactivated with <b>wirecutters</b>.")
/obj/machinery/camera/emp_act(severity)
. = ..()
if(!status)
return
if(!(. & EMP_PROTECT_SELF))
if(prob(150/severity))
update_appearance()
network = list()
GLOB.cameranet.removeCamera(src)
set_machine_stat(machine_stat | EMPED)
set_light(0)
emped = emped+1 //Increase the number of consecutive EMP's
update_appearance()
addtimer(CALLBACK(src, PROC_REF(post_emp_reset), emped, network), 90 SECONDS)
for(var/i in GLOB.player_list)
var/mob/M = i
if (M.client?.eye == src)
M.unset_machine()
M.reset_perspective(null)
to_chat(M, span_warning("The screen bursts into static!"))
/obj/machinery/camera/proc/post_emp_reset(thisemp, previous_network)
if(QDELETED(src))
return
triggerCameraAlarm() //camera alarm triggers even if multiple EMPs are in effect.
if(emped != thisemp) //Only fix it if the camera hasn't been EMP'd again
return
network = previous_network
set_machine_stat(machine_stat & ~EMPED)
update_appearance()
if(can_use())
GLOB.cameranet.addCamera(src)
emped = 0 //Resets the consecutive EMP count
addtimer(CALLBACK(src, PROC_REF(cancelCameraAlarm)), 100)
/obj/machinery/camera/ex_act(severity, target)
if(invuln)
return FALSE
return ..()
/obj/machinery/camera/proc/setViewRange(num = 7)
src.view_range = num
GLOB.cameranet.updateVisibility(src, 0)
/obj/machinery/camera/proc/shock(mob/living/user)
if(!istype(user))
return
user.electrocute_act(10, src)
/obj/machinery/camera/singularity_pull(S, current_size)
if (status && current_size >= STAGE_FIVE) // If the singulo is strong enough to pull anchored objects and the camera is still active, turn off the camera as it gets ripped off the wall.
toggle_cam(null, 0)
..()
// Construction/Deconstruction
/obj/machinery/camera/screwdriver_act(mob/living/user, obj/item/I)
if(..())
return TRUE
toggle_panel_open()
to_chat(user, span_notice("You screw the camera's panel [panel_open ? "open" : "closed"]."))
I.play_tool_sound(src)
update_appearance()
return TRUE
/obj/machinery/camera/crowbar_act(mob/living/user, obj/item/I)
. = ..()
if(!panel_open)
return
var/obj/structure/camera_assembly/assembly = assembly_ref?.resolve()
if(!assembly)
assembly_ref = null
return
var/list/droppable_parts = list()
if(assembly.xray_module)
droppable_parts += assembly.xray_module
if(assembly.emp_module)
droppable_parts += assembly.emp_module
if(assembly.proxy_module)
droppable_parts += assembly.proxy_module
if(!length(droppable_parts))
return
var/obj/item/choice = tgui_input_list(user, "Select a part to remove", "Part Removal", sort_names(droppable_parts))
if(isnull(choice))
return
if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
return
to_chat(user, span_notice("You remove [choice] from [src]."))
if(choice == assembly.xray_module)
assembly.drop_upgrade(assembly.xray_module)
removeXRay()
if(choice == assembly.emp_module)
assembly.drop_upgrade(assembly.emp_module)
removeEmpProof()
if(choice == assembly.proxy_module)
assembly.drop_upgrade(assembly.proxy_module)
removeMotion()
I.play_tool_sound(src)
return TRUE
/obj/machinery/camera/wirecutter_act(mob/living/user, obj/item/I)
. = ..()
if(!panel_open)
return
toggle_cam(user, 1)
atom_integrity = max_integrity //this is a pretty simplistic way to heal the camera, but there's no reason for this to be complex.
set_machine_stat(machine_stat & ~BROKEN)
I.play_tool_sound(src)
return TRUE
/obj/machinery/camera/multitool_act(mob/living/user, obj/item/I)
. = ..()
if(!panel_open)
return
setViewRange((view_range == initial(view_range)) ? short_range : initial(view_range))
to_chat(user, span_notice("You [(view_range == initial(view_range)) ? "restore" : "mess up"] the camera's focus."))
return TRUE
/obj/machinery/camera/welder_act(mob/living/user, obj/item/I)
. = ..()
if(!panel_open)
return
if(!I.tool_start_check(user, amount=0))
return TRUE
to_chat(user, span_notice("You start to weld [src]..."))
if(I.use_tool(src, user, 100, volume=50))
user.visible_message(span_warning("[user] unwelds [src], leaving it as just a frame bolted to the wall."),
span_warning("You unweld [src], leaving it as just a frame bolted to the wall"))
deconstruct(TRUE)
return TRUE
/obj/machinery/camera/attackby(obj/item/attacking_item, mob/living/user, params)
// UPGRADES
if(panel_open)
var/obj/structure/camera_assembly/assembly = assembly_ref?.resolve()
if(!assembly)
assembly_ref = null
if(attacking_item.tool_behaviour == TOOL_ANALYZER)
if(!isXRay(TRUE)) //don't reveal it was already upgraded if was done via MALF AI Upgrade Camera Network ability
if(!user.temporarilyRemoveItemFromInventory(attacking_item))
return
upgradeXRay(FALSE, TRUE)
to_chat(user, span_notice("You attach [attacking_item] into [assembly]'s inner circuits."))
qdel(attacking_item)
else
to_chat(user, span_warning("[src] already has that upgrade!"))
return
else if(istype(attacking_item, /obj/item/stack/sheet/mineral/plasma))
if(!isEmpProof(TRUE)) //don't reveal it was already upgraded if was done via MALF AI Upgrade Camera Network ability
if(attacking_item.use_tool(src, user, 0, amount=1))
upgradeEmpProof(FALSE, TRUE)
to_chat(user, span_notice("You attach [attacking_item] into [assembly]'s inner circuits."))
else
to_chat(user, span_warning("[src] already has that upgrade!"))
return
else if(isprox(attacking_item))
if(!isMotion())
if(!user.temporarilyRemoveItemFromInventory(attacking_item))
return
upgradeMotion()
to_chat(user, span_notice("You attach [attacking_item] into [assembly]'s inner circuits."))
qdel(attacking_item)
else
to_chat(user, span_warning("[src] already has that upgrade!"))
return
// OTHER
if(istype(attacking_item, /obj/item/modular_computer/pda))
var/itemname = ""
var/info = ""
var/obj/item/modular_computer/computer = attacking_item
for(var/datum/computer_file/program/notepad/notepad_app in computer.stored_files)
info = notepad_app.written_note
break
itemname = computer.name
itemname = sanitize(itemname)
info = sanitize(info)
to_chat(user, span_notice("You hold \the [itemname] up to the camera..."))
user.log_talk(itemname, LOG_GAME, log_globally=TRUE, tag="Pressed to camera")
user.changeNext_move(CLICK_CD_MELEE)
for(var/mob/potential_viewer as anything in GLOB.player_list)
if(isAI(potential_viewer))
var/mob/living/silicon/ai/ai = potential_viewer
if(ai.control_disabled || (ai.stat == DEAD))
continue
ai.log_talk(itemname, LOG_VICTIM, tag="Pressed to camera from [key_name(user)]", log_globally=FALSE)
ai.last_tablet_note_seen = "<HTML><HEAD><TITLE>[itemname]</TITLE></HEAD><BODY><TT>[info]</TT></BODY></HTML>"
if(user.name == "Unknown")
to_chat(ai, "[span_name(user)] holds <a href='?_src_=usr;show_tablet=1;'>\a [itemname]</a> up to one of your cameras ...")
else
to_chat(ai, "<b><a href='?src=[REF(ai)];track=[html_encode(user.name)]'>[user]</a></b> holds <a href='?_src_=usr;last_shown_paper=1;'>\a [itemname]</a> up to one of your cameras ...")
continue
if (potential_viewer.client?.eye == src)
to_chat(potential_viewer, "[span_name("[user]")] holds \a [itemname] up to one of the cameras ...")
potential_viewer.log_talk(itemname, LOG_VICTIM, tag="Pressed to camera from [key_name(user)]", log_globally=FALSE)
potential_viewer << browse(text("<HTML><HEAD><TITLE>[]</TITLE></HEAD><BODY><TT>[]</TT></BODY></HTML>", itemname, info), text("window=[]", itemname))
return
if(istype(attacking_item, /obj/item/paper))
// Grab the paper, sanitise the name as we're about to just throw it into chat wrapped in HTML tags.
var/obj/item/paper/paper = attacking_item
// Make a complete copy of the paper, store a ref to it locally on the camera.
last_shown_paper = paper.copy(paper.type, null);
// Then sanitise the name because we're putting it directly in chat later.
var/item_name = sanitize(last_shown_paper.name)
// Start the process of holding it up to the camera.
to_chat(user, span_notice("You hold \the [item_name] up to the camera..."))
user.log_talk(item_name, LOG_GAME, log_globally=TRUE, tag="Pressed to camera")
user.changeNext_move(CLICK_CD_MELEE)
// And make a weakref we can throw around to all potential viewers.
last_shown_paper.camera_holder = WEAKREF(src)
// Iterate over all living mobs and check if anyone is elibile to view the paper.
// This is backwards, but cameras don't store a list of people that are looking through them,
// and we'll have to iterate this list anyway so we can use it to pull out AIs too.
for(var/mob/potential_viewer in GLOB.player_list)
// All AIs view through cameras, so we need to check them regardless.
if(isAI(potential_viewer))
var/mob/living/silicon/ai/ai = potential_viewer
if(ai.control_disabled || (ai.stat == DEAD))
continue
ai.log_talk(item_name, LOG_VICTIM, tag="Pressed to camera from [key_name(user)]", log_globally=FALSE)
log_paper("[key_name(user)] held [last_shown_paper] up to [src], requesting [key_name(ai)] read it.")
if(user.name == "Unknown")
to_chat(ai, "[span_name(user.name)] holds <a href='?_src_=usr;show_paper_note=[REF(last_shown_paper)];'>\a [item_name]</a> up to one of your cameras ...")
else
to_chat(ai, "<b><a href='?src=[REF(ai)];track=[html_encode(user.name)]'>[user]</a></b> holds <a href='?_src_=usr;show_paper_note=[REF(last_shown_paper)];'>\a [item_name]</a> up to one of your cameras ...")
continue
// If it's not an AI, eye if the client's eye is set to the camera. I wonder if this even works anymore with tgui camera apps and stuff?
if (potential_viewer.client?.eye == src)
log_paper("[key_name(user)] held [last_shown_paper] up to [src], and [key_name(potential_viewer)] may read it.")
potential_viewer.log_talk(item_name, LOG_VICTIM, tag="Pressed to camera from [key_name(user)]", log_globally=FALSE)
to_chat(potential_viewer, "[span_name(user)] holds <a href='?_src_=usr;show_paper_note=[REF(last_shown_paper)];'>\a [item_name]</a> up to your camera...")
return
if(istype(attacking_item, /obj/item/camera_bug))
if(!can_use())
to_chat(user, span_notice("Camera non-functional."))
return
if(bug)
to_chat(user, span_notice("Camera bug removed."))
bug.bugged_cameras -= src.c_tag
bug = null
else
to_chat(user, span_notice("Camera bugged."))
bug = attacking_item
bug.bugged_cameras[src.c_tag] = WEAKREF(src)
return
return ..()
/obj/machinery/camera/run_atom_armor(damage_amount, damage_type, damage_flag = 0, attack_dir)
if(machine_stat & BROKEN)
return damage_amount
. = ..()
/obj/machinery/camera/atom_break(damage_flag)
if(!status)
return
. = ..()
if(.)
triggerCameraAlarm()
toggle_cam(null, 0)
/obj/machinery/camera/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
if(disassembled)
var/obj/structure/camera_assembly/assembly = assembly_ref?.resolve()
if(!assembly)
assembly = new()
assembly.forceMove(drop_location())
assembly.state = 1
assembly.setDir(dir)
assembly_ref = null
else
var/obj/item/I = new /obj/item/wallframe/camera (loc)
I.update_integrity(I.max_integrity * 0.5)
new /obj/item/stack/cable_coil(loc, 2)
qdel(src)
/obj/machinery/camera/update_icon_state() //TO-DO: Make panel open states, xray camera, and indicator lights overlays instead.
var/xray_module
if(isXRay(TRUE))
xray_module = "xray"
if(!status)
icon_state = "[xray_module][default_camera_icon]_off"
return ..()
if(machine_stat & EMPED)
icon_state = "[xray_module][default_camera_icon]_emp"
return ..()
icon_state = "[xray_module][default_camera_icon][in_use_lights ? "_in_use" : ""]"
return ..()
/obj/machinery/camera/proc/toggle_cam(mob/user, displaymessage = TRUE)
status = !status
if(can_use())
GLOB.cameranet.addCamera(src)
if (isturf(loc))
myarea = get_area(src)
LAZYADD(myarea.cameras, src)
else
myarea = null
else
set_light(0)
GLOB.cameranet.removeCamera(src)
if (isarea(myarea))
LAZYREMOVE(myarea.cameras, src)
// We are not guarenteed that the camera will be on a turf. account for that
var/turf/our_turf = get_turf(src)
GLOB.cameranet.updateChunk(our_turf.x, our_turf.y, our_turf.z)
var/change_msg = "deactivates"
if(status)
change_msg = "reactivates"
triggerCameraAlarm()
if(!QDELETED(src)) //We'll be doing it anyway in destroy
addtimer(CALLBACK(src, PROC_REF(cancelCameraAlarm)), 100)
if(displaymessage)
if(user)
visible_message(span_danger("[user] [change_msg] [src]!"))
add_hiddenprint(user)
else
visible_message(span_danger("\The [src] [change_msg]!"))
playsound(src, 'sound/items/wirecutter.ogg', 100, TRUE)
update_appearance() //update Initialize() if you remove this.
// 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?
for(var/mob/O in GLOB.player_list)
if (O.client?.eye == src)
O.unset_machine()
O.reset_perspective(null)
to_chat(O, span_warning("The screen bursts into static!"))
/obj/machinery/camera/proc/triggerCameraAlarm()
alarm_on = TRUE
alarm_manager.send_alarm(ALARM_CAMERA, src, src)
/obj/machinery/camera/proc/cancelCameraAlarm()
alarm_on = FALSE
alarm_manager.clear_alarm(ALARM_CAMERA)
/obj/machinery/camera/proc/can_use()
if(!status)
return FALSE
if(machine_stat & EMPED)
return FALSE
return TRUE
/obj/machinery/camera/proc/can_see()
var/list/see = null
var/turf/pos = get_turf(src)
var/check_lower = pos != get_lowest_turf(pos)
var/check_higher = pos != get_highest_turf(pos)
if(isXRay())
see = range(view_range, pos)
else
see = get_hear(view_range, pos)
if(check_lower || check_higher)
// Haha datum var access KILL ME
var/datum/controller/subsystem/mapping/local_mapping = SSmapping
for(var/turf/seen in see)
if(check_lower)
var/turf/visible = seen
while(visible && istransparentturf(visible))
var/turf/below = local_mapping.get_turf_below(visible)
for(var/turf/adjacent in range(1, below))
see += adjacent
see += adjacent.contents
visible = below
if(check_higher)
var/turf/above = local_mapping.get_turf_above(seen)
while(above && istransparentturf(above))
for(var/turf/adjacent in range(1, above))
see += adjacent
see += adjacent.contents
above = local_mapping.get_turf_above(above)
return see
/obj/machinery/camera/proc/Togglelight(on=0)
for(var/mob/living/silicon/ai/A in GLOB.ai_list)
for(var/obj/machinery/camera/cam in A.lit_cameras)
if(cam == src)
return
if(on)
set_light(AI_CAMERA_LUMINOSITY)
else
set_light(0)
/obj/machinery/camera/get_remote_view_fullscreens(mob/user)
if(view_range == short_range) //unfocused
user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 2)
/obj/machinery/camera/update_remote_sight(mob/living/user)
user.set_invis_see(SEE_INVISIBLE_LIVING) //can't see ghosts through cameras
if(isXRay())
user.add_sight(SEE_TURFS|SEE_MOBS|SEE_OBJS)
else
user.clear_sight(SEE_TURFS|SEE_MOBS|SEE_OBJS)
return 1